Files
nt8-sdk/docs/architecture/order_manager_implementation.md
Billy Valentine 92f3732b3d
Some checks failed
Build and Test / build (push) Has been cancelled
Phase 0 completion: NT8 SDK core framework with risk management and position sizing
2025-09-09 17:06:37 -04:00

22 KiB

OrderManager Class Implementation Design

Overview

The OrderManager class is the primary implementation of the IOrderManager interface, providing core order management functionality, integration with risk management, and support for algorithmic execution strategies.

Class Structure

using NT8.Core.Common.Models;
using NT8.Core.Risk;
using NT8.Core.Sizing;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace NT8.Core.Orders
{
    /// <summary>
    /// Order manager implementation with smart routing and algorithmic execution
    /// </summary>
    public class OrderManager : IOrderManager
    {
        private readonly IRiskManager _riskManager;
        private readonly IPositionSizer _positionSizer;
        private readonly ILogger<OrderManager> _logger;
        private readonly object _lock = new object();
        
        // Configuration
        private RoutingConfig _routingConfig;
        private AlgorithmParameters _algorithmParameters;
        
        // State
        private readonly Dictionary<string, OrderStatus> _orders;
        private readonly Dictionary<string, ExecutionVenue> _venues;
        private readonly RoutingMetrics _routingMetrics;
        private readonly OmsMetrics _omsMetrics;
        
        public OrderManager(
            IRiskManager riskManager,
            IPositionSizer positionSizer,
            ILogger<OrderManager> logger)
        {
            _riskManager = riskManager ?? throw new ArgumentNullException(nameof(riskManager));
            _positionSizer = positionSizer ?? throw new ArgumentNullException(nameof(positionSizer));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            
            _orders = new Dictionary<string, OrderStatus>();
            _venues = new Dictionary<string, ExecutionVenue>();
            _routingMetrics = new RoutingMetrics(new Dictionary<string, VenueMetrics>(), 0, 0.0, DateTime.UtcNow);
            _omsMetrics = new OmsMetrics(0, 0, 0.0, 0.0, 0.0, 0, DateTime.UtcNow);
            
            InitializeDefaultConfig();
            InitializeVenues();
        }
        
        private void InitializeDefaultConfig()
        {
            _routingConfig = new RoutingConfig(
                SmartRoutingEnabled: true,
                DefaultVenue: "Primary",
                VenuePreferences: new Dictionary<string, double> { ["Primary"] = 1.0, ["Secondary"] = 0.8 },
                MaxSlippagePercent: 0.5,
                MaxRoutingTime: TimeSpan.FromSeconds(30),
                RouteByCost: true,
                RouteBySpeed: true,
                RouteByReliability: true
            );
            
            _algorithmParameters = new AlgorithmParameters(
                new TwapConfig(TimeSpan.FromMinutes(15), 30, true),
                new VwapConfig(0.1, true),
                new IcebergConfig(0.1, true)
            );
        }
        
        private void InitializeVenues()
        {
            _venues.Add("Primary", new ExecutionVenue(
                "Primary", "Primary execution venue", true, 1.0, 1.0, 0.99));
                
            _venues.Add("Secondary", new ExecutionVenue(
                "Secondary", "Backup execution venue", true, 1.2, 0.9, 0.95));
        }
    }
}

Core Order Management Implementation

Order Submission

public async Task<OrderResult> SubmitOrderAsync(OrderRequest request, StrategyContext context)
{
    if (request == null) throw new ArgumentNullException(nameof(request));
    if (context == null) throw new ArgumentNullException(nameof(context));
    
    try
    {
        _logger.LogInformation("Submitting order for {Symbol} {Side} {Quantity}", 
            request.Symbol, request.Side, request.Quantity);
        
        // 1. Validate order through risk management
        var riskDecision = await ValidateOrderAsync(request, context);
        if (!riskDecision.Allow)
        {
            _logger.LogWarning("Order rejected by risk management: {Reason}", riskDecision.RejectReason);
            return new OrderResult(false, null, riskDecision.RejectReason, null);
        }
        
        // 2. Route order based on smart routing logic
        var routingResult = await RouteOrderAsync(request, context);
        if (!routingResult.Success)
        {
            _logger.LogError("Order routing failed: {Message}", routingResult.Message);
            return new OrderResult(false, null, routingResult.Message, null);
        }
        
        // 3. Submit to selected venue (simulated)
        var orderId = Guid.NewGuid().ToString();
        var orderStatus = new OrderStatus(
            orderId, request.Symbol, request.Side, request.Type, request.Quantity, 0,
            request.LimitPrice, request.StopPrice, OrderState.Submitted, DateTime.UtcNow, null, 
            new List<OrderFill>());
            
        lock (_lock)
        {
            _orders[orderId] = orderStatus;
            UpdateOmsMetrics();
        }
        
        _logger.LogInformation("Order {OrderId} submitted to {Venue}", orderId, routingResult.SelectedVenue.Name);
        
        return new OrderResult(true, orderId, "Order submitted successfully", orderStatus);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error submitting order for {Symbol}", request.Symbol);
        return new OrderResult(false, null, $"Error submitting order: {ex.Message}", null);
    }
}

