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
| Field | Type | Description |
|---|---|---|
t | str | The bar’s timestamp in ISO 8601 format (UTC). This is the start time of the bar period. |
o | float | Open price — the first trade price during this bar’s time period. |
h | float | High price — the highest trade price during this bar’s time period. |
l | float | Low price — the lowest trade price during this bar’s time period. |
c | float | Close price — the last trade price during this bar’s time period. This is the most commonly used price for indicator calculations. |
v | int | Volume — 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 fieldstimestamp = bar["t"]open_price = bar["o"]high_price = bar["h"]low_price = bar["l"]close_price = bar["c"]volume = bar["v"]
# Common patternsprice = bar["c"] # Most strategies use the closespread = bar["h"] - bar["l"] # Bar range (high - low)is_bullish = bar["c"] > bar["o"] # Close above open = bullishis_bearish = bar["c"] < bar["o"] # Close below open = bearishbody_size = abs(bar["c"] - bar["o"]) # Size of the candle bodyOHLCV 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:
| Timeframe | Bar duration | Bars per trading session (~6.5 hrs) | Best for |
|---|---|---|---|
1s | 1 second | ~23,400 | Tick-level analysis, ultra-short-term scalping |
5s | 5 seconds | ~4,680 | Short-term scalping |
1m | 1 minute | ~390 | Intraday strategies, most common |
5m | 5 minutes | ~78 | Intraday swing trading |
15m | 15 minutes | ~26 | Longer intraday or multi-day analysis |
Timeframe impact on indicators
The same indicator period means very different things depending on the timeframe:
| Indicator | 1s bars | 1m bars | 5m bars | 15m bars |
|---|---|---|---|---|
| 10-period SMA | 10 seconds | 10 minutes | 50 minutes | 2.5 hours |
| 30-period SMA | 30 seconds | 30 minutes | 2.5 hours | 7.5 hours |
| 14-period RSI | 14 seconds | 14 minutes | 70 minutes | 3.5 hours |
| 50-period channel | 50 seconds | 50 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 fieldsopens = []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-marketif hour >= 20 and minute >= 30: continue # Skip after-hours
# Detect session boundariesdate = timestamp[:10] # "2025-01-15"if date != previous_date: print(f"[INFO] New trading day: {date}") previous_date = dateCommon bar calculations
Here are useful calculations you can perform with bar data:
Bar range and volatility
# Single bar rangebar_range = bar["h"] - bar["l"]
# Average True Range (ATR) approximationranges = [highs[j] - lows[j] for j in range(-period, 0)]atr = sum(ranges) / periodCandlestick patterns
# Doji — open and close are very closebody = 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 wickupper_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 detectionif 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) approximationif 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_sumNull bars
get_next_bar() returns None in two cases:
- All bars consumed — The replay has reached the end of the available data for the selected date range.
- 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 hereprice = bar["c"]