Files
nt8-sdk/src/NT8.Adapters/Strategies/SimpleORBNT8.cs
mo 9a28a49292
Some checks failed
Build and Test / build (push) Has been cancelled
Pre-cleanup baseline snapshot
2026-04-05 16:50:18 -04:00

259 lines
10 KiB
C#

// 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
{
/// <summary>
/// Simple Opening Range Breakout strategy integrated with NT8 SDK.
/// </summary>
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);
}
}
/// <summary>
/// Appends ORB-specific parameters to the base settings export.
/// </summary>
protected override List<string> 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;
}
/// <summary>
/// Builds a DailyBarContext from the secondary daily bar series.
/// Returns a context with Count=0 if fewer than 2 daily bars are available.
/// </summary>
/// <param name="tradeDirection">1 for long, -1 for short.</param>
/// <param name="orbRangeTicks">ORB range in ticks for ORB range factor.</param>
/// <param name="breakoutBarVolume">Volume of the current breakout bar.</param>
/// <returns>Populated daily context for confluence scoring.</returns>
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;
}
}
}