/home/jgrusewski/Work/foxhunt/config/src/structures.rs
Line | Count | Source |
1 | | //! Configuration structures |
2 | | |
3 | | use rust_decimal::Decimal; |
4 | | use serde::{Deserialize, Serialize}; |
5 | | use std::collections::HashMap; |
6 | | |
7 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
8 | | pub struct RiskConfig { |
9 | | /// Maximum single position size in base currency |
10 | | pub max_position_size: Decimal, |
11 | | /// Maximum total portfolio exposure in base currency |
12 | | pub max_portfolio_exposure: Decimal, |
13 | | /// Maximum concentration percentage for a single position (0.0-1.0) |
14 | | pub max_concentration_pct: Decimal, |
15 | | /// Maximum daily loss threshold in base currency |
16 | | pub max_daily_loss: Decimal, |
17 | | /// Maximum drawdown percentage allowed (0.0-1.0) |
18 | | pub max_drawdown_pct: Decimal, |
19 | | /// Stop loss threshold in base currency |
20 | | pub stop_loss_threshold: Decimal, |
21 | | /// VaR confidence level (e.g., 0.95 for 95%) |
22 | | pub var_confidence_level: f64, |
23 | | /// VaR time horizon in days |
24 | | pub var_time_horizon: u32, |
25 | | /// 1-day VaR limit in base currency |
26 | | pub var_limit_1d: Decimal, |
27 | | /// 10-day VaR limit in base currency |
28 | | pub var_limit_10d: Decimal, |
29 | | /// Maximum single order size in base currency |
30 | | pub max_order_size: Decimal, |
31 | | /// Maximum orders per second (rate limiting) |
32 | | pub max_orders_per_second: u64, |
33 | | /// Maximum notional value per hour in base currency |
34 | | pub max_notional_per_hour: Decimal, |
35 | | /// Kelly criterion fraction limit (0.0-1.0) |
36 | | pub kelly_fraction_limit: f64, |
37 | | /// Maximum Kelly criterion position size (0.0-1.0) |
38 | | pub max_kelly_position_size: f64, |
39 | | /// Emergency stop threshold as fraction of capital (0.0-1.0) |
40 | | pub emergency_stop_threshold: f64, |
41 | | /// VaR configuration |
42 | | pub var_config: VarConfig, |
43 | | /// Circuit breaker configuration |
44 | | pub circuit_breaker: CircuitBreakerConfig, |
45 | | /// Position limits configuration |
46 | | pub position_limits: PositionLimitsConfig, |
47 | | /// Asset classification configuration |
48 | | pub asset_classification: AssetClassificationConfig, |
49 | | } |
50 | | |
51 | | impl Default for RiskConfig { |
52 | 0 | fn default() -> Self { |
53 | 0 | Self { |
54 | 0 | // Position and exposure limits |
55 | 0 | max_position_size: Decimal::new(1_000_000, 0), // $1M max single position |
56 | 0 | max_portfolio_exposure: Decimal::new(10_000_000, 0), // $10M total portfolio exposure |
57 | 0 | max_concentration_pct: Decimal::new(25, 2), // 25% max concentration |
58 | 0 | |
59 | 0 | // Loss and drawdown limits |
60 | 0 | max_daily_loss: Decimal::new(100_000, 0), // $100K max daily loss |
61 | 0 | max_drawdown_pct: Decimal::new(15, 2), // 15% max drawdown |
62 | 0 | stop_loss_threshold: Decimal::new(50_000, 0), // $50K stop loss threshold |
63 | 0 | |
64 | 0 | // VaR configuration |
65 | 0 | var_confidence_level: 0.95, // 95% confidence |
66 | 0 | var_time_horizon: 1, // 1-day horizon |
67 | 0 | var_limit_1d: Decimal::new(50_000, 0), // $50K 1-day VaR limit |
68 | 0 | var_limit_10d: Decimal::new(150_000, 0), // $150K 10-day VaR limit |
69 | 0 | |
70 | 0 | // Order limits and rate limiting |
71 | 0 | max_order_size: Decimal::new(100_000, 0), // $100K max order size |
72 | 0 | max_orders_per_second: 100, // 100 orders/sec |
73 | 0 | max_notional_per_hour: Decimal::new(10_000_000, 0), // $10M hourly notional |
74 | 0 | |
75 | 0 | // Kelly criterion parameters |
76 | 0 | kelly_fraction_limit: 0.25, // 25% Kelly fraction limit |
77 | 0 | max_kelly_position_size: 0.20, // 20% max Kelly position |
78 | 0 | |
79 | 0 | // Emergency stop |
80 | 0 | emergency_stop_threshold: 0.10, // 10% loss triggers emergency stop |
81 | 0 | |
82 | 0 | // Nested configurations |
83 | 0 | var_config: VarConfig::default(), |
84 | 0 | circuit_breaker: CircuitBreakerConfig::default(), |
85 | 0 | position_limits: PositionLimitsConfig::default(), |
86 | 0 | asset_classification: AssetClassificationConfig::default(), |
87 | 0 | } |
88 | 0 | } |
89 | | } |
90 | | |
91 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
92 | | pub struct VarConfig { |
93 | | /// VaR confidence level (0.0-1.0) |
94 | | pub confidence_level: f64, |
95 | | /// Time horizon in days |
96 | | pub time_horizon_days: u32, |
97 | | /// Historical lookback period in days |
98 | | pub lookback_period_days: u32, |
99 | | /// Calculation method (e.g., "historical", "monte_carlo") |
100 | | pub calculation_method: String, |
101 | | /// Maximum VaR limit |
102 | | pub max_var_limit: f64, |
103 | | } |
104 | | |
105 | | impl Default for VarConfig { |
106 | 0 | fn default() -> Self { |
107 | 0 | Self { |
108 | 0 | confidence_level: 0.95, |
109 | 0 | time_horizon_days: 1, |
110 | 0 | lookback_period_days: 252, |
111 | 0 | calculation_method: "historical".to_string(), |
112 | 0 | max_var_limit: 100_000.0, |
113 | 0 | } |
114 | 0 | } |
115 | | } |
116 | | |
117 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
118 | | pub struct KellyConfig { |
119 | | pub kelly_fraction: f64, |
120 | | pub max_kelly_leverage: f64, |
121 | | pub min_kelly_leverage: f64, |
122 | | pub confidence_threshold: f64, |
123 | | pub lookback_periods: usize, |
124 | | pub default_position_fraction: f64, |
125 | | pub enabled: bool, |
126 | | pub fractional_kelly: f64, |
127 | | pub min_kelly_fraction: f64, |
128 | | pub max_kelly_fraction: f64, |
129 | | } |
130 | | |
131 | | impl Default for KellyConfig { |
132 | 0 | fn default() -> Self { |
133 | 0 | Self { |
134 | 0 | kelly_fraction: 0.25, |
135 | 0 | max_kelly_leverage: 2.0, |
136 | 0 | min_kelly_leverage: 0.1, |
137 | 0 | confidence_threshold: 0.95, |
138 | 0 | lookback_periods: 252, |
139 | 0 | default_position_fraction: 0.02, |
140 | 0 | enabled: true, |
141 | 0 | fractional_kelly: 0.5, |
142 | 0 | min_kelly_fraction: 0.01, |
143 | 0 | max_kelly_fraction: 0.5, |
144 | 0 | } |
145 | 0 | } |
146 | | } |
147 | | |
148 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
149 | | pub struct CircuitBreakerConfig { |
150 | | /// Enable circuit breaker |
151 | | pub enabled: bool, |
152 | | /// Price movement threshold to trigger halt (0.0-1.0) |
153 | | pub price_move_threshold: f64, |
154 | | /// Duration to halt trading in seconds |
155 | | pub halt_duration_seconds: u64, |
156 | | } |
157 | | |
158 | | impl Default for CircuitBreakerConfig { |
159 | 0 | fn default() -> Self { |
160 | 0 | Self { |
161 | 0 | enabled: true, |
162 | 0 | price_move_threshold: 0.05, // 5% price move |
163 | 0 | halt_duration_seconds: 300, // 5 minutes |
164 | 0 | } |
165 | 0 | } |
166 | | } |
167 | | |
168 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
169 | | pub struct PositionLimitsConfig { |
170 | | /// Global position limit |
171 | | pub global_limit: f64, |
172 | | /// Maximum leverage allowed |
173 | | pub max_leverage: f64, |
174 | | /// Maximum VaR limit |
175 | | pub max_var_limit: f64, |
176 | | } |
177 | | |
178 | | impl Default for PositionLimitsConfig { |
179 | 0 | fn default() -> Self { |
180 | 0 | Self { |
181 | 0 | global_limit: 10_000_000.0, |
182 | 0 | max_leverage: 3.0, |
183 | 0 | max_var_limit: 100_000.0, |
184 | 0 | } |
185 | 0 | } |
186 | | } |
187 | | |
188 | | /// Broker configuration for order routing and execution |
189 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
190 | | pub struct BrokerConfig { |
191 | | /// Broker routing rules based on symbol patterns and sizes |
192 | | pub routing_rules: Vec<BrokerRoutingRule>, |
193 | | /// Default broker when no rules match |
194 | | pub default_broker: String, |
195 | | /// Commission rates by broker |
196 | | pub commission_rates: HashMap<String, CommissionConfig>, |
197 | | } |
198 | | |
199 | | /// Rule for routing orders to specific brokers |
200 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
201 | | pub struct BrokerRoutingRule { |
202 | | /// Priority (higher numbers take precedence) |
203 | | pub priority: u32, |
204 | | /// Symbol pattern (regex) |
205 | | pub symbol_pattern: String, |
206 | | /// Minimum quantity for this rule |
207 | | pub min_quantity: Option<f64>, |
208 | | /// Maximum quantity for this rule |
209 | | pub max_quantity: Option<f64>, |
210 | | /// Target broker ID |
211 | | pub broker_id: String, |
212 | | /// Rule description for debugging |
213 | | pub description: String, |
214 | | } |
215 | | |
216 | | /// Commission configuration per broker |
217 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
218 | | pub struct CommissionConfig { |
219 | | /// Commission rate (basis points, e.g., 0.00007 = 0.7 bps) |
220 | | pub rate_bps: f64, |
221 | | /// Minimum commission per trade |
222 | | pub min_commission: f64, |
223 | | } |
224 | | |
225 | | impl Default for BrokerConfig { |
226 | 0 | fn default() -> Self { |
227 | 0 | let mut commission_rates = HashMap::new(); |
228 | | |
229 | 0 | commission_rates.insert( |
230 | 0 | "ICMARKETS".to_string(), |
231 | 0 | CommissionConfig { |
232 | 0 | rate_bps: 0.00007, // 0.7 bps |
233 | 0 | min_commission: 0.0, |
234 | 0 | }, |
235 | | ); |
236 | | |
237 | 0 | commission_rates.insert( |
238 | 0 | "IBKR".to_string(), |
239 | 0 | CommissionConfig { |
240 | 0 | rate_bps: 0.00005, // 0.5 bps |
241 | 0 | min_commission: 1.0, |
242 | 0 | }, |
243 | | ); |
244 | | |
245 | 0 | let routing_rules = vec![ |
246 | 0 | BrokerRoutingRule { |
247 | 0 | priority: 100, |
248 | 0 | symbol_pattern: r"^(BTC|ETH).*".to_string(), |
249 | 0 | min_quantity: None, |
250 | 0 | max_quantity: None, |
251 | 0 | broker_id: "ICMARKETS".to_string(), |
252 | 0 | description: "Route all crypto symbols to ICMarkets".to_string(), |
253 | 0 | }, |
254 | 0 | BrokerRoutingRule { |
255 | 0 | priority: 90, |
256 | 0 | symbol_pattern: r".*USD$".to_string(), |
257 | 0 | min_quantity: None, |
258 | 0 | max_quantity: Some(1_000_000.0), |
259 | 0 | broker_id: "ICMARKETS".to_string(), |
260 | 0 | description: "Route smaller USD pairs to ICMarkets".to_string(), |
261 | 0 | }, |
262 | 0 | BrokerRoutingRule { |
263 | 0 | priority: 50, |
264 | 0 | symbol_pattern: r".*".to_string(), // Catch-all |
265 | 0 | min_quantity: None, |
266 | 0 | max_quantity: None, |
267 | 0 | broker_id: "IBKR".to_string(), |
268 | 0 | description: "Default routing to IBKR".to_string(), |
269 | 0 | }, |
270 | | ]; |
271 | | |
272 | 0 | Self { |
273 | 0 | routing_rules, |
274 | 0 | default_broker: "IBKR".to_string(), |
275 | 0 | commission_rates, |
276 | 0 | } |
277 | 0 | } |
278 | | } |
279 | | |
280 | | impl BrokerConfig { |
281 | | /// Select optimal broker based on symbol and quantity using routing rules |
282 | 0 | pub fn select_broker(&self, symbol: &str, quantity: f64) -> String { |
283 | 0 | let symbol_upper = symbol.to_uppercase(); |
284 | | |
285 | | // Sort rules by priority (highest first) |
286 | 0 | let mut applicable_rules: Vec<_> = self |
287 | 0 | .routing_rules |
288 | 0 | .iter() |
289 | 0 | .filter(|rule| { |
290 | | // Check symbol pattern |
291 | 0 | let symbol_matches = if let Ok(regex) = regex::Regex::new(&rule.symbol_pattern) { |
292 | 0 | regex.is_match(&symbol_upper) |
293 | | } else { |
294 | 0 | false |
295 | | }; |
296 | | |
297 | | // Check quantity bounds |
298 | 0 | let quantity_matches = { |
299 | 0 | let min_ok = rule.min_quantity.map_or(true, |min| quantity >= min); |
300 | 0 | let max_ok = rule.max_quantity.map_or(true, |max| quantity <= max); |
301 | 0 | min_ok && max_ok |
302 | | }; |
303 | | |
304 | 0 | symbol_matches && quantity_matches |
305 | 0 | }) |
306 | 0 | .collect(); |
307 | | |
308 | 0 | applicable_rules.sort_by(|a, b| b.priority.cmp(&a.priority)); |
309 | | |
310 | 0 | if let Some(rule) = applicable_rules.first() { |
311 | 0 | rule.broker_id.clone() |
312 | | } else { |
313 | 0 | self.default_broker.clone() |
314 | | } |
315 | 0 | } |
316 | | |
317 | | /// Calculate commission for a given broker and notional value |
318 | 0 | pub fn calculate_commission(&self, broker_id: &str, notional: f64) -> f64 { |
319 | 0 | if let Some(config) = self.commission_rates.get(broker_id) { |
320 | 0 | (notional * config.rate_bps).max(config.min_commission) |
321 | | } else { |
322 | | // Default commission if broker not found |
323 | 0 | notional * 0.0001 // 1 bps |
324 | | } |
325 | 0 | } |
326 | | } |
327 | | |
328 | | /// Asset classification for risk management and volatility profiling |
329 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
330 | | pub enum AssetClass { |
331 | | /// Equity securities and stocks |
332 | | Equities, |
333 | | /// Bonds and fixed income securities |
334 | | FixedIncome, |
335 | | /// Physical and financial commodities |
336 | | Commodities, |
337 | | /// Foreign exchange and currencies |
338 | | Currencies, |
339 | | /// Alternative investments |
340 | | Alternatives, |
341 | | /// Derivative instruments |
342 | | Derivatives, |
343 | | /// Cash and cash equivalents |
344 | | Cash, |
345 | | } |
346 | | |
347 | | /// Volatility and risk profile for an asset class |
348 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
349 | | pub struct VolatilityProfile { |
350 | | /// Annual volatility (0.0 to 1.0, e.g., 0.25 = 25%) |
351 | | pub annual_volatility: f64, |
352 | | /// Maximum position size as fraction of portfolio (0.0 to 1.0) |
353 | | pub max_position_fraction: f64, |
354 | | /// Volatility threshold for risk alerts (0.0 to 1.0) |
355 | | pub volatility_threshold: f64, |
356 | | /// Maximum daily loss threshold (0.0 to 1.0) |
357 | | pub daily_loss_threshold: f64, |
358 | | } |
359 | | |
360 | | /// Asset classification configuration with symbol mappings and volatility profiles |
361 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
362 | | pub struct AssetClassificationConfig { |
363 | | /// Explicit symbol to asset class mappings |
364 | | pub symbol_mappings: HashMap<String, AssetClass>, |
365 | | /// Volatility profiles for each asset class |
366 | | pub volatility_profiles: HashMap<AssetClass, VolatilityProfile>, |
367 | | /// Pattern-based classification rules (regex patterns) |
368 | | pub pattern_rules: Vec<PatternRule>, |
369 | | } |
370 | | |
371 | | /// Pattern-based rule for asset classification |
372 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
373 | | pub struct PatternRule { |
374 | | /// Regex pattern to match against symbol |
375 | | pub pattern: String, |
376 | | /// Asset class to assign if pattern matches |
377 | | pub asset_class: AssetClass, |
378 | | /// Priority (higher numbers take precedence) |
379 | | pub priority: u32, |
380 | | } |
381 | | |
382 | | /// Encryption configuration for secure model storage |
383 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
384 | | pub struct EncryptionConfig { |
385 | | /// Enable/disable encryption for model storage |
386 | | pub enable_encryption: bool, |
387 | | /// Encryption algorithm (e.g., "AES-256-GCM") |
388 | | pub algorithm: String, |
389 | | /// Key rotation period in days |
390 | | pub key_rotation_days: u64, |
391 | | /// Vault path for encryption keys (optional, can use local keys) |
392 | | pub encryption_keys_vault_path: Option<String>, |
393 | | /// Local key file path for development/testing |
394 | | pub local_key_file: Option<String>, |
395 | | } |
396 | | |
397 | | impl Default for EncryptionConfig { |
398 | 0 | fn default() -> Self { |
399 | 0 | Self { |
400 | 0 | enable_encryption: false, |
401 | 0 | algorithm: "AES-256-GCM".to_string(), |
402 | 0 | key_rotation_days: 90, |
403 | 0 | encryption_keys_vault_path: None, |
404 | 0 | local_key_file: None, |
405 | 0 | } |
406 | 0 | } |
407 | | } |
408 | | |
409 | | impl Default for AssetClassificationConfig { |
410 | 0 | fn default() -> Self { |
411 | 0 | let mut symbol_mappings = HashMap::new(); |
412 | | |
413 | | // Equity stocks |
414 | 0 | for symbol in [ |
415 | 0 | "AAPL", "MSFT", "GOOGL", "AMZN", "META", "TSLA", "NVDA", "JPM", "JNJ", "V", |
416 | 0 | ] { |
417 | 0 | symbol_mappings.insert(symbol.to_string(), AssetClass::Equities); |
418 | 0 | } |
419 | | |
420 | | // Major cryptocurrencies |
421 | 0 | for symbol in ["BTC", "ETH", "BTCUSD", "ETHUSD", "BTCUSDT", "ETHUSDT"] { |
422 | 0 | symbol_mappings.insert(symbol.to_string(), AssetClass::Alternatives); |
423 | 0 | } |
424 | | |
425 | 0 | let mut volatility_profiles = HashMap::new(); |
426 | | |
427 | 0 | volatility_profiles.insert( |
428 | 0 | AssetClass::Equities, |
429 | 0 | VolatilityProfile { |
430 | 0 | annual_volatility: 0.25, |
431 | 0 | max_position_fraction: 0.20, |
432 | 0 | volatility_threshold: 0.025, |
433 | 0 | daily_loss_threshold: 0.03, |
434 | 0 | }, |
435 | | ); |
436 | | |
437 | 0 | volatility_profiles.insert( |
438 | 0 | AssetClass::Alternatives, |
439 | 0 | VolatilityProfile { |
440 | 0 | annual_volatility: 0.80, |
441 | 0 | max_position_fraction: 0.08, |
442 | 0 | volatility_threshold: 0.15, |
443 | 0 | daily_loss_threshold: 0.05, |
444 | 0 | }, |
445 | | ); |
446 | | |
447 | 0 | volatility_profiles.insert( |
448 | 0 | AssetClass::Currencies, |
449 | 0 | VolatilityProfile { |
450 | 0 | annual_volatility: 0.15, |
451 | 0 | max_position_fraction: 0.30, |
452 | 0 | volatility_threshold: 0.02, |
453 | 0 | daily_loss_threshold: 0.02, |
454 | 0 | }, |
455 | | ); |
456 | | |
457 | 0 | volatility_profiles.insert( |
458 | 0 | AssetClass::Cash, |
459 | 0 | VolatilityProfile { |
460 | 0 | annual_volatility: 0.01, |
461 | 0 | max_position_fraction: 1.00, |
462 | 0 | volatility_threshold: 0.001, |
463 | 0 | daily_loss_threshold: 0.001, |
464 | 0 | }, |
465 | | ); |
466 | | |
467 | 0 | volatility_profiles.insert( |
468 | 0 | AssetClass::FixedIncome, |
469 | 0 | VolatilityProfile { |
470 | 0 | annual_volatility: 0.25, |
471 | 0 | max_position_fraction: 0.15, |
472 | 0 | volatility_threshold: 0.03, |
473 | 0 | daily_loss_threshold: 0.025, |
474 | 0 | }, |
475 | | ); |
476 | | |
477 | 0 | volatility_profiles.insert( |
478 | 0 | AssetClass::Derivatives, |
479 | 0 | VolatilityProfile { |
480 | 0 | annual_volatility: 0.40, |
481 | 0 | max_position_fraction: 0.10, |
482 | 0 | volatility_threshold: 0.05, |
483 | 0 | daily_loss_threshold: 0.04, |
484 | 0 | }, |
485 | | ); |
486 | | |
487 | 0 | volatility_profiles.insert( |
488 | 0 | AssetClass::Commodities, |
489 | 0 | VolatilityProfile { |
490 | 0 | annual_volatility: 0.30, |
491 | 0 | max_position_fraction: 0.15, |
492 | 0 | volatility_threshold: 0.04, |
493 | 0 | daily_loss_threshold: 0.03, |
494 | 0 | }, |
495 | | ); |
496 | | |
497 | 0 | let pattern_rules = vec![ |
498 | 0 | PatternRule { |
499 | 0 | pattern: r"^(BTC|ETH).*".to_string(), |
500 | 0 | asset_class: AssetClass::Alternatives, |
501 | 0 | priority: 100, |
502 | 0 | }, |
503 | 0 | PatternRule { |
504 | 0 | pattern: r".*USD$".to_string(), |
505 | 0 | asset_class: AssetClass::Currencies, |
506 | 0 | priority: 80, |
507 | 0 | }, |
508 | 0 | PatternRule { |
509 | 0 | pattern: r".*JPY$".to_string(), |
510 | 0 | asset_class: AssetClass::Currencies, |
511 | 0 | priority: 90, |
512 | 0 | }, |
513 | 0 | PatternRule { |
514 | 0 | pattern: r"^[A-Z]{3,6}$".to_string(), // 3-6 letter symbols (likely equities) |
515 | 0 | asset_class: AssetClass::Equities, |
516 | 0 | priority: 50, |
517 | 0 | }, |
518 | | ]; |
519 | | |
520 | 0 | Self { |
521 | 0 | symbol_mappings, |
522 | 0 | volatility_profiles, |
523 | 0 | pattern_rules, |
524 | 0 | } |
525 | 0 | } |
526 | | } |
527 | | |
528 | | impl AssetClassificationConfig { |
529 | | /// Classify a symbol based on explicit mappings and pattern rules |
530 | 0 | pub fn classify_symbol(&self, symbol: &str) -> AssetClass { |
531 | 0 | let symbol_upper = symbol.to_uppercase(); |
532 | | |
533 | | // First check explicit mappings |
534 | 0 | if let Some(asset_class) = self.symbol_mappings.get(&symbol_upper) { |
535 | 0 | return asset_class.clone(); |
536 | 0 | } |
537 | | |
538 | | // Then check pattern rules (sorted by priority, highest first) |
539 | 0 | let mut applicable_rules: Vec<_> = self |
540 | 0 | .pattern_rules |
541 | 0 | .iter() |
542 | 0 | .filter(|rule| { |
543 | 0 | if let Ok(regex) = regex::Regex::new(&rule.pattern) { |
544 | 0 | regex.is_match(&symbol_upper) |
545 | | } else { |
546 | 0 | false |
547 | | } |
548 | 0 | }) |
549 | 0 | .collect(); |
550 | | |
551 | 0 | applicable_rules.sort_by(|a, b| b.priority.cmp(&a.priority)); |
552 | | |
553 | 0 | if let Some(rule) = applicable_rules.first() { |
554 | 0 | rule.asset_class.clone() |
555 | | } else { |
556 | 0 | AssetClass::Cash // Default fallback for unknown symbols |
557 | | } |
558 | 0 | } |
559 | | |
560 | | /// Get volatility profile for a symbol |
561 | 0 | pub fn get_volatility_profile(&self, symbol: &str) -> VolatilityProfile { |
562 | 0 | let asset_class = self.classify_symbol(symbol); |
563 | 0 | self.volatility_profiles |
564 | 0 | .get(&asset_class) |
565 | 0 | .cloned() |
566 | 0 | .unwrap_or(VolatilityProfile { |
567 | 0 | annual_volatility: 0.20, |
568 | 0 | max_position_fraction: 0.05, |
569 | 0 | volatility_threshold: 0.02, |
570 | 0 | daily_loss_threshold: 0.01, |
571 | 0 | }) |
572 | 0 | } |
573 | | |
574 | | /// Get daily volatility for a symbol |
575 | 0 | pub fn get_daily_volatility(&self, symbol: &str) -> f64 { |
576 | 0 | let profile = self.get_volatility_profile(symbol); |
577 | 0 | profile.annual_volatility / 252.0_f64.sqrt() |
578 | 0 | } |
579 | | |
580 | | /// Get risk configuration tuple (position_fraction, volatility_threshold, daily_loss_threshold) |
581 | 0 | pub fn get_risk_config(&self, symbol: &str) -> (f64, f64, f64) { |
582 | 0 | let profile = self.get_volatility_profile(symbol); |
583 | 0 | ( |
584 | 0 | profile.max_position_fraction, |
585 | 0 | profile.volatility_threshold, |
586 | 0 | profile.daily_loss_threshold, |
587 | 0 | ) |
588 | 0 | } |
589 | | } |
590 | | |
591 | | /// Configuration for backtesting database connections |
592 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
593 | | pub struct BacktestingDatabaseConfig { |
594 | | /// Database connection URL |
595 | | pub database_url: String, |
596 | | /// Maximum number of database connections in the pool |
597 | | pub max_connections: Option<u32>, |
598 | | /// Minimum number of database connections in the pool |
599 | | pub min_connections: Option<u32>, |
600 | | /// Timeout in milliseconds for acquiring a connection |
601 | | pub acquire_timeout_ms: Option<u64>, |
602 | | /// Statement cache capacity |
603 | | pub statement_cache_capacity: Option<usize>, |
604 | | /// Enable SQL query logging |
605 | | pub enable_logging: Option<bool>, |
606 | | } |
607 | | |
608 | | /// Configuration for backtesting strategy execution |
609 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
610 | | pub struct BacktestingStrategyConfig { |
611 | | /// Commission rate for trades (e.g., 0.001 = 0.1%) |
612 | | pub commission_rate: f64, |
613 | | /// Slippage rate for trades (e.g., 0.0005 = 0.05%) |
614 | | pub slippage_rate: f64, |
615 | | /// Maximum position size as fraction of portfolio |
616 | | pub max_position_size: Option<f64>, |
617 | | /// Enable short selling |
618 | | pub allow_short_selling: Option<bool>, |
619 | | } |
620 | | |
621 | | impl Default for BacktestingStrategyConfig { |
622 | 0 | fn default() -> Self { |
623 | 0 | Self { |
624 | 0 | commission_rate: 0.0007, // 0.07% = 7 bps |
625 | 0 | slippage_rate: 0.0002, // 0.02% = 2 bps |
626 | 0 | max_position_size: Some(0.2), // 20% max position |
627 | 0 | allow_short_selling: Some(false), |
628 | 0 | } |
629 | 0 | } |
630 | | } |
631 | | |
632 | | /// Configuration for backtesting performance analysis |
633 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
634 | | pub struct BacktestingPerformanceConfig { |
635 | | /// Risk-free rate for Sharpe ratio calculations (annual rate) |
636 | | pub risk_free_rate: f64, |
637 | | /// Resolution for equity curve (number of points) |
638 | | pub equity_curve_resolution: usize, |
639 | | /// Enable advanced performance metrics |
640 | | pub enable_advanced_metrics: Option<bool>, |
641 | | } |
642 | | |
643 | | impl Default for BacktestingPerformanceConfig { |
644 | 0 | fn default() -> Self { |
645 | 0 | Self { |
646 | 0 | risk_free_rate: 0.04, // 4% annual risk-free rate |
647 | 0 | equity_curve_resolution: 1000, |
648 | 0 | enable_advanced_metrics: Some(true), |
649 | 0 | } |
650 | 0 | } |
651 | | } |
652 | | |
653 | | /// TLS/SSL configuration for secure gRPC connections |
654 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
655 | | pub struct TlsConfig { |
656 | | /// Enable/disable TLS for gRPC connections |
657 | | pub enabled: bool, |
658 | | /// Path to server certificate file |
659 | | pub cert_path: String, |
660 | | /// Path to server private key file |
661 | | pub key_path: String, |
662 | | /// Path to CA certificate for client verification (optional) |
663 | | pub ca_cert_path: Option<String>, |
664 | | /// Require client certificate verification |
665 | | pub require_client_cert: bool, |
666 | | /// TLS protocol versions to support (e.g., ["TLSv1.2", "TLSv1.3"]) |
667 | | pub protocol_versions: Vec<String>, |
668 | | /// Cipher suites to use (empty means default) |
669 | | pub cipher_suites: Vec<String>, |
670 | | } |
671 | | |
672 | | impl Default for TlsConfig { |
673 | 0 | fn default() -> Self { |
674 | | // Wave 75 Fix: Use environment variables with fallback to /tmp instead of /etc |
675 | 0 | let cert_path = std::env::var("TLS_CERT_PATH") |
676 | 0 | .unwrap_or_else(|_| "/tmp/foxhunt/certs/server.crt".to_string()); |
677 | 0 | let key_path = std::env::var("TLS_KEY_PATH") |
678 | 0 | .unwrap_or_else(|_| "/tmp/foxhunt/certs/server.key".to_string()); |
679 | 0 | let ca_cert_path = std::env::var("TLS_CA_PATH").ok(); |
680 | | |
681 | 0 | Self { |
682 | 0 | enabled: false, |
683 | 0 | cert_path, |
684 | 0 | key_path, |
685 | 0 | ca_cert_path, |
686 | 0 | require_client_cert: false, |
687 | 0 | protocol_versions: vec!["TLSv1.3".to_string()], |
688 | 0 | cipher_suites: Vec::new(), |
689 | 0 | } |
690 | 0 | } |
691 | | } |
692 | | |
693 | | /// Trading system configuration |
694 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
695 | | pub struct TradingConfig { |
696 | | /// Maximum order size (in base units) |
697 | | pub max_order_size: f64, |
698 | | /// Minimum order size (in base units) |
699 | | pub min_order_size: f64, |
700 | | /// Maximum price deviation from market (as fraction, e.g., 0.05 = 5%) |
701 | | pub max_price_deviation: f64, |
702 | | /// Enable symbol validation |
703 | | pub enable_symbol_validation: bool, |
704 | | /// Maximum batch notional value (total value of orders in a batch) |
705 | | pub max_batch_notional: f64, |
706 | | /// Maximum position VaR (Value at Risk) limit |
707 | | pub max_position_var: f64, |
708 | | } |
709 | | |
710 | | impl Default for TradingConfig { |
711 | 0 | fn default() -> Self { |
712 | 0 | Self { |
713 | 0 | max_order_size: 1_000_000.0, |
714 | 0 | min_order_size: 0.001, |
715 | 0 | max_price_deviation: 0.05, |
716 | 0 | enable_symbol_validation: false, |
717 | 0 | max_batch_notional: 10_000_000.0, // $10M batch limit |
718 | 0 | max_position_var: 50_000.0, // $50K VaR limit |
719 | 0 | } |
720 | 0 | } |
721 | | } |
722 | | |
723 | | /// Market data ingestion configuration |
724 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
725 | | pub struct MarketDataConfig { |
726 | | /// Market data server host |
727 | | pub host: String, |
728 | | /// WebSocket port for streaming data |
729 | | pub websocket_port: u16, |
730 | | /// API key for authentication |
731 | | pub api_key: String, |
732 | | /// Use SSL/TLS for connections |
733 | | pub use_ssl: bool, |
734 | | /// Connection timeout in seconds |
735 | | pub timeout_seconds: u64, |
736 | | } |
737 | | |
738 | | impl Default for MarketDataConfig { |
739 | 0 | fn default() -> Self { |
740 | 0 | Self { |
741 | 0 | host: "localhost".to_string(), |
742 | 0 | websocket_port: 8080, |
743 | 0 | api_key: String::new(), |
744 | 0 | use_ssl: false, |
745 | 0 | timeout_seconds: 30, |
746 | 0 | } |
747 | 0 | } |
748 | | } |