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:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,16 +11,43 @@ 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)
|
||||||
{
|
{
|
||||||
_riskManager = riskManager;
|
if (riskManager == null)
|
||||||
_positionSizer = positionSizer;
|
{
|
||||||
|
throw new ArgumentNullException("riskManager");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positionSizer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("positionSizer");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_riskManager = riskManager;
|
||||||
|
_positionSizer = positionSizer;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -27,31 +55,70 @@ namespace NT8.Adapters.NinjaTrader
|
|||||||
/// </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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the intent through risk management
|
try
|
||||||
var riskDecision = _riskManager.ValidateOrder(intent, context, config.RiskSettings);
|
|
||||||
if (!riskDecision.Allow)
|
|
||||||
{
|
{
|
||||||
// Log rejection and return
|
// Validate the intent through risk management
|
||||||
// In a real implementation, we would use a proper logging system
|
var riskDecision = _riskManager.ValidateOrder(intent, context, config.RiskSettings);
|
||||||
return;
|
if (!riskDecision.Allow)
|
||||||
}
|
{
|
||||||
|
// Risk rejected the order flow.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate position size
|
// Calculate position size
|
||||||
var sizingResult = _positionSizer.CalculateSize(intent, context, config.SizingSettings);
|
var sizingResult = _positionSizer.CalculateSize(intent, context, config.SizingSettings);
|
||||||
if (sizingResult.Contracts <= 0)
|
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
|
throw;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// In a real implementation, this would call NT8's order execution methods
|
/// <summary>
|
||||||
// For now, we'll just log what would be executed
|
/// Gets a snapshot of executions submitted through this adapter.
|
||||||
ExecuteInNT8(intent, sizingResult);
|
/// </summary>
|
||||||
|
/// <returns>Execution history snapshot.</returns>
|
||||||
|
public IList<NT8OrderExecutionRecord> GetExecutionHistory()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return new List<NT8OrderExecutionRecord>(_executionHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -59,10 +126,32 @@ namespace NT8.Adapters.NinjaTrader
|
|||||||
/// </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))
|
||||||
if (_riskManager != null)
|
|
||||||
{
|
{
|
||||||
// In a real implementation, we would convert NT8 order data to SDK format
|
throw new ArgumentException("orderId");
|
||||||
// and pass it to the risk manager
|
}
|
||||||
|
|
||||||
|
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
|
|||||||
/// </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))
|
||||||
if (_riskManager != null)
|
|
||||||
{
|
{
|
||||||
// In a real implementation, we would convert NT8 execution data to SDK format
|
throw new ArgumentException("executionId");
|
||||||
// and pass it to the risk manager
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,12 +86,38 @@ 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 (intent != null)
|
if (context == null)
|
||||||
|
throw new ArgumentNullException("context");
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Convert SDK results to NT8 actions
|
StrategyIntent intent;
|
||||||
ExecuteIntent(intent, context);
|
|
||||||
|
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
|
|||||||
/// <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,13 +192,36 @@ 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");
|
||||||
|
|
||||||
// Execute through NT8 adapter
|
try
|
||||||
_nt8Adapter.ExecuteIntent(intent, sizingResult);
|
{
|
||||||
|
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
|
#endregion
|
||||||
|
|||||||
@@ -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,15 +118,90 @@ 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
|
{
|
||||||
return 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)
|
public StrategyIntent OnTick(TickData tick, StrategyContext context)
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user