44 KiB
44 KiB
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:
- Global Configuration: System-wide default settings
- Venue-Specific Configuration: Settings that apply to specific execution venues
- Symbol-Specific Configuration: Settings that apply to specific trading symbols
- Strategy-Specific Configuration: Settings that apply to specific trading strategies
- Runtime Configuration: Dynamic configuration that can be updated during operation
Configuration Models
Base Configuration Interface
/// <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
/// <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
/// <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
/// <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
/// <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
/// <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
/// <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
/// <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
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
/// <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
- Configuration Validation: Test validation of different configuration types
- Configuration Loading/Saving: Test persistence of configurations
- Configuration Updates: Test updating configurations with versioning
- Venue Selection: Test venue selection based on different configurations
- Symbol-Specific Routing: Test routing based on symbol configurations
Integration Tests
- Configuration Repository: Test file-based configuration storage
- Dynamic Configuration Updates: Test updating configurations at runtime
- Performance Impact: Test performance with large configurations
- Error Handling: Test error handling for invalid configurations
Performance Considerations
Configuration Caching
/// <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
/// <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
- Database Configuration Storage: Store configurations in a database for enterprise deployments
- Configuration Versioning: Full version history and rollback capabilities
- Configuration Templates: Predefined configuration templates for different trading scenarios
- A/B Testing: Test different configurations with subsets of orders
- Machine Learning: Use ML to optimize configuration parameters based on performance
- Real-time Configuration Updates: Push configuration updates to running instances
- Configuration Auditing: Full audit trail of all configuration changes
- Multi-tenancy: Support for multiple tenants with separate configurations