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
This commit is contained in:
2026-02-16 18:31:21 -05:00
parent 6325c091a0
commit 79dcb1890c
6 changed files with 565 additions and 71 deletions

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using NT8.Core.Common.Interfaces; using NT8.Core.Common.Interfaces;
using NT8.Core.Common.Models; using NT8.Core.Common.Models;
using NT8.Core.Risk; using NT8.Core.Risk;
@@ -12,9 +13,11 @@ namespace NT8.Adapters.NinjaTrader
/// </summary> /// </summary>
public class NT8Adapter : INT8Adapter public class NT8Adapter : INT8Adapter
{ {
private readonly object _lock = new object();
private readonly NT8DataAdapter _dataAdapter; private readonly NT8DataAdapter _dataAdapter;
private readonly NT8OrderAdapter _orderAdapter; private readonly NT8OrderAdapter _orderAdapter;
private readonly NT8LoggingAdapter _loggingAdapter; private readonly NT8LoggingAdapter _loggingAdapter;
private readonly List<NT8OrderExecutionRecord> _executionHistory;
private IRiskManager _riskManager; private IRiskManager _riskManager;
private IPositionSizer _positionSizer; private IPositionSizer _positionSizer;
@@ -26,6 +29,7 @@ namespace NT8.Adapters.NinjaTrader
_dataAdapter = new NT8DataAdapter(); _dataAdapter = new NT8DataAdapter();
_orderAdapter = new NT8OrderAdapter(); _orderAdapter = new NT8OrderAdapter();
_loggingAdapter = new NT8LoggingAdapter(); _loggingAdapter = new NT8LoggingAdapter();
_executionHistory = new List<NT8OrderExecutionRecord>();
} }
/// <summary> /// <summary>
@@ -67,10 +71,32 @@ namespace NT8.Adapters.NinjaTrader
/// </summary> /// </summary>
public void ExecuteIntent(StrategyIntent intent, SizingResult sizing) 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 // In a full implementation, this would execute the order through NT8
// For now, we'll just log what would be executed // For now, we'll just log what would be executed
_loggingAdapter.LogInformation("Executing intent: {0} {1} contracts at {2} ticks stop", _loggingAdapter.LogInformation("Executing intent: {0} {1} contracts at {2} ticks stop",
intent.Side, sizing.Contracts, intent.StopTicks); 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));
}
} }
/// <summary> /// <summary>
@@ -88,5 +114,17 @@ namespace NT8.Adapters.NinjaTrader
{ {
_orderAdapter.OnExecutionUpdate(executionId, orderId, price, quantity, marketPosition, time); _orderAdapter.OnExecutionUpdate(executionId, orderId, price, quantity, marketPosition, time);
} }
/// <summary>
/// Gets execution history captured by the order adapter.
/// </summary>
/// <returns>Execution history snapshot.</returns>
public IList<NT8OrderExecutionRecord> GetExecutionHistory()
{
lock (_lock)
{
return new List<NT8OrderExecutionRecord>(_executionHistory);
}
}
} }
} }

View File

