using System; using System.Collections.Generic; using NT8.Core.Common.Interfaces; using NT8.Core.Common.Models; using NT8.Core.Logging; using NT8.Adapters.NinjaTrader; namespace NT8.Adapters.Wrappers { /// /// Simple ORB (Opening Range Breakout) strategy wrapper for NT8 /// This demonstrates how to implement a strategy that works with the SDK /// public class SimpleORBNT8Wrapper : BaseNT8StrategyWrapper { #region Strategy Parameters /// /// Opening range period in minutes /// public int OpeningRangeMinutes { get; set; } /// /// Number of standard deviations for breakout threshold /// public double StdDevMultiplier { get; set; } #endregion #region Constructor /// /// Constructor for SimpleORBNT8Wrapper /// public SimpleORBNT8Wrapper() { OpeningRangeMinutes = 30; StdDevMultiplier = 1.0; } #endregion #region Base Class Implementation /// /// Exposes adapter reference for integration test assertions. /// public NT8Adapter GetAdapterForTesting() { return _nt8Adapter; } /// /// Create the SDK strategy implementation /// protected override IStrategy CreateSdkStrategy() { var openingRangeMinutes = OpeningRangeMinutes > 0 ? OpeningRangeMinutes : 30; var stdDevMultiplier = StdDevMultiplier > 0.0 ? StdDevMultiplier : 1.0; return new SimpleORBStrategy(openingRangeMinutes, stdDevMultiplier); } #endregion #region Strategy Logic /// /// Simple ORB strategy implementation /// private class SimpleORBStrategy : IStrategy { private readonly int _openingRangeMinutes; private readonly double _stdDevMultiplier; private ILogger _logger; private DateTime _currentSessionDate; private DateTime _openingRangeStart; private DateTime _openingRangeEnd; private double _openingRangeHigh; private double _openingRangeLow; private bool _openingRangeReady; private bool _tradeTaken; public StrategyMetadata Metadata { get; private set; } public SimpleORBStrategy(int openingRangeMinutes, double stdDevMultiplier) { if (openingRangeMinutes <= 0) { throw new ArgumentException("openingRangeMinutes"); } if (stdDevMultiplier <= 0.0) { throw new ArgumentException("stdDevMultiplier"); } _openingRangeMinutes = openingRangeMinutes; _stdDevMultiplier = stdDevMultiplier; _currentSessionDate = DateTime.MinValue; _openingRangeStart = DateTime.MinValue; _openingRangeEnd = DateTime.MinValue; _openingRangeHigh = Double.MinValue; _openingRangeLow = Double.MaxValue; _openingRangeReady = false; _tradeTaken = false; Metadata = new StrategyMetadata( name: "Simple ORB", description: "Opening Range Breakout strategy", version: "1.0", author: "NT8 SDK Team", symbols: new string[] { "ES", "NQ", "YM" }, requiredBars: 20 ); } public void Initialize(StrategyConfig config, IMarketDataProvider dataProvider, ILogger logger) { if (logger == null) { throw new ArgumentNullException("logger"); } _logger = logger; _logger.LogInformation("SimpleORBStrategy initialized with OR period {0} minutes and multiplier {1:F2}", _openingRangeMinutes, _stdDevMultiplier); } public StrategyIntent OnBar(BarData bar, StrategyContext context) { if (bar == null) { throw new ArgumentNullException("bar"); } if (context == null) { throw new ArgumentNullException("context"); } try { if (_currentSessionDate != context.CurrentTime.Date) { ResetSession(context.Session.SessionStart); } if (bar.Time <= _openingRangeEnd) { UpdateOpeningRange(bar); return null; } if (!_openingRangeReady) { if (_openingRangeHigh > _openingRangeLow) { _openingRangeReady = true; } else { return null; } } if (_tradeTaken) { return null; } var openingRange = _openingRangeHigh - _openingRangeLow; var volatilityBuffer = openingRange * (_stdDevMultiplier - 1.0); if (volatilityBuffer < 0) { volatilityBuffer = 0; } var longTrigger = _openingRangeHigh + volatilityBuffer; var shortTrigger = _openingRangeLow - volatilityBuffer; if (bar.Close > longTrigger) { _tradeTaken = true; return CreateIntent(context.Symbol, OrderSide.Buy, openingRange, bar.Close); } if (bar.Close < shortTrigger) { _tradeTaken = true; return CreateIntent(context.Symbol, OrderSide.Sell, openingRange, bar.Close); } return null; } catch (Exception ex) { if (_logger != null) { _logger.LogError("SimpleORBStrategy OnBar failed: {0}", ex.Message); } throw; } } public StrategyIntent OnTick(TickData tick, StrategyContext context) { // Most strategies don't need tick-level logic return null; } public Dictionary GetParameters() { var parameters = new Dictionary(); parameters.Add("opening_range_minutes", _openingRangeMinutes); parameters.Add("std_dev_multiplier", _stdDevMultiplier); return parameters; } public void SetParameters(Dictionary parameters) { // Parameters are constructor-bound for deterministic behavior in this wrapper. // Method retained for interface compatibility. } private void ResetSession(DateTime sessionStart) { _currentSessionDate = sessionStart.Date; _openingRangeStart = sessionStart; _openingRangeEnd = sessionStart.AddMinutes(_openingRangeMinutes); _openingRangeHigh = Double.MinValue; _openingRangeLow = Double.MaxValue; _openingRangeReady = false; _tradeTaken = false; } private void UpdateOpeningRange(BarData bar) { if (bar.High > _openingRangeHigh) { _openingRangeHigh = bar.High; } if (bar.Low < _openingRangeLow) { _openingRangeLow = bar.Low; } } private StrategyIntent CreateIntent(string symbol, OrderSide side, double openingRange, double lastPrice) { var metadata = new Dictionary(); metadata.Add("orb_high", _openingRangeHigh); metadata.Add("orb_low", _openingRangeLow); metadata.Add("orb_range", openingRange); metadata.Add("trigger_price", lastPrice); metadata.Add("multiplier", _stdDevMultiplier); if (_logger != null) { _logger.LogInformation("SimpleORBStrategy generated {0} intent for {1}. OR High={2:F2}, OR Low={3:F2}, Last={4:F2}", side, symbol, _openingRangeHigh, _openingRangeLow, lastPrice); } return new StrategyIntent( symbol, side, OrderType.Market, null, 8, 16, 0.75, "ORB breakout signal", metadata); } } #endregion } }