Multi-Timeframe Analysis

TradeStaq supports multi-timeframe (MTF) analysis, allowing your strategies to access indicators and price data from multiple timeframes simultaneously.

Overview

Multi-timeframe analysis enables:

  • Higher timeframe confirmation - Confirm entries with longer-term trends
  • Multiple perspectives - See market structure across timeframes
  • Better entries - Time entries on lower timeframes
  • Reduced noise - Filter signals with higher timeframe context

Enabling Multi-Timeframe

Bot Configuration

When creating or editing a bot, enable MTF in settings:

{
    "mtfConfig": {
        "enabled": true,
        "timeframes": ["4h", "1d"],
        "includeIndicators": true
    }
}

Configuration Options

OptionTypeDescription
enabledbooleanEnable MTF data loading
timeframesstring[]Additional timeframes to load
includeIndicatorsbooleanPre-calculate indicators for each timeframe
candleHistory.mtf{ timeframe, candles }[]Candles per higher timeframe, 50 to 5000. Declared on the strategy version, not in mtfConfig (see below)

How many candles each timeframe gets

The candle count is part of the strategy, declared in the version's candleHistory:

{
    "candleHistory": {
        "primary": 470,
        "mtf": [
            { "timeframe": "4h", "candles": 300 },
            { "timeframe": "1d", "candles": 100 }
        ]
    }
}
  • Set it with the Candle History field when you create or edit a strategy (the primary timeframe), or send candleHistory with the strategy when you create or update it through the API (primary and per-timeframe counts).
  • A higher timeframe not listed in mtf gets the primary count. A version that declares nothing gets the default: about 500 candles, capped by what the exchange returns in one request.
  • Live bots and backtests use the same count: td.mtf.candles('4h') holds exactly that many closed candles in both.

Available Timeframes

  • 1m, 5m, 15m, 30m - Intraday
  • 1h, 4h - Short-term
  • 1d, 1w - Long-term

The td.mtf Object

Access multi-timeframe data through td.mtf:

td.mtf = {
    // Get indicators for a specific timeframe
    indicators(timeframe: string): IndicatorValues | null;

    // Get OHLCV candles for a specific timeframe
    candles(timeframe: string): OHLCV[];

    // Get ticker data (last candle OHLC)
    ticker(timeframe: string): { high, low, open, close } | null;

    // List available timeframes
    available(): string[];

    // Check if timeframe is loaded
    has(timeframe: string): boolean;

    // The primary (bot configured) timeframe
    primary: string;

    // Raw MTF data for advanced access
    data: Record<string, MTFData>;
}

Accessing MTF Indicators

Basic Usage

// Your primary timeframe indicators (e.g., 1h)
const rsi1h = td.indicators.rsi;
const macd1h = td.indicators.macd;

// 4-hour timeframe indicators
const rsi4h = td.mtf.indicators('4h')?.rsi;
const macd4h = td.mtf.indicators('4h')?.macd;

// Daily timeframe indicators
const rsiDaily = td.mtf.indicators('1d')?.rsi;
const adxDaily = td.mtf.indicators('1d')?.adx;

Safe Access Pattern

// Always check if timeframe is available
if (td.mtf.has('4h')) {
    const htfIndicators = td.mtf.indicators('4h');
    if (htfIndicators) {
        const rsi4h = htfIndicators.rsi;
        const trend4h = htfIndicators.macd.trend;
        // Use indicators...
    }
}

All Available Indicators per Timeframe

Each timeframe has the full indicator set:

const htf = td.mtf.indicators('4h');

// All these are available:
htf.rsi                    // RSI value
htf.rsiArray               // RSI history
htf.macd                   // { line, signal, histogram, trend }
htf.atr                    // ATR value
htf.atrArray               // ATR history
htf.bbands                 // { upper, middle, lower, width, percentB }
htf.adx                    // { value, diPlus, diMinus, trend }
htf.stochastic             // { k, d, zone }
htf.pivots                 // { pivot, r1, r2, r3, s1, s2, s3 }
htf.mfi                    // Money Flow Index

// Custom indicators - use helper function for safe access
function getHtfIndicator(htf, key) {
    const val = htf?.custom?.[key];
    if (val === undefined || val === null) return 0;
    if (Array.isArray(val)) return val[val.length - 1];
    return val;
}
getHtfIndicator(htf, 'ema_20')   // EMA with period 20
getHtfIndicator(htf, 'sma_50')   // SMA with period 50

Accessing MTF Candles

