hyperliquid_backtest/
risk_manager.rs

1//! # Risk Management System
2//!
3//! This module provides risk management functionality for trading strategies,
4//! including position size limits, maximum daily loss protection, stop-loss and
5//! take-profit mechanisms, and leverage limits.
6
7use std::collections::HashMap;
8use chrono::{DateTime, FixedOffset, Utc};
9use thiserror::Error;
10use tracing::{info, warn, error};
11
12use crate::trading_mode::{RiskConfig};
13use crate::unified_data::{Position, OrderRequest, OrderSide, OrderType};
14
15/// Error types specific to risk management operations
16#[derive(Debug, Error)]
17pub enum RiskError {
18    /// Error when position size exceeds limits
19    #[error("Position size exceeds limit: {message}")]
20    PositionSizeExceeded {
21        message: String,
22    },
23    
24    /// Error when daily loss limit is reached
25    #[error("Daily loss limit reached: {current_loss_pct}% exceeds {max_loss_pct}%")]
26    DailyLossLimitReached {
27        current_loss_pct: f64,
28        max_loss_pct: f64,
29    },
30    
31    /// Error when leverage limit is exceeded
32    #[error("Leverage limit exceeded: {current_leverage}x exceeds {max_leverage}x")]
33    LeverageLimitExceeded {
34        current_leverage: f64,
35        max_leverage: f64,
36    },
37    
38    /// Error when margin is insufficient
39    #[error("Insufficient margin: {required_margin} exceeds {available_margin}")]
40    InsufficientMargin {
41        required_margin: f64,
42        available_margin: f64,
43    },
44    
45    /// Error when portfolio concentration limit is exceeded
46    #[error("Portfolio concentration limit exceeded: {asset_class} at {concentration_pct}% exceeds {max_concentration_pct}%")]
47    ConcentrationLimitExceeded {
48        asset_class: String,
49        concentration_pct: f64,
50        max_concentration_pct: f64,
51    },
52    
53    /// Error when position correlation limit is exceeded
54    #[error("Position correlation limit exceeded: {symbol1} and {symbol2} correlation {correlation} exceeds {max_correlation}")]
55    CorrelationLimitExceeded {
56        symbol1: String,
57        symbol2: String,
58        correlation: f64,
59        max_correlation: f64,
60    },
61    
62    /// Error when portfolio volatility limit is exceeded
63    #[error("Portfolio volatility limit exceeded: {current_volatility_pct}% exceeds {max_volatility_pct}%")]
64    VolatilityLimitExceeded {
65        current_volatility_pct: f64,
66        max_volatility_pct: f64,
67    },
68    
69    /// Error when drawdown limit is exceeded
70    #[error("Drawdown limit exceeded: {current_drawdown_pct}% exceeds {max_drawdown_pct}%")]
71    DrawdownLimitExceeded {
72        current_drawdown_pct: f64,
73        max_drawdown_pct: f64,
74    },
75    
76    /// General risk management error
77    #[error("Risk management error: {0}")]
78    General(String),
79}
80
81/// Result type for risk management operations
82pub type Result<T> = std::result::Result<T, RiskError>;
83
84/// Stop-loss or take-profit order
85#[derive(Debug, Clone)]
86pub struct RiskOrder {
87    /// Original order ID this risk order is associated with
88    pub parent_order_id: String,
89    
90    /// Symbol/ticker of the asset
91    pub symbol: String,
92    
93    /// Order side (buy/sell)
94    pub side: OrderSide,
95    
96    /// Order type
97    pub order_type: OrderType,
98    
99    /// Order quantity
100    pub quantity: f64,
101    
102    /// Trigger price
103    pub trigger_price: f64,
104    
105    /// Whether this is a stop-loss order
106    pub is_stop_loss: bool,
107    
108    /// Whether this is a take-profit order
109    pub is_take_profit: bool,
110}
111
112/// Daily risk tracking
113#[derive(Debug, Clone)]
114struct DailyRiskTracker {
115    /// Date of tracking
116    date: chrono::NaiveDate,
117    
118    /// Starting portfolio value
119    starting_value: f64,
120    
121    /// Current portfolio value
122    current_value: f64,
123    
124    /// Realized profit/loss for the day
125    realized_pnl: f64,
126    
127    /// Unrealized profit/loss for the day
128    unrealized_pnl: f64,
129    
130    /// Maximum drawdown for the day
131    max_drawdown: f64,
132    
133    /// Highest portfolio value for the day
134    highest_value: f64,
135}
136
137impl DailyRiskTracker {
138    /// Create a new daily risk tracker
139    fn new(portfolio_value: f64) -> Self {
140        Self {
141            date: Utc::now().date_naive(),
142            starting_value: portfolio_value,
143            current_value: portfolio_value,
144            realized_pnl: 0.0,
145            unrealized_pnl: 0.0,
146            max_drawdown: 0.0,
147            highest_value: portfolio_value,
148        }
149    }
150    
151    /// Update the tracker with new portfolio value
152    fn update(&mut self, portfolio_value: f64, realized_pnl_delta: f64) {
153        self.current_value = portfolio_value;
154        self.realized_pnl += realized_pnl_delta;
155        self.unrealized_pnl = portfolio_value - self.starting_value - self.realized_pnl;
156        
157        // Update highest value if needed
158        if portfolio_value > self.highest_value {
159            self.highest_value = portfolio_value;
160        }
161        
162        // Update max drawdown if needed
163        let current_drawdown = (self.highest_value - portfolio_value) / self.highest_value;
164        if current_drawdown > self.max_drawdown {
165            self.max_drawdown = current_drawdown;
166        }
167    }
168    
169    /// Check if the daily loss limit is reached
170    fn is_daily_loss_limit_reached(&self, max_daily_loss_pct: f64) -> bool {
171        let daily_loss_pct = (self.starting_value - self.current_value) / self.starting_value * 100.0;
172        daily_loss_pct >= max_daily_loss_pct
173    }
174    
175    /// Get the current daily loss percentage
176    fn daily_loss_pct(&self) -> f64 {
177        (self.starting_value - self.current_value) / self.starting_value * 100.0
178    }
179    
180    /// Reset the tracker for a new day
181    fn reset(&mut self, portfolio_value: f64) {
182        self.date = Utc::now().date_naive();
183        self.starting_value = portfolio_value;
184        self.current_value = portfolio_value;
185        self.realized_pnl = 0.0;
186        self.unrealized_pnl = 0.0;
187        self.max_drawdown = 0.0;
188        self.highest_value = portfolio_value;
189    }
190}
191
192/// Asset class for correlation and concentration management
193#[derive(Debug, Clone, PartialEq, Eq, Hash)]
194pub enum AssetClass {
195    /// Cryptocurrencies
196    Crypto,
197    
198    /// Stablecoins
199    Stablecoin,
200    
201    /// Defi tokens
202    Defi,
203    
204    /// Layer 1 blockchain tokens
205    Layer1,
206    
207    /// Layer 2 scaling solution tokens
208    Layer2,
209    
210    /// Meme coins
211    Meme,
212    
213    /// NFT related tokens
214    NFT,
215    
216    /// Gaming tokens
217    Gaming,
218    
219    /// Other tokens
220    Other,
221}
222
223/// Historical volatility data for an asset
224#[derive(Debug, Clone)]
225pub struct VolatilityData {
226    /// Symbol of the asset
227    pub symbol: String,
228    
229    /// Daily volatility (percentage)
230    pub daily_volatility: f64,
231    
232    /// Weekly volatility (percentage)
233    pub weekly_volatility: f64,
234    
235    /// Monthly volatility (percentage)
236    pub monthly_volatility: f64,
237    
238    /// Historical price data for volatility calculation
239    pub price_history: Vec<f64>,
240    
241    /// Last update timestamp
242    pub last_update: DateTime<FixedOffset>,
243}
244
245/// Correlation data between two assets
246#[derive(Debug, Clone)]
247pub struct CorrelationData {
248    /// First symbol
249    pub symbol1: String,
250    
251    /// Second symbol
252    pub symbol2: String,
253    
254    /// Correlation coefficient (-1.0 to 1.0)
255    pub correlation: f64,
256    
257    /// Last update timestamp
258    pub last_update: DateTime<FixedOffset>,
259}
260
261/// Portfolio metrics for risk management
262#[derive(Debug, Clone)]
263pub struct PortfolioMetrics {
264    /// Portfolio value
265    pub value: f64,
266    
267    /// Portfolio volatility (percentage)
268    pub volatility: f64,
269    
270    /// Maximum drawdown (percentage)
271    pub max_drawdown: f64,
272    
273    /// Value at Risk (VaR) at 95% confidence
274    pub var_95: f64,
275    
276    /// Value at Risk (VaR) at 99% confidence
277    pub var_99: f64,
278    
279    /// Concentration by asset class
280    pub concentration: HashMap<AssetClass, f64>,
281}
282
283/// Risk manager for trading strategies
284#[derive(Debug)]
285pub struct RiskManager {
286    /// Risk configuration
287    config: RiskConfig,
288    
289    /// Current portfolio value
290    portfolio_value: f64,
291    
292    /// Available margin
293    available_margin: f64,
294    
295    /// Daily risk tracker
296    daily_tracker: DailyRiskTracker,
297    
298    /// Stop-loss orders
299    stop_loss_orders: HashMap<String, RiskOrder>,
300    
301    /// Take-profit orders
302    take_profit_orders: HashMap<String, RiskOrder>,
303    
304    /// Emergency stop flag
305    emergency_stop: bool,
306    
307    /// Asset class mapping
308    asset_classes: HashMap<String, AssetClass>,
309    
310    /// Volatility data by symbol
311    volatility_data: HashMap<String, VolatilityData>,
312    
313    /// Correlation data between symbols
314    correlation_data: HashMap<(String, String), CorrelationData>,
315    
316    /// Portfolio metrics
317    portfolio_metrics: PortfolioMetrics,
318    
319    /// Historical portfolio values for drawdown calculation
320    historical_portfolio_values: Vec<(DateTime<FixedOffset>, f64)>,
321}
322
323impl RiskManager {
324    /// Create a new risk manager with the specified configuration
325    pub fn new(config: RiskConfig, initial_portfolio_value: f64) -> Self {
326        let available_margin = initial_portfolio_value;
327        let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
328        
329        Self {
330            config,
331            portfolio_value: initial_portfolio_value,
332            available_margin,
333            daily_tracker: DailyRiskTracker::new(initial_portfolio_value),
334            stop_loss_orders: HashMap::new(),
335            take_profit_orders: HashMap::new(),
336            emergency_stop: false,
337            asset_classes: Self::default_asset_classes(),
338            volatility_data: HashMap::new(),
339            correlation_data: HashMap::new(),
340            portfolio_metrics: PortfolioMetrics {
341                value: initial_portfolio_value,
342                volatility: 0.0,
343                max_drawdown: 0.0,
344                var_95: 0.0,
345                var_99: 0.0,
346                concentration: HashMap::new(),
347            },
348            historical_portfolio_values: vec![(now, initial_portfolio_value)],
349        }
350    }
351    
352    /// Create a new risk manager with default configuration
353    pub fn default(initial_portfolio_value: f64) -> Self {
354        Self::new(RiskConfig::default(), initial_portfolio_value)
355    }
356    
357    /// Create default asset class mappings
358    fn default_asset_classes() -> HashMap<String, AssetClass> {
359        let mut map = HashMap::new();
360        
361        // Major cryptocurrencies
362        map.insert("BTC".to_string(), AssetClass::Crypto);
363        map.insert("ETH".to_string(), AssetClass::Crypto);
364        map.insert("BNB".to_string(), AssetClass::Crypto);
365        map.insert("SOL".to_string(), AssetClass::Crypto);
366        map.insert("XRP".to_string(), AssetClass::Crypto);
367        map.insert("ADA".to_string(), AssetClass::Crypto);
368        map.insert("AVAX".to_string(), AssetClass::Crypto);
369        
370        // Stablecoins
371        map.insert("USDT".to_string(), AssetClass::Stablecoin);
372        map.insert("USDC".to_string(), AssetClass::Stablecoin);
373        map.insert("DAI".to_string(), AssetClass::Stablecoin);
374        map.insert("BUSD".to_string(), AssetClass::Stablecoin);
375        
376        // DeFi tokens
377        map.insert("UNI".to_string(), AssetClass::Defi);
378        map.insert("AAVE".to_string(), AssetClass::Defi);
379        map.insert("MKR".to_string(), AssetClass::Defi);
380        map.insert("COMP".to_string(), AssetClass::Defi);
381        map.insert("SNX".to_string(), AssetClass::Defi);
382        map.insert("SUSHI".to_string(), AssetClass::Defi);
383        
384        // Layer 1 blockchains
385        map.insert("DOT".to_string(), AssetClass::Layer1);
386        map.insert("ATOM".to_string(), AssetClass::Layer1);
387        map.insert("NEAR".to_string(), AssetClass::Layer1);
388        map.insert("ALGO".to_string(), AssetClass::Layer1);
389        
390        // Layer 2 solutions
391        map.insert("MATIC".to_string(), AssetClass::Layer2);
392        map.insert("LRC".to_string(), AssetClass::Layer2);
393        map.insert("OMG".to_string(), AssetClass::Layer2);
394        map.insert("IMX".to_string(), AssetClass::Layer2);
395        
396        // Meme coins
397        map.insert("DOGE".to_string(), AssetClass::Meme);
398        map.insert("SHIB".to_string(), AssetClass::Meme);
399        map.insert("PEPE".to_string(), AssetClass::Meme);
400        
401        // NFT related
402        map.insert("APE".to_string(), AssetClass::NFT);
403        map.insert("SAND".to_string(), AssetClass::NFT);
404        map.insert("MANA".to_string(), AssetClass::NFT);
405        
406        // Gaming
407        map.insert("AXS".to_string(), AssetClass::Gaming);
408        map.insert("ENJ".to_string(), AssetClass::Gaming);
409        map.insert("GALA".to_string(), AssetClass::Gaming);
410        
411        map
412    }
413    
414    /// Get the current risk configuration
415    pub fn config(&self) -> &RiskConfig {
416        &self.config
417    }
418    
419    /// Update the risk configuration
420    pub fn update_config(&mut self, config: RiskConfig) {
421        info!("Updating risk configuration");
422        self.config = config;
423    }
424    
425    /// Update portfolio value and check daily risk limits
426    pub fn update_portfolio_value(&mut self, new_value: f64, realized_pnl_delta: f64) -> Result<()> {
427        // Check if we need to reset the daily tracker (new day)
428        let current_date = Utc::now().date_naive();
429        if current_date != self.daily_tracker.date {
430            info!("New trading day, resetting daily risk tracker");
431            self.daily_tracker.reset(new_value);
432        } else {
433            // Update the daily tracker
434            self.daily_tracker.update(new_value, realized_pnl_delta);
435            
436            // Check if daily loss limit is reached
437            if self.daily_tracker.is_daily_loss_limit_reached(self.config.max_daily_loss_pct) {
438                let daily_loss_pct = self.daily_tracker.daily_loss_pct();
439                warn!("Daily loss limit reached: {:.2}% exceeds {:.2}%", 
440                      daily_loss_pct, self.config.max_daily_loss_pct);
441                
442                // Set emergency stop
443                self.emergency_stop = true;
444                
445                return Err(RiskError::DailyLossLimitReached {
446                    current_loss_pct: daily_loss_pct,
447                    max_loss_pct: self.config.max_daily_loss_pct,
448                });
449            }
450        }
451        
452        // Update portfolio value and available margin
453        self.portfolio_value = new_value;
454        self.available_margin = new_value; // Simplified, in reality would depend on existing positions
455        
456        Ok(())
457    }
458    
459    /// Validate an order against risk limits
460    pub fn validate_order(&self, order: &OrderRequest, current_positions: &HashMap<String, Position>) -> Result<()> {
461        // Check if emergency stop is active
462        if self.emergency_stop {
463            return Err(RiskError::General("Emergency stop is active".to_string()));
464        }
465        
466        // Check daily loss limit
467        if self.daily_tracker.is_daily_loss_limit_reached(self.config.max_daily_loss_pct) {
468            let daily_loss_pct = self.daily_tracker.daily_loss_pct();
469            return Err(RiskError::DailyLossLimitReached {
470                current_loss_pct: daily_loss_pct,
471                max_loss_pct: self.config.max_daily_loss_pct,
472            });
473        }
474        
475        // Check drawdown limit
476        if self.portfolio_metrics.max_drawdown > self.config.max_drawdown_pct {
477            return Err(RiskError::DrawdownLimitExceeded {
478                current_drawdown_pct: self.portfolio_metrics.max_drawdown * 100.0,
479                max_drawdown_pct: self.config.max_drawdown_pct * 100.0,
480            });
481        }
482        
483        // Calculate order value
484        let order_value = match order.price {
485            Some(price) => order.quantity * price,
486            None => {
487                // For market orders, we need to estimate the price
488                // In a real implementation, we would use the current market price
489                // For simplicity, we'll use the current position price if available
490                if let Some(position) = current_positions.get(&order.symbol) {
491                    order.quantity * position.current_price
492                } else {
493                    // If no position exists, we can't validate the order properly
494                    return Err(RiskError::General(
495                        "Cannot validate market order without price information".to_string()
496                    ));
497                }
498            }
499        };
500        
501        // Check position size limit
502        let max_position_value = self.portfolio_value * self.config.max_position_size_pct;
503        
504        // Calculate the total position value after this order
505        let mut new_position_value = order_value;
506        if let Some(position) = current_positions.get(&order.symbol) {
507            // Add existing position value
508            new_position_value = match order.side {
509                OrderSide::Buy => position.size.abs() * position.current_price + order_value,
510                OrderSide::Sell => {
511                    if order.quantity <= position.size {
512                        // Reducing position
513                        (position.size - order.quantity).abs() * position.current_price
514                    } else {
515                        // Flipping position
516                        (order.quantity - position.size).abs() * position.current_price
517                    }
518                }
519            };
520        }
521        
522        // Apply volatility-based position sizing if volatility data is available
523        if let Some(volatility_data) = self.volatility_data.get(&order.symbol) {
524            let volatility_adjusted_max_size = self.calculate_volatility_adjusted_position_size(
525                &order.symbol, 
526                max_position_value
527            );
528            
529            if new_position_value > volatility_adjusted_max_size {
530                return Err(RiskError::PositionSizeExceeded {
531                    message: format!(
532                        "Position value ${:.2} exceeds volatility-adjusted limit ${:.2}",
533                        new_position_value, volatility_adjusted_max_size
534                    ),
535                });
536            }
537        } else if new_position_value > max_position_value {
538            return Err(RiskError::PositionSizeExceeded {
539                message: format!(
540                    "Position value ${:.2} exceeds limit ${:.2} ({:.2}% of portfolio)",
541                    new_position_value, max_position_value, self.config.max_position_size_pct * 100.0
542                ),
543            });
544        }
545        
546        // Check leverage limit
547        let total_position_value = current_positions.values()
548            .map(|p| p.size.abs() * p.current_price)
549            .sum::<f64>() + order_value;
550        
551        let current_leverage = total_position_value / self.portfolio_value;
552        if current_leverage > self.config.max_leverage {
553            return Err(RiskError::LeverageLimitExceeded {
554                current_leverage,
555                max_leverage: self.config.max_leverage,
556            });
557        }
558        
559        // Check margin requirements
560        let required_margin = total_position_value / self.config.max_leverage;
561        if required_margin > self.available_margin {
562            return Err(RiskError::InsufficientMargin {
563                required_margin,
564                available_margin: self.available_margin,
565            });
566        }
567        
568        // Check portfolio concentration limits
569        if let Some(asset_class) = self.get_asset_class(&order.symbol) {
570            let new_concentration = self.calculate_concentration_after_order(
571                current_positions, 
572                order, 
573                asset_class
574            );
575            
576            if new_concentration > self.config.max_concentration_pct {
577                return Err(RiskError::ConcentrationLimitExceeded {
578                    asset_class: format!("{:?}", asset_class),
579                    concentration_pct: new_concentration * 100.0,
580                    max_concentration_pct: self.config.max_concentration_pct * 100.0,
581                });
582            }
583        }
584        
585        // Check correlation limits
586        if let Err(e) = self.validate_correlation_limits(current_positions, order) {
587            return Err(e);
588        }
589        
590        // Check portfolio volatility limits
591        if let Err(e) = self.validate_portfolio_volatility(current_positions, order) {
592            return Err(e);
593        }
594        
595        Ok(())
596    }
597    
598    /// Generate stop-loss order for a position
599    pub fn generate_stop_loss(&self, position: &Position, parent_order_id: &str) -> Option<RiskOrder> {
600        if position.size == 0.0 {
601            return None;
602        }
603        
604        // Calculate stop loss price
605        let stop_loss_price = if position.size > 0.0 {
606            // Long position
607            position.entry_price * (1.0 - self.config.stop_loss_pct)
608        } else {
609            // Short position
610            position.entry_price * (1.0 + self.config.stop_loss_pct)
611        };
612        
613        // Create stop loss order
614        Some(RiskOrder {
615            parent_order_id: parent_order_id.to_string(),
616            symbol: position.symbol.clone(),
617            side: if position.size > 0.0 { OrderSide::Sell } else { OrderSide::Buy },
618            order_type: OrderType::StopMarket,
619            quantity: position.size.abs(),
620            trigger_price: stop_loss_price,
621            is_stop_loss: true,
622            is_take_profit: false,
623        })
624    }
625    
626    /// Generate take-profit order for a position
627    pub fn generate_take_profit(&self, position: &Position, parent_order_id: &str) -> Option<RiskOrder> {
628        if position.size == 0.0 {
629            return None;
630        }
631        
632        // Calculate take profit price
633        let take_profit_price = if position.size > 0.0 {
634            // Long position
635            position.entry_price * (1.0 + self.config.take_profit_pct)
636        } else {
637            // Short position
638            position.entry_price * (1.0 - self.config.take_profit_pct)
639        };
640        
641        // Create take profit order
642        Some(RiskOrder {
643            parent_order_id: parent_order_id.to_string(),
644            symbol: position.symbol.clone(),
645            side: if position.size > 0.0 { OrderSide::Sell } else { OrderSide::Buy },
646            order_type: OrderType::TakeProfitMarket,
647            quantity: position.size.abs(),
648            trigger_price: take_profit_price,
649            is_stop_loss: false,
650            is_take_profit: true,
651        })
652    }
653    
654    /// Register a stop-loss order
655    pub fn register_stop_loss(&mut self, order: RiskOrder) {
656        self.stop_loss_orders.insert(order.parent_order_id.clone(), order);
657    }
658    
659    /// Register a take-profit order
660    pub fn register_take_profit(&mut self, order: RiskOrder) {
661        self.take_profit_orders.insert(order.parent_order_id.clone(), order);
662    }
663    
664    /// Check if any stop-loss or take-profit orders should be triggered
665    pub fn check_risk_orders(&mut self, current_prices: &HashMap<String, f64>) -> Vec<RiskOrder> {
666        let mut triggered_orders = Vec::new();
667        
668        // Check stop-loss orders
669        let mut triggered_stop_loss_ids = Vec::new();
670        for (id, order) in &self.stop_loss_orders {
671            if let Some(&current_price) = current_prices.get(&order.symbol) {
672                let should_trigger = match order.side {
673                    OrderSide::Sell => current_price <= order.trigger_price,
674                    OrderSide::Buy => current_price >= order.trigger_price,
675                };
676                
677                if should_trigger {
678                    triggered_orders.push(order.clone());
679                    triggered_stop_loss_ids.push(id.clone());
680                }
681            }
682        }
683        
684        // Remove triggered stop-loss orders
685        for id in triggered_stop_loss_ids {
686            self.stop_loss_orders.remove(&id);
687        }
688        
689        // Check take-profit orders
690        let mut triggered_take_profit_ids = Vec::new();
691        for (id, order) in &self.take_profit_orders {
692            if let Some(&current_price) = current_prices.get(&order.symbol) {
693                let should_trigger = match order.side {
694                    OrderSide::Sell => current_price >= order.trigger_price,
695                    OrderSide::Buy => current_price <= order.trigger_price,
696                };
697                
698                if should_trigger {
699                    triggered_orders.push(order.clone());
700                    triggered_take_profit_ids.push(id.clone());
701                }
702            }
703        }
704        
705        // Remove triggered take-profit orders
706        for id in triggered_take_profit_ids {
707            self.take_profit_orders.remove(&id);
708        }
709        
710        triggered_orders
711    }
712    
713    /// Check if trading should be stopped due to risk limits
714    pub fn should_stop_trading(&self) -> bool {
715        self.emergency_stop || 
716        self.daily_tracker.is_daily_loss_limit_reached(self.config.max_daily_loss_pct)
717    }
718    
719    /// Activate emergency stop
720    pub fn activate_emergency_stop(&mut self) {
721        warn!("Emergency stop activated");
722        self.emergency_stop = true;
723    }
724    
725    /// Deactivate emergency stop
726    pub fn deactivate_emergency_stop(&mut self) {
727        info!("Emergency stop deactivated");
728        self.emergency_stop = false;
729    }
730    
731    /// Get current daily risk metrics
732    pub fn daily_risk_metrics(&self) -> (f64, f64, f64) {
733        (
734            self.daily_tracker.daily_loss_pct(),
735            self.daily_tracker.max_drawdown * 100.0,
736            (self.daily_tracker.realized_pnl / self.daily_tracker.starting_value) * 100.0
737        )
738    }
739    
740    /// Calculate required margin for a position
741    pub fn calculate_required_margin(&self, position_value: f64) -> f64 {
742        position_value / self.config.max_leverage
743    }
744    
745    /// Get available margin
746    pub fn available_margin(&self) -> f64 {
747        self.available_margin
748    }
749    
750    /// Update available margin
751    pub fn update_available_margin(&mut self, margin: f64) {
752        self.available_margin = margin;
753    }
754    
755    /// Calculate volatility-adjusted position size
756    pub fn calculate_volatility_adjusted_position_size(&self, symbol: &str, base_max_size: f64) -> f64 {
757        if let Some(volatility_data) = self.volatility_data.get(symbol) {
758            // Adjust position size based on volatility
759            // Higher volatility = smaller position size
760            let volatility_factor = 1.0 / (1.0 + volatility_data.daily_volatility / 100.0);
761            base_max_size * volatility_factor
762        } else {
763            // If no volatility data, use base max size
764            base_max_size
765        }
766    }
767    
768    /// Get asset class for a symbol
769    pub fn get_asset_class(&self, symbol: &str) -> Option<&AssetClass> {
770        self.asset_classes.get(symbol)
771    }
772    
773    /// Calculate concentration after order
774    pub fn calculate_concentration_after_order(
775        &self,
776        current_positions: &HashMap<String, Position>,
777        order: &OrderRequest,
778        asset_class: &AssetClass,
779    ) -> f64 {
780        let mut total_value = 0.0;
781        let mut asset_class_value = 0.0;
782        
783        // Calculate current portfolio value by asset class
784        for position in current_positions.values() {
785            let position_value = position.size.abs() * position.current_price;
786            total_value += position_value;
787            
788            if let Some(pos_asset_class) = self.asset_classes.get(&position.symbol) {
789                if pos_asset_class == asset_class {
790                    asset_class_value += position_value;
791                }
792            }
793        }
794        
795        // Add the new order value if it's the same asset class
796        if let Some(order_asset_class) = self.asset_classes.get(&order.symbol) {
797            if order_asset_class == asset_class {
798                let order_value = match order.price {
799                    Some(price) => order.quantity * price,
800                    None => {
801                        // Estimate using current position price if available
802                        if let Some(position) = current_positions.get(&order.symbol) {
803                            order.quantity * position.current_price
804                        } else {
805                            0.0 // Can't calculate without price
806                        }
807                    }
808                };
809                asset_class_value += order_value;
810            }
811        }
812        
813        // Add the order value to total
814        let order_value = match order.price {
815            Some(price) => order.quantity * price,
816            None => {
817                if let Some(position) = current_positions.get(&order.symbol) {
818                    order.quantity * position.current_price
819                } else {
820                    0.0
821                }
822            }
823        };
824        total_value += order_value;
825        
826        if total_value > 0.0 {
827            asset_class_value / total_value
828        } else {
829            0.0
830        }
831    }
832    
833    /// Validate correlation limits
834    pub fn validate_correlation_limits(
835        &self,
836        current_positions: &HashMap<String, Position>,
837        order: &OrderRequest,
838    ) -> Result<()> {
839        // Check correlation with existing positions
840        for position in current_positions.values() {
841            if position.symbol != order.symbol {
842                // Check if we have correlation data for this pair
843                let key1 = (position.symbol.clone(), order.symbol.clone());
844                let key2 = (order.symbol.clone(), position.symbol.clone());
845                
846                if let Some(correlation_data) = self.correlation_data.get(&key1)
847                    .or_else(|| self.correlation_data.get(&key2)) {
848                    
849                    // If correlation is too high and positions are in same direction, reject
850                    if correlation_data.correlation.abs() > self.config.max_correlation_pct {
851                        let position_direction = if position.size > 0.0 { 1.0 } else { -1.0 };
852                        let order_direction = match order.side {
853                            OrderSide::Buy => 1.0,
854                            OrderSide::Sell => -1.0,
855                        };
856                        
857                        // If same direction and high correlation, it's risky
858                        if position_direction * order_direction > 0.0 {
859                            return Err(RiskError::CorrelationLimitExceeded {
860                                symbol1: position.symbol.clone(),
861                                symbol2: order.symbol.clone(),
862                                correlation: correlation_data.correlation,
863                                max_correlation: self.config.max_correlation_pct,
864                            });
865                        }
866                    }
867                }
868            }
869        }
870        
871        Ok(())
872    }
873    
874    /// Validate portfolio volatility
875    pub fn validate_portfolio_volatility(
876        &self,
877        current_positions: &HashMap<String, Position>,
878        order: &OrderRequest,
879    ) -> Result<()> {
880        // Calculate portfolio volatility after adding the order
881        let mut portfolio_variance = 0.0;
882        let mut total_value = 0.0;
883        
884        // Current positions contribution to volatility
885        for position in current_positions.values() {
886            if let Some(volatility_data) = self.volatility_data.get(&position.symbol) {
887                let position_value = position.size.abs() * position.current_price;
888                let weight = position_value / self.portfolio_value;
889                portfolio_variance += (weight * volatility_data.daily_volatility / 100.0).powi(2);
890                total_value += position_value;
891            }
892        }
893        
894        // Add new order contribution
895        if let Some(volatility_data) = self.volatility_data.get(&order.symbol) {
896            let order_value = match order.price {
897                Some(price) => order.quantity * price,
898                None => {
899                    if let Some(position) = current_positions.get(&order.symbol) {
900                        order.quantity * position.current_price
901                    } else {
902                        return Ok(()); // Can't validate without price
903                    }
904                }
905            };
906            
907            let new_total_value = total_value + order_value;
908            let weight = order_value / new_total_value;
909            portfolio_variance += (weight * volatility_data.daily_volatility / 100.0).powi(2);
910        }
911        
912        let portfolio_volatility = portfolio_variance.sqrt() * 100.0; // Convert to percentage
913        
914        if portfolio_volatility > self.config.max_portfolio_volatility_pct {
915            return Err(RiskError::VolatilityLimitExceeded {
916                current_volatility_pct: portfolio_volatility,
917                max_volatility_pct: self.config.max_portfolio_volatility_pct,
918            });
919        }
920        
921        Ok(())
922    }
923}
924
925#[cfg(test)]
926mod tests {
927    use super::*;
928    use chrono::TimeZone;
929    
930    fn create_test_position(symbol: &str, size: f64, entry_price: f64, current_price: f64) -> Position {
931        Position {
932            symbol: symbol.to_string(),
933            size,
934            entry_price,
935            current_price,
936            unrealized_pnl: (current_price - entry_price) * size,
937            realized_pnl: 0.0,
938            funding_pnl: 0.0,
939            timestamp: Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap()),
940        }
941    }
942    
943    fn create_test_order(symbol: &str, side: OrderSide, quantity: f64, price: Option<f64>) -> OrderRequest {
944        OrderRequest {
945            symbol: symbol.to_string(),
946            side,
947            order_type: OrderType::Limit,
948            quantity,
949            price,
950            reduce_only: false,
951            time_in_force: crate::trading_mode_impl::TimeInForce::GoodTillCancel,
952        }
953    }
954    
955    #[test]
956    fn test_position_size_validation() {
957        let config = RiskConfig {
958            max_position_size_pct: 0.1,  // 10% of portfolio
959            max_daily_loss_pct: 0.02,    // 2% max daily loss
960            stop_loss_pct: 0.05,         // 5% stop loss
961            take_profit_pct: 0.1,        // 10% take profit
962            max_leverage: 3.0,           // 3x max leverage
963        };
964        
965        let portfolio_value = 10000.0;
966        let mut risk_manager = RiskManager::new(config, portfolio_value);
967        
968        let mut positions = HashMap::new();
969        
970        // Test valid order within position size limit
971        let order = create_test_order("BTC", OrderSide::Buy, 0.1, Some(9000.0));
972        // Order value: 0.1 * 9000 = 900, which is < 10% of 10000 (1000)
973        assert!(risk_manager.validate_order(&order, &positions).is_ok());
974        
975        // Test order exceeding position size limit
976        let order = create_test_order("BTC", OrderSide::Buy, 0.2, Some(9000.0));
977        // Order value: 0.2 * 9000 = 1800, which is > 10% of 10000 (1000)
978        assert!(risk_manager.validate_order(&order, &positions).is_err());
979        
980        // Test with existing position
981        positions.insert(
982            "BTC".to_string(),
983            create_test_position("BTC", 0.05, 8000.0, 9000.0)
984        );
985        
986        // Test valid order with existing position
987        let order = create_test_order("BTC", OrderSide::Buy, 0.05, Some(9000.0));
988        // Existing position value: 0.05 * 9000 = 450
989        // Order value: 0.05 * 9000 = 450
990        // Total: 900, which is < 10% of 10000 (1000)
991        assert!(risk_manager.validate_order(&order, &positions).is_ok());
992        
993        // Test order exceeding position size limit with existing position
994        let order = create_test_order("BTC", OrderSide::Buy, 0.07, Some(9000.0));
995        // Existing position value: 0.05 * 9000 = 450
996        // Order value: 0.07 * 9000 = 630
997        // Total: 1080, which is > 10% of 10000 (1000)
998        assert!(risk_manager.validate_order(&order, &positions).is_err());
999    }
1000    
1001    #[test]
1002    fn test_leverage_validation() {
1003        let config = RiskConfig {
1004            max_position_size_pct: 0.5,  // 50% of portfolio (high to test leverage)
1005            max_daily_loss_pct: 0.02,    // 2% max daily loss
1006            stop_loss_pct: 0.05,         // 5% stop loss
1007            take_profit_pct: 0.1,        // 10% take profit
1008            max_leverage: 2.0,           // 2x max leverage
1009        };
1010        
1011        let portfolio_value = 10000.0;
1012        let mut risk_manager = RiskManager::new(config, portfolio_value);
1013        
1014        let mut positions = HashMap::new();
1015        positions.insert(
1016            "ETH".to_string(),
1017            create_test_position("ETH", 2.0, 1500.0, 1600.0)
1018        );
1019        // ETH position value: 2.0 * 1600 = 3200
1020        
1021        // Test valid order within leverage limit
1022        let order = create_test_order("BTC", OrderSide::Buy, 0.1, Some(9000.0));
1023        // Order value: 0.1 * 9000 = 900
1024        // Total position value: 3200 + 900 = 4100
1025        // Leverage: 4100 / 10000 = 0.41, which is < 2.0
1026        assert!(risk_manager.validate_order(&order, &positions).is_ok());
1027        
1028        // Test order exceeding leverage limit
1029        let order = create_test_order("BTC", OrderSide::Buy, 2.0, Some(9000.0));
1030        // Order value: 2.0 * 9000 = 18000
1031        // Total position value: 3200 + 18000 = 21200
1032        // Leverage: 21200 / 10000 = 2.12, which is > 2.0
1033        assert!(risk_manager.validate_order(&order, &positions).is_err());
1034    }
1035    
1036    #[test]
1037    fn test_daily_loss_limit() {
1038        let config = RiskConfig {
1039            max_position_size_pct: 0.1,  // 10% of portfolio
1040            max_daily_loss_pct: 2.0,     // 2% max daily loss
1041            stop_loss_pct: 0.05,         // 5% stop loss
1042            take_profit_pct: 0.1,        // 10% take profit
1043            max_leverage: 3.0,           // 3x max leverage
1044        };
1045        
1046        let portfolio_value = 10000.0;
1047        let mut risk_manager = RiskManager::new(config, portfolio_value);
1048        
1049        // Update portfolio value with small loss (1%)
1050        assert!(risk_manager.update_portfolio_value(9900.0, -100.0).is_ok());
1051        
1052        // Verify daily loss is tracked correctly
1053        let (daily_loss_pct, _, _) = risk_manager.daily_risk_metrics();
1054        assert_eq!(daily_loss_pct, 1.0);
1055        
1056        // Update portfolio value with loss exceeding daily limit (3% total)
1057        assert!(risk_manager.update_portfolio_value(9700.0, -200.0).is_err());
1058        
1059        // Verify trading should be stopped
1060        assert!(risk_manager.should_stop_trading());
1061    }
1062    
1063    #[test]
1064    fn test_stop_loss_generation() {
1065        let config = RiskConfig {
1066            max_position_size_pct: 0.1,  // 10% of portfolio
1067            max_daily_loss_pct: 2.0,     // 2% max daily loss
1068            stop_loss_pct: 0.05,         // 5% stop loss
1069            take_profit_pct: 0.1,        // 10% take profit
1070            max_leverage: 3.0,           // 3x max leverage
1071        };
1072        
1073        let portfolio_value = 10000.0;
1074        let risk_manager = RiskManager::new(config, portfolio_value);
1075        
1076        // Test stop loss for long position
1077        let long_position = create_test_position("BTC", 0.1, 10000.0, 10000.0);
1078        let stop_loss = risk_manager.generate_stop_loss(&long_position, "order1").unwrap();
1079        
1080        assert_eq!(stop_loss.symbol, "BTC");
1081        assert!(matches!(stop_loss.side, OrderSide::Sell));
1082        assert!(matches!(stop_loss.order_type, OrderType::StopMarket));
1083        assert_eq!(stop_loss.quantity, 0.1);
1084        assert_eq!(stop_loss.trigger_price, 9500.0); // 5% below entry price
1085        
1086        // Test stop loss for short position
1087        let short_position = create_test_position("BTC", -0.1, 10000.0, 10000.0);
1088        let stop_loss = risk_manager.generate_stop_loss(&short_position, "order2").unwrap();
1089        
1090        assert_eq!(stop_loss.symbol, "BTC");
1091        assert!(matches!(stop_loss.side, OrderSide::Buy));
1092        assert!(matches!(stop_loss.order_type, OrderType::StopMarket));
1093        assert_eq!(stop_loss.quantity, 0.1);
1094        assert_eq!(stop_loss.trigger_price, 10500.0); // 5% above entry price
1095    }
1096    
1097    #[test]
1098    fn test_take_profit_generation() {
1099        let config = RiskConfig {
1100            max_position_size_pct: 0.1,  // 10% of portfolio
1101            max_daily_loss_pct: 2.0,     // 2% max daily loss
1102            stop_loss_pct: 0.05,         // 5% stop loss
1103            take_profit_pct: 0.1,        // 10% take profit
1104            max_leverage: 3.0,           // 3x max leverage
1105        };
1106        
1107        let portfolio_value = 10000.0;
1108        let risk_manager = RiskManager::new(config, portfolio_value);
1109        
1110        // Test take profit for long position
1111        let long_position = create_test_position("BTC", 0.1, 10000.0, 10000.0);
1112        let take_profit = risk_manager.generate_take_profit(&long_position, "order1").unwrap();
1113        
1114        assert_eq!(take_profit.symbol, "BTC");
1115        assert!(matches!(take_profit.side, OrderSide::Sell));
1116        assert!(matches!(take_profit.order_type, OrderType::TakeProfitMarket));
1117        assert_eq!(take_profit.quantity, 0.1);
1118        assert_eq!(take_profit.trigger_price, 11000.0); // 10% above entry price
1119        
1120        // Test take profit for short position
1121        let short_position = create_test_position("BTC", -0.1, 10000.0, 10000.0);
1122        let take_profit = risk_manager.generate_take_profit(&short_position, "order2").unwrap();
1123        
1124        assert_eq!(take_profit.symbol, "BTC");
1125        assert!(matches!(take_profit.side, OrderSide::Buy));
1126        assert!(matches!(take_profit.order_type, OrderType::TakeProfitMarket));
1127        assert_eq!(take_profit.quantity, 0.1);
1128        assert_eq!(take_profit.trigger_price, 9000.0); // 10% below entry price
1129    }
1130    
1131    #[test]
1132    fn test_risk_orders_triggering() {
1133        let config = RiskConfig {
1134            max_position_size_pct: 0.1,
1135            max_daily_loss_pct: 2.0,
1136            stop_loss_pct: 0.05,
1137            take_profit_pct: 0.1,
1138            max_leverage: 3.0,
1139        };
1140        
1141        let portfolio_value = 10000.0;
1142        let mut risk_manager = RiskManager::new(config, portfolio_value);
1143        
1144        // Create and register a stop loss order
1145        let long_position = create_test_position("BTC", 0.1, 10000.0, 10000.0);
1146        let stop_loss = risk_manager.generate_stop_loss(&long_position, "order1").unwrap();
1147        risk_manager.register_stop_loss(stop_loss);
1148        
1149        // Create and register a take profit order
1150        let take_profit = risk_manager.generate_take_profit(&long_position, "order1").unwrap();
1151        risk_manager.register_take_profit(take_profit);
1152        
1153        // Test no orders triggered at current price
1154        let mut current_prices = HashMap::new();
1155        current_prices.insert("BTC".to_string(), 10000.0);
1156        let triggered = risk_manager.check_risk_orders(&current_prices);
1157        assert_eq!(triggered.len(), 0);
1158        
1159        // Test stop loss triggered
1160        current_prices.insert("BTC".to_string(), 9400.0); // Below stop loss price
1161        let triggered = risk_manager.check_risk_orders(&current_prices);
1162        assert_eq!(triggered.len(), 1);
1163        assert!(triggered[0].is_stop_loss);
1164        
1165        // Register new orders
1166        let long_position = create_test_position("BTC", 0.1, 10000.0, 10000.0);
1167        let stop_loss = risk_manager.generate_stop_loss(&long_position, "order2").unwrap();
1168        risk_manager.register_stop_loss(stop_loss);
1169        let take_profit = risk_manager.generate_take_profit(&long_position, "order2").unwrap();
1170        risk_manager.register_take_profit(take_profit);
1171        
1172        // Test take profit triggered
1173        current_prices.insert("BTC".to_string(), 11100.0); // Above take profit price
1174        let triggered = risk_manager.check_risk_orders(&current_prices);
1175        assert_eq!(triggered.len(), 1);
1176        assert!(triggered[0].is_take_profit);
1177    }
1178    
1179    #[test]
1180    fn test_emergency_stop() {
1181        let config = RiskConfig::default();
1182        let portfolio_value = 10000.0;
1183        let mut risk_manager = RiskManager::new(config, portfolio_value);
1184        
1185        // Initially, emergency stop should be false
1186        assert!(!risk_manager.should_stop_trading());
1187        
1188        // Activate emergency stop
1189        risk_manager.activate_emergency_stop();
1190        assert!(risk_manager.should_stop_trading());
1191        
1192        // Orders should be rejected when emergency stop is active
1193        let positions = HashMap::new();
1194        let order = create_test_order("BTC", OrderSide::Buy, 0.1, Some(10000.0));
1195        assert!(risk_manager.validate_order(&order, &positions).is_err());
1196        
1197        // Deactivate emergency stop
1198        risk_manager.deactivate_emergency_stop();
1199        assert!(!risk_manager.should_stop_trading());
1200        
1201        // Orders should be accepted again
1202        assert!(risk_manager.validate_order(&order, &positions).is_ok());
1203    }
1204    /// Get the asset class for a symbol
1205    pub fn get_asset_class(&self, symbol: &str) -> Option<&AssetClass> {
1206        self.asset_classes.get(symbol)
1207    }
1208    
1209    /// Set the asset class for a symbol
1210    pub fn set_asset_class(&mut self, symbol: String, asset_class: AssetClass) {
1211        self.asset_classes.insert(symbol, asset_class);
1212    }
1213    
1214    /// Calculate the concentration of an asset class after a potential order
1215    fn calculate_concentration_after_order(
1216        &self,
1217        current_positions: &HashMap<String, Position>,
1218        order: &OrderRequest,
1219        asset_class: &AssetClass
1220    ) -> f64 {
1221        // Calculate current concentration by asset class
1222        let mut asset_class_values = HashMap::new();
1223        let mut total_position_value = 0.0;
1224        
1225        // Add current positions
1226        for (symbol, position) in current_positions {
1227            let position_value = position.size.abs() * position.current_price;
1228            total_position_value += position_value;
1229            
1230            if let Some(class) = self.get_asset_class(symbol) {
1231                *asset_class_values.entry(class).or_insert(0.0) += position_value;
1232            }
1233        }
1234        
1235        // Calculate order value
1236        let order_value = match order.price {
1237            Some(price) => order.quantity * price,
1238            None => {
1239                if let Some(position) = current_positions.get(&order.symbol) {
1240                    order.quantity * position.current_price
1241                } else {
1242                    // If we can't determine the price, assume zero (conservative)
1243                    0.0
1244                }
1245            }
1246        };
1247        
1248        // Update total position value
1249        total_position_value += order_value;
1250        
1251        // Update asset class value
1252        *asset_class_values.entry(asset_class).or_insert(0.0) += order_value;
1253        
1254        // Calculate concentration
1255        if total_position_value > 0.0 {
1256            asset_class_values.get(asset_class).unwrap_or(&0.0) / total_position_value
1257        } else {
1258            0.0
1259        }
1260    }
1261    
1262    /// Update volatility data for a symbol
1263    pub fn update_volatility_data(&mut self, symbol: String, price_history: Vec<f64>) {
1264        if price_history.len() < 30 {
1265            warn!("Insufficient price history for volatility calculation for {}", symbol);
1266            return;
1267        }
1268        
1269        // Calculate daily returns
1270        let mut daily_returns = Vec::with_capacity(price_history.len() - 1);
1271        for i in 1..price_history.len() {
1272            let daily_return = (price_history[i] - price_history[i-1]) / price_history[i-1];
1273            daily_returns.push(daily_return);
1274        }
1275        
1276        // Calculate daily volatility (standard deviation of returns)
1277        let mean_return = daily_returns.iter().sum::<f64>() / daily_returns.len() as f64;
1278        let variance = daily_returns.iter()
1279            .map(|r| (r - mean_return).powi(2))
1280            .sum::<f64>() / daily_returns.len() as f64;
1281        let daily_volatility = variance.sqrt() * 100.0; // Convert to percentage
1282        
1283        // Calculate weekly volatility (approximate)
1284        let weekly_volatility = daily_volatility * (5.0_f64).sqrt();
1285        
1286        // Calculate monthly volatility (approximate)
1287        let monthly_volatility = daily_volatility * (21.0_f64).sqrt();
1288        
1289        // Update volatility data
1290        self.volatility_data.insert(symbol.clone(), VolatilityData {
1291            symbol,
1292            daily_volatility,
1293            weekly_volatility,
1294            monthly_volatility,
1295            price_history,
1296            last_update: Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap()),
1297        });
1298        
1299        // Update portfolio metrics
1300        self.update_portfolio_metrics();
1301    }
1302    
1303    /// Calculate volatility-adjusted position size
1304    fn calculate_volatility_adjusted_position_size(&self, symbol: &str, base_position_size: f64) -> f64 {
1305        if let Some(volatility_data) = self.volatility_data.get(symbol) {
1306            // Use daily volatility for adjustment
1307            // Higher volatility -> smaller position size
1308            let volatility_factor = 1.0 - (self.config.volatility_sizing_factor * 
1309                                          (volatility_data.daily_volatility / 100.0));
1310            
1311            // Ensure factor is between 0.1 and 1.0
1312            let adjusted_factor = volatility_factor.max(0.1).min(1.0);
1313            
1314            base_position_size * adjusted_factor
1315        } else {
1316            // If no volatility data, return the base position size
1317            base_position_size
1318        }
1319    }
1320    
1321    /// Update correlation data between two symbols
1322    pub fn update_correlation_data(&mut self, symbol1: String, symbol2: String, price_history1: &[f64], price_history2: &[f64]) {
1323        if price_history1.len() < 30 || price_history2.len() < 30 || price_history1.len() != price_history2.len() {
1324            warn!("Insufficient or mismatched price history for correlation calculation");
1325            return;
1326        }
1327        
1328        // Calculate returns
1329        let mut returns1 = Vec::with_capacity(price_history1.len() - 1);
1330        let mut returns2 = Vec::with_capacity(price_history2.len() - 1);
1331        
1332        for i in 1..price_history1.len() {
1333            let return1 = (price_history1[i] - price_history1[i-1]) / price_history1[i-1];
1334            let return2 = (price_history2[i] - price_history2[i-1]) / price_history2[i-1];
1335            returns1.push(return1);
1336            returns2.push(return2);
1337        }
1338        
1339        // Calculate correlation coefficient
1340        let mean1 = returns1.iter().sum::<f64>() / returns1.len() as f64;
1341        let mean2 = returns2.iter().sum::<f64>() / returns2.len() as f64;
1342        
1343        let mut numerator = 0.0;
1344        let mut denom1 = 0.0;
1345        let mut denom2 = 0.0;
1346        
1347        for i in 0..returns1.len() {
1348            let diff1 = returns1[i] - mean1;
1349            let diff2 = returns2[i] - mean2;
1350            numerator += diff1 * diff2;
1351            denom1 += diff1 * diff1;
1352            denom2 += diff2 * diff2;
1353        }
1354        
1355        let correlation = if denom1 > 0.0 && denom2 > 0.0 {
1356            numerator / (denom1.sqrt() * denom2.sqrt())
1357        } else {
1358            0.0
1359        };
1360        
1361        // Store correlation data (both directions)
1362        let correlation_data = CorrelationData {
1363            symbol1: symbol1.clone(),
1364            symbol2: symbol2.clone(),
1365            correlation,
1366            last_update: Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap()),
1367        };
1368        
1369        self.correlation_data.insert((symbol1.clone(), symbol2.clone()), correlation_data.clone());
1370        self.correlation_data.insert((symbol2, symbol1), CorrelationData {
1371            symbol1: correlation_data.symbol2,
1372            symbol2: correlation_data.symbol1,
1373            correlation: correlation_data.correlation,
1374            last_update: correlation_data.last_update,
1375        });
1376    }
1377    
1378    /// Get correlation between two symbols
1379    pub fn get_correlation(&self, symbol1: &str, symbol2: &str) -> Option<f64> {
1380        self.correlation_data.get(&(symbol1.to_string(), symbol2.to_string()))
1381            .map(|data| data.correlation)
1382    }
1383    
1384    /// Validate correlation limits for a new order
1385    fn validate_correlation_limits(&self, current_positions: &HashMap<String, Position>, order: &OrderRequest) -> Result<()> {
1386        // Skip validation if no correlation data or no existing positions
1387        if self.correlation_data.is_empty() || current_positions.is_empty() {
1388            return Ok(());
1389        }
1390        
1391        // Check correlation with existing positions
1392        for (symbol, position) in current_positions {
1393            // Skip the same symbol or positions with zero size
1394            if symbol == &order.symbol || position.size == 0.0 {
1395                continue;
1396            }
1397            
1398            // Check if we have correlation data
1399            if let Some(correlation) = self.get_correlation(&order.symbol, symbol) {
1400                // Only check for high positive correlation if positions are in the same direction
1401                let same_direction = (order.side == OrderSide::Buy && position.size > 0.0) ||
1402                                    (order.side == OrderSide::Sell && position.size < 0.0);
1403                
1404                if same_direction && correlation.abs() > self.config.max_position_correlation {
1405                    return Err(RiskError::CorrelationLimitExceeded {
1406                        symbol1: order.symbol.clone(),
1407                        symbol2: symbol.clone(),
1408                        correlation,
1409                        max_correlation: self.config.max_position_correlation,
1410                    });
1411                }
1412            }
1413        }
1414        
1415        Ok(())
1416    }
1417    
1418    /// Update portfolio metrics
1419    pub fn update_portfolio_metrics(&mut self) {
1420        // Calculate portfolio volatility if we have volatility data
1421        if !self.volatility_data.is_empty() {
1422            self.calculate_portfolio_volatility();
1423        }
1424        
1425        // Update drawdown
1426        self.calculate_drawdown();
1427        
1428        // Update Value at Risk (VaR)
1429        self.calculate_value_at_risk();
1430    }
1431    
1432    /// Calculate portfolio volatility
1433    fn calculate_portfolio_volatility(&mut self) {
1434        // This is a simplified portfolio volatility calculation
1435        // In a real implementation, we would use a covariance matrix
1436        
1437        // Get total portfolio value
1438        let total_value = self.portfolio_value;
1439        if total_value <= 0.0 {
1440            self.portfolio_metrics.volatility = 0.0;
1441            return;
1442        }
1443        
1444        // Calculate weighted volatility
1445        let mut weighted_volatility = 0.0;
1446        let mut total_weighted_value = 0.0;
1447        
1448        for (symbol, volatility_data) in &self.volatility_data {
1449            // Assume we have a position in this asset
1450            // In a real implementation, we would check actual positions
1451            let weight = 1.0 / self.volatility_data.len() as f64;
1452            weighted_volatility += volatility_data.daily_volatility * weight;
1453            total_weighted_value += weight;
1454        }
1455        
1456        // Normalize
1457        if total_weighted_value > 0.0 {
1458            self.portfolio_metrics.volatility = weighted_volatility / total_weighted_value;
1459        } else {
1460            self.portfolio_metrics.volatility = 0.0;
1461        }
1462    }
1463    
1464    /// Calculate drawdown
1465    fn calculate_drawdown(&mut self) {
1466        if self.historical_portfolio_values.is_empty() {
1467            self.portfolio_metrics.max_drawdown = 0.0;
1468            return;
1469        }
1470        
1471        // Find peak value
1472        let mut peak_value = self.historical_portfolio_values[0].1;
1473        let mut max_drawdown = 0.0;
1474        
1475        for &(_, value) in &self.historical_portfolio_values {
1476            if value > peak_value {
1477                peak_value = value;
1478            }
1479            
1480            let drawdown = if peak_value > 0.0 {
1481                (peak_value - value) / peak_value
1482            } else {
1483                0.0
1484            };
1485            
1486            if drawdown > max_drawdown {
1487                max_drawdown = drawdown;
1488            }
1489        }
1490        
1491        self.portfolio_metrics.max_drawdown = max_drawdown;
1492    }
1493    
1494    /// Calculate Value at Risk (VaR)
1495    fn calculate_value_at_risk(&mut self) {
1496        // This is a simplified VaR calculation using historical simulation
1497        // In a real implementation, we would use more sophisticated methods
1498        
1499        if self.historical_portfolio_values.len() < 30 {
1500            self.portfolio_metrics.var_95 = 0.0;
1501            self.portfolio_metrics.var_99 = 0.0;
1502            return;
1503        }
1504        
1505        // Calculate daily returns
1506        let mut daily_returns = Vec::with_capacity(self.historical_portfolio_values.len() - 1);
1507        for i in 1..self.historical_portfolio_values.len() {
1508            let prev_value = self.historical_portfolio_values[i-1].1;
1509            let curr_value = self.historical_portfolio_values[i].1;
1510            
1511            if prev_value > 0.0 {
1512                let daily_return = (curr_value - prev_value) / prev_value;
1513                daily_returns.push(daily_return);
1514            }
1515        }
1516        
1517        // Sort returns in ascending order
1518        daily_returns.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1519        
1520        // Calculate VaR at 95% confidence
1521        let index_95 = (daily_returns.len() as f64 * 0.05).floor() as usize;
1522        if index_95 < daily_returns.len() {
1523            self.portfolio_metrics.var_95 = -daily_returns[index_95] * self.portfolio_value;
1524        }
1525        
1526        // Calculate VaR at 99% confidence
1527        let index_99 = (daily_returns.len() as f64 * 0.01).floor() as usize;
1528        if index_99 < daily_returns.len() {
1529            self.portfolio_metrics.var_99 = -daily_returns[index_99] * self.portfolio_value;
1530        }
1531    }
1532    
1533    /// Validate portfolio volatility limits
1534    fn validate_portfolio_volatility(&self, current_positions: &HashMap<String, Position>, order: &OrderRequest) -> Result<()> {
1535        // Skip validation if we don't have enough volatility data
1536        if self.volatility_data.is_empty() {
1537            return Ok(());
1538        }
1539        
1540        // Check if adding this position would exceed portfolio volatility limits
1541        // This is a simplified check - in a real implementation, we would recalculate portfolio volatility
1542        
1543        if let Some(volatility_data) = self.volatility_data.get(&order.symbol) {
1544            // If the asset is more volatile than our limit and it's a significant position
1545            let order_value = match order.price {
1546                Some(price) => order.quantity * price,
1547                None => {
1548                    if let Some(position) = current_positions.get(&order.symbol) {
1549                        order.quantity * position.current_price
1550                    } else {
1551                        0.0
1552                    }
1553                }
1554            };
1555            
1556            let position_weight = order_value / self.portfolio_value;
1557            
1558            // If this is a significant position in a highly volatile asset
1559            if position_weight > 0.1 && volatility_data.daily_volatility > self.config.max_portfolio_volatility_pct {
1560                return Err(RiskError::VolatilityLimitExceeded {
1561                    current_volatility_pct: volatility_data.daily_volatility,
1562                    max_volatility_pct: self.config.max_portfolio_volatility_pct,
1563                });
1564            }
1565            
1566            // If current portfolio volatility is already near the limit
1567            if self.portfolio_metrics.volatility > self.config.max_portfolio_volatility_pct * 0.9 {
1568                // And this asset is more volatile than the portfolio
1569                if volatility_data.daily_volatility > self.portfolio_metrics.volatility {
1570                    return Err(RiskError::VolatilityLimitExceeded {
1571                        current_volatility_pct: self.portfolio_metrics.volatility,
1572                        max_volatility_pct: self.config.max_portfolio_volatility_pct,
1573                    });
1574                }
1575            }
1576        }
1577        
1578        Ok(())
1579    }
1580    
1581    /// Update portfolio value and track historical values
1582    pub fn update_portfolio_value_with_history(&mut self, new_value: f64, realized_pnl_delta: f64) -> Result<()> {
1583        // Update regular portfolio value
1584        let result = self.update_portfolio_value(new_value, realized_pnl_delta);
1585        
1586        // Add to historical values
1587        let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
1588        self.historical_portfolio_values.push((now, new_value));
1589        
1590        // Limit history size to prevent memory issues
1591        if self.historical_portfolio_values.len() > 1000 {
1592            self.historical_portfolio_values.remove(0);
1593        }
1594        
1595        // Update portfolio metrics
1596        self.update_portfolio_metrics();
1597        
1598        // Check drawdown limit
1599        if self.portfolio_metrics.max_drawdown > self.config.max_drawdown_pct {
1600            warn!(
1601                "Maximum drawdown limit reached: {:.2}% exceeds {:.2}%",
1602                self.portfolio_metrics.max_drawdown * 100.0,
1603                self.config.max_drawdown_pct * 100.0
1604            );
1605            
1606            // Activate emergency stop
1607            self.activate_emergency_stop();
1608            
1609            return Err(RiskError::DrawdownLimitExceeded {
1610                current_drawdown_pct: self.portfolio_metrics.max_drawdown * 100.0,
1611                max_drawdown_pct: self.config.max_drawdown_pct * 100.0,
1612            });
1613        }
1614        
1615        result
1616    }
1617    
1618    /// Get current portfolio metrics
1619    pub fn get_portfolio_metrics(&self) -> &PortfolioMetrics {
1620        &self.portfolio_metrics
1621    }
1622    
1623    /// Get volatility data for a symbol
1624    pub fn get_volatility_data(&self, symbol: &str) -> Option<&VolatilityData> {
1625        self.volatility_data.get(symbol)
1626    }
1627    
1628    /// Get all volatility data
1629    pub fn get_all_volatility_data(&self) -> &HashMap<String, VolatilityData> {
1630        &self.volatility_data
1631    }
1632    
1633    /// Get all correlation data
1634    pub fn get_all_correlation_data(&self) -> &HashMap<(String, String), CorrelationData> {
1635        &self.correlation_data
1636    }
1637
1638    /// Calculate volatility-adjusted position size
1639    pub fn calculate_volatility_adjusted_position_size(&self, symbol: &str, base_size: f64) -> f64 {
1640        if let Some(volatility_data) = self.volatility_data.get(symbol) {
1641            // Adjust position size based on volatility
1642            let volatility_factor = 1.0 / (1.0 + volatility_data.daily_volatility);
1643            base_size * volatility_factor
1644        } else {
1645            base_size
1646        }
1647    }
1648
1649    /// Get asset class for a symbol (placeholder implementation)
1650    pub fn get_asset_class(&self, _symbol: &str) -> Option<String> {
1651        // Placeholder - in real implementation, this would classify assets
1652        Some("crypto".to_string())
1653    }
1654
1655    /// Calculate concentration after order
1656    pub fn calculate_concentration_after_order(
1657        &self, 
1658        current_positions: &HashMap<String, Position>, 
1659        order: &OrderRequest, 
1660        asset_class: String
1661    ) -> f64 {
1662        // Calculate current concentration for this asset class
1663        let current_class_value: f64 = current_positions.values()
1664            .filter(|p| self.get_asset_class(&p.symbol).as_deref() == Some(&asset_class))
1665            .map(|p| p.size.abs() * p.current_price)
1666            .sum();
1667        
1668        // Add the new order value if it's the same asset class
1669        let order_value = if self.get_asset_class(&order.symbol).as_deref() == Some(&asset_class) {
1670            order.quantity.abs() * order.price.unwrap_or(0.0)
1671        } else {
1672            0.0
1673        };
1674        
1675        let total_class_value = current_class_value + order_value;
1676        
1677        // Return concentration as percentage of portfolio
1678        if self.portfolio_value > 0.0 {
1679            total_class_value / self.portfolio_value
1680        } else {
1681            0.0
1682        }
1683    }
1684
1685    /// Validate correlation limits
1686    pub fn validate_correlation_limits(
1687        &self, 
1688        current_positions: &HashMap<String, Position>, 
1689        order: &OrderRequest
1690    ) -> Result<()> {
1691        // Simplified implementation - check if adding this position would create high correlation
1692        for position in current_positions.values() {
1693            if let Some(correlation_data) = self.correlation_data.get(&(position.symbol.clone(), order.symbol.clone())) {
1694                if correlation_data.correlation.abs() > 0.8 {
1695                    return Err(RiskError::CorrelationLimitExceeded {
1696                        symbol1: position.symbol.clone(),
1697                        symbol2: order.symbol.clone(),
1698                        correlation: correlation_data.correlation,
1699                        max_correlation: 0.8,
1700                    });
1701                }
1702            }
1703        }
1704        Ok(())
1705    }
1706
1707    /// Validate portfolio volatility
1708    pub fn validate_portfolio_volatility(
1709        &self, 
1710        _current_positions: &HashMap<String, Position>, 
1711        _order: &OrderRequest
1712    ) -> Result<()> {
1713        // Check if current portfolio volatility is within limits
1714        if self.portfolio_metrics.volatility > self.config.max_portfolio_volatility_pct {
1715            return Err(RiskError::VolatilityLimitExceeded {
1716                current_volatility_pct: self.portfolio_metrics.volatility,
1717                max_volatility_pct: self.config.max_portfolio_volatility_pct,
1718            });
1719        }
1720        Ok(())
1721    }
1722}