1use 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, Medium, High, Critical, }
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, }
25
26pub struct RiskEngine;
27
28impl RiskEngine {
29 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 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, }
50 }
51
52 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 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 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 pub fn calculate_var_95(
89 position_value: u64,
90 volatility_bps: u16,
91 ) -> Result<u64> {
92 let z_score = 1645; 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 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); Ok((pnl, is_liquidatable))
115 }
116
117 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; let total_fee = (hourly_rate * time_passed_hours as u128) / 3600;
131 Ok(total_fee as u64)
132 }
133
134 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; let is_healthy = current_insurance >= required;
141
142 Ok((required, is_healthy))
143 }
144
145 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 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}