// Get candles for higher timeframe
const candles4h = td.mtf.candles('4h');
const candlesDaily = td.mtf.candles('1d');

// Latest candle
const last4hCandle = candles4h[candles4h.length - 1];
const lastDailyCandle = candlesDaily[candlesDaily.length - 1];

// Use ticker for quick access to last candle OHLC
const ticker4h = td.mtf.ticker('4h');
if (ticker4h) {
    const { high, low, open, close } = ticker4h;
}

MTF Strategy Patterns

Pattern 1: Higher Timeframe Trend Filter

Only trade in direction of higher timeframe trend.

// Check 4h trend
const htfIndicators = td.mtf.indicators('4h');
const htfTrend = htfIndicators?.macd.trend;
const htfAdx = htfIndicators?.adx;

// Only long if 4h is bullish with strong trend
const bullishHTF = htfTrend === 'bullish' && htfAdx?.trend === 'strong';
const bearishHTF = htfTrend === 'bearish' && htfAdx?.trend === 'strong';

// Primary timeframe entry signal
const rsi = td.indicators.rsi;
const oversold = rsi < 30;
const overbought = rsi > 70;

// Trade with HTF confirmation
if (!td.position.hasPosition) {
    if (oversold && bullishHTF) {
        td.trade.buy({
            amountPercent: 50,
            reason: 'RSI oversold + 4h bullish trend'
        });
    }

    if (overbought && bearishHTF) {
        td.trade.sell({
            amountPercent: 50,
            reason: 'RSI overbought + 4h bearish trend'
        });
    }
}

Pattern 2: Multi-Timeframe RSI Confluence

Enter when RSI is oversold on multiple timeframes.

const rsi1h = td.indicators.rsi;
const rsi4h = td.mtf.indicators('4h')?.rsi || 50;
const rsiDaily = td.mtf.indicators('1d')?.rsi || 50;

// Count oversold timeframes
let oversoldCount = 0;
if (rsi1h < 30) oversoldCount++;
if (rsi4h < 35) oversoldCount++;
if (rsiDaily < 40) oversoldCount++;

// Strong buy signal when multiple timeframes oversold
if (!td.position.hasPosition && oversoldCount >= 2) {
    td.trade.buy({
        amountPercent: 50,
        stopLoss: td.market.price * 0.97,
        reason: `${oversoldCount} timeframes oversold`
    });
}

// Log confluence
td.utils.log('RSI Confluence', {
    rsi1h,
    rsi4h,
    rsiDaily,
    oversoldCount
});

Pattern 3: Timeframe Alignment Entry

Entry when trend aligns across all timeframes.

// Get trends from multiple timeframes
const trend1h = td.indicators.macd.trend;
const trend4h = td.mtf.indicators('4h')?.macd.trend;
const trendDaily = td.mtf.indicators('1d')?.macd.trend;

// Check alignment
const allBullish = trend1h === 'bullish' &&
                   trend4h === 'bullish' &&
                   trendDaily === 'bullish';

const allBearish = trend1h === 'bearish' &&
                   trend4h === 'bearish' &&
                   trendDaily === 'bearish';

// Only trade when fully aligned
if (!td.position.hasPosition) {
    if (allBullish && td.indicators.rsi < 50) {
        td.trade.buy({
            amountPercent: 75,  // Higher confidence = bigger size
            reason: 'All timeframes bullish aligned'
        });
    }

    if (allBearish && td.indicators.rsi > 50) {
        td.trade.sell({
            amountPercent: 75,
            reason: 'All timeframes bearish aligned'
        });
    }
}

Pattern 4: Daily Support/Resistance

Use daily pivot points for entry/exit levels.

// Get daily pivots
const dailyPivots = td.mtf.indicators('1d')?.pivots;
const currentPrice = td.market.price;

if (dailyPivots && !td.position.hasPosition) {
    const { pivot, s1, s2, r1, r2 } = dailyPivots;

    // Long near support
    if (currentPrice <= s1 * 1.005 && td.indicators.rsi < 35) {
        td.trade.buy({
            amountPercent: 50,
            stopLoss: s2 * 0.995,
            takeProfit: pivot,
            reason: `Long at daily S1 (${s1.toFixed(2)})`
        });
    }

    // Short near resistance
    if (currentPrice >= r1 * 0.995 && td.indicators.rsi > 65) {
        td.trade.sell({
            amountPercent: 50,
            stopLoss: r2 * 1.005,
            takeProfit: pivot,
            reason: `Short at daily R1 (${r1.toFixed(2)})`
        });
    }
}

Pattern 5: ATR-Based Position Sizing with HTF

