//! Neural Value-at-Risk Models for HFT Risk Management //! //! Implements advanced neural network architectures for VaR estimation, //! Expected Shortfall calculation, and stress testing with canonical types. use chrono::{DateTime, Utc}; use ndarray::{Array1, Array2}; use serde::{Deserialize, Serialize}; // Import types from crate root (which imports from common) use crate::{MLError, MLResult as Result}; use common::types::{Price, Quantity, Symbol}; // AssetId type for VaR models #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AssetId(String); /// Market tick data for VaR calculations #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MarketTick { pub symbol: Symbol, pub price: Price, pub quantity: Quantity, pub timestamp: DateTime, } /// VaR prediction result using canonical types #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VarPrediction { pub asset_id: AssetId, pub var_estimates: Vec, pub expected_shortfall: Vec, pub volatility_forecast: Price, pub model_confidence: Price, pub stress_test_results: Option, } /// Stress test results using canonical types #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StressTestResults { pub stress_var: Price, pub stress_es: Price, pub scenario_name: String, } /// Neural VaR model configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NeuralVarConfig { pub confidence_levels: Vec, pub lookback_period: usize, pub lookback_days: usize, pub monte_carlo_simulations: usize, pub enable_stress_testing: bool, pub hidden_layers: Vec, } impl Default for NeuralVarConfig { fn default() -> Self { Self { confidence_levels: vec![0.95, 0.99, 0.999], lookback_period: 252, lookback_days: 252, monte_carlo_simulations: 10000, enable_stress_testing: true, hidden_layers: vec![128, 64, 32], } } } /// Neural VaR model #[derive(Debug)] pub struct NeuralVarModel { pub config: NeuralVarConfig, weights: Vec>, biases: Vec>, } impl NeuralVarModel { pub fn new(config: NeuralVarConfig) -> Result { let mut weights = Vec::new(); let mut biases = Vec::new(); // Initialize neural network layers let mut prev_size = 100; // Input features size for &hidden_size in &config.hidden_layers { weights.push(Array2::from_elem((hidden_size, prev_size), 0.1)); biases.push(Array1::from_elem(hidden_size, 0.0)); prev_size = hidden_size; } // Output layer for VaR and ES estimates let output_size = config.confidence_levels.len() * 2; weights.push(Array2::from_elem((output_size, prev_size), 0.1)); biases.push(Array1::from_elem(output_size, 0.0)); Ok(Self { config, weights, biases, }) } pub async fn predict_var( &mut self, asset_id: AssetId, _market_data: &[MarketTick], ) -> Result { // Simple VaR calculation for now - production would use full neural network let mut var_estimates = Vec::new(); let mut expected_shortfall = Vec::new(); for confidence in &self.config.confidence_levels { // Production calculations - production would use trained model let var_value = Price::from_f64(*confidence * 0.01).map_err(|e| MLError::ValidationError { message: format!("Invalid VaR price: {}", e), })?; let es_value = Price::from_f64(*confidence * 0.012).map_err(|e| MLError::ValidationError { message: format!("Invalid ES price: {}", e), })?; var_estimates.push(var_value); expected_shortfall.push(es_value); } let stress_test_results = self .config .enable_stress_testing .then(|| { Ok::<_, MLError>(StressTestResults { stress_var: Price::from_f64(0.05).map_err(|e| MLError::ValidationError { message: format!("Invalid stress VaR: {}", e), })?, stress_es: Price::from_f64(0.08).map_err(|e| MLError::ValidationError { message: format!("Invalid stress ES: {}", e), })?, scenario_name: "Market Crash".to_owned(), }) }) .transpose()?; Ok(VarPrediction { asset_id, var_estimates, expected_shortfall, volatility_forecast: Price::from_f64(0.02).map_err(|e| MLError::ValidationError { message: format!("Invalid volatility forecast: {}", e), })?, model_confidence: Price::from_f64(0.95).map_err(|e| MLError::ValidationError { message: format!("Invalid model confidence: {}", e), })?, stress_test_results, }) } } /// VaR features extracted from market data #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VarFeatures { pub returns: Vec, pub volatility: f64, pub volume: f64, pub timestamp: DateTime, } impl VarFeatures { pub fn from_market_data(market_data: &[MarketTick], lookback_period: usize) -> Result { if market_data.is_empty() { return Err(MLError::InvalidInput("Empty market data".to_owned())); } let mut returns = Vec::new(); let data_len = market_data.len().min(lookback_period); // Calculate returns from price data for i in 1..data_len { let prev_price = market_data .get(i - 1) .ok_or_else(|| MLError::ValidationError { message: format!("Index {} out of bounds", i - 1), })? .price .to_f64(); let curr_price = market_data .get(i) .ok_or_else(|| MLError::ValidationError { message: format!("Index {} out of bounds", i), })? .price .to_f64(); let return_val = (curr_price - prev_price) / prev_price; returns.push(return_val); } // Calculate rolling volatility let mean_return = returns.iter().sum::() / returns.len() as f64; let variance = returns .iter() .map(|r| (r - mean_return).powi(2)) .sum::() / returns.len() as f64; let volatility = variance.sqrt(); // Calculate average volume let volume = market_data .iter() .take(data_len) .map(|tick| tick.quantity.to_f64()) .sum::() / data_len as f64; Ok(Self { returns, volatility, volume, timestamp: market_data .last() .ok_or_else(|| MLError::InvalidInput("No market data provided".to_owned()))? .timestamp, }) } pub fn to_feature_vector(&self) -> Array1 { let mut features = Vec::new(); // Add statistical features features.push(self.volatility); features.push(self.volume); // Add recent returns (up to 10) let recent_returns = self .returns .iter() .rev() .take(10) .cloned() .collect::>(); features.extend(recent_returns); // Pad with zeros if needed while features.len() < 100 { features.push(0.0); } Array1::from_vec(features) } } /// Linear layer for neural network #[derive(Debug)] pub struct LinearLayer { weights: Array2, bias: Array1, } impl LinearLayer { pub fn new(input_size: usize, output_size: usize) -> Result { Ok(Self { weights: Array2::from_elem((output_size, input_size), 0.1), bias: Array1::from_elem(output_size, 0.0), }) } pub fn forward(&self, input: &Array1) -> Result> { let output = self.weights.dot(input) + &self.bias; Ok(output) } } /// Feature scaler for normalization #[derive(Debug)] pub struct FeatureScaler { mean: Option>, std: Option>, } impl FeatureScaler { pub fn new() -> Self { Self { mean: None, std: None, } } pub fn fit(&mut self, data: &Array2) -> Result<()> { let mean = data .mean_axis(ndarray::Axis(0)) .ok_or_else(|| MLError::InvalidInput("Cannot compute mean".to_owned()))?; let std = data.std_axis(ndarray::Axis(0), 0.0); self.mean = Some(mean); self.std = Some(std); Ok(()) } pub fn transform(&self, data: &Array1) -> Result> { match (&self.mean, &self.std) { (Some(mean), Some(std)) => { let normalized = (data - mean) / std; Ok(normalized) }, _ => Err(MLError::InvalidInput("Scaler not fitted".to_owned())), } } } #[cfg(test)] #[allow(clippy::assertions_on_result_states)] mod tests { use super::*; #[test] fn test_neural_var_model_creation() -> Result<()> { let config = NeuralVarConfig::default(); let model = NeuralVarModel::new(config); assert!(model.is_ok()); Ok(()) } #[test] fn test_var_features_from_market_data() -> Result<()> { let mut market_data = Vec::new(); let symbol = Symbol::from("AAPL"); for i in 0..10 { market_data.push(MarketTick { symbol: symbol.clone(), price: Price::from_f64(100.0 + i as f64).unwrap(), quantity: Quantity::from_f64(1000.0).unwrap(), timestamp: Utc::now(), }); } let features = VarFeatures::from_market_data(&market_data, 252); assert!(features.is_ok()); let features = features?; assert!(!features.returns.is_empty()); assert!(features.volatility > 0.0); assert!(features.volume > 0.0); Ok(()) } #[test] fn test_linear_layer() -> Result<()> { let layer = LinearLayer::new(10, 5)?; let input = Array1::from_elem(10, 1.0); let output = layer.forward(&input); assert!(output.is_ok()); let output = output?; assert_eq!(output.len(), 5); Ok(()) } #[test] fn test_feature_scaler() { let mut scaler = FeatureScaler::new(); let data = Array2::from_elem((100, 10), 1.0); let fit_result = scaler.fit(&data); assert!(fit_result.is_ok()); let input = Array1::from_elem(10, 1.0); let transformed = scaler.transform(&input); assert!(transformed.is_ok()); } }