1286 lines
44 KiB
Markdown
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
|