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; }
}
}