Use higher timeframe ATR for volatility-adjusted sizing.

// Use 4h ATR for more stable volatility reading
const atr4h = td.mtf.indicators('4h')?.atr || td.indicators.atr;
const entryPrice = td.market.price;

// Calculate stop based on 4h ATR
const stopDistance = atr4h * 1.5;
const stopLoss = entryPrice - stopDistance;
const takeProfit = entryPrice + (stopDistance * 2);

// Risk-based entry
if (!td.position.hasPosition && td.indicators.rsi < 30) {
    td.trade.buyRisk({
        riskPercent: 2,
        stopLoss,
        takeProfit,
        reason: 'Risk entry with 4h ATR stops'
    });
}

Complete MTF Strategy

// ═══════════════════════════════════════════════════════════════
// Multi-Timeframe Trend Following Strategy
// Primary: 1h | Confirmation: 4h | Bias: Daily
// ═══════════════════════════════════════════════════════════════

// ─── Configuration ───────────────────────────────────────────────
const RSI_OVERSOLD = td.config.getNumber('RSI_OVERSOLD', 30);
const RSI_OVERBOUGHT = td.config.getNumber('RSI_OVERBOUGHT', 70);
const RISK_PERCENT = td.config.getNumber('RISK_PERCENT', 2);

// ─── Helper for custom indicators ────────────────────────────────
function getIndicator(key) {
    const val = td.indicators.custom[key];
    if (val === undefined || val === null) return 0;
    if (Array.isArray(val)) return val[val.length - 1];
    return val;
}

// ─── Get Multi-Timeframe Data ────────────────────────────────────
const primary = {
    rsi: td.indicators.rsi,
    macd: td.indicators.macd,
    atr: td.indicators.atr,
    ema9: getIndicator('ema_9'),
    ema21: getIndicator('ema_21'),
    openGraph: { title: 'Multi-Timeframe Analysis', description: 'How to use multiple timeframes in custom strategies.' },
};

const htf4h = td.mtf.indicators('4h');
const htfDaily = td.mtf.indicators('1d');

// Ensure MTF data is available
if (!htf4h || !htfDaily) {
    td.utils.log('MTF data not available, skipping');
    return;
}

// ─── Define Trend Bias ───────────────────────────────────────────
const dailyTrend = htfDaily.macd.trend;
const htfTrend = htf4h.macd.trend;
const htfTrendStrong = htf4h.adx.trend === 'strong';

const bullishBias = dailyTrend === 'bullish' || htfTrend === 'bullish';
const bearishBias = dailyTrend === 'bearish' || htfTrend === 'bearish';

// ─── Entry Logic ─────────────────────────────────────────────────
if (!td.position.hasPosition) {
    // EMA crossover on primary timeframe
    const ema9 = getIndicator('ema_9');
    const ema21 = getIndicator('ema_21');
    const prevEma9 = td.state.get('prevEma9', ema9);
    const prevEma21 = td.state.get('prevEma21', ema21);

    const emaCrossUp = prevEma9 <= prevEma21 && ema9 > ema21;
    const emaCrossDown = prevEma9 >= prevEma21 && ema9 < ema21;

    // Store for next spin
    td.state.set('prevEma9', ema9);
    td.state.set('prevEma21', ema21);

    // Long entry
    if (emaCrossUp && bullishBias && primary.rsi < 60) {
        const atrStop = primary.atr * 1.5;

        td.trade.buyRisk({
            riskPercent: RISK_PERCENT,
            stopLoss: td.market.price - atrStop,
            takeProfit: td.market.price + (atrStop * 2),
            reason: `Long: EMA cross + ${dailyTrend} daily + ${htfTrend} 4h`
        });

        td.utils.log('Long Entry', {
            rsi: primary.rsi,
            dailyTrend,
            htfTrend,
            htfTrendStrong
        });
    }

    // Short entry
    if (emaCrossDown && bearishBias && primary.rsi > 40) {
        const atrStop = primary.atr * 1.5;

        td.trade.sellRisk({
            riskPercent: RISK_PERCENT,
            stopLoss: td.market.price + atrStop,
            takeProfit: td.market.price - (atrStop * 2),
            reason: `Short: EMA cross + ${dailyTrend} daily + ${htfTrend} 4h`
        });
    }
}

