Skip to content
Back to App

Bar Data Format

Every call to get_next_bar() returns a single bar of market data as a Python dictionary. This page documents the bar structure, what each field means, and how bars relate to the timeframe you select.

Bar object schema

{
"t": "2025-01-15T14:30:00Z", # Timestamp (ISO 8601 UTC)
"o": 21500.25, # Open price
"h": 21505.50, # High price
"l": 21498.75, # Low price
"c": 21503.00, # Close price
"v": 150 # Volume (number of contracts traded)
}

Field reference

FieldTypeDescription
tstrThe bar’s timestamp in ISO 8601 format (UTC). This is the start time of the bar period.
ofloatOpen price — the first trade price during this bar’s time period.
hfloatHigh price — the highest trade price during this bar’s time period.
lfloatLow price — the lowest trade price during this bar’s time period.
cfloatClose price — the last trade price during this bar’s time period. This is the most commonly used price for indicator calculations.
vintVolume — the total number of contracts traded during this bar’s time period.

Accessing bar data

bar = get_next_bar()
if bar is None:
break # No more data
# Access individual fields
timestamp = bar["t"]
open_price = bar["o"]
high_price = bar["h"]
low_price = bar["l"]
close_price = bar["c"]
volume = bar["v"]
# Common patterns
price = bar["c"] # Most strategies use the close
spread = bar["h"] - bar["l"] # Bar range (high - low)
is_bullish = bar["c"] > bar["o"] # Close above open = bullish
is_bearish = bar["c"] < bar["o"] # Close below open = bearish
body_size = abs(bar["c"] - bar["o"]) # Size of the candle body

OHLCV explained

If you are new to candlestick data, here is what each price represents within a single bar’s time period:

  • Open (o) — Where price was at the start of the bar. For a 1-minute bar starting at 14:30:00, this is the price of the first trade at or after 14:30:00.
  • High (h) — The maximum price reached during the bar. Always greater than or equal to both open and close.
  • Low (l) — The minimum price reached during the bar. Always less than or equal to both open and close.
  • Close (c) — Where price was at the end of the bar. For a 1-minute bar, this is the last trade price before 14:31:00.
  • Volume (v) — How many contracts changed hands. Higher volume indicates more trading activity and often more reliable price action.

The relationship between these fields is always: l <= o <= h and l <= c <= h. The open and close can be in any order relative to each other.

Bars and timeframes

The timeframe you select in the Playground determines how much time each bar represents:

TimeframeBar durationBars per trading session (~6.5 hrs)Best for
1s1 second~23,400Tick-level analysis, ultra-short-term scalping
5s5 seconds~4,680Short-term scalping
1m1 minute~390Intraday strategies, most common
5m5 minutes~78Intraday swing trading
15m15 minutes~26Longer intraday or multi-day analysis

Timeframe impact on indicators

The same indicator period means very different things depending on the timeframe:

Indicator1s bars1m bars5m bars15m bars
10-period SMA10 seconds10 minutes50 minutes2.5 hours
30-period SMA30 seconds30 minutes2.5 hours7.5 hours
14-period RSI14 seconds14 minutes70 minutes3.5 hours
50-period channel50 seconds50 minutes~4 hours~12.5 hours

When switching timeframes, always reconsider your indicator periods. A 30-period SMA that works well on 1-minute bars (30 minutes of data) is far too short on 1-second bars (30 seconds of data) and would need to be scaled up.

Building data arrays

Most strategies accumulate bar data in arrays for indicator calculations:

# Track just closes (most common)
closes = []
# Track all OHLCV fields
opens = []
highs = []
lows = []
closes = []
volumes = []
for i in range(MAX_BARS):
bar = get_next_bar()
if bar is None:
break
# Append to arrays
closes.append(bar["c"])
highs.append(bar["h"])
lows.append(bar["l"])
volumes.append(bar["v"])
# Use the arrays for calculations
if len(closes) >= 20:
sma_20 = sum(closes[-20:]) / 20
avg_volume = sum(volumes[-20:]) / 20
highest_high = max(highs[-20:])
lowest_low = min(lows[-20:])

Working with timestamps

The t field is an ISO 8601 UTC timestamp string. You can use it for time-based filtering:

bar = get_next_bar()
timestamp = bar["t"] # "2025-01-15T14:30:00Z"
# Extract time components (string parsing, no datetime import needed)
hour = int(timestamp[11:13])
minute = int(timestamp[14:16])
# Only trade during core hours (9:30 AM - 3:30 PM ET = 14:30 - 20:30 UTC)
if hour < 14 or (hour == 14 and minute < 30):
continue # Skip pre-market
if hour >= 20 and minute >= 30:
continue # Skip after-hours
# Detect session boundaries
date = timestamp[:10] # "2025-01-15"
if date != previous_date:
print(f"[INFO] New trading day: {date}")
previous_date = date

Common bar calculations

Here are useful calculations you can perform with bar data:

Bar range and volatility

# Single bar range
bar_range = bar["h"] - bar["l"]
# Average True Range (ATR) approximation
ranges = [highs[j] - lows[j] for j in range(-period, 0)]
atr = sum(ranges) / period

Candlestick patterns

# Doji — open and close are very close
body = abs(bar["c"] - bar["o"])
full_range = bar["h"] - bar["l"]
is_doji = full_range > 0 and body / full_range < 0.1
# Hammer — small body at top, long lower wick
upper_wick = bar["h"] - max(bar["o"], bar["c"])
lower_wick = min(bar["o"], bar["c"]) - bar["l"]
is_hammer = lower_wick > 2 * body and upper_wick < body
# Engulfing pattern (requires previous bar)
if len(closes) >= 2:
prev_open = opens[-2]
prev_close = closes[-2]
is_bullish_engulfing = (
prev_close < prev_open and # Previous bar bearish
bar["c"] > bar["o"] and # Current bar bullish
bar["o"] < prev_close and # Opens below prev close
bar["c"] > prev_open # Closes above prev open
)

Volume analysis

# Volume spike detection
if len(volumes) >= 20:
avg_vol = sum(volumes[-20:]) / 20
if bar["v"] > avg_vol * 2:
print(f"[INFO] Volume spike: {bar['v']} vs avg {avg_vol:.0f}")
# Volume-weighted average price (VWAP) approximation
if len(closes) > 0 and len(volumes) > 0:
vwap_sum = sum(c * v for c, v in zip(closes, volumes))
vol_sum = sum(volumes)
if vol_sum > 0:
vwap = vwap_sum / vol_sum

Null bars

get_next_bar() returns None in two cases:

  1. All bars consumed — The replay has reached the end of the available data for the selected date range.
  2. Past end date — The bar’s timestamp exceeds the end date plus T23:59:59.

Always check for None before accessing bar fields:

bar = get_next_bar()
if bar is None:
print("[INFO] No more bars available")
break
# Safe to access bar fields here
price = bar["c"]