---
title: "Replay & Backtesting | 0xArchive Docs"
description: "Replay historical data over the same socket, then feed it through your live event loop for backtests. Gap events are included."
canonical_url: "https://www.0xarchive.io/docs/websocket/replay/"
markdown_url: "https://www.0xarchive.io/docs/websocket/replay.md"
route: "/docs/websocket/replay"
robots: "index, follow"
generated_from: "prerendered_html"
---

# Replay & Backtesting

Replay historical data over the same socket, then feed it through your live event loop for backtests. Gap events are included.

Mode Replay over WebSocket Continuity gap_detected included Use Backtests and reprocessing

## 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 granularity
ws.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 interval
ws.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 data
ws.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 channels
ws.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 replay
ws.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 replay
ws.send(JSON.stringify({ op: "replay.pause" }));

// Resume replay
ws.send(JSON.stringify({ op: "replay.resume" }));

// Seek to specific timestamp
ws.send(JSON.stringify({
  op: "replay.seek",
  timestamp: 1681550000000
}));

// Stop replay
ws.send(JSON.stringify({ op: "replay.stop" }));
```

### Gap detection

`gap_detected` marks missing windows during replay.

JavaScript

```
// Gap detection during replay
ws.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())
```
