Engineering

Foresight v5.0: How I Rebuilt a Prediction Market Bot Around Candle Boundaries

The bot was right. The timing was wrong. v4.x had a fundamental reactive architecture problem — by the time signals scored, the CLOB asks were too expensive. v5.0 solved it with event-driven candle boundaries and predictive early-window scoring.

March 4, 2026
9 min read
#polymarket#prediction-markets#trading-bots
Foresight v5.0: How I Rebuilt a Prediction Market Bot Around Candle Boundaries
Share

The bot was right about direction 91% of the time last week. It still left money on the table.

Not from bad predictions. Not from position sizing. From timing. The architecture was reactive — it waited for price to move before it could score a signal. By the time the move was large enough to register, the CLOB had already repriced. You were buying at 85 cents what should have been a 30-cent entry.

That's the v4.x problem in one sentence. Foresight v5.0 is the answer.

7-Day Win Rate
91.3%
100 trades · +$694.25 PnL · 62.7% ROI
30-Day Win Rate
65.9%
193 trades · +$650.65 PnL · 48.2% ROI

Those numbers are live. Real USDC. On-chain settlements on Polygon. The 30-day includes the entire v4.x era — every expensive entry, every signal that scored too late, every trade where the direction was right but the execution ate the edge. The 7-day is pure v5.0. The gap between them is the architectural story.


The Reactive Trap

Here's what v4.x actually did when a new candle opened.

The scanner polled Polymarket every 30 seconds looking for active markets. When it found one, PolyEdge — our 11-factor scoring engine — evaluated it. The single largest factor in PolyEdge is price_momentum, worth up to 35 points out of 100. It measures the velocity of the current candle's price movement.

See the problem? At candle open — t=0 — the current candle has no data. No delta. No velocity. The bar just started. price_momentum returns zero.

So PolyEdge waits. The scanner polls again at t=30s, t=60s. Eventually, 30 to 60 seconds into the candle, price has moved enough to register a score. Now price_momentum comes alive: 20, 25, 30 points. The total PolyEdge score crosses the STRONG threshold. The bot fires.

But the market moved too. The CLOB participants saw the same price action. By t=60s, the asks on directional tokens have repriced from 30-40 cents to 85-99 cents. You're buying at 85 cents to win 15 cents gross. After Polymarket's 10% taker fee, the trade is fee-negative.

Correct direction. Negative expected value.

This is the reactive trap. The scoring engine requires the same data that the market is already pricing. By the time the signal fires, you're buying the consensus, not the edge.

And there was a second problem compounding it. The InDecision framework — the directional oracle that powers Foresight's indecision_bias factor (worth up to 35 points) — has a spread gate at strategy.py:607. When the InDecision spread is below 8, it returns a flat component score of 6. That caps total scores at around 72-78 — MODERATE conviction. You need spread above 8 to even reach STRONG. In conservative mode, the bot requires MODERATE or higher (score >= 70), but the best entries — the ones where you want to be aggressive — were architecturally unreachable in the early window.

The result: the system's best predictions were locked behind a timing wall. Great signals, terrible timing, expensive entries.


The Name Change

The v5.0 rewrite earned the project a new name. It went from "Polymarket Bot" — descriptive, generic — to Foresight.

Foresight means the ability to predict future outcomes. The v4.x architecture couldn't do that — it was definitionally reactive, requiring current-candle data to score. v5.0 scores using prior-candle data before the new candle has any price action at all. That's literally foresight: reading what happened to predict what's next, before the market prices it.

The name isn't marketing. It's an architectural description.