@@ -13,7 +13,7 @@ namespace NT8.Adapters.NinjaTrader
/// </summary> /// </summary>
public BarData ConvertToSdkBar(string symbol, DateTime time, double open, double high, double low, double close, long volume, int barSizeMinutes) 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);
} }
/// <summary> /// <summary>
@@ -21,7 +21,7 @@ namespace NT8.Adapters.NinjaTrader
/// </summary> /// </summary>
public AccountInfo ConvertToSdkAccount(double equity, double buyingPower, double dailyPnL, double maxDrawdown, DateTime lastUpdate) 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);
} }
/// <summary> /// <summary>
@@ -29,7 +29,7 @@ namespace NT8.Adapters.NinjaTrader
/// </summary> /// </summary>
public Position ConvertToSdkPosition(string symbol, int quantity, double averagePrice, double unrealizedPnL, double realizedPnL, DateTime lastUpdate) 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);
} }
/// <summary> /// <summary>
@@ -37,7 +37,7 @@ namespace NT8.Adapters.NinjaTrader
/// </summary> /// </summary>
public MarketSession ConvertToSdkSession(DateTime sessionStart, DateTime sessionEnd, bool isRth, string sessionName) 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);
} }
/// <summary> /// <summary>
@@ -45,7 +45,7 @@ namespace NT8.Adapters.NinjaTrader
/// </summary> /// </summary>
public StrategyContext ConvertToSdkContext(string symbol, DateTime currentTime, Position currentPosition, AccountInfo account, MarketSession session, System.Collections.Generic.Dictionary<string, object> customData) public StrategyContext ConvertToSdkContext(string symbol, DateTime currentTime, Position currentPosition, AccountInfo account, MarketSession session, System.Collections.Generic.Dictionary<string, object> customData)
{ {
return new StrategyContext(symbol, currentTime, currentPosition, account, session, customData); return NT8DataConverter.ConvertContext(symbol, currentTime, currentPosition, account, session, customData);
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using NT8.Core.Common.Models; using NT8.Core.Common.Models;
using NT8.Core.Risk; using NT8.Core.Risk;
using NT8.Core.Sizing; using NT8.Core.Sizing;
@@ -10,34 +11,77 @@ namespace NT8.Adapters.NinjaTrader
/// </summary> /// </summary>
public class NT8OrderAdapter public class NT8OrderAdapter
{ {
private readonly object _lock = new object();
private IRiskManager _riskManager; private IRiskManager _riskManager;
private IPositionSizer _positionSizer; private IPositionSizer _positionSizer;
private readonly List<NT8OrderExecutionRecord> _executionHistory;
/// <summary>
/// Constructor for NT8OrderAdapter.
/// </summary>
public NT8OrderAdapter()
{
_executionHistory = new List<NT8OrderExecutionRecord>();
}
/// <summary> /// <summary>
/// Initialize the order adapter with required components /// Initialize the order adapter with required components
/// </summary> /// </summary>
public void Initialize(IRiskManager riskManager, IPositionSizer positionSizer) public void Initialize(IRiskManager riskManager, IPositionSizer positionSizer)
{
if (riskManager == null)
{
throw new ArgumentNullException("riskManager");
}
if (positionSizer == null)
{
throw new ArgumentNullException("positionSizer");
}
try
{ {
_riskManager = riskManager; _riskManager = riskManager;
_positionSizer = positionSizer; _positionSizer = positionSizer;
} }
catch (Exception)
{
throw;
}
}
/// <summary> /// <summary>
/// Execute strategy intent through NT8 order management /// Execute strategy intent through NT8 order management
/// </summary> /// </summary>
public void ExecuteIntent(StrategyIntent intent, StrategyContext context, StrategyConfig config) 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) if (_riskManager == null || _positionSizer == null)
{ {
throw new InvalidOperationException("Adapter not initialized. Call Initialize() first."); throw new InvalidOperationException("Adapter not initialized. Call Initialize() first.");
} }
try
{
// Validate the intent through risk management // Validate the intent through risk management
var riskDecision = _riskManager.ValidateOrder(intent, context, config.RiskSettings); var riskDecision = _riskManager.ValidateOrder(intent, context, config.RiskSettings);
if (!riskDecision.Allow) if (!riskDecision.Allow)
{ {
// Log rejection and return // Risk rejected the order flow.
// In a real implementation, we would use a proper logging system
return; return;
} }
@@ -45,24 +89,69 @@ namespace NT8.Adapters.NinjaTrader
var sizingResult = _positionSizer.CalculateSize(intent, context, config.SizingSettings); var sizingResult = _positionSizer.CalculateSize(intent, context, config.SizingSettings);
if (sizingResult.Contracts <= 0) if (sizingResult.Contracts <= 0)
{ {
// Log that no position size was calculated // No tradable size produced.
return; return;
} }
// In a real implementation, this would call NT8's order execution methods // 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); ExecuteInNT8(intent, sizingResult);
} }
catch (Exception)
{
throw;
}
}
/// <summary>
/// Gets a snapshot of executions submitted through this adapter.
/// </summary>
/// <returns>Execution history snapshot.</returns>
public IList<NT8OrderExecutionRecord> GetExecutionHistory()
{
try
{
lock (_lock)
{
return new List<NT8OrderExecutionRecord>(_executionHistory);
}
}
catch (Exception)
{
throw;
}
}
/// <summary> /// <summary>
/// Execute the order in NT8 (placeholder implementation) /// Execute the order in NT8 (placeholder implementation)
/// </summary> /// </summary>
private void ExecuteInNT8(StrategyIntent intent, SizingResult sizing) 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 // This is where the actual NT8 order execution would happen
// In a real implementation, this would call NT8's EnterLong/EnterShort methods // In a real implementation, this would call NT8's EnterLong/EnterShort methods
// along with SetStopLoss, SetProfitTarget, etc. // 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: // Example of what this might look like in NT8:
/* /*
if (intent.Side == OrderSide.Buy) if (intent.Side == OrderSide.Buy)
@@ -91,11 +180,22 @@ namespace NT8.Adapters.NinjaTrader
/// </summary> /// </summary>
public void OnOrderUpdate(string orderId, double limitPrice, double stopPrice, int quantity, int filled, double averageFillPrice, string orderState, DateTime time, string errorCode, string nativeError) 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 (string.IsNullOrWhiteSpace(orderId))
{
throw new ArgumentException("orderId");
}
try
{
// Pass order updates to risk manager for tracking.
if (_riskManager != null) if (_riskManager != null)
{ {
// In a real implementation, we would convert NT8 order data to SDK format // In a real implementation, convert NT8 order data to SDK models.
// and pass it to the risk manager }
}
catch (Exception)
{
throw;
} }
} }
@@ -104,12 +204,83 @@ namespace NT8.Adapters.NinjaTrader
/// </summary> /// </summary>
public void OnExecutionUpdate(string executionId, string orderId, double price, int quantity, string marketPosition, DateTime time) 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 (string.IsNullOrWhiteSpace(executionId))
{
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) if (_riskManager != null)
{ {
// In a real implementation, we would convert NT8 execution data to SDK format // In a real implementation, convert NT8 execution data to SDK models.
// and pass it to the risk manager
} }
} }
catch (Exception)
{
throw;
}
}
}
/// <summary>
/// Execution record captured by NT8OrderAdapter for diagnostics and tests.
/// </summary>
public class NT8OrderExecutionRecord
{
/// <summary>
/// Trading symbol.
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Order side.
/// </summary>
public OrderSide Side { get; set; }
/// <summary>
/// Entry order type.
/// </summary>
public OrderType EntryType { get; set; }
/// <summary>
/// Executed contract quantity.
/// </summary>
public int Contracts { get; set; }
/// <summary>
/// Stop-loss distance in ticks.
/// </summary>
public int StopTicks { get; set; }
/// <summary>
/// Profit target distance in ticks.
/// </summary>
public int? TargetTicks { get; set; }
/// <summary>
/// Timestamp when the execution was recorded.
/// </summary>
public DateTime Timestamp { get; set; }
/// <summary>
/// Constructor for NT8OrderExecutionRecord.
/// </summary>
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;
}
} }
} }

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using NT8.Core.Common.Interfaces; using NT8.Core.Common.Interfaces;
using NT8.Core.Common.Models; using NT8.Core.Common.Models;
using NT8.Core.Logging;
using NT8.Core.Risk; using NT8.Core.Risk;
using NT8.Core.Sizing; using NT8.Core.Sizing;
using NT8.Adapters.NinjaTrader; using NT8.Adapters.NinjaTrader;
@@ -14,6 +15,8 @@ namespace NT8.Adapters.Wrappers
/// </summary> /// </summary>
public abstract class BaseNT8StrategyWrapper public abstract class BaseNT8StrategyWrapper
{ {
private readonly object _lock = new object();
#region SDK Components #region SDK Components
protected IStrategy _sdkStrategy; protected IStrategy _sdkStrategy;
@@ -21,6 +24,7 @@ namespace NT8.Adapters.Wrappers
protected IPositionSizer _positionSizer; protected IPositionSizer _positionSizer;
protected NT8Adapter _nt8Adapter; protected NT8Adapter _nt8Adapter;
protected StrategyConfig _strategyConfig; protected StrategyConfig _strategyConfig;
protected ILogger _logger;
#endregion #endregion
@@ -55,8 +59,13 @@ namespace NT8.Adapters.Wrappers
TargetTicks = 20; TargetTicks = 20;
RiskAmount = 100.0; RiskAmount = 100.0;
// Initialize SDK components // Initialize SDK components with default implementations.
InitializeSdkComponents(); // Derived wrappers can replace these through InitializeSdkComponents.
_logger = new BasicLogger("BaseNT8StrategyWrapper");
_riskManager = new BasicRiskManager(_logger);
_positionSizer = new BasicPositionSizer(_logger);
InitializeSdkComponents(_riskManager, _positionSizer, _logger);
} }
#endregion #endregion
@@ -77,14 +86,40 @@ namespace NT8.Adapters.Wrappers
/// </summary> /// </summary>
public void ProcessBarUpdate(BarData barData, StrategyContext context) public void ProcessBarUpdate(BarData barData, StrategyContext context)
{ {
// Call SDK strategy logic if (barData == null)
var intent = _sdkStrategy.OnBar(barData, context); throw new ArgumentNullException("barData");
if (context == null)
throw new ArgumentNullException("context");
try
{
StrategyIntent intent;
lock (_lock)
{
if (_sdkStrategy == null)
{
throw new InvalidOperationException("SDK strategy has not been initialized.");
}
intent = _sdkStrategy.OnBar(barData, context);
}
if (intent != null) if (intent != null)
{ {
// Convert SDK results to NT8 actions
ExecuteIntent(intent, context); ExecuteIntent(intent, context);
} }
} }
catch (Exception ex)
{
if (_logger != null)
{
_logger.LogError("Failed processing bar update for {0}: {1}", context.Symbol, ex.Message);
}
throw;
}
}
#endregion #endregion
@@ -93,19 +128,31 @@ namespace NT8.Adapters.Wrappers
/// <summary> /// <summary>
/// Initialize SDK components /// Initialize SDK components
/// </summary> /// </summary>
private void InitializeSdkComponents() protected virtual void InitializeSdkComponents(IRiskManager riskManager, IPositionSizer positionSizer, ILogger logger)
{ {
// In a real implementation, these would be injected or properly instantiated if (riskManager == null)
// For now, we'll create placeholder instances throw new ArgumentNullException("riskManager");
_riskManager = null; // This would be properly instantiated if (positionSizer == null)
_positionSizer = null; // This would be properly instantiated throw new ArgumentNullException("positionSizer");
if (logger == null)
throw new ArgumentNullException("logger");
_riskManager = riskManager;
_positionSizer = positionSizer;
_logger = logger;
// Create NT8 adapter
_nt8Adapter = new NT8Adapter(); _nt8Adapter = new NT8Adapter();
_nt8Adapter.Initialize(_riskManager, _positionSizer); _nt8Adapter.Initialize(_riskManager, _positionSizer);
// Create SDK strategy CreateSdkConfiguration();
_sdkStrategy = CreateSdkStrategy(); _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);
} }
/// <summary> /// <summary>
@@ -145,14 +192,37 @@ namespace NT8.Adapters.Wrappers
/// </summary> /// </summary>
private void ExecuteIntent(StrategyIntent intent, StrategyContext context) private void ExecuteIntent(StrategyIntent intent, StrategyContext context)
{ {
// Calculate position size if (intent == null)
var sizingResult = _positionSizer != null ? throw new ArgumentNullException("intent");
_positionSizer.CalculateSize(intent, context, _strategyConfig.SizingSettings) : if (context == null)
new SizingResult(1, RiskAmount, SizingMethod.FixedDollarRisk, new Dictionary<string, object>()); throw new ArgumentNullException("context");
try
{
SizingResult sizingResult;
lock (_lock)
{
if (_positionSizer == null)
{
throw new InvalidOperationException("Position sizer has not been initialized.");
}
sizingResult = _positionSizer.CalculateSize(intent, context, _strategyConfig.SizingSettings);
}
// Execute through NT8 adapter
_nt8Adapter.ExecuteIntent(intent, sizingResult); _nt8Adapter.ExecuteIntent(intent, sizingResult);
} }
catch (Exception ex)
{
if (_logger != null)
{
_logger.LogError("Failed executing intent for {0}: {1}", intent.Symbol, ex.Message);
}
throw;
}
}
#endregion #endregion
} }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using NT8.Core.Common.Interfaces; using NT8.Core.Common.Interfaces;
using NT8.Core.Common.Models; using NT8.Core.Common.Models;
using NT8.Core.Logging; using NT8.Core.Logging;
using NT8.Adapters.NinjaTrader;
namespace NT8.Adapters.Wrappers namespace NT8.Adapters.Wrappers
{ {
@@ -26,16 +27,6 @@ namespace NT8.Adapters.Wrappers
#endregion #endregion
#region Strategy State
private DateTime _openingRangeStart;
private double _openingRangeHigh;
private double _openingRangeLow;
private bool _openingRangeCalculated;
private double _rangeSize;
#endregion
#region Constructor #region Constructor
/// <summary> /// <summary>
@@ -45,19 +36,28 @@ namespace NT8.Adapters.Wrappers
{ {
OpeningRangeMinutes = 30; OpeningRangeMinutes = 30;
StdDevMultiplier = 1.0; StdDevMultiplier = 1.0;
_openingRangeCalculated = false;
} }
#endregion #endregion
#region Base Class Implementation #region Base Class Implementation
/// <summary>
/// Exposes adapter reference for integration test assertions.
/// </summary>
public NT8Adapter GetAdapterForTesting()
{
return _nt8Adapter;
}
/// <summary> /// <summary>
/// Create the SDK strategy implementation /// Create the SDK strategy implementation
/// </summary> /// </summary>
protected override IStrategy CreateSdkStrategy() 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 #endregion
@@ -69,10 +69,43 @@ namespace NT8.Adapters.Wrappers
/// </summary> /// </summary>
private class SimpleORBStrategy : IStrategy 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 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( Metadata = new StrategyMetadata(
name: "Simple ORB", name: "Simple ORB",
description: "Opening Range Breakout strategy", description: "Opening Range Breakout strategy",
@@ -85,17 +118,92 @@ namespace NT8.Adapters.Wrappers
public void Initialize(StrategyConfig config, IMarketDataProvider dataProvider, ILogger logger) public void Initialize(StrategyConfig config, IMarketDataProvider dataProvider, ILogger logger)
{ {
// Initialize strategy with configuration if (logger == null)
// In a real implementation, we would store references to the data provider and logger {
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) public StrategyIntent OnBar(BarData bar, StrategyContext context)
{ {
// This is where the actual strategy logic would go if (bar == null)
// For this example, we'll just return null to indicate no trade {
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; 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) public StrategyIntent OnTick(TickData tick, StrategyContext context)
{ {
// Most strategies don't need tick-level logic // Most strategies don't need tick-level logic
@@ -104,12 +212,66 @@ namespace NT8.Adapters.Wrappers
public Dictionary<string, object> GetParameters() public Dictionary<string, object> GetParameters()
{ {
return new Dictionary<string, object>(); var parameters = new Dictionary<string, object>();
parameters.Add("opening_range_minutes", _openingRangeMinutes);
parameters.Add("std_dev_multiplier", _stdDevMultiplier);
return parameters;
} }
public void SetParameters(Dictionary<string, object> parameters) public void SetParameters(Dictionary<string, object> 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<string, object>();
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);
} }
} }

View File

@@ -8,6 +8,8 @@ namespace NT8.Core.Common.Models
/// </summary> /// </summary>
public class RiskConfig public class RiskConfig
{ {
// Phase 1 - Basic Risk Properties
/// <summary> /// <summary>
/// Daily loss limit in dollars /// Daily loss limit in dollars
/// </summary> /// </summary>
@@ -28,8 +30,30 @@ namespace NT8.Core.Common.Models
/// </summary> /// </summary>
public bool EmergencyFlattenEnabled { get; set; } public bool EmergencyFlattenEnabled { get; set; }
// Phase 2 - Advanced Risk Properties (Optional)
/// <summary> /// <summary>
/// Constructor for RiskConfig /// Weekly loss limit in dollars (optional, for advanced risk management)
/// </summary>
public double? WeeklyLossLimit { get; set; }
/// <summary>
/// Trailing drawdown limit in dollars (optional, for advanced risk management)
/// </summary>
public double? TrailingDrawdownLimit { get; set; }
/// <summary>
/// Maximum cross-strategy exposure in dollars (optional, for advanced risk management)
/// </summary>
public double? MaxCrossStrategyExposure { get; set; }
/// <summary>
/// Maximum correlated exposure in dollars (optional, for advanced risk management)
/// </summary>
public double? MaxCorrelatedExposure { get; set; }
/// <summary>
/// Constructor for RiskConfig (Phase 1 - backward compatible)
/// </summary> /// </summary>
public RiskConfig( public RiskConfig(
double dailyLossLimit, double dailyLossLimit,
@@ -41,6 +65,35 @@ namespace NT8.Core.Common.Models
MaxTradeRisk = maxTradeRisk; MaxTradeRisk = maxTradeRisk;
MaxOpenPositions = maxOpenPositions; MaxOpenPositions = maxOpenPositions;
EmergencyFlattenEnabled = emergencyFlattenEnabled; EmergencyFlattenEnabled = emergencyFlattenEnabled;
// Phase 2 properties default to null (not set)
WeeklyLossLimit = null;
TrailingDrawdownLimit = null;
MaxCrossStrategyExposure = null;
MaxCorrelatedExposure = null;
}
/// <summary>
/// Constructor for RiskConfig (Phase 2 - with advanced parameters)
/// </summary>
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;
} }
} }