Root causes identified and fixed:
1. SPREAD_MAX 0.02 → 0.04 (biggest culprit — near-expiry books often have
0.03 spread, hard gate was blocking all valid entries)
2. tteMax 90s → 150s (direction is established by TTE=150s on 5m markets;
previous 90s window was too narrow, skipped the "trend building" phase)
3. Added momentum as scoring factor W_MOMENTUM=0.30 ("follow where odds
are moving" — midSlope6s from FeatureEngine now drives 30% of entry score)
4. Added SLOPE_CANCEL momentum gate (-0.0020): if dominant side's mid is
actively falling (reversal risk), block entry regardless of mid level.
New reason code: SIG_FADING_DOMINANT
5. Revised score weights: MID 45%→35%, IMBALANCE 35%→20%, SPREAD 20%→15%,
MOMENTUM 0%→30%
6. Score threshold 0.55 → 0.42 (now calibrated for 4-factor scoring)
7. minDominantMid default 0.60 → 0.58, tteMin 20 → 15
8. Spread gate now checks min(up.spread, down.spread) — dominant side only
needs to be tradeable, not both sides
Updated .env and .env.example defaults to match new parameters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a position's market expires, RedeemEngine automatically polls the
CTF contract and redeems the winning position on-chain — no manual redeem needed.
Flow:
1. expirePosition() queues the expired position into RedeemEngine
2. RedeemEngine polls every 30s (ONESHOT_REDEEM_POLL_MS)
3. Checks Gamma API first, then CTF.payoutDenominator() on-chain
4. When settled: emits redemption:complete event with final P&L
5. Orchestrator passes P&L to RiskEngine
DRY_RUN=true: simulates by reading on-chain payouts and logging win/loss
DRY_RUN=false: submits real redeemPositions() tx on Polygon (gasLimit 300k)
Also stores conditionId and negRisk in PositionEngine state so the
expired position has all data needed for redemption without extra lookups.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the per-market state machine check (sm.is(IDLE)) only blocked
re-entry on the same slug. A fresh market slot (different slug) would get
its own IDLE state machine and could trigger another entry while the previous
market's position was still being held.
Added posEngine.hasAnyPosition() global guard in onSignal so the engine holds
exactly one position at a time across all tracked markets.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous behaviour: enter any side with positive momentum, exit at TP (+1 tick),
cycle back to IDLE — causing rapid buy-sell-buy loops on low-probability tokens.
New strategy:
- Enter ONLY the side the market already prices as probable winner (mid >= MIN_DOMINANT_MID)
- Hold position to market expiry; on-chain redeemer settles at $1.00 win / $0.00 loss
- Emergency stop-loss only (absolute mid floor, e.g. 0.20) for catastrophic reversals
- One entry per market slot — no re-entry while POSITION_OPEN
Key changes:
- SignalEngine: detect dominant side (up.mid vs down.mid), require MIN_DOMINANT_MID
threshold, new scoring weights (mid 45% / imbalance 35% / spread 20%)
- PositionEngine: remove TP, slope-drop, time-reduce exits; add expired handler;
stop-loss is now an absolute mid floor instead of relative-to-entry ticks
- oneshot.js: expirePosition() clears state without submitting sell orders;
flattenPosition() only called for emergency stops; update cfg vars
- constants.js: add SIG_NO_DOMINANT, SIG_LOW_DOMINANT, EXIT_EXPIRED reason codes
- .env.example: replace ONESHOT_TP_TICKS with MIN_DOMINANT_MID, STOP_LOSS_MID,
TTE_MIN, TTE_MAX
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use /markets/slug/{slug} direct endpoint (not /markets?slug=...&limit=1)
- Extract token IDs from clobTokenIds field with JSON string parsing fallback
- Read tick size from market.orderPriceMinTickSize (no separate API call)
- Prioritise endDate (full datetime) over endDateIso (date-only) to fix false expiry
- Slot formula matches sniperDetector/mmDetector exactly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Gamma API returns two date fields:
endDateIso = "2026-02-24" ← date only, no time
endDate = "2026-02-24T06:35:00Z" ← correct close datetime
_parseEndTs() was preferring endDateIso, which parsed to midnight UTC
and was already in the past by the time any market opened during the day.
Both current and next-slot markets were therefore rejected as "already expired".
Fix: prioritise endDate (full ISO datetime) over endDateIso (date-only).
Co-Authored-By: direkturcrypto <direkturcrypto.x@mail3.me>
Adds a debug flag (ONESHOT_DEBUG=true / --debug / npm run oneshot-debug)
that surfaces the engine's internal decision process at every key step.
Debug output tags
─────────────────
[DBG:FEED] — Market discovery: every slug probed, API response status,
token IDs extracted, tick size fetched.
Throttled poll summary every 10 ticks per market showing
bid/ask/spread/mid/depth for both UP and DOWN sides.
[DBG:GATE] — Hard gate result every 5 evaluations per market:
TTE range, spread width, depth thinness, stale flag.
Shows exact gate fail reason or PASS confirmation.
[DBG:FEAT] — Feature breakdown for each side every 5 evals:
slope, imbalance, spread, retrace raw values plus
per-component scores and weighted total.
[DBG:SCORE] — Per-side qualify check: score vs threshold,
trend confirm flag, and QUALIFY / skip verdict.
[DBG:SIGNAL] — Always logged (no throttle) when an ENTER signal fires.
[DBG:HEART] — 5-second heartbeat: active markets, per-market SM state,
dailyPnl, consecLosses, cooldownLeft, halted flag.
New script
──────────
npm run oneshot-debug → DRY_RUN=true ONESHOT_DEBUG=true node src/oneshot.js
Co-Authored-By: direkturcrypto <direkturcrypto.x@mail3.me>
- Rename project to polymarket-terminal
- Add Market Maker bot (src/mm.js) with on-chain CTF split/merge/redeem via Gnosis Safe
- Add Orderbook Sniper bot (src/sniper.js) with multi-asset GTC low-price orders
- Add WebSocket watcher (src/services/wsWatcher.js) for real-time RTDS trade events
- Add terminal dashboard UI (src/ui/dashboard.js) using blessed
- Add CTF contract helpers (src/services/ctf.js) for splitPosition, mergePositions, redeemPositions
- Add mmDetector, mmExecutor, sniperDetector, sniperExecutor services
- Add simStats utility for dry-run P&L tracking
- Translate all Indonesian-language strings to professional English across all files
- Rewrite README.md in English with full setup guide, configuration reference, and architecture overview
- Rewrite AGENT.MD in English as comprehensive AI agent and developer reference
- Update package.json name, description, scripts, and keywords
Co-Authored-By: direkturcrypto <direkturcrypto.x@mail3.me>