Bot development API

Use the Kaizen indicators from your own NinjaScript strategies through a public API. Your strategy hosts the indicator and reads its values directly, no DLL access needed.

Updated: September 10, 2026

The Kaizen indicators expose a public API for use from your own NinjaScript strategies. You do not need to open or decompile the DLL — your strategy hosts the indicator and reads its data directly.

using NinjaTrader.NinjaScript.Indicators.Kaizen;

Omitting this using produces error CS0246.

Six rules that decide whether it works

Break any of these and the API returns empty data without an error message. That silence is what makes them expensive — the strategy runs, the log stays clean, and every value is zero.

  1. Always use the helper method, never new. new KaizenFootprint() does not pass through NinjaTrader’s caching and lifecycle wiring; the indicator hangs in (Calculating…) forever and every read returns 0 or null. Use KaizenFootprint(...) with all its parameters. There are no parameterless overloads — the helper takes every property, from 3 parameters (SuperTrend) to 37 (Footprint).

  2. Create it in State.DataLoaded, not State.Configure. AddChartIndicator(...), if you use it, belongs there too.

  3. Call Update() on indicators that have no plots. NinjaTrader drives hosted indicators lazily. For an indicator with plots, touching Values[0][0] triggers the update chain; an indicator without plots has nothing to touch, so its OnBarUpdate only runs when Tick Replay happens to drive it. 11 of the 17 indicators have no plots — see the table below. Without Update() their engines never receive a single bar.

  4. Filter for look-ahead when reading zone or level lists. These lists carry the current state of every zone, including ones that did not exist yet at the bar you are evaluating, and IsActive reflects today rather than that bar. In a backtest this silently becomes look-ahead. Filter on the timestamp or the origin bar index — both examples below show the pattern.

  5. Multi-timeframe: the host must preload the series. If you set useMTF: true, your strategy must load that same series in its State.Configure, e.g. AddDataSeries(BarsPeriodType.Minute, 30). A hosted NinjaScript may not load its own data. Skip this and NinjaTrader disables the strategy before the first bar — you get 0 trades, empty output files, and the error appears only in the NinjaTrader log, never on screen.

  6. An invalid license returns empty data, not an error. Every API returns 0, null, or an empty list if the licence check fails. Confirm the indicator works on a chart first.

Example: Supply/Demand zones combined with VWAP

Both examples below assume the standard using block that NinjaTrader inserts when you create a new strategy (it already contains System.Windows.Media for Brushes and NinjaTrader.Gui for DashStyleHelper) — plus the Kaizen namespace shown above.

public class MySupplyDemandBot : Strategy
{
    private KaizenSupplyDemand _sd;
    private KaizenVWAP         _vwap;

    protected override void OnStateChange()
    {
        if (State == State.SetDefaults)
        {
            Name                = "MySupplyDemandBot";
            Calculate           = Calculate.OnBarClose;
            BarsRequiredToTrade = 25;
        }
        else if (State == State.DataLoaded)
        {
            _sd = KaizenSupplyDemand(
                false,                        // useMTF
                BarsPeriodType.Minute, 30,    // mtfBarType, mtfBarPeriod (ignored when useMTF = false)
                Brushes.DodgerBlue,           // demandColor
                Brushes.OrangeRed,            // supplyColor
                0.40f, 0.15f, 0.10f, 0.05f,   // opacities: active line/area, broken line/area
                1,                            // zoneLineWidth
                false,                        // extendZones
                false, true,                  // hideActiveZones, hideBrokenZones
                200);                         // maxZoneCount

            _vwap = KaizenVWAP(
                KVWAPStyle.Directional,       // vWAPStyle
                1.28, 2.01, 2.51,             // band 1 / 2 / 3 multipliers
                5,                            // bandOpacity
                DashStyleHelper.Dash);        // bandLineStyle
        }
    }

