Market Data
Learn how to access and use market data in your custom strategies.
Overview
Market data is accessed through the td.market object, which provides real-time price information, historical candles, and ticker data.
Execution Mode: What td.market Actually Shows
The fields on td.market behave differently depending on your bot's Execution Mode. Read this once. It matters.
New bots start in on_bar_close unless the strategy version opts into continuous; an existing bot keeps the mode it has.
| Field | continuous mode | on_bar_close mode |
|---|---|---|
td.market.candles, .opens, .highs, .lows, .closes, .volumes, .timestamps | Includes the currently forming candle as the last element | Trimmed to closed bars only — last element is the most recently closed bar |
td.market.open, .high, .low, .close, .volume | Forming candle's running values | Last closed bar's final values |
td.market.hl2, .hlc3, .ohlc4 | Derived from forming candle | Derived from last closed bar |
td.market.price | Live ticker price | Live ticker price (same — this field is always live regardless of mode) |
td.market.bid, .ask, .spread, .ticker | Live | Live (unchanged by mode) |
td.mtf.candles(tf), td.mtf.indicators(tf) | Higher-TF candles include the forming higher-TF bar | Higher-TF candles trimmed to closed bars only |
Why: on_bar_close mode is designed so strategies see the same world a backtest would see at the close of each bar. That means indicators, OHLC arrays, and scalar OHLC all reflect closed bars. But td.market.price is the live ticker even in on_bar_close mode, because that's the price your bot actually places orders at. If you use td.market.close vs td.market.price, they will diverge on an on_bar_close bot — and that's intentional. Use close for bar-based signal logic. Use price for order placement (sizing, DCA, limit offsets).
Backtest parity: when your bot runs in on_bar_close mode, its signals should line up with a backtest of the same strategy and candle window, within ±1 primary bar.
Available Data
Current Price
const price = td.market.price; // Live market price (always live, even in on_bar_close mode)
const bid = td.market.bid; // Best bid price
const ask = td.market.ask; // Best ask price
const spread = td.market.spread; // Bid-ask spread
Trading Context
const symbol = td.market.symbol; // e.g., "BTC/USDT"
const exchange = td.market.exchange; // e.g., "binance"
const timeframe = td.market.timeframe; // e.g., "1h", "4h"
Latest Candle Values
In continuous mode these are the forming candle's running values. In on_bar_close mode they are the last closed candle's final values.
const open = td.market.open; // Candle open
const high = td.market.high; // Candle high
const low = td.market.low; // Candle low
const close = td.market.close; // Candle close
const volume = td.market.volume; // Candle volume
Derived Price Types
Commonly used in technical analysis and indicator calculations:
const hl2 = td.market.hl2; // (high + low) / 2 - Median price
const hlc3 = td.market.hlc3; // (high + low + close) / 3 - Typical price
const ohlc4 = td.market.ohlc4; // (open + high + low + close) / 4 - Average price
These are useful for:
- hl2: Median price, reduces noise from extreme wicks
- hlc3: Typical price, commonly used in VWAP and MFI calculations
- ohlc4: Average price, smoothest representation of the candle
Ticker Data
The ticker provides 24-hour market statistics:
const ticker = td.market.ticker;
ticker.symbol // Trading pair
ticker.bid // Current bid
ticker.ask // Current ask
ticker.last // Last traded price
ticker.high24h // 24h high
ticker.low24h // 24h low
ticker.volume24h // 24h volume
ticker.timestamp // Data timestamp
Historical Candles
The td.market.candles array contains the most recent OHLCV candles: as many as the strategy version's candle history declares (see "How many candles each timeframe gets" in Multi-Timeframe), or about 500 when it declares none, capped by what the exchange returns in one request.
In continuous mode the last element is the forming candle (still being built). In on_bar_close mode the array is trimmed to closed bars only — the last element is the most recently closed bar.
const candles = td.market.candles;
// Each candle has:
// {
// timestamp: number, // Unix timestamp (ms) — candle open time
// open: number,
// high: number,
// low: number,
// close: number,
// volume: number
// }
Accessing Candles
// Latest candle
const latest = td.market.candles[td.market.candles.length - 1];
// Previous candle
const previous = td.market.candles[td.market.candles.length - 2];
// First candle (oldest)
const oldest = td.market.candles[0];
// Last N candles
const last10 = td.market.candles.slice(-10);
Practical Examples
Calculate Price Change
// 24h price change
const ticker = td.market.ticker;
const change24h = ((ticker.last - ticker.low24h) / ticker.low24h) * 100;
td.utils.log('24h change', { change: change24h.toFixed(2) + '%' });
Detect Large Spread
const spreadPercent = (td.market.spread / td.market.price) * 100;
if (spreadPercent > 0.5) {
td.utils.log('Warning: Large spread', { spread: spreadPercent.toFixed(3) + '%' });
return; // Skip trading when spread is too wide
}
Calculate VWAP
// Volume Weighted Average Price from candles
function calculateVWAP(candles) {
let sumPriceVolume = 0;
let sumVolume = 0;
for (const candle of candles) {
const typicalPrice = (candle.high + candle.low + candle.close) / 3;
sumPriceVolume += typicalPrice * candle.volume;
sumVolume += candle.volume;
}
return sumVolume > 0 ? sumPriceVolume / sumVolume : 0;
}
const vwap = calculateVWAP(td.market.candles.slice(-20));
Detect Support/Resistance
// Find recent swing highs and lows
function findSwingPoints(candles, lookback = 5) {
const highs = [];
const lows = [];
for (let i = lookback; i < candles.length - lookback; i++) {
const current = candles[i];
let isSwingHigh = true;
let isSwingLow = true;
for (let j = 1; j <= lookback; j++) {
if (candles[i - j].high >= current.high || candles[i + j].high >= current.high) {
isSwingHigh = false;
}
if (candles[i - j].low <= current.low || candles[i + j].low <= current.low) {
isSwingLow = false;
}
}
if (isSwingHigh) highs.push(current.high);
if (isSwingLow) lows.push(current.low);
}
return { highs, lows openGraph: { title: 'Market Data', description: 'How to access and use market data in your strategies.' },
};
}
const swings = findSwingPoints(td.market.candles);
Check for Gap Up/Down
const candles = td.market.candles;
const current = candles[candles.length - 1];
const previous = candles[candles.length - 2];
// Gap up: current open > previous high
if (current.open > previous.high) {
td.utils.log('Gap up detected', {
gap: ((current.open - previous.high) / previous.high * 100).toFixed(2) + '%'
});
}
// Gap down: current open < previous low
if (current.open < previous.low) {
td.utils.log('Gap down detected', {
gap: ((previous.low - current.open) / previous.low * 100).toFixed(2) + '%'
});
}
Volume Analysis
const candles = td.market.candles;
const recentCandles = candles.slice(-20);
// Average volume
const avgVolume = recentCandles.reduce((sum, c) => sum + c.volume, 0) / recentCandles.length;
// Current volume relative to average
const currentVolume = candles[candles.length - 1].volume;
const volumeRatio = currentVolume / avgVolume;
if (volumeRatio > 2) {
td.utils.log('High volume spike', { ratio: volumeRatio.toFixed(2) });
}
Order Book Data
If order book data is enabled, you can access bid/ask depth and imbalance:
if (td.market.orderbook) {
const { bids, asks, imbalance } = td.market.orderbook;
// Imbalance ranges from -1 to 1
// Positive = more bid volume (buying pressure)
// Negative = more ask volume (selling pressure)
if (imbalance > 0.3) {
td.utils.log('Strong buying pressure', { imbalance });
} else if (imbalance < -0.3) {
td.utils.log('Strong selling pressure', { imbalance });
}
// Access top of book
const bestBid = bids[0]; // { price, amount }
const bestAsk = asks[0]; // { price, amount }
}
Funding Rate (Futures)
For futures trading, funding rate data is available:
if (td.market.funding) {
const { rate, nextTime } = td.market.funding;
// Funding rate is typically expressed as a percentage
// Positive: longs pay shorts
// Negative: shorts pay longs
if (rate > 0.001) { // 0.1%
td.utils.log('High positive funding - consider shorts');
} else if (rate < -0.001) {
td.utils.log('Negative funding - longs receiving payments');
}
// Time until next funding
const hoursUntilFunding = (nextTime - Date.now()) / 3600000;
}
Timeframe Considerations
The candle data reflects your bot's configured timeframe:
| Timeframe | Candle Duration | 200 Candles Covers |
|---|---|---|
| 1m | 1 minute | ~3.3 hours |
| 5m | 5 minutes | ~16.7 hours |
| 15m | 15 minutes | ~50 hours |
| 1h | 1 hour | ~8.3 days |
| 4h | 4 hours | ~33 days |
| 1d | 1 day | ~200 days |
Best Practices
Cache Expensive Calculations
// Store in state to avoid recalculating every spin
const cachedVWAP = td.state.get('vwap');
const lastCalcTime = td.state.get('vwapCalcTime', 0);
// Recalculate every 5 minutes
if (Date.now() - lastCalcTime > 300000) {
const newVWAP = calculateVWAP(td.market.candles.slice(-50));
td.state.set('vwap', newVWAP);
td.state.set('vwapCalcTime', Date.now());
}
Handle Missing Data
// Check for sufficient candle data
if (td.market.candles.length < 50) {
td.utils.log('Insufficient candle data');
return;
}
Use Spread Protection
const maxSpread = td.config.getNumber('MAX_SPREAD_PCT', 0.3);
const spreadPct = (td.market.spread / td.market.price) * 100;
if (spreadPct > maxSpread) {
td.utils.log('Spread too wide, skipping trade');
return;
}
Next Steps
- Indicators Guide - Technical indicators
- Trade Execution - Executing trades
- Examples - Strategy examples