Replay the bars you ship.
History and real time come back from one endpoint, so nothing about your test path is retrofitted.
Run a rule on the bars.
Fills, realized and open — off one response, at whatever speed you read it.
Unavailable
no response, so nothing to describe
Unavailable
no response, so nothing to describe
Unavailable
no response, so nothing to describe
Unavailable
no response, so nothing to describe
The read did not land. Nothing is drawn from a response that did not arrive.
GET /v1/prices/ohlcv?asset=EUR&interval=1d&from=2026-09-09T00:00:00.000Z&to=2026-11-08T00:00:00.000Z&limit=60One response. The cursor is an index into it, not a second call.
- Rule
- breakout · 4-bucket channel · one unit · long only · filled at the close · no fees or slippage
Page the window, then replay.
let from = windowStart while (from < windowEnd) { const { data } = await ohlcv('EUR', { interval: '1d', from }) if (data.length === 0) break for (const bar of data) strategy.onBar(bar) // to is exclusive, so the next page opens on the next bucket from = nextBucket(data[data.length - 1].timestamp) }
One request per page, ordered by construction, and the same four prices the live screens draw.
A gap is an absence.
A bucket nothing was published for is missing from the response, not carried forward and not zeroed.
Null is not zero.
volume is null when no size was published — and on a coarse bucket rather than a partial sum of the minutes that did.
The universe stays yours.
Survivorship, corporate actions and which instruments you were allowed to hold that day are your harness’s — this contract ends at the bar.
Test against what ships.
Read the candles reference, then point a run at it.