//! Risk Data Models //! //! Database schema models and data structures for risk management in //! high-frequency trading systems. Provides comprehensive data models //! for `VaR` calculations, compliance logging, and position limits. // Allow pedantic lints for data models #![allow(clippy::missing_docs_in_private_items)] #![allow(missing_docs)] use chrono::{DateTime, Utc}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use sqlx::FromRow; use std::collections::HashMap; use uuid::Uuid; /// Database connection pool - proper newtype wrapper #[derive(Debug, Clone)] pub struct DbPool(sqlx::PgPool); impl DbPool { /// Create a new database pool wrapper pub const fn new(pool: sqlx::PgPool) -> Self { Self(pool) } /// Get the underlying pool pub const fn inner(&self) -> &sqlx::PgPool { &self.0 } /// Into the underlying pool pub fn into_inner(self) -> sqlx::PgPool { self.0 } } impl std::ops::Deref for DbPool { type Target = sqlx::PgPool; fn deref(&self) -> &Self::Target { &self.0 } } impl From for DbPool { fn from(pool: sqlx::PgPool) -> Self { Self::new(pool) } } impl From for sqlx::PgPool { fn from(pool: DbPool) -> Self { pool.into_inner() } } /// Redis connection - proper newtype wrapper #[derive(Debug, Clone)] pub struct RedisConnection(redis::aio::MultiplexedConnection); impl RedisConnection { /// Create a new Redis connection wrapper pub const fn new(conn: redis::aio::MultiplexedConnection) -> Self { Self(conn) } /// Get the underlying connection pub const fn inner(&self) -> &redis::aio::MultiplexedConnection { &self.0 } /// Into the underlying connection pub fn into_inner(self) -> redis::aio::MultiplexedConnection { self.0 } } impl std::ops::Deref for RedisConnection { type Target = redis::aio::MultiplexedConnection; fn deref(&self) -> &Self::Target { &self.0 } } impl From for RedisConnection { fn from(conn: redis::aio::MultiplexedConnection) -> Self { Self::new(conn) } } impl From for redis::aio::MultiplexedConnection { fn from(conn: RedisConnection) -> Self { conn.into_inner() } } /// Financial instrument types #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] #[sqlx(type_name = "instrument_type", rename_all = "snake_case")] pub enum InstrumentType { /// Stock or equity security Equity, /// Fixed income bond Bond, /// Physical commodity or commodity future Commodity, /// Foreign exchange currency pair Currency, /// Generic derivative instrument Derivative, /// Futures contract Future, /// Options contract Option, /// Interest rate or currency swap Swap, /// Contract for difference Cfd, /// Cryptocurrency Crypto, } /// Asset classes for risk categorization #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] #[sqlx(type_name = "asset_class", rename_all = "snake_case")] 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, } /// Market sectors for concentration risk #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] #[sqlx(type_name = "market_sector", rename_all = "snake_case")] pub enum MarketSector { /// Technology and software companies Technology, /// Healthcare and pharmaceutical companies Healthcare, /// Financial services and banking Financials, /// Energy and oil companies Energy, /// Consumer goods and services Consumer, /// Industrial and manufacturing companies Industrials, /// Basic materials and mining Materials, /// Utilities and infrastructure Utilities, /// Real estate and REITs RealEstate, /// Telecommunications and media Telecommunications, /// Government bonds and securities Government, /// Other or unclassified sectors Other, } /// Trading venues #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)] #[sqlx(type_name = "venue_type", rename_all = "snake_case")] pub enum VenueType { /// Regulated exchange Exchange, /// Electronic Communication Network Ecn, /// Dark pool venue DarkPool, /// Over-the-counter market OverTheCounter, /// Internal crossing network InternalCross, /// Systematic Internalizer Systematic, } /// Risk metric types #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)] #[sqlx(type_name = "risk_metric_type", rename_all = "snake_case")] pub enum RiskMetricType { /// Value at Risk calculation Var, /// Expected Shortfall (Conditional `VaR`) ExpectedShortfall, /// Maximum drawdown metric MaxDrawdown, /// Sharpe ratio calculation SharpeRatio, /// Beta coefficient Beta, /// Volatility measurement Volatility, /// Correlation analysis Correlation, /// Portfolio concentration risk ConcentrationRisk, /// Liquidity risk assessment LiquidityRisk, /// Counterparty risk evaluation CounterpartyRisk, } /// Time periods for risk calculations #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)] #[sqlx(type_name = "time_period", rename_all = "snake_case")] pub enum TimePeriod { /// Intraday time period (within a day) Intraday, /// Daily time period Daily, /// Weekly time period Weekly, /// Monthly time period Monthly, /// Quarterly time period Quarterly, /// Yearly time period Yearly, } /// Financial instrument master data #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Instrument { /// Unique identifier for the instrument pub id: Uuid, /// Trading symbol (e.g., "AAPL", "MSFT") pub symbol: String, /// International Securities Identification Number pub isin: Option, /// Committee on Uniform Securities Identification Procedures number pub cusip: Option, /// Bloomberg identifier for the instrument pub bloomberg_id: Option, /// Reuters identifier for the instrument pub reuters_id: Option, /// Full name of the instrument (e.g., "Apple Inc.") pub name: String, /// Type of financial instrument pub instrument_type: InstrumentType, /// Asset class categorization pub asset_class: AssetClass, /// Market sector classification pub sector: Option, /// Base currency of the instrument (ISO 3-letter code) pub currency: String, /// Primary exchange where the instrument is traded pub exchange: Option, /// Minimum price movement (tick size) pub tick_size: Option, /// Standard trading lot size pub lot_size: Option, /// Contract multiplier for derivatives pub multiplier: Option, /// Maturity date for bonds, futures, and options pub maturity_date: Option>, /// Strike price for options and warrants pub strike_price: Option, /// Option type ("Call" or "Put" for options) pub option_type: Option, /// Symbol of underlying asset for derivatives pub underlying_symbol: Option, /// Whether the instrument is currently active for trading pub is_active: bool, /// Timestamp when the record was created pub created_at: DateTime, /// Timestamp when the record was last updated pub updated_at: DateTime, /// Additional instrument metadata as JSON pub metadata: serde_json::Value, } /// Portfolio definition #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Portfolio { /// Unique portfolio identifier pub id: String, /// Human-readable portfolio name pub name: String, /// Optional portfolio description pub description: Option, /// Base currency for portfolio calculations (ISO 3-letter code) pub base_currency: String, /// Portfolio type (Strategy, Client, Prop, etc.) pub portfolio_type: String, /// Date when the portfolio was created/incepted pub inception_date: DateTime, /// Identifier of the portfolio manager pub manager_id: String, /// Benchmark symbol for performance comparison pub benchmark: Option, /// Allocated risk budget (typically as volatility target) pub risk_budget: Option, /// Value at Risk limit for the portfolio pub var_limit: Option, /// Maximum allowed drawdown percentage pub max_drawdown_limit: Option, /// Whether the portfolio is currently active pub is_active: bool, /// Timestamp when the record was created pub created_at: DateTime, /// Timestamp when the record was last updated pub updated_at: DateTime, /// Additional portfolio metadata as JSON pub metadata: serde_json::Value, } /// Position snapshot for risk calculations #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Position { /// Unique position identifier pub id: Uuid, /// Portfolio containing this position pub portfolio_id: String, /// Instrument symbol being held pub symbol: String, /// Number of shares/contracts held (positive for long, negative for short) pub quantity: Decimal, /// Average price at which the position was established pub average_price: Decimal, /// Current market price of the instrument pub market_price: Decimal, /// Total market value of the position (quantity × market price) pub market_value: Decimal, /// Unrealized profit and loss on the position pub unrealized_pnl: Decimal, /// Currency of the position (ISO 3-letter code) pub currency: String, /// Date when the position was first established pub entry_date: DateTime, /// Timestamp of last update to position data pub last_updated: DateTime, /// Position weight as percentage of total portfolio value pub weight: Option, /// Beta coefficient relative to market benchmark pub beta: Option, /// Duration for fixed income securities pub duration: Option, /// Delta sensitivity for derivatives (price sensitivity) pub delta: Option, /// Gamma sensitivity for derivatives (delta sensitivity) pub gamma: Option, /// Vega sensitivity for derivatives (volatility sensitivity) pub vega: Option, /// Theta sensitivity for derivatives (time decay) pub theta: Option, } /// Daily portfolio performance metrics #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct PortfolioPerformance { /// Unique performance record identifier pub id: Uuid, /// Portfolio this performance data belongs to pub portfolio_id: String, /// Date of the performance calculation pub date: DateTime, /// Net Asset Value of the portfolio pub nav: Decimal, /// Daily return percentage pub daily_return: Decimal, /// Cumulative return since inception pub cumulative_return: Decimal, /// Annualized volatility pub volatility: Decimal, /// Sharpe ratio (risk-adjusted return) pub sharpe_ratio: Option, /// Maximum drawdown percentage pub max_drawdown: Decimal, /// Value at Risk at 95% confidence level pub var_95: Option, /// Value at Risk at 99% confidence level pub var_99: Option, /// Expected Shortfall at 95% confidence level pub expected_shortfall_95: Option, /// Beta coefficient relative to benchmark pub beta: Option, /// Alpha (excess return over benchmark) pub alpha: Option, /// Information ratio (alpha divided by tracking error) pub information_ratio: Option, /// Portfolio turnover rate pub turnover: Option, /// Weight of the largest position in the portfolio pub largest_position: Option, /// Total number of positions held pub number_of_positions: i32, /// Sector exposure breakdown as JSON pub sector_concentration: serde_json::Value, /// Currency exposure breakdown as JSON pub currency_exposure: serde_json::Value, } /// Risk factor exposures #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct RiskFactorExposure { /// Unique risk factor exposure identifier pub id: Uuid, /// Portfolio this exposure belongs to pub portfolio_id: String, /// Factor name (e.g., "Equity Market", "Interest Rates") pub risk_factor: String, /// Factor type ("Market", "Style", "Currency", "Country", etc.) pub factor_type: String, /// Factor loading/exposure amount pub exposure: Decimal, /// Contribution to portfolio variance pub contribution_to_risk: Decimal, /// Date of the exposure calculation pub date: DateTime, /// Confidence interval for the exposure estimate pub confidence_interval: Option, /// R-squared goodness of fit measure pub r_squared: Option, } /// Stress test scenarios #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct StressScenario { /// Unique stress scenario identifier pub id: Uuid, /// Human-readable scenario name pub name: String, /// Detailed description of the stress scenario pub description: String, /// Scenario type (Historical, Hypothetical, Monte Carlo) pub scenario_type: String, /// Whether this scenario is currently active for testing pub active: bool, /// Factor shocks definition as JSON pub shock_factors: serde_json::Value, /// User who created this scenario pub created_by: String, /// Timestamp when the scenario was created pub created_at: DateTime, /// Timestamp when the scenario was last updated pub updated_at: DateTime, } /// Stress test results #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct StressTestResult { /// Unique stress test result identifier pub id: Uuid, /// Portfolio that was stress tested pub portfolio_id: String, /// Stress scenario that was applied pub scenario_id: Uuid, /// Date when the stress test was performed pub test_date: DateTime, /// Portfolio value before applying stress pub base_portfolio_value: Decimal, /// Portfolio value after applying stress scenario pub stressed_portfolio_value: Decimal, /// Absolute loss amount from the stress test pub absolute_loss: Decimal, /// Percentage loss from the stress test pub percentage_loss: Decimal, /// Symbol of the worst performing position pub worst_performing_position: Option, /// Loss amount of the worst performing position pub worst_position_loss: Option, /// Impact breakdown by sector as JSON pub sector_impacts: serde_json::Value, /// Detailed stress test results as JSON pub detailed_results: serde_json::Value, } /// Counterparty information for counterparty risk #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Counterparty { /// Unique counterparty identifier pub id: String, /// Legal name of the counterparty pub name: String, /// Counterparty type (Bank, Broker, Exchange, etc.) pub counterparty_type: String, /// Country where the counterparty is domiciled pub country: String, /// Credit rating from rating agencies pub credit_rating: Option, /// Legal Entity Identifier code pub lei_code: Option, /// Parent company if applicable pub parent_company: Option, /// Whether the counterparty is currently active pub is_active: bool, /// Maximum allowed exposure to this counterparty pub exposure_limit: Option, /// Margin requirement for this counterparty pub margin_requirement: Option, /// Whether a netting agreement is in place pub netting_agreement: bool, /// Timestamp when the record was created pub created_at: DateTime, /// Timestamp when the record was last updated pub updated_at: DateTime, /// Additional counterparty metadata as JSON pub metadata: serde_json::Value, } /// Counterparty exposure tracking #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct CounterpartyExposure { /// Unique counterparty exposure identifier pub id: Uuid, /// Counterparty this exposure relates to pub counterparty_id: String, /// Portfolio generating this exposure (if applicable) pub portfolio_id: Option, /// Exposure type (Current, Potential, Settlement) pub exposure_type: String, /// Gross exposure amount before netting pub gross_exposure: Decimal, /// Net exposure amount after netting agreements pub net_exposure: Decimal, /// Collateral held from the counterparty pub collateral_held: Decimal, /// Collateral posted to the counterparty pub collateral_posted: Decimal, /// Current mark-to-market value pub mark_to_market: Decimal, /// Currency of the exposure (ISO 3-letter code) pub currency: String, /// Maturity bucket classification (0-1Y, 1-5Y, etc.) pub maturity_bucket: Option, /// Risk weight for regulatory capital calculations pub risk_weight: Option, /// Date of the exposure calculation pub date: DateTime, } /// Liquidity metrics for positions #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct LiquidityMetrics { /// Unique liquidity metrics identifier pub id: Uuid, /// Instrument symbol being analyzed pub symbol: String, /// Date of the liquidity analysis pub date: DateTime, /// Average daily trading volume pub average_daily_volume: Decimal, /// Bid-ask spread in basis points pub bid_ask_spread_bps: Decimal, /// Market impact coefficient for large trades pub market_impact_coefficient: Option, /// Days to liquidate 10% of average daily volume pub days_to_liquidate_10pct: Option, /// Days to liquidate 50% of average daily volume pub days_to_liquidate_50pct: Option, /// Overall liquidity score (1-10 scale) pub liquidity_score: Option, /// High frequency trading volume ratio pub high_frequency_ratio: Option, /// Dark pool volume ratio pub dark_pool_ratio: Option, /// Price volatility measure pub volatility: Decimal, /// Amihud illiquidity measure pub amihud_illiquidity: Option, } /// Economic scenarios for scenario analysis #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct EconomicScenario { /// Unique economic scenario identifier pub id: Uuid, /// Human-readable scenario name pub name: String, /// Detailed description of the economic scenario pub description: String, /// Probability assignment for this scenario pub probability: Option, /// Time horizon for the scenario pub time_horizon: TimePeriod, /// GDP growth rate change in the scenario pub gdp_growth_rate: Option, /// Inflation rate change in the scenario pub inflation_rate: Option, /// Interest rate change in the scenario pub interest_rate_change: Option, /// Unemployment rate in the scenario pub unemployment_rate: Option, /// Currency pair shocks as JSON pub currency_shock: serde_json::Value, /// Commodity price shocks as JSON pub commodity_shock: serde_json::Value, /// Market index shocks as JSON pub equity_market_shock: serde_json::Value, /// Volatility regime changes as JSON pub volatility_shock: serde_json::Value, /// User who created this scenario pub created_by: String, /// Timestamp when the scenario was created pub created_at: DateTime, /// Whether this scenario is currently active pub is_active: bool, } /// Risk report templates #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct RiskReportTemplate { /// Unique report template identifier pub id: Uuid, /// Human-readable template name pub name: String, /// Description of the report template pub description: String, /// Report type (Daily, Weekly, Monthly, Regulatory) pub report_type: String, /// Report structure and parameters as JSON pub template_config: serde_json::Value, /// Email distribution list as JSON pub recipients: serde_json::Value, /// Cron schedule for automated reports pub schedule_cron: Option, /// Whether this template is currently active pub is_active: bool, /// User who created this template pub created_by: String, /// Timestamp when the template was created pub created_at: DateTime, /// Timestamp when the template was last updated pub updated_at: DateTime, } /// Generated risk reports #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct RiskReport { /// Unique risk report identifier pub id: Uuid, /// Template used to generate this report pub template_id: Uuid, /// Portfolio this report covers (if applicable) pub portfolio_id: Option, /// Date the report covers pub report_date: DateTime, /// Timestamp when the report was generated pub generated_at: DateTime, /// User who generated the report pub generated_by: String, /// Full report content as JSON pub report_data: serde_json::Value, /// Path to generated PDF/Excel file pub file_path: Option, /// Report status (Generated, Sent, Failed) pub status: String, /// Error message if report generation failed pub error_message: Option, /// List of recipients who received the report as JSON pub recipients_sent: serde_json::Value, } /// Market data feeds configuration #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct MarketDataFeed { /// Unique market data feed identifier pub id: Uuid, /// Name of the data provider pub provider_name: String, /// Feed type (Real-time, End-of-day, Historical) pub feed_type: String, /// List of symbols covered by this feed as JSON pub symbols_covered: serde_json::Value, /// Connection parameters as JSON pub connection_config: serde_json::Value, /// Whether this is the primary feed (vs backup) pub is_primary: bool, /// Whether this feed is currently active pub is_active: bool, /// Latency SLA in milliseconds pub latency_sla_ms: Option, /// Uptime SLA as percentage pub uptime_sla_pct: Option, /// Timestamp of last heartbeat from the feed pub last_heartbeat: Option>, /// Timestamp when the record was created pub created_at: DateTime, /// Timestamp when the record was last updated pub updated_at: DateTime, } /// Risk calculation jobs queue #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct RiskCalculationJob { /// Unique job identifier pub id: Uuid, /// Job type (`VaR`, `StressTest`, Scenario, etc.) pub job_type: String, /// Portfolio to calculate risk for (if applicable) pub portfolio_id: Option, /// Job-specific parameters as JSON pub parameters: serde_json::Value, /// Job priority (1-10, higher is more urgent) pub priority: i32, /// Job status (Queued, Running, Completed, Failed) pub status: String, /// Timestamp when job execution started pub started_at: Option>, /// Timestamp when job execution completed pub completed_at: Option>, /// Job completion percentage (0-100) pub progress_pct: Option, /// Job result data as JSON pub result_data: Option, /// Error message if job failed pub error_message: Option, /// Number of times this job has been retried pub retry_count: i32, /// Maximum number of retry attempts allowed pub max_retries: i32, /// User who created this job pub created_by: String, /// Timestamp when the job was created pub created_at: DateTime, } /// Custom risk metrics configuration #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct CustomRiskMetric { /// Unique custom risk metric identifier pub id: Uuid, /// Human-readable metric name pub name: String, /// Description of what this metric measures pub description: String, /// Mathematical formula or SQL query for calculation pub formula: String, /// Configurable parameters as JSON pub parameters: serde_json::Value, /// Output type (Number, Percentage, Currency) pub output_type: String, /// Calculation frequency pub frequency: TimePeriod, /// Metric scope (Portfolio, Position, Global) pub scope: String, /// Whether this metric is currently active pub is_active: bool, /// User who created this metric pub created_by: String, /// Timestamp when the metric was created pub created_at: DateTime, /// Timestamp when the metric was last updated pub updated_at: DateTime, } /// Calculated custom risk metrics #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct CustomRiskMetricResult { /// Unique metric result identifier pub id: Uuid, /// Custom risk metric that was calculated pub metric_id: Uuid, /// Portfolio this result applies to (if applicable) pub portfolio_id: Option, /// Symbol this result applies to (if applicable) pub symbol: Option, /// Date when the calculation was performed pub calculation_date: DateTime, /// Calculated metric value pub value: Decimal, /// Additional calculation details as JSON pub metadata: serde_json::Value, } /// Common financial calculations and utilities #[derive(Debug)] pub struct FinancialCalculations; impl FinancialCalculations { /// Calculate annualized volatility from daily returns #[allow(clippy::arithmetic_side_effects)] pub fn annualized_volatility(daily_vol: Decimal) -> Decimal { daily_vol * Decimal::from(16_i32) // sqrt(252) ≈ 15.87, using 16 as approximation } /// Calculate Sharpe ratio #[allow(clippy::arithmetic_side_effects)] pub fn sharpe_ratio( returns: Decimal, risk_free_rate: Decimal, volatility: Decimal, ) -> Option { if volatility == Decimal::ZERO { None } else { Some((returns - risk_free_rate) / volatility) } } /// Calculate maximum drawdown /// /// # Returns /// - `Ok(Decimal)` - Maximum drawdown as a percentage /// /// - `Err(String)` - Error if peak is zero or calculation fails /// /// /// # Errors /// Returns error if the operation fails /// # Errors /// Returns error if peak is zero or negative, preventing valid drawdown calculation #[allow(clippy::arithmetic_side_effects)] pub fn max_drawdown(peak: Decimal, trough: Decimal) -> Result { if peak == Decimal::ZERO { // CRITICAL: Zero peak value prevents meaningful drawdown calculation // This could indicate: // 1. No historical high water mark (data corruption) // 2. Portfolio started with zero value (configuration error) // 3. Missing performance data return Err( "Cannot calculate drawdown with zero peak value - this may indicate \ missing performance data or data corruption" .to_owned(), ); } if peak < Decimal::ZERO { return Err(format!("Invalid negative peak value: {}", peak)); } Ok(((trough - peak) / peak) * Decimal::from(100_i32)) } } /// Portfolio aggregation utilities #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PortfolioSummary { /// Total market value of all positions pub total_market_value: Decimal, /// Breakdown of exposure by currency pub currency_breakdown: HashMap, /// Breakdown of exposure by market sector pub sector_breakdown: HashMap, /// Breakdown of exposure by asset class pub asset_class_breakdown: HashMap, /// Top positions by weight (Symbol, Weight) pub top_positions: Vec<(String, Decimal)>, /// Total number of positions in the portfolio pub number_of_positions: usize, /// Weight of the largest single position pub largest_position_weight: Decimal, /// Effective number of positions (diversification measure) pub effective_number_of_positions: Decimal, /// Gross exposure (sum of absolute position values) pub gross_exposure: Decimal, /// Net exposure (sum of signed position values) pub net_exposure: Decimal, /// Portfolio beta relative to benchmark pub beta: Option, /// Tracking error relative to benchmark pub tracking_error: Option, } /// Risk factor model utilities #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FactorModel { /// Name of the risk factor model pub model_name: String, /// List of risk factors in the model pub factors: Vec, /// Factor loadings for each symbol (Symbol -> Factor -> Loading) pub factor_loadings: HashMap>, /// Covariance matrix between factors pub factor_covariance_matrix: HashMap>, /// Specific (idiosyncratic) risks for each symbol pub specific_risks: HashMap, /// R-squared values for each symbol's factor model fit pub r_squared: HashMap, /// Timestamp when the model was last updated pub last_updated: DateTime, } /// Validation utilities impl Instrument { /// Validates the instrument configuration /// /// # Errors /// Returns error if symbol or name is empty, or currency is not a 3-character ISO code pub fn validate(&self) -> Result<(), String> { if self.symbol.is_empty() { return Err("Symbol cannot be empty".to_owned()); } if self.name.is_empty() { return Err("Instrument name cannot be empty".to_owned()); } if self.currency.len() != 3 { return Err("Currency must be 3-character ISO code".to_owned()); } Ok(()) } } impl Portfolio { /// Validates the portfolio configuration /// /// # Errors /// /// Returns error if: /// - Portfolio name is empty /// - Currency is not a 3-character ISO code /// - No instruments are defined /// /// # Errors /// Returns error if portfolio ID or name is empty, currency is invalid, or VAR limit is not positive pub fn validate(&self) -> Result<(), String> { if self.id.is_empty() { return Err("Portfolio ID cannot be empty".to_owned()); } if self.name.is_empty() { return Err("Portfolio name cannot be empty".to_owned()); } if self.base_currency.len() != 3 { return Err("Base currency must be 3-character ISO code".to_owned()); } if let Some(var_limit) = self.var_limit { if var_limit <= Decimal::ZERO { return Err("VaR limit must be positive".to_owned()); } } Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_decimal_calculations() { let daily_vol = Decimal::from_str_exact("0.02").unwrap(); let annual_vol = FinancialCalculations::annualized_volatility(daily_vol); assert!(annual_vol > daily_vol); let returns = Decimal::from_str_exact("0.12").unwrap(); let risk_free = Decimal::from_str_exact("0.03").unwrap(); let volatility = Decimal::from_str_exact("0.15").unwrap(); let sharpe = FinancialCalculations::sharpe_ratio(returns, risk_free, volatility).unwrap(); assert!(sharpe > Decimal::ZERO); let peak = Decimal::from(100_i32); let trough = Decimal::from(85_i32); let drawdown = FinancialCalculations::max_drawdown(peak, trough); assert_eq!(drawdown, Ok(Decimal::from(-15_i32))); } #[test] fn test_instrument_validation() { let valid_instrument = Instrument { id: Uuid::new_v4(), symbol: "AAPL".to_string(), isin: Some("US0378331005".to_string()), cusip: None, bloomberg_id: Some("AAPL UW Equity".to_string()), reuters_id: None, name: "Apple Inc.".to_string(), instrument_type: InstrumentType::Equity, asset_class: AssetClass::Equities, sector: Some(MarketSector::Technology), currency: "USD".to_string(), exchange: Some("NASDAQ".to_string()), tick_size: Some(Decimal::from_str_exact("0.01").unwrap()), lot_size: Some(Decimal::from(1_i32)), multiplier: Some(Decimal::from(1_i32)), maturity_date: None, strike_price: None, option_type: None, underlying_symbol: None, is_active: true, created_at: Utc::now(), updated_at: Utc::now(), metadata: serde_json::json!({}), }; valid_instrument.validate().unwrap(); // Test invalid currency let invalid_instrument = Instrument { currency: "INVALID".to_string(), ..valid_instrument }; assert!(invalid_instrument.validate().is_err()); } #[test] fn test_portfolio_validation() { let valid_portfolio = Portfolio { id: "TEST_PORTFOLIO".to_string(), name: "Test Portfolio".to_string(), description: Some("Test portfolio for validation".to_string()), base_currency: "USD".to_string(), portfolio_type: "Strategy".to_string(), inception_date: Utc::now(), manager_id: "test_manager".to_string(), benchmark: Some("SPY".to_string()), risk_budget: Some(Decimal::from_str_exact("0.15").unwrap()), var_limit: Some(Decimal::from(100_000_i32)), max_drawdown_limit: Some(Decimal::from_str_exact("0.20").unwrap()), is_active: true, created_at: Utc::now(), updated_at: Utc::now(), metadata: serde_json::json!({}), }; valid_portfolio.validate().unwrap(); // Test invalid VaR limit let invalid_portfolio = Portfolio { var_limit: Some(Decimal::from(-1_000_i32)), ..valid_portfolio }; assert!(invalid_portfolio.validate().is_err()); } }