# 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
///
/// Base interface for all configuration objects
///
public interface IConfiguration
{
///
/// Unique identifier for this configuration
///
string Id { get; }
///
/// Name of this configuration
///
string Name { get; }
///
/// Description of this configuration
///
string Description { get; }
///
/// Whether this configuration is active
///
bool IsActive { get; set; }
///
/// When this configuration was created
///
DateTime CreatedAt { get; }
///
/// When this configuration was last updated
///
DateTime UpdatedAt { get; set; }
///
/// Version of this configuration
///
int Version { get; }
}
```
### Routing Configuration Model
```csharp
///
/// Main routing configuration parameters
///
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 VenuePreferences { get; set; } = new Dictionary
{
["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 AlgorithmVenuePreferences { get; set; } = new Dictionary
{
["TWAP"] = "primary-broker",
["VWAP"] = "primary-broker",
["Iceberg"] = "dark-pool"
};
// Symbol-specific routing
public Dictionary SymbolVenuePreferences { get; set; } = new Dictionary();
// Time-based routing
public Dictionary TimeBasedVenuePreferences { get; set; } = new Dictionary();
public static RoutingConfig Default => new RoutingConfig();
}
```
### Time of Day Range Model
```csharp
///
/// Represents a time range during the day
///
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
///
/// Configuration for a specific execution venue
///
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 SupportedOrderTypes { get; set; } = new List
{
VenueOrderType.Market,
VenueOrderType.Limit,
VenueOrderType.StopMarket,
VenueOrderType.StopLimit
};
public List SupportedSymbols { get; set; } = new List();
public Dictionary MinimumOrderSizes { get; set; } = new Dictionary();
public Dictionary MaximumOrderSizes { get; set; } = new Dictionary();
// 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
///
/// Configuration for routing specific symbols
///
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 BackupVenues { get; set; } = new List();
// 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 VenuePreferences { get; set; } = new Dictionary();
}
```
## Configuration Management System
### Configuration Manager
```csharp
///
/// Manages routing configuration for the OMS
///
public class RoutingConfigurationManager
{
private readonly ILogger _logger;
private readonly IConfigurationRepository _configRepository;
private readonly Dictionary _configurations;
private readonly object _lock = new object();
public RoutingConfigurationManager(
ILogger logger,
IConfigurationRepository configRepository)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_configRepository = configRepository ?? throw new ArgumentNullException(nameof(configRepository));
_configurations = new Dictionary();
// Load initial configurations
LoadConfigurationsAsync().Wait();
}
///
/// Get the current routing configuration
///
public async Task GetRoutingConfigAsync()
{
var config = await GetConfigurationAsync("routing-config");
return config ?? RoutingConfig.Default;
}
///
/// Update the routing configuration
///
public async Task UpdateRoutingConfigAsync(RoutingConfig config)
{
if (config == null) throw new ArgumentNullException(nameof(config));
await UpdateConfigurationAsync(config);
_logger.LogInformation("Routing configuration updated");
}
///
/// Get configuration for a specific venue
///
public async Task GetVenueConfigAsync(string venueId)
{
if (string.IsNullOrEmpty(venueId)) throw new ArgumentException("Venue ID required", nameof(venueId));
var config = await GetConfigurationAsync($"venue-{venueId}");
return config;
}
///
/// Update configuration for a specific venue
///
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);
}
///
/// Get routing configuration for a specific symbol
///
public async Task GetSymbolRoutingConfigAsync(string symbol)
{
if (string.IsNullOrEmpty(symbol)) throw new ArgumentException("Symbol required", nameof(symbol));
var config = await GetConfigurationAsync($"symbol-{symbol}");
return config;
}
///
/// Update routing configuration for a specific symbol
///
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);
}
///
/// Get configuration by ID
///
public async Task GetConfigurationAsync(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(configId);
if (config != null)
{
lock (_lock)
{
_configurations[configId] = config;
}
}
return config;
}
///
/// Update a configuration
///
public async Task UpdateConfigurationAsync(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;
}
}
///
/// Validate a configuration
///
public bool ValidateConfiguration(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;
}
///
/// Load all configurations from repository
///
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;
}
}
///
/// Reload configurations from repository
///
public async Task ReloadConfigurationsAsync()
{
await LoadConfigurationsAsync();
_logger.LogInformation("Configurations reloaded");
}
}
```
### Configuration Repository Interface
```csharp
///
/// Repository for configuration storage and retrieval
///
public interface IConfigurationRepository
{
///
/// Get a configuration by ID
///
Task GetConfigurationAsync(string configId) where T : class, IConfiguration;
///
/// Save a configuration
///
Task SaveConfigurationAsync(T config) where T : class, IConfiguration;
///
/// Delete a configuration
///
Task DeleteConfigurationAsync(string configId);
///
/// Get all configurations
///
Task> GetAllConfigurationsAsync();
///
/// Get configurations by type
///
Task> GetConfigurationsByTypeAsync() where T : class, IConfiguration;
}
```
### File-Based Configuration Repository
```csharp
///
/// File-based implementation of configuration repository
///
public class FileConfigurationRepository : IConfigurationRepository
{
private readonly string _configDirectory;
private readonly ILogger _logger;
private readonly JsonSerializerOptions _jsonOptions;
public FileConfigurationRepository(
string configDirectory,
ILogger 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 GetConfigurationAsync(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(json, _jsonOptions);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error reading configuration from {FilePath}", filePath);
throw;
}
}
public async Task SaveConfigurationAsync(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> GetAllConfigurationsAsync()
{
var configs = new List();
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> GetConfigurationsByTypeAsync() where T : class, IConfiguration
{
var configs = new List();
try
{
var files = Directory.GetFiles(_configDirectory, "*.json");
foreach (var file in files)
{
try
{
var json = await File.ReadAllTextAsync(file);
var config = JsonSerializer.Deserialize(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(json, _jsonOptions);
}
else if (root.TryGetProperty("costFactor", out _))
{
return JsonSerializer.Deserialize(json, _jsonOptions);
}
else if (root.TryGetProperty("symbol", out _))
{
return JsonSerializer.Deserialize(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 logger,
RoutingConfigurationManager configManager) : base(riskManager, positionSizer, logger)
{
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
_venueManager = new VenueManager(logger);
_omsToVenueOrderIdMap = new Dictionary();
_venueToOmsOrderIdMap = new Dictionary();
// 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();
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 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 { ["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 { ["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 { ["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 { ["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
{
["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 { ["error"] = ex.Message });
}
}
private async Task 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();
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 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
///
/// Validates routing configurations
///
public class RoutingConfigurationValidator
{
private readonly ILogger _logger;
public RoutingConfigurationValidator(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task ValidateConfigurationAsync(RoutingConfig config)
{
var result = new ConfigurationValidationResult
{
IsValid = true,
Errors = new List(),
Warnings = new List()
};
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;
}
}
///
/// Result of configuration validation
///
public record ConfigurationValidationResult
{
public bool IsValid { get; set; }
public List Errors { get; set; } = new List();
public List Warnings { get; set; } = new List();
}
```
## 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
///
/// Caching layer for configurations
///
public class ConfigurationCache
{
private readonly MemoryCache _cache;
private readonly ILogger _logger;
public ConfigurationCache(ILogger 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(string key) where T : class
{
return _cache.Get(key);
}
public void Set(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
///
/// Tracks configuration changes for monitoring and auditing
///
public class ConfigurationChangeTracker
{
private readonly ILogger _logger;
public ConfigurationChangeTracker(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public void TrackConfigurationChange(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 CompareConfigurations(T oldConfig, T newConfig) where T : class, IConfiguration
{
var changes = new List();
// 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