    protected override void OnBarUpdate()
    {
        if (CurrentBar < 25) return;

        _sd.Update();                         // required: Supply/Demand has no plots

        double vwap   = _vwap.Values[0][0];
        double lower1 = _vwap.Values[2][0];   // lower band at 1.28 SD
        if (vwap <= 0) return;

        IReadOnlyList<KaizenSupplyDemandZoneInfo> zones = _sd.Zones;
        if (zones == null) return;

        for (int i = 0; i < zones.Count; i++)
        {
            KaizenSupplyDemandZoneInfo z = zones[i];

            // anti look-ahead: the zone must already exist, and must not have
            // been broken, at the bar being evaluated
            if (z.Date > Time[0]) continue;
            if (!z.IsActive && z.DateInactive.HasValue && z.DateInactive.Value <= Time[0])
                continue;

            if (z.Type == KaizenSupplyDemandZoneType.Demand
                && Close[0] <= z.High && Close[0] >= z.Low
                && Close[0] < lower1)
            {
                EnterLong(1, "LongDemandBelowVWAP");
            }
        }
    }
}

Example: Stacked Imbalance zones

Note the parameter list — the helper has no short form. The values below are the indicator’s own defaults.

public class MyStackedImbalanceBot : Strategy
{
    private KaizenStackedImbalance _si;

    protected override void OnStateChange()
    {
        if (State == State.SetDefaults)
        {
            Name                = "MyStackedImbalanceBot";
            Calculate           = Calculate.OnBarClose;
            BarsRequiredToTrade = 20;
        }
        else if (State == State.DataLoaded)
        {
            _si = KaizenStackedImbalance(
                300,                          // imbalanceRatio (%)
                3,                            // minStackSize
                10,                           // volumeFilter
                false,                        // ignoreZeroValues
                true, 6, 2,                   // showTickMarks, tickMarkWidthPx, tickMarkThickness
                true, 20,                     // showZones, zoneOpacity
                KSIZoneEndCondition.Touch,    // zoneEndCondition
                true, 2, 10,                  // showAbsorption, absorptionDepth, absorptionMinVolume
                KBTMarkerShape.Diamond, 10,   // absorptionMarkerShape, absorptionMarkerSize
                500);                         // maxStoredBars
        }
    }

    protected override void OnBarUpdate()
    {
        if (_si == null || CurrentBar < 20) return;

        _si.Update();                         // required: no plots

        IReadOnlyList<KaizenStackedImbalance.ImbalanceZone> zones = _si.ActiveZones;
        if (zones == null) return;

        for (int i = 0; i < zones.Count; i++)
        {
            KaizenStackedImbalance.ImbalanceZone z = zones[i];

            // anti look-ahead
            if (z.OriginBarIndex > CurrentBar) continue;
            if (z.MitigationBarIndex >= 0 && z.MitigationBarIndex <= CurrentBar) continue;

            if (Close[0] >= z.PriceLow && Close[0] <= z.PriceHigh)
            {
                Print("Price inside " + (z.IsBullish ? "bullish" : "bearish") + " zone "
                      + z.PriceLow + " - " + z.PriceHigh);
            }
        }
    }
}

Which indicator needs what

Update() is required whenever the indicator has no plots. Tick Replay must be enabled on the chart for the order-flow indicators.

IndicatorPlotsTick ReplayUpdate() requiredHelper parameters
KaizenBigTradesyesyes15
KaizenCVD3yesno9
KaizenClusterSearchyesyes12
KaizenDeltaCandle2yesno8
KaizenFootprintyesyes37
KaizenNakedLevelsnoyes11
KaizenOrderInfonoyes5
KaizenReversalIndicatoryesyes25
KaizenSpeedOfTape2yesno7
KaizenStackedImbalanceyesyes16
KaizenStatGrid2yesno25
KaizenStatsProfilenoyes18
KaizenSuperTrend2nono3
KaizenSupplyDemandnoyes14
KaizenTPOnoyes34
KaizenVWAP8nono6
KaizenVolumeProfileyesyes23

