using System; using System.Collections.Generic; using NT8.Core.OMS; namespace NT8.Adapters.NinjaTrader { /// /// Adapter for executing orders through NinjaTrader 8 platform. /// Bridges SDK order requests to NT8 order submission and handles callbacks. /// Thread-safe for concurrent NT8 callbacks. /// public class NT8ExecutionAdapter { private readonly object _lock = new object(); private readonly Dictionary _orderTracking; private readonly Dictionary _nt8ToSdkOrderMap; /// /// Creates a new NT8 execution adapter. /// public NT8ExecutionAdapter() { _orderTracking = new Dictionary(); _nt8ToSdkOrderMap = new Dictionary(); } /// /// Submit an order to NinjaTrader 8. /// NOTE: This method tracks order state only. Actual NT8 submission is performed by strategy wrapper code. /// /// SDK order request. /// Unique SDK order ID. /// Tracking info for the submitted order. /// Thrown when request or sdkOrderId is invalid. /// Thrown when the same order ID is submitted twice. public OrderTrackingInfo SubmitOrder(OrderRequest request, string sdkOrderId) { if (request == null) { throw new ArgumentNullException("request"); } if (string.IsNullOrWhiteSpace(sdkOrderId)) { throw new ArgumentNullException("sdkOrderId"); } try { lock (_lock) { if (_orderTracking.ContainsKey(sdkOrderId)) { throw new InvalidOperationException(string.Format("Order {0} already exists", sdkOrderId)); } var trackingInfo = new OrderTrackingInfo(); trackingInfo.SdkOrderId = sdkOrderId; trackingInfo.Nt8OrderId = null; trackingInfo.OriginalRequest = request; trackingInfo.CurrentState = OrderState.Pending; trackingInfo.FilledQuantity = 0; trackingInfo.AverageFillPrice = 0.0; trackingInfo.LastUpdate = DateTime.UtcNow; trackingInfo.ErrorMessage = null; _orderTracking.Add(sdkOrderId, trackingInfo); return trackingInfo; } } catch (Exception) { throw; } } /// /// Process order update callback from NinjaTrader 8. /// Called by NT8 strategy wrapper OnOrderUpdate. /// /// NT8 order ID. /// SDK order ID. /// NT8 order state string. /// Filled quantity. /// Average fill price. /// Error code if rejected. /// Error message if rejected. public void ProcessOrderUpdate( string nt8OrderId, string sdkOrderId, string orderState, int filled, double averageFillPrice, int errorCode, string errorMessage) { if (string.IsNullOrWhiteSpace(sdkOrderId)) { return; } try { lock (_lock) { if (!_orderTracking.ContainsKey(sdkOrderId)) { return; } var info = _orderTracking[sdkOrderId]; if (!string.IsNullOrWhiteSpace(nt8OrderId) && info.Nt8OrderId == null) { info.Nt8OrderId = nt8OrderId; _nt8ToSdkOrderMap[nt8OrderId] = sdkOrderId; } info.CurrentState = MapNT8OrderState(orderState); info.FilledQuantity = filled; info.AverageFillPrice = averageFillPrice; info.LastUpdate = DateTime.UtcNow; if (errorCode != 0 && !string.IsNullOrWhiteSpace(errorMessage)) { info.ErrorMessage = string.Format("[{0}] {1}", errorCode, errorMessage); info.CurrentState = OrderState.Rejected; } } } catch (Exception) { throw; } } /// /// Process execution callback from NinjaTrader 8. /// Called by NT8 strategy wrapper OnExecutionUpdate. /// /// NT8 order ID. /// Execution identifier. /// Execution price. /// Execution quantity. /// Execution time. public void ProcessExecution( string nt8OrderId, string executionId, double price, int quantity, DateTime time) { if (string.IsNullOrWhiteSpace(nt8OrderId)) { return; } try { lock (_lock) { if (!_nt8ToSdkOrderMap.ContainsKey(nt8OrderId)) { return; } var sdkOrderId = _nt8ToSdkOrderMap[nt8OrderId]; if (!_orderTracking.ContainsKey(sdkOrderId)) { return; } var info = _orderTracking[sdkOrderId]; info.LastUpdate = time; if (info.FilledQuantity >= info.OriginalRequest.Quantity) { info.CurrentState = OrderState.Filled; } else if (info.FilledQuantity > 0) { info.CurrentState = OrderState.PartiallyFilled; } } } catch (Exception) { throw; } } /// /// Request to cancel an order. /// NOTE: Actual cancellation is performed by strategy wrapper code. /// /// SDK order ID to cancel. /// True when cancel request is accepted; otherwise false. public bool CancelOrder(string sdkOrderId) { if (string.IsNullOrWhiteSpace(sdkOrderId)) { throw new ArgumentNullException("sdkOrderId"); } try { lock (_lock) { if (!_orderTracking.ContainsKey(sdkOrderId)) { return false; } var info = _orderTracking[sdkOrderId]; if (info.CurrentState == OrderState.Filled || info.CurrentState == OrderState.Cancelled || info.CurrentState == OrderState.Rejected) { return false; } info.LastUpdate = DateTime.UtcNow; return true; } } catch (Exception) { throw; } } /// /// Get current status of an order. /// /// SDK order ID. /// Order status snapshot; null when not found. public OrderStatus GetOrderStatus(string sdkOrderId) { if (string.IsNullOrWhiteSpace(sdkOrderId)) { return null; } try { lock (_lock) { if (!_orderTracking.ContainsKey(sdkOrderId)) { return null; } var info = _orderTracking[sdkOrderId]; var status = new OrderStatus(); status.OrderId = info.SdkOrderId; status.Symbol = info.OriginalRequest.Symbol; status.Side = info.OriginalRequest.Side; status.Quantity = info.OriginalRequest.Quantity; status.Type = info.OriginalRequest.Type; status.State = info.CurrentState; status.FilledQuantity = info.FilledQuantity; status.AverageFillPrice = info.FilledQuantity > 0 ? (decimal)info.AverageFillPrice : 0m; status.CreatedTime = info.LastUpdate; status.FilledTime = info.FilledQuantity > 0 ? (DateTime?)info.LastUpdate : null; return status; } } catch (Exception) { throw; } } /// /// Maps NinjaTrader order state string to SDK order state. /// /// NT8 order state string. /// Mapped SDK state. private OrderState MapNT8OrderState(string nt8State) { if (string.IsNullOrWhiteSpace(nt8State)) { return OrderState.Expired; } switch (nt8State.ToUpperInvariant()) { case "ACCEPTED": case "WORKING": return OrderState.Working; case "FILLED": return OrderState.Filled; case "PARTFILLED": case "PARTIALLYFILLED": return OrderState.PartiallyFilled; case "CANCELLED": case "CANCELED": return OrderState.Cancelled; case "REJECTED": return OrderState.Rejected; case "PENDINGCANCEL": return OrderState.Working; case "PENDINGCHANGE": case "PENDINGSUBMIT": return OrderState.Pending; default: return OrderState.Expired; } } } /// /// Internal tracking information for orders managed by NT8ExecutionAdapter. /// public class OrderTrackingInfo { /// /// SDK order identifier. /// public string SdkOrderId { get; set; } /// /// NinjaTrader order identifier. /// public string Nt8OrderId { get; set; } /// /// Original order request. /// public OrderRequest OriginalRequest { get; set; } /// /// Current SDK order state. /// public OrderState CurrentState { get; set; } /// /// Filled quantity. /// public int FilledQuantity { get; set; } /// /// Average fill price. /// public double AverageFillPrice { get; set; } /// /// Last update timestamp. /// public DateTime LastUpdate { get; set; } /// /// Last error message. /// public string ErrorMessage { get; set; } } }