Skip to content
Back to App

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:

  1. StartReplay/start initializes a session with an instrument, date, and timeframe
  2. LoopReplay/step advances bars one at a time; your strategy analyzes data and places orders
  3. EndReplay/end finalizes 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"
}
FieldTypeRequiredDescription
instrumentSymbolstringYesInstrument symbol (e.g., "NQ", "ES", "GC", "CL", "EURUSD")
startDatestringYesSession start date in YYYY-MM-DD format
timeframestringYesBar timeframe (e.g., "1s", "1m", "5m", "15m", "30m", "1h", "4h", "1D")
endDatestring or nullNoSession end date. If omitted, defaults to the end of the startDate trading session.

Timeframe values

ValueDescription
"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"
}
FieldTypeDescription
accountIdnumberThe account ID to use for orders and positions in this session
contractIdstringThe resolved contract ID for the instrument
totalBarsnumberTotal number of bars available in the session
startTimestringISO 8601 timestamp for the start of the session
endTimestringISO 8601 timestamp for the end of the session

Example

# Start a 5-minute replay session on NQ
result = 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']}")

Error responses

ScenarioerrorMessage
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
}
FieldTypeRequiredDefaultDescription
accountIdnumberYesAccount ID from Replay/start
stepsnumberNo1Number 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
}
FieldTypeDescription
barsarrayArray of bar objects, one per step. See Bar Data Format.
bars[].tstringISO 8601 timestamp
bars[].onumberOpen price
bars[].hnumberHigh price
bars[].lnumberLow price
bars[].cnumberClose price
bars[].vnumberVolume
currentBarnumberIndex of the current bar (0-based)
totalBarsnumberTotal bars in the session
completebooleantrue 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 bars
result = api("Replay/step", {
"accountId": ACCOUNT_ID,
"steps": 10
})
# Process all returned bars
for 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"
}
FieldTypeRequiredDescription
accountIdnumberYesAccount ID
timestampstringYesISO 8601 timestamp to jump to

Response

{
"success": true,
"errorCode": 0,
"errorMessage": null,
"currentBar": 180,
"totalBars": 1440,
"complete": false
}
FieldTypeDescription
currentBarnumberThe bar index after jumping
totalBarsnumberTotal bars in the session
completebooleantrue 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
}
FieldTypeRequiredDefaultDescription
accountIdnumberYesAccount ID
speednumberNo1Playback speed multiplier (1 to 50). 1 = real-time, 50 = 50x faster.

Response

{
"success": true,
"errorCode": 0,
"errorMessage": null
}

Example

# Start auto-play at 10x speed
result = 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
}
FieldTypeRequiredDescription
accountIdnumberYesAccount ID

Response

{
"success": true,
"errorCode": 0,
"errorMessage": null
}

Example

# Pause auto-play
result = 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
}
FieldTypeRequiredDescription
accountIdnumberYesAccount 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
}
}
FieldTypeDescription
resultsobjectPerformance summary for the session
results.totalPnlnumberNet profit/loss in USD
results.totalTradesnumberTotal number of round-trip trades
results.winningTradesnumberNumber of profitable trades
results.losingTradesnumberNumber of losing trades
results.winRatenumberWin percentage (0-100)
results.averageWinnumberAverage profit on winning trades
results.averageLossnumberAverage loss on losing trades (negative)
results.largestWinnumberBest single trade P&L
results.largestLossnumberWorst single trade P&L (negative)
results.profitFactornumberGross profit / gross loss
results.maxDrawdownnumberMaximum peak-to-trough equity decline (negative)
results.finalBalancenumberAccount balance at session end

Example

# End the session and print results
result = 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}")

Complete backtest example

Here is a minimal end-to-end example that starts a replay, steps through bars, and ends the session:

import urllib.request
import json
import 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. Authenticate
login = api("Auth/loginKey", {
"userName": os.environ["TESTMAX_EMAIL"],
"apiKey": os.environ["TESTMAX_API_KEY"]
})
TOKEN = login["token"]
# 2. Start replay
session = api("Replay/start", {
"instrumentSymbol": "NQ",
"startDate": "2025-01-15",
"timeframe": "5m"
})
ACCOUNT_ID = session["accountId"]
CONTRACT_ID = session["contractId"]
# 3. Step through bars
try:
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}")