Files
nt8-sdk/docs/architecture/smart_order_routing_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

Smart Order Routing Implementation Design

Overview

This document details the implementation of smart order routing logic in the Order Management System (OMS), which determines the optimal execution venue for orders based on cost, speed, reliability, and other configurable factors.

Routing Architecture

The smart order routing system consists of several components:

  1. Execution Venues: Different venues where orders can be executed
  2. Routing Algorithm: Logic that selects the best venue based on configurable criteria
  3. Performance Metrics: Tracking of venue performance for continuous optimization
  4. Configuration System: Parameters that control routing decisions

Execution Venues

Venue Definition

/// <summary>
/// Execution venue information
/// </summary>
public record ExecutionVenue(
    string Name,
    string Description,
    bool IsActive,
    double CostFactor, // Relative cost (1.0 = baseline)
    double SpeedFactor, // Relative speed (1.0 = baseline)
    double ReliabilityFactor // Reliability score (0.0 to 1.0)
)
{
    /// <summary>
    /// Calculates a composite score for this venue based on routing criteria
    /// </summary>
    public double CalculateScore(RoutingConfig config, OrderRequest order)
    {
        double score = 0;
        
        // Factor in venue preferences
        if (config.VenuePreferences.ContainsKey(Name))
        {
            score += config.VenuePreferences[Name] * 100;
        }
        
        // Factor in cost if enabled
        if (config.RouteByCost)
        {
            score -= CostFactor * 50; // Lower cost is better
        }
        
        // Factor in speed if enabled
        if (config.RouteBySpeed)
        {
            score += SpeedFactor * 30; // Higher speed is better
        }
        
        // Factor in reliability
        if (config.RouteByReliability)
        {
            score += ReliabilityFactor * 20; // Higher reliability is better
        }
        
        return score;
    }
}

Default Venues

private void InitializeVenues()
{
    // Primary venue - typically the main broker or exchange
    _venues.Add("Primary", new ExecutionVenue(
        "Primary", 
        "Primary execution venue", 
        true, 
        1.0, // Baseline cost
        1.0,  // Baseline speed
        0.99  // High reliability
    ));
    
    // Secondary venue - backup or alternative execution path
    _venues.Add("Secondary", new ExecutionVenue(
        "Secondary", 
        "Backup execution venue", 
        true, 
        1.2,  // 20% higher cost
        0.9,  // 10% slower
        0.95  // Good reliability
    ));
    
    // Dark pool venue - for large orders to minimize market impact
    _venues.Add("DarkPool", new ExecutionVenue(
        "DarkPool", 
        "Dark pool execution venue", 
        true, 
        1.5, // 50% higher cost
        0.7,  // 30% slower
        0.90  // Moderate reliability
    ));
}

Routing Configuration

Routing Configuration Model

/// <summary>
/// Routing configuration parameters
/// </summary>
public record RoutingConfig(
    bool SmartRoutingEnabled,
    string DefaultVenue,
    Dictionary<string, double> VenuePreferences,
    double MaxSlippagePercent,
    TimeSpan MaxRoutingTime,
    bool RouteByCost,
    bool RouteBySpeed,
    bool RouteByReliability
)
{
    public static RoutingConfig Default => new RoutingConfig(
        SmartRoutingEnabled: true,
        DefaultVenue: "Primary",
        VenuePreferences: new Dictionary<string, double> 
        { 
            ["Primary"] = 1.0, 
            ["Secondary"] = 0.8, 
            ["DarkPool"] = 0.6 
        },
        MaxSlippagePercent: 0.5,
        MaxRoutingTime: TimeSpan.FromSeconds(30),
        RouteByCost: true,
        RouteBySpeed: true,
        RouteByReliability: true
    );
}

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

Routing Algorithm Implementation

Main Routing Logic