Order Cancellation

public async Task<bool> CancelOrderAsync(string orderId)
{
    if (string.IsNullOrEmpty(orderId)) throw new ArgumentException("Order ID required", nameof(orderId));
    
    try
    {
        lock (_lock)
        {
            if (!_orders.ContainsKey(orderId))
            {
                _logger.LogWarning("Cannot cancel order {OrderId} - not found", orderId);
                return false;
            }
            
            var order = _orders[orderId];
            if (order.State == OrderState.Filled || order.State == OrderState.Cancelled)
            {
                _logger.LogWarning("Cannot cancel order {OrderId} - already {State}", orderId, order.State);
                return false;
            }
            
            // Update order state to cancelled
            var updatedOrder = order with { State = OrderState.Cancelled, FilledTime = DateTime.UtcNow };
            _orders[orderId] = updatedOrder;
            
            UpdateOmsMetrics();
        }
        
        _logger.LogInformation("Order {OrderId} cancelled successfully", orderId);
        return true;
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error cancelling order {OrderId}", orderId);
        return false;
    }
}

Risk Integration Implementation

public async Task<RiskDecision> ValidateOrderAsync(OrderRequest request, StrategyContext context)
{
    if (request == null) throw new ArgumentNullException(nameof(request));
    if (context == null) throw new ArgumentNullException(nameof(context));
    
    // Convert OrderRequest to StrategyIntent for risk validation
    var intent = new StrategyIntent(
        request.Symbol,
        request.Side,
        ConvertOrderType(request.Type),
        (double?)request.LimitPrice,
        GetStopTicks(request),
        null, // TargetTicks
        1.0, // Confidence
        "OMS Order Submission",
        new Dictionary<string, object>()
    );
    
    // Create a mock risk config for validation
    var riskConfig = new RiskConfig(1000, 200, 10, true);
    
    return _riskManager.ValidateOrder(intent, context, riskConfig);
}

private OrderType ConvertOrderType(NT8.Core.Orders.OrderType orderType)
{
    return orderType switch
    {
        NT8.Core.Orders.OrderType.Market => OrderType.Market,
        NT8.Core.Orders.OrderType.Limit => OrderType.Limit,
        NT8.Core.Orders.OrderType.StopMarket => OrderType.StopMarket,
        NT8.Core.Orders.OrderType.StopLimit => OrderType.StopLimit,
        _ => OrderType.Market
    };
}

private int GetStopTicks(OrderRequest request)
{
    // Simplified stop ticks calculation
    return 10;
}

Smart Order Routing Implementation

public async Task<RoutingResult> RouteOrderAsync(OrderRequest request, StrategyContext context)
{
    if (request == null) throw new ArgumentNullException(nameof(request));
    if (context == null) throw new ArgumentNullException(nameof(context));
    
    if (!_routingConfig.SmartRoutingEnabled)
    {
        var defaultVenue = _venues[_routingConfig.DefaultVenue];
        return new RoutingResult(true, null, defaultVenue, "Routing disabled, using default venue", 
            new Dictionary<string, object> { ["venue"] = defaultVenue.Name });
    }
    
    // Select best venue based on configuration
    var selectedVenue = SelectBestVenue(request, context);
    
    // Update routing metrics
    UpdateRoutingMetrics(selectedVenue);
    
    return new RoutingResult(true, null, selectedVenue, "Order routed successfully",
        new Dictionary<string, object> 
        { 
            ["venue"] = selectedVenue.Name, 
            ["cost_factor"] = selectedVenue.CostFactor,
            ["speed_factor"] = selectedVenue.SpeedFactor
        });
}

