//! Configuration structures use serde::{Deserialize, Serialize}; use rust_decimal::Decimal; use std::collections::HashMap; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RiskConfig { pub max_position_size: Decimal, pub max_daily_loss: Decimal, pub var_confidence_level: f64, pub var_time_horizon: u32, pub var_config: VarConfig, pub circuit_breaker: CircuitBreakerConfig, pub position_limits: PositionLimitsConfig, pub asset_classification: AssetClassificationConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VarConfig { pub confidence_level: f64, pub time_horizon_days: u32, pub lookback_period_days: u32, pub calculation_method: String, pub max_var_limit: f64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct KellyConfig { pub kelly_fraction: f64, pub max_kelly_leverage: f64, pub min_kelly_leverage: f64, pub confidence_threshold: f64, pub lookback_periods: usize, pub default_position_fraction: f64, pub enabled: bool, pub fractional_kelly: f64, pub min_kelly_fraction: f64, pub max_kelly_fraction: f64, } impl Default for KellyConfig { fn default() -> Self { Self { kelly_fraction: 0.25, max_kelly_leverage: 2.0, min_kelly_leverage: 0.1, confidence_threshold: 0.95, lookback_periods: 252, default_position_fraction: 0.02, enabled: true, fractional_kelly: 0.5, min_kelly_fraction: 0.01, max_kelly_fraction: 0.5, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CircuitBreakerConfig { pub enabled: bool, pub price_move_threshold: f64, pub halt_duration_seconds: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PositionLimitsConfig { pub global_limit: f64, pub max_leverage: f64, pub max_var_limit: f64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BacktestingDatabaseConfig { pub url: String, pub max_connections: u32, pub query_timeout: std::time::Duration, pub enable_query_logging: bool, } /// Broker configuration for order routing and execution #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BrokerConfig { /// Broker routing rules based on symbol patterns and sizes pub routing_rules: Vec, /// Default broker when no rules match pub default_broker: String, /// Commission rates by broker pub commission_rates: HashMap, } /// Rule for routing orders to specific brokers #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BrokerRoutingRule { /// Priority (higher numbers take precedence) pub priority: u32, /// Symbol pattern (regex) pub symbol_pattern: String, /// Minimum quantity for this rule pub min_quantity: Option, /// Maximum quantity for this rule pub max_quantity: Option, /// Target broker ID pub broker_id: String, /// Rule description for debugging pub description: String, } /// Commission configuration per broker #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommissionConfig { /// Commission rate (basis points, e.g., 0.00007 = 0.7 bps) pub rate_bps: f64, /// Minimum commission per trade pub min_commission: f64, } impl Default for BrokerConfig { fn default() -> Self { let mut commission_rates = HashMap::new(); commission_rates.insert( "ICMARKETS".to_string(), CommissionConfig { rate_bps: 0.00007, // 0.7 bps min_commission: 0.0, }, ); commission_rates.insert( "IBKR".to_string(), CommissionConfig { rate_bps: 0.00005, // 0.5 bps min_commission: 1.0, }, ); let routing_rules = vec![ BrokerRoutingRule { priority: 100, symbol_pattern: r"^(BTC|ETH).*".to_string(), min_quantity: None, max_quantity: None, broker_id: "ICMARKETS".to_string(), description: "Route all crypto symbols to ICMarkets".to_string(), }, BrokerRoutingRule { priority: 90, symbol_pattern: r".*USD$".to_string(), min_quantity: None, max_quantity: Some(1_000_000.0), broker_id: "ICMARKETS".to_string(), description: "Route smaller USD pairs to ICMarkets".to_string(), }, BrokerRoutingRule { priority: 50, symbol_pattern: r".*".to_string(), // Catch-all min_quantity: None, max_quantity: None, broker_id: "IBKR".to_string(), description: "Default routing to IBKR".to_string(), }, ]; Self { routing_rules, default_broker: "IBKR".to_string(), commission_rates, } } } impl BrokerConfig { /// Select optimal broker based on symbol and quantity using routing rules pub fn select_broker(&self, symbol: &str, quantity: f64) -> String { let symbol_upper = symbol.to_uppercase(); // Sort rules by priority (highest first) let mut applicable_rules: Vec<_> = self.routing_rules.iter() .filter(|rule| { // Check symbol pattern let symbol_matches = if let Ok(regex) = regex::Regex::new(&rule.symbol_pattern) { regex.is_match(&symbol_upper) } else { false }; // Check quantity bounds let quantity_matches = { let min_ok = rule.min_quantity.map_or(true, |min| quantity >= min); let max_ok = rule.max_quantity.map_or(true, |max| quantity <= max); min_ok && max_ok }; symbol_matches && quantity_matches }) .collect(); applicable_rules.sort_by(|a, b| b.priority.cmp(&a.priority)); if let Some(rule) = applicable_rules.first() { rule.broker_id.clone() } else { self.default_broker.clone() } } /// Calculate commission for a given broker and notional value pub fn calculate_commission(&self, broker_id: &str, notional: f64) -> f64 { if let Some(config) = self.commission_rates.get(broker_id) { (notional * config.rate_bps).max(config.min_commission) } else { // Default commission if broker not found notional * 0.0001 // 1 bps } } } /// Asset classification for risk management and volatility profiling #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum AssetClass { /// Equity securities and stocks Equities, /// Bonds and fixed income securities FixedIncome, /// Physical and financial commodities Commodities, /// Foreign exchange and currencies Currencies, /// Alternative investments Alternatives, /// Derivative instruments Derivatives, /// Cash and cash equivalents Cash, } /// Volatility and risk profile for an asset class #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VolatilityProfile { /// Annual volatility (0.0 to 1.0, e.g., 0.25 = 25%) pub annual_volatility: f64, /// Maximum position size as fraction of portfolio (0.0 to 1.0) pub max_position_fraction: f64, /// Volatility threshold for risk alerts (0.0 to 1.0) pub volatility_threshold: f64, /// Maximum daily loss threshold (0.0 to 1.0) pub daily_loss_threshold: f64, } /// Asset classification configuration with symbol mappings and volatility profiles #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AssetClassificationConfig { /// Explicit symbol to asset class mappings pub symbol_mappings: HashMap, /// Volatility profiles for each asset class pub volatility_profiles: HashMap, /// Pattern-based classification rules (regex patterns) pub pattern_rules: Vec, } /// Pattern-based rule for asset classification #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PatternRule { /// Regex pattern to match against symbol pub pattern: String, /// Asset class to assign if pattern matches pub asset_class: AssetClass, /// Priority (higher numbers take precedence) pub priority: u32, } impl Default for AssetClassificationConfig { fn default() -> Self { let mut symbol_mappings = HashMap::new(); // Equity stocks for symbol in ["AAPL", "MSFT", "GOOGL", "AMZN", "META", "TSLA", "NVDA", "JPM", "JNJ", "V"] { symbol_mappings.insert(symbol.to_string(), AssetClass::Equities); } // Major cryptocurrencies for symbol in ["BTC", "ETH", "BTCUSD", "ETHUSD", "BTCUSDT", "ETHUSDT"] { symbol_mappings.insert(symbol.to_string(), AssetClass::Alternatives); } let mut volatility_profiles = HashMap::new(); volatility_profiles.insert(AssetClass::Equities, VolatilityProfile { annual_volatility: 0.25, max_position_fraction: 0.20, volatility_threshold: 0.025, daily_loss_threshold: 0.03, }); volatility_profiles.insert(AssetClass::Alternatives, VolatilityProfile { annual_volatility: 0.80, max_position_fraction: 0.08, volatility_threshold: 0.15, daily_loss_threshold: 0.05, }); volatility_profiles.insert(AssetClass::Currencies, VolatilityProfile { annual_volatility: 0.15, max_position_fraction: 0.30, volatility_threshold: 0.02, daily_loss_threshold: 0.02, }); volatility_profiles.insert(AssetClass::Cash, VolatilityProfile { annual_volatility: 0.01, max_position_fraction: 1.00, volatility_threshold: 0.001, daily_loss_threshold: 0.001, }); volatility_profiles.insert(AssetClass::FixedIncome, VolatilityProfile { annual_volatility: 0.25, max_position_fraction: 0.15, volatility_threshold: 0.03, daily_loss_threshold: 0.025, }); volatility_profiles.insert(AssetClass::Derivatives, VolatilityProfile { annual_volatility: 0.40, max_position_fraction: 0.10, volatility_threshold: 0.05, daily_loss_threshold: 0.04, }); volatility_profiles.insert(AssetClass::Commodities, VolatilityProfile { annual_volatility: 0.30, max_position_fraction: 0.15, volatility_threshold: 0.04, daily_loss_threshold: 0.03, }); let pattern_rules = vec![ PatternRule { pattern: r"^(BTC|ETH).*".to_string(), asset_class: AssetClass::Alternatives, priority: 100, }, PatternRule { pattern: r".*USD$".to_string(), asset_class: AssetClass::Currencies, priority: 80, }, PatternRule { pattern: r".*JPY$".to_string(), asset_class: AssetClass::Currencies, priority: 90, }, PatternRule { pattern: r"^[A-Z]{3,6}$".to_string(), // 3-6 letter symbols (likely equities) asset_class: AssetClass::Equities, priority: 50, }, ]; Self { symbol_mappings, volatility_profiles, pattern_rules, } } } impl AssetClassificationConfig { /// Classify a symbol based on explicit mappings and pattern rules pub fn classify_symbol(&self, symbol: &str) -> AssetClass { let symbol_upper = symbol.to_uppercase(); // First check explicit mappings if let Some(asset_class) = self.symbol_mappings.get(&symbol_upper) { return asset_class.clone(); } // Then check pattern rules (sorted by priority, highest first) let mut applicable_rules: Vec<_> = self.pattern_rules.iter() .filter(|rule| { if let Ok(regex) = regex::Regex::new(&rule.pattern) { regex.is_match(&symbol_upper) } else { false } }) .collect(); applicable_rules.sort_by(|a, b| b.priority.cmp(&a.priority)); if let Some(rule) = applicable_rules.first() { rule.asset_class.clone() } else { AssetClass::Cash // Default fallback for unknown symbols } } /// Get volatility profile for a symbol pub fn get_volatility_profile(&self, symbol: &str) -> VolatilityProfile { let asset_class = self.classify_symbol(symbol); self.volatility_profiles.get(&asset_class) .cloned() .unwrap_or_else(|| VolatilityProfile { annual_volatility: 0.20, max_position_fraction: 0.05, volatility_threshold: 0.02, daily_loss_threshold: 0.01, }) } /// Get daily volatility for a symbol pub fn get_daily_volatility(&self, symbol: &str) -> f64 { let profile = self.get_volatility_profile(symbol); profile.annual_volatility / 252.0_f64.sqrt() } /// Get risk configuration tuple (position_fraction, volatility_threshold, daily_loss_threshold) pub fn get_risk_config(&self, symbol: &str) -> (f64, f64, f64) { let profile = self.get_volatility_profile(symbol); (profile.max_position_fraction, profile.volatility_threshold, profile.daily_loss_threshold) } }