// Holdout API example — an OPEN, editable NinjaTrader 8 strategy that reads Holdout Pro indicators from code. // Free to copy, change and build on. Needs Holdout Pro installed and activated (the indicators it calls check the license). // // What it does (a starting point, not a promise): // • Takes the Holdout ORB breakout signal (the ORB's own CVD and day-direction checks already applied) on TREND DAYS only: // the ORB's trend-day gate skips a break once price has traded back to the prior close (the gap filled), // • stops at the ORB's range stop, then trails at the Core Trend stop of a 15-MINUTE series once the trend is with the trade, // • and is flat by 15:55 New York time (or at the session end on early-close days). // • Optional extra filters (off by default): the Multi-Timeframe Panel's score and the Market Structure bias. // // How it tested (offline replay of exactly these rules, 1 MNQ / 1 MES, $2.50 + 1 tick slippage per trade, 2014-04 → 2026-09): // NQ 752 trades, profit factor 1.56, max drawdown about $1,200 (every year but 2016 positive, 2026 to September +$2,900); // ES PF 1.46. The previous version of this example trailed Core Trend on the CHART's bars: fine on 5- and 15-minute charts, // but on a 1-minute chart the trail was so tight it lost money (NQ 2026 PF 0.50). The MTF and structure filters did not add // anything on top of the trend-day gate (MTF ≥ 3 roughly halved the profit), so they are options here, not defaults. // Past results don't predict future ones; test in Markers mode and on a Sim account first. // // Install: NinjaTrader → New → NinjaScript Editor → right-click Strategies → New Strategy, name it HoldoutApiExample, replace // everything with this file and press F5. Or copy it into Documents\NinjaTrader 8\bin\Custom\Strategies and compile. // Apply it to a 1-, 5- or 15-minute chart of a futures contract with an extended-hours session template. Markers mode (default) // only draws what it would do; Live places orders, so test on a Sim account first. // // Reading Holdout values: every Holdout indicator can be created in code with its name, e.g. HoldoutMarketStructure(...), passing // its settings in the order shown in the NinjaScript Editor's IntelliSense. Pass a series first (here BarsArray[3]) to run it on // that timeframe instead of the chart's. NinjaTrader requires your strategy to AddDataSeries() (in State.Configure) every extra // series an indicator loads; the website's API page lists them per indicator. // Each exposes Series you can index like Close: // HoldoutOrb: Signal, SkippedSignal, TradePosition, RangeHigh, RangeLow, TradeStop, TradeTarget // HoldoutNoiseArea: Signal, SkippedSignal, TradePosition, Upper, Lower, Vwap // HoldoutMarketStructure: Bias, BreakSignal, LastSwingHigh, LastSwingLow // HoldoutCoreTrend: Direction, TrendLine, TrailStop // HoldoutMtfPanel: Score, Trend5m, Trend15m, Trend1h, Trend4h, TrendDaily (+ StructureState, current bar) // HoldoutOrderFlow: BarDelta, Cvd, IsTrueDelta, AggSignal, AbsSignal, DivergenceSignal // HoldoutCvd: CvdSeries // HoldoutSessionLevels: PriorDayHigh/Low/Close, OvernightHigh/Low, AsiaHigh/Low, LondonHigh/Low, PremarketHigh/Low, // InitialBalanceHigh/Low, DayOpen, PriorWeekHigh/Low // HoldoutVolumeProfile: Poc, ValueAreaHigh, ValueAreaLow, LvnAbove, LvnBelow, PriorPoc, PriorValueAreaHigh/Low, // NakedPocAbove, NakedPocBelow // HoldoutVwapBands: its plots (VWAP and bands) // HoldoutSupplyDemand: NearestDemandTop/Bottom, NearestSupplyBottom/Top, InZone (current bar only) // Full reference: https://holdouttrading.com/developers/ #region Using declarations using System; using System.ComponentModel.DataAnnotations; using System.Windows.Media; using NinjaTrader.Cbi; using NinjaTrader.Data; using NinjaTrader.Gui; using NinjaTrader.NinjaScript; using NinjaTrader.NinjaScript.Indicators; using NinjaTrader.NinjaScript.DrawingTools; #endregion namespace NinjaTrader.NinjaScript.Strategies { public class HoldoutApiExample : Strategy { private HoldoutOrb orb; private HoldoutMtfPanel mtf; private HoldoutMarketStructure ms; private HoldoutCoreTrend ct; private TimeZoneInfo chartTz, etTz; private int side; private double stop; private DateTime entryTime; private double entryPrice; protected override void OnStateChange() { if (State == State.SetDefaults) { Name = "Holdout API Example"; Description = "Example: Holdout ORB signals on trend days, trailed with Core Trend on 15-minute bars. Optional MTF Panel and Market Structure filters."; Calculate = Calculate.OnBarClose; EntriesPerDirection = 1; IsExitOnSessionCloseStrategy = true; BarsRequiredToTrade = 20; LiveOrders = false; Contracts = 1; TrendDayOnly = true; MinScore = 0; SkipStructureAgainst = false; ChartTimeZone = "Eastern Standard Time"; } else if (State == State.Configure) { // NinjaTrader rule: a strategy must load every extra data series the indicators it uses load themselves, here, // before they are created. The ORB's CVD check (1-minute) and the MTF Panel's five timeframes: AddDataSeries(BarsPeriodType.Minute, 1); // BarsArray[1] AddDataSeries(BarsPeriodType.Minute, 5); // BarsArray[2] AddDataSeries(BarsPeriodType.Minute, 15); // BarsArray[3] — also the Core Trend trail's timeframe AddDataSeries(BarsPeriodType.Minute, 60); // BarsArray[4] AddDataSeries(BarsPeriodType.Minute, 240); // BarsArray[5] AddDataSeries(BarsPeriodType.Day, 1); // BarsArray[6] } else if (State == State.DataLoaded) { // Each indicator's default settings, except: the ORB's CVD source is BarProxy (the 1-minute estimate the rule was // tested with) so the strategy doesn't have to load tick data, and its trend-day gate follows TrendDayOnly. orb = HoldoutOrb(platformTimeZoneId: ChartTimeZone, mode: OrbMode.Breakout, orMinutes: 15, dayFilter: HoldoutDayFilter.GapAndOvernight, trendDayGate: TrendDayOnly, vixGate: HoldoutVixGate.Off, breakCloseMinutes: 15, targetR: 1.5, useMeasuredTarget: false, breakevenR: 1.0, exitMode: OrbExit.Target, tickConfirmation: false, showSkippedBreaks: true, cvdConfirmation: true, cvdSource: OrbCvdSource.BarProxy, showStats: false, showStatus: false, showTargetPreview: false, costTicks: 2, entryWindowMin: 210, stopBufferTicks: 2, showSignals: false, filterExit: true, filterExitBufferTicks: 0); mtf = HoldoutMtfPanel(corner: PanelCorner.TopRight, panelScale: 100, emaFast: 20, emaSlow: 50, structPivot: 5); ms = HoldoutMarketStructure(strength: 3, showZigzag: true, showSwingLabels: true, showBosChoch: true, showDevelopingLeg: true, labelSize: 11, maxSwings: 300); // Core Trend on the 15-minute series, whatever the chart's timeframe: on 1-minute bars its trailing stop sits too // close to price for a breakout to breathe. (An indicator on a secondary series isn't drawn on the chart; add Holdout // Core Trend to a 15-minute chart to see the line it trails at.) ct = HoldoutCoreTrend(BarsArray[3], lineSmoothing: 3, atrLength: 10, atrMult: 2.0, cloudMult: 2.0, cloudOpacity: 15, cloudLayers: 5, showCloud: false, showSignals: false, showReversalLabels: false); AddChartIndicator(ms); // optional: show what the strategy reads try { chartTz = TimeZoneInfo.FindSystemTimeZoneById(ChartTimeZone); } catch { chartTz = TimeZoneInfo.Local; } etTz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); } } protected override void OnBarUpdate() { if (BarsInProgress != 0 || CurrentBar < BarsRequiredToTrade || CurrentBars[3] < 1) return; // decide on the chart's own bars only DateTime et = TimeZoneInfo.ConvertTime(DateTime.SpecifyKind(Time[0], DateTimeKind.Unspecified), chartTz, etTz); // ── manage an open trade: stop, Core Trend trail, end of day ── if (side != 0) { // early-close days have no 15:55 bar: close the trade at the last bar of the session that ended if (Bars.IsFirstBarOfSession) { Exit(Close[1], "session end"); return; } bool stopHit = side > 0 ? Low[0] <= stop : High[0] >= stop; bool late = et.TimeOfDay >= new TimeSpan(15, 55, 0); if (stopHit || late) { Exit(stopHit ? stop : Close[0], stopHit ? "stop" : "close"); return; } if (ct.Direction[0] == side) // 15-minute trend with the trade: trail at its Core Trend stop, never loosen { double trail = ct.TrailStop[0]; if (side > 0 ? trail > stop : trail < stop) { stop = trail; if (LiveOrders) SetStopLoss("API", CalculationMode.Price, stop, false); } } return; } // ── a new ORB signal, filtered ── int signal = (int)orb.Signal[0]; if (signal == 0) return; double score = mtf.Score[0], bias = ms.Bias[0]; bool mtfOk = MinScore <= 0 || (signal > 0 ? score >= MinScore : score <= -MinScore); bool msOk = !SkipStructureAgainst || bias == 0 || bias == signal; string why = (mtfOk ? "" : "MTF score " + score.ToString("+0;-0") + " ") + (msOk ? "" : "structure against"); if (!mtfOk || !msOk) { Draw.Text(this, "skip" + CurrentBar, "skip ORB " + (signal > 0 ? "long" : "short") + ": " + why.Trim(), 0, signal > 0 ? Low[0] - 6 * TickSize : High[0] + 6 * TickSize, Brushes.Gray); return; } side = signal; stop = orb.TradeStop[0]; entryPrice = Close[0]; entryTime = Time[0]; if (LiveOrders) { SetStopLoss("API", CalculationMode.Price, stop, false); if (side > 0) EnterLong(Contracts, "API"); else EnterShort(Contracts, "API"); } if (side > 0) Draw.ArrowUp(this, "in" + CurrentBar, false, 0, Low[0] - 2 * TickSize, Brushes.MediumSeaGreen); else Draw.ArrowDown(this, "in" + CurrentBar, false, 0, High[0] + 2 * TickSize, Brushes.IndianRed); Draw.Text(this, "inT" + CurrentBar, (side > 0 ? "LONG" : "SHORT") + " MTF " + score.ToString("+0;-0") + " stop " + Instrument.MasterInstrument.FormatPrice(stop), 0, side > 0 ? Low[0] - 8 * TickSize : High[0] + 8 * TickSize, side > 0 ? Brushes.MediumSeaGreen : Brushes.IndianRed); } // Live: when the broker's stop (or the session-close exit) flattens the position, stop managing the trade here too. protected override void OnPositionUpdate(Position position, double averagePrice, int quantity, MarketPosition marketPosition) { if (LiveOrders && side != 0 && marketPosition == MarketPosition.Flat) side = 0; } private void Exit(double price, string why) { if (LiveOrders) { if (side > 0) ExitLong("API"); else ExitShort("API"); } double pts = side * (price - entryPrice); Draw.Line(this, "tr" + CurrentBar, false, entryTime, entryPrice, Time[0], price, pts >= 0 ? Brushes.MediumSeaGreen : Brushes.IndianRed, DashStyleHelper.Dash, 1); Draw.Text(this, "out" + CurrentBar, why + " " + pts.ToString("+0.00;-0.00") + " pts", 0, price, pts >= 0 ? Brushes.MediumSeaGreen : Brushes.IndianRed); side = 0; } #region Properties [NinjaScriptProperty] [Display(Name = "Place live orders (off = markers only)", GroupName = "1. Example", Order = 0)] public bool LiveOrders { get; set; } [NinjaScriptProperty] [Range(1, 100)] [Display(Name = "Contracts", GroupName = "1. Example", Order = 1)] public int Contracts { get; set; } [NinjaScriptProperty] [Display(Name = "Trend days only (skip once the gap has filled)", GroupName = "1. Example", Order = 2)] public bool TrendDayOnly { get; set; } [NinjaScriptProperty] [Range(0, 5)] [Display(Name = "Minimum MTF score with the trade (0 = off)", GroupName = "1. Example", Order = 3)] public int MinScore { get; set; } [NinjaScriptProperty] [Display(Name = "Skip when market structure is against", GroupName = "1. Example", Order = 4)] public bool SkipStructureAgainst { get; set; } [NinjaScriptProperty] [Display(Name = "Chart time zone", GroupName = "1. Example", Order = 5)] public string ChartTimeZone { get; set; } #endregion } }