private ExecutionVenue SelectBestVenue(OrderRequest request, StrategyContext context)
{
    ExecutionVenue bestVenue = null;
    double bestScore = double.MinValue;
    
    foreach (var venue in _venues.Values)
    {
        if (!venue.IsActive) continue;
        
        double score = 0;
        
        // Factor in venue preferences
        if (_routingConfig.VenuePreferences.ContainsKey(venue.Name))
        {
            score += _routingConfig.VenuePreferences[venue.Name] * 100;
        }
        
        // Factor in cost if enabled
        if (_routingConfig.RouteByCost)
        {
            score -= venue.CostFactor * 50; // Lower cost is better
        }
        
        // Factor in speed if enabled
        if (_routingConfig.RouteBySpeed)
        {
            score += venue.SpeedFactor * 30; // Higher speed is better
        }
        
        // Factor in reliability
        if (_routingConfig.RouteByReliability)
        {
            score += venue.ReliabilityFactor * 20; // Higher reliability is better
        }
        
        if (score > bestScore)
        {
            bestScore = score;
            bestVenue = venue;
        }
    
    return bestVenue ?? _venues[_routingConfig.DefaultVenue];
}

private void UpdateRoutingMetrics(ExecutionVenue venue)
{
    lock (_lock)
    {
        var venueMetrics = _routingMetrics.VenuePerformance.ContainsKey(venue.Name) ? 
            _routingMetrics.VenuePerformance[venue.Name] : 
            new VenueMetrics(venue.Name, 0, 0.0, 0.0, 0.0, 0);
            
        var updatedMetrics = venueMetrics with
        {
            OrdersRouted = venueMetrics.OrdersRouted + 1
        };
        
        _routingMetrics.VenuePerformance[venue.Name] = updatedMetrics;
        _routingMetrics.TotalRoutedOrders++;
        _routingMetrics.LastUpdated = DateTime.UtcNow;
    }
}

Algorithmic Execution Support

TWAP Algorithm Integration

public async Task<OrderResult> ExecuteTwapAsync(TwapParameters parameters, StrategyContext context)
{
    if (parameters == null) throw new ArgumentNullException(nameof(parameters));
    if (context == null) throw new ArgumentNullException(nameof(context));
    
    _logger.LogInformation("Executing TWAP order for {Symbol} {Side} {Quantity} over {Duration}", 
        parameters.Symbol, parameters.Side, parameters.TotalQuantity, parameters.Duration);
    
    // Create a parent order for tracking
    var parentOrderId = Guid.NewGuid().ToString();
    
    // Calculate slice parameters
    var sliceCount = (int)(parameters.Duration.TotalSeconds / parameters.IntervalSeconds);
    var sliceQuantity = parameters.TotalQuantity / sliceCount;
    
    // Execute slices
    for (int i = 0; i < sliceCount; i++)
    {
        // Create slice order
        var sliceRequest = new OrderRequest(
            parameters.Symbol,
            parameters.Side,
            OrderType.Market, // Simplified to market orders
            sliceQuantity,
            parameters.LimitPrice,
            null, // StopPrice
            TimeInForce.Day,
            null, // No algorithm for slices
            new Dictionary<string, object>()
        );
        
        // Submit slice order
        var result = await SubmitOrderAsync(sliceRequest, context);
        
        if (!result.Success)
        {
            _logger.LogWarning("TWAP slice {Slice}/{Total} failed: {Message}", 
                i + 1, sliceCount, result.Message);
        }
        else
        {
            _logger.LogInformation("TWAP slice {Slice}/{Total} submitted: {OrderId}", 
                i + 1, sliceCount, result.OrderId);
        }
        
        // Wait for next interval (except for last slice)
        if (i < sliceCount - 1)
        {
            await Task.Delay(TimeSpan.FromSeconds(parameters.IntervalSeconds));
        }
    }
    
    return new OrderResult(true, parentOrderId, "TWAP execution completed", null);
}

VWAP Algorithm Integration

public async Task<OrderResult> ExecuteVwapAsync(VwapParameters parameters, StrategyContext context)
{
    if (parameters == null) throw new ArgumentNullException(nameof(parameters));
    if (context == null) throw new ArgumentNullException(nameof(context));
    
    _logger.LogInformation("Executing VWAP order for {Symbol} {Side} {Quantity} from {Start} to {End}", 
        parameters.Symbol, parameters.Side, parameters.TotalQuantity, parameters.StartTime, parameters.EndTime);
    
    // Create a parent order for tracking
    var parentOrderId = Guid.NewGuid().ToString();
    
    // Simplified VWAP implementation - in a real system, this would:
    // 1. Monitor market volume throughout the execution period
    // 2. Calculate participation rate based on target participation
    // 3. Execute orders in proportion to volume
    
    // For now, we'll execute the order as a single market order
    var request = new OrderRequest(
        parameters.Symbol,
        parameters.Side,
        OrderType.Market,
        parameters.TotalQuantity,
        parameters.LimitPrice,
        null, // StopPrice
        TimeInForce.Day,
        null, // No algorithm for this simplified version
        new Dictionary<string, object>()
    );
    
    var result = await SubmitOrderAsync(request, context);
    
    return new OrderResult(result.Success, parentOrderId, 
        result.Success ? "VWAP execution completed" : $"VWAP execution failed: {result.Message}", 
        result.Status);
}

Iceberg Algorithm Integration

public async Task<OrderResult> ExecuteIcebergAsync(IcebergParameters parameters, StrategyContext context)
{
    if (parameters == null) throw new ArgumentNullException(nameof(parameters));
    if (context == null) throw new ArgumentNullException(nameof(context));
    
    _logger.LogInformation("Executing Iceberg order for {Symbol} {Side} {TotalQuantity} (visible: {VisibleQuantity})", 
        parameters.Symbol, parameters.Side, parameters.TotalQuantity, parameters.VisibleQuantity);
    
    // Create a parent order for tracking
    var parentOrderId = Guid.NewGuid().ToString();
    
    var remainingQuantity = parameters.TotalQuantity;
    
    while (remainingQuantity > 0)
    {
        // Determine visible quantity for this slice
        var visibleQuantity = Math.Min(parameters.VisibleQuantity, remainingQuantity);
        
        // Create slice order
        var sliceRequest = new OrderRequest(
            parameters.Symbol,
            parameters.Side,
            parameters.LimitPrice.HasValue ? OrderType.Limit : OrderType.Market,
            visibleQuantity,
            parameters.LimitPrice,
            null, // StopPrice
            TimeInForce.Day,
            null, // No algorithm for slices
            new Dictionary<string, object>()
        );
        
        // Submit slice order
        var result = await SubmitOrderAsync(sliceRequest, context);
        
        if (!result.Success)
        {
            _logger.LogWarning("Iceberg slice failed with {Remaining} qty remaining: {Message}", 
                remainingQuantity, result.Message);
            break;
        }
        
        // Update remaining quantity
        remainingQuantity -= visibleQuantity;
        
        _logger.LogInformation("Iceberg slice submitted, {Remaining} qty remaining", remainingQuantity);
        
        // In a real implementation, we would wait for the order to fill
        // before submitting the next slice. For this design, we'll add a delay.
        if (remainingQuantity > 0)
        {
            await Task.Delay(TimeSpan.FromSeconds(5)); // Simulate time between slices
        }
    
    return new OrderResult(true, parentOrderId, "Iceberg execution completed", null);
}

Configuration Management

public void UpdateRoutingConfig(RoutingConfig config)
{
    if (config == null) throw new ArgumentNullException(nameof(config));
    
    lock (_lock)
    {
        _routingConfig = config;
    }
    
    _logger.LogInformation("Routing configuration updated");
}

public RoutingConfig GetRoutingConfig()
{
    lock (_lock)
    {
        return _routingConfig;
    }
}

public void UpdateAlgorithmParameters(AlgorithmParameters parameters)
{
    if (parameters == null) throw new ArgumentNullException(nameof(parameters));
    
    lock (_lock)
    {
        _algorithmParameters = parameters;
    }
    
    _logger.LogInformation("Algorithm parameters updated");
}

public AlgorithmParameters GetAlgorithmParameters()
{
    lock (_lock)
    {
        return _algorithmParameters;
    }
}

Metrics and Monitoring

private void UpdateOmsMetrics()
{
    lock (_lock)
    {
        var activeOrders = 0;
        var filledOrders = 0;
        var failedOrders = 0;
        var totalQuantity = 0;
        
        foreach (var order in _orders.Values)
        {
            switch (order.State)
            {
                case OrderState.Submitted:
                case OrderState.Accepted:
                case OrderState.PartiallyFilled:
                    activeOrders++;
                    break;
                case OrderState.Filled:
                    filledOrders++;
                    break;
                case OrderState.Rejected:
                case OrderState.Expired:
                case OrderState.Cancelled:
                    failedOrders++;
                    break;
            }
            
            totalQuantity += order.Quantity;
        }
        
        var totalOrders = _orders.Count;
        var fillRate = totalOrders > 0 ? (double)filledOrders / totalOrders : 0.0;
        
        _omsMetrics = _omsMetrics with
        {
            TotalOrders = totalOrders,
            ActiveOrders = activeOrders,
            FailedOrders = failedOrders,
            FillRate = fillRate,
            TotalValueTraded = totalQuantity, // Simplified
            LastUpdated = DateTime.UtcNow
        };
    }
}

public OmsMetrics GetMetrics()
{
    lock (_lock)
    {
        return _omsMetrics;
    }
}

public RoutingMetrics GetRoutingMetrics()
{
    lock (_lock)
    {
        return _routingMetrics;
    }
}

public bool IsHealthy()
{
    // Simple health check - in a real implementation, this would check:
    // - Connection to execution venues
    // - Risk management system availability
    // - Position sizing system availability
    // - Internal state consistency
    
    return true;
}

Order Status Management

public async Task<OrderStatus> GetOrderStatusAsync(string orderId)
{
    if (string.IsNullOrEmpty(orderId)) throw new ArgumentException("Order ID required", nameof(orderId));
    
    lock (_lock)
    {
        return _orders.ContainsKey(orderId) ? _orders[orderId] : null;
    }
}

public async Task<List<OrderStatus>> GetOrdersBySymbolAsync(string symbol)
{
    if (string.IsNullOrEmpty(symbol)) throw new ArgumentException("Symbol required", nameof(symbol));
    
    var result = new List<OrderStatus>();
    
    lock (_lock)
    {
        foreach (var order in _orders.Values)
        {
            if (order.Symbol.Equals(symbol, StringComparison.OrdinalIgnoreCase))
            {
                result.Add(order);
            }
        }
    }
    
    return result;
}

public async Task<List<OrderStatus>> GetActiveOrdersAsync()
{
    var result = new List<OrderStatus>();
    
    lock (_lock)
    {
        foreach (var order in _orders.Values)
        {
            if (order.State == OrderState.Submitted || 
                order.State == OrderState.Accepted || 
                order.State == OrderState.PartiallyFilled)
            {
                result.Add(order);
            }
        }
    }
    
    return result;
}

Error Handling and Validation

The OrderManager implements comprehensive error handling:

  1. Input Validation: All public methods validate their parameters
  2. Exception Handling: Try-catch blocks around critical operations
  3. Logging: Detailed logging of all operations and errors
  4. Graceful Degradation: When possible, the system continues operating even when some components fail

Thread Safety

The OrderManager uses a lock-based approach to ensure thread safety:

  1. State Protection: All shared state is protected by a single lock
  2. Atomic Operations: Complex state updates are performed atomically
  3. Immutable Data: Where possible, immutable data structures are used

Integration Points

Risk Management

  • All orders pass through the IRiskManager.ValidateOrder method
  • Risk decisions are respected before order submission

Position Sizing

  • Future enhancement could integrate with IPositionSizer for dynamic quantity adjustments

Execution Venues

  • Orders are routed to configured execution venues
  • Routing decisions are based on configurable criteria

Testing Considerations

The OrderManager is designed to be testable:

  1. Dependency Injection: All dependencies are injected through the constructor
  2. Interface-Based: Depends on interfaces rather than concrete implementations
  3. State Access: Provides methods to access internal state for verification
  4. Configuration: All behavior can be controlled through configuration

Performance Considerations

  1. Lock Contention: The single lock could become a bottleneck under high load
  2. Memory Usage: Order state is maintained in memory
  3. Latency: Order routing adds minimal latency
  4. Scalability: Design supports horizontal scaling through instance isolation