precolator-program 1.0.0

Core Rust library for the Precolator perpetual futures trading protocol on Solana — oracle management, position handling, risk engine, and liquidation system.
Documentation
// Risk module - Risk assessment and management

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,      // Utilization < 40%
    Medium,   // Utilization 40-75%
    High,     // Utilization > 75%
    Critical, // Liquidation imminent
}

#[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, // Value at Risk (95% confidence)
}

pub struct RiskEngine;

impl RiskEngine {
    /// Assess overall risk level based on utilization
    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
        }
    }

    /// Calculate dynamic leverage multiplier based on risk level
    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, // 0.25x - severely restricted
        }
    }

    /// Calculate maximum allowed leverage based on utilization
    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)
    }

    /// Calculate margin requirement for a position
    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)
    }

    /// Calculate liquidation price considering fees
    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))
        }
    }

    /// Calculate Value at Risk (95% confidence)
    pub fn calculate_var_95(
        position_value: u64,
        volatility_bps: u16,
    ) -> Result<u64> {
        let z_score = 1645; // 95% confidence z-score * 10000
        let var = (position_value as u128 * volatility_bps as u128 * z_score as u128) / (10_000 * 10_000);
        Ok((var / 100) as u64)
    }

    /// Stress test a portfolio
    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); // 5% maintenance

        Ok((pnl, is_liquidatable))
    }

    /// Calculate funding fee impact
    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; // 5% of utilization per hour

        let total_fee = (hourly_rate * time_passed_hours as u128) / 3600;
        Ok(total_fee as u64)
    }

    /// Calculate insurance fund requirement
    pub fn calculate_insurance_requirement(
        total_open_interest: u64,
        current_insurance: u64,
    ) -> Result<(u64, bool)> {
        let required = (total_open_interest / 10) as u64; // 10% of open interest
        let is_healthy = current_insurance >= required;

        Ok((required, is_healthy))
    }

    /// Validate risk parameters before opening position
    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(())
    }

    /// Calculate risk-adjusted position size
    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());
    }
}