From 63200fe9b4e64ed1b25728f92f0e9c753bcffddc Mon Sep 17 00:00:00 2001 From: Billy Valentine Date: Tue, 9 Sep 2025 17:19:14 -0400 Subject: [PATCH] Implement NinjaTrader 8 adapter for integration --- src/NT8.Adapters/NinjaTrader/INT8Adapter.cs | 49 ++++++ src/NT8.Adapters/NinjaTrader/NT8Adapter.cs | 92 ++++++++++ .../NinjaTrader/NT8DataAdapter.cs | 51 ++++++ .../NinjaTrader/NT8LoggingAdapter.cs | 87 ++++++++++ .../NinjaTrader/NT8OrderAdapter.cs | 115 +++++++++++++ .../Wrappers/BaseNT8StrategyWrapper.cs | 159 ++++++++++++++++++ .../Wrappers/SimpleORBNT8Wrapper.cs | 118 +++++++++++++ 7 files changed, 671 insertions(+) create mode 100644 src/NT8.Adapters/NinjaTrader/INT8Adapter.cs create mode 100644 src/NT8.Adapters/NinjaTrader/NT8Adapter.cs create mode 100644 src/NT8.Adapters/NinjaTrader/NT8DataAdapter.cs create mode 100644 src/NT8.Adapters/NinjaTrader/NT8LoggingAdapter.cs create mode 100644 src/NT8.Adapters/NinjaTrader/NT8OrderAdapter.cs create mode 100644 src/NT8.Adapters/Wrappers/BaseNT8StrategyWrapper.cs create mode 100644 src/NT8.Adapters/Wrappers/SimpleORBNT8Wrapper.cs diff --git a/src/NT8.Adapters/NinjaTrader/INT8Adapter.cs b/src/NT8.Adapters/NinjaTrader/INT8Adapter.cs new file mode 100644 index 0000000..335b688 --- /dev/null +++ b/src/NT8.Adapters/NinjaTrader/INT8Adapter.cs @@ -0,0 +1,49 @@ +using System; +using NT8.Core.Common.Interfaces; +using NT8.Core.Common.Models; +using NT8.Core.Risk; +using NT8.Core.Sizing; + +namespace NT8.Adapters.NinjaTrader +{ + /// + /// Interface for NinjaTrader 8 integration adapter + /// + public interface INT8Adapter + { + /// + /// Initialize the adapter with required components + /// + void Initialize(IRiskManager riskManager, IPositionSizer positionSizer); + + /// + /// Convert NT8 bar data to SDK format + /// + BarData ConvertToSdkBar(string symbol, DateTime time, double open, double high, double low, double close, long volume, int barSize); + + /// + /// Convert NT8 account data to SDK format + /// + AccountInfo ConvertToSdkAccount(double equity, double buyingPower, double dailyPnL, double maxDrawdown, DateTime lastUpdate); + + /// + /// Convert NT8 position data to SDK format + /// + Position ConvertToSdkPosition(string symbol, int quantity, double averagePrice, double unrealizedPnL, double realizedPnL, DateTime lastUpdate); + + /// + /// Execute strategy intent through NT8 + /// + void ExecuteIntent(StrategyIntent intent, SizingResult sizing); + + /// + /// Handle order updates from NT8 + /// + void OnOrderUpdate(string orderId, double limitPrice, double stopPrice, int quantity, int filled, double averageFillPrice, string orderState, DateTime time, string errorCode, string nativeError); + + /// + /// Handle execution updates from NT8 + /// + void OnExecutionUpdate(string executionId, string orderId, double price, int quantity, string marketPosition, DateTime time); + } +} diff --git a/src/NT8.Adapters/NinjaTrader/NT8Adapter.cs b/src/NT8.Adapters/NinjaTrader/NT8Adapter.cs new file mode 100644 index 0000000..7b635ec --- /dev/null +++ b/src/NT8.Adapters/NinjaTrader/NT8Adapter.cs @@ -0,0 +1,92 @@ +using System; +using NT8.Core.Common.Interfaces; +using NT8.Core.Common.Models; +using NT8.Core.Risk; +using NT8.Core.Sizing; +using NT8.Core.Logging; + +namespace NT8.Adapters.NinjaTrader +{ + /// + /// Main NT8 adapter implementation that integrates all components + /// + public class NT8Adapter : INT8Adapter + { + private readonly NT8DataAdapter _dataAdapter; + private readonly NT8OrderAdapter _orderAdapter; + private readonly NT8LoggingAdapter _loggingAdapter; + private IRiskManager _riskManager; + private IPositionSizer _positionSizer; + + /// + /// Constructor for NT8Adapter + /// + public NT8Adapter() + { + _dataAdapter = new NT8DataAdapter(); + _orderAdapter = new NT8OrderAdapter(); + _loggingAdapter = new NT8LoggingAdapter(); + } + + /// + /// Initialize the adapter with required components + /// + public void Initialize(IRiskManager riskManager, IPositionSizer positionSizer) + { + _riskManager = riskManager; + _positionSizer = positionSizer; + _orderAdapter.Initialize(riskManager, positionSizer); + } + + /// + /// Convert NT8 bar data to SDK format + /// + public BarData ConvertToSdkBar(string symbol, DateTime time, double open, double high, double low, double close, long volume, int barSizeMinutes) + { + return _dataAdapter.ConvertToSdkBar(symbol, time, open, high, low, close, volume, barSizeMinutes); + } + + /// + /// Convert NT8 account data to SDK format + /// + public AccountInfo ConvertToSdkAccount(double equity, double buyingPower, double dailyPnL, double maxDrawdown, DateTime lastUpdate) + { + return _dataAdapter.ConvertToSdkAccount(equity, buyingPower, dailyPnL, maxDrawdown, lastUpdate); + } + + /// + /// Convert NT8 position data to SDK format + /// + public Position ConvertToSdkPosition(string symbol, int quantity, double averagePrice, double unrealizedPnL, double realizedPnL, DateTime lastUpdate) + { + return _dataAdapter.ConvertToSdkPosition(symbol, quantity, averagePrice, unrealizedPnL, realizedPnL, lastUpdate); + } + + /// + /// Execute strategy intent through NT8 + /// + public void ExecuteIntent(StrategyIntent intent, SizingResult 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); + } + + /// + /// Handle order updates from NT8 + /// + public void OnOrderUpdate(string orderId, double limitPrice, double stopPrice, int quantity, int filled, double averageFillPrice, string orderState, DateTime time, string errorCode, string nativeError) + { + _orderAdapter.OnOrderUpdate(orderId, limitPrice, stopPrice, quantity, filled, averageFillPrice, orderState, time, errorCode, nativeError); + } + + /// + /// Handle execution updates from NT8 + /// + public void OnExecutionUpdate(string executionId, string orderId, double price, int quantity, string marketPosition, DateTime time) + { + _orderAdapter.OnExecutionUpdate(executionId, orderId, price, quantity, marketPosition, time); + } + } +} diff --git a/src/NT8.Adapters/NinjaTrader/NT8DataAdapter.cs b/src/NT8.Adapters/NinjaTrader/NT8DataAdapter.cs new file mode 100644 index 0000000..10ee2cf --- /dev/null +++ b/src/NT8.Adapters/NinjaTrader/NT8DataAdapter.cs @@ -0,0 +1,51 @@ +using System; +using NT8.Core.Common.Models; + +namespace NT8.Adapters.NinjaTrader +{ + /// + /// Data adapter for converting between NT8 and SDK formats + /// + public class NT8DataAdapter + { + /// + /// Convert NT8 bar data to SDK format + /// + 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)); + } + + /// + /// Convert NT8 account data to SDK format + /// + public AccountInfo ConvertToSdkAccount(double equity, double buyingPower, double dailyPnL, double maxDrawdown, DateTime lastUpdate) + { + return new AccountInfo(equity, buyingPower, dailyPnL, maxDrawdown, lastUpdate); + } + + /// + /// Convert NT8 position data to SDK format + /// + public Position ConvertToSdkPosition(string symbol, int quantity, double averagePrice, double unrealizedPnL, double realizedPnL, DateTime lastUpdate) + { + return new Position(symbol, quantity, averagePrice, unrealizedPnL, realizedPnL, lastUpdate); + } + + /// + /// Convert NT8 market session data to SDK format + /// + public MarketSession ConvertToSdkSession(DateTime sessionStart, DateTime sessionEnd, bool isRth, string sessionName) + { + return new MarketSession(sessionStart, sessionEnd, isRth, sessionName); + } + + /// + /// Convert NT8 strategy context to SDK format + /// + 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); + } + } +} diff --git a/src/NT8.Adapters/NinjaTrader/NT8LoggingAdapter.cs b/src/NT8.Adapters/NinjaTrader/NT8LoggingAdapter.cs new file mode 100644 index 0000000..5b04778 --- /dev/null +++ b/src/NT8.Adapters/NinjaTrader/NT8LoggingAdapter.cs @@ -0,0 +1,87 @@ +using System; +using NT8.Core.Logging; + +namespace NT8.Adapters.NinjaTrader +{ + /// + /// Logging adapter for integrating with NT8's logging system + /// + public class NT8LoggingAdapter : ILogger + { + /// + /// Log debug message + /// + public void LogDebug(string message, params object[] args) + { + // In a real implementation, this would call NT8's logging system + // For now, we'll just use Console.WriteLine as a placeholder + Console.WriteLine("[DEBUG] " + FormatMessage(message, args)); + } + + /// + /// Log information message + /// + public void LogInformation(string message, params object[] args) + { + // In a real implementation, this would call NT8's logging system + Console.WriteLine("[INFO] " + FormatMessage(message, args)); + } + + /// + /// Log warning message + /// + public void LogWarning(string message, params object[] args) + { + // In a real implementation, this would call NT8's logging system + Console.WriteLine("[WARN] " + FormatMessage(message, args)); + } + + /// + /// Log error message + /// + public void LogError(string message, params object[] args) + { + // In a real implementation, this would call NT8's logging system + Console.WriteLine("[ERROR] " + FormatMessage(message, args)); + } + + /// + /// Log critical message + /// + public void LogCritical(string message, params object[] args) + { + // In a real implementation, this would call NT8's logging system + Console.WriteLine("[CRITICAL] " + FormatMessage(message, args)); + } + + /// + /// Log error with exception + /// + public void LogError(Exception ex, string message, params object[] args) + { + // In a real implementation, this would call NT8's logging system + Console.WriteLine("[ERROR] " + FormatMessage(message, args) + " - Exception: " + ex.ToString()); + } + + /// + /// Format message with arguments + /// + private string FormatMessage(string message, object[] args) + { + if (args == null || args.Length == 0) + { + return message; + } + + try + { + return string.Format(message, args); + } + catch + { + // If formatting fails, return the original message + return message; + } + } + } +} diff --git a/src/NT8.Adapters/NinjaTrader/NT8OrderAdapter.cs b/src/NT8.Adapters/NinjaTrader/NT8OrderAdapter.cs new file mode 100644 index 0000000..d266248 --- /dev/null +++ b/src/NT8.Adapters/NinjaTrader/NT8OrderAdapter.cs @@ -0,0 +1,115 @@ +using System; +using NT8.Core.Common.Models; +using NT8.Core.Risk; +using NT8.Core.Sizing; + +namespace NT8.Adapters.NinjaTrader +{ + /// + /// Order adapter for executing trades through NT8 + /// + public class NT8OrderAdapter + { + private IRiskManager _riskManager; + private IPositionSizer _positionSizer; + + /// + /// Initialize the order adapter with required components + /// + public void Initialize(IRiskManager riskManager, IPositionSizer positionSizer) + { + _riskManager = riskManager; + _positionSizer = positionSizer; + } + + /// + /// Execute strategy intent through NT8 order management + /// + public void ExecuteIntent(StrategyIntent intent, StrategyContext context, StrategyConfig 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) + { + // Log rejection and return + // In a real implementation, we would use a proper logging system + return; + } + + // Calculate position size + var sizingResult = _positionSizer.CalculateSize(intent, context, config.SizingSettings); + if (sizingResult.Contracts <= 0) + { + // Log that no position size was calculated + return; + } + + // 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); + } + + /// + /// Execute the order in NT8 (placeholder implementation) + /// + private void ExecuteInNT8(StrategyIntent intent, SizingResult 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. + + // Example of what this might look like in NT8: + /* + if (intent.Side == OrderSide.Buy) + { + EnterLong(sizing.Contracts, "SDK_Entry"); + SetStopLoss("SDK_Entry", CalculationMode.Ticks, intent.StopTicks); + if (intent.TargetTicks.HasValue) + { + SetProfitTarget("SDK_Entry", CalculationMode.Ticks, intent.TargetTicks.Value); + } + } + else if (intent.Side == OrderSide.Sell) + { + EnterShort(sizing.Contracts, "SDK_Entry"); + SetStopLoss("SDK_Entry", CalculationMode.Ticks, intent.StopTicks); + if (intent.TargetTicks.HasValue) + { + SetProfitTarget("SDK_Entry", CalculationMode.Ticks, intent.TargetTicks.Value); + } + } + */ + } + + /// + /// Handle order updates from NT8 + /// + 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) + { + // In a real implementation, we would convert NT8 order data to SDK format + // and pass it to the risk manager + } + } + + /// + /// Handle execution updates from NT8 + /// + 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) + { + // In a real implementation, we would convert NT8 execution data to SDK format + // and pass it to the risk manager + } + } + } +} diff --git a/src/NT8.Adapters/Wrappers/BaseNT8StrategyWrapper.cs b/src/NT8.Adapters/Wrappers/BaseNT8StrategyWrapper.cs new file mode 100644 index 0000000..0d48eb9 --- /dev/null +++ b/src/NT8.Adapters/Wrappers/BaseNT8StrategyWrapper.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using NT8.Core.Common.Interfaces; +using NT8.Core.Common.Models; +using NT8.Core.Risk; +using NT8.Core.Sizing; +using NT8.Adapters.NinjaTrader; + +namespace NT8.Adapters.Wrappers +{ + /// + /// Base wrapper class for NT8 strategies that integrate with the SDK + /// This is a template that would be extended in actual NT8 strategy files + /// + public abstract class BaseNT8StrategyWrapper + { + #region SDK Components + + protected IStrategy _sdkStrategy; + protected IRiskManager _riskManager; + protected IPositionSizer _positionSizer; + protected NT8Adapter _nt8Adapter; + protected StrategyConfig _strategyConfig; + + #endregion + + #region Properties + + /// + /// Stop loss in ticks + /// + public int StopTicks { get; set; } + + /// + /// Profit target in ticks (optional) + /// + public int TargetTicks { get; set; } + + /// + /// Risk amount per trade in dollars + /// + public double RiskAmount { get; set; } + + #endregion + + #region Constructor + + /// + /// Constructor for BaseNT8StrategyWrapper + /// + public BaseNT8StrategyWrapper() + { + // Set default values + StopTicks = 10; + TargetTicks = 20; + RiskAmount = 100.0; + + // Initialize SDK components + InitializeSdkComponents(); + } + + #endregion + + #region Abstract Methods + + /// + /// Create the SDK strategy implementation + /// + protected abstract IStrategy CreateSdkStrategy(); + + #endregion + + #region Public Methods + + /// + /// Process a bar update (would be called from NT8's OnBarUpdate) + /// + public void ProcessBarUpdate(BarData barData, StrategyContext context) + { + // Call SDK strategy logic + var intent = _sdkStrategy.OnBar(barData, context); + if (intent != null) + { + // Convert SDK results to NT8 actions + ExecuteIntent(intent, context); + } + } + + #endregion + + #region Private Methods + + /// + /// Initialize SDK components + /// + private void InitializeSdkComponents() + { + // 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 + + // Create NT8 adapter + _nt8Adapter = new NT8Adapter(); + _nt8Adapter.Initialize(_riskManager, _positionSizer); + + // Create SDK strategy + _sdkStrategy = CreateSdkStrategy(); + } + + /// + /// Create SDK configuration from parameters + /// + private void CreateSdkConfiguration() + { + // Create risk configuration + var riskConfig = new RiskConfig( + dailyLossLimit: 500.0, + maxTradeRisk: RiskAmount, + maxOpenPositions: 5, + emergencyFlattenEnabled: true + ); + + // Create sizing configuration + var sizingConfig = new SizingConfig( + method: SizingMethod.FixedDollarRisk, + minContracts: 1, + maxContracts: 100, + riskPerTrade: RiskAmount, + methodParameters: new Dictionary() + ); + + // Create strategy configuration + _strategyConfig = new StrategyConfig( + name: "NT8Strategy", + symbol: "Unknown", + parameters: new Dictionary(), + riskSettings: riskConfig, + sizingSettings: sizingConfig + ); + } + + /// + /// Execute strategy intent through NT8 + /// + 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()); + + // Execute through NT8 adapter + _nt8Adapter.ExecuteIntent(intent, sizingResult); + } + + #endregion + } +} diff --git a/src/NT8.Adapters/Wrappers/SimpleORBNT8Wrapper.cs b/src/NT8.Adapters/Wrappers/SimpleORBNT8Wrapper.cs new file mode 100644 index 0000000..0b30a8f --- /dev/null +++ b/src/NT8.Adapters/Wrappers/SimpleORBNT8Wrapper.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using NT8.Core.Common.Interfaces; +using NT8.Core.Common.Models; +using NT8.Core.Logging; + +namespace NT8.Adapters.Wrappers +{ + /// + /// Simple ORB (Opening Range Breakout) strategy wrapper for NT8 + /// This demonstrates how to implement a strategy that works with the SDK + /// + public class SimpleORBNT8Wrapper : BaseNT8StrategyWrapper + { + #region Strategy Parameters + + /// + /// Opening range period in minutes + /// + public int OpeningRangeMinutes { get; set; } + + /// + /// Number of standard deviations for breakout threshold + /// + public double StdDevMultiplier { get; set; } + + #endregion + + #region Strategy State + + private DateTime _openingRangeStart; + private double _openingRangeHigh; + private double _openingRangeLow; + private bool _openingRangeCalculated; + private double _rangeSize; + + #endregion + + #region Constructor + + /// + /// Constructor for SimpleORBNT8Wrapper + /// + public SimpleORBNT8Wrapper() + { + OpeningRangeMinutes = 30; + StdDevMultiplier = 1.0; + _openingRangeCalculated = false; + } + + #endregion + + #region Base Class Implementation + + /// + /// Create the SDK strategy implementation + /// + protected override IStrategy CreateSdkStrategy() + { + return new SimpleORBStrategy(); + } + + #endregion + + #region Strategy Logic + + /// + /// Simple ORB strategy implementation + /// + private class SimpleORBStrategy : IStrategy + { + public StrategyMetadata Metadata { get; private set; } + + public SimpleORBStrategy() + { + Metadata = new StrategyMetadata( + name: "Simple ORB", + description: "Opening Range Breakout strategy", + version: "1.0", + author: "NT8 SDK Team", + symbols: new string[] { "ES", "NQ", "YM" }, + requiredBars: 20 + ); + } + + public void Initialize(StrategyConfig config, IMarketDataProvider dataProvider, ILogger logger) + { + // Initialize strategy with configuration + // In a real implementation, we would store references to the data provider and logger + } + + 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; + } + + public StrategyIntent OnTick(TickData tick, StrategyContext context) + { + // Most strategies don't need tick-level logic + return null; + } + + public Dictionary GetParameters() + { + return new Dictionary(); + } + + public void SetParameters(Dictionary parameters) + { + // Set strategy parameters from configuration + } + } + + #endregion + } +}