Polymarket: https://polymarket.com/@abrownfox001?tab=activity
GitHub: https://github.com/abrownfox0/abrownfox001-twap60-prediction-trigger-system
Telegram: https://t.me/abrownfox001
If the TWAP feed is late, stale, disconnected, or pinned to the wrong slot open, every downstream “edge” is fake.
The contract the feed must satisfy
For BTC 5-minute Up/Down, settlement is a 30-second Chainlink TWAP, not a last trade.
The live engine therefore needs four values in memory at all times:
| Field | Meaning |
|---|---|
slot_id |
Current btc-updown-5m-{ts}
|
open_ref |
Price-to-beat at slot start |
twap_now |
Latest official TWAP |
twap_ts |
Event time of that TWAP update |
The signal module is only allowed to run if all four are present and fresh.
That rule is stricter than “WebSocket is connected.”
A connected socket can still be serving a dead value.
Why RTDS is the primary path
src/twapFeed.ts is built around Polymarket RTDS (wss://ws-live-data.polymarket.com).
Reasons:
- It is the venue-facing stream, not a private Chainlink key path
- It already carries the crypto TWAP topic used by short-duration markets
- The same process can subscribe to book/user channels nearby
- Reconnect policy stays inside one client instead of two oracle stacks
CEX mid (src/cexFeed.ts) is secondary. It can lead. It cannot resolve.
If CEX says up and TWAP says down, the engine believes TWAP.
Subscription shape
The 5-minute engine cares about the official short-window TWAP for btc/usd.
Conceptually:
{
"topic": "crypto_prices_twap_thirty",
"type": "update",
"filters": "{\"symbol\":\"btc/usd\"}"
}
Exact topic names can differ by SDK generation. The invariant does not:
- subscribe by symbol, not by Polymarket market slug
- store the latest TWAP value + timestamp
- never infer TWAP from CLOB mid
- never infer TWAP from a single Binance print
The market slug is joined later in src/markets.ts.
The oracle is joined first.
Reconnect is not “close and open again”
A 5-minute bot dies in the gaps.
The reconnect policy I actually want is:
- Detect dead socket:
close,error, missed pong, or no TWAP update inside the freshness window - Backoff:
250ms → 500ms → 1s → 2s → 5s, cap at 5s - On resume, invalidate
twap_nowuntil a new stamped update arrives - Do not reuse the pre-disconnect value
- If reconnect takes longer than one signal cycle, halt entries
The dangerous bug is optimistic reuse:
socket dropped
last TWAP = 111,842.17 from 8 seconds ago
bot keeps scoring as if that is live
That value is now a snapshot. Snapshots are how pre-TWAP bots got wrecked.
After resume, the feed is unknown until the next valid update. Unknown means no new buys.
Freshness gates
I treat TWAP as a time series with an expiry.
Useful thresholds for a 5m / 30s-TWAP market:
| Condition | Action |
|---|---|
Update age < 2s
|
Healthy |
Update age 2–5s
|
Score, but do not increase size |
Update age > 5s
|
Block new entries |
Update age > 8s
|
Force scratch-or-hold freeze; no adds |
| No update across reconnect | Hard halt |
These numbers are policy, not magic. The point is that staleness is a trading signal.
A stale TWAP should look like a circuit breaker, not like a slightly old input.
Pseudo-check:
function twapUsable(sample: TwapSample, now: number) {
if (!sample) return false;
if (!Number.isFinite(sample.value)) return false;
if (!sample.ts) return false;
return now - sample.ts <= 5_000;
}
If twapUsable is false, src/engine.ts cannot enter.
Open reference is a separate feed problem
People obsess over the live TWAP and then pin the wrong open.
The slot-open reference is the denominator of the whole trade:
[
\text{side} = \mathbf{1}{\text{TWAP}_{\text{settle}} > \text{open_ref}}
]
If open_ref is off by a few dollars, the model can look “confident” while betting the wrong binary.
Rules that belong in the feed layer, not the model layer:
- Capture
open_refat slot start from the same official family as settlement - Freeze it for the life of that
slot_id - Do not let it drift with later TWAP updates
- If slot discovery is late, either wait for the official open or skip the slot
- Never backfill open from a random CEX candle close
Late join is a real production case. The bot does not always wake at t=0.
A late join with a guessed open is worse than missing the slot.
Clock alignment
RTDS event time, local machine time, and slot boundary time are three clocks.
The engine should score on event time.
If the socket delivers a TWAP stamped T and local now is T+3s, the sample is 3s old.
If you use local now as the sample time, reconnect delay gets hidden.
Practical pattern:
receive update
parse value, event_ts
if event_ts > last_event_ts: accept
if event_ts <= last_event_ts: ignore (out-of-order)
age = local_now - event_ts
Out-of-order updates happen after reconnect. Last-write-wins on arrival time will corrupt the series.
What the feed module should expose
src/twapFeed.ts should not expose “a number.”
It should expose a state object:
type TwapState = {
symbol: "btc/usd";
value: number | null;
eventTs: number | null;
recvTs: number | null;
stale: boolean;
connected: boolean;
source: "rtds" | "gamma_fallback" | "none";
};
Then the engine can log why it refused a slot:
stale=trueconnected=falsesource=none-
open_refmissing
Silent skips are how you debug the wrong layer for a week.
Fallback policy
Gamma outcomePrices remains useful after the slot ends, as an authoritative resolved label.
It is a bad live TWAP substitute.
My rule:
- Live scoring: RTDS TWAP only
- Post-slot scoring / research labels: Gamma first, RTDS archive second
- No live fallback to last CEX trade
- No live fallback to CLOB mid
If RTDS is down, the correct production behavior is halt, not “use Binance for a minute.”
That minute is exactly when books are most distorted.
Failure modes this layer is built to kill
- Phantom edge from stale TWAP after a quiet socket
- Wrong binary from a guessed slot open
- Double-counting old and new TWAP after reconnect
- Scoring through a gap while the 30s window has already moved
- Training/live mismatch if research used snapshot close and prod uses TWAP
Part 1 said the signal is private.
That only matters if the feed is honest. A private model on a stale oracle is just a confident error generator.
Checklist before Part 3
If you are implementing this, do not touch signal weights until you can answer yes to all of these:
- Do I know the exact slot open I will be judged against?
- Is my live value the official TWAP, not a proxy?
- Can I prove the last update is fresh in milliseconds?
- Do I discard state on reconnect?
- Do I halt when the feed is unknown?
Only then is P(up) allowed to exist.
Part 3 will define the signal contract: what P(up) is allowed to mean, when it may fire, and why most “direction models” are not valid inputs to a 5-minute TWAP market.
Not financial advice. Paper or micro-size first.
If you have more questions, please feel free to contact me at any time: https://t.me/abrownfox001
My Polymarket Activity: https://polymarket.com/@abrownfox001?tab=activity
#Polymarket #TWAP #RTDS #TradingBot #BTC #WebSocket #AlgoTrading #PredictionMarkets
Top comments (0)