Replay Control
The Replay Control endpoints are the heart of TestMax’s backtesting engine. They let you start a historical replay session, step through bars one at a time (or in batches), control playback speed, jump to specific timestamps, and end the session to get final results.
Replay session lifecycle
A typical backtest session follows this flow:
- Start —
Replay/startinitializes a session with an instrument, date, and timeframe - Loop —
Replay/stepadvances bars one at a time; your strategy analyzes data and places orders - End —
Replay/endfinalizes the session and returns performance results
You can also use Replay/play for auto-playback, Replay/pause to stop auto-play, and Replay/jumpTo to skip to a specific point in time.
POST Replay/start
Initialize a new backtest replay session. This loads historical data for the specified instrument and date range and prepares the session for bar-by-bar replay.
URL: POST /api/topstep-sim/Replay/start
Authentication: Bearer token required.
Request body
{ "instrumentSymbol": "NQ", "startDate": "2025-01-15", "timeframe": "5m", "endDate": "2025-01-15"}| Field | Type | Required | Description |
|---|---|---|---|
instrumentSymbol | string | Yes | Instrument symbol (e.g., "NQ", "ES", "GC", "CL", "EURUSD") |
startDate | string | Yes | Session start date in YYYY-MM-DD format |
timeframe | string | Yes | Bar timeframe (e.g., "1s", "1m", "5m", "15m", "30m", "1h", "4h", "1D") |
endDate | string or null | No | Session end date. If omitted, defaults to the end of the startDate trading session. |
Timeframe values
| Value | Description |
|---|---|
"1s" | 1-second bars |
"1m" | 1-minute bars |
"5m" | 5-minute bars |
"15m" | 15-minute bars |
"30m" | 30-minute bars |
"1h" | 1-hour bars |
"4h" | 4-hour bars |
"1D" | Daily bars |
Response
{ "success": true, "errorCode": 0, "errorMessage": null, "accountId": 12345, "contractId": "CON.F.US.ENQ.H25", "totalBars": 1440, "startTime": "2025-01-15T00:00:00Z", "endTime": "2025-01-15T23:59:59Z"}| Field | Type | Description |
|---|---|---|
accountId | number | The account ID to use for orders and positions in this session |
contractId | string | The resolved contract ID for the instrument |
totalBars | number | Total number of bars available in the session |
startTime | string | ISO 8601 timestamp for the start of the session |
endTime | string | ISO 8601 timestamp for the end of the session |
Example
# Start a 5-minute replay session on NQresult = api("Replay/start", { "instrumentSymbol": "NQ", "startDate": "2025-01-15", "timeframe": "5m"})
if result["success"]: ACCOUNT_ID = result["accountId"] CONTRACT_ID = result["contractId"] TOTAL_BARS = result["totalBars"] print(f"Session started: {TOTAL_BARS} bars available") print(f"Account: {ACCOUNT_ID}, Contract: {CONTRACT_ID}")else: print(f"Failed to start: {result['errorMessage']}")curl -X POST https://api.test-max.com/api/topstep-sim/Replay/start \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{ "instrumentSymbol": "NQ", "startDate": "2025-01-15", "timeframe": "5m" }'Error responses
| Scenario | errorMessage |
|---|---|
| Invalid instrument | "Instrument not found" |
| No data for date | "No data available for the specified date" |
| Invalid timeframe | "Invalid timeframe" |
| Session already active | "An active session already exists" |
POST Replay/step
Advance the replay by one or more bars. This is the primary way to move through historical data in your strategy’s main loop.
URL: POST /api/topstep-sim/Replay/step
Authentication: Bearer token required.
Request body
{ "accountId": 12345, "steps": 1}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
accountId | number | Yes | — | Account ID from Replay/start |
steps | number | No | 1 | Number of bars to advance |
Response
{ "success": true, "errorCode": 0, "errorMessage": null, "bars": [ { "t": "2025-01-15T14:30:00Z", "o": 21500.25, "h": 21505.50, "l": 21498.75, "c": 21503.00, "v": 150 } ], "currentBar": 42, "totalBars": 1440, "complete": false}| Field | Type | Description |
|---|---|---|
bars | array | Array of bar objects, one per step. See Bar Data Format. |
bars[].t | string | ISO 8601 timestamp |
bars[].o | number | Open price |
bars[].h | number | High price |
bars[].l | number | Low price |
bars[].c | number | Close price |
bars[].v | number | Volume |
currentBar | number | Index of the current bar (0-based) |
totalBars | number | Total bars in the session |
complete | boolean | true if the replay has reached the end of the data |
Example
# Step through bars one at a time (typical strategy loop)for i in range(TOTAL_BARS): result = api("Replay/step", { "accountId": ACCOUNT_ID, "steps": 1 })
if not result["success"] or result.get("complete"): break
bar = result["bars"][0] close = bar["c"] timestamp = bar["t"]
# Your strategy logic here print(f"Bar {result['currentBar']}/{result['totalBars']}: Close={close}")Stepping multiple bars at once
You can advance multiple bars in a single call. This is useful when you want to skip ahead quickly without processing each bar individually.
# Skip forward 10 barsresult = api("Replay/step", { "accountId": ACCOUNT_ID, "steps": 10})
# Process all returned barsfor bar in result["bars"]: # Each bar in the batch print(f"{bar['t']}: O={bar['o']} H={bar['h']} L={bar['l']} C={bar['c']}")POST Replay/jumpTo
Jump to a specific point in time within the replay session. Bars before the target timestamp are skipped. This is useful for jumping past the market open, skipping to a specific time of day, or fast-forwarding to a known event.
URL: POST /api/topstep-sim/Replay/jumpTo
Authentication: Bearer token required.
Request body
{ "accountId": 12345, "timestamp": "2025-01-15T15:00:00Z"}| Field | Type | Required | Description |
|---|---|---|---|
accountId | number | Yes | Account ID |
timestamp | string | Yes | ISO 8601 timestamp to jump to |
Response
{ "success": true, "errorCode": 0, "errorMessage": null, "currentBar": 180, "totalBars": 1440, "complete": false}| Field | Type | Description |
|---|---|---|
currentBar | number | The bar index after jumping |
totalBars | number | Total bars in the session |
complete | boolean | true if the timestamp is past the end of the data |
Example
# Skip to the US market open (9:30 AM ET = 14:30 UTC)result = api("Replay/jumpTo", { "accountId": ACCOUNT_ID, "timestamp": "2025-01-15T14:30:00Z"})
if result["success"]: print(f"Jumped to bar {result['currentBar']}")else: print(f"Jump failed: {result['errorMessage']}")POST Replay/play
Start auto-playing the replay at a specified speed multiplier. The session will advance automatically, simulating real-time market progression at the given speed.
URL: POST /api/topstep-sim/Replay/play
Authentication: Bearer token required.
Request body
{ "accountId": 12345, "speed": 10}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
accountId | number | Yes | — | Account ID |
speed | number | No | 1 | Playback speed multiplier (1 to 50). 1 = real-time, 50 = 50x faster. |
Response
{ "success": true, "errorCode": 0, "errorMessage": null}Example
# Start auto-play at 10x speedresult = api("Replay/play", { "accountId": ACCOUNT_ID, "speed": 10})
print("Auto-play started at 10x speed")POST Replay/pause
Pause auto-playback. The session remains active and can be resumed with Replay/play or advanced manually with Replay/step.
URL: POST /api/topstep-sim/Replay/pause
Authentication: Bearer token required.
Request body
{ "accountId": 12345}| Field | Type | Required | Description |
|---|---|---|---|
accountId | number | Yes | Account ID |
Response
{ "success": true, "errorCode": 0, "errorMessage": null}Example
# Pause auto-playresult = api("Replay/pause", {"accountId": ACCOUNT_ID})print("Playback paused")POST Replay/end
End the replay session and retrieve final results. This is the last call in a backtest — it closes any remaining positions, calculates final P&L, and returns a performance summary.
URL: POST /api/topstep-sim/Replay/end
Authentication: Bearer token required.
Request body
{ "accountId": 12345}| Field | Type | Required | Description |
|---|---|---|---|
accountId | number | Yes | Account ID |
Response
{ "success": true, "errorCode": 0, "errorMessage": null, "results": { "totalPnl": 1250.00, "totalTrades": 8, "winningTrades": 5, "losingTrades": 3, "winRate": 62.5, "averageWin": 375.00, "averageLoss": -125.00, "largestWin": 750.00, "largestLoss": -200.00, "profitFactor": 2.5, "maxDrawdown": -325.00, "finalBalance": 51250.00 }}| Field | Type | Description |
|---|---|---|
results | object | Performance summary for the session |
results.totalPnl | number | Net profit/loss in USD |
results.totalTrades | number | Total number of round-trip trades |
results.winningTrades | number | Number of profitable trades |
results.losingTrades | number | Number of losing trades |
results.winRate | number | Win percentage (0-100) |
results.averageWin | number | Average profit on winning trades |
results.averageLoss | number | Average loss on losing trades (negative) |
results.largestWin | number | Best single trade P&L |
results.largestLoss | number | Worst single trade P&L (negative) |
results.profitFactor | number | Gross profit / gross loss |
results.maxDrawdown | number | Maximum peak-to-trough equity decline (negative) |
results.finalBalance | number | Account balance at session end |
Example
# End the session and print resultsresult = api("Replay/end", {"accountId": ACCOUNT_ID})
if result["success"]: r = result["results"] print(f"\n=== Backtest Results ===") print(f"Total P&L: ${r['totalPnl']:+,.2f}") print(f"Total Trades: {r['totalTrades']}") print(f"Win Rate: {r['winRate']:.1f}%") print(f"Profit Factor: {r['profitFactor']:.2f}") print(f"Max Drawdown: ${r['maxDrawdown']:,.2f}") print(f"Final Balance: ${r['finalBalance']:,.2f}")curl -X POST https://api.test-max.com/api/topstep-sim/Replay/end \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{"accountId": 12345}'Complete backtest example
Here is a minimal end-to-end example that starts a replay, steps through bars, and ends the session:
import urllib.requestimport jsonimport os
API_URL = "https://api.test-max.com/api/topstep-sim"
def api(path, body=None): data = json.dumps(body).encode() if body else None req = urllib.request.Request( f"{API_URL}/{path}", data=data, headers={ "Content-Type": "application/json", "Authorization": f"Bearer {TOKEN}" } ) return json.loads(urllib.request.urlopen(req).read())
# 1. Authenticatelogin = api("Auth/loginKey", { "userName": os.environ["TESTMAX_EMAIL"], "apiKey": os.environ["TESTMAX_API_KEY"]})TOKEN = login["token"]
# 2. Start replaysession = api("Replay/start", { "instrumentSymbol": "NQ", "startDate": "2025-01-15", "timeframe": "5m"})ACCOUNT_ID = session["accountId"]CONTRACT_ID = session["contractId"]
# 3. Step through barstry: for i in range(session["totalBars"]): result = api("Replay/step", {"accountId": ACCOUNT_ID, "steps": 1}) if not result["success"] or result.get("complete"): break
bar = result["bars"][0]
# Simple strategy: buy on first bar, sell on last if i == 0: api("Order/place", { "accountId": ACCOUNT_ID, "contractId": CONTRACT_ID, "type": 2, "side": 0, "size": 1 }) elif i == session["totalBars"] - 2: api("Position/closeContract", { "accountId": ACCOUNT_ID, "contractId": CONTRACT_ID })
finally: # 4. Always end the session results = api("Replay/end", {"accountId": ACCOUNT_ID}) if results["success"]: print(f"P&L: ${results['results']['totalPnl']:+,.2f}")