KaizenAlertLine and KaizenDeltaProfile have no bot API. They expose configuration properties only.

Available public APIs

IndicatorMemberReturns
KaizenSupplyDemand.ZonesIReadOnlyList<KaizenSupplyDemandZoneInfo> — all zones, active and broken
.ActiveSupplyCount / .ActiveDemandCountint
KaizenVWAPValues[0][0]Values[7][0]VWAP, 3 band pairs, prior-session VWAP — see below
.CurrentVWAP / .CurrentStdDev / .PriorSessionVWAPdouble — current bar only
KaizenStackedImbalance.ActiveZonesIReadOnlyList<ImbalanceZone>all zones, active and mitigated
.HistoricalZonesIReadOnlyList<ImbalanceZone> — mitigated zones only
.RecentAbsorptionsIReadOnlyList<KaizenAbsorptionInfo>
.LastImbalanceBarIndexint-1 if none yet
KaizenFootprint.GetBarDelta(barIndex)long — signed delta for the bar
.GetBarPOCPrice(barIndex)double
.GetBullishImbalancePrices(barIndex) / .GetBearishImbalancePrices(barIndex)List<double>
.GetBullishAbsorptionPrices(barIndex) / .GetBearishAbsorptionPrices(barIndex)List<double>
.GetFootprintForBar(barIndex)KaizenFootprintBarInfo — see limitation below
.RecentAbsorptionsIReadOnlyList<KaizenAbsorptionInfo>
.UnfinishedLevelsIReadOnlyList<KaizenFootprintUnfinishedLevelInfo>
KaizenVolumeProfile.CurrentPOC / .CurrentVAH / .CurrentVALdouble
.CurrentPriceVolumeSortedDictionary<double, long> — price → volume
.AllProfilesIReadOnlyList<KaizenVolumeProfileInfo>
.NakedLevelsReadOnlyCollection<KaizenVolumeProfileNakedLevelInfo>
KaizenTPO.SinglePrintsIReadOnlyList<KaizenSinglePrintInfo>
.AllProfilesIReadOnlyList<KaizenTpoProfileInfo>
.CurrentPOC / .CurrentVAH / .CurrentVALdouble
.CurrentIBHigh / .CurrentIBLowdouble — initial balance
KaizenCVD.GetCvd(barsAgo) / .GetCvdAt(barIndex)double — also Open/High/Low variants
.SessionCvddouble
.CvdCandleOpen / High / Low / Closedouble
KaizenDeltaCandle.GetDeltaClose(barsAgo) / .GetDeltaCloseAt(barIndex)double — also Open/High/Low variants
.CurrentBarDeltalong
.RecentAbsorptionsIReadOnlyList<KaizenAbsorptionInfo>
KaizenBigTrades.GetTradesForBar(barIndex)IReadOnlyList<KaizenBigTradeInfo>
KaizenClusterSearch.GetClustersForBar(barIndex)IReadOnlyList<KaizenClusterHitInfo>
KaizenNakedLevels.NakedLevelsIReadOnlyList<KaizenNakedLevelInfo>
.CurrentSessionHigh / Low, .CurrentDailyHigh / Low, .CurrentWeeklyHigh / Lowdouble
KaizenStatsProfile.GetTodayTouchProbability(levelKey)double?
.GetTodaySampleSize(levelKey) / .GetLevelPrice(levelKey)int? / double?
.IsLevelTouchedToday(levelKey)bool
.TodayAmtClass / .StatsInstrumentstring
.StatsLoaded / .StatsSessionCountbool / int
KaizenSuperTrend.TrendDirectionint+1 up, -1 down, 0 before the first calculation
.CurrentValue / .UpperBand / .LowerBanddouble
.FlippedToUpThisBar / .FlippedToDownThisBarbool
KaizenReversalIndicator.GetSignal(barIndex)KaizenReversalSignal
.IsBuySignal(barIndex) / .IsSellSignal(barIndex)bool
.CurrentATR / .CurrentEffectiveFilterdouble
KaizenSpeedOfTape.GetSpeed(barsAgo) / .GetSpeedAt(barIndex)double
.GetIsHighlight(barsAgo) / .GetIsHighlightAt(barIndex)bool
.CurrentSpeedSum / .CurrentFilterValue / .CurrentIsBullishdouble / double / bool
KaizenStatGrid.GetStatsForBar(barIndex)KaizenStatGridBarInfo — delta, volume, buy/sell split, duration
KaizenOrderInfo.GetRenderedOrders()IReadOnlyDictionary<double, KaizenOrderLevelInfo>

