Systematic deployment of 10+ parallel agents successfully resolved ALL 371 compilation errors through comprehensive root cause analysis and implementation fixes. 🚀 **ACHIEVEMENT SUMMARY:** - ✅ Reduced from 371 errors to ZERO compilation errors - ✅ ML crate: Maintained at 0 errors throughout - ✅ Workspace-wide: Complete compilation success - ✅ SQLx integration: All database types now properly implemented 🔧 **TECHNICAL ACCOMPLISHMENTS:** - **Type System Unification**: Fixed split-brain architecture across all crates - **SQLx Database Integration**: Implemented all missing Encode/Decode/Type traits - **Import Resolution**: Fixed all core::types and dependency issues - **Storage Integration**: Database models fully integrated with common types - **Service Architecture**: All services now compile and integrate properly 📊 **PARALLEL AGENT RESULTS:** - Agent 1: Fixed backtesting crate - BacktestingPerformanceConfig exports resolved - Agent 2: Fixed trading_engine - Type system conflicts and BestExecutionError resolved - Agent 3: Fixed storage crate - Database integration and S3 configuration resolved - Agent 4: Fixed config crate - Workspace dependency conflicts resolved - Agent 5: Fixed database crate - SQLX offline mode and object_store resolved - Agent 6: Fixed risk-data crate - Type integration and Redis annotations resolved - Agent 7: Fixed service integration - ML training service and async_trait resolved - Agent 8: Fixed workspace integration - Cross-crate dependency resolution resolved - Agent 9: Fixed type system consistency - Split-brain architecture eliminated - Agents 10-16: Implemented comprehensive SQLx traits for all financial types 🎯 **ROOT CAUSES SYSTEMATICALLY RESOLVED:** - Split-brain type system between common and trading_engine - Missing SQLx trait implementations for custom financial types - Workspace dependency version conflicts (SQLite 0.7 vs 0.8) - Import resolution failures and missing config exports - Database serialization gaps for Price, Quantity, OrderStatus, etc. ✅ **VERIFICATION CONFIRMED:** - cargo check --workspace: 0 errors ✅ - cargo check -p ml: 0 errors ✅ - All crates compile successfully with only warnings - Full workspace integration validated 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
696 lines
22 KiB
Rust
696 lines
22 KiB
Rust
//! 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.
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::FromRow;
|
|
use std::collections::HashMap;
|
|
use common::Decimal; // Use common::Decimal for consistency
|
|
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 fn new(pool: sqlx::PgPool) -> Self {
|
|
Self(pool)
|
|
}
|
|
|
|
/// Get the underlying pool
|
|
pub 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<sqlx::PgPool> for DbPool {
|
|
fn from(pool: sqlx::PgPool) -> Self {
|
|
Self::new(pool)
|
|
}
|
|
}
|
|
|
|
impl From<DbPool> 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 fn new(conn: redis::aio::MultiplexedConnection) -> Self {
|
|
Self(conn)
|
|
}
|
|
|
|
/// Get the underlying connection
|
|
pub 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<redis::aio::MultiplexedConnection> for RedisConnection {
|
|
fn from(conn: redis::aio::MultiplexedConnection) -> Self {
|
|
Self::new(conn)
|
|
}
|
|
}
|
|
|
|
impl From<RedisConnection> 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 {
|
|
Equity,
|
|
Bond,
|
|
Commodity,
|
|
Currency,
|
|
Derivative,
|
|
Future,
|
|
Option,
|
|
Swap,
|
|
Cfd,
|
|
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 {
|
|
Equities,
|
|
FixedIncome,
|
|
Commodities,
|
|
Currencies,
|
|
Alternatives,
|
|
Derivatives,
|
|
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,
|
|
Healthcare,
|
|
Financials,
|
|
Energy,
|
|
Consumer,
|
|
Industrials,
|
|
Materials,
|
|
Utilities,
|
|
RealEstate,
|
|
Telecommunications,
|
|
Government,
|
|
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 {
|
|
Exchange,
|
|
Ecn, // Electronic Communication Network
|
|
DarkPool,
|
|
OverTheCounter,
|
|
InternalCross,
|
|
Systematic, // Systematic Internalizer
|
|
}
|
|
|
|
/// 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 {
|
|
Var, // Value at Risk
|
|
ExpectedShortfall, // Conditional VaR
|
|
MaxDrawdown,
|
|
SharpeRatio,
|
|
Beta,
|
|
Volatility,
|
|
Correlation,
|
|
ConcentrationRisk,
|
|
LiquidityRisk,
|
|
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,
|
|
Daily,
|
|
Weekly,
|
|
Monthly,
|
|
Quarterly,
|
|
Yearly,
|
|
}
|
|
|
|
/// Financial instrument master data
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct Instrument {
|
|
pub id: Uuid,
|
|
pub symbol: String,
|
|
pub isin: Option<String>,
|
|
pub cusip: Option<String>,
|
|
pub bloomberg_id: Option<String>,
|
|
pub reuters_id: Option<String>,
|
|
pub name: String,
|
|
pub instrument_type: InstrumentType,
|
|
pub asset_class: AssetClass,
|
|
pub sector: Option<MarketSector>,
|
|
pub currency: String,
|
|
pub exchange: Option<String>,
|
|
pub tick_size: Option<Decimal>,
|
|
pub lot_size: Option<Decimal>,
|
|
pub multiplier: Option<Decimal>,
|
|
pub maturity_date: Option<DateTime<Utc>>,
|
|
pub strike_price: Option<Decimal>,
|
|
pub option_type: Option<String>, // Call/Put for options
|
|
pub underlying_symbol: Option<String>,
|
|
pub is_active: bool,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
pub metadata: serde_json::Value,
|
|
}
|
|
|
|
/// Portfolio definition
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct Portfolio {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub base_currency: String,
|
|
pub portfolio_type: String, // Strategy, Client, Prop, etc.
|
|
pub inception_date: DateTime<Utc>,
|
|
pub manager_id: String,
|
|
pub benchmark: Option<String>,
|
|
pub risk_budget: Option<Decimal>,
|
|
pub var_limit: Option<Decimal>,
|
|
pub max_drawdown_limit: Option<Decimal>,
|
|
pub is_active: bool,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
pub metadata: serde_json::Value,
|
|
}
|
|
|
|
/// Position snapshot for risk calculations
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct Position {
|
|
pub id: Uuid,
|
|
pub portfolio_id: String,
|
|
pub symbol: String,
|
|
pub quantity: Decimal,
|
|
pub average_price: Decimal,
|
|
pub market_price: Decimal,
|
|
pub market_value: Decimal,
|
|
pub unrealized_pnl: Decimal,
|
|
pub currency: String,
|
|
pub entry_date: DateTime<Utc>,
|
|
pub last_updated: DateTime<Utc>,
|
|
pub weight: Option<Decimal>, // Portfolio weight
|
|
pub beta: Option<Decimal>,
|
|
pub duration: Option<Decimal>, // For fixed income
|
|
pub delta: Option<Decimal>, // For derivatives
|
|
pub gamma: Option<Decimal>, // For derivatives
|
|
pub vega: Option<Decimal>, // For derivatives
|
|
pub theta: Option<Decimal>, // For derivatives
|
|
}
|
|
|
|
/// Daily portfolio performance metrics
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct PortfolioPerformance {
|
|
pub id: Uuid,
|
|
pub portfolio_id: String,
|
|
pub date: DateTime<Utc>,
|
|
pub nav: Decimal, // Net Asset Value
|
|
pub daily_return: Decimal,
|
|
pub cumulative_return: Decimal,
|
|
pub volatility: Decimal,
|
|
pub sharpe_ratio: Option<Decimal>,
|
|
pub max_drawdown: Decimal,
|
|
pub var_95: Option<Decimal>,
|
|
pub var_99: Option<Decimal>,
|
|
pub expected_shortfall_95: Option<Decimal>,
|
|
pub beta: Option<Decimal>,
|
|
pub alpha: Option<Decimal>,
|
|
pub information_ratio: Option<Decimal>,
|
|
pub turnover: Option<Decimal>,
|
|
pub largest_position: Option<Decimal>,
|
|
pub number_of_positions: i32,
|
|
pub sector_concentration: serde_json::Value, // Sector exposure breakdown
|
|
pub currency_exposure: serde_json::Value, // Currency exposure breakdown
|
|
}
|
|
|
|
/// Risk factor exposures
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct RiskFactorExposure {
|
|
pub id: Uuid,
|
|
pub portfolio_id: String,
|
|
pub risk_factor: String, // Factor name (e.g., "Equity Market", "Interest Rates")
|
|
pub factor_type: String, // "Market", "Style", "Currency", "Country", etc.
|
|
pub exposure: Decimal, // Factor loading/exposure
|
|
pub contribution_to_risk: Decimal, // Contribution to portfolio variance
|
|
pub date: DateTime<Utc>,
|
|
pub confidence_interval: Option<Decimal>,
|
|
pub r_squared: Option<Decimal>, // Goodness of fit
|
|
}
|
|
|
|
/// Stress test scenarios
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct StressScenario {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub description: String,
|
|
pub scenario_type: String, // Historical, Hypothetical, Monte Carlo
|
|
pub active: bool,
|
|
pub shock_factors: serde_json::Value, // Factor shocks as JSON
|
|
pub created_by: String,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
/// Stress test results
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct StressTestResult {
|
|
pub id: Uuid,
|
|
pub portfolio_id: String,
|
|
pub scenario_id: Uuid,
|
|
pub test_date: DateTime<Utc>,
|
|
pub base_portfolio_value: Decimal,
|
|
pub stressed_portfolio_value: Decimal,
|
|
pub absolute_loss: Decimal,
|
|
pub percentage_loss: Decimal,
|
|
pub worst_performing_position: Option<String>,
|
|
pub worst_position_loss: Option<Decimal>,
|
|
pub sector_impacts: serde_json::Value,
|
|
pub detailed_results: serde_json::Value,
|
|
}
|
|
|
|
/// Counterparty information for counterparty risk
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct Counterparty {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub counterparty_type: String, // Bank, Broker, Exchange, etc.
|
|
pub country: String,
|
|
pub credit_rating: Option<String>,
|
|
pub lei_code: Option<String>, // Legal Entity Identifier
|
|
pub parent_company: Option<String>,
|
|
pub is_active: bool,
|
|
pub exposure_limit: Option<Decimal>,
|
|
pub margin_requirement: Option<Decimal>,
|
|
pub netting_agreement: bool,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
pub metadata: serde_json::Value,
|
|
}
|
|
|
|
/// Counterparty exposure tracking
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct CounterpartyExposure {
|
|
pub id: Uuid,
|
|
pub counterparty_id: String,
|
|
pub portfolio_id: Option<String>,
|
|
pub exposure_type: String, // Current, Potential, Settlement
|
|
pub gross_exposure: Decimal,
|
|
pub net_exposure: Decimal,
|
|
pub collateral_held: Decimal,
|
|
pub collateral_posted: Decimal,
|
|
pub mark_to_market: Decimal,
|
|
pub currency: String,
|
|
pub maturity_bucket: Option<String>, // 0-1Y, 1-5Y, etc.
|
|
pub risk_weight: Option<Decimal>,
|
|
pub date: DateTime<Utc>,
|
|
}
|
|
|
|
/// Liquidity metrics for positions
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct LiquidityMetrics {
|
|
pub id: Uuid,
|
|
pub symbol: String,
|
|
pub date: DateTime<Utc>,
|
|
pub average_daily_volume: Decimal,
|
|
pub bid_ask_spread_bps: Decimal,
|
|
pub market_impact_coefficient: Option<Decimal>,
|
|
pub days_to_liquidate_10pct: Option<Decimal>, // Days to liquidate 10% of ADV
|
|
pub days_to_liquidate_50pct: Option<Decimal>, // Days to liquidate 50% of ADV
|
|
pub liquidity_score: Option<Decimal>, // 1-10 scale
|
|
pub high_frequency_ratio: Option<Decimal>, // HFT volume ratio
|
|
pub dark_pool_ratio: Option<Decimal>, // Dark pool volume ratio
|
|
pub volatility: Decimal,
|
|
pub amihud_illiquidity: Option<Decimal>, // Amihud illiquidity measure
|
|
}
|
|
|
|
/// Economic scenarios for scenario analysis
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct EconomicScenario {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub description: String,
|
|
pub probability: Option<Decimal>, // Probability assignment
|
|
pub time_horizon: TimePeriod,
|
|
pub gdp_growth_rate: Option<Decimal>,
|
|
pub inflation_rate: Option<Decimal>,
|
|
pub interest_rate_change: Option<Decimal>,
|
|
pub unemployment_rate: Option<Decimal>,
|
|
pub currency_shock: serde_json::Value, // Currency pair shocks
|
|
pub commodity_shock: serde_json::Value, // Commodity price shocks
|
|
pub equity_market_shock: serde_json::Value, // Market index shocks
|
|
pub volatility_shock: serde_json::Value, // Volatility regime changes
|
|
pub created_by: String,
|
|
pub created_at: DateTime<Utc>,
|
|
pub is_active: bool,
|
|
}
|
|
|
|
/// Risk report templates
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct RiskReportTemplate {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub description: String,
|
|
pub report_type: String, // Daily, Weekly, Monthly, Regulatory
|
|
pub template_config: serde_json::Value, // Report structure and parameters
|
|
pub recipients: serde_json::Value, // Email distribution list
|
|
pub schedule_cron: Option<String>, // Cron schedule for automated reports
|
|
pub is_active: bool,
|
|
pub created_by: String,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
/// Generated risk reports
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct RiskReport {
|
|
pub id: Uuid,
|
|
pub template_id: Uuid,
|
|
pub portfolio_id: Option<String>,
|
|
pub report_date: DateTime<Utc>,
|
|
pub generated_at: DateTime<Utc>,
|
|
pub generated_by: String,
|
|
pub report_data: serde_json::Value, // Full report content
|
|
pub file_path: Option<String>, // Path to generated PDF/Excel
|
|
pub status: String, // Generated, Sent, Failed
|
|
pub error_message: Option<String>,
|
|
pub recipients_sent: serde_json::Value, // Who received the report
|
|
}
|
|
|
|
/// Market data feeds configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct MarketDataFeed {
|
|
pub id: Uuid,
|
|
pub provider_name: String,
|
|
pub feed_type: String, // Real-time, End-of-day, Historical
|
|
pub symbols_covered: serde_json::Value, // List of symbols
|
|
pub connection_config: serde_json::Value, // Connection parameters
|
|
pub is_primary: bool, // Primary vs backup feed
|
|
pub is_active: bool,
|
|
pub latency_sla_ms: Option<i32>,
|
|
pub uptime_sla_pct: Option<Decimal>,
|
|
pub last_heartbeat: Option<DateTime<Utc>>,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
/// Risk calculation jobs queue
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct RiskCalculationJob {
|
|
pub id: Uuid,
|
|
pub job_type: String, // VaR, StressTest, Scenario, etc.
|
|
pub portfolio_id: Option<String>,
|
|
pub parameters: serde_json::Value, // Job-specific parameters
|
|
pub priority: i32, // Job priority (1-10)
|
|
pub status: String, // Queued, Running, Completed, Failed
|
|
pub started_at: Option<DateTime<Utc>>,
|
|
pub completed_at: Option<DateTime<Utc>>,
|
|
pub progress_pct: Option<Decimal>,
|
|
pub result_data: Option<serde_json::Value>,
|
|
pub error_message: Option<String>,
|
|
pub retry_count: i32,
|
|
pub max_retries: i32,
|
|
pub created_by: String,
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
/// Custom risk metrics configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct CustomRiskMetric {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub description: String,
|
|
pub formula: String, // Mathematical formula or SQL query
|
|
pub parameters: serde_json::Value, // Configurable parameters
|
|
pub output_type: String, // Number, Percentage, Currency
|
|
pub frequency: TimePeriod,
|
|
pub scope: String, // Portfolio, Position, Global
|
|
pub is_active: bool,
|
|
pub created_by: String,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
/// Calculated custom risk metrics
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct CustomRiskMetricResult {
|
|
pub id: Uuid,
|
|
pub metric_id: Uuid,
|
|
pub portfolio_id: Option<String>,
|
|
pub symbol: Option<String>,
|
|
pub calculation_date: DateTime<Utc>,
|
|
pub value: Decimal,
|
|
pub metadata: serde_json::Value, // Additional calculation details
|
|
}
|
|
|
|
/// Common financial calculations and utilities
|
|
#[derive(Debug)]
|
|
pub struct FinancialCalculations;
|
|
|
|
impl FinancialCalculations {
|
|
/// Calculate annualized volatility from daily returns
|
|
pub fn annualized_volatility(daily_vol: Decimal) -> Decimal {
|
|
daily_vol * Decimal::from(16) // sqrt(252) ≈ 15.87, using 16 as approximation
|
|
}
|
|
|
|
/// Calculate Sharpe ratio
|
|
pub fn sharpe_ratio(
|
|
returns: Decimal,
|
|
risk_free_rate: Decimal,
|
|
volatility: Decimal,
|
|
) -> Option<Decimal> {
|
|
if volatility == Decimal::ZERO {
|
|
None
|
|
} else {
|
|
Some((returns - risk_free_rate) / volatility)
|
|
}
|
|
}
|
|
|
|
/// Calculate maximum drawdown
|
|
pub fn max_drawdown(peak: Decimal, trough: Decimal) -> Decimal {
|
|
if peak == Decimal::ZERO {
|
|
Decimal::ZERO
|
|
} else {
|
|
((trough - peak) / peak) * Decimal::from(100)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Portfolio aggregation utilities
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PortfolioSummary {
|
|
pub total_market_value: Decimal,
|
|
pub currency_breakdown: HashMap<String, Decimal>,
|
|
pub sector_breakdown: HashMap<MarketSector, Decimal>,
|
|
pub asset_class_breakdown: HashMap<AssetClass, Decimal>,
|
|
pub top_positions: Vec<(String, Decimal)>, // Symbol, Weight
|
|
pub number_of_positions: usize,
|
|
pub largest_position_weight: Decimal,
|
|
pub effective_number_of_positions: Decimal, // Diversification measure
|
|
pub gross_exposure: Decimal,
|
|
pub net_exposure: Decimal,
|
|
pub beta: Option<Decimal>,
|
|
pub tracking_error: Option<Decimal>,
|
|
}
|
|
|
|
/// Risk factor model utilities
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FactorModel {
|
|
pub model_name: String,
|
|
pub factors: Vec<String>,
|
|
pub factor_loadings: HashMap<String, HashMap<String, Decimal>>, // Symbol -> Factor -> Loading
|
|
pub factor_covariance_matrix: HashMap<String, HashMap<String, Decimal>>,
|
|
pub specific_risks: HashMap<String, Decimal>, // Symbol -> Specific Risk
|
|
pub r_squared: HashMap<String, Decimal>, // Symbol -> R²
|
|
pub last_updated: DateTime<Utc>,
|
|
}
|
|
|
|
/// Validation utilities
|
|
impl Instrument {
|
|
pub fn validate(&self) -> Result<(), String> {
|
|
if self.symbol.is_empty() {
|
|
return Err("Symbol cannot be empty".to_string());
|
|
}
|
|
|
|
if self.name.is_empty() {
|
|
return Err("Instrument name cannot be empty".to_string());
|
|
}
|
|
|
|
if self.currency.len() != 3 {
|
|
return Err("Currency must be 3-character ISO code".to_string());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Portfolio {
|
|
pub fn validate(&self) -> Result<(), String> {
|
|
if self.id.is_empty() {
|
|
return Err("Portfolio ID cannot be empty".to_string());
|
|
}
|
|
|
|
if self.name.is_empty() {
|
|
return Err("Portfolio name cannot be empty".to_string());
|
|
}
|
|
|
|
if self.base_currency.len() != 3 {
|
|
return Err("Base currency must be 3-character ISO code".to_string());
|
|
}
|
|
|
|
if let Some(var_limit) = self.var_limit {
|
|
if var_limit <= Decimal::ZERO {
|
|
return Err("VaR limit must be positive".to_string());
|
|
}
|
|
}
|
|
|
|
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);
|
|
let trough = Decimal::from(85);
|
|
let drawdown = FinancialCalculations::max_drawdown(peak, trough);
|
|
assert_eq!(drawdown, Decimal::from(-15));
|
|
}
|
|
|
|
#[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)),
|
|
multiplier: Some(Decimal::from(1)),
|
|
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!({}),
|
|
};
|
|
|
|
assert!(valid_instrument.validate().is_ok());
|
|
|
|
// 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(100000)),
|
|
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!({}),
|
|
};
|
|
|
|
assert!(valid_portfolio.validate().is_ok());
|
|
|
|
// Test invalid VaR limit
|
|
let invalid_portfolio = Portfolio {
|
|
var_limit: Some(Decimal::from(-1000)),
|
|
..valid_portfolio
|
|
};
|
|
|
|
assert!(invalid_portfolio.validate().is_err());
|
|
}
|
|
}
|