Multi-timeframe

Indicators

Multi-timeframe

Subscribe to extra data series — a higher timeframe, a different instrument — and read them alongside your primary, knowing which series fired each callback.

Adding a series

An indicator starts with one series — the chart's, at index 0. Call ctx.AddDataSeries(BarSpecification spec) to subscribe to another. It returns a Task<int> that completes with the new series index. Subscribing a spec you already have is idempotent — the existing index comes back, no second series is allocated.

Build the secondary spec from the primary's instrument (read it off ctx.Bars(0).Spec) so you stay on the same symbol, then choose any bar spec the provider supports — a TimeBarSpec, RangeBarSpec, VolumeBarSpec and so on (all in TradeStrike.Pipeline.Bars).

subscribing a higher timeframeusing TradeStrike.Pipeline.Bars;
using TradeStrike.Pipeline.Indicators;

private int _htf;   // index of the 60-minute series

public override void OnInit(IIndicatorContext ctx)
{
    base.OnInit(ctx);

    // Same instrument as the primary, but a 60-minute period.
    string symbol = ctx.Bars(0).Spec.InstrumentId;
    var spec = new TimeBarSpec(symbol, System.TimeSpan.FromMinutes(60));

    // Blocking on .Result is fine inside OnInit (a historical-phase hook).
    _htf = ctx.AddDataSeries(spec).Result;
}

Knowing which series fired

In a callback, ctx.BarsInProgress is the index of the series whose bar just closed. Branch on it, and read any series with ctx.Bars(index). ctx.CurrentBars[index] gives the last-bar index of any local series independently (mirrors NinjaScript's CurrentBars array) — useful for warmup guards on a series that isn't the one that just fired.

routing per seriespublic override void OnBarUpdate(IIndicatorContext ctx)
{
    if (ctx.BarsInProgress == 0)
    {
        // primary series bar closed
        double primaryClose = ctx.Bars(0)[0].Close;

        // read the most recent CLOSED higher-timeframe bar, if we have enough
        if (ctx.CurrentBars[_htf] >= 0)
        {
            double htfClose = ctx.Bars(_htf)[0].Close;
            _aligned.Append(primaryClose - htfClose);
        }
    }
    else if (ctx.BarsInProgress == _htf)
    {
        // a 60-minute bar just closed — recompute the higher-timeframe trend
    }
}

Async backfill & OnDataSeriesReady

A series added via AddDataSeries backfills asynchronously. The runtime calls OnDataSeriesReady(int seriesIndex, ctx) once that series has finished backfilling and joined the live stream — after which reads on that index return real bars. Override it when you need to do a one-time pass over the secondary's history; for most indicators, simply reading the index in OnBarUpdate (guarded by ctx.CurrentBars[index] >= 0) is enough.

To drop a series later, call ctx.RemoveDataSeries(index). Index 0 (the primary) cannot be removed.

No look-ahead

Higher-timeframe bars become visible only when they close. An in-progress 60-minute bar is not exposed at Bars(_htf)[0] until it finishes, so an MTF indicator cannot accidentally peek at the future. That makes the same indicator safe in strategies and backtests.