public async Task<RoutingResult> RouteOrderAsync(OrderRequest request, StrategyContext context)
{
    if (request == null) throw new ArgumentNullException(nameof(request));
    
    try
    {
        _logger.LogInformation("Routing order for {Symbol} {Side} {Quantity}", 
            request.Symbol, request.Side, request.Quantity);
        
        // Check if smart routing is enabled
        if (!_routingConfig.SmartRoutingEnabled)
        {
            var defaultVenue = GetVenue(_routingConfig.DefaultVenue);
            if (defaultVenue == null)
            {
                return new RoutingResult(false, null, null, "Default venue not found", 
                    new Dictionary<string, object> { ["error"] = "Default venue not found" });
            }
            
            _logger.LogInformation("Smart routing disabled, using default venue: {Venue}", defaultVenue.Name);
            return new RoutingResult(true, null, defaultVenue, "Routing disabled, using default venue", 
                new Dictionary<string, object> { ["venue"] = defaultVenue.Name });
        }
        
        // Select best venue based on smart routing logic
        var selectedVenue = SelectBestVenue(request, context);
        
        if (selectedVenue == null)
        {
            return new RoutingResult(false, null, "No suitable venue found", 
                new Dictionary<string, object> { ["error"] = "No suitable venue found" });
        }
        
        // Validate venue is active
        if (!selectedVenue.IsActive)
        {
            return new RoutingResult(false, null, null, $"Venue {selectedVenue.Name} is not active", 
                new Dictionary<string, object> { ["error"] = "Venue inactive" });
        }
        
        // Update routing metrics
        UpdateRoutingMetrics(selectedVenue);
        
        _logger.LogInformation("Order routed to venue: {Venue} (Cost: {Cost}, Speed: {Speed}, Reliability: {Reliability})", 
            selectedVenue.Name, selectedVenue.CostFactor, selectedVenue.SpeedFactor, selectedVenue.ReliabilityFactor);
        
        return new RoutingResult(true, null, selectedVenue, "Order routed successfully",
            new Dictionary<string, object> 
            { 
                ["venue"] = selectedVenue.Name, 
                ["cost_factor"] = selectedVenue.CostFactor,
                ["speed_factor"] = selectedVenue.SpeedFactor,
                ["reliability_factor"] = selectedVenue.ReliabilityFactor
            });
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error routing order for {Symbol}", request.Symbol);
        return new RoutingResult(false, null, null, $"Error routing order: {ex.Message}", 
            new Dictionary<string, object> { ["error"] = ex.Message });
    }
}

Venue Selection Logic

private ExecutionVenue SelectBestVenue(OrderRequest request, StrategyContext context)
{
    ExecutionVenue bestVenue = null;
    double bestScore = double.MinValue;
    
    // Special handling for large orders that might benefit from dark pools
    bool isLargeOrder = request.Quantity > GetLargeOrderThreshold(request.Symbol);
    
    foreach (var venue in _venues.Values)
    {
        // Skip inactive venues
        if (!venue.IsActive) continue;
        
        // Skip dark pools for small orders unless specifically requested
        if (venue.Name == "DarkPool" && !isLargeOrder && request.Algorithm != "Iceberg")
        {
            continue;
        }
        
        // Calculate venue score
        double score = venue.CalculateScore(_routingConfig, request);
        
        // Adjust score based on order characteristics
        score = AdjustScoreForOrderCharacteristics(score, venue, request, context);
        
        if (score > bestScore)
        {
            bestScore = score;
            bestVenue = venue;
        }
    }
    
    return bestVenue ?? GetVenue(_routingConfig.DefaultVenue);
}

