Files
foxhunt/crates/config/src/asset_classification.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

844 lines
27 KiB
Rust

//! Comprehensive Asset Classification Configuration System
//!
//! This module provides production-ready asset classification capabilities with:
//! - Sophisticated asset class hierarchies
//! - Dynamic trading parameter configuration
//! - Pattern-based symbol matching with regex support
//! - Database-backed configuration with hot-reload
//! - Volatility profiling and risk management integration
use chrono::{DateTime, Datelike, NaiveTime, Utc};
use regex::Regex;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
/// Comprehensive asset classification enum with detailed sub-categories
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum AssetClass {
/// Equity instruments with sector-specific characteristics
Equity {
sector: EquitySector,
market_cap: MarketCapTier,
region: GeographicRegion,
},
/// Futures contracts with underlying asset classification
Future {
underlying: FutureType,
expiry_type: ExpiryType,
exchange: String,
},
/// Foreign exchange pairs with specific characteristics
Forex {
base: String,
quote: String,
pair_type: ForexPairType,
},
/// Cryptocurrency assets with network and type classification
Crypto {
network: String,
crypto_type: CryptoType,
market_cap_rank: Option<u32>,
},
/// Commodity instruments with category classification
Commodity {
category: CommodityType,
storage_type: StorageType,
},
/// Fixed income securities
FixedIncome {
instrument_type: FixedIncomeType,
credit_rating: CreditRating,
maturity: MaturityBucket,
},
/// Derivatives and structured products
Derivative {
underlying_class: Box<AssetClass>,
derivative_type: DerivativeType,
},
/// Unknown or unclassified assets (conservative defaults)
Unknown,
}
/// Equity sector classifications aligned with industry standards
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum EquitySector {
Technology,
Healthcare,
Financial,
ConsumerDiscretionary,
ConsumerStaples,
Industrial,
Energy,
Materials,
Utilities,
RealEstate,
CommunicationServices,
}
/// Market capitalization tiers for equity classification
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum MarketCapTier {
LargeCap, // > $10B
MidCap, // $2B - $10B
SmallCap, // $300M - $2B
MicroCap, // < $300M
}
/// Geographic regions for asset classification
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum GeographicRegion {
NorthAmerica,
Europe,
Asia,
EmergingMarkets,
Global,
}
/// Future contract underlying asset types
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum FutureType {
Equity,
Currency,
Commodity,
Interest,
Volatility,
}
/// Futures expiry categorization
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum ExpiryType {
Weekly,
Monthly,
Quarterly,
Annual,
}
/// Forex pair type classification
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum ForexPairType {
Major, // EUR/USD, GBP/USD, USD/JPY, etc.
Minor, // Cross-currency pairs without USD
Exotic, // Emerging market currencies
JPYPair, // Special handling for JPY pairs
}
/// Cryptocurrency type classification
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum CryptoType {
Bitcoin,
Ethereum,
Stablecoin,
AltcoinMajor, // Top 20 market cap
AltcoinMinor, // Beyond top 20
DeFi,
GameFi,
Meme,
}
/// Commodity categories
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum CommodityType {
PreciousMetals,
Energy,
Agricultural,
IndustrialMetals,
Livestock,
}
/// Storage characteristics for commodities
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum StorageType {
Physical,
Financial,
}
/// Fixed income instrument types
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum FixedIncomeType {
Government,
Corporate,
Municipal,
InflationProtected,
}
/// Credit rating classifications
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum CreditRating {
AAA,
AA,
A,
BBB,
BB,
B,
CCC,
Unrated,
}
/// Maturity buckets for fixed income
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum MaturityBucket {
ShortTerm, // < 2 years
MediumTerm, // 2-10 years
LongTerm, // > 10 years
}
/// Derivative instrument types
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum DerivativeType {
Option,
Swap,
Forward,
Structured,
}
/// Comprehensive volatility profile with regime-aware parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VolatilityProfile {
/// Base annual volatility (standard market conditions)
pub base_annual_volatility: f64,
/// Stress volatility multiplier for high-stress periods
pub stress_volatility_multiplier: f64,
/// Intraday volatility pattern (hourly multipliers)
pub intraday_pattern: Vec<f64>,
/// Volatility clustering parameter (GARCH-like)
pub volatility_persistence: f64,
/// Jump risk probability and magnitude
pub jump_risk: JumpRiskProfile,
}
/// Jump risk characteristics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JumpRiskProfile {
/// Probability of large price jumps per day
pub jump_probability: f64,
/// Average magnitude of jumps (as fraction of price)
pub jump_magnitude: f64,
/// Maximum expected jump size
pub max_jump_size: f64,
}
/// Dynamic trading parameters that adapt to market conditions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradingParameters {
/// Position sizing constraints
pub position_limits: PositionLimits,
/// Risk management thresholds
pub risk_thresholds: RiskThresholds,
/// Execution parameters
pub execution_config: ExecutionConfig,
/// Market making parameters (if applicable)
pub market_making: Option<MarketMakingConfig>,
}
/// Position sizing and exposure limits
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PositionLimits {
/// Maximum position size as fraction of portfolio NAV
pub max_position_fraction: f64,
/// Maximum leverage allowed for this asset
pub max_leverage: f64,
/// Concentration limit (max % of total positions in this asset class)
pub concentration_limit: f64,
/// Minimum position size (to avoid micro-positions)
pub min_position_size: Decimal,
}
/// Risk management thresholds and limits
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiskThresholds {
/// VaR limit as fraction of portfolio
pub var_limit: f64,
/// Daily loss limit
pub daily_loss_limit: f64,
/// Stop-loss threshold
pub stop_loss_threshold: f64,
/// Volatility circuit breaker threshold
pub volatility_circuit_breaker: f64,
/// Maximum drawdown before position reduction
pub max_drawdown_threshold: f64,
}
/// Execution configuration parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionConfig {
/// Preferred order types for this asset
pub preferred_order_types: Vec<OrderType>,
/// Tick size for price increments
pub tick_size: Decimal,
/// Minimum order size
pub min_order_size: Decimal,
/// Maximum order size before breaking up
pub max_order_size: Decimal,
/// Execution time constraints
pub time_in_force_default: TimeInForce,
/// Slippage tolerance
pub slippage_tolerance: f64,
}
/// Market making specific configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketMakingConfig {
/// Bid-ask spread targets
pub target_spread: f64,
/// Inventory limits
pub max_inventory: Decimal,
/// Quote size
pub quote_size: Decimal,
/// Refresh frequency
pub refresh_frequency: std::time::Duration,
}
/// Order type enumeration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OrderType {
Market,
Limit,
Stop,
StopLimit,
Hidden,
Iceberg,
}
/// Time in force options
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TimeInForce {
Day,
GoodTillCancel,
ImmediateOrCancel,
FillOrKill,
GTD, // Good Till Date
}
/// Symbol pattern matching configuration with compiled regex
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssetConfig {
/// UUID for database storage
pub id: Uuid,
/// Human-readable name for this configuration
pub name: String,
/// Regex pattern for symbol matching
pub symbol_pattern: String,
/// Compiled regex (not serialized, rebuilt on load)
#[serde(skip)]
pub compiled_pattern: Option<Regex>,
/// Asset class classification
pub asset_class: AssetClass,
/// Volatility profile
pub volatility_profile: VolatilityProfile,
/// Trading parameters
pub trading_parameters: TradingParameters,
/// Priority for pattern matching (higher = checked first)
pub priority: u32,
/// Whether this configuration is active
pub is_active: bool,
/// Creation timestamp
pub created_at: DateTime<Utc>,
/// Last update timestamp
pub updated_at: DateTime<Utc>,
/// Trading hours (if applicable)
pub trading_hours: Option<TradingHours>,
/// Settlement details
pub settlement_config: SettlementConfig,
}
/// Trading hours configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradingHours {
/// Regular trading session start
pub market_open: NaiveTime,
/// Regular trading session end
pub market_close: NaiveTime,
/// Pre-market session (if available)
pub pre_market: Option<(NaiveTime, NaiveTime)>,
/// After-hours session (if available)
pub after_hours: Option<(NaiveTime, NaiveTime)>,
/// Timezone for these hours
pub timezone: String,
/// Days of week when trading is active (0=Sunday, 6=Saturday)
pub trading_days: Vec<u8>,
}
/// Settlement configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettlementConfig {
/// Settlement period (T+n days)
pub settlement_days: u32,
/// Settlement currency
pub settlement_currency: String,
/// Whether physical delivery is possible
pub physical_settlement: bool,
}
/// Asset classification manager with caching and hot-reload capabilities
#[allow(clippy::module_name_repetitions)]
pub struct AssetClassificationManager {
/// Asset configurations indexed by priority
configs: Vec<AssetConfig>,
/// Explicit symbol mappings for fast lookup
symbol_cache: HashMap<String, AssetClass>,
/// Last configuration reload timestamp
last_reload: DateTime<Utc>,
/// Configuration reload interval
reload_interval: std::time::Duration,
}
impl AssetClassificationManager {
/// Create a new asset classification manager
#[allow(clippy::unwrap_used)] // Uses hardcoded values that are guaranteed to be valid
pub fn new() -> Self {
Self {
configs: Vec::new(),
symbol_cache: HashMap::new(),
last_reload: Utc::now(),
reload_interval: std::time::Duration::from_secs(300), // 5 minutes
}
}
/// Load configurations from database
///
/// # Errors
///
/// Returns error if:
/// - Regex pattern compilation fails
/// - Configuration validation fails
/// - Database access fails
///
/// # Errors
/// Returns error if the operation fails
pub async fn load_configurations(
&mut self,
configs: Vec<AssetConfig>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.configs = configs;
// Sort by priority (highest first)
self.configs.sort_by(|a, b| b.priority.cmp(&a.priority));
// Compile regex patterns
for config in &mut self.configs {
match Regex::new(&config.symbol_pattern) {
Ok(regex) => config.compiled_pattern = Some(regex),
Err(e) => {
tracing::warn!(
"Failed to compile regex pattern '{}': {}",
config.symbol_pattern,
e
);
config.is_active = false;
}
}
}
self.last_reload = Utc::now();
tracing::info!(
"Loaded {} asset classification configurations",
self.configs.len()
);
Ok(())
}
/// Classify a symbol using the configured rules
pub fn classify_symbol(&self, symbol: &str) -> AssetClass {
let symbol_upper = symbol.to_uppercase();
// Check cache first
if let Some(asset_class) = self.symbol_cache.get(&symbol_upper) {
return asset_class.clone();
}
// Check pattern rules in priority order
for config in &self.configs {
if !config.is_active {
continue;
}
if let Some(ref regex) = config.compiled_pattern {
if regex.is_match(&symbol_upper) {
return config.asset_class.clone();
}
}
}
AssetClass::Unknown
}
/// Get complete asset configuration for a symbol
pub fn get_asset_config(&self, symbol: &str) -> Option<&AssetConfig> {
let symbol_upper = symbol.to_uppercase();
for config in &self.configs {
if !config.is_active {
continue;
}
if let Some(ref regex) = config.compiled_pattern {
if regex.is_match(&symbol_upper) {
return Some(config);
}
}
}
None
}
/// Get volatility profile for a symbol
pub fn get_volatility_profile(&self, symbol: &str) -> Option<&VolatilityProfile> {
self.get_asset_config(symbol)
.map(|config| &config.volatility_profile)
}
/// Get trading parameters for a symbol
pub fn get_trading_parameters(&self, symbol: &str) -> Option<&TradingParameters> {
self.get_asset_config(symbol)
.map(|config| &config.trading_parameters)
}
/// Get daily volatility estimate for a symbol
pub fn get_daily_volatility(&self, symbol: &str) -> f64 {
if let Some(profile) = self.get_volatility_profile(symbol) {
#[allow(clippy::float_arithmetic)]
let result = profile.base_annual_volatility / 252.0_f64.sqrt();
result
} else {
#[allow(clippy::float_arithmetic)]
let result = 0.5 / 252.0_f64.sqrt();
result // Default high volatility
}
}
/// Get position sizing recommendation
pub fn get_position_size_recommendation(
&self,
symbol: &str,
portfolio_nav: Decimal,
) -> Option<Decimal> {
if let Some(config) = self.get_asset_config(symbol) {
let max_fraction = config
.trading_parameters
.position_limits
.max_position_fraction;
if let Some(decimal_fraction) = Decimal::from_f64_retain(max_fraction) {
portfolio_nav
.checked_mul(decimal_fraction)
.or(Some(Decimal::ZERO))
} else {
Some(Decimal::ZERO)
}
} else {
None
}
}
/// Check if symbol is within trading hours
pub fn is_trading_active(&self, symbol: &str, timestamp: DateTime<Utc>) -> bool {
if let Some(config) = self.get_asset_config(symbol) {
if let Some(ref trading_hours) = config.trading_hours {
// Simplified check - in production would need proper timezone handling
let weekday = timestamp
.weekday()
.num_days_from_sunday()
.try_into()
.unwrap_or(0u8);
trading_hours.trading_days.contains(&weekday)
} else {
true // No trading hours restriction
}
} else {
true // Default to always active for unknown symbols
}
}
/// Add explicit symbol mapping to cache
pub fn cache_symbol_mapping(&mut self, symbol: String, asset_class: AssetClass) {
self.symbol_cache.insert(symbol.to_uppercase(), asset_class);
}
/// Clear symbol cache
pub fn clear_cache(&mut self) {
self.symbol_cache.clear();
}
/// Check if configuration needs reload
pub fn needs_reload(&self) -> bool {
Utc::now().signed_duration_since(self.last_reload)
> chrono::Duration::from_std(self.reload_interval).unwrap_or_default()
}
/// Get all active configurations
pub fn get_active_configurations(&self) -> Vec<&AssetConfig> {
self.configs
.iter()
.filter(|config| config.is_active)
.collect()
}
/// Get configurations by asset class
pub fn get_configurations_by_class(&self, asset_class: &AssetClass) -> Vec<&AssetConfig> {
self.configs
.iter()
.filter(|config| config.is_active && &config.asset_class == asset_class)
.collect()
}
}
#[allow(clippy::unwrap_used)] // Default impl uses hardcoded values that are guaranteed to be valid
impl Default for AssetClassificationManager {
fn default() -> Self {
Self::new()
}
}
/// Create default asset configurations for common instruments
#[allow(clippy::unwrap_used)] // Uses hardcoded values that are guaranteed to be valid
pub fn create_default_configurations() -> Vec<AssetConfig> {
let mut configs = Vec::new();
let now = Utc::now();
// Blue chip US equities
configs.push(AssetConfig {
id: Uuid::new_v4(),
name: "Blue Chip US Equities".to_owned(),
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_owned(),
compiled_pattern: None,
asset_class: AssetClass::Equity {
sector: EquitySector::Technology,
market_cap: MarketCapTier::LargeCap,
region: GeographicRegion::NorthAmerica,
},
volatility_profile: VolatilityProfile {
base_annual_volatility: 0.25,
stress_volatility_multiplier: 2.0,
intraday_pattern: vec![1.0_f64; 24], // Flat pattern for simplicity
volatility_persistence: 0.85,
jump_risk: JumpRiskProfile {
jump_probability: 0.02,
jump_magnitude: 0.05,
max_jump_size: 0.15,
},
},
trading_parameters: TradingParameters {
position_limits: PositionLimits {
max_position_fraction: 0.20,
max_leverage: 2.0,
concentration_limit: 0.30,
min_position_size: Decimal::from(100_i64),
},
risk_thresholds: RiskThresholds {
var_limit: 0.05,
daily_loss_limit: 0.03,
stop_loss_threshold: 0.10,
volatility_circuit_breaker: 0.05,
max_drawdown_threshold: 0.15,
},
execution_config: ExecutionConfig {
preferred_order_types: vec![OrderType::Limit, OrderType::Market],
tick_size: Decimal::new(1, 2), // 0.01
min_order_size: Decimal::from(1_i64),
max_order_size: Decimal::from(10000_i64),
time_in_force_default: TimeInForce::Day,
slippage_tolerance: 0.001,
},
market_making: None,
},
priority: 100,
is_active: true,
created_at: now,
updated_at: now,
trading_hours: Some(TradingHours {
market_open: NaiveTime::from_hms_opt(9, 30, 0).unwrap_or_default(),
market_close: NaiveTime::from_hms_opt(16, 0, 0).unwrap_or_default(),
pre_market: Some((NaiveTime::from_hms_opt(4, 0, 0).unwrap_or_default(), NaiveTime::from_hms_opt(9, 30, 0).unwrap_or_default())),
after_hours: Some((NaiveTime::from_hms_opt(16, 0, 0).unwrap_or_default(), NaiveTime::from_hms_opt(20, 0, 0).unwrap_or_default())),
timezone: "America/New_York".to_owned(),
trading_days: vec![1, 2, 3, 4, 5], // Monday-Friday
}),
settlement_config: SettlementConfig {
settlement_days: 2,
settlement_currency: "USD".to_owned(),
physical_settlement: false,
},
});
// Major cryptocurrency pairs
configs.push(AssetConfig {
id: Uuid::new_v4(),
name: "Major Cryptocurrencies".to_owned(),
symbol_pattern: "^(BTC|ETH|BTCUSD|ETHUSD|BTCUSDT|ETHUSDT).*$".to_owned(),
compiled_pattern: None,
asset_class: AssetClass::Crypto {
network: "Bitcoin".to_owned(),
crypto_type: CryptoType::Bitcoin,
market_cap_rank: Some(1),
},
volatility_profile: VolatilityProfile {
base_annual_volatility: 0.80,
stress_volatility_multiplier: 3.0,
intraday_pattern: vec![1.0_f64; 24],
volatility_persistence: 0.90,
jump_risk: JumpRiskProfile {
jump_probability: 0.05,
jump_magnitude: 0.10,
max_jump_size: 0.30,
},
},
trading_parameters: TradingParameters {
position_limits: PositionLimits {
max_position_fraction: 0.10,
max_leverage: 1.5,
concentration_limit: 0.15,
min_position_size: Decimal::new(1, 3), // 0.001
},
risk_thresholds: RiskThresholds {
var_limit: 0.10,
daily_loss_limit: 0.05,
stop_loss_threshold: 0.15,
volatility_circuit_breaker: 0.15,
max_drawdown_threshold: 0.25,
},
execution_config: ExecutionConfig {
preferred_order_types: vec![OrderType::Limit, OrderType::Market],
tick_size: Decimal::new(1, 2), // 0.01
min_order_size: Decimal::new(1, 3), // 0.001
max_order_size: Decimal::from(100_i64),
time_in_force_default: TimeInForce::GoodTillCancel,
slippage_tolerance: 0.005,
},
market_making: None,
},
priority: 90,
is_active: true,
created_at: now,
updated_at: now,
trading_hours: None, // 24/7 trading
settlement_config: SettlementConfig {
settlement_days: 0,
settlement_currency: "USD".to_owned(),
physical_settlement: true,
},
});
// Major forex pairs
configs.push(AssetConfig {
id: Uuid::new_v4(),
name: "Major Forex Pairs".to_owned(),
symbol_pattern: "^(EUR|GBP|USD|JPY|AUD|CAD|CHF|NZD)(USD|EUR|GBP|JPY)$".to_owned(),
compiled_pattern: None,
asset_class: AssetClass::Forex {
base: "EUR".to_owned(),
quote: "USD".to_owned(),
pair_type: ForexPairType::Major,
},
volatility_profile: VolatilityProfile {
base_annual_volatility: 0.12,
stress_volatility_multiplier: 2.5,
intraday_pattern: vec![1.0_f64; 24],
volatility_persistence: 0.80,
jump_risk: JumpRiskProfile {
jump_probability: 0.01,
jump_magnitude: 0.02,
max_jump_size: 0.08,
},
},
trading_parameters: TradingParameters {
position_limits: PositionLimits {
max_position_fraction: 0.30,
max_leverage: 10.0,
concentration_limit: 0.40,
min_position_size: Decimal::from(1000_i64),
},
risk_thresholds: RiskThresholds {
var_limit: 0.03,
daily_loss_limit: 0.02,
stop_loss_threshold: 0.05,
volatility_circuit_breaker: 0.03,
max_drawdown_threshold: 0.10,
},
execution_config: ExecutionConfig {
preferred_order_types: vec![OrderType::Limit, OrderType::Market],
tick_size: Decimal::new(1, 5), // 0.00001
min_order_size: Decimal::from(1000_i64),
max_order_size: Decimal::from(10_000_000_i64),
time_in_force_default: TimeInForce::GoodTillCancel,
slippage_tolerance: 0.0002_f64,
},
market_making: Some(MarketMakingConfig {
target_spread: 0.0001_f64,
max_inventory: Decimal::from(100_000_i64),
quote_size: Decimal::from(10000_i64),
refresh_frequency: std::time::Duration::from_millis(100),
}),
},
priority: 80,
is_active: true,
created_at: now,
updated_at: now,
trading_hours: None, // 24/5 trading
settlement_config: SettlementConfig {
settlement_days: 2,
settlement_currency: "USD".to_owned(),
physical_settlement: false,
},
});
configs
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[tokio::test]
async fn test_symbol_classification() {
let mut manager = AssetClassificationManager::new();
let configs = create_default_configurations();
manager.load_configurations(configs).await.unwrap();
// Test blue chip classification
match manager.classify_symbol("AAPL") {
AssetClass::Equity {
sector: EquitySector::Technology,
..
} => (),
_ => panic!("AAPL should be classified as Technology equity"),
}
// Test crypto classification
match manager.classify_symbol("BTCUSD") {
AssetClass::Crypto {
crypto_type: CryptoType::Bitcoin,
..
} => (),
_ => panic!("BTCUSD should be classified as Bitcoin crypto"),
}
// Test unknown symbol
assert_eq!(manager.classify_symbol("UNKNOWN"), AssetClass::Unknown);
}
#[tokio::test]
async fn test_volatility_profile() {
let mut manager = AssetClassificationManager::new();
let configs = create_default_configurations();
manager.load_configurations(configs).await.unwrap();
let profile = manager.get_volatility_profile("AAPL").unwrap();
assert_eq!(profile.base_annual_volatility, 0.25);
let daily_vol = manager.get_daily_volatility("AAPL");
assert!((daily_vol - (0.25 / 252.0_f64.sqrt())).abs() < 1e-10);
}
#[tokio::test]
async fn test_trading_parameters() {
let mut manager = AssetClassificationManager::new();
let configs = create_default_configurations();
manager.load_configurations(configs).await.unwrap();
let params = manager.get_trading_parameters("AAPL").unwrap();
assert_eq!(params.position_limits.max_position_fraction, 0.20);
assert_eq!(params.position_limits.max_leverage, 2.0);
}
}