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

1286 lines
44 KiB
Markdown

# Routing Configuration System Design
## Overview
This document details the implementation of the routing configuration system for the Order Management System (OMS), which allows users to configure and customize the smart order routing behavior through a flexible and extensible configuration system.
## Configuration Architecture
The routing configuration system provides a hierarchical approach to configuration management:
1. **Global Configuration**: System-wide default settings
2. **Venue-Specific Configuration**: Settings that apply to specific execution venues
3. **Symbol-Specific Configuration**: Settings that apply to specific trading symbols
4. **Strategy-Specific Configuration**: Settings that apply to specific trading strategies
5. **Runtime Configuration**: Dynamic configuration that can be updated during operation
## Configuration Models
### Base Configuration Interface
```csharp
/// <summary>
/// Base interface for all configuration objects
/// </summary>
public interface IConfiguration
{
/// <summary>
/// Unique identifier for this configuration
/// </summary>
string Id { get; }
/// <summary>
/// Name of this configuration
/// </summary>
string Name { get; }
/// <summary>
/// Description of this configuration
/// </summary>
string Description { get; }
/// <summary>
/// Whether this configuration is active
/// </summary>
bool IsActive { get; set; }
/// <summary>
/// When this configuration was created
/// </summary>
DateTime CreatedAt { get; }
/// <summary>
/// When this configuration was last updated
/// </summary>
DateTime UpdatedAt { get; set; }
/// <summary>
/// Version of this configuration
/// </summary>
int Version { get; }
}
```
### Routing Configuration Model
```csharp
/// <summary>
/// Main routing configuration parameters
/// </summary>
public record RoutingConfig : IConfiguration
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Name { get; set; } = "Default Routing Configuration";
public string Description { get; set; } = "Default routing configuration for the OMS";
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public int Version { get; set; } = 1;
// Core routing settings
public bool SmartRoutingEnabled { get; set; } = true;
public string DefaultVenue { get; set; } = "primary-broker";
public Dictionary<string, double> VenuePreferences { get; set; } = new Dictionary<string, double>
{
["primary-broker"] = 1.0,
["secondary-broker"] = 0.8,
["dark-pool"] = 0.6
};
// Routing criteria weights
public double CostWeight { get; set; } = 0.4; // 40% weight to cost
public double SpeedWeight { get; set; } = 0.3; // 30% weight to speed
public double ReliabilityWeight { get; set; } = 0.3; // 30% weight to reliability
// Risk controls
public double MaxSlippagePercent { get; set; } = 0.5;
public TimeSpan MaxRoutingTime { get; set; } = TimeSpan.FromSeconds(30);
public bool EnableSlippageControl { get; set; } = true;
public bool EnableTimeoutControl { get; set; } = true;
// Advanced routing features
public bool EnableTimeBasedRouting { get; set; } = true;
public bool EnableLiquidityBasedRouting { get; set; } = true;
public bool EnableSizeBasedRouting { get; set; } = true;
// Performance thresholds
public double MinFillRateThreshold { get; set; } = 0.95; // 95% minimum fill rate
public double MaxLatencyThresholdMs { get; set; } = 500; // 500ms maximum latency
public int MaxConsecutiveFailures { get; set; } = 5; // Max consecutive failures before deactivating venue
// Algorithmic order routing
public Dictionary<string, string> AlgorithmVenuePreferences { get; set; } = new Dictionary<string, string>
{
["TWAP"] = "primary-broker",
["VWAP"] = "primary-broker",
["Iceberg"] = "dark-pool"
};
// Symbol-specific routing
public Dictionary<string, string> SymbolVenuePreferences { get; set; } = new Dictionary<string, string>();
// Time-based routing
public Dictionary<TimeOfDayRange, string> TimeBasedVenuePreferences { get; set; } = new Dictionary<TimeOfDayRange, string>();
public static RoutingConfig Default => new RoutingConfig();
}
```
### Time of Day Range Model
```csharp
/// <summary>
/// Represents a time range during the day
/// </summary>
public record TimeOfDayRange(
TimeSpan StartTime,
TimeSpan EndTime
)
{
public bool Contains(DateTime time)
{
var timeOfDay = time.TimeOfDay;
return timeOfDay >= StartTime && timeOfDay <= EndTime;
}
public bool Overlaps(TimeOfDayRange other)
{
return StartTime <= other.EndTime && EndTime >= other.StartTime;
}
}
```
### Venue Configuration Model
```csharp
/// <summary>
/// Configuration for a specific execution venue
/// </summary>
public record VenueConfig : IConfiguration
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public int Version { get; set; } = 1;
// Venue-specific routing settings
public double CostFactor { get; set; } = 1.0;
public double SpeedFactor { get; set; } = 1.0;
public double ReliabilityFactor { get; set; } = 1.0;
// Venue capabilities
public List<VenueOrderType> SupportedOrderTypes { get; set; } = new List<VenueOrderType>
{
VenueOrderType.Market,
VenueOrderType.Limit,
VenueOrderType.StopMarket,
VenueOrderType.StopLimit
};
public List<string> SupportedSymbols { get; set; } = new List<string>();
public Dictionary<string, decimal> MinimumOrderSizes { get; set; } = new Dictionary<string, decimal>();
public Dictionary<string, decimal> MaximumOrderSizes { get; set; } = new Dictionary<string, decimal>();
// Connection settings
public string ApiKey { get; set; }
public string ApiSecret { get; set; }
public string BaseUrl { get; set; }
public int RateLimit { get; set; } = 100; // Requests per minute
// Performance settings
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
public int MaxRetries { get; set; } = 3;
public TimeSpan RetryDelay { get; set; } = TimeSpan.FromSeconds(1);
// Market data settings
public bool EnableMarketData { get; set; } = true;
public int MarketDataDepth { get; set; } = 10; // Levels of market depth
public TimeSpan MarketDataRefreshInterval { get; set; } = TimeSpan.FromMilliseconds(100);
}
```
### Symbol Configuration Model
```csharp
/// <summary>
/// Configuration for routing specific symbols
/// </summary>
public record SymbolRoutingConfig : IConfiguration
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public int Version { get; set; } = 1;
// Symbol-specific routing settings
public string Symbol { get; set; }
public string PreferredVenue { get; set; }
public List<string> BackupVenues { get; set; } = new List<string>();
// Symbol characteristics
public LiquidityLevel Liquidity { get; set; } = LiquidityLevel.Medium;
public VolatilityLevel Volatility { get; set; } = VolatilityLevel.Medium;
public string AssetClass { get; set; } = "Equity";
// Order size thresholds
public int LargeOrderThreshold { get; set; } = 100;
public int BlockOrderThreshold { get; set; } = 1000;
// Pricing settings
public int TickSize { get; set; } = 1; // In cents or appropriate units
public decimal MinPriceIncrement { get; set; } = 0.01m;
// Venue preferences for this symbol
public Dictionary<string, double> VenuePreferences { get; set; } = new Dictionary<string, double>();
}
```
## Configuration Management System
### Configuration Manager
```csharp
/// <summary>
/// Manages routing configuration for the OMS
/// </summary>
public class RoutingConfigurationManager
{
private readonly ILogger<RoutingConfigurationManager> _logger;
private readonly IConfigurationRepository _configRepository;
private readonly Dictionary<string, IConfiguration> _configurations;
private readonly object _lock = new object();
public RoutingConfigurationManager(
ILogger<RoutingConfigurationManager> logger,
IConfigurationRepository configRepository)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_configRepository = configRepository ?? throw new ArgumentNullException(nameof(configRepository));
_configurations = new Dictionary<string, IConfiguration>();
// Load initial configurations
LoadConfigurationsAsync().Wait();
}
/// <summary>
/// Get the current routing configuration
/// </summary>
public async Task<RoutingConfig> GetRoutingConfigAsync()
{
var config = await GetConfigurationAsync<RoutingConfig>("routing-config");
return config ?? RoutingConfig.Default;
}
/// <summary>
/// Update the routing configuration
/// </summary>
public async Task UpdateRoutingConfigAsync(RoutingConfig config)
{
if (config == null) throw new ArgumentNullException(nameof(config));
await UpdateConfigurationAsync(config);
_logger.LogInformation("Routing configuration updated");
}
/// <summary>
/// Get configuration for a specific venue
/// </summary>
public async Task<VenueConfig> GetVenueConfigAsync(string venueId)
{
if (string.IsNullOrEmpty(venueId)) throw new ArgumentException("Venue ID required", nameof(venueId));
var config = await GetConfigurationAsync<VenueConfig>($"venue-{venueId}");
return config;
}
/// <summary>
/// Update configuration for a specific venue
/// </summary>
public async Task UpdateVenueConfigAsync(VenueConfig config)
{
if (config == null) throw new ArgumentNullException(nameof(config));
if (string.IsNullOrEmpty(config.Id)) throw new ArgumentException("Venue config ID required", nameof(config));
await UpdateConfigurationAsync(config);
_logger.LogInformation("Venue configuration updated for {VenueId}", config.Id);
}
/// <summary>
/// Get routing configuration for a specific symbol
/// </summary>
public async Task<SymbolRoutingConfig> GetSymbolRoutingConfigAsync(string symbol)
{
if (string.IsNullOrEmpty(symbol)) throw new ArgumentException("Symbol required", nameof(symbol));
var config = await GetConfigurationAsync<SymbolRoutingConfig>($"symbol-{symbol}");
return config;
}
/// <summary>
/// Update routing configuration for a specific symbol
/// </summary>
public async Task UpdateSymbolRoutingConfigAsync(SymbolRoutingConfig config)
{
if (config == null) throw new ArgumentNullException(nameof(config));
if (string.IsNullOrEmpty(config.Symbol)) throw new ArgumentException("Symbol required", nameof(config));
var configId = $"symbol-{config.Symbol}";
config.Id = configId;
config.Name = $"Routing config for {config.Symbol}";
config.Description = $"Routing configuration for symbol {config.Symbol}";
await UpdateConfigurationAsync(config);
_logger.LogInformation("Symbol routing configuration updated for {Symbol}", config.Symbol);
}
/// <summary>
/// Get configuration by ID
/// </summary>
public async Task<T> GetConfigurationAsync<T>(string configId) where T : class, IConfiguration
{
if (string.IsNullOrEmpty(configId)) throw new ArgumentException("Config ID required", nameof(configId));
lock (_lock)
{
if (_configurations.ContainsKey(configId))
{
return _configurations[configId] as T;
}
}
// Load from repository if not in memory
var config = await _configRepository.GetConfigurationAsync<T>(configId);
if (config != null)
{
lock (_lock)
{
_configurations[configId] = config;
}
}
return config;
}
/// <summary>
/// Update a configuration
/// </summary>
public async Task UpdateConfigurationAsync<T>(T config) where T : class, IConfiguration
{
if (config == null) throw new ArgumentNullException(nameof(config));
if (string.IsNullOrEmpty(config.Id)) throw new ArgumentException("Config ID required", nameof(config));
// Validate configuration
if (!ValidateConfiguration(config))
{
throw new ArgumentException("Invalid configuration", nameof(config));
}
// Increment version
var versionProperty = typeof(T).GetProperty("Version");
if (versionProperty != null && versionProperty.CanWrite)
{
var currentVersion = (int)versionProperty.GetValue(config);
versionProperty.SetValue(config, currentVersion + 1);
}
// Update timestamp
var updatedAtProperty = typeof(T).GetProperty("UpdatedAt");
if (updatedAtProperty != null && updatedAtProperty.CanWrite)
{
updatedAtProperty.SetValue(config, DateTime.UtcNow);
}
// Save to repository
await _configRepository.SaveConfigurationAsync(config);
// Update in memory cache
lock (_lock)
{
_configurations[config.Id] = config;
}
}
/// <summary>
/// Validate a configuration
/// </summary>
public bool ValidateConfiguration<T>(T config) where T : class, IConfiguration
{
if (config == null) return false;
// Basic validation
if (string.IsNullOrEmpty(config.Id)) return false;
if (string.IsNullOrEmpty(config.Name)) return false;
if (config.CreatedAt > DateTime.UtcNow) return false;
if (config.UpdatedAt < config.CreatedAt) return false;
if (config.Version < 1) return false;
// Type-specific validation
switch (config)
{
case RoutingConfig routingConfig:
return ValidateRoutingConfig(routingConfig);
case VenueConfig venueConfig:
return ValidateVenueConfig(venueConfig);
case SymbolRoutingConfig symbolConfig:
return ValidateSymbolRoutingConfig(symbolConfig);
default:
return true; // Unknown config type, assume valid
}
}
private bool ValidateRoutingConfig(RoutingConfig config)
{
// Validate weights sum to 1.0 (approximately)
var totalWeight = config.CostWeight + config.SpeedWeight + config.ReliabilityWeight;
if (Math.Abs(totalWeight - 1.0) > 0.01) return false;
// Validate thresholds
if (config.MaxSlippagePercent < 0 || config.MaxSlippagePercent > 100) return false;
if (config.MaxRoutingTime.TotalMilliseconds <= 0) return false;
if (config.MinFillRateThreshold < 0 || config.MinFillRateThreshold > 1) return false;
if (config.MaxLatencyThresholdMs <= 0) return false;
if (config.MaxConsecutiveFailures <= 0) return false;
return true;
}
private bool ValidateVenueConfig(VenueConfig config)
{
// Validate factors are positive
if (config.CostFactor <= 0) return false;
if (config.SpeedFactor <= 0) return false;
if (config.ReliabilityFactor <= 0) return false;
// Validate connection settings
if (string.IsNullOrEmpty(config.BaseUrl)) return false;
if (config.RateLimit <= 0) return false;
if (config.Timeout.TotalMilliseconds <= 0) return false;
if (config.MaxRetries < 0) return false;
return true;
}
private bool ValidateSymbolRoutingConfig(SymbolRoutingConfig config)
{
// Validate symbol
if (string.IsNullOrEmpty(config.Symbol)) return false;
// Validate thresholds
if (config.LargeOrderThreshold <= 0) return false;
if (config.BlockOrderThreshold <= 0) return false;
if (config.LargeOrderThreshold > config.BlockOrderThreshold) return false;
// Validate pricing settings
if (config.TickSize <= 0) return false;
if (config.MinPriceIncrement <= 0) return false;
return true;
}
/// <summary>
/// Load all configurations from repository
/// </summary>
private async Task LoadConfigurationsAsync()
{
try
{
var configs = await _configRepository.GetAllConfigurationsAsync();
lock (_lock)
{
_configurations.Clear();
foreach (var config in configs)
{
_configurations[config.Id] = config;
}
}
_logger.LogInformation("Loaded {Count} configurations", configs.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading configurations");
throw;
}
}
/// <summary>
/// Reload configurations from repository
/// </summary>
public async Task ReloadConfigurationsAsync()
{
await LoadConfigurationsAsync();
_logger.LogInformation("Configurations reloaded");
}
}
```
### Configuration Repository Interface
```csharp
/// <summary>
/// Repository for configuration storage and retrieval
/// </summary>
public interface IConfigurationRepository
{
/// <summary>
/// Get a configuration by ID
/// </summary>
Task<T> GetConfigurationAsync<T>(string configId) where T : class, IConfiguration;
/// <summary>
/// Save a configuration
/// </summary>
Task SaveConfigurationAsync<T>(T config) where T : class, IConfiguration;
/// <summary>
/// Delete a configuration
/// </summary>
Task DeleteConfigurationAsync(string configId);
/// <summary>
/// Get all configurations
/// </summary>
Task<List<IConfiguration>> GetAllConfigurationsAsync();
/// <summary>
/// Get configurations by type
/// </summary>
Task<List<T>> GetConfigurationsByTypeAsync<T>() where T : class, IConfiguration;
}
```
### File-Based Configuration Repository
```csharp
/// <summary>
/// File-based implementation of configuration repository
/// </summary>
public class FileConfigurationRepository : IConfigurationRepository
{
private readonly string _configDirectory;
private readonly ILogger<FileConfigurationRepository> _logger;
private readonly JsonSerializerOptions _jsonOptions;
public FileConfigurationRepository(
string configDirectory,
ILogger<FileConfigurationRepository> logger)
{
_configDirectory = configDirectory ?? throw new ArgumentNullException(nameof(configDirectory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
// Ensure directory exists
if (!Directory.Exists(_configDirectory))
{
Directory.CreateDirectory(_configDirectory);
}
_jsonOptions = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
}
public async Task<T> GetConfigurationAsync<T>(string configId) where T : class, IConfiguration
{
if (string.IsNullOrEmpty(configId)) throw new ArgumentException("Config ID required", nameof(configId));
var filePath = Path.Combine(_configDirectory, $"{configId}.json");
if (!File.Exists(filePath))
{
return null;
}
try
{
var json = await File.ReadAllTextAsync(filePath);
return JsonSerializer.Deserialize<T>(json, _jsonOptions);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error reading configuration from {FilePath}", filePath);
throw;
}
}
public async Task SaveConfigurationAsync<T>(T config) where T : class, IConfiguration
{
if (config == null) throw new ArgumentNullException(nameof(config));
if (string.IsNullOrEmpty(config.Id)) throw new ArgumentException("Config ID required", nameof(config));
var filePath = Path.Combine(_configDirectory, $"{config.Id}.json");
try
{
var json = JsonSerializer.Serialize(config, _jsonOptions);
await File.WriteAllTextAsync(filePath, json);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error saving configuration to {FilePath}", filePath);
throw;
}
}
public async Task DeleteConfigurationAsync(string configId)
{
if (string.IsNullOrEmpty(configId)) throw new ArgumentException("Config ID required", nameof(configId));
var filePath = Path.Combine(_configDirectory, $"{configId}.json");
if (File.Exists(filePath))
{
try
{
File.Delete(filePath);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting configuration file {FilePath}", filePath);
throw;
}
}
}
public async Task<List<IConfiguration>> GetAllConfigurationsAsync()
{
var configs = new List<IConfiguration>();
try
{
var files = Directory.GetFiles(_configDirectory, "*.json");
foreach (var file in files)
{
try
{
var json = await File.ReadAllTextAsync(file);
// Try to deserialize as different config types
// In a real implementation, you might store type information in the file
var config = TryDeserializeConfig(json);
if (config != null)
{
configs.Add(config);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error reading configuration file {FilePath}", file);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error reading configuration directory {ConfigDirectory}", _configDirectory);
throw;
}
return configs;
}
public async Task<List<T>> GetConfigurationsByTypeAsync<T>() where T : class, IConfiguration
{
var configs = new List<T>();
try
{
var files = Directory.GetFiles(_configDirectory, "*.json");
foreach (var file in files)
{
try
{
var json = await File.ReadAllTextAsync(file);
var config = JsonSerializer.Deserialize<T>(json, _jsonOptions);
if (config != null)
{
configs.Add(config);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error reading configuration file {FilePath}", file);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error reading configuration directory {ConfigDirectory}", _configDirectory);
throw;
}
return configs;
}
private IConfiguration TryDeserializeConfig(string json)
{
try
{
// Try different configuration types
var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
if (root.TryGetProperty("costWeight", out _))
{
return JsonSerializer.Deserialize<RoutingConfig>(json, _jsonOptions);
}
else if (root.TryGetProperty("costFactor", out _))
{
return JsonSerializer.Deserialize<VenueConfig>(json, _jsonOptions);
}
else if (root.TryGetProperty("symbol", out _))
{
return JsonSerializer.Deserialize<SymbolRoutingConfig>(json, _jsonOptions);
}
return null;
}
catch
{
return null;
}
}
}
```
## Integration with OrderManager
### Configuration Integration in OrderManager
```csharp
public partial class OrderManager : IOrderManager
{
private readonly RoutingConfigurationManager _configManager;
// Enhanced constructor with configuration manager
public OrderManager(
IRiskManager riskManager,
IPositionSizer positionSizer,
ILogger<OrderManager> logger,
RoutingConfigurationManager configManager) : base(riskManager, positionSizer, logger)
{
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
_venueManager = new VenueManager(logger);
_omsToVenueOrderIdMap = new Dictionary<string, string>();
_venueToOmsOrderIdMap = new Dictionary<string, string>();
// Initialize with configurations
InitializeWithConfigurationsAsync().Wait();
}
private async Task InitializeWithConfigurationsAsync()
{
try
{
// Get routing configuration
var routingConfig = await _configManager.GetRoutingConfigAsync();
// Initialize venues based on configuration
await InitializeVenuesFromConfigAsync(routingConfig);
_logger.LogInformation("OrderManager initialized with configurations");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error initializing OrderManager with configurations");
throw;
}
}
private async Task InitializeVenuesFromConfigAsync(RoutingConfig routingConfig)
{
// Get all venue configurations
var venueConfigs = await _configManager.GetConfigurationsByTypeAsync<VenueConfig>();
foreach (var venueConfig in venueConfigs)
{
if (!venueConfig.IsActive) continue;
try
{
// Create venue based on configuration
var venue = CreateVenueFromConfig(venueConfig);
if (venue != null)
{
_venueManager.AddVenue(venue);
_logger.LogInformation("Venue {VenueId} initialized from configuration", venueConfig.Id);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error initializing venue {VenueId} from configuration", venueConfig.Id);
}
}
}
private IExecutionVenue CreateVenueFromConfig(VenueConfig config)
{
// Create venue based on type and configuration
// This is a simplified implementation - in reality, you would have
// specific venue implementations for different broker/exchange APIs
return new BrokerExecutionVenue(
id: config.Id,
name: config.Name,
description: config.Description,
type: VenueType.Broker,
config: config,
brokerApi: CreateBrokerApiFromConfig(config),
logger: _logger
);
}
private IBrokerApi CreateBrokerApiFromConfig(VenueConfig config)
{
// Create broker API client based on configuration
// This would be specific to each broker's API
return new GenericBrokerApi(
apiKey: config.ApiKey,
apiSecret: config.ApiSecret,
baseUrl: config.BaseUrl,
rateLimit: config.RateLimit,
timeout: config.Timeout,
maxRetries: config.MaxRetries,
retryDelay: config.RetryDelay
);
}
// Enhanced routing with configuration
public async Task<RoutingResult> RouteOrderAsync(OrderRequest request, StrategyContext context)
{
try
{
// Get current routing configuration
var routingConfig = await _configManager.GetRoutingConfigAsync();
// Check if smart routing is enabled
if (!routingConfig.SmartRoutingEnabled)
{
var defaultVenue = _venueManager.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 });
}
// Get symbol-specific configuration if available
SymbolRoutingConfig symbolConfig = null;
if (!string.IsNullOrEmpty(request.Symbol))
{
symbolConfig = await _configManager.GetSymbolRoutingConfigAsync(request.Symbol);
}
// Select best venue based on configuration
var selectedVenue = await SelectBestVenueAsync(request, context, routingConfig, symbolConfig);
if (selectedVenue == null)
{
return new RoutingResult(false, null, 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 });
}
}
private async Task<IExecutionVenue> SelectBestVenueAsync(
OrderRequest request,
StrategyContext context,
RoutingConfig routingConfig,
SymbolRoutingConfig symbolConfig)
{
// Get all active venues
var venues = _venueManager.GetActiveVenues();
if (venues.Count == 0)
{
return null;
}
if (venues.Count == 1)
{
return venues[0];
}
// Apply symbol-specific venue preference if available
if (symbolConfig != null && !string.IsNullOrEmpty(symbolConfig.PreferredVenue))
{
var preferredVenue = venues.FirstOrDefault(v => v.Id == symbolConfig.PreferredVenue);
if (preferredVenue != null)
{
_logger.LogInformation("Using symbol-preferred venue: {Venue}", preferredVenue.Name);
return preferredVenue;
}
}
// Apply algorithm-specific venue preference if available
if (!string.IsNullOrEmpty(request.Algorithm))
{
if (routingConfig.AlgorithmVenuePreferences.ContainsKey(request.Algorithm))
{
var algorithmVenue = routingConfig.AlgorithmVenuePreferences[request.Algorithm];
var preferredVenue = venues.FirstOrDefault(v => v.Id == algorithmVenue);
if (preferredVenue != null)
{
_logger.LogInformation("Using algorithm-preferred venue: {Venue} for {Algorithm}",
preferredVenue.Name, request.Algorithm);
return preferredVenue;
}
}
}
// Apply time-based routing if enabled
if (routingConfig.EnableTimeBasedRouting)
{
var timeBasedVenue = SelectVenueBasedOnTime(venues, routingConfig);
if (timeBasedVenue != null)
{
_logger.LogInformation("Using time-based venue: {Venue}", timeBasedVenue.Name);
return timeBasedVenue;
}
}
// Calculate scores for all venues
var venueScores = new Dictionary<IExecutionVenue, double>();
foreach (var venue in venues)
{
double score = 0;
// Get venue-specific configuration
var venueConfig = await _configManager.GetVenueConfigAsync(venue.Id);
// Factor in venue preferences from routing config
if (routingConfig.VenuePreferences.ContainsKey(venue.Id))
{
score += routingConfig.VenuePreferences[venue.Id] * 100;
}
// Factor in cost
var costFactor = venueConfig?.CostFactor ?? venue.CostFactor;
score -= costFactor * (routingConfig.CostWeight * 100);
// Factor in speed
var speedFactor = venueConfig?.SpeedFactor ?? venue.SpeedFactor;
score += speedFactor * (routingConfig.SpeedWeight * 100);
// Factor in reliability
var reliabilityFactor = venueConfig?.ReliabilityFactor ?? venue.ReliabilityFactor;
score += reliabilityFactor * (routingConfig.ReliabilityWeight * 100);
// Adjust for order characteristics
score = AdjustScoreForOrderCharacteristics(score, venue, request, context, routingConfig, symbolConfig);
venueScores[venue] = score;
}
// Select venue with highest score
return venueScores.OrderByDescending(kvp => kvp.Value).First().Key;
}
private double AdjustScoreForOrderCharacteristics(
double score,
IExecutionVenue venue,
OrderRequest request,
StrategyContext context,
RoutingConfig routingConfig,
SymbolRoutingConfig symbolConfig)
{
// Adjust for order size
if (request.Quantity > (symbolConfig?.LargeOrderThreshold ?? 100))
{
// Prefer venues that handle large orders well
if (venue.Type == VenueType.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.Id == routingConfig.DefaultVenue)
{
score += 5; // Small bonus for default venue handling algorithms
}
}
// Adjust for symbol-specific venue preferences
if (symbolConfig?.VenuePreferences?.ContainsKey(venue.Id) == true)
{
score += symbolConfig.VenuePreferences[venue.Id] * 10;
}
return score;
}
private IExecutionVenue SelectVenueBasedOnTime(List<IExecutionVenue> venues, RoutingConfig routingConfig)
{
var currentTime = DateTime.UtcNow.TimeOfDay;
// Check time-based venue preferences
foreach (var kvp in routingConfig.TimeBasedVenuePreferences)
{
if (kvp.Key.Contains(DateTime.UtcNow))
{
var venue = venues.FirstOrDefault(v => v.Id == kvp.Value);
if (venue != null)
{
return venue;
}
}
}
return null;
}
}
```
## Configuration Validation and Monitoring
### Configuration Validation
```csharp
/// <summary>
/// Validates routing configurations
/// </summary>
public class RoutingConfigurationValidator
{
private readonly ILogger<RoutingConfigurationValidator> _logger;
public RoutingConfigurationValidator(ILogger<RoutingConfigurationValidator> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<ConfigurationValidationResult> ValidateConfigurationAsync(RoutingConfig config)
{
var result = new ConfigurationValidationResult
{
IsValid = true,
Errors = new List<string>(),
Warnings = new List<string>()
};
if (config == null)
{
result.IsValid = false;
result.Errors.Add("Configuration is null");
return result;
}
// Validate weights
var totalWeight = config.CostWeight + config.SpeedWeight + config.ReliabilityWeight;
if (Math.Abs(totalWeight - 1.0) > 0.01)
{
result.Warnings.Add($"Routing weights do not sum to 1.0 (current sum: {totalWeight:F2})");
}
// Validate venue preferences
foreach (var venueId in config.VenuePreferences.Keys)
{
if (config.VenuePreferences[venueId] < 0 || config.VenuePreferences[venueId] > 1)
{
result.Errors.Add($"Invalid venue preference for {venueId}: {config.VenuePreferences[venueId]} (must be between 0 and 1)");
}
}
// Validate algorithm venue preferences
foreach (var algorithm in config.AlgorithmVenuePreferences.Keys)
{
var venueId = config.AlgorithmVenuePreferences[algorithm];
if (!config.VenuePreferences.ContainsKey(venueId))
{
result.Warnings.Add($"Algorithm {algorithm} prefers venue {venueId} which is not in venue preferences");
}
}
// Validate thresholds
if (config.MaxSlippagePercent < 0.01)
{
result.Warnings.Add("Max slippage percent is very low, may reject valid orders");
}
if (config.MaxRoutingTime.TotalMilliseconds < 100)
{
result.Warnings.Add("Max routing time is very short, may cause timeouts");
}
result.IsValid = result.Errors.Count == 0;
return result;
}
}
/// <summary>
/// Result of configuration validation
/// </summary>
public record ConfigurationValidationResult
{
public bool IsValid { get; set; }
public List<string> Errors { get; set; } = new List<string>();
public List<string> Warnings { get; set; } = new List<string>();
}
```
## Testing Considerations
### Unit Tests for Configuration System
1. **Configuration Validation**: Test validation of different configuration types
2. **Configuration Loading/Saving**: Test persistence of configurations
3. **Configuration Updates**: Test updating configurations with versioning
4. **Venue Selection**: Test venue selection based on different configurations
5. **Symbol-Specific Routing**: Test routing based on symbol configurations
### Integration Tests
1. **Configuration Repository**: Test file-based configuration storage
2. **Dynamic Configuration Updates**: Test updating configurations at runtime
3. **Performance Impact**: Test performance with large configurations
4. **Error Handling**: Test error handling for invalid configurations
## Performance Considerations
### Configuration Caching
```csharp
/// <summary>
/// Caching layer for configurations
/// </summary>
public class ConfigurationCache
{
private readonly MemoryCache _cache;
private readonly ILogger<ConfigurationCache> _logger;
public ConfigurationCache(ILogger<ConfigurationCache> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
var options = new MemoryCacheOptions
{
SizeLimit = 1000, // Maximum number of entries
ExpirationScanFrequency = TimeSpan.FromMinutes(5)
};
_cache = new MemoryCache(options);
}
public T Get<T>(string key) where T : class
{
return _cache.Get<T>(key);
}
public void Set<T>(string key, T value, TimeSpan expiration) where T : class
{
var cacheEntryOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiration,
Size = 1
};
_cache.Set(key, value, cacheEntryOptions);
}
public void Remove(string key)
{
_cache.Remove(key);
}
public void Clear()
{
_cache.Clear();
}
}
```
## Monitoring and Alerting
### Configuration Change Tracking
```csharp
/// <summary>
/// Tracks configuration changes for monitoring and auditing
/// </summary>
public class ConfigurationChangeTracker
{
private readonly ILogger<ConfigurationChangeTracker> _logger;
public ConfigurationChangeTracker(ILogger<ConfigurationChangeTracker> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public void TrackConfigurationChange<T>(T oldConfig, T newConfig, string userId) where T : class, IConfiguration
{
if (oldConfig == null || newConfig == null) return;
var changes = CompareConfigurations(oldConfig, newConfig);
if (changes.Any())
{
_logger.LogInformation("Configuration {ConfigId} changed by {UserId}: {Changes}",
newConfig.Id, userId, string.Join(", ", changes));
}
}
private List<string> CompareConfigurations<T>(T oldConfig, T newConfig) where T : class, IConfiguration
{
var changes = new List<string>();
// Use reflection to compare properties
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead);
foreach (var property in properties)
{
var oldValue = property.GetValue(oldConfig);
var newValue = property.GetValue(newConfig);
if (!Equals(oldValue, newValue))
{
changes.Add($"{property.Name}: {oldValue} -> {newValue}");
}
}
return changes;
}
}
```
## Future Enhancements
1. **Database Configuration Storage**: Store configurations in a database for enterprise deployments
2. **Configuration Versioning**: Full version history and rollback capabilities
3. **Configuration Templates**: Predefined configuration templates for different trading scenarios
4. **A/B Testing**: Test different configurations with subsets of orders
5. **Machine Learning**: Use ML to optimize configuration parameters based on performance
6. **Real-time Configuration Updates**: Push configuration updates to running instances
7. **Configuration Auditing**: Full audit trail of all configuration changes
8. **Multi-tenancy**: Support for multiple tenants with separate configurations