//! Configuration structures use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RiskConfig { /// Maximum single position size in base currency pub max_position_size: Decimal, /// Maximum total portfolio exposure in base currency pub max_portfolio_exposure: Decimal, /// Maximum concentration percentage for a single position (0.0-1.0) pub max_concentration_pct: Decimal, /// Maximum daily loss threshold in base currency pub max_daily_loss: Decimal, /// Maximum drawdown percentage allowed (0.0-1.0) pub max_drawdown_pct: Decimal, /// Stop loss threshold in base currency pub stop_loss_threshold: Decimal, /// VaR confidence level (e.g., 0.95 for 95%) pub var_confidence_level: f64, /// VaR time horizon in days pub var_time_horizon: u32, /// 1-day VaR limit in base currency pub var_limit_1d: Decimal, /// 10-day VaR limit in base currency pub var_limit_10d: Decimal, /// Maximum single order size in base currency pub max_order_size: Decimal, /// Maximum orders per second (rate limiting) pub max_orders_per_second: u64, /// Maximum notional value per hour in base currency pub max_notional_per_hour: Decimal, /// Kelly criterion fraction limit (0.0-1.0) pub kelly_fraction_limit: f64, /// Maximum Kelly criterion position size (0.0-1.0) pub max_kelly_position_size: f64, /// Emergency stop threshold as fraction of capital (0.0-1.0) pub emergency_stop_threshold: f64, /// VaR configuration pub var_config: VarConfig, /// Circuit breaker configuration pub circuit_breaker: CircuitBreakerConfig, /// Position limits configuration pub position_limits: PositionLimitsConfig, /// Asset classification configuration pub asset_classification: crate::schemas::AssetClassificationSchema, } impl Default for RiskConfig { fn default() -> Self { Self { // Position and exposure limits max_position_size: Decimal::new(1_000_000, 0), // $1M max single position max_portfolio_exposure: Decimal::new(10_000_000, 0), // $10M total portfolio exposure max_concentration_pct: Decimal::new(25, 2), // 25% max concentration // Loss and drawdown limits max_daily_loss: Decimal::new(100_000, 0), // $100K max daily loss max_drawdown_pct: Decimal::new(15, 2), // 15% max drawdown stop_loss_threshold: Decimal::new(50_000, 0), // $50K stop loss threshold // VaR configuration var_confidence_level: 0.95, // 95% confidence var_time_horizon: 1, // 1-day horizon var_limit_1d: Decimal::new(50_000, 0), // $50K 1-day VaR limit var_limit_10d: Decimal::new(150_000, 0), // $150K 10-day VaR limit // Order limits and rate limiting max_order_size: Decimal::new(100_000, 0), // $100K max order size max_orders_per_second: 100, // 100 orders/sec max_notional_per_hour: Decimal::new(10_000_000, 0), // $10M hourly notional // Kelly criterion parameters kelly_fraction_limit: 0.25, // 25% Kelly fraction limit max_kelly_position_size: 0.20, // 20% max Kelly position // Emergency stop emergency_stop_threshold: 0.10, // 10% loss triggers emergency stop // Nested configurations var_config: VarConfig::default(), circuit_breaker: CircuitBreakerConfig::default(), position_limits: PositionLimitsConfig::default(), asset_classification: crate::schemas::AssetClassificationSchema::default(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VarConfig { /// VaR confidence level (0.0-1.0) pub confidence_level: f64, /// Time horizon in days pub time_horizon_days: u32, /// Historical lookback period in days pub lookback_period_days: u32, /// Calculation method (e.g., "historical", "monte_carlo") pub calculation_method: String, /// Maximum VaR limit pub max_var_limit: f64, } impl Default for VarConfig { fn default() -> Self { Self { confidence_level: 0.95, time_horizon_days: 1, lookback_period_days: 252, calculation_method: "historical".to_owned(), max_var_limit: 100_000.0, } } } #[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 { /// Enable circuit breaker pub enabled: bool, /// Price movement threshold to trigger halt (0.0-1.0) pub price_move_threshold: f64, /// Duration to halt trading in seconds pub halt_duration_seconds: u64, } impl Default for CircuitBreakerConfig { fn default() -> Self { Self { enabled: true, price_move_threshold: 0.05, // 5% price move halt_duration_seconds: 300, // 5 minutes } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PositionLimitsConfig { /// Global position limit pub global_limit: f64, /// Maximum leverage allowed pub max_leverage: f64, /// Maximum VaR limit pub max_var_limit: f64, } impl Default for PositionLimitsConfig { fn default() -> Self { Self { global_limit: 10_000_000.0, max_leverage: 3.0, max_var_limit: 100_000.0, } } } /// 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_owned(), CommissionConfig { rate_bps: 0.00007, // 0.7 bps min_commission: 0.0, }, ); commission_rates.insert( "IBKR".to_owned(), 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_owned(), min_quantity: None, max_quantity: None, broker_id: "ICMARKETS".to_owned(), description: "Route all crypto symbols to ICMarkets".to_owned(), }, BrokerRoutingRule { priority: 90, symbol_pattern: r".*USD$".to_owned(), min_quantity: None, max_quantity: Some(1_000_000.0_f64), broker_id: "ICMARKETS".to_owned(), description: "Route smaller USD pairs to ICMarkets".to_owned(), }, BrokerRoutingRule { priority: 50, symbol_pattern: r".*".to_owned(), // Catch-all min_quantity: None, max_quantity: None, broker_id: "IBKR".to_owned(), description: "Default routing to IBKR".to_owned(), }, ]; Self { routing_rules, default_broker: "IBKR".to_owned(), 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 .mul_add(config.rate_bps, 0.0) .max(config.min_commission) } else { // Default commission if broker not found notional.mul_add(0.0001, 0.0) // 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, } /// Encryption configuration for secure model storage #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EncryptionConfig { /// Enable/disable encryption for model storage pub enable_encryption: bool, /// Encryption algorithm (e.g., "AES-256-GCM") pub algorithm: String, /// Key rotation period in days pub key_rotation_days: u64, /// Vault path for encryption keys (optional, can use local keys) pub encryption_keys_vault_path: Option, /// Local key file path for development/testing pub local_key_file: Option, } impl Default for EncryptionConfig { fn default() -> Self { Self { enable_encryption: false, algorithm: "AES-256-GCM".to_owned(), key_rotation_days: 90, encryption_keys_vault_path: None, local_key_file: None, } } } 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_owned(), AssetClass::Equities); } // Major cryptocurrencies for symbol in ["BTC", "ETH", "BTCUSD", "ETHUSD", "BTCUSDT", "ETHUSDT"] { symbol_mappings.insert(symbol.to_owned(), 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_owned(), asset_class: AssetClass::Alternatives, priority: 100, }, PatternRule { pattern: r".*USD$".to_owned(), asset_class: AssetClass::Currencies, priority: 80, }, PatternRule { pattern: r".*JPY$".to_owned(), asset_class: AssetClass::Currencies, priority: 90, }, PatternRule { pattern: r"^[A-Z]{3,6}$".to_owned(), // 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(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); #[allow(clippy::float_arithmetic)] let result = profile.annual_volatility / 252.0_f64.sqrt(); result } /// 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, ) } } /// Configuration for backtesting database connections #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BacktestingDatabaseConfig { /// Database connection URL pub database_url: String, /// Maximum number of database connections in the pool pub max_connections: Option, /// Minimum number of database connections in the pool pub min_connections: Option, /// Timeout in milliseconds for acquiring a connection pub acquire_timeout_ms: Option, /// Statement cache capacity pub statement_cache_capacity: Option, /// Enable SQL query logging pub enable_logging: Option, } /// Configuration for backtesting strategy execution #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BacktestingStrategyConfig { /// Commission rate for trades (e.g., 0.001 = 0.1%) pub commission_rate: f64, /// Slippage rate for trades (e.g., 0.0005 = 0.05%) pub slippage_rate: f64, /// Maximum position size as fraction of portfolio pub max_position_size: Option, /// Enable short selling pub allow_short_selling: Option, } impl Default for BacktestingStrategyConfig { fn default() -> Self { Self { commission_rate: 0.0007, // 0.07% = 7 bps slippage_rate: 0.0002, // 0.02% = 2 bps max_position_size: Some(0.2), // 20% max position allow_short_selling: Some(false), } } } /// Configuration for backtesting performance analysis #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BacktestingPerformanceConfig { /// Risk-free rate for Sharpe ratio calculations (annual rate) pub risk_free_rate: f64, /// Resolution for equity curve (number of points) pub equity_curve_resolution: usize, /// Enable advanced performance metrics pub enable_advanced_metrics: Option, } impl Default for BacktestingPerformanceConfig { fn default() -> Self { Self { risk_free_rate: 0.04, // 4% annual risk-free rate equity_curve_resolution: 1000, enable_advanced_metrics: Some(true), } } } /// TLS/SSL configuration for secure gRPC connections #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TlsConfig { /// Enable/disable TLS for gRPC connections pub enabled: bool, /// Path to server certificate file pub cert_path: String, /// Path to server private key file pub key_path: String, /// Path to CA certificate for client verification (optional) pub ca_cert_path: Option, /// Require client certificate verification pub require_client_cert: bool, /// TLS protocol versions to support (e.g., ["TLSv1.2", "TLSv1.3"]) pub protocol_versions: Vec, /// Cipher suites to use (empty means default) pub cipher_suites: Vec, /// Enable OCSP certificate revocation checking pub enable_ocsp: bool, /// Fallback OCSP responder URL if not present in certificate AIA extension pub ocsp_responder_url: Option, /// Time-to-live for OCSP responses in the cache, in seconds pub ocsp_cache_ttl_secs: u64, } impl Default for TlsConfig { fn default() -> Self { // Wave 75 Fix: Use environment variables with fallback to /tmp instead of /etc let cert_path = std::env::var("TLS_CERT_PATH") .unwrap_or_else(|_| "/tmp/foxhunt/certs/server.crt".to_owned()); let key_path = std::env::var("TLS_KEY_PATH") .unwrap_or_else(|_| "/tmp/foxhunt/certs/server.key".to_owned()); let ca_cert_path = std::env::var("TLS_CA_PATH").ok(); Self { enabled: false, cert_path, key_path, ca_cert_path, require_client_cert: false, protocol_versions: vec!["TLSv1.3".to_owned()], cipher_suites: Vec::new(), enable_ocsp: false, ocsp_responder_url: None, ocsp_cache_ttl_secs: 1800, // 30 minutes } } } /// Trading system configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TradingConfig { /// Maximum order size (in base units) pub max_order_size: f64, /// Minimum order size (in base units) pub min_order_size: f64, /// Maximum price deviation from market (as fraction, e.g., 0.05 = 5%) pub max_price_deviation: f64, /// Enable symbol validation pub enable_symbol_validation: bool, /// Maximum batch notional value (total value of orders in a batch) pub max_batch_notional: f64, /// Maximum position VaR (Value at Risk) limit pub max_position_var: f64, } impl Default for TradingConfig { fn default() -> Self { Self { max_order_size: 1_000_000.0, min_order_size: 0.001, max_price_deviation: 0.05, enable_symbol_validation: false, max_batch_notional: 10_000_000.0, // $10M batch limit max_position_var: 50_000.0, // $50K VaR limit } } } /// Market data ingestion configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MarketDataConfig { /// Market data server host pub host: String, /// WebSocket port for streaming data pub websocket_port: u16, /// API key for authentication pub api_key: String, /// Use SSL/TLS for connections pub use_ssl: bool, /// Connection timeout in seconds pub timeout_seconds: u64, } impl Default for MarketDataConfig { fn default() -> Self { Self { host: "localhost".to_owned(), websocket_port: 8080, api_key: String::new(), use_ssl: false, timeout_seconds: 30, } } }