// File: SimpleORBNT8.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using NinjaTrader.Cbi; using NinjaTrader.Data; using NinjaTrader.Gui; using NinjaTrader.Gui.Chart; using NinjaTrader.Gui.Tools; using NinjaTrader.NinjaScript; using NinjaTrader.NinjaScript.Indicators; using NinjaTrader.NinjaScript.Strategies; using NT8.Core.Common.Interfaces; using NT8.Core.Intelligence; using NT8.Strategies.Examples; using SdkSimpleORB = NT8.Strategies.Examples.SimpleORBStrategy; namespace NinjaTrader.NinjaScript.Strategies { /// /// Simple Opening Range Breakout strategy integrated with NT8 SDK. /// public class SimpleORBNT8 : NT8StrategyBase { private int _lastSignalDirection; [NinjaScriptProperty] [Display(Name = "Opening Range Minutes", GroupName = "ORB Strategy", Order = 1)] [Range(5, 120)] public int OpeningRangeMinutes { get; set; } [NinjaScriptProperty] [Display(Name = "Std Dev Multiplier", GroupName = "ORB Strategy", Order = 2)] [Range(0.5, 3.0)] public double StdDevMultiplier { get; set; } [NinjaScriptProperty] [Display(Name = "Stop Loss Ticks", GroupName = "ORB Risk", Order = 1)] [Range(1, 50)] public int StopTicks { get; set; } [NinjaScriptProperty] [Display(Name = "Force Session Reset On Start", GroupName = "ORB Strategy", Order = 10)] public bool ForceSessionReset { get; set; } [NinjaScriptProperty] [Display(Name = "Profit Target Ticks", GroupName = "ORB Risk", Order = 2)] [Range(1, 100)] public int TargetTicks { get; set; } protected override void OnStateChange() { if (State == State.SetDefaults) { Name = "Simple ORB NT8"; Description = "v0.4.0 | 2026-03-19 | NR7+ORB factors, PortfolioRiskManager, connection recovery, live account balance"; // Daily bar series is added automatically via AddDataSeries in Configure. OpeningRangeMinutes = 30; StdDevMultiplier = 1.0; StopTicks = 8; TargetTicks = 16; DailyLossLimit = 1000.0; MaxTradeRisk = 200.0; MaxOpenPositions = 2; RiskPerTrade = 100.0; MinContracts = 1; MaxContracts = 3; Calculate = Calculate.OnBarClose; BarsRequiredToTrade = 50; MinTradeGrade = 5; EnableLongTrades = true; // Long-only: short trades permanently disabled pending backtest confirmation EnableShortTrades = false; EnableAutoBreakeven = true; BreakevenTriggerTicks = 20; BreakevenOffsetTicks = 1; EnableRunner = true; RunnerTrailTicks = 20; ForceSessionReset = false; StartBehavior = StartBehavior.AdoptAccountPosition; } else if (State == State.Configure) { AddDataSeries(BarsPeriodType.Day, 1); } base.OnStateChange(); } protected override void OnBarUpdate() { if (_strategyConfig != null && BarsArray != null && BarsArray.Length > 1) { DailyBarContext dailyContext = BuildDailyBarContext(_lastSignalDirection, 0.0, (double)Volume[0]); _strategyConfig.Parameters["daily_bars"] = dailyContext; } base.OnBarUpdate(); if (Position != null) { if (Position.MarketPosition == MarketPosition.Long) _lastSignalDirection = 1; else if (Position.MarketPosition == MarketPosition.Short) _lastSignalDirection = -1; } } protected override bool GetForceSessionReset() { return ForceSessionReset; } protected override IStrategy CreateSdkStrategy() { return new SdkSimpleORB(OpeningRangeMinutes, StdDevMultiplier); } protected override void ConfigureStrategyParameters() { _strategyConfig.RiskSettings.DailyLossLimit = DailyLossLimit; _strategyConfig.RiskSettings.MaxTradeRisk = MaxTradeRisk; _strategyConfig.RiskSettings.MaxOpenPositions = MaxOpenPositions; // Guard: Instrument may be null during strategy list loading if (Instrument != null && Instrument.MasterInstrument != null) { var pointValue = Instrument.MasterInstrument.PointValue; var tickSize = Instrument.MasterInstrument.TickSize; var dollarRisk = StopTicks * tickSize * pointValue; if (dollarRisk > _strategyConfig.RiskSettings.MaxTradeRisk) _strategyConfig.RiskSettings.MaxTradeRisk = dollarRisk; } _strategyConfig.SizingSettings.RiskPerTrade = RiskPerTrade; _strategyConfig.SizingSettings.MinContracts = MinContracts; _strategyConfig.SizingSettings.MaxContracts = MaxContracts; _strategyConfig.Parameters["StopTicks"] = StopTicks; _strategyConfig.Parameters["TargetTicks"] = TargetTicks; _strategyConfig.Parameters["OpeningRangeMinutes"] = OpeningRangeMinutes; _strategyConfig.Parameters["MinTradeGrade"] = MinTradeGrade; if (Instrument != null && Instrument.MasterInstrument != null) { _strategyConfig.Parameters["TickSize"] = Instrument.MasterInstrument.TickSize; } if (_logger != null) { _logger.LogInformation( "Simple ORB configured: OR={0}min, Stop={1}ticks, Target={2}ticks, Long={3}, Short={4}", OpeningRangeMinutes, StopTicks, TargetTicks, EnableLongTrades, EnableShortTrades); } } /// /// Appends ORB-specific parameters to the base settings export. /// protected override List GetStrategySettingsLines() { var lines = base.GetStrategySettingsLines(); // Insert ORB section before the final === END SETTINGS === line int endIdx = lines.Count - 1; lines.Insert(endIdx, "--- ORB Strategy ---"); lines.Insert(endIdx + 1, string.Format("OpeningRangeMinutes: {0}", OpeningRangeMinutes)); lines.Insert(endIdx + 2, string.Format("StdDevMultiplier : {0:F2}", StdDevMultiplier)); lines.Insert(endIdx + 3, string.Format("StopTicks : {0}", StopTicks)); lines.Insert(endIdx + 4, string.Format("TargetTicks : {0}", TargetTicks)); lines.Insert(endIdx + 5, string.Format("MinTradeGrade : {0}", MinTradeGrade)); double tickDollarValue = 0.25 * 50.0; if (Instrument != null && Instrument.MasterInstrument != null) tickDollarValue = Instrument.MasterInstrument.TickSize * Instrument.MasterInstrument.PointValue; lines.Insert(endIdx + 6, string.Format("StopDollars : {0:C}", StopTicks * tickDollarValue)); lines.Insert(endIdx + 7, string.Format("TargetDollars : {0:C}", TargetTicks * tickDollarValue)); lines.Insert(endIdx + 8, string.Format("RR_Ratio : {0:F2}:1", (double)TargetTicks / StopTicks)); lines.Insert(endIdx + 9, String.Format("AutoBreakeven : {0} @ {1}ticks + {2}tick offset", EnableAutoBreakeven, BreakevenTriggerTicks, BreakevenOffsetTicks)); lines.Insert(endIdx + 10, String.Format("Runner : {0} | Trail={1}ticks", EnableRunner, RunnerTrailTicks)); return lines; } /// /// Builds a DailyBarContext from the secondary daily bar series. /// Returns a context with Count=0 if fewer than 2 daily bars are available. /// /// 1 for long, -1 for short. /// ORB range in ticks for ORB range factor. /// Volume of the current breakout bar. /// Populated daily context for confluence scoring. private DailyBarContext BuildDailyBarContext(int tradeDirection, double orbRangeTicks, double breakoutBarVolume) { DailyBarContext ctx = new DailyBarContext(); ctx.TradeDirection = tradeDirection; ctx.BreakoutBarVolume = breakoutBarVolume; ctx.TodayOpen = Open[0]; if (BarsArray == null || BarsArray.Length < 2 || CurrentBars == null || CurrentBars.Length < 2) { ctx.Count = 0; return ctx; } int dailyBarsAvailable = CurrentBars[1] + 1; int lookback = Math.Min(10, dailyBarsAvailable); if (lookback < 2) { ctx.Count = 0; return ctx; } ctx.Highs = new double[lookback]; ctx.Lows = new double[lookback]; ctx.Closes = new double[lookback]; ctx.Opens = new double[lookback]; ctx.Volumes = new long[lookback]; ctx.Count = lookback; for (int i = 0; i < lookback; i++) { int barsAgo = lookback - 1 - i; ctx.Highs[i] = Highs[1][barsAgo]; ctx.Lows[i] = Lows[1][barsAgo]; ctx.Closes[i] = Closes[1][barsAgo]; ctx.Opens[i] = Opens[1][barsAgo]; ctx.Volumes[i] = (long)Volumes[1][barsAgo]; } double sumVol = 0.0; int intradayCount = 0; int maxBars = Math.Min(78, CurrentBar + 1); for (int i = 0; i < maxBars; i++) { sumVol += Volume[i]; intradayCount++; } ctx.AvgIntradayBarVolume = intradayCount > 0 ? sumVol / intradayCount : Volume[0]; return ctx; } } }