use serde::{Deserialize, Serialize};
use crate::errors::{ProgramError, Result};
use crate::constants::*;
use crate::state::Position;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum RiskLevel {
Low, Medium, High, Critical, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiskMetrics {
pub utilization_pct: u8,
pub risk_level: RiskLevel,
pub leverage_multiplier: u8,
pub max_position_size: u64,
pub health_factor: u16,
pub var_95: u64, }
pub struct RiskEngine;
impl RiskEngine {
pub fn assess_risk_level(utilization_pct: u8) -> RiskLevel {
if utilization_pct <= RISK_LOW_THRESHOLD {
RiskLevel::Low
} else if utilization_pct <= RISK_MEDIUM_THRESHOLD {
RiskLevel::Medium
} else if utilization_pct < 96 {
RiskLevel::High
} else {
RiskLevel::Critical
}
}
pub fn get_leverage_multiplier(risk_level: RiskLevel) -> u8 {
match risk_level {
RiskLevel::Low => RISK_LOW_MULTIPLIER,
RiskLevel::Medium => RISK_MEDIUM_MULTIPLIER,
RiskLevel::High => RISK_HIGH_MULTIPLIER,
RiskLevel::Critical => 25, }
}
pub fn calculate_max_leverage(utilization_pct: u8, base_leverage: u8) -> u8 {
let risk_level = Self::assess_risk_level(utilization_pct);
let multiplier = Self::get_leverage_multiplier(risk_level);
let adjusted = ((base_leverage as u16 * multiplier as u16) / 100) as u8;
adjusted.max(MIN_LEVERAGE).min(MAX_LEVERAGE)
}
pub fn calculate_margin_requirement(
position_value: u64,
leverage: u8,
) -> Result<u64> {
let requirement = (position_value as u128 * 100) / leverage as u128;
Ok(requirement as u64)
}
pub fn calculate_liquidation_price(
entry_price: u64,
leverage: u8,
is_long: bool,
) -> Result<u64> {
let fee_offset = (entry_price as u128 * LIQUIDATION_FEE_BPS as u128) / 10_000;
if is_long {
let liquidation = ((entry_price as u128 * (leverage - 1) as u128) / leverage as u128) as u64;
Ok(liquidation.saturating_sub(fee_offset as u64))
} else {
let liquidation = ((entry_price as u128 * (leverage + 1) as u128) / leverage as u128) as u64;
Ok(liquidation.saturating_add(fee_offset as u64))
}
}
pub fn calculate_var_95(
position_value: u64,
volatility_bps: u16,
) -> Result<u64> {
let z_score = 1645; let var = (position_value as u128 * volatility_bps as u128 * z_score as u128) / (10_000 * 10_000);
Ok((var / 100) as u64)
}
pub fn stress_test_position(
position: &Position,
price_shock_bps: u16,
) -> Result<(i64, bool)> {
let shock = (position.entry_price as u128 * price_shock_bps as u128) / 10_000;
let stress_price = if price_shock_bps as i32 > 0 {
(position.entry_price as u128 + shock) as u64
} else {
position.entry_price.saturating_sub(shock as u64)
};
let pnl = crate::position::PositionManager::calculate_pnl(position, stress_price)?;
let margin = crate::position::PositionManager::calculate_unrealized_margin(position, stress_price)?;
let is_liquidatable = margin < (position.collateral / 20);
Ok((pnl, is_liquidatable))
}
pub fn calculate_funding_fee(
open_interest: u64,
available_liquidity: u64,
time_passed_hours: u32,
) -> Result<u64> {
if available_liquidity == 0 {
return Ok(0);
}
let utilization = (open_interest as u128 * 100) / available_liquidity as u128;
let hourly_rate = (utilization * 5) / 100;
let total_fee = (hourly_rate * time_passed_hours as u128) / 3600;
Ok(total_fee as u64)
}
pub fn calculate_insurance_requirement(
total_open_interest: u64,
current_insurance: u64,
) -> Result<(u64, bool)> {
let required = (total_open_interest / 10) as u64; let is_healthy = current_insurance >= required;
Ok((required, is_healthy))
}
pub fn validate_risk_parameters(
collateral: u64,
position_value: u64,
leverage: u8,
utilization_pct: u8,
) -> Result<()> {
if collateral < MIN_COLLATERAL {
return Err(ProgramError::InsufficientCollateral);
}
let max_allowed_leverage = Self::calculate_max_leverage(utilization_pct, MAX_LEVERAGE);
if leverage > max_allowed_leverage {
return Err(ProgramError::ExcessiveLeverage);
}
let margin_requirement = Self::calculate_margin_requirement(position_value, leverage)?;
if collateral < margin_requirement {
return Err(ProgramError::InsufficientCollateral);
}
Ok(())
}
pub fn calculate_risk_adjusted_size(
available_capital: u64,
leverage: u8,
risk_level: RiskLevel,
entry_price: u64,
) -> Result<u64> {
let multiplier = Self::get_leverage_multiplier(risk_level);
let adjusted_capital = (available_capital as u128 * multiplier as u128) / 100;
let max_notional = (adjusted_capital * leverage as u128) / 100;
let max_size = max_notional / entry_price as u128;
Ok(max_size as u64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_assess_risk_level() {
assert_eq!(RiskEngine::assess_risk_level(30), RiskLevel::Low);
assert_eq!(RiskEngine::assess_risk_level(50), RiskLevel::Medium);
assert_eq!(RiskEngine::assess_risk_level(80), RiskLevel::High);
assert_eq!(RiskEngine::assess_risk_level(98), RiskLevel::Critical);
}
#[test]
fn test_leverage_multiplier() {
assert_eq!(RiskEngine::get_leverage_multiplier(RiskLevel::Low), 100);
assert_eq!(RiskEngine::get_leverage_multiplier(RiskLevel::Medium), 75);
}
#[test]
fn test_calculate_var_95() {
let result = RiskEngine::calculate_var_95(1_000_000, 500);
assert!(result.is_ok());
}
}