diff --git a/src/NT8.Core/OMS/BasicOrderManager.cs b/src/NT8.Core/OMS/BasicOrderManager.cs
new file mode 100644
index 0000000..bd3d362
--- /dev/null
+++ b/src/NT8.Core/OMS/BasicOrderManager.cs
@@ -0,0 +1,678 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace NT8.Core.OMS
+{
+ ///
+ /// Basic implementation of the Order Management System with state machine
+ ///
+ public class BasicOrderManager : IOrderManager
+ {
+ private readonly ILogger _logger;
+ private readonly INT8OrderAdapter _nt8Adapter;
+ private readonly Dictionary _activeOrders;
+ private readonly Dictionary _pendingOrders;
+ private readonly List> _orderCallbacks;
+ private readonly object _lock;
+ private bool _disposed = false;
+
+ ///
+ /// Constructor for BasicOrderManager
+ ///
+ /// Logger instance
+ /// NT8 order adapter instance
+ public BasicOrderManager(ILogger logger, INT8OrderAdapter nt8Adapter)
+ {
+ if (logger == null)
+ throw new ArgumentNullException("logger");
+ if (nt8Adapter == null)
+ throw new ArgumentNullException("nt8Adapter");
+
+ _logger = logger;
+ _nt8Adapter = nt8Adapter;
+ _activeOrders = new Dictionary();
+ _pendingOrders = new Dictionary();
+ _orderCallbacks = new List>();
+ _lock = new object();
+
+ // Register callback to receive order updates from NT8
+ _nt8Adapter.RegisterOrderCallback(OnNT8OrderUpdate);
+
+ _logger.LogInformation("BasicOrderManager initialized");
+ }
+
+ ///
+ /// Submit new order for execution
+ ///
+ public async Task SubmitOrderAsync(OrderRequest request)
+ {
+ if (request == null)
+ throw new ArgumentNullException("request");
+
+ try
+ {
+ ValidateOrderRequest(request);
+ }
+ catch (ArgumentException ex)
+ {
+ _logger.LogError("Order validation failed: {0}", ex.Message);
+ return new OrderResult(false, null, ex.Message, request);
+ }
+
+ try
+ {
+ string orderId = GenerateOrderId(request);
+
+ // Create initial order status
+ var orderStatus = new OrderStatus
+ {
+ OrderId = orderId,
+ ClientOrderId = request.ClientOrderId,
+ Symbol = request.Symbol,
+ Side = request.Side,
+ Type = request.Type,
+ Quantity = request.Quantity,
+ LimitPrice = request.LimitPrice,
+ StopPrice = request.StopPrice,
+ State = OrderState.Pending,
+ CreatedTime = DateTime.UtcNow,
+ Fills = new List()
+ };
+
+ lock (_lock)
+ {
+ _pendingOrders[orderId] = request;
+ _activeOrders[orderId] = orderStatus;
+ }
+
+ _logger.LogDebug("Order {0} submitted to NT8 with request {1}", orderId, request.Symbol);
+
+ // Submit to NT8
+ bool nt8Result = await _nt8Adapter.SubmitOrderAsync(request);
+
+ if (nt8Result)
+ {
+ // Update state to submitted
+ UpdateOrderState(orderId, OrderState.Submitted);
+
+ _logger.LogInformation("Order {0} submitted successfully to NT8 for symbol {1}",
+ orderId, request.Symbol);
+
+ return new OrderResult(true, orderId, "Order submitted successfully", request);
+ }
+ else
+ {
+ // Update state to rejected
+ UpdateOrderState(orderId, OrderState.Rejected);
+
+ _logger.LogWarning("Order {0} submission failed at NT8 level for symbol {1}",
+ orderId, request.Symbol);
+
+ return new OrderResult(false, orderId, "Order submission failed at NT8 level", request);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Failed to submit order: {0}", ex.Message);
+ throw;
+ }
+ }
+
+ ///
+ /// Modify existing order
+ ///
+ public async Task ModifyOrderAsync(OrderModification modification)
+ {
+ if (modification == null)
+ throw new ArgumentNullException("modification");
+
+ try
+ {
+ ValidateOrderModification(modification);
+ }
+ catch (ArgumentException ex)
+ {
+ _logger.LogError("Order modification validation failed: {0}", ex.Message);
+ return false;
+ }
+
+ try
+ {
+ // Check if order exists and is in a modifiable state
+ OrderStatus orderStatus;
+ lock (_lock)
+ {
+ if (!_activeOrders.TryGetValue(modification.OrderId, out orderStatus))
+ {
+ _logger.LogWarning("Attempt to modify non-existent order: {0}", modification.OrderId);
+ return false;
+ }
+
+ // Only allow modifications for certain states
+ if (orderStatus.State != OrderState.Working && orderStatus.State != OrderState.Submitted)
+ {
+ _logger.LogWarning("Cannot modify order {0} in state {1}",
+ modification.OrderId, orderStatus.State);
+ return false;
+ }
+ }
+
+ _logger.LogDebug("Modifying order {0}", modification.OrderId);
+
+ // Send modification to NT8
+ bool result = await _nt8Adapter.ModifyOrderAsync(modification);
+
+ if (result)
+ {
+ _logger.LogInformation("Order {0} modified successfully", modification.OrderId);
+ }
+ else
+ {
+ _logger.LogWarning("Order {0} modification failed", modification.OrderId);
+ }
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Failed to modify order {0}: {1}", modification.OrderId, ex.Message);
+ throw;
+ }
+ }
+
+ ///
+ /// Cancel existing order
+ ///
+ public async Task CancelOrderAsync(OrderCancellation cancellation)
+ {
+ if (cancellation == null)
+ throw new ArgumentNullException("cancellation");
+
+ try
+ {
+ ValidateOrderCancellation(cancellation);
+ }
+ catch (ArgumentException ex)
+ {
+ _logger.LogError("Order cancellation validation failed: {0}", ex.Message);
+ return false;
+ }
+
+ try
+ {
+ // Check if order exists and is in a cancellable state
+ OrderStatus orderStatus;
+ lock (_lock)
+ {
+ if (!_activeOrders.TryGetValue(cancellation.OrderId, out orderStatus))
+ {
+ _logger.LogWarning("Attempt to cancel non-existent order: {0}", cancellation.OrderId);
+ return false;
+ }
+
+ // Only allow cancellation for certain states
+ if (orderStatus.State == OrderState.Filled ||
+ orderStatus.State == OrderState.Cancelled ||
+ orderStatus.State == OrderState.Rejected)
+ {
+ _logger.LogWarning("Cannot cancel order {0} in state {1}",
+ cancellation.OrderId, orderStatus.State);
+ return false;
+ }
+ }
+
+ _logger.LogDebug("Cancelling order {0}", cancellation.OrderId);
+
+ // Send cancellation to NT8
+ bool result = await _nt8Adapter.CancelOrderAsync(cancellation);
+
+ if (result)
+ {
+ _logger.LogInformation("Order {0} cancellation sent successfully to NT8", cancellation.OrderId);
+ }
+ else
+ {
+ _logger.LogWarning("Order {0} cancellation failed at NT8 level", cancellation.OrderId);
+ }
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Failed to cancel order {0}: {1}", cancellation.OrderId, ex.Message);
+ throw;
+ }
+ }
+
+ ///
+ /// Get current status of an order
+ ///
+ public async Task GetOrderStatusAsync(string orderId)
+ {
+ if (string.IsNullOrEmpty(orderId))
+ throw new ArgumentNullException("orderId");
+
+ lock (_lock)
+ {
+ OrderStatus status;
+ _activeOrders.TryGetValue(orderId, out status);
+ return status;
+ }
+ }
+
+ ///
+ /// Get all active orders (working, partially filled, etc.)
+ ///
+ public async Task> GetActiveOrdersAsync()
+ {
+ lock (_lock)
+ {
+ return _activeOrders.Values
+ .Where(o => IsOrderActive(o.State))
+ .ToList();
+ }
+ }
+
+ ///
+ /// Get all orders for a specific symbol
+ ///
+ public async Task> GetOrdersBySymbolAsync(string symbol)
+ {
+ if (string.IsNullOrEmpty(symbol))
+ throw new ArgumentNullException("symbol");
+
+ lock (_lock)
+ {
+ return _activeOrders.Values
+ .Where(o => string.Equals(o.Symbol, symbol, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+ }
+ }
+
+ ///
+ /// Flatten all positions for a specific symbol (cancel all working orders)
+ ///
+ public async Task FlattenSymbolAsync(string symbol)
+ {
+ if (string.IsNullOrEmpty(symbol))
+ throw new ArgumentNullException("symbol");
+
+ try
+ {
+ var ordersToCancel = await GetOrdersBySymbolAsync(symbol);
+
+ var cancellableOrders = ordersToCancel.Where(o =>
+ o.State == OrderState.Working ||
+ o.State == OrderState.Submitted ||
+ o.State == OrderState.PartiallyFilled).ToList();
+
+ bool allSuccess = true;
+
+ foreach (var order in cancellableOrders)
+ {
+ var cancellation = new OrderCancellation(order.OrderId, "FlattenSymbol requested");
+ bool result = await CancelOrderAsync(cancellation);
+
+ if (!result)
+ {
+ allSuccess = false;
+ _logger.LogWarning("Failed to cancel order {0} during FlattenSymbol for {1}",
+ order.OrderId, symbol);
+ }
+ }
+
+ if (allSuccess)
+ {
+ _logger.LogInformation("Successfully flattened symbol {0}, cancelled {1} orders",
+ symbol, cancellableOrders.Count);
+ }
+ else
+ {
+ _logger.LogWarning("Partial success flattening symbol {0}, failed to cancel some orders", symbol);
+ }
+
+ return allSuccess;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Error during FlattenSymbol for {0}: {1}", symbol, ex.Message);
+ throw;
+ }
+ }
+
+ ///
+ /// Flatten all positions across all symbols (cancel all working orders)
+ ///
+ public async Task FlattenAllAsync()
+ {
+ try
+ {
+ var allOrders = await GetActiveOrdersAsync();
+
+ var cancellableOrders = allOrders.Where(o =>
+ o.State == OrderState.Working ||
+ o.State == OrderState.Submitted ||
+ o.State == OrderState.PartiallyFilled).ToList();
+
+ bool allSuccess = true;
+
+ foreach (var order in cancellableOrders)
+ {
+ var cancellation = new OrderCancellation(order.OrderId, "FlattenAll requested");
+ bool result = await CancelOrderAsync(cancellation);
+
+ if (!result)
+ {
+ allSuccess = false;
+ _logger.LogWarning("Failed to cancel order {0} during FlattenAll", order.OrderId);
+ }
+ }
+
+ if (allSuccess)
+ {
+ _logger.LogInformation("Successfully flattened all symbols, cancelled {0} orders",
+ cancellableOrders.Count);
+ }
+ else
+ {
+ _logger.LogWarning("Partial success flattening all symbols, failed to cancel some orders");
+ }
+
+ return allSuccess;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Error during FlattenAll: {0}", ex.Message);
+ throw;
+ }
+ }
+
+ ///
+ /// Subscribe to order status updates
+ ///
+ public void SubscribeToOrderUpdates(Action callback)
+ {
+ if (callback == null)
+ throw new ArgumentNullException("callback");
+
+ lock (_lock)
+ {
+ _orderCallbacks.Add(callback);
+ }
+ }
+
+ ///
+ /// Unsubscribe from order status updates
+ ///
+ public void UnsubscribeFromOrderUpdates(Action callback)
+ {
+ if (callback == null)
+ throw new ArgumentNullException("callback");
+
+ lock (_lock)
+ {
+ _orderCallbacks.Remove(callback);
+ }
+ }
+
+ ///
+ /// Process order updates from NT8
+ ///
+ private void OnNT8OrderUpdate(OrderStatus updatedStatus)
+ {
+ try
+ {
+ OrderStatus existingStatus;
+ lock (_lock)
+ {
+ if (!_activeOrders.TryGetValue(updatedStatus.OrderId, out existingStatus))
+ {
+ _logger.LogWarning("Received update for unknown order: {0}", updatedStatus.OrderId);
+ return;
+ }
+
+ // Update the order status
+ _activeOrders[updatedStatus.OrderId] = updatedStatus;
+
+ // Remove from pending if it was there
+ if (_pendingOrders.ContainsKey(updatedStatus.OrderId))
+ {
+ _pendingOrders.Remove(updatedStatus.OrderId);
+ }
+ }
+
+ // Log state changes
+ if (existingStatus.State != updatedStatus.State)
+ {
+ _logger.LogDebug("Order {0} state changed from {1} to {2}",
+ updatedStatus.OrderId, existingStatus.State, updatedStatus.State);
+ }
+
+ // Trigger callbacks outside of lock to prevent deadlocks
+ lock (_lock)
+ {
+ foreach (var callback in _orderCallbacks.ToList())
+ {
+ try
+ {
+ callback(updatedStatus);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Error in order callback: {0}", ex.Message);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Error processing NT8 order update: {0}", ex.Message);
+ }
+ }
+
+ ///
+ /// Update order state safely
+ ///
+ private void UpdateOrderState(string orderId, OrderState newState)
+ {
+ OrderStatus existingStatus;
+
+ lock (_lock)
+ {
+ if (!_activeOrders.TryGetValue(orderId, out existingStatus))
+ {
+ return;
+ }
+
+ // Validate state transition
+ if (!IsValidStateTransition(existingStatus.State, newState))
+ {
+ _logger.LogWarning("Invalid state transition for order {0}: {1} -> {2}",
+ orderId, existingStatus.State, newState);
+ return;
+ }
+
+ // Create updated status
+ var updatedStatus = new OrderStatus
+ {
+ OrderId = existingStatus.OrderId,
+ ClientOrderId = existingStatus.ClientOrderId,
+ Symbol = existingStatus.Symbol,
+ Side = existingStatus.Side,
+ Type = existingStatus.Type,
+ Quantity = existingStatus.Quantity,
+ FilledQuantity = existingStatus.FilledQuantity,
+ LimitPrice = existingStatus.LimitPrice,
+ StopPrice = existingStatus.StopPrice,
+ State = newState,
+ CreatedTime = existingStatus.CreatedTime,
+ FilledTime = existingStatus.FilledTime,
+ Fills = existingStatus.Fills,
+ AverageFillPrice = existingStatus.AverageFillPrice,
+ FillValue = existingStatus.FillValue
+ };
+
+ // Set fill time if transitioning to filled state
+ if (newState == OrderState.Filled && existingStatus.State != OrderState.Filled)
+ {
+ updatedStatus.FilledTime = DateTime.UtcNow;
+ }
+
+ _activeOrders[orderId] = updatedStatus;
+ }
+
+ // Trigger callbacks outside of lock to prevent deadlocks
+ lock (_lock)
+ {
+ foreach (var callback in _orderCallbacks.ToList())
+ {
+ try
+ {
+ callback(_activeOrders[orderId]);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Error in order callback: {0}", ex.Message);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Validate order request
+ ///
+ private void ValidateOrderRequest(OrderRequest request)
+ {
+ if (string.IsNullOrEmpty(request.Symbol))
+ throw new ArgumentException("Symbol is required", "request.Symbol");
+
+ if (request.Quantity <= 0)
+ throw new ArgumentException("Quantity must be greater than 0", "request.Quantity");
+
+ if (request.LimitPrice.HasValue && request.LimitPrice <= 0)
+ throw new ArgumentException("Limit price must be greater than 0", "request.LimitPrice");
+
+ if (request.StopPrice.HasValue && request.StopPrice <= 0)
+ throw new ArgumentException("Stop price must be greater than 0", "request.StopPrice");
+ }
+
+ ///
+ /// Validate order modification
+ ///
+ private void ValidateOrderModification(OrderModification modification)
+ {
+ if (string.IsNullOrEmpty(modification.OrderId))
+ throw new ArgumentException("Order ID is required", "modification.OrderId");
+
+ if (!modification.NewQuantity.HasValue &&
+ !modification.NewLimitPrice.HasValue &&
+ !modification.NewStopPrice.HasValue &&
+ !modification.NewTimeInForce.HasValue)
+ {
+ throw new ArgumentException("At least one modification parameter must be specified");
+ }
+
+ if (modification.NewQuantity.HasValue && modification.NewQuantity <= 0)
+ throw new ArgumentException("New quantity must be greater than 0", "modification.NewQuantity");
+
+ if (modification.NewLimitPrice.HasValue && modification.NewLimitPrice <= 0)
+ throw new ArgumentException("New limit price must be greater than 0", "modification.NewLimitPrice");
+
+ if (modification.NewStopPrice.HasValue && modification.NewStopPrice <= 0)
+ throw new ArgumentException("New stop price must be greater than 0", "modification.NewStopPrice");
+ }
+
+ ///
+ /// Validate order cancellation
+ ///
+ private void ValidateOrderCancellation(OrderCancellation cancellation)
+ {
+ if (string.IsNullOrEmpty(cancellation.OrderId))
+ throw new ArgumentException("Order ID is required", "cancellation.OrderId");
+ }
+
+ ///
+ /// Generate unique order ID
+ ///
+ private string GenerateOrderId(OrderRequest request)
+ {
+ string guidString = Guid.NewGuid().ToString("N");
+ string shortGuid = guidString.Substring(0, Math.Min(8, guidString.Length)).ToUpper();
+ return string.Format("OMS-{0}-{1}",
+ request.Symbol.Replace(".", ""),
+ shortGuid);
+ }
+
+ ///
+ /// Check if state transition is valid
+ ///
+ private bool IsValidStateTransition(OrderState currentState, OrderState newState)
+ {
+ // Define valid state transitions
+ switch (currentState)
+ {
+ case OrderState.Pending:
+ return newState == OrderState.Submitted ||
+ newState == OrderState.Rejected;
+
+ case OrderState.Submitted:
+ return newState == OrderState.Accepted ||
+ newState == OrderState.Rejected ||
+ newState == OrderState.Cancelled;
+
+ case OrderState.Accepted:
+ return newState == OrderState.Working ||
+ newState == OrderState.Cancelled ||
+ newState == OrderState.Rejected;
+
+ case OrderState.Working:
+ return newState == OrderState.PartiallyFilled ||
+ newState == OrderState.Filled ||
+ newState == OrderState.Cancelled ||
+ newState == OrderState.Expired;
+
+ case OrderState.PartiallyFilled:
+ return newState == OrderState.Filled ||
+ newState == OrderState.Cancelled ||
+ newState == OrderState.Expired;
+
+ case OrderState.Filled:
+ case OrderState.Cancelled:
+ case OrderState.Rejected:
+ case OrderState.Expired:
+ // Terminal states - no further transitions allowed
+ return false;
+
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// Check if order is in active state
+ ///
+ private bool IsOrderActive(OrderState state)
+ {
+ return state == OrderState.Pending ||
+ state == OrderState.Submitted ||
+ state == OrderState.Accepted ||
+ state == OrderState.Working ||
+ state == OrderState.PartiallyFilled;
+ }
+
+ ///
+ /// Dispose resources
+ ///
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _nt8Adapter.UnregisterOrderCallback(OnNT8OrderUpdate);
+ _disposed = true;
+ }
+ }
+ }
+}
diff --git a/src/NT8.Core/OMS/INT8OrderAdapter.cs b/src/NT8.Core/OMS/INT8OrderAdapter.cs
new file mode 100644
index 0000000..de99cfa
--- /dev/null
+++ b/src/NT8.Core/OMS/INT8OrderAdapter.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Threading.Tasks;
+
+namespace NT8.Core.OMS
+{
+ ///
+ /// NinjaTrader 8 order adapter interface - provides abstraction layer between OMS and NT8
+ ///
+ public interface INT8OrderAdapter : IDisposable
+ {
+ ///
+ /// Submit order to NinjaTrader 8
+ ///
+ /// Order request to submit
+ /// True if submission successful, false otherwise
+ Task SubmitOrderAsync(OrderRequest request);
+
+ ///
+ /// Modify existing order in NinjaTrader 8
+ ///
+ /// Order modification parameters
+ /// True if modification successful, false otherwise
+ Task ModifyOrderAsync(OrderModification modification);
+
+ ///
+ /// Cancel order in NinjaTrader 8
+ ///
+ /// Order cancellation request
+ /// True if cancellation successful, false otherwise
+ Task CancelOrderAsync(OrderCancellation cancellation);
+
+ ///
+ /// Register callback for order status updates from NinjaTrader 8
+ ///
+ /// Callback function to receive order updates
+ void RegisterOrderCallback(Action callback);
+
+ ///
+ /// Unregister callback for order status updates
+ ///
+ /// Callback function to unregister
+ void UnregisterOrderCallback(Action callback);
+
+ ///
+ /// Connect to NinjaTrader 8
+ ///
+ /// True if connection successful, false otherwise
+ Task ConnectAsync();
+
+ ///
+ /// Disconnect from NinjaTrader 8
+ ///
+ /// True if disconnection successful, false otherwise
+ Task DisconnectAsync();
+ }
+}
diff --git a/src/NT8.Core/OMS/IOrderManager.cs b/src/NT8.Core/OMS/IOrderManager.cs
new file mode 100644
index 0000000..1a0f6f4
--- /dev/null
+++ b/src/NT8.Core/OMS/IOrderManager.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace NT8.Core.OMS
+{
+ ///
+ /// Order management interface - manages complete order lifecycle
+ ///
+ public interface IOrderManager : IDisposable
+ {
+ ///
+ /// Submit new order for execution
+ ///
+ /// Order request with all parameters
+ /// Order result with unique order ID for tracking
+ /// Request is null
+ /// Request validation fails
+ Task SubmitOrderAsync(OrderRequest request);
+
+ ///
+ /// Modify existing order
+ ///
+ /// Order modification parameters
+ /// True if modification was successful, false otherwise
+ /// Modification is null
+ /// Modification validation fails
+ Task ModifyOrderAsync(OrderModification modification);
+
+ ///
+ /// Cancel existing order
+ ///
+ /// Order cancellation request
+ /// True if cancellation was successful, false otherwise
+ /// Cancellation is null
+ /// Cancellation validation fails
+ Task CancelOrderAsync(OrderCancellation cancellation);
+
+ ///
+ /// Get current status of an order
+ ///
+ /// Order ID to query
+ /// Current order status, or null if order not found
+ Task GetOrderStatusAsync(string orderId);
+
+ ///
+ /// Get all active orders (working, partially filled, etc.)
+ ///
+ /// List of active order statuses
+ Task> GetActiveOrdersAsync();
+
+ ///
+ /// Get all orders for a specific symbol
+ ///
+ /// Symbol to filter by
+ /// List of order statuses for the symbol
+ Task> GetOrdersBySymbolAsync(string symbol);
+
+ ///
+ /// Flatten all positions for a specific symbol (cancel all working orders and close positions)
+ ///
+ /// Symbol to flatten
+ /// True if flatten operation initiated successfully
+ Task FlattenSymbolAsync(string symbol);
+
+ ///
+ /// Flatten all positions across all symbols (cancel all working orders and close all positions)
+ ///
+ /// True if flatten all operation initiated successfully
+ Task FlattenAllAsync();
+
+ ///
+ /// Subscribe to order status updates
+ ///
+ /// Callback function to receive order updates
+ void SubscribeToOrderUpdates(Action callback);
+
+ ///
+ /// Unsubscribe from order status updates
+ ///
+ /// Callback function to unsubscribe
+ void UnsubscribeFromOrderUpdates(Action callback);
+ }
+}
diff --git a/src/NT8.Core/OMS/OrderModels.cs b/src/NT8.Core/OMS/OrderModels.cs
new file mode 100644
index 0000000..f180d08
--- /dev/null
+++ b/src/NT8.Core/OMS/OrderModels.cs
@@ -0,0 +1,359 @@
+using System;
+using System.Collections.Generic;
+
+namespace NT8.Core.OMS
+{
+ #region Enumerations
+
+ ///
+ /// Order side enumeration
+ ///
+ public enum OrderSide
+ {
+ Buy = 1,
+ Sell = -1
+ }
+
+ ///
+ /// Order type enumeration
+ ///
+ public enum OrderType
+ {
+ Market,
+ Limit,
+ StopMarket,
+ StopLimit
+ }
+
+ ///
+ /// Order state enumeration for the OMS state machine
+ ///
+ public enum OrderState
+ {
+ Pending, // Order request created, waiting for risk approval
+ Submitted, // Sent to broker, waiting for acceptance
+ Accepted, // Broker accepted the order
+ Working, // Order is live in the market
+ PartiallyFilled, // Order partially filled
+ Filled, // Order completely filled
+ Cancelled, // Order cancelled by user or system
+ Rejected, // Order rejected by broker or system
+ Expired // Order expired
+ }
+
+ ///
+ /// Time in force enumeration
+ ///
+ public enum TimeInForce
+ {
+ Day,
+ Gtc, // Good Till Cancelled
+ Ioc, // Immediate Or Cancel
+ Fok // Fill Or Kill
+ }
+
+ #endregion
+
+ #region Core Order Models
+
+ ///
+ /// Order request parameters
+ ///
+ public class OrderRequest
+ {
+ ///
+ /// Trading symbol
+ ///
+ public string Symbol { get; set; }
+
+ ///
+ /// Order side
+ ///
+ public OrderSide Side { get; set; }
+
+ ///
+ /// Order type
+ ///
+ public OrderType Type { get; set; }
+
+ ///
+ /// Order quantity
+ ///
+ public int Quantity { get; set; }
+
+ ///
+ /// Limit price (if applicable)
+ ///
+ public decimal? LimitPrice { get; set; }
+
+ ///
+ /// Stop price (if applicable)
+ ///
+ public decimal? StopPrice { get; set; }
+
+ ///
+ /// Time in force
+ ///
+ public TimeInForce TimeInForce { get; set; }
+
+ ///
+ /// Unique identifier for this order request
+ ///
+ public string ClientOrderId { get; set; }
+
+ ///
+ /// Timestamp when order was created
+ ///
+ public DateTime CreatedTime { get; set; }
+
+ ///
+ /// Constructor for OrderRequest
+ ///
+ public OrderRequest()
+ {
+ CreatedTime = DateTime.UtcNow;
+ }
+ }
+
+ ///
+ /// Order submission result
+ ///
+ public class OrderResult
+ {
+ ///
+ /// Whether the order submission was successful
+ ///
+ public bool Success { get; set; }
+
+ ///
+ /// Order ID if successful
+ ///
+ public string OrderId { get; set; }
+
+ ///
+ /// Message describing the result
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// Original order request
+ ///
+ public OrderRequest Request { get; set; }
+
+ ///
+ /// Constructor for OrderResult
+ ///
+ public OrderResult(bool success, string orderId, string message, OrderRequest request)
+ {
+ Success = success;
+ OrderId = orderId;
+ Message = message;
+ Request = request;
+ }
+ }
+
+ ///
+ /// Current order status with full state information
+ ///
+ public class OrderStatus
+ {
+ ///
+ /// Internal order ID assigned by the OMS
+ ///
+ public string OrderId { get; set; }
+
+ ///
+ /// Client-provided order ID
+ ///
+ public string ClientOrderId { get; set; }
+
+ ///
+ /// Trading symbol
+ ///
+ public string Symbol { get; set; }
+
+ ///
+ /// Order side
+ ///
+ public OrderSide Side { get; set; }
+
+ ///
+ /// Order type
+ ///
+ public OrderType Type { get; set; }
+
+ ///
+ /// Original order quantity
+ ///
+ public int Quantity { get; set; }
+
+ ///
+ /// Filled quantity
+ ///
+ public int FilledQuantity { get; set; }
+
+ ///
+ /// Remaining quantity
+ ///
+ public int RemainingQuantity { get { return Quantity - FilledQuantity; } }
+
+ ///
+ /// Limit price (if applicable)
+ ///
+ public decimal? LimitPrice { get; set; }
+
+ ///
+ /// Stop price (if applicable)
+ ///
+ public decimal? StopPrice { get; set; }
+
+ ///
+ /// Current order state
+ ///
+ public OrderState State { get; set; }
+
+ ///
+ /// Order creation time
+ ///
+ public DateTime CreatedTime { get; set; }
+
+ ///
+ /// Order fill time (if filled)
+ ///
+ public DateTime? FilledTime { get; set; }
+
+ ///
+ /// Order fills
+ ///
+ public List Fills { get; set; }
+
+ ///
+ /// Average fill price
+ ///
+ public decimal AverageFillPrice { get; set; }
+
+ ///
+ /// Total value of filled shares
+ ///
+ public decimal FillValue { get; set; }
+
+ ///
+ /// Constructor for OrderStatus
+ ///
+ public OrderStatus()
+ {
+ Fills = new List();
+ CreatedTime = DateTime.UtcNow;
+ }
+ }
+
+ ///
+ /// Represents a single fill event for an order
+ ///
+ public class OrderFill
+ {
+ ///
+ /// Fill ID from the broker
+ ///
+ public string FillId { get; set; }
+
+ ///
+ /// Order ID this fill belongs to
+ ///
+ public string OrderId { get; set; }
+
+ ///
+ /// Quantity filled in this transaction
+ ///
+ public int FillQuantity { get; set; }
+
+ ///
+ /// Price at which the fill occurred
+ ///
+ public decimal FillPrice { get; set; }
+
+ ///
+ /// Timestamp of the fill
+ ///
+ public DateTime FillTime { get; set; }
+
+ ///
+ /// Commission paid for this fill
+ ///
+ public decimal Commission { get; set; }
+
+ ///
+ /// Constructor for OrderFill
+ ///
+ public OrderFill()
+ {
+ FillTime = DateTime.UtcNow;
+ }
+ }
+
+ ///
+ /// Order modification parameters
+ ///
+ public class OrderModification
+ {
+ ///
+ /// Order ID to modify
+ ///
+ public string OrderId { get; set; }
+
+ ///
+ /// New quantity (if changing)
+ ///
+ public int? NewQuantity { get; set; }
+
+ ///
+ /// New limit price (if changing)
+ ///
+ public decimal? NewLimitPrice { get; set; }
+
+ ///
+ /// New stop price (if changing)
+ ///
+ public decimal? NewStopPrice { get; set; }
+
+ ///
+ /// New time in force (if changing)
+ ///
+ public TimeInForce? NewTimeInForce { get; set; }
+
+ ///
+ /// Constructor for OrderModification
+ ///
+ public OrderModification(string orderId)
+ {
+ OrderId = orderId;
+ }
+ }
+
+ ///
+ /// Order cancellation request
+ ///
+ public class OrderCancellation
+ {
+ ///
+ /// Order ID to cancel
+ ///
+ public string OrderId { get; set; }
+
+ ///
+ /// Reason for cancellation
+ ///
+ public string Reason { get; set; }
+
+ ///
+ /// Constructor for OrderCancellation
+ ///
+ public OrderCancellation(string orderId, string reason)
+ {
+ OrderId = orderId;
+ Reason = reason;
+ }
+ }
+
+ #endregion
+}
diff --git a/tests/NT8.Core.Tests/Mocks/MockLogger.cs b/tests/NT8.Core.Tests/Mocks/MockLogger.cs
new file mode 100644
index 0000000..078e62f
--- /dev/null
+++ b/tests/NT8.Core.Tests/Mocks/MockLogger.cs
@@ -0,0 +1,34 @@
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace NT8.Core.Tests.Mocks
+{
+ ///
+ /// Simple mock implementation of ILogger for testing purposes
+ ///
+ public class MockLogger : ILogger
+ {
+ public IDisposable BeginScope(TState state)
+ {
+ return new MockDisposable();
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return true;
+ }
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ // Mock implementation - do nothing
+ }
+
+ private class MockDisposable : IDisposable
+ {
+ public void Dispose()
+ {
+ // Mock implementation - do nothing
+ }
+ }
+ }
+}
diff --git a/tests/NT8.Core.Tests/Mocks/MockNT8OrderAdapter.cs b/tests/NT8.Core.Tests/Mocks/MockNT8OrderAdapter.cs
new file mode 100644
index 0000000..9372265
--- /dev/null
+++ b/tests/NT8.Core.Tests/Mocks/MockNT8OrderAdapter.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using NT8.Core.OMS;
+
+namespace NT8.Core.Tests.Mocks
+{
+ ///
+ /// Mock implementation of INT8OrderAdapter for testing purposes
+ ///
+ public class MockNT8OrderAdapter : INT8OrderAdapter
+ {
+ private readonly List> _callbacks;
+ private readonly object _lock;
+ private bool _disposed = false;
+ private bool _isConnected = false;
+ private bool _shouldSucceed = true;
+ private bool _shouldFail = false;
+ private int _submitOrderCallCount = 0;
+ private int _modifyOrderCallCount = 0;
+ private int _cancelOrderCallCount = 0;
+
+ ///
+ /// Gets or sets whether the next operation should succeed
+ ///
+ public bool ShouldSucceed
+ {
+ get { return _shouldSucceed; }
+ set { _shouldSucceed = value; }
+ }
+
+ ///
+ /// Gets or sets whether the next operation should fail
+ ///
+ public bool ShouldFail
+ {
+ get { return _shouldFail; }
+ set { _shouldFail = value; }
+ }
+
+ ///
+ /// Gets the count of submitted orders
+ ///
+ public int SubmitOrderCallCount
+ {
+ get { return _submitOrderCallCount; }
+ private set { _submitOrderCallCount = value; }
+ }
+
+ ///
+ /// Gets the count of modified orders
+ ///
+ public int ModifyOrderCallCount
+ {
+ get { return _modifyOrderCallCount; }
+ private set { _modifyOrderCallCount = value; }
+ }
+
+ ///
+ /// Gets the count of cancelled orders
+ ///
+ public int CancelOrderCallCount
+ {
+ get { return _cancelOrderCallCount; }
+ private set { _cancelOrderCallCount = value; }
+ }
+
+ ///
+ /// Constructor for MockNT8OrderAdapter
+ ///
+ public MockNT8OrderAdapter()
+ {
+ _callbacks = new List>();
+ _lock = new object();
+ }
+
+ ///
+ /// Submit order to NinjaTrader 8 (mock implementation)
+ ///
+ public async Task SubmitOrderAsync(OrderRequest request)
+ {
+ SubmitOrderCallCount++;
+
+ if (ShouldFail)
+ {
+ return false;
+ }
+
+ // Simulate successful submission
+ return ShouldSucceed;
+ }
+
+ ///
+ /// Modify existing order in NinjaTrader 8 (mock implementation)
+ ///
+ public async Task ModifyOrderAsync(OrderModification modification)
+ {
+ ModifyOrderCallCount++;
+
+ if (ShouldFail)
+ {
+ return false;
+ }
+
+ // Simulate successful modification
+ return ShouldSucceed;
+ }
+
+ ///
+ /// Cancel order in NinjaTrader 8 (mock implementation)
+ ///
+ public async Task CancelOrderAsync(OrderCancellation cancellation)
+ {
+ CancelOrderCallCount++;
+
+ if (ShouldFail)
+ {
+ return false;
+ }
+
+ // Simulate successful cancellation
+ return ShouldSucceed;
+ }
+
+ ///
+ /// Register callback for order status updates (mock implementation)
+ ///
+ public void RegisterOrderCallback(Action callback)
+ {
+ if (callback == null)
+ throw new ArgumentNullException("callback");
+
+ lock (_lock)
+ {
+ _callbacks.Add(callback);
+ }
+ }
+
+ ///
+ /// Unregister callback for order status updates (mock implementation)
+ ///
+ public void UnregisterOrderCallback(Action callback)
+ {
+ if (callback == null)
+ throw new ArgumentNullException("callback");
+
+ lock (_lock)
+ {
+ _callbacks.Remove(callback);
+ }
+ }
+
+ ///
+ /// Connect to NinjaTrader 8 (mock implementation)
+ ///
+ public async Task ConnectAsync()
+ {
+ if (ShouldFail)
+ {
+ return false;
+ }
+
+ _isConnected = true;
+ return ShouldSucceed;
+ }
+
+ ///
+ /// Disconnect from NinjaTrader 8 (mock implementation)
+ ///
+ public async Task DisconnectAsync()
+ {
+ _isConnected = false;
+ return true;
+ }
+
+ ///
+ /// Fire an order status update to all registered callbacks
+ ///
+ /// The order status to fire
+ public void FireOrderUpdate(OrderStatus status)
+ {
+ lock (_lock)
+ {
+ foreach (var callback in _callbacks)
+ {
+ try
+ {
+ callback(status);
+ }
+ catch
+ {
+ // Ignore exceptions in callbacks for this mock
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets whether the adapter is currently connected
+ ///
+ public bool IsConnected
+ {
+ get
+ {
+ return _isConnected;
+ }
+ }
+
+ ///
+ /// Dispose resources
+ ///
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ }
+ }
+ }
+}
diff --git a/tests/NT8.Core.Tests/OMS/BasicOrderManagerTests.cs b/tests/NT8.Core.Tests/OMS/BasicOrderManagerTests.cs
new file mode 100644
index 0000000..3cbea76
--- /dev/null
+++ b/tests/NT8.Core.Tests/OMS/BasicOrderManagerTests.cs
@@ -0,0 +1,509 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NT8.Core.OMS;
+using NT8.Core.Tests.Mocks;
+
+namespace NT8.Core.Tests.OMS
+{
+ [TestClass]
+ public class BasicOrderManagerTests
+ {
+ private MockLogger _mockLogger;
+ private MockNT8OrderAdapter _mockAdapter;
+ private BasicOrderManager _orderManager;
+
+ [TestInitialize]
+ public void Setup()
+ {
+ _mockLogger = new MockLogger();
+ _mockAdapter = new MockNT8OrderAdapter();
+ _orderManager = new BasicOrderManager(_mockLogger, _mockAdapter);
+ }
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ if (_orderManager != null)
+ {
+ _orderManager.Dispose();
+ }
+ }
+
+ [TestMethod]
+ public async Task SubmitOrderAsync_ValidRequest_ReturnsSuccessResult()
+ {
+ // Arrange
+ var request = new OrderRequest
+ {
+ Symbol = "ES",
+ Side = OrderSide.Buy,
+ Type = OrderType.Market,
+ Quantity = 1,
+ ClientOrderId = "TEST123"
+ };
+
+ // Act
+ var result = await _orderManager.SubmitOrderAsync(request);
+
+ // Assert
+ Assert.IsTrue(result.Success);
+ Assert.IsNotNull(result.OrderId);
+ Assert.AreEqual(request, result.Request);
+ Assert.AreEqual("Order submitted successfully", result.Message);
+ }
+
+ [TestMethod]
+ public async Task SubmitOrderAsync_NullRequest_ThrowsArgumentNullException()
+ {
+ // Arrange
+ OrderRequest request = null;
+
+ // Act & Assert
+ await Assert.ThrowsExceptionAsync(
+ () => _orderManager.SubmitOrderAsync(request));
+ }
+
+ [TestMethod]
+ public async Task SubmitOrderAsync_InvalidRequest_ReturnsFailureResult()
+ {
+ // Arrange
+ var request = new OrderRequest
+ {
+ Symbol = "", // Invalid symbol
+ Side = OrderSide.Buy,
+ Type = OrderType.Market,
+ Quantity = 1
+ };
+
+ // Act
+ var result = await _orderManager.SubmitOrderAsync(request);
+
+ // Assert
+ Assert.IsFalse(result.Success);
+ Assert.IsNull(result.OrderId);
+ }
+
+ [TestMethod]
+ public async Task SubmitOrderAsync_Nt8SubmissionFails_ReturnsFailureResult()
+ {
+ // Arrange
+ _mockAdapter.ShouldSucceed = false;
+ _mockAdapter.ShouldFail = true;
+
+ var request = new OrderRequest
+ {
+ Symbol = "ES",
+ Side = OrderSide.Buy,
+ Type = OrderType.Market,
+ Quantity = 1,
+ ClientOrderId = "TEST123"
+ };
+
+ // Act
+ var result = await _orderManager.SubmitOrderAsync(request);
+
+ // Assert
+ Assert.IsFalse(result.Success);
+ Assert.IsNotNull(result.OrderId); // Order ID is generated before NT8 submission
+ Assert.AreEqual("Order submission failed at NT8 level", result.Message);
+ }
+
+ [TestMethod]
+ public async Task ModifyOrderAsync_ValidRequest_ReturnsTrue()
+ {
+ // Arrange
+ var request = new OrderRequest
+ {
+ Symbol = "ES",
+ Side = OrderSide.Buy,
+ Type = OrderType.Limit,
+ Quantity = 1,
+ LimitPrice = 4000m,
+ ClientOrderId = "TEST123"
+ };
+
+ var submitResult = await _orderManager.SubmitOrderAsync(request);
+ var modification = new OrderModification(submitResult.OrderId)
+ {
+ NewQuantity = 2
+ };
+
+ // Act
+ var result = await _orderManager.ModifyOrderAsync(modification);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ public async Task ModifyOrderAsync_NullRequest_ThrowsArgumentNullException()
+ {
+ // Arrange
+ OrderModification modification = null;
+
+ // Act & Assert
+ await Assert.ThrowsExceptionAsync(
+ () => _orderManager.ModifyOrderAsync(modification));
+ }
+
+ [TestMethod]
+ public async Task CancelOrderAsync_ValidRequest_ReturnsTrue()
+ {
+ // Arrange
+ var request = new OrderRequest
+ {
+ Symbol = "ES",
+ Side = OrderSide.Buy,
+ Type = OrderType.Market,
+ Quantity = 1,
+ ClientOrderId = "TEST123"
+ };
+
+ var submitResult = await _orderManager.SubmitOrderAsync(request);
+ var cancellation = new OrderCancellation(submitResult.OrderId, "Test cancellation");
+
+ // Act
+ var result = await _orderManager.CancelOrderAsync(cancellation);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ public async Task CancelOrderAsync_NullRequest_ThrowsArgumentNullException()
+ {
+ // Arrange
+ OrderCancellation cancellation = null;
+
+ // Act & Assert
+ await Assert.ThrowsExceptionAsync(
+ () => _orderManager.CancelOrderAsync(cancellation));
+ }
+
+ [TestMethod]
+ public async Task GetOrderStatusAsync_ExistingOrder_ReturnsOrderStatus()
+ {
+ // Arrange
+ var request = new OrderRequest
+ {
+ Symbol = "ES",
+ Side = OrderSide.Buy,
+ Type = OrderType.Market,
+ Quantity = 1,
+ ClientOrderId = "TEST123"
+ };
+
+ var submitResult = await _orderManager.SubmitOrderAsync(request);
+
+ // Act
+ var status = await _orderManager.GetOrderStatusAsync(submitResult.OrderId);
+
+ // Assert
+ Assert.IsNotNull(status);
+ Assert.AreEqual(submitResult.OrderId, status.OrderId);
+ Assert.AreEqual("ES", status.Symbol);
+ }
+
+ [TestMethod]
+ public async Task GetOrderStatusAsync_NonExistentOrder_ReturnsNull()
+ {
+ // Act
+ var status = await _orderManager.GetOrderStatusAsync("NONEXISTENT");
+
+ // Assert
+ Assert.IsNull(status);
+ }
+
+ [TestMethod]
+ public async Task GetOrderStatusAsync_NullOrderId_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ await Assert.ThrowsExceptionAsync(
+ () => _orderManager.GetOrderStatusAsync(null));
+ }
+
+ [TestMethod]
+ public async Task GetActiveOrdersAsync_HasActiveOrders_ReturnsList()
+ {
+ // Arrange
+ var request1 = new OrderRequest
+ {
+ Symbol = "ES",
+ Side = OrderSide.Buy,
+ Type = OrderType.Market,
+ Quantity = 1,
+ ClientOrderId = "TEST123"
+ };
+
+ var request2 = new OrderRequest
+ {
+ Symbol = "NQ",
+ Side = OrderSide.Sell,
+ Type = OrderType.Market,
+ Quantity = 2,
+ ClientOrderId = "TEST124"
+ };
+
+ await _orderManager.SubmitOrderAsync(request1);
+ await _orderManager.SubmitOrderAsync(request2);
+
+ // Act
+ var activeOrders = await _orderManager.GetActiveOrdersAsync();
+
+ // Assert
+ Assert.IsNotNull(activeOrders);
+ Assert.IsTrue(activeOrders.Count >= 2);
+ }
+
+ [TestMethod]
+ public async Task GetOrdersBySymbolAsync_ValidSymbol_ReturnsFilteredOrders()
+ {
+ // Arrange
+ var request1 = new OrderRequest
+ {
+ Symbol = "ES",
+ Side = OrderSide.Buy,
+ Type = OrderType.Market,
+ Quantity = 1,
+ ClientOrderId = "TEST123"
+ };
+
+ var request2 = new OrderRequest
+ {
+ Symbol = "NQ",
+ Side = OrderSide.Sell,
+ Type = OrderType.Market,
+ Quantity = 2,
+ ClientOrderId = "TEST124"
+ };
+
+ await _orderManager.SubmitOrderAsync(request1);
+ await _orderManager.SubmitOrderAsync(request2);
+
+ // Act
+ var esOrders = await _orderManager.GetOrdersBySymbolAsync("ES");
+
+ // Assert
+ Assert.IsNotNull(esOrders);
+ foreach (var order in esOrders)
+ {
+ Assert.AreEqual("ES", order.Symbol, true); // Case insensitive comparison
+ }
+ }
+
+ [TestMethod]
+ public async Task GetOrdersBySymbolAsync_NullSymbol_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ await Assert.ThrowsExceptionAsync(
+ () => _orderManager.GetOrdersBySymbolAsync(null));
+ }
+
+ [TestMethod]
+ public async Task FlattenSymbolAsync_ValidSymbol_CancelsOrders()
+ {
+ // Arrange
+ var request1 = new OrderRequest
+ {
+ Symbol = "ES",
+ Side = OrderSide.Buy,
+ Type = OrderType.Market,
+ Quantity = 1,
+ ClientOrderId = "TEST123"
+ };
+
+ var request2 = new OrderRequest
+ {
+ Symbol = "ES", // Same symbol
+ Side = OrderSide.Sell,
+ Type = OrderType.Market,
+ Quantity = 2,
+ ClientOrderId = "TEST124"
+ };
+
+ await _orderManager.SubmitOrderAsync(request1);
+ await _orderManager.SubmitOrderAsync(request2);
+
+ // Act
+ var result = await _orderManager.FlattenSymbolAsync("ES");
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ public async Task FlattenAllAsync_CancelsAllOrders()
+ {
+ // Arrange
+ var request1 = new OrderRequest
+ {
+ Symbol = "ES",
+ Side = OrderSide.Buy,
+ Type = OrderType.Market,
+ Quantity = 1,
+ ClientOrderId = "TEST123"
+ };
+
+ var request2 = new OrderRequest
+ {
+ Symbol = "NQ",
+ Side = OrderSide.Sell,
+ Type = OrderType.Market,
+ Quantity = 2,
+ ClientOrderId = "TEST124"
+ };
+
+ await _orderManager.SubmitOrderAsync(request1);
+ await _orderManager.SubmitOrderAsync(request2);
+
+ // Act
+ var result = await _orderManager.FlattenAllAsync();
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ public void SubscribeAndUnsubscribeToOrderUpdates_WorksCorrectly()
+ {
+ // Arrange - First create an order so the manager knows about it
+ var request = new OrderRequest
+ {
+ Symbol = "ES",
+ Side = OrderSide.Buy,
+ Type = OrderType.Market,
+ Quantity = 1,
+ ClientOrderId = "TEST_CLIENT_ORDER"
+ };
+
+ var submitResult = _orderManager.SubmitOrderAsync(request).Result;
+ Assert.IsTrue(submitResult.Success);
+ string orderId = submitResult.OrderId;
+ Assert.IsNotNull(orderId);
+
+ bool callbackCalled = false;
+ Action callback = delegate(OrderStatus statusParam) { callbackCalled = true; };
+
+ // Act - subscribe
+ _orderManager.SubscribeToOrderUpdates(callback);
+
+ // Simulate an order update via the mock adapter for the known order
+ var statusUpdate = new OrderStatus
+ {
+ OrderId = orderId, // Use the actual order ID from the created order
+ Symbol = "ES",
+ State = OrderState.Filled
+ };
+ _mockAdapter.FireOrderUpdate(statusUpdate);
+
+ // Assert that callback was called
+ Assert.IsTrue(callbackCalled, "Callback should have been called after subscription and order update");
+
+ // Reset flag
+ callbackCalled = false;
+
+ // Act - unsubscribe
+ _orderManager.UnsubscribeFromOrderUpdates(callback);
+
+ // Simulate another order update for the same order
+ var statusUpdate2 = new OrderStatus
+ {
+ OrderId = orderId, // Use the same order ID
+ Symbol = "ES",
+ State = OrderState.Cancelled
+ };
+ _mockAdapter.FireOrderUpdate(statusUpdate2);
+
+ // Assert that callback was NOT called after unsubscribe
+ Assert.IsFalse(callbackCalled, "Callback should NOT have been called after unsubscription");
+ }
+
+ [TestMethod]
+ public void SubscribeToOrderUpdates_NullCallback_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ Assert.ThrowsException(
+ () => _orderManager.SubscribeToOrderUpdates(null));
+ }
+
+ [TestMethod]
+ public void UnsubscribeFromOrderUpdates_NullCallback_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ Assert.ThrowsException(
+ () => _orderManager.UnsubscribeFromOrderUpdates(null));
+ }
+
+ [TestMethod]
+ public async Task OrderStateTransition_ValidTransitions_AreAllowed()
+ {
+ // Arrange - create an order and submit it
+ var request = new OrderRequest
+ {
+ Symbol = "ES",
+ Side = OrderSide.Buy,
+ Type = OrderType.Market,
+ Quantity = 1,
+ ClientOrderId = "TEST123"
+ };
+
+ var submitResult = await _orderManager.SubmitOrderAsync(request);
+ Assert.IsNotNull(submitResult.OrderId);
+
+ // Act - simulate state updates through the mock adapter
+ var pendingStatus = new OrderStatus
+ {
+ OrderId = submitResult.OrderId,
+ Symbol = "ES",
+ State = OrderState.Pending
+ };
+
+ var submittedStatus = new OrderStatus
+ {
+ OrderId = submitResult.OrderId,
+ Symbol = "ES",
+ State = OrderState.Submitted
+ };
+
+ var acceptedStatus = new OrderStatus
+ {
+ OrderId = submitResult.OrderId,
+ Symbol = "ES",
+ State = OrderState.Accepted
+ };
+
+ var workingStatus = new OrderStatus
+ {
+ OrderId = submitResult.OrderId,
+ Symbol = "ES",
+ State = OrderState.Working
+ };
+
+ // Simulate the state transitions
+ _mockAdapter.FireOrderUpdate(pendingStatus);
+ _mockAdapter.FireOrderUpdate(submittedStatus);
+ _mockAdapter.FireOrderUpdate(acceptedStatus);
+ _mockAdapter.FireOrderUpdate(workingStatus);
+
+ // Assert - get the final status and verify it's in working state
+ var finalStatus = await _orderManager.GetOrderStatusAsync(submitResult.OrderId);
+ Assert.AreEqual(OrderState.Working, finalStatus.State);
+ }
+
+ [TestMethod]
+ public async Task Dispose_DisposesResources()
+ {
+ // Arrange
+ var orderManager = new BasicOrderManager(_mockLogger, _mockAdapter);
+
+ // Act
+ orderManager.Dispose();
+
+ // Assert - no exception should be thrown
+ // Additional assertions could check if resources were properly cleaned up
+ }
+ }
+}