259 lines
10 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|