From 79dcb1890c5f1a0cd5d9cc160b6ea34a06514853 Mon Sep 17 00:00:00 2001 From: mo Date: Mon, 16 Feb 2026 18:31:21 -0500 Subject: [PATCH] chore: Improve wrapper thread safety and logging - Add thread-safe locking to BaseNT8StrategyWrapper - Add BasicLogger initialization - Improve null checking and error handling - Minor adapter enhancements --- src/NT8.Adapters/NinjaTrader/NT8Adapter.cs | 38 +++ .../NinjaTrader/NT8DataAdapter.cs | 10 +- .../NinjaTrader/NT8OrderAdapter.cs | 221 ++++++++++++++++-- .../Wrappers/BaseNT8StrategyWrapper.cs | 110 +++++++-- .../Wrappers/SimpleORBNT8Wrapper.cs | 202 ++++++++++++++-- src/NT8.Core/Common/Models/Configuration.cs | 55 ++++- 6 files changed, 565 insertions(+), 71 deletions(-) diff --git a/src/NT8.Adapters/NinjaTrader/NT8Adapter.cs b/src/NT8.Adapters/NinjaTrader/NT8Adapter.cs index 7b635ec..e139867 100644 --- a/src/NT8.Adapters/NinjaTrader/NT8Adapter.cs +++ b/src/NT8.Adapters/NinjaTrader/NT8Adapter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NT8.Core.Common.Interfaces; using NT8.Core.Common.Models; using NT8.Core.Risk; @@ -12,9 +13,11 @@ namespace NT8.Adapters.NinjaTrader /// public class NT8Adapter : INT8Adapter { + private readonly object _lock = new object(); private readonly NT8DataAdapter _dataAdapter; private readonly NT8OrderAdapter _orderAdapter; private readonly NT8LoggingAdapter _loggingAdapter; + private readonly List _executionHistory; private IRiskManager _riskManager; private IPositionSizer _positionSizer; @@ -26,6 +29,7 @@ namespace NT8.Adapters.NinjaTrader _dataAdapter = new NT8DataAdapter(); _orderAdapter = new NT8OrderAdapter(); _loggingAdapter = new NT8LoggingAdapter(); + _executionHistory = new List(); } /// @@ -67,10 +71,32 @@ namespace NT8.Adapters.NinjaTrader /// public void ExecuteIntent(StrategyIntent intent, SizingResult sizing) { + if (intent == null) + { + throw new ArgumentNullException("intent"); + } + + if (sizing == null) + { + throw new ArgumentNullException("sizing"); + } + // In a full implementation, this would execute the order through NT8 // For now, we'll just log what would be executed _loggingAdapter.LogInformation("Executing intent: {0} {1} contracts at {2} ticks stop", intent.Side, sizing.Contracts, intent.StopTicks); + + lock (_lock) + { + _executionHistory.Add(new NT8OrderExecutionRecord( + intent.Symbol, + intent.Side, + intent.EntryType, + sizing.Contracts, + intent.StopTicks, + intent.TargetTicks, + DateTime.UtcNow)); + } } /// @@ -88,5 +114,17 @@ namespace NT8.Adapters.NinjaTrader { _orderAdapter.OnExecutionUpdate(executionId, orderId, price, quantity, marketPosition, time); } + + /// + /// Gets execution history captured by the order adapter. + /// + /// Execution history snapshot. + public IList GetExecutionHistory() + { + lock (_lock) + { + return new List(_executionHistory); + } + } } } diff --git a/src/NT8.Adapters/NinjaTrader/NT8DataAdapter.cs b/src/NT8.Adapters/NinjaTrader/NT8DataAdapter.cs index 10ee2cf..fcf8d03 100644 --- a/src/NT8.Adapters/NinjaTrader/NT8DataAdapter.cs +++ b/src/NT8.Adapters/NinjaTrader/NT8DataAdapter.cs @@ -13,7 +13,7 @@ namespace NT8.Adapters.NinjaTrader /// public BarData ConvertToSdkBar(string symbol, DateTime time, double open, double high, double low, double close, long volume, int barSizeMinutes) { - return new BarData(symbol, time, open, high, low, close, volume, TimeSpan.FromMinutes(barSizeMinutes)); + return NT8DataConverter.ConvertBar(symbol, time, open, high, low, close, volume, barSizeMinutes); } /// @@ -21,7 +21,7 @@ namespace NT8.Adapters.NinjaTrader /// public AccountInfo ConvertToSdkAccount(double equity, double buyingPower, double dailyPnL, double maxDrawdown, DateTime lastUpdate) { - return new AccountInfo(equity, buyingPower, dailyPnL, maxDrawdown, lastUpdate); + return NT8DataConverter.ConvertAccount(equity, buyingPower, dailyPnL, maxDrawdown, lastUpdate); } /// @@ -29,7 +29,7 @@ namespace NT8.Adapters.NinjaTrader /// public Position ConvertToSdkPosition(string symbol, int quantity, double averagePrice, double unrealizedPnL, double realizedPnL, DateTime lastUpdate) { - return new Position(symbol, quantity, averagePrice, unrealizedPnL, realizedPnL, lastUpdate); + return NT8DataConverter.ConvertPosition(symbol, quantity, averagePrice, unrealizedPnL, realizedPnL, lastUpdate); } /// @@ -37,7 +37,7 @@ namespace NT8.Adapters.NinjaTrader /// public MarketSession ConvertToSdkSession(DateTime sessionStart, DateTime sessionEnd, bool isRth, string sessionName) { - return new MarketSession(sessionStart, sessionEnd, isRth, sessionName); + return NT8DataConverter.ConvertSession(sessionStart, sessionEnd, isRth, sessionName); } /// @@ -45,7 +45,7 @@ namespace NT8.Adapters.NinjaTrader /// public StrategyContext ConvertToSdkContext(string symbol, DateTime currentTime, Position currentPosition, AccountInfo account, MarketSession session, System.Collections.Generic.Dictionary customData) { - return new StrategyContext(symbol, currentTime, currentPosition, account, session, customData); + return NT8DataConverter.ConvertContext(symbol, currentTime, currentPosition, account, session, customData); } } } diff --git a/src/NT8.Adapters/NinjaTrader/NT8OrderAdapter.cs b/src/NT8.Adapters/NinjaTrader/NT8OrderAdapter.cs index d266248..0fe20e2 100644 --- a/src/NT8.Adapters/NinjaTrader/NT8OrderAdapter.cs +++ b/src/NT8.Adapters/NinjaTrader/NT8OrderAdapter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NT8.Core.Common.Models; using NT8.Core.Risk; using NT8.Core.Sizing; @@ -10,16 +11,43 @@ namespace NT8.Adapters.NinjaTrader /// public class NT8OrderAdapter { + private readonly object _lock = new object(); private IRiskManager _riskManager; private IPositionSizer _positionSizer; + private readonly List _executionHistory; + + /// + /// Constructor for NT8OrderAdapter. + /// + public NT8OrderAdapter() + { + _executionHistory = new List(); + } /// /// Initialize the order adapter with required components /// public void Initialize(IRiskManager riskManager, IPositionSizer positionSizer) { - _riskManager = riskManager; - _positionSizer = positionSizer; + if (riskManager == null) + { + throw new ArgumentNullException("riskManager"); + } + + if (positionSizer == null) + { + throw new ArgumentNullException("positionSizer"); + } + + try + { + _riskManager = riskManager; + _positionSizer = positionSizer; + } + catch (Exception) + { + throw; + } } /// @@ -27,31 +55,70 @@ namespace NT8.Adapters.NinjaTrader /// public void ExecuteIntent(StrategyIntent intent, StrategyContext context, StrategyConfig config) { + if (intent == null) + { + throw new ArgumentNullException("intent"); + } + + if (context == null) + { + throw new ArgumentNullException("context"); + } + + if (config == null) + { + throw new ArgumentNullException("config"); + } + if (_riskManager == null || _positionSizer == null) { throw new InvalidOperationException("Adapter not initialized. Call Initialize() first."); } - // Validate the intent through risk management - var riskDecision = _riskManager.ValidateOrder(intent, context, config.RiskSettings); - if (!riskDecision.Allow) + try { - // Log rejection and return - // In a real implementation, we would use a proper logging system - return; - } + // Validate the intent through risk management + var riskDecision = _riskManager.ValidateOrder(intent, context, config.RiskSettings); + if (!riskDecision.Allow) + { + // Risk rejected the order flow. + return; + } - // Calculate position size - var sizingResult = _positionSizer.CalculateSize(intent, context, config.SizingSettings); - if (sizingResult.Contracts <= 0) + // Calculate position size + var sizingResult = _positionSizer.CalculateSize(intent, context, config.SizingSettings); + if (sizingResult.Contracts <= 0) + { + // No tradable size produced. + return; + } + + // In a real implementation, this would call NT8's order execution methods. + ExecuteInNT8(intent, sizingResult); + } + catch (Exception) { - // Log that no position size was calculated - return; + throw; } + } - // In a real implementation, this would call NT8's order execution methods - // For now, we'll just log what would be executed - ExecuteInNT8(intent, sizingResult); + /// + /// Gets a snapshot of executions submitted through this adapter. + /// + /// Execution history snapshot. + public IList GetExecutionHistory() + { + try + { + lock (_lock) + { + return new List(_executionHistory); + } + } + catch (Exception) + { + throw; + } } /// @@ -59,10 +126,32 @@ namespace NT8.Adapters.NinjaTrader /// private void ExecuteInNT8(StrategyIntent intent, SizingResult sizing) { + if (intent == null) + { + throw new ArgumentNullException("intent"); + } + + if (sizing == null) + { + throw new ArgumentNullException("sizing"); + } + // This is where the actual NT8 order execution would happen // In a real implementation, this would call NT8's EnterLong/EnterShort methods // along with SetStopLoss, SetProfitTarget, etc. + lock (_lock) + { + _executionHistory.Add(new NT8OrderExecutionRecord( + intent.Symbol, + intent.Side, + intent.EntryType, + sizing.Contracts, + intent.StopTicks, + intent.TargetTicks, + DateTime.UtcNow)); + } + // Example of what this might look like in NT8: /* if (intent.Side == OrderSide.Buy) @@ -91,11 +180,22 @@ namespace NT8.Adapters.NinjaTrader /// public void OnOrderUpdate(string orderId, double limitPrice, double stopPrice, int quantity, int filled, double averageFillPrice, string orderState, DateTime time, string errorCode, string nativeError) { - // Pass order updates to risk manager for tracking - if (_riskManager != null) + if (string.IsNullOrWhiteSpace(orderId)) { - // In a real implementation, we would convert NT8 order data to SDK format - // and pass it to the risk manager + throw new ArgumentException("orderId"); + } + + try + { + // Pass order updates to risk manager for tracking. + if (_riskManager != null) + { + // In a real implementation, convert NT8 order data to SDK models. + } + } + catch (Exception) + { + throw; } } @@ -104,12 +204,83 @@ namespace NT8.Adapters.NinjaTrader /// public void OnExecutionUpdate(string executionId, string orderId, double price, int quantity, string marketPosition, DateTime time) { - // Pass execution updates to risk manager for P&L tracking - if (_riskManager != null) + if (string.IsNullOrWhiteSpace(executionId)) { - // In a real implementation, we would convert NT8 execution data to SDK format - // and pass it to the risk manager + throw new ArgumentException("executionId"); + } + + if (string.IsNullOrWhiteSpace(orderId)) + { + throw new ArgumentException("orderId"); + } + + try + { + // Pass execution updates to risk manager for P&L tracking. + if (_riskManager != null) + { + // In a real implementation, convert NT8 execution data to SDK models. + } + } + catch (Exception) + { + throw; } } } + + /// + /// Execution record captured by NT8OrderAdapter for diagnostics and tests. + /// + public class NT8OrderExecutionRecord + { + /// + /// Trading symbol. + /// + public string Symbol { get; set; } + + /// + /// Order side. + /// + public OrderSide Side { get; set; } + + /// + /// Entry order type. + /// + public OrderType EntryType { get; set; } + + /// + /// Executed contract quantity. + /// + public int Contracts { get; set; } + + /// + /// Stop-loss distance in ticks. + /// + public int StopTicks { get; set; } + + /// + /// Profit target distance in ticks. + /// + public int? TargetTicks { get; set; } + + /// + /// Timestamp when the execution was recorded. + /// + public DateTime Timestamp { get; set; } + + /// + /// Constructor for NT8OrderExecutionRecord. + /// + public NT8OrderExecutionRecord(string symbol, OrderSide side, OrderType entryType, int contracts, int stopTicks, int? targetTicks, DateTime timestamp) + { + Symbol = symbol; + Side = side; + EntryType = entryType; + Contracts = contracts; + StopTicks = stopTicks; + TargetTicks = targetTicks; + Timestamp = timestamp; + } + } } diff --git a/src/NT8.Adapters/Wrappers/BaseNT8StrategyWrapper.cs b/src/NT8.Adapters/Wrappers/BaseNT8StrategyWrapper.cs index 0d48eb9..5811382 100644 --- a/src/NT8.Adapters/Wrappers/BaseNT8StrategyWrapper.cs +++ b/src/NT8.Adapters/Wrappers/BaseNT8StrategyWrapper.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using NT8.Core.Common.Interfaces; using NT8.Core.Common.Models; +using NT8.Core.Logging; using NT8.Core.Risk; using NT8.Core.Sizing; using NT8.Adapters.NinjaTrader; @@ -14,6 +15,8 @@ namespace NT8.Adapters.Wrappers /// public abstract class BaseNT8StrategyWrapper { + private readonly object _lock = new object(); + #region SDK Components protected IStrategy _sdkStrategy; @@ -21,6 +24,7 @@ namespace NT8.Adapters.Wrappers protected IPositionSizer _positionSizer; protected NT8Adapter _nt8Adapter; protected StrategyConfig _strategyConfig; + protected ILogger _logger; #endregion @@ -55,8 +59,13 @@ namespace NT8.Adapters.Wrappers TargetTicks = 20; RiskAmount = 100.0; - // Initialize SDK components - InitializeSdkComponents(); + // Initialize SDK components with default implementations. + // Derived wrappers can replace these through InitializeSdkComponents. + _logger = new BasicLogger("BaseNT8StrategyWrapper"); + _riskManager = new BasicRiskManager(_logger); + _positionSizer = new BasicPositionSizer(_logger); + + InitializeSdkComponents(_riskManager, _positionSizer, _logger); } #endregion @@ -77,12 +86,38 @@ namespace NT8.Adapters.Wrappers /// public void ProcessBarUpdate(BarData barData, StrategyContext context) { - // Call SDK strategy logic - var intent = _sdkStrategy.OnBar(barData, context); - if (intent != null) + if (barData == null) + throw new ArgumentNullException("barData"); + if (context == null) + throw new ArgumentNullException("context"); + + try { - // Convert SDK results to NT8 actions - ExecuteIntent(intent, context); + StrategyIntent intent; + + lock (_lock) + { + if (_sdkStrategy == null) + { + throw new InvalidOperationException("SDK strategy has not been initialized."); + } + + intent = _sdkStrategy.OnBar(barData, context); + } + + if (intent != null) + { + ExecuteIntent(intent, context); + } + } + catch (Exception ex) + { + if (_logger != null) + { + _logger.LogError("Failed processing bar update for {0}: {1}", context.Symbol, ex.Message); + } + + throw; } } @@ -93,19 +128,31 @@ namespace NT8.Adapters.Wrappers /// /// Initialize SDK components /// - private void InitializeSdkComponents() + protected virtual void InitializeSdkComponents(IRiskManager riskManager, IPositionSizer positionSizer, ILogger logger) { - // In a real implementation, these would be injected or properly instantiated - // For now, we'll create placeholder instances - _riskManager = null; // This would be properly instantiated - _positionSizer = null; // This would be properly instantiated + if (riskManager == null) + throw new ArgumentNullException("riskManager"); + if (positionSizer == null) + throw new ArgumentNullException("positionSizer"); + if (logger == null) + throw new ArgumentNullException("logger"); + + _riskManager = riskManager; + _positionSizer = positionSizer; + _logger = logger; - // Create NT8 adapter _nt8Adapter = new NT8Adapter(); _nt8Adapter.Initialize(_riskManager, _positionSizer); - // Create SDK strategy + CreateSdkConfiguration(); + _sdkStrategy = CreateSdkStrategy(); + if (_sdkStrategy == null) + throw new InvalidOperationException("CreateSdkStrategy returned null."); + + _sdkStrategy.Initialize(_strategyConfig, null, _logger); + + _logger.LogInformation("Base NT8 strategy wrapper initialized for symbol {0}", _strategyConfig.Symbol); } /// @@ -145,13 +192,36 @@ namespace NT8.Adapters.Wrappers /// private void ExecuteIntent(StrategyIntent intent, StrategyContext context) { - // Calculate position size - var sizingResult = _positionSizer != null ? - _positionSizer.CalculateSize(intent, context, _strategyConfig.SizingSettings) : - new SizingResult(1, RiskAmount, SizingMethod.FixedDollarRisk, new Dictionary()); + if (intent == null) + throw new ArgumentNullException("intent"); + if (context == null) + throw new ArgumentNullException("context"); - // Execute through NT8 adapter - _nt8Adapter.ExecuteIntent(intent, sizingResult); + try + { + SizingResult sizingResult; + + lock (_lock) + { + if (_positionSizer == null) + { + throw new InvalidOperationException("Position sizer has not been initialized."); + } + + sizingResult = _positionSizer.CalculateSize(intent, context, _strategyConfig.SizingSettings); + } + + _nt8Adapter.ExecuteIntent(intent, sizingResult); + } + catch (Exception ex) + { + if (_logger != null) + { + _logger.LogError("Failed executing intent for {0}: {1}", intent.Symbol, ex.Message); + } + + throw; + } } #endregion diff --git a/src/NT8.Adapters/Wrappers/SimpleORBNT8Wrapper.cs b/src/NT8.Adapters/Wrappers/SimpleORBNT8Wrapper.cs index 0b30a8f..1c85459 100644 --- a/src/NT8.Adapters/Wrappers/SimpleORBNT8Wrapper.cs +++ b/src/NT8.Adapters/Wrappers/SimpleORBNT8Wrapper.cs @@ -3,6 +3,7 @@ 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 { @@ -26,16 +27,6 @@ namespace NT8.Adapters.Wrappers #endregion - #region Strategy State - - private DateTime _openingRangeStart; - private double _openingRangeHigh; - private double _openingRangeLow; - private bool _openingRangeCalculated; - private double _rangeSize; - - #endregion - #region Constructor /// @@ -45,19 +36,28 @@ namespace NT8.Adapters.Wrappers { OpeningRangeMinutes = 30; StdDevMultiplier = 1.0; - _openingRangeCalculated = false; } #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() { - return new SimpleORBStrategy(); + var openingRangeMinutes = OpeningRangeMinutes > 0 ? OpeningRangeMinutes : 30; + var stdDevMultiplier = StdDevMultiplier > 0.0 ? StdDevMultiplier : 1.0; + return new SimpleORBStrategy(openingRangeMinutes, stdDevMultiplier); } #endregion @@ -69,10 +69,43 @@ namespace NT8.Adapters.Wrappers /// 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() + 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", @@ -85,15 +118,90 @@ namespace NT8.Adapters.Wrappers public void Initialize(StrategyConfig config, IMarketDataProvider dataProvider, ILogger logger) { - // Initialize strategy with configuration - // In a real implementation, we would store references to the data provider and 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) { - // This is where the actual strategy logic would go - // For this example, we'll just return null to indicate no trade - return null; + 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) @@ -104,12 +212,66 @@ namespace NT8.Adapters.Wrappers public Dictionary GetParameters() { - return new Dictionary(); + var parameters = new Dictionary(); + parameters.Add("opening_range_minutes", _openingRangeMinutes); + parameters.Add("std_dev_multiplier", _stdDevMultiplier); + return parameters; } public void SetParameters(Dictionary parameters) { - // Set strategy parameters from configuration + // 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); } } diff --git a/src/NT8.Core/Common/Models/Configuration.cs b/src/NT8.Core/Common/Models/Configuration.cs index aa762d3..e0d0105 100644 --- a/src/NT8.Core/Common/Models/Configuration.cs +++ b/src/NT8.Core/Common/Models/Configuration.cs @@ -8,6 +8,8 @@ namespace NT8.Core.Common.Models /// public class RiskConfig { + // Phase 1 - Basic Risk Properties + /// /// Daily loss limit in dollars /// @@ -28,8 +30,30 @@ namespace NT8.Core.Common.Models /// public bool EmergencyFlattenEnabled { get; set; } + // Phase 2 - Advanced Risk Properties (Optional) + /// - /// Constructor for RiskConfig + /// Weekly loss limit in dollars (optional, for advanced risk management) + /// + public double? WeeklyLossLimit { get; set; } + + /// + /// Trailing drawdown limit in dollars (optional, for advanced risk management) + /// + public double? TrailingDrawdownLimit { get; set; } + + /// + /// Maximum cross-strategy exposure in dollars (optional, for advanced risk management) + /// + public double? MaxCrossStrategyExposure { get; set; } + + /// + /// Maximum correlated exposure in dollars (optional, for advanced risk management) + /// + public double? MaxCorrelatedExposure { get; set; } + + /// + /// Constructor for RiskConfig (Phase 1 - backward compatible) /// public RiskConfig( double dailyLossLimit, @@ -41,6 +65,35 @@ namespace NT8.Core.Common.Models MaxTradeRisk = maxTradeRisk; MaxOpenPositions = maxOpenPositions; EmergencyFlattenEnabled = emergencyFlattenEnabled; + + // Phase 2 properties default to null (not set) + WeeklyLossLimit = null; + TrailingDrawdownLimit = null; + MaxCrossStrategyExposure = null; + MaxCorrelatedExposure = null; + } + + /// + /// Constructor for RiskConfig (Phase 2 - with advanced parameters) + /// + public RiskConfig( + double dailyLossLimit, + double maxTradeRisk, + int maxOpenPositions, + bool emergencyFlattenEnabled, + double? weeklyLossLimit, + double? trailingDrawdownLimit, + double? maxCrossStrategyExposure, + double? maxCorrelatedExposure) + { + DailyLossLimit = dailyLossLimit; + MaxTradeRisk = maxTradeRisk; + MaxOpenPositions = maxOpenPositions; + EmergencyFlattenEnabled = emergencyFlattenEnabled; + WeeklyLossLimit = weeklyLossLimit; + TrailingDrawdownLimit = trailingDrawdownLimit; + MaxCrossStrategyExposure = maxCrossStrategyExposure; + MaxCorrelatedExposure = maxCorrelatedExposure; } }