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.
| Field | continuous mode (default) | 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 (up to 200).
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