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.

Fieldcontinuous mode (default)on_bar_close mode
td.market.candles, .opens, .highs, .lows, .closes, .volumes, .timestampsIncludes the currently forming candle as the last elementTrimmed to closed bars only — last element is the most recently closed bar
td.market.open, .high, .low, .close, .volumeForming candle's running valuesLast closed bar's final values
td.market.hl2, .hlc3, .ohlc4Derived from forming candleDerived from last closed bar
td.market.priceLive ticker priceLive ticker price (same — this field is always live regardless of mode)
td.market.bid, .ask, .spread, .tickerLiveLive (unchanged by mode)
td.mtf.candles(tf), td.mtf.indicators(tf)Higher-TF candles include the forming higher-TF barHigher-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:

TimeframeCandle Duration200 Candles Covers
1m1 minute~3.3 hours
5m5 minutes~16.7 hours
15m15 minutes~50 hours
1h1 hour~8.3 days
4h4 hours~33 days
1d1 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