Skip to main content

precolator/
risk.rs

1// Risk module - Risk assessment and management
2
3use serde::{Deserialize, Serialize};
4use crate::errors::{ProgramError, Result};
5use crate::constants::*;
6use crate::state::Position;
7
8#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
9pub enum RiskLevel {
10    Low,      // Utilization < 40%
11    Medium,   // Utilization 40-75%
12    High,     // Utilization > 75%
13    Critical, // Liquidation imminent
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct RiskMetrics {
18    pub utilization_pct: u8,
19    pub risk_level: RiskLevel,
20    pub leverage_multiplier: u8,
21    pub max_position_size: u64,
22    pub health_factor: u16,
23    pub var_95: u64, // Value at Risk (95% confidence)
24}
25
26pub struct RiskEngine;
27
28impl RiskEngine {
29    /// Assess overall risk level based on utilization
30    pub fn assess_risk_level(utilization_pct: u8) -> RiskLevel {
31        if utilization_pct <= RISK_LOW_THRESHOLD {
32            RiskLevel::Low
33        } else if utilization_pct <= RISK_MEDIUM_THRESHOLD {
34            RiskLevel::Medium
35        } else if utilization_pct < 96 {
36            RiskLevel::High
37        } else {
38            RiskLevel::Critical
39        }
40    }
41
42    /// Calculate dynamic leverage multiplier based on risk level
43    pub fn get_leverage_multiplier(risk_level: RiskLevel) -> u8 {
44        match risk_level {
45            RiskLevel::Low => RISK_LOW_MULTIPLIER,
46            RiskLevel::Medium => RISK_MEDIUM_MULTIPLIER,
47            RiskLevel::High => RISK_HIGH_MULTIPLIER,
48            RiskLevel::Critical => 25, // 0.25x - severely restricted
49        }
50    }
51
52    /// Calculate maximum allowed leverage based on utilization
53    pub fn calculate_max_leverage(utilization_pct: u8, base_leverage: u8) -> u8 {
54        let risk_level = Self::assess_risk_level(utilization_pct);
55        let multiplier = Self::get_leverage_multiplier(risk_level);
56        
57        let adjusted = ((base_leverage as u16 * multiplier as u16) / 100) as u8;
58        adjusted.max(MIN_LEVERAGE).min(MAX_LEVERAGE)
59    }
60
61    /// Calculate margin requirement for a position
62    pub fn calculate_margin_requirement(
63        position_value: u64,
64        leverage: u8,
65    ) -> Result<u64> {
66        let requirement = (position_value as u128 * 100) / leverage as u128;
67        Ok(requirement as u64)
68    }
69
70    /// Calculate liquidation price considering fees
71    pub fn calculate_liquidation_price(
72        entry_price: u64,
73        leverage: u8,
74        is_long: bool,
75    ) -> Result<u64> {
76        let fee_offset = (entry_price as u128 * LIQUIDATION_FEE_BPS as u128) / 10_000;
77        
78        if is_long {
79            let liquidation = ((entry_price as u128 * (leverage - 1) as u128) / leverage as u128) as u64;
80            Ok(liquidation.saturating_sub(fee_offset as u64))
81        } else {
82            let liquidation = ((entry_price as u128 * (leverage + 1) as u128) / leverage as u128) as u64;
83            Ok(liquidation.saturating_add(fee_offset as u64))
84        }
85    }
86
87    /// Calculate Value at Risk (95% confidence)
88    pub fn calculate_var_95(
89        position_value: u64,
90        volatility_bps: u16,
91    ) -> Result<u64> {
92        let z_score = 1645; // 95% confidence z-score * 10000
93        let var = (position_value as u128 * volatility_bps as u128 * z_score as u128) / (10_000 * 10_000);
94        Ok((var / 100) as u64)
95    }
96
97    /// Stress test a portfolio
98    pub fn stress_test_position(
99        position: &Position,
100        price_shock_bps: u16,
101    ) -> Result<(i64, bool)> {
102        let shock = (position.entry_price as u128 * price_shock_bps as u128) / 10_000;
103        let stress_price = if price_shock_bps as i32 > 0 {
104            (position.entry_price as u128 + shock) as u64
105        } else {
106            position.entry_price.saturating_sub(shock as u64)
107        };
108
109        let pnl = crate::position::PositionManager::calculate_pnl(position, stress_price)?;
110        let margin = crate::position::PositionManager::calculate_unrealized_margin(position, stress_price)?;
111        
112        let is_liquidatable = margin < (position.collateral / 20); // 5% maintenance
113
114        Ok((pnl, is_liquidatable))
115    }
116
117    /// Calculate funding fee impact
118    pub fn calculate_funding_fee(
119        open_interest: u64,
120        available_liquidity: u64,
121        time_passed_hours: u32,
122    ) -> Result<u64> {
123        if available_liquidity == 0 {
124            return Ok(0);
125        }
126
127        let utilization = (open_interest as u128 * 100) / available_liquidity as u128;
128        let hourly_rate = (utilization * 5) / 100; // 5% of utilization per hour
129
130        let total_fee = (hourly_rate * time_passed_hours as u128) / 3600;
131        Ok(total_fee as u64)
132    }
133
134    /// Calculate insurance fund requirement
135    pub fn calculate_insurance_requirement(
136        total_open_interest: u64,
137        current_insurance: u64,
138    ) -> Result<(u64, bool)> {
139        let required = (total_open_interest / 10) as u64; // 10% of open interest
140        let is_healthy = current_insurance >= required;
141
142        Ok((required, is_healthy))
143    }
144
145    /// Validate risk parameters before opening position
146    pub fn validate_risk_parameters(
147        collateral: u64,
148        position_value: u64,
149        leverage: u8,
150        utilization_pct: u8,
151    ) -> Result<()> {
152        if collateral < MIN_COLLATERAL {
153            return Err(ProgramError::InsufficientCollateral);
154        }
155
156        let max_allowed_leverage = Self::calculate_max_leverage(utilization_pct, MAX_LEVERAGE);
157        if leverage > max_allowed_leverage {
158            return Err(ProgramError::ExcessiveLeverage);
159        }
160
161        let margin_requirement = Self::calculate_margin_requirement(position_value, leverage)?;
162        if collateral < margin_requirement {
163            return Err(ProgramError::InsufficientCollateral);
164        }
165
166        Ok(())
167    }
168
169    /// Calculate risk-adjusted position size
170    pub fn calculate_risk_adjusted_size(
171        available_capital: u64,
172        leverage: u8,
173        risk_level: RiskLevel,
174        entry_price: u64,
175    ) -> Result<u64> {
176        let multiplier = Self::get_leverage_multiplier(risk_level);
177        let adjusted_capital = (available_capital as u128 * multiplier as u128) / 100;
178        
179        let max_notional = (adjusted_capital * leverage as u128) / 100;
180        let max_size = max_notional / entry_price as u128;
181
182        Ok(max_size as u64)
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_assess_risk_level() {
192        assert_eq!(RiskEngine::assess_risk_level(30), RiskLevel::Low);
193        assert_eq!(RiskEngine::assess_risk_level(50), RiskLevel::Medium);
194        assert_eq!(RiskEngine::assess_risk_level(80), RiskLevel::High);
195        assert_eq!(RiskEngine::assess_risk_level(98), RiskLevel::Critical);
196    }
197
198    #[test]
199    fn test_leverage_multiplier() {
200        assert_eq!(RiskEngine::get_leverage_multiplier(RiskLevel::Low), 100);
201        assert_eq!(RiskEngine::get_leverage_multiplier(RiskLevel::Medium), 75);
202    }
203
204    #[test]
205    fn test_calculate_var_95() {
206        let result = RiskEngine::calculate_var_95(1_000_000, 500);
207        assert!(result.is_ok());
208    }
209}