/home/jgrusewski/Work/foxhunt/config/src/asset_classification.rs
Line | Count | Source |
1 | | //! Comprehensive Asset Classification Configuration System |
2 | | //! |
3 | | //! This module provides production-ready asset classification capabilities with: |
4 | | //! - Sophisticated asset class hierarchies |
5 | | //! - Dynamic trading parameter configuration |
6 | | //! - Pattern-based symbol matching with regex support |
7 | | //! - Database-backed configuration with hot-reload |
8 | | //! - Volatility profiling and risk management integration |
9 | | |
10 | | use chrono::{DateTime, Datelike, NaiveTime, Utc}; |
11 | | use log; |
12 | | use regex::Regex; |
13 | | use rust_decimal::{prelude::FromPrimitive, Decimal}; |
14 | | use serde::{Deserialize, Serialize}; |
15 | | use std::collections::HashMap; |
16 | | use uuid::Uuid; |
17 | | |
18 | | /// Comprehensive asset classification enum with detailed sub-categories |
19 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
20 | | pub enum AssetClass { |
21 | | /// Equity instruments with sector-specific characteristics |
22 | | Equity { |
23 | | sector: EquitySector, |
24 | | market_cap: MarketCapTier, |
25 | | region: GeographicRegion, |
26 | | }, |
27 | | /// Futures contracts with underlying asset classification |
28 | | Future { |
29 | | underlying: FutureType, |
30 | | expiry_type: ExpiryType, |
31 | | exchange: String, |
32 | | }, |
33 | | /// Foreign exchange pairs with specific characteristics |
34 | | Forex { |
35 | | base: String, |
36 | | quote: String, |
37 | | pair_type: ForexPairType, |
38 | | }, |
39 | | /// Cryptocurrency assets with network and type classification |
40 | | Crypto { |
41 | | network: String, |
42 | | crypto_type: CryptoType, |
43 | | market_cap_rank: Option<u32>, |
44 | | }, |
45 | | /// Commodity instruments with category classification |
46 | | Commodity { |
47 | | category: CommodityType, |
48 | | storage_type: StorageType, |
49 | | }, |
50 | | /// Fixed income securities |
51 | | FixedIncome { |
52 | | instrument_type: FixedIncomeType, |
53 | | credit_rating: CreditRating, |
54 | | maturity: MaturityBucket, |
55 | | }, |
56 | | /// Derivatives and structured products |
57 | | Derivative { |
58 | | underlying_class: Box<AssetClass>, |
59 | | derivative_type: DerivativeType, |
60 | | }, |
61 | | /// Unknown or unclassified assets (conservative defaults) |
62 | | Unknown, |
63 | | } |
64 | | |
65 | | /// Equity sector classifications aligned with industry standards |
66 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
67 | | pub enum EquitySector { |
68 | | Technology, |
69 | | Healthcare, |
70 | | Financial, |
71 | | ConsumerDiscretionary, |
72 | | ConsumerStaples, |
73 | | Industrial, |
74 | | Energy, |
75 | | Materials, |
76 | | Utilities, |
77 | | RealEstate, |
78 | | CommunicationServices, |
79 | | } |
80 | | |
81 | | /// Market capitalization tiers for equity classification |
82 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
83 | | pub enum MarketCapTier { |
84 | | LargeCap, // > $10B |
85 | | MidCap, // $2B - $10B |
86 | | SmallCap, // $300M - $2B |
87 | | MicroCap, // < $300M |
88 | | } |
89 | | |
90 | | /// Geographic regions for asset classification |
91 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
92 | | pub enum GeographicRegion { |
93 | | NorthAmerica, |
94 | | Europe, |
95 | | Asia, |
96 | | EmergingMarkets, |
97 | | Global, |
98 | | } |
99 | | |
100 | | /// Future contract underlying asset types |
101 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
102 | | pub enum FutureType { |
103 | | Equity, |
104 | | Currency, |
105 | | Commodity, |
106 | | Interest, |
107 | | Volatility, |
108 | | } |
109 | | |
110 | | /// Futures expiry categorization |
111 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
112 | | pub enum ExpiryType { |
113 | | Weekly, |
114 | | Monthly, |
115 | | Quarterly, |
116 | | Annual, |
117 | | } |
118 | | |
119 | | /// Forex pair type classification |
120 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
121 | | pub enum ForexPairType { |
122 | | Major, // EUR/USD, GBP/USD, USD/JPY, etc. |
123 | | Minor, // Cross-currency pairs without USD |
124 | | Exotic, // Emerging market currencies |
125 | | JPYPair, // Special handling for JPY pairs |
126 | | } |
127 | | |
128 | | /// Cryptocurrency type classification |
129 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
130 | | pub enum CryptoType { |
131 | | Bitcoin, |
132 | | Ethereum, |
133 | | Stablecoin, |
134 | | AltcoinMajor, // Top 20 market cap |
135 | | AltcoinMinor, // Beyond top 20 |
136 | | DeFi, |
137 | | GameFi, |
138 | | Meme, |
139 | | } |
140 | | |
141 | | /// Commodity categories |
142 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
143 | | pub enum CommodityType { |
144 | | PreciousMetals, |
145 | | Energy, |
146 | | Agricultural, |
147 | | IndustrialMetals, |
148 | | Livestock, |
149 | | } |
150 | | |
151 | | /// Storage characteristics for commodities |
152 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
153 | | pub enum StorageType { |
154 | | Physical, |
155 | | Financial, |
156 | | } |
157 | | |
158 | | /// Fixed income instrument types |
159 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
160 | | pub enum FixedIncomeType { |
161 | | Government, |
162 | | Corporate, |
163 | | Municipal, |
164 | | InflationProtected, |
165 | | } |
166 | | |
167 | | /// Credit rating classifications |
168 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
169 | | pub enum CreditRating { |
170 | | AAA, |
171 | | AA, |
172 | | A, |
173 | | BBB, |
174 | | BB, |
175 | | B, |
176 | | CCC, |
177 | | Unrated, |
178 | | } |
179 | | |
180 | | /// Maturity buckets for fixed income |
181 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
182 | | pub enum MaturityBucket { |
183 | | ShortTerm, // < 2 years |
184 | | MediumTerm, // 2-10 years |
185 | | LongTerm, // > 10 years |
186 | | } |
187 | | |
188 | | /// Derivative instrument types |
189 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
190 | | pub enum DerivativeType { |
191 | | Option, |
192 | | Swap, |
193 | | Forward, |
194 | | Structured, |
195 | | } |
196 | | |
197 | | /// Comprehensive volatility profile with regime-aware parameters |
198 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
199 | | pub struct VolatilityProfile { |
200 | | /// Base annual volatility (standard market conditions) |
201 | | pub base_annual_volatility: f64, |
202 | | /// Stress volatility multiplier for high-stress periods |
203 | | pub stress_volatility_multiplier: f64, |
204 | | /// Intraday volatility pattern (hourly multipliers) |
205 | | pub intraday_pattern: Vec<f64>, |
206 | | /// Volatility clustering parameter (GARCH-like) |
207 | | pub volatility_persistence: f64, |
208 | | /// Jump risk probability and magnitude |
209 | | pub jump_risk: JumpRiskProfile, |
210 | | } |
211 | | |
212 | | /// Jump risk characteristics |
213 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
214 | | pub struct JumpRiskProfile { |
215 | | /// Probability of large price jumps per day |
216 | | pub jump_probability: f64, |
217 | | /// Average magnitude of jumps (as fraction of price) |
218 | | pub jump_magnitude: f64, |
219 | | /// Maximum expected jump size |
220 | | pub max_jump_size: f64, |
221 | | } |
222 | | |
223 | | /// Dynamic trading parameters that adapt to market conditions |
224 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
225 | | pub struct TradingParameters { |
226 | | /// Position sizing constraints |
227 | | pub position_limits: PositionLimits, |
228 | | /// Risk management thresholds |
229 | | pub risk_thresholds: RiskThresholds, |
230 | | /// Execution parameters |
231 | | pub execution_config: ExecutionConfig, |
232 | | /// Market making parameters (if applicable) |
233 | | pub market_making: Option<MarketMakingConfig>, |
234 | | } |
235 | | |
236 | | /// Position sizing and exposure limits |
237 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
238 | | pub struct PositionLimits { |
239 | | /// Maximum position size as fraction of portfolio NAV |
240 | | pub max_position_fraction: f64, |
241 | | /// Maximum leverage allowed for this asset |
242 | | pub max_leverage: f64, |
243 | | /// Concentration limit (max % of total positions in this asset class) |
244 | | pub concentration_limit: f64, |
245 | | /// Minimum position size (to avoid micro-positions) |
246 | | pub min_position_size: Decimal, |
247 | | } |
248 | | |
249 | | /// Risk management thresholds and limits |
250 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
251 | | pub struct RiskThresholds { |
252 | | /// VaR limit as fraction of portfolio |
253 | | pub var_limit: f64, |
254 | | /// Daily loss limit |
255 | | pub daily_loss_limit: f64, |
256 | | /// Stop-loss threshold |
257 | | pub stop_loss_threshold: f64, |
258 | | /// Volatility circuit breaker threshold |
259 | | pub volatility_circuit_breaker: f64, |
260 | | /// Maximum drawdown before position reduction |
261 | | pub max_drawdown_threshold: f64, |
262 | | } |
263 | | |
264 | | /// Execution configuration parameters |
265 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
266 | | pub struct ExecutionConfig { |
267 | | /// Preferred order types for this asset |
268 | | pub preferred_order_types: Vec<OrderType>, |
269 | | /// Tick size for price increments |
270 | | pub tick_size: Decimal, |
271 | | /// Minimum order size |
272 | | pub min_order_size: Decimal, |
273 | | /// Maximum order size before breaking up |
274 | | pub max_order_size: Decimal, |
275 | | /// Execution time constraints |
276 | | pub time_in_force_default: TimeInForce, |
277 | | /// Slippage tolerance |
278 | | pub slippage_tolerance: f64, |
279 | | } |
280 | | |
281 | | /// Market making specific configuration |
282 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
283 | | pub struct MarketMakingConfig { |
284 | | /// Bid-ask spread targets |
285 | | pub target_spread: f64, |
286 | | /// Inventory limits |
287 | | pub max_inventory: Decimal, |
288 | | /// Quote size |
289 | | pub quote_size: Decimal, |
290 | | /// Refresh frequency |
291 | | pub refresh_frequency: std::time::Duration, |
292 | | } |
293 | | |
294 | | /// Order type enumeration |
295 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
296 | | pub enum OrderType { |
297 | | Market, |
298 | | Limit, |
299 | | Stop, |
300 | | StopLimit, |
301 | | Hidden, |
302 | | Iceberg, |
303 | | } |
304 | | |
305 | | /// Time in force options |
306 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
307 | | pub enum TimeInForce { |
308 | | Day, |
309 | | GoodTillCancel, |
310 | | ImmediateOrCancel, |
311 | | FillOrKill, |
312 | | GTD, // Good Till Date |
313 | | } |
314 | | |
315 | | /// Symbol pattern matching configuration with compiled regex |
316 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
317 | | pub struct AssetConfig { |
318 | | /// UUID for database storage |
319 | | pub id: Uuid, |
320 | | /// Human-readable name for this configuration |
321 | | pub name: String, |
322 | | /// Regex pattern for symbol matching |
323 | | pub symbol_pattern: String, |
324 | | /// Compiled regex (not serialized, rebuilt on load) |
325 | | #[serde(skip)] |
326 | | pub compiled_pattern: Option<Regex>, |
327 | | /// Asset class classification |
328 | | pub asset_class: AssetClass, |
329 | | /// Volatility profile |
330 | | pub volatility_profile: VolatilityProfile, |
331 | | /// Trading parameters |
332 | | pub trading_parameters: TradingParameters, |
333 | | /// Priority for pattern matching (higher = checked first) |
334 | | pub priority: u32, |
335 | | /// Whether this configuration is active |
336 | | pub is_active: bool, |
337 | | /// Creation timestamp |
338 | | pub created_at: DateTime<Utc>, |
339 | | /// Last update timestamp |
340 | | pub updated_at: DateTime<Utc>, |
341 | | /// Trading hours (if applicable) |
342 | | pub trading_hours: Option<TradingHours>, |
343 | | /// Settlement details |
344 | | pub settlement_config: SettlementConfig, |
345 | | } |
346 | | |
347 | | /// Trading hours configuration |
348 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
349 | | pub struct TradingHours { |
350 | | /// Regular trading session start |
351 | | pub market_open: NaiveTime, |
352 | | /// Regular trading session end |
353 | | pub market_close: NaiveTime, |
354 | | /// Pre-market session (if available) |
355 | | pub pre_market: Option<(NaiveTime, NaiveTime)>, |
356 | | /// After-hours session (if available) |
357 | | pub after_hours: Option<(NaiveTime, NaiveTime)>, |
358 | | /// Timezone for these hours |
359 | | pub timezone: String, |
360 | | /// Days of week when trading is active (0=Sunday, 6=Saturday) |
361 | | pub trading_days: Vec<u8>, |
362 | | } |
363 | | |
364 | | /// Settlement configuration |
365 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
366 | | pub struct SettlementConfig { |
367 | | /// Settlement period (T+n days) |
368 | | pub settlement_days: u32, |
369 | | /// Settlement currency |
370 | | pub settlement_currency: String, |
371 | | /// Whether physical delivery is possible |
372 | | pub physical_settlement: bool, |
373 | | } |
374 | | |
375 | | /// Asset classification manager with caching and hot-reload capabilities |
376 | | pub struct AssetClassificationManager { |
377 | | /// Asset configurations indexed by priority |
378 | | configs: Vec<AssetConfig>, |
379 | | /// Explicit symbol mappings for fast lookup |
380 | | symbol_cache: HashMap<String, AssetClass>, |
381 | | /// Last configuration reload timestamp |
382 | | last_reload: DateTime<Utc>, |
383 | | /// Configuration reload interval |
384 | | reload_interval: std::time::Duration, |
385 | | } |
386 | | |
387 | | impl AssetClassificationManager { |
388 | | /// Create a new asset classification manager |
389 | 17 | pub fn new() -> Self { |
390 | 17 | Self { |
391 | 17 | configs: Vec::new(), |
392 | 17 | symbol_cache: HashMap::new(), |
393 | 17 | last_reload: Utc::now(), |
394 | 17 | reload_interval: std::time::Duration::from_secs(300), // 5 minutes |
395 | 17 | } |
396 | 17 | } |
397 | | |
398 | | /// Load configurations from database |
399 | 14 | pub async fn load_configurations( |
400 | 14 | &mut self, |
401 | 14 | configs: Vec<AssetConfig>, |
402 | 14 | ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
403 | 14 | self.configs = configs; |
404 | | // Sort by priority (highest first) |
405 | 23 | self.configs14 .sort_by14 (|a, b| b.priority.cmp(&a.priority)); |
406 | | |
407 | | // Compile regex patterns |
408 | 51 | for config37 in &mut self.configs { |
409 | 37 | match Regex::new(&config.symbol_pattern) { |
410 | 36 | Ok(regex) => config.compiled_pattern = Some(regex), |
411 | 1 | Err(e) => { |
412 | 1 | log::warn!( |
413 | 0 | "Failed to compile regex pattern '{}': {}", |
414 | | config.symbol_pattern, |
415 | | e |
416 | | ); |
417 | 1 | config.is_active = false; |
418 | | } |
419 | | } |
420 | | } |
421 | | |
422 | 14 | self.last_reload = Utc::now(); |
423 | 14 | log::info!( |
424 | 0 | "Loaded {} asset classification configurations", |
425 | 0 | self.configs.len() |
426 | | ); |
427 | 14 | Ok(()) |
428 | 14 | } |
429 | | |
430 | | /// Classify a symbol using the configured rules |
431 | 15 | pub fn classify_symbol(&self, symbol: &str) -> AssetClass { |
432 | 15 | let symbol_upper = symbol.to_uppercase(); |
433 | | |
434 | | // Check cache first |
435 | 15 | if let Some(asset_class0 ) = self.symbol_cache.get(&symbol_upper) { |
436 | 0 | return asset_class.clone(); |
437 | 15 | } |
438 | | |
439 | | // Check pattern rules in priority order |
440 | 31 | for config28 in &self.configs { |
441 | 28 | if !config.is_active { |
442 | 0 | continue; |
443 | 28 | } |
444 | | |
445 | 28 | if let Some(ref regex) = config.compiled_pattern { |
446 | 28 | if regex.is_match(&symbol_upper) { |
447 | 12 | return config.asset_class.clone(); |
448 | 16 | } |
449 | 0 | } |
450 | | } |
451 | | |
452 | 3 | AssetClass::Unknown |
453 | 15 | } |
454 | | |
455 | | /// Get complete asset configuration for a symbol |
456 | 27 | pub fn get_asset_config(&self, symbol: &str) -> Option<&AssetConfig> { |
457 | 27 | let symbol_upper = symbol.to_uppercase(); |
458 | | |
459 | 43 | for config41 in &self.configs { |
460 | 41 | if !config.is_active { |
461 | 0 | continue; |
462 | 41 | } |
463 | | |
464 | 41 | if let Some(ref regex) = config.compiled_pattern { |
465 | 41 | if regex.is_match(&symbol_upper) { |
466 | 25 | return Some(config); |
467 | 16 | } |
468 | 0 | } |
469 | | } |
470 | | |
471 | 2 | None |
472 | 27 | } |
473 | | |
474 | | /// Get volatility profile for a symbol |
475 | 11 | pub fn get_volatility_profile(&self, symbol: &str) -> Option<&VolatilityProfile> { |
476 | 11 | self.get_asset_config(symbol) |
477 | 11 | .map(|config| &config.volatility_profile) |
478 | 11 | } |
479 | | |
480 | | /// Get trading parameters for a symbol |
481 | 9 | pub fn get_trading_parameters(&self, symbol: &str) -> Option<&TradingParameters> { |
482 | 9 | self.get_asset_config(symbol) |
483 | 9 | .map(|config| &config.trading_parameters) |
484 | 9 | } |
485 | | |
486 | | /// Get daily volatility estimate for a symbol |
487 | 9 | pub fn get_daily_volatility(&self, symbol: &str) -> f64 { |
488 | 9 | if let Some(profile8 ) = self.get_volatility_profile(symbol) { |
489 | 8 | profile.base_annual_volatility / 252.0_f64.sqrt() |
490 | | } else { |
491 | 1 | 0.5 / 252.0_f64.sqrt() // Default high volatility |
492 | | } |
493 | 9 | } |
494 | | |
495 | | /// Get position sizing recommendation |
496 | 5 | pub fn get_position_size_recommendation( |
497 | 5 | &self, |
498 | 5 | symbol: &str, |
499 | 5 | portfolio_nav: Decimal, |
500 | 5 | ) -> Option<Decimal> { |
501 | 5 | if let Some(config) = self.get_asset_config(symbol) { |
502 | 5 | let max_fraction = config |
503 | 5 | .trading_parameters |
504 | 5 | .position_limits |
505 | 5 | .max_position_fraction; |
506 | 5 | if let Some(decimal_fraction) = Decimal::from_f64(max_fraction) { |
507 | 5 | Some(portfolio_nav * decimal_fraction) |
508 | | } else { |
509 | 0 | Some(Decimal::ZERO) |
510 | | } |
511 | | } else { |
512 | 0 | None |
513 | | } |
514 | 5 | } |
515 | | |
516 | | /// Check if symbol is within trading hours |
517 | 2 | pub fn is_trading_active(&self, symbol: &str, timestamp: DateTime<Utc>) -> bool { |
518 | 2 | if let Some(config) = self.get_asset_config(symbol) { |
519 | 2 | if let Some(ref trading_hours1 ) = config.trading_hours { |
520 | | // Simplified check - in production would need proper timezone handling |
521 | 1 | let weekday = timestamp.weekday().num_days_from_sunday() as u8; |
522 | 1 | trading_hours.trading_days.contains(&weekday) |
523 | | } else { |
524 | 1 | true // No trading hours restriction |
525 | | } |
526 | | } else { |
527 | 0 | true // Default to always active for unknown symbols |
528 | | } |
529 | 2 | } |
530 | | |
531 | | /// Add explicit symbol mapping to cache |
532 | 0 | pub fn cache_symbol_mapping(&mut self, symbol: String, asset_class: AssetClass) { |
533 | 0 | self.symbol_cache.insert(symbol.to_uppercase(), asset_class); |
534 | 0 | } |
535 | | |
536 | | /// Clear symbol cache |
537 | 0 | pub fn clear_cache(&mut self) { |
538 | 0 | self.symbol_cache.clear(); |
539 | 0 | } |
540 | | |
541 | | /// Check if configuration needs reload |
542 | 0 | pub fn needs_reload(&self) -> bool { |
543 | 0 | Utc::now().signed_duration_since(self.last_reload) |
544 | 0 | > chrono::Duration::from_std(self.reload_interval).unwrap_or_default() |
545 | 0 | } |
546 | | |
547 | | /// Get all active configurations |
548 | 2 | pub fn get_active_configurations(&self) -> Vec<&AssetConfig> { |
549 | 2 | self.configs |
550 | 2 | .iter() |
551 | 2 | .filter(|config| config.is_active) |
552 | 2 | .collect() |
553 | 2 | } |
554 | | |
555 | | /// Get configurations by asset class |
556 | 0 | pub fn get_configurations_by_class(&self, asset_class: &AssetClass) -> Vec<&AssetConfig> { |
557 | 0 | self.configs |
558 | 0 | .iter() |
559 | 0 | .filter(|config| config.is_active && &config.asset_class == asset_class) |
560 | 0 | .collect() |
561 | 0 | } |
562 | | } |
563 | | |
564 | | impl Default for AssetClassificationManager { |
565 | 0 | fn default() -> Self { |
566 | 0 | Self::new() |
567 | 0 | } |
568 | | } |
569 | | |
570 | | /// Create default asset configurations for common instruments |
571 | 11 | pub fn create_default_configurations() -> Vec<AssetConfig> { |
572 | 11 | let mut configs = Vec::new(); |
573 | 11 | let now = Utc::now(); |
574 | | |
575 | | // Blue chip US equities |
576 | 11 | configs.push(AssetConfig { |
577 | 11 | id: Uuid::new_v4(), |
578 | 11 | name: "Blue Chip US Equities".to_string(), |
579 | 11 | symbol_pattern: "^(AAPL|MSFT|GOOGL|AMZN|META|TSLA|NVDA|JPM|JNJ|V|PG|UNH|HD|BAC|DIS|MA|NFLX|CRM|ADBE|PYPL|INTC|CMCSA|PFE|T|VZ|MRK|WMT|KO|NKE|CVX|XOM)$".to_string(), |
580 | 11 | compiled_pattern: None, |
581 | 11 | asset_class: AssetClass::Equity { |
582 | 11 | sector: EquitySector::Technology, |
583 | 11 | market_cap: MarketCapTier::LargeCap, |
584 | 11 | region: GeographicRegion::NorthAmerica, |
585 | 11 | }, |
586 | 11 | volatility_profile: VolatilityProfile { |
587 | 11 | base_annual_volatility: 0.25, |
588 | 11 | stress_volatility_multiplier: 2.0, |
589 | 11 | intraday_pattern: vec![1.0; 24], // Flat pattern for simplicity |
590 | 11 | volatility_persistence: 0.85, |
591 | 11 | jump_risk: JumpRiskProfile { |
592 | 11 | jump_probability: 0.02, |
593 | 11 | jump_magnitude: 0.05, |
594 | 11 | max_jump_size: 0.15, |
595 | 11 | }, |
596 | 11 | }, |
597 | 11 | trading_parameters: TradingParameters { |
598 | 11 | position_limits: PositionLimits { |
599 | 11 | max_position_fraction: 0.20, |
600 | 11 | max_leverage: 2.0, |
601 | 11 | concentration_limit: 0.30, |
602 | 11 | min_position_size: Decimal::from(100), |
603 | 11 | }, |
604 | 11 | risk_thresholds: RiskThresholds { |
605 | 11 | var_limit: 0.05, |
606 | 11 | daily_loss_limit: 0.03, |
607 | 11 | stop_loss_threshold: 0.10, |
608 | 11 | volatility_circuit_breaker: 0.05, |
609 | 11 | max_drawdown_threshold: 0.15, |
610 | 11 | }, |
611 | 11 | execution_config: ExecutionConfig { |
612 | 11 | preferred_order_types: vec![OrderType::Limit, OrderType::Market], |
613 | 11 | tick_size: "0.01".parse().unwrap(), |
614 | 11 | min_order_size: Decimal::from(1), |
615 | 11 | max_order_size: Decimal::from(10000), |
616 | 11 | time_in_force_default: TimeInForce::Day, |
617 | 11 | slippage_tolerance: 0.001, |
618 | 11 | }, |
619 | 11 | market_making: None, |
620 | 11 | }, |
621 | 11 | priority: 100, |
622 | 11 | is_active: true, |
623 | 11 | created_at: now, |
624 | 11 | updated_at: now, |
625 | 11 | trading_hours: Some(TradingHours { |
626 | 11 | market_open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(), |
627 | 11 | market_close: NaiveTime::from_hms_opt(16, 0, 0).unwrap(), |
628 | 11 | pre_market: Some((NaiveTime::from_hms_opt(4, 0, 0).unwrap(), NaiveTime::from_hms_opt(9, 30, 0).unwrap())), |
629 | 11 | after_hours: Some((NaiveTime::from_hms_opt(16, 0, 0).unwrap(), NaiveTime::from_hms_opt(20, 0, 0).unwrap())), |
630 | 11 | timezone: "America/New_York".to_string(), |
631 | 11 | trading_days: vec![1, 2, 3, 4, 5], // Monday-Friday |
632 | 11 | }), |
633 | 11 | settlement_config: SettlementConfig { |
634 | 11 | settlement_days: 2, |
635 | 11 | settlement_currency: "USD".to_string(), |
636 | 11 | physical_settlement: false, |
637 | 11 | }, |
638 | 11 | }); |
639 | | |
640 | | // Major cryptocurrency pairs |
641 | 11 | configs.push(AssetConfig { |
642 | 11 | id: Uuid::new_v4(), |
643 | 11 | name: "Major Cryptocurrencies".to_string(), |
644 | 11 | symbol_pattern: "^(BTC|ETH|BTCUSD|ETHUSD|BTCUSDT|ETHUSDT).*$".to_string(), |
645 | 11 | compiled_pattern: None, |
646 | 11 | asset_class: AssetClass::Crypto { |
647 | 11 | network: "Bitcoin".to_string(), |
648 | 11 | crypto_type: CryptoType::Bitcoin, |
649 | 11 | market_cap_rank: Some(1), |
650 | 11 | }, |
651 | 11 | volatility_profile: VolatilityProfile { |
652 | 11 | base_annual_volatility: 0.80, |
653 | 11 | stress_volatility_multiplier: 3.0, |
654 | 11 | intraday_pattern: vec![1.0; 24], |
655 | 11 | volatility_persistence: 0.90, |
656 | 11 | jump_risk: JumpRiskProfile { |
657 | 11 | jump_probability: 0.05, |
658 | 11 | jump_magnitude: 0.10, |
659 | 11 | max_jump_size: 0.30, |
660 | 11 | }, |
661 | 11 | }, |
662 | 11 | trading_parameters: TradingParameters { |
663 | 11 | position_limits: PositionLimits { |
664 | 11 | max_position_fraction: 0.10, |
665 | 11 | max_leverage: 1.5, |
666 | 11 | concentration_limit: 0.15, |
667 | 11 | min_position_size: "0.001".parse().unwrap(), |
668 | 11 | }, |
669 | 11 | risk_thresholds: RiskThresholds { |
670 | 11 | var_limit: 0.10, |
671 | 11 | daily_loss_limit: 0.05, |
672 | 11 | stop_loss_threshold: 0.15, |
673 | 11 | volatility_circuit_breaker: 0.15, |
674 | 11 | max_drawdown_threshold: 0.25, |
675 | 11 | }, |
676 | 11 | execution_config: ExecutionConfig { |
677 | 11 | preferred_order_types: vec![OrderType::Limit, OrderType::Market], |
678 | 11 | tick_size: "0.01".parse().unwrap(), |
679 | 11 | min_order_size: "0.001".parse().unwrap(), |
680 | 11 | max_order_size: Decimal::from(100), |
681 | 11 | time_in_force_default: TimeInForce::GoodTillCancel, |
682 | 11 | slippage_tolerance: 0.005, |
683 | 11 | }, |
684 | 11 | market_making: None, |
685 | 11 | }, |
686 | 11 | priority: 90, |
687 | 11 | is_active: true, |
688 | 11 | created_at: now, |
689 | 11 | updated_at: now, |
690 | 11 | trading_hours: None, // 24/7 trading |
691 | 11 | settlement_config: SettlementConfig { |
692 | 11 | settlement_days: 0, |
693 | 11 | settlement_currency: "USD".to_string(), |
694 | 11 | physical_settlement: true, |
695 | 11 | }, |
696 | 11 | }); |
697 | | |
698 | | // Major forex pairs |
699 | 11 | configs.push(AssetConfig { |
700 | 11 | id: Uuid::new_v4(), |
701 | 11 | name: "Major Forex Pairs".to_string(), |
702 | 11 | symbol_pattern: "^(EUR|GBP|USD|JPY|AUD|CAD|CHF|NZD)(USD|EUR|GBP|JPY)$".to_string(), |
703 | 11 | compiled_pattern: None, |
704 | 11 | asset_class: AssetClass::Forex { |
705 | 11 | base: "EUR".to_string(), |
706 | 11 | quote: "USD".to_string(), |
707 | 11 | pair_type: ForexPairType::Major, |
708 | 11 | }, |
709 | 11 | volatility_profile: VolatilityProfile { |
710 | 11 | base_annual_volatility: 0.12, |
711 | 11 | stress_volatility_multiplier: 2.5, |
712 | 11 | intraday_pattern: vec![1.0; 24], |
713 | 11 | volatility_persistence: 0.80, |
714 | 11 | jump_risk: JumpRiskProfile { |
715 | 11 | jump_probability: 0.01, |
716 | 11 | jump_magnitude: 0.02, |
717 | 11 | max_jump_size: 0.08, |
718 | 11 | }, |
719 | 11 | }, |
720 | 11 | trading_parameters: TradingParameters { |
721 | 11 | position_limits: PositionLimits { |
722 | 11 | max_position_fraction: 0.30, |
723 | 11 | max_leverage: 10.0, |
724 | 11 | concentration_limit: 0.40, |
725 | 11 | min_position_size: Decimal::from(1000), |
726 | 11 | }, |
727 | 11 | risk_thresholds: RiskThresholds { |
728 | 11 | var_limit: 0.03, |
729 | 11 | daily_loss_limit: 0.02, |
730 | 11 | stop_loss_threshold: 0.05, |
731 | 11 | volatility_circuit_breaker: 0.03, |
732 | 11 | max_drawdown_threshold: 0.10, |
733 | 11 | }, |
734 | 11 | execution_config: ExecutionConfig { |
735 | 11 | preferred_order_types: vec![OrderType::Limit, OrderType::Market], |
736 | 11 | tick_size: "0.00001".parse().unwrap(), |
737 | 11 | min_order_size: Decimal::from(1000), |
738 | 11 | max_order_size: Decimal::from(10000000), |
739 | 11 | time_in_force_default: TimeInForce::GoodTillCancel, |
740 | 11 | slippage_tolerance: 0.0002, |
741 | 11 | }, |
742 | 11 | market_making: Some(MarketMakingConfig { |
743 | 11 | target_spread: 0.0001, |
744 | 11 | max_inventory: Decimal::from(100000), |
745 | 11 | quote_size: Decimal::from(10000), |
746 | 11 | refresh_frequency: std::time::Duration::from_millis(100), |
747 | 11 | }), |
748 | 11 | }, |
749 | 11 | priority: 80, |
750 | 11 | is_active: true, |
751 | 11 | created_at: now, |
752 | 11 | updated_at: now, |
753 | 11 | trading_hours: None, // 24/5 trading |
754 | 11 | settlement_config: SettlementConfig { |
755 | 11 | settlement_days: 2, |
756 | 11 | settlement_currency: "USD".to_string(), |
757 | 11 | physical_settlement: false, |
758 | 11 | }, |
759 | 11 | }); |
760 | | |
761 | 11 | configs |
762 | 11 | } |
763 | | |
764 | | #[cfg(test)] |
765 | | mod tests { |
766 | | use super::*; |
767 | | |
768 | | #[tokio::test] |
769 | 1 | async fn test_symbol_classification() { |
770 | 1 | let mut manager = AssetClassificationManager::new(); |
771 | 1 | let configs = create_default_configurations(); |
772 | 1 | manager.load_configurations(configs).await.unwrap(); |
773 | | |
774 | | // Test blue chip classification |
775 | 1 | match manager.classify_symbol("AAPL") { |
776 | 1 | AssetClass::Equity { |
777 | 1 | sector: EquitySector::Technology, |
778 | 1 | .. |
779 | 1 | } => (), |
780 | 1 | _ => panic!0 ("AAPL should be classified as Technology equity"0 ), |
781 | 1 | } |
782 | 1 | |
783 | 1 | // Test crypto classification |
784 | 1 | match manager.classify_symbol("BTCUSD") { |
785 | 1 | AssetClass::Crypto { |
786 | 1 | crypto_type: CryptoType::Bitcoin, |
787 | 1 | .. |
788 | 1 | } => (), |
789 | 1 | _ => panic!0 ("BTCUSD should be classified as Bitcoin crypto"0 ), |
790 | 1 | } |
791 | 1 | |
792 | 1 | // Test unknown symbol |
793 | 1 | assert_eq!(manager.classify_symbol("UNKNOWN"), AssetClass::Unknown); |
794 | 1 | } |
795 | | |
796 | | #[tokio::test] |
797 | 1 | async fn test_volatility_profile() { |
798 | 1 | let mut manager = AssetClassificationManager::new(); |
799 | 1 | let configs = create_default_configurations(); |
800 | 1 | manager.load_configurations(configs).await.unwrap(); |
801 | | |
802 | 1 | let profile = manager.get_volatility_profile("AAPL").unwrap(); |
803 | 1 | assert_eq!(profile.base_annual_volatility, 0.25); |
804 | | |
805 | 1 | let daily_vol = manager.get_daily_volatility("AAPL"); |
806 | 1 | assert!((daily_vol - (0.25 / 252.0_f64.sqrt())).abs() < 1e-10); |
807 | 1 | } |
808 | | |
809 | | #[tokio::test] |
810 | 1 | async fn test_trading_parameters() { |
811 | 1 | let mut manager = AssetClassificationManager::new(); |
812 | 1 | let configs = create_default_configurations(); |
813 | 1 | manager.load_configurations(configs).await.unwrap(); |
814 | | |
815 | 1 | let params = manager.get_trading_parameters("AAPL").unwrap(); |
816 | 1 | assert_eq!(params.position_limits.max_position_fraction, 0.20); |
817 | 1 | assert_eq!(params.position_limits.max_leverage, 2.0); |
818 | 1 | } |
819 | | } |