Multi-Timeframe Indicators

Build indicators that read data from multiple timeframes for comprehensive market analysis.

Why Multi-Timeframe Analysis?

Professional traders rarely rely on a single timeframe. A common approach is to use a higher timeframe to identify the trend and a lower timeframe to time entries. Multi-timeframe (MTF) indicators automate this by displaying data from one timeframe on a chart of a different timeframe.

For example, you might want to see the Daily RSI value on your H1 chart, or display the H4 moving average on your M15 chart.

The Challenge of MTF Indicators

MTF indicators are harder to build than single-timeframe indicators because:

  • Higher timeframe bars do not align 1:1 with lower timeframe bars
  • One H4 bar covers 16 M15 bars — you must map the value across all 16
  • Data synchronization: the higher timeframe data might not be loaded yet
  • Bars form at different times — you need to match by timestamp, not index

Core Technique: iBarShift

The key function for MTF indicators is finding which bar on the higher timeframe corresponds to each bar on the current timeframe:

// For each bar on current chart, find the corresponding
// bar index on the higher timeframe
int htfBarIndex = iBarShift(_Symbol, higherTF, time[i]);

// Then get the indicator value for that bar
double htfValue = iClose(_Symbol, higherTF, htfBarIndex);

Complete MTF RSI Example

#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_width1  2
#property indicator_minimum 0
#property indicator_maximum 100
#property indicator_level1  30
#property indicator_level2  70

input ENUM_TIMEFRAMES HTF_Period = PERIOD_H4;  // Higher Timeframe
input int RSI_Period = 14;                      // RSI Period

int rsiHandle;
double rsiBuffer[];

int OnInit()
{
    // Create RSI handle on the HIGHER timeframe
    rsiHandle = iRSI(_Symbol, HTF_Period, RSI_Period, PRICE_CLOSE);
    if(rsiHandle == INVALID_HANDLE)
    {
        Print("Failed to create RSI handle for ", EnumToString(HTF_Period));
        return INIT_FAILED;
    }

    SetIndexBuffer(0, rsiBuffer, INDICATOR_DATA);
    IndicatorSetString(INDICATOR_SHORTNAME,
        "MTF RSI(" + IntegerToString(RSI_Period) + ", " +
        EnumToString(HTF_Period) + ")");

    return INIT_SUCCEEDED;
}

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
    // Need enough bars on higher timeframe
    int htfBars = iBars(_Symbol, HTF_Period);
    if(htfBars < RSI_Period + 1)
        return 0;

    // Copy RSI values from higher timeframe
    double htfRSI[];
    ArraySetAsSeries(htfRSI, true);
    int copied = CopyBuffer(rsiHandle, 0, 0, htfBars, htfRSI);
    if(copied <= 0) return 0;

    // Get higher timeframe bar times
    datetime htfTime[];
    ArraySetAsSeries(htfTime, true);
    CopyTime(_Symbol, HTF_Period, 0, htfBars, htfTime);

    int start = (prev_calculated == 0) ? RSI_Period : prev_calculated - 1;

    for(int i = start; i < rates_total; i++)
    {
        // Find which HTF bar this current bar belongs to
        int htfIndex = -1;
        for(int j = 0; j < copied - 1; j++)
        {
            if(time[i] >= htfTime[j + 1] && time[i] < htfTime[j])
            {
                htfIndex = j + 1;  // use completed HTF bar
                break;
            }
        }

        if(htfIndex >= 0 && htfIndex < copied)
            rsiBuffer[i] = htfRSI[htfIndex];
        else
            rsiBuffer[i] = EMPTY_VALUE;
    }

    return rates_total;
}

void OnDeinit(const int reason)
{
    IndicatorRelease(rsiHandle);
}

Key Design Decisions

Use completed bars only: Always display the value from the last completed higher timeframe bar, not the currently forming one. The forming bar's value changes with every tick, which creates misleading signals on the lower timeframe.

Step vs smooth display: This indicator creates a "step" pattern — the value stays flat until a new HTF bar completes, then jumps to the new value. This is the correct representation. Interpolating between values would create fictional data.

⚠️
MTF indicators and backtesting

MTF indicators can produce misleading results in the Strategy Tester if it does not have enough higher timeframe history. Always verify that the HTF data is loaded before relying on backtest results.

Using iCustom for MTF

You can also create an MTF version of any existing custom indicator using iCustom():

// Run any custom indicator on a different timeframe
int customHandle = iCustom(_Symbol, PERIOD_H4,
    "MyIndicator",   // indicator filename (without .ex5)
    param1, param2   // input parameters
);
// Then use CopyBuffer to read its values

Performance Considerations

  • MTF indicators are slower because they access data from multiple timeframes
  • Cache higher timeframe values — do not call CopyBuffer on every tick if the HTF bar has not changed
  • Use the new bar detection pattern to minimize recalculations
  • Limit the lookback — do not copy thousands of HTF bars if you only need the last 100