Files
foxhunt/risk-data/src/models.rs
jgrusewski 4dfe00b3e0 🎉 COMPLETE SUCCESS: Zero Compilation Errors Achieved Across Entire Workspace
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>
2025-09-27 00:04:07 +02:00

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());
}
}