// ─── Position Management ─────────────────────────────────────────
if (td.position.hasPosition) {
    const profit = td.position.pnlPercent;
    const side = td.position.side;

    // Exit if higher timeframe trend reverses
    const trendReversed =
        (side === 'long' && htfTrend === 'bearish' && htfTrendStrong) ||
        (side === 'short' && htfTrend === 'bullish' && htfTrendStrong);

    if (trendReversed && profit > 0) {
        td.trade.close('HTF trend reversed');
    }

    // Move to breakeven at 2% profit
    if (profit >= 2 && !td.state.get('breakeven')) {
        td.trade.breakeven({ breakevenOffset: 0.1 });
        td.state.set('breakeven', true);
    }

    // Scale out at profit targets
    if (profit >= 3 && !td.state.get('scaled1')) {
        td.trade.close('Scale out 33% at 3%');
        td.state.set('scaled1', true);
    }
}

// ─── Logging ─────────────────────────────────────────────────────
td.utils.log('MTF Analysis', {
    primary: {
        rsi: primary.rsi.toFixed(2),
        macdTrend: primary.macd.trend,
    },
    htf4h: {
        rsi: htf4h.rsi.toFixed(2),
        macdTrend: htf4h.macd.trend,
        adxTrend: htf4h.adx.trend,
    },
    daily: {
        rsi: htfDaily.rsi.toFixed(2),
        macdTrend: htfDaily.macd.trend,
    },
    bias: bullishBias ? 'BULLISH' : bearishBias ? 'BEARISH' : 'NEUTRAL'
});

Best Practices

1. Don't Over-Complicate

// Good: 2-3 timeframes
const timeframes = ['4h', '1d'];

// Avoid: Too many timeframes causes confusion
const timeframes = ['5m', '15m', '30m', '1h', '4h', '1d', '1w'];

2. Use HTF for Direction, LTF for Timing

// Daily/4h for trend direction
const trendDirection = htfDaily.macd.trend;

// 1h/15m for entry timing
const entrySignal = td.indicators.rsi < 30;

// Combine both
if (trendDirection === 'bullish' && entrySignal) {
    td.trade.buy();
}

3. Confirm with Multiple Indicators

// Don't rely on single HTF indicator
const htfBullish =
    htf4h.macd.trend === 'bullish' &&
    htf4h.rsi > 50 &&
    htf4h.adx.value > 25;

4. Handle Missing Data

// Always check for null
const htfRsi = td.mtf.indicators('4h')?.rsi;
if (htfRsi === undefined || htfRsi === null) {
    td.utils.log('HTF data missing, using default');
    return; // or use fallback
}

Troubleshooting

MTF Data Not Available

Cause: MTF not enabled in bot config or timeframes not specified.

Solution: Check bot settings:

{
    "mtfConfig": {
        "enabled": true,
        "timeframes": ["4h", "1d"],
        "includeIndicators": true
    }
}

Indicators Returning null

Cause: Insufficient candle data for indicator calculation.

Solution: Use safe access with fallbacks:

const htfRsi = td.mtf.indicators('4h')?.rsi ?? 50;

Performance Issues

Cause: Too many timeframes or indicators.

Solution: Limit to 2-3 additional timeframes and use includeIndicators: false if you only need candle data.


S/R Zone Analysis (td.zones)

In addition to MTF indicators, TradeStaq offers automatic multi-timeframe support and resistance zone analysis. When enabled in the Multi-Timeframe Setup step, the platform:

  1. Dynamically selects 3 candle timeframes (short, medium, long) from your exchange
  2. Generates up to 20 time intervals spanning from intraday to yearly windows
  3. Computes classic pivot points (P, S1-S3, R1-R3) for each interval
  4. Checks if the current price is near any S/R level

Enabling Zones

Enable S/R Zone Analysis in the strategy wizard's Multi-Timeframe Setup step (same step as MTF configuration). Configure:

  • Proximity Type: atr (volatility-based), percentage (fixed %), or both
  • ATR Multiplier: How close price must be to count as "near" (uses primary TF ATR)
  • % Threshold: Fixed percentage proximity threshold

Quick Example

const zones = td.zones;
if (!zones) return; // Not enabled

const supportDepth = zones.withinSupportCount;   // 0 to zones.totalIntervals
const resistanceDepth = zones.withinResistanceCount;

// Buy near strong multi-timeframe support confluence
if (!td.position.hasPosition && supportDepth >= 5 && td.indicators.rsi < 40) {
    td.trade.buy({
        amountPercent: 5,
        stopLoss: zones.strongestSupport * 0.99,
        takeProfit: zones.strongestResistance,
        reason: `Support confluence: ${supportDepth}/${zones.totalIntervals}`
    });
}

For full details on td.zones properties, see the TD API Reference.


Next Steps