KaizenVWAP plot indices: 0 VWAP · 1/2 band 1 upper/lower · 3/4 band 2 · 5/6 band 3 · 7 prior-session VWAP.

Data structures

// KaizenSupplyDemand.Zones
public sealed class KaizenSupplyDemandZoneInfo
{
    public double    High, Low;
    public int       BarIndex, EndBarIndex;
    public KaizenSupplyDemandZoneType   Type;      // Supply, Demand
    public KaizenSupplyDemandZoneOrigin Origin;    // Regular, Continuation
    public bool      IsActive;
    public DateTime  Date;                         // when the zone formed
    public DateTime? DateInactive;                 // when it broke; null if still active
}

// KaizenStackedImbalance.ActiveZones / .HistoricalZones
public sealed class ImbalanceZone
{
    public int    OriginBarIndex;
    public int    MitigationBarIndex;   // -1 while still active
    public double PriceHigh, PriceLow;
    public bool   IsBullish;
    public bool   IsActive;
}

// KaizenBigTrades.GetTradesForBar(...)
public sealed class KaizenBigTradeInfo
{
    public int    BarIndex;
    public double Price;
    public long   Volume, TickPrice, Timestamp;
    public int    Side;                 // +1 buy aggressor, -1 sell aggressor
    public bool   IsBuyAggressor, IsSellAggressor;
}

// KaizenTPO.SinglePrints
public sealed class KaizenSinglePrintInfo
{
    public double Price;
    public int    OriginBarIndex, MitigationBarIndex, OwnerProfileStartBar;
    public bool   IsActive;
}

When it silently returns nothing

SymptomCause
A list is always empty, no error anywhereUpdate() missing (rule 3), or the licence is not valid for that indicator
Indicator stuck in (Calculating…), all reads 0new instead of the helper method (rule 1)
Strategy runs but makes 0 trades, output files contain headers onlyMTF series not preloaded by the host (rule 5); the error is in the NinjaTrader log
Backtest looks excellent, live does notLook-ahead through unfiltered zone lists (rule 4)
CS0246 on the indicator nameusing NinjaTrader.NinjaScript.Indicators.Kaizen; missing

Important notes

  • Tick Replay must be enabled on the chart for the order-flow indicators (see the table above).
  • Namespace: always NinjaTrader.NinjaScript.Indicators.Kaizen, never .Custom.
  • Compiling: NinjaScript Editor → New → Strategy → paste your code → F5.
  • Licence: your key must cover every Kaizen indicator you reference.
  • Bar index under OnBarClose hosts (fixed in v2.3): when a Kaizen indicator was hosted inside a strategy running Calculate = OnBarClose, the bot APIs returned bar data off by one index — “this bar” gave the next bar’s values. If you built rules on those values and backtested before v2.3, review the results: the offset acted like look-ahead.
  • Known limitation — Footprint summary values: CurrentBarDelta, CurrentBarTotalVolume, CurrentBarPOC and the summary fields of GetFootprintForBar(...) stay 0 in a strategy. The engine computes them in OnRender, which a strategy host does not reliably trigger. Use RecentAbsorptions instead — it is populated in OnBarUpdate. The per-level methods (GetBarDelta, GetBarPOCPrice, the imbalance and absorption price lists) are unaffected.
  • Minimum version: v2.3. Earlier releases had different helper signatures and the bar-index offset described above.