Replay & Backtesting
Replay historical data over the same socket, then feed it through your live event loop for backtests. Gap events are included.
Replay & Backtesting
Replay historical data over the same socket, then feed it through your live event loop for backtests.
Historical replay
Replay historical data with original timing and adjustable speed.
JavaScript
// Start historical replay at 10x speed (Hyperliquid)ws.send(JSON.stringify({ op: "replay", channel: "orderbook", symbol: "BTC", start: 1681516800000, // Unix timestamp in ms end: 1681603200000, speed: 10 // 10x playback speed}));
// Start Lighter.xyz orderbook replay with granularityws.send(JSON.stringify({ op: "replay", channel: "lighter_orderbook", symbol: "BTC", start: 1706140800000, end: 1706227200000, speed: 10, granularity: "10s" // Options: checkpoint, 30s, 10s, 1s, tick}));
// Replay candles with specific intervalws.send(JSON.stringify({ op: "replay", channel: "candles", symbol: "ETH", start: 1681516800000, end: 1681603200000, speed: 10, interval: "1h" // Options: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w}));
// Replay OI/funding dataws.send(JSON.stringify({ op: "replay", channel: "open_interest", symbol: "BTC", start: 1684540800000, end: 1684627200000, speed: 10}));
// Multi-channel synchronized replay (all channels must be same exchange)// Data is interleaved in timestamp order across channelsws.send(JSON.stringify({ op: "replay", channels: ["orderbook", "trades", "funding"], // Use "channels" (plural) for multi-channel symbol: "BTC", start: 1681516800000, end: 1681603200000, speed: 10}));
// Multi-channel Lighter replayws.send(JSON.stringify({ op: "replay", channels: ["lighter_orderbook", "lighter_trades", "lighter_funding"], symbol: "ETH", start: 1706140800000, end: 1706227200000, speed: 10, granularity: "10s" // Applies to lighter_orderbook channel}));
// Multi-channel replay sends "replay_snapshot" messages before the timeline// starts, providing initial state for each channel:// {"type": "replay_snapshot", "channel": "orderbook", "coin": "BTC", "symbol": "BTC", "timestamp": ..., "data": {...}}// {"type": "replay_snapshot", "channel": "funding", "coin": "BTC", "symbol": "BTC", "timestamp": ..., "data": {...}}// Then interleaved historical_data messages follow in timestamp order
// Pause replayws.send(JSON.stringify({ op: "replay.pause" }));
// Resume replayws.send(JSON.stringify({ op: "replay.resume" }));
// Seek to specific timestampws.send(JSON.stringify({ op: "replay.seek", timestamp: 1681550000000}));
// Stop replayws.send(JSON.stringify({ op: "replay.stop" }));Gap detection
gap_detected marks missing windows during replay.
JavaScript
// Gap detection during replayws.onmessage = (event) => { const msg = JSON.parse(event.data);
if (msg.type === 'gap_detected') { console.log(`Gap in ${msg.channel}/${msg.symbol}:`); console.log(` From: ${new Date(msg.gap_start).toISOString()}`); console.log(` To: ${new Date(msg.gap_end).toISOString()}`); console.log(` Duration: ${msg.duration_minutes} minutes`); }
// Handle other message types... if (msg.type === 'historical_data') { // Process data }};Backtesting
Feed synchronized historical market data through the same event loop you use for live handling.
Python
import asyncio, json, websockets
async def backtest_strategy(): uri = "wss://api.0xarchive.io/ws?apiKey=0xa_your_api_key" async with websockets.connect(uri) as ws: # Replay orderbook + trades together at 50x speed await ws.send(json.dumps({ "op": "replay", "channels": ["orderbook", "trades"], "symbol": "BTC", "start": 1704067200000, # Jan 1 2024 "end": 1704153600000, # Jan 2 2024 "speed": 50 }))
orderbook = None trades = []
async for message in ws: msg = json.loads(message)
if msg["type"] == "replay_snapshot": # Initial state before timeline starts if msg["channel"] == "orderbook": orderbook = msg["data"]
elif msg["type"] == "historical_data": if msg["channel"] == "orderbook": orderbook = msg["data"] # Run strategy on each orderbook update signal = my_strategy(orderbook, trades) if signal: execute_paper_trade(signal, msg["timestamp"]) elif msg["channel"] == "trades": trades.append(msg["data"])
elif msg["type"] == "gap_detected": print(f"Data gap: {msg['duration_minutes']}min at {msg['gap_start']}")
elif msg["type"] == "replay_completed": print(f"Backtest done. {msg['snapshots_sent']} data points processed.") break
asyncio.run(backtest_strategy())