Tier 1: Event-Driven Foundation (PR #95)

The first change was infrastructure. Before building a predictive scorer, we needed the system to actually detect candle boundaries in real time.

Scanner acceleration. The Polymarket scanner poll dropped from 30 seconds to 5 seconds. In v4.x, a 30-second poll cycle meant the bot could miss the first 30 seconds of a new candle entirely. At 5 seconds, the maximum detection latency for a new market window is 5 seconds. That's the difference between entering at 20 cents and entering at 50 cents.

TD Sequential fix. We discovered that the TD Sequential counter — one of our technical analysis signals — was counting the current (still-forming) bar as a closed bar. On a 5-minute timeframe, this means your sequential count is using incomplete data for the most recent entry. The fix: only count bars where the close timestamp is in the past. Sounds obvious. It took 3 hours of production debugging to find because the numbers were plausible either way.

Candle boundary detection. This is the core Tier 1 contribution. BinanceWSFeed — our WebSocket feed from Binance — now detects candle boundaries by comparing the current bar's open time against the previous bar's. When the open time advances, a new candle just started. That boundary event triggers two things immediately:

  1. IntraBiasFeed.request_refresh() — forces the InDecision intraday feed to re-score right now, using the just-closed candle's complete data. No waiting for the next 5-minute poll cycle.
  2. A full market re-evaluation cycle with the fresh InDecision scores.

The refresh matters because InDecision's IntraBiasFeed normally polls every 5 minutes. Without request_refresh(), there's a potential 5-minute lag between a candle closing and InDecision processing it. With the refresh, the lag is under 1 second.

INSIGHT

The candle boundary event is the hinge of the entire v5.0 architecture. Everything downstream — early window scoring, predictive signals, fast entries — depends on the system knowing the exact moment a new candle opens and having fresh InDecision scores within a second of that moment.


Tier 2: Early Window Predictive Mode (PR #96)

This is the architectural breakthrough.

A new module — src/early_window_scorer.py — implements a PredictiveWindowScore formula. Instead of waiting for the current candle to develop price action, it scores using signals from the prior closed candle.

Think about what the prior candle tells you. It closed with a specific RSI reading, a MACD cross direction, a Bollinger Band position, and a TD Sequential count. It has a complete InDecision conviction score. All of those signals are backward-looking from the candle's perspective but forward-looking from the market's perspective — they describe the conditions entering the new candle, before any price action occurs.

The early window scorer uses exactly those signals. No price_momentum dependency. No need for the current candle to develop. The moment the candle boundary fires, the scorer has everything it needs.

Activation logic: The early window path activates when two conditions are true:

  1. Time-to-close (ttc) exceeds the snipe window — meaning the reactive PolyEdge path wouldn't fire yet anyway
  2. Elapsed time since candle open is less than 90 seconds

This is the dual-path architecture:

Market Scanner (5s poll)
         |
         v
   Candle Boundary Event (BinanceWSFeed)
         |
         +---> IntraBiasFeed.request_refresh()
         |
         v
   Parallel Market Evaluation (asyncio.gather, 8 markets)
         |
         +--[ttc > snipe_window, elapsed < 90s]---> Path B: Early Window
         |                                          PredictiveWindowScore
         |                                          (prior-candle signals)
         |
         +--[ttc <= snipe_window]---> Path A: Snipe Window
                                      PolyEdge (11 factors) + Momentum
                                      (reactive, current price delta)

Path A is the original reactive path. It still works. When the snipe window opens (20 seconds before close on 5m, 45 seconds on 15m), PolyEdge fires with live price momentum data. This is the highest-conviction path because it has both historical signals AND current-candle confirmation.

Path B is the new predictive path. It fires early — within the first 90 seconds of a candle — using prior-candle data exclusively. The CLOB asks during this window are typically 10-40 cents because the market hasn't moved yet. There's no price action to price in. You're buying direction before the crowd confirms it.

Assets
9
BTC · ETH · SOL · XRP · DOGE · AVAX · LINK · MATIC · SPX
Live Slots
18
9 assets × 2 timeframes (5m + 15m)

The 90-second window isn't arbitrary. It's the empirical sweet spot. After 90 seconds, the current candle has enough price action that the reactive path's price_momentum factor starts carrying meaningful data. The two paths would begin scoring similar signals. Before 90 seconds, the predictive path has a monopoly on actionable information.


How PolyEdge and InDecision Fit Together

PolyEdge is the execution engine. InDecision is the directional oracle. They're separate systems that compose at evaluation time.

PolyEdge runs 11 factors, each contributing to a 0-100 composite score:

  • price_momentum (0-35 pts): current candle velocity — the factor that's blind at candle open
  • technical_analysis (0-30 pts): RSI, MACD, Bollinger Bands, TD Sequential
  • indecision_bias (0-35 pts): InDecision Engine directional feed
POLYEDGE EVALUATION PIPELINE
End-to-End Decision Architecture · 16 markets per loop
Market Scanner
Polymarket CLOB API · polls every loop
Active Markets
8 assets × 2 timeframes = 16 markets
Pre-flight Filter
skip window < 60s · startup grace 180s
InDecision Intraday
IntraCaseAggregator · 5m refresh
InDecision Daily
DualCaseAggregator · 4h refresh
TA Engine
RSI · MACD · Bollinger · Binance
Pattern Engine
formations · chart structure
Per-Market Eval
PolyEdge Score 0–100
Momentum Score 0–100
best score wins
STRONG ≥ 90
Execute · Kelly bet size
MODERATE ≥ 80
Execute · Kelly bet size
WEAK < 80
Skip · no edge

indecision_bias is the single largest potential contributor. It calls the InDecision engine running at 127.0.0.1:8081 via the IntraBiasFeed, which fires every 5 minutes per asset (or immediately on candle boundary in v5.0). When InDecision returns BEARISH with high conviction on BTC, that suppresses BUY signals regardless of what price momentum says. When it returns BULLISH with strong spread, it can inject up to 35 points — enough to push a moderate signal over the STRONG threshold on its own.

This is why the request_refresh() in Tier 1 matters so much. The early window scorer depends on InDecision having processed the just-closed candle. If InDecision is still running on 5-minute-old data when the predictive scorer fires, the indecision_bias factor is stale. Fresh boundary-triggered refresh eliminates that entirely.

The Tesseract Intelligence thesis — that competitive advantage comes from processing information faster and more precisely than the field — is literally encoded in this architecture. The edge isn't better data. It's fresher data, scored before the market acts on it.


Tier 3: Warm Reload (PR #114)

The third tier solved a different category of problem: cold starts.

In v4.x, restarting the bot meant losing everything. The InDecision feeds needed 5+ minutes to populate. The technical analysis engine needed several candles of history to calculate RSI and MACD. Elliott Wave detection needed pattern context. The risk manager's position tracking reset.

For a 24/7 trading system, this matters more than you'd think. Deploys, config changes, watchdog restarts, system updates — any of these interrupt the process. Every interruption meant 5-15 minutes of degraded signal quality while the feeds warmed up. In a system that trades 5-minute windows, that's 1-3 complete windows running partially blind.

The fix: StateCheckpointer.

Every 30 seconds, the checkpointer snapshots the bot's full analytical state to the bot_state database table:

  • Current regime classification (trending/ranging/volatile)
  • Risk manager state (open positions, daily P&L, exposure)
  • Elliott Wave pattern context
  • InDecision bias scores per asset
  • Technical indicator buffers

On restart, the bot reads the latest checkpoint. If the checkpoint is less than 30 seconds old — which it will be for any planned restart — the bot starts with 100% signal quality. No warm-up period. No degraded windows. The state is preserved across the process boundary.

SIGNAL

Warm reload turned restarts from a 5-15 minute signal quality penalty into a non-event. The bot now deploys with zero analytical downtime. State crosses the process boundary via the database, not memory.

Tests
1,970+
Comprehensive test suite across all 3 tiers

What the Dual-Path Actually Changes

Here's a concrete scenario. BTC just closed a 5-minute candle with strong bearish momentum: RSI at 28, MACD crossing down, Bollinger Band lower break, InDecision scoring BEARISH at 78% conviction.

v4.x behavior: The new candle opens. The scanner polls at t=0, t=5, t=10... but price_momentum returns zero. No delta yet. PolyEdge can't score above MODERATE without it. The bot waits. At t=45s, BTC has dropped enough for momentum to register. PolyEdge fires at 84/100 — STRONG. But the CLOB has repriced DOWN tokens from 30 cents to 88 cents. The bot buys at 88 cents. After fees, the expected value is approximately zero.

v5.0 behavior: The candle boundary fires. IntraBiasFeed.request_refresh() immediately re-scores with the just-closed candle. The early window scorer activates — ttc > snipe_window and elapsed < 5s. It reads the prior candle's RSI (28), MACD (crossing down), InDecision (BEARISH 78%). PredictiveWindowScore: 81/100. The bot places a TAKER order. DOWN tokens are at 32 cents. Entry at 32 cents versus entry at 88 cents — on the same directional call.

That's the difference. Same signal. Same direction. Same outcome. But the v5.0 entry has 56 cents more margin. After fees, the v5.0 trade is meaningfully profitable. The v4.x trade barely breaks even.

DOCTRINE

The edge in prediction markets isn't knowing the direction. Most momentum signals get direction right. The edge is entering before the CLOB reprices — and that requires scoring without current-candle data. Prediction, not reaction.


The System Today

Foresight v5.0 runs 24/7 on a Mac Mini. Nine assets across two timeframes — 18 live market slots evaluated concurrently via asyncio.gather. Conservative mode. $5 base bets. Kelly-adjusted sizing.

The infrastructure stack:

  • BinanceWSFeed for real-time candle data and boundary detection
  • IntraBiasFeed calling InDecision at 127.0.0.1:8081 for directional conviction
  • PolyEdge (11-factor scorer) for the reactive snipe window path
  • PredictiveWindowScore for the early window path
  • StateCheckpointer snapshotting to bot_state every 30 seconds
  • Horus (external watchdog) monitoring process health and log staleness
  • Mission Control dashboard for real-time visibility into every trade, score, and feed state

The three tiers compound. Tier 1 (event-driven foundation) gives the system sub-second candle boundary detection. Tier 2 (early window scoring) uses that detection to fire predictive signals before the market moves. Tier 3 (warm reload) ensures the analytical state that powers those signals survives any restart.

Each tier is independently useful. Together, they're the reason the 7-day numbers look fundamentally different from the 30-day numbers.


What v6.0 Might Look Like

The architecture has room to grow in two directions.

More assets. The PolyEdge scoring engine and early window scorer are asset-agnostic. Adding a new asset means adding a Binance feed, configuring InDecision for that pair, and registering it in the scanner. The framework handles the rest. The limiting factor isn't code — it's InDecision Engine calibration per asset, which currently takes a day of backtesting.

Cross-timeframe early window. Right now, the 5m early window scorer only uses 5m prior-candle data. But the 15m candle that contains the current 5m window has its own signal state — partially formed, but structurally informative. A v6.0 scorer could blend 5m prior-candle signals with the enclosing 15m candle's developing pattern for a richer predictive signal. This is harder than it sounds because the timeframes aren't synchronized — a 5m candle boundary doesn't always align with meaningful 15m structure — but the information is there.

Dynamic snipe windows. The current snipe windows are fixed per timeframe: 20 seconds for 5m, 45 seconds for 15m. A dynamic system could adjust based on CLOB liquidity depth and recent fill rates. Thinner books might warrant wider windows. Deep liquidity might allow tighter ones. The data to build this already exists in our trade logs.

The naming turns out to be aspirational in the right way. Foresight v5.0 predicts using the last candle. v6.0 would predict using the last candle and the enclosing timeframe simultaneously. Each version extends how far ahead the system can see.


The Through-Line

The v4.x bot worked. It made money. The 30-day record proves it. But it was structurally capped — the reactive architecture imposed a ceiling on entry quality that no amount of signal tuning could break through. You can't score momentum that hasn't happened yet.

v5.0 doesn't score momentum. It scores the conditions that produce momentum — and acts before the momentum arrives. That's the difference between a reactive system and a predictive one. Both get direction right. Only one gets the price.

30-Day ROI
48.2%
193 trades · conservative mode · $5 base bets

The bot is running. The signals are real. The architecture finally matches the ambition.

Explore the Invictus Labs Ecosystem

// Join the Network

Follow the Signal

If this was useful, follow along. Daily intelligence across AI, crypto, and strategy — before the mainstream catches on.

No spam. Unsubscribe anytime.

Share
// More SignalsAll Posts →