Skip to content
Back to App

History & Bars

The History endpoint lets you retrieve historical OHLCV (Open, High, Low, Close, Volume) bar data for any available instrument. Use this to pre-load data for indicator calculations, analyze historical price patterns, or build lookback buffers before entering the replay loop.


POST History/retrieveBars

Retrieve historical bars for a contract within a time window. Bars are aggregated server-side to the unit/unitNumber you request.

URL: POST /api/topstep-sim/History/retrieveBars

Authentication: Bearer token required.

Request body

{
"contractId": "CON.F.US.ENQ.H25",
"unit": 2,
"unitNumber": 5,
"startTime": "2025-01-15T00:00:00Z",
"endTime": "2025-01-16T00:00:00Z",
"limit": 500
}
FieldTypeRequiredDescription
contractIdstringYesThe contract ID to retrieve data for
unitnumberYesBar aggregation unit. See AggregateBarUnit below.
unitNumbernumberYesNumber of units per bar (e.g., 5 with unit=2 gives 5-minute bars). Must be a positive integer.
startTimestring or nullNoISO 8601 start timestamp. Defaults to the beginning of the instrument’s available data.
endTimestring or nullNoISO 8601 end timestamp. Defaults to the end of the instrument’s available data.
limitnumber or nullNoMaximum bars to return (default and cap: 20,000)
includePartialBarbooleanNoAccepted for ProjectX schema compatibility; the trailing partial bar is always included

Bars are returned from startTime forward — to fetch the most recent bars, compute startTime by counting back from the end of the data (see the example below).

AggregateBarUnit

ValueUnit
1Second
2Minute
3Hour
4Day

Common timeframe configurations

TimeframeunitunitNumber
1-second11
1-minute21
5-minute25
15-minute215
30-minute230
1-hour31
4-hour34
Daily41

Response

{
"success": true,
"errorCode": 0,
"errorMessage": null,
"bars": [
{
"t": "2025-01-15T14:00:00Z",
"o": 21480.50,
"h": 21495.25,
"l": 21478.00,
"c": 21492.75,
"v": 3420
},
{
"t": "2025-01-15T14:05:00Z",
"o": 21492.75,
"h": 21505.50,
"l": 21490.00,
"c": 21503.25,
"v": 2815
}
]
}
FieldTypeDescription
barsarrayList of bar objects in chronological order (oldest first)
bars[].tstringISO 8601 timestamp for the bar’s open time
bars[].onumberOpen price
bars[].hnumberHigh price
bars[].lnumberLow price
bars[].cnumberClose price
bars[].vnumberVolume (number of contracts traded)

Example: Look back N bars

Compute startTime by counting back N bar-widths from your reference point (e.g., the replay session date):

from datetime import datetime, timedelta, timezone
# Get the 200 5-minute bars leading up to the session start
session_start = datetime(2025, 1, 15, 14, 30, tzinfo=timezone.utc)
lookback = timedelta(minutes=5 * 200)
result = api("History/retrieveBars", {
"contractId": CONTRACT_ID,
"unit": 2, # Minute
"unitNumber": 5, # 5-minute bars
"startTime": (session_start - lookback).isoformat(),
"endTime": session_start.isoformat(),
"limit": 200
})
if result["success"]:
bars = result.get("bars", [])
print(f"Retrieved {len(bars)} bars")
# Calculate a simple moving average from historical data
closes = [b["c"] for b in bars]
sma_20 = sum(closes[-20:]) / 20
print(f"SMA(20) = {sma_20:.2f}")

Example: Time window

# Get all 1-minute bars between 14:00 and 15:00 UTC
result = api("History/retrieveBars", {
"contractId": CONTRACT_ID,
"unit": 2, # Minute
"unitNumber": 1, # 1-minute bars
"startTime": "2025-01-15T14:00:00Z",
"endTime": "2025-01-15T15:00:00Z"
})
bars = result.get("bars", [])
print(f"Retrieved {len(bars)} bars in the 14:00-15:00 window")

Building a lookback buffer

Many strategies require historical data to calculate indicators (e.g., moving averages, RSI, Bollinger Bands) before entering the replay loop. Use History/retrieveBars to pre-load this data:

# Pre-load 50 5-minute bars for SMA calculation
lookback = timedelta(minutes=5 * 50)
history = api("History/retrieveBars", {
"contractId": CONTRACT_ID,
"unit": 2,
"unitNumber": 5,
"startTime": (session_start - lookback).isoformat(),
"endTime": session_start.isoformat(),
"limit": 50
})
closes = [b["c"] for b in history.get("bars", [])]
# Now enter the replay loop with a warm indicator
for i in range(TOTAL_BARS):
bar = get_next_bar()
if bar is None:
break
closes.append(bar["c"])
# Need at least 20 bars for SMA(20)
if len(closes) >= 20:
sma = sum(closes[-20:]) / 20
if bar["c"] > sma:
# Price above SMA — bullish trigger
pass

Multi-timeframe analysis

You can retrieve bars at different timeframes for the same instrument. This is useful for strategies that use a higher timeframe for trend direction and a lower timeframe for entries.

# Get 20 daily bars for trend context
daily = api("History/retrieveBars", {
"contractId": CONTRACT_ID,
"unit": 4, # Day
"unitNumber": 1, # Daily
"startTime": (session_start - timedelta(days=20)).isoformat(),
"endTime": session_start.isoformat(),
"limit": 20
})
daily_bars = daily.get("bars", [])
daily_closes = [b["c"] for b in daily_bars]
daily_sma = sum(daily_closes[-10:]) / 10
daily_trend = "bullish" if daily_closes[-1] > daily_sma else "bearish"
# Get hourly bars for recent structure
hourly = api("History/retrieveBars", {
"contractId": CONTRACT_ID,
"unit": 3, # Hour
"unitNumber": 1, # 1-hour bars
"startTime": (session_start - timedelta(hours=24)).isoformat(),
"endTime": session_start.isoformat(),
"limit": 24
})
hourly_bars = hourly.get("bars", [])
recent_high = max(b["h"] for b in hourly_bars[-4:])
recent_low = min(b["l"] for b in hourly_bars[-4:])
print(f"Daily trend: {daily_trend}")
print(f"4-hour range: {recent_low}{recent_high}")

Usage notes

  • Bars are returned in chronological order (oldest first), starting at startTime.
  • If startTime/endTime are omitted, they default to the boundaries of the instrument’s available data.
  • At most 20,000 bars are returned per request, and each request scans a bounded time window measured from startTime: 30 days for second/minute bars, 120 days for hour bars, and 365 days for day bars. A request spanning a longer range succeeds with the bars from the start of the range — it is not an error and does not mean you reached the end of the data. To fetch more, page forward by setting the next request’s startTime to just after the last returned bar’s timestamp.
  • unit accepts 1–4 (Second, Minute, Hour, Day). Week/Month aggregation is not supported.
  • Requests are rate-limited per account (20/minute). A 429 response includes a Retry-After header.
  • Historical bar data uses the same format as bars returned by Replay/step. See Bar Data Format for full field documentation.

Error responses

ScenarioerrorCodeerrorMessage
Missing or unknown contractId1"Contract not found"
Missing or unknown unit2"Unit invalid"
Unparseable startTime/endTime2"Invalid time range"
unitNumber missing, non-integer, < 1, or bar width exceeds the scan ceiling3"Unit number invalid"
limit < 14"Limit invalid"
Rate limit exceededHTTP 429"Too many requests" (with Retry-After)