private double AdjustScoreForOrderCharacteristics(double score, ExecutionVenue venue, OrderRequest request, StrategyContext context)
{
    // Adjust for order size
    if (request.Quantity > GetLargeOrderThreshold(request.Symbol))
    {
        // Prefer dark pools for large orders
        if (venue.Name == "DarkPool")
        {
            score += 20; // Bonus for dark pool handling of large orders
        }
        else
        {
            score -= 10; // Penalty for regular venues handling large orders
        }
    }
    
    // Adjust for algorithmic orders
    if (!string.IsNullOrEmpty(request.Algorithm))
    {
        // Some venues may be better optimized for algorithmic orders
        if (venue.Name == "Primary")
        {
            score += 5; // Small bonus for primary venue handling algorithms
        }
    
    // Adjust for symbol-specific venue preferences
    var symbolVenuePref = GetSymbolVenuePreference(request.Symbol, venue.Name);
    if (symbolVenuePref.HasValue)
    {
        score += symbolVenuePref.Value * 10;
    }
    
    return score;
}

private int GetLargeOrderThreshold(string symbol)
{
    // Different thresholds for different symbols
    return symbol switch
    {
        "ES" => 100,    // E-mini S&P 500
        "NQ" => 200,    // E-mini NASDAQ
        "CL" => 50,     // Crude Oil
        _ => 100        // Default threshold
    };
}

private double? GetSymbolVenuePreference(string symbol, string venueName)
{
    // In a real implementation, this would be configurable
    // For now, return null (no preference)
    return null;
}

Venue Management

public List<ExecutionVenue> GetAvailableVenues()
{
    lock (_lock)
    {
        return _venues.Values.Where(v => v.IsActive).ToList();
    }
}

private ExecutionVenue GetVenue(string name)
{
    lock (_lock)
    {
        return _venues.ContainsKey(name) ? _venues[name] : null;
    }
}

public void AddVenue(ExecutionVenue venue)
{
    if (venue == null) throw new ArgumentNullException(nameof(venue));
    if (string.IsNullOrEmpty(venue.Name)) throw new ArgumentException("Venue name required", nameof(venue));
    
    lock (_lock)
    {
        _venues[venue.Name] = venue;
    }
    
    _logger.LogInformation("Venue added: {Venue}", venue.Name);
}

public void RemoveVenue(string venueName)
{
    if (string.IsNullOrEmpty(venueName)) throw new ArgumentException("Venue name required", nameof(venueName));
    
    lock (_lock)
    {
        if (_venues.ContainsKey(venueName))
        {
            _venues.Remove(venueName);
        }
    }
    
    _logger.LogInformation("Venue removed: {Venue}", venueName);
}

public void UpdateVenue(ExecutionVenue venue)
{
    if (venue == null) throw new ArgumentNullException(nameof(venue));
    if (string.IsNullOrEmpty(venue.Name)) throw new ArgumentException("Venue name required", nameof(venue));
    
    lock (_lock)
    {
        _venues[venue.Name] = venue;
    }
    
    _logger.LogInformation("Venue updated: {Venue}", venue.Name);
}

Performance Metrics

Routing Metrics Model

/// <summary>
/// Routing performance metrics
/// </summary>
public record RoutingMetrics(
    Dictionary<string, VenueMetrics> VenuePerformance,
    int TotalRoutedOrders,
    double AverageRoutingTimeMs,
    DateTime LastUpdated
);

/// <summary>
/// Metrics for a specific execution venue
/// </summary>
public record VenueMetrics(
    string VenueName,
    int OrdersRouted,
    double FillRate,
    double AverageSlippage,
    double AverageExecutionTimeMs,
    decimal TotalValueRouted
);

Metrics Collection

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

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

private void UpdateVenuePerformance(string venueName, OrderResult orderResult)
{
    // Update performance metrics based on order execution results
    lock (_lock)
    {
        if (_routingMetrics.VenuePerformance.ContainsKey(venueName))
        {
            var metrics = _routingMetrics.VenuePerformance[venueName];
            
            // Update fill rate
            var newFillRate = orderResult.Success ? 
                (metrics.FillRate * metrics.OrdersRouted + 1.0) / (metrics.OrdersRouted + 1) :
                (metrics.FillRate * metrics.OrdersRouted) / (metrics.OrdersRouted + 1);
                
            var updatedMetrics = metrics with
            {
                FillRate = newFillRate
                // In a real implementation, we would also update:
                // - AverageSlippage based on execution prices vs. expected prices
                // - AverageExecutionTimeMs based on time from order submission to fill
                // - TotalValueRouted based on order values
            };
            
            _routingMetrics.VenuePerformance[venueName] = updatedMetrics;
        }
    }
}

Advanced Routing Features

Time-Based Routing

private ExecutionVenue SelectVenueBasedOnTime(ExecutionVenue defaultVenue, OrderRequest request)
{
    var currentTime = DateTime.UtcNow.TimeOfDay;
    
    // Prefer faster venues during market open/close
    if ((currentTime >= new TimeSpan(13, 30, 0) && currentTime <= new TimeSpan(14, 0, 0)) || // Market open
        (currentTime >= new TimeSpan(20, 0, 0) && currentTime <= new TimeSpan(20, 30, 0)))   // Market close
    {
        // Find the fastest active venue
        var fastestVenue = _venues.Values
            .Where(v => v.IsActive)
            .OrderByDescending(v => v.SpeedFactor)
            .FirstOrDefault();
            
        return fastestVenue ?? defaultVenue;
    }
    
    return defaultVenue;
}

Liquidity-Based Routing

private ExecutionVenue SelectVenueBasedOnLiquidity(ExecutionVenue defaultVenue, OrderRequest request)
{
    // In a real implementation, this would check real-time liquidity data
    // For now, we'll use a simplified approach based on symbol and venue characteristics
    
    var symbolLiquidity = GetSymbolLiquidity(request.Symbol);
    
    if (symbolLiquidity == LiquidityLevel.High)
    {
        // For highly liquid symbols, prefer cost-effective venues
        return _venues.Values
            .Where(v => v.IsActive)
            .OrderBy(v => v.CostFactor)
            .FirstOrDefault() ?? defaultVenue;
    }
    else if (symbolLiquidity == LiquidityLevel.Low)
    {
        // For less liquid symbols, prefer reliable venues
        return _venues.Values
            .Where(v => v.IsActive)
            .OrderByDescending(v => v.ReliabilityFactor)
            .FirstOrDefault() ?? defaultVenue;
    }
    
    return defaultVenue;
}

private LiquidityLevel GetSymbolLiquidity(string symbol)
{
    // Simplified liquidity assessment
    return symbol switch
    {
        "ES" => LiquidityLevel.High,    // E-mini S&P 500 - highly liquid
        "NQ" => LiquidityLevel.High,    // E-mini NASDAQ - highly liquid
        "CL" => LiquidityLevel.Medium,  // Crude Oil - moderately liquid
        _ => LiquidityLevel.Medium      // Default
    };
}

public enum LiquidityLevel
{
    Low,
    Medium,
    High
}

Slippage Control

private bool ValidateSlippage(ExecutionVenue venue, OrderRequest request)
{
    // Check if expected slippage exceeds configured maximum
    var expectedSlippage = CalculateExpectedSlippage(venue, request);
    return expectedSlippage <= _routingConfig.MaxSlippagePercent;
}

private double CalculateExpectedSlippage(ExecutionVenue venue, OrderRequest request)
{
    // Simplified slippage calculation
    // In a real implementation, this would be more sophisticated
    
    // Base slippage based on venue
    var baseSlippage = venue.Name switch
    {
        "Primary" => 0.1,
        "Secondary" => 0.2,
        "DarkPool" => 0.3,
        _ => 0.2
    };
    
    // Adjust for order size
    var sizeAdjustment = Math.Min(1.0, request.Quantity / 1000.0);
    
    // Adjust for market conditions (simplified)
    var marketConditionAdjustment = 1.0; // Would be dynamic in real implementation
    
    return baseSlippage * (1 + sizeAdjustment) * marketConditionAdjustment;
}

Error Handling and Fallbacks

Venue Fallback Logic

private ExecutionVenue GetFallbackVenue(ExecutionVenue primaryVenue)
{
    // Try to find an alternative venue
    return _venues.Values
        .Where(v => v.IsActive && v.Name != primaryVenue.Name)
        .OrderByDescending(v => v.ReliabilityFactor)
        .FirstOrDefault() ?? GetVenue(_routingConfig.DefaultVenue);
}

Routing Timeout Handling

public async Task<RoutingResult> RouteOrderAsync(OrderRequest request, StrategyContext context)
{
    // Implement timeout for routing decisions
    using (var cts = new CancellationTokenSource(_routingConfig.MaxRoutingTime))
    {
        try
        {
            return await RouteOrderWithTimeoutAsync(request, context, cts.Token);
        }
        catch (OperationCanceledException)
        {
            _logger.LogWarning("Routing timeout exceeded for order {Symbol}", request.Symbol);
            var defaultVenue = GetVenue(_routingConfig.DefaultVenue);
            return new RoutingResult(false, null, defaultVenue, "Routing timeout exceeded", 
                new Dictionary<string, object> { ["error"] = "timeout" });
        }
    }
}

private async Task<RoutingResult> RouteOrderWithTimeoutAsync(OrderRequest request, StrategyContext context, CancellationToken cancellationToken)
{
    // Implementation with cancellation support
    // ... (existing routing logic)
}

Testing Considerations

Unit Tests for Routing Logic

  1. Venue Selection: Verify correct venue is selected based on criteria
  2. Configuration Changes: Test behavior with different routing configurations
  3. Large Orders: Verify large orders are routed appropriately
  4. Inactive Venues: Test handling of inactive venues
  5. Fallback Scenarios: Test fallback behavior when preferred venues are unavailable

Integration Tests

  1. Venue Management: Test adding, removing, and updating venues
  2. Performance Metrics: Verify metrics are collected and updated correctly
  3. Real-time Adjustments: Test dynamic routing based on market conditions
  4. Error Handling: Test graceful degradation when venues are unavailable

Performance Considerations

Routing Cache

Cache routing decisions for identical orders within a short time window:

private readonly Dictionary<string, (RoutingResult result, DateTime timestamp)> _routingCache 
    = new Dictionary<string, (RoutingResult, DateTime)>();

private string GenerateRoutingCacheKey(OrderRequest request)
{
    // Generate a cache key based on order parameters
    return $"{request.Symbol}_{request.Side}_{request.Quantity}_{request.Type}";
}

private RoutingResult GetCachedRoutingResult(string cacheKey)
{
    if (_routingCache.ContainsKey(cacheKey))
    {
        var (result, timestamp) = _routingCache[cacheKey];
        // Expire cache after 500ms
        if (DateTime.UtcNow.Subtract(timestamp).TotalMilliseconds < 500)
        {
            return result;
        }
        else
        {
            _routingCache.Remove(cacheKey);
        }
    }
    
    return null;
}

Asynchronous Routing

Perform routing decisions asynchronously to avoid blocking order submission:

public async Task<RoutingResult> RouteOrderAsync(OrderRequest request, StrategyContext context)
{
    // Start routing in background
    var routingTask = PerformRoutingAsync(request, context);
    
    // Continue with other order processing steps
    
    // Wait for routing result (with timeout)
    using (var cts = new CancellationTokenSource(_routingConfig.MaxRoutingTime))
    {
        try
        {
            return await routingTask.WaitAsync(cts.Token);
        }
        catch (OperationCanceledException)
        {
            // Handle timeout
            return GetDefaultRoutingResult(request);
        }
    }
}

Monitoring and Alerting

Routing Alerts

Generate alerts for routing issues or performance degradation:

private void GenerateRoutingAlerts(RoutingResult result, OrderRequest request)
{
    if (!result.Success)
    {
        _logger.LogWarning("Routing failed for order {Symbol}: {Message}", 
            request.Symbol, result.Message);
            
        // In a real implementation, this might trigger:
        // - Email alerts to operations team
        // - Slack notifications
        // - Dashboard warnings
    }
}

Routing Dashboard Integration

Provide metrics for routing dashboard integration:

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

Future Enhancements

  1. Machine Learning: Use ML models to predict optimal venues based on historical data
  2. Real-time Market Data: Integrate real-time liquidity and volatility data into routing decisions
  3. Cross-Venue Optimization: Coordinate orders across multiple venues for better execution
  4. Dynamic Venue Preferences: Adjust venue preferences based on real-time performance
  5. Regulatory Compliance: Ensure routing decisions comply with regulatory requirements
  6. Cost Analysis: Detailed cost analysis including rebates and fees