use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TailHedgingStrategy {
PutOptions {
strike_ratio: Decimal,
coverage: Decimal,
},
Volatility {
target_exposure: Decimal,
},
TrendFollowing {
lookback_days: usize,
allocation: Decimal,
},
SafeHaven {
allocation: Decimal,
},
TailRiskParity {
target_tail_contribution: Decimal,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlackSwanScenario {
pub name: String,
pub probability: Decimal,
pub market_crash_pct: Decimal,
pub volatility_spike: Decimal,
pub correlation_spike: Decimal,
}
impl BlackSwanScenario {
pub fn financial_crisis() -> Self {
Self {
name: "Financial Crisis".to_string(),
probability: dec!(0.01), market_crash_pct: dec!(0.40), volatility_spike: dec!(3.0), correlation_spike: dec!(0.9), }
}
pub fn crypto_winter() -> Self {
Self {
name: "Crypto Winter".to_string(),
probability: dec!(0.05), market_crash_pct: dec!(0.70), volatility_spike: dec!(4.0), correlation_spike: dec!(0.95), }
}
pub fn flash_crash() -> Self {
Self {
name: "Flash Crash".to_string(),
probability: dec!(0.10), market_crash_pct: dec!(0.20), volatility_spike: dec!(5.0), correlation_spike: dec!(0.80),
}
}
pub fn black_swan() -> Self {
Self {
name: "Black Swan Event".to_string(),
probability: dec!(0.001), market_crash_pct: dec!(0.50), volatility_spike: dec!(10.0), correlation_spike: dec!(1.0), }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TailRiskMetrics {
pub expected_shortfall_1pct: Decimal,
pub expected_shortfall_5pct: Decimal,
pub max_drawdown: Decimal,
pub tail_ratio: Decimal,
pub skewness: Decimal,
pub kurtosis: Decimal,
}
pub struct TailRiskHedger {
portfolio_value: Decimal,
strategies: Vec<TailHedgingStrategy>,
scenarios: Vec<BlackSwanScenario>,
}
impl TailRiskHedger {
pub fn new(portfolio_value: Decimal) -> Self {
Self {
portfolio_value,
strategies: Vec::new(),
scenarios: vec![
BlackSwanScenario::financial_crisis(),
BlackSwanScenario::crypto_winter(),
BlackSwanScenario::flash_crash(),
],
}
}
pub fn add_strategy(&mut self, strategy: TailHedgingStrategy) {
self.strategies.push(strategy);
}
pub fn calculate_hedging_cost(&self) -> Decimal {
let mut total_cost = dec!(0);
for strategy in &self.strategies {
let cost = match strategy {
TailHedgingStrategy::PutOptions {
strike_ratio,
coverage,
} => {
let notional = self.portfolio_value * coverage;
let otm_factor = dec!(1) - strike_ratio; let base_cost = dec!(0.03); notional * base_cost * (dec!(1) - otm_factor)
}
TailHedgingStrategy::Volatility { target_exposure } => {
target_exposure * dec!(0.05) }
TailHedgingStrategy::TrendFollowing { allocation, .. } => {
self.portfolio_value * allocation * dec!(0.01) }
TailHedgingStrategy::SafeHaven { allocation } => {
self.portfolio_value * allocation * dec!(0.02) }
TailHedgingStrategy::TailRiskParity { .. } => {
self.portfolio_value * dec!(0.005) }
};
total_cost += cost;
}
total_cost
}
pub fn calculate_protection(&self, scenario: &BlackSwanScenario) -> Decimal {
let unhedged_loss = self.portfolio_value * scenario.market_crash_pct;
let mut total_protection = dec!(0);
for strategy in &self.strategies {
let protection = match strategy {
TailHedgingStrategy::PutOptions {
strike_ratio,
coverage,
} => {
let strike_level = dec!(1) - (dec!(1) - strike_ratio);
let crash_level = dec!(1) - scenario.market_crash_pct;
if crash_level < strike_level {
let payoff_pct = strike_level - crash_level;
self.portfolio_value * coverage * payoff_pct
} else {
dec!(0)
}
}
TailHedgingStrategy::Volatility { target_exposure } => {
target_exposure * scenario.volatility_spike * dec!(0.5)
}
TailHedgingStrategy::TrendFollowing { allocation, .. } => {
if scenario.market_crash_pct > dec!(0.30) {
self.portfolio_value * allocation * dec!(0.20) } else {
dec!(0)
}
}
TailHedgingStrategy::SafeHaven { allocation } => {
self.portfolio_value * allocation * dec!(0.10) }
TailHedgingStrategy::TailRiskParity { .. } => {
unhedged_loss * dec!(0.30) }
};
total_protection += protection;
}
total_protection
}
pub fn hedge_effectiveness(&self, scenario: &BlackSwanScenario) -> Decimal {
let unhedged_loss = self.portfolio_value * scenario.market_crash_pct;
let protection = self.calculate_protection(scenario);
if unhedged_loss > dec!(0) {
protection / unhedged_loss
} else {
dec!(0)
}
}
pub fn crisis_alpha(&self, scenario: &BlackSwanScenario) -> Decimal {
let unhedged_loss = self.portfolio_value * scenario.market_crash_pct;
let protection = self.calculate_protection(scenario);
let hedging_cost = self.calculate_hedging_cost();
let net_loss = unhedged_loss - protection + hedging_cost;
((unhedged_loss - net_loss) / self.portfolio_value) * dec!(100)
}
pub fn expected_utility(&self, risk_aversion: Decimal) -> Decimal {
let hedging_cost = self.calculate_hedging_cost();
let mut expected_value = -hedging_cost;
for scenario in &self.scenarios {
let unhedged_loss = self.portfolio_value * scenario.market_crash_pct;
let protection = self.calculate_protection(scenario);
let net_loss = unhedged_loss - protection;
let risk_penalty = net_loss * risk_aversion;
expected_value += scenario.probability * (protection - risk_penalty);
}
expected_value
}
pub fn optimal_hedge_ratio(&self, _expected_return: Decimal, volatility: Decimal) -> Decimal {
let expected_loss: Decimal = self
.scenarios
.iter()
.map(|s| s.probability * self.portfolio_value * s.market_crash_pct)
.sum();
let hedging_cost = self.calculate_hedging_cost();
if volatility > dec!(0) {
((expected_loss - hedging_cost) / (volatility * volatility)).max(dec!(0))
} else {
dec!(0)
}
}
}
pub struct TailRiskParity;
impl TailRiskParity {
pub fn calculate_tail_contributions(
weights: &[Decimal],
tail_covariance: &[Vec<Decimal>],
) -> Vec<Decimal> {
let n = weights.len();
let mut contributions = vec![dec!(0); n];
for i in 0..n {
let mut tail_var_contribution = dec!(0);
for (j, weight) in weights.iter().enumerate().take(n) {
tail_var_contribution += weight * tail_covariance[i][j];
}
contributions[i] = weights[i] * tail_var_contribution;
}
contributions
}
pub fn optimize_weights(
initial_weights: &[Decimal],
tail_covariance: &[Vec<Decimal>],
max_iterations: usize,
) -> Vec<Decimal> {
let n = initial_weights.len();
let mut weights = initial_weights.to_vec();
for _ in 0..max_iterations {
let contributions = Self::calculate_tail_contributions(&weights, tail_covariance);
let avg_contribution: Decimal =
contributions.iter().sum::<Decimal>() / Decimal::from(n as i64);
for i in 0..n {
if contributions[i] > dec!(0) {
let adjustment = avg_contribution / contributions[i];
weights[i] *= dec!(1) + (adjustment - dec!(1)) * dec!(0.1); }
}
let total: Decimal = weights.iter().sum();
if total > dec!(0) {
for weight in &mut weights {
*weight /= total;
}
}
}
weights
}
pub fn tail_risk_ratio(weights: &[Decimal], tail_covariance: &[Vec<Decimal>]) -> Decimal {
let contributions = Self::calculate_tail_contributions(weights, tail_covariance);
if contributions.is_empty() {
return dec!(0);
}
let max_contrib = contributions.iter().max().copied().unwrap_or(dec!(0));
let min_contrib = contributions
.iter()
.filter(|&&c| c > dec!(0))
.min()
.copied()
.unwrap_or(dec!(0));
if min_contrib > dec!(0) {
max_contrib / min_contrib
} else {
dec!(0)
}
}
}
pub struct CrisisAlphaStrategy {
pub name: String,
pub normal_return: Decimal,
pub crisis_return: Decimal,
pub normal_volatility: Decimal,
pub crisis_volatility: Decimal,
}
impl CrisisAlphaStrategy {
pub fn long_volatility() -> Self {
Self {
name: "Long Volatility".to_string(),
normal_return: dec!(-0.10), crisis_return: dec!(0.50), normal_volatility: dec!(0.30),
crisis_volatility: dec!(0.80),
}
}
pub fn trend_following() -> Self {
Self {
name: "Trend Following".to_string(),
normal_return: dec!(0.05), crisis_return: dec!(0.25), normal_volatility: dec!(0.15),
crisis_volatility: dec!(0.25),
}
}
pub fn tail_hedge_fund() -> Self {
Self {
name: "Tail Risk Hedge Fund".to_string(),
normal_return: dec!(-0.05), crisis_return: dec!(1.00), normal_volatility: dec!(0.40),
crisis_volatility: dec!(1.50),
}
}
pub fn expected_return(&self, crisis_probability: Decimal) -> Decimal {
crisis_probability * self.crisis_return
+ (dec!(1) - crisis_probability) * self.normal_return
}
pub fn normal_sharpe(&self, risk_free_rate: Decimal) -> Decimal {
if self.normal_volatility > dec!(0) {
(self.normal_return - risk_free_rate) / self.normal_volatility
} else {
dec!(0)
}
}
pub fn crisis_alpha(&self, market_crash_pct: Decimal) -> Decimal {
self.crisis_return - (-market_crash_pct)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_black_swan_scenarios() {
let crisis = BlackSwanScenario::financial_crisis();
assert_eq!(crisis.market_crash_pct, dec!(0.40));
assert!(crisis.probability > dec!(0));
let crypto = BlackSwanScenario::crypto_winter();
assert_eq!(crypto.market_crash_pct, dec!(0.70));
let flash = BlackSwanScenario::flash_crash();
assert_eq!(flash.market_crash_pct, dec!(0.20));
}
#[test]
fn test_tail_risk_hedger_creation() {
let hedger = TailRiskHedger::new(dec!(100000));
assert_eq!(hedger.portfolio_value, dec!(100000));
assert_eq!(hedger.strategies.len(), 0);
}
#[test]
fn test_put_option_strategy_cost() {
let mut hedger = TailRiskHedger::new(dec!(100000));
hedger.add_strategy(TailHedgingStrategy::PutOptions {
strike_ratio: dec!(0.90), coverage: dec!(0.50), });
let cost = hedger.calculate_hedging_cost();
assert!(cost > dec!(0));
assert!(cost < dec!(5000)); }
#[test]
fn test_put_option_protection() {
let mut hedger = TailRiskHedger::new(dec!(100000));
hedger.add_strategy(TailHedgingStrategy::PutOptions {
strike_ratio: dec!(0.80), coverage: dec!(1.0), });
let scenario = BlackSwanScenario::financial_crisis(); let protection = hedger.calculate_protection(&scenario);
assert!(protection > dec!(15000));
assert!(protection < dec!(25000));
}
#[test]
fn test_hedge_effectiveness() {
let mut hedger = TailRiskHedger::new(dec!(100000));
hedger.add_strategy(TailHedgingStrategy::PutOptions {
strike_ratio: dec!(0.80),
coverage: dec!(1.0),
});
let scenario = BlackSwanScenario::financial_crisis();
let effectiveness = hedger.hedge_effectiveness(&scenario);
assert!(effectiveness > dec!(0));
assert!(effectiveness <= dec!(1));
}
#[test]
fn test_crisis_alpha() {
let mut hedger = TailRiskHedger::new(dec!(100000));
hedger.add_strategy(TailHedgingStrategy::Volatility {
target_exposure: dec!(10000),
});
let scenario = BlackSwanScenario::flash_crash();
let alpha = hedger.crisis_alpha(&scenario);
assert!(alpha != dec!(0));
}
#[test]
fn test_tail_risk_parity_contributions() {
let weights = vec![dec!(0.5), dec!(0.3), dec!(0.2)];
let tail_cov = vec![
vec![dec!(0.04), dec!(0.02), dec!(0.01)],
vec![dec!(0.02), dec!(0.09), dec!(0.03)],
vec![dec!(0.01), dec!(0.03), dec!(0.16)],
];
let contributions = TailRiskParity::calculate_tail_contributions(&weights, &tail_cov);
assert_eq!(contributions.len(), 3);
assert!(contributions.iter().all(|&c| c >= dec!(0)));
}
#[test]
fn test_tail_risk_parity_optimization() {
let initial = vec![dec!(0.5), dec!(0.3), dec!(0.2)];
let tail_cov = vec![
vec![dec!(0.04), dec!(0.01), dec!(0.01)],
vec![dec!(0.01), dec!(0.04), dec!(0.01)],
vec![dec!(0.01), dec!(0.01), dec!(0.04)],
];
let optimized = TailRiskParity::optimize_weights(&initial, &tail_cov, 10);
let sum: Decimal = optimized.iter().sum();
assert!(sum > dec!(0.95) && sum < dec!(1.05));
assert!(optimized.iter().all(|&w| w >= dec!(0)));
}
#[test]
fn test_crisis_alpha_strategy() {
let strategy = CrisisAlphaStrategy::long_volatility();
assert_eq!(strategy.name, "Long Volatility");
assert!(strategy.normal_return < dec!(0)); assert!(strategy.crisis_return > dec!(0)); }
#[test]
fn test_strategy_expected_return() {
let strategy = CrisisAlphaStrategy::trend_following();
let crisis_prob = dec!(0.10);
let exp_return = strategy.expected_return(crisis_prob);
assert!(exp_return > strategy.normal_return);
assert!(exp_return < strategy.crisis_return);
}
#[test]
fn test_strategy_crisis_alpha() {
let strategy = CrisisAlphaStrategy::tail_hedge_fund();
let market_crash = dec!(0.40);
let alpha = strategy.crisis_alpha(market_crash);
assert!(alpha > dec!(1.0));
}
#[test]
fn test_multiple_strategies() {
let mut hedger = TailRiskHedger::new(dec!(1000000));
hedger.add_strategy(TailHedgingStrategy::PutOptions {
strike_ratio: dec!(0.85),
coverage: dec!(0.50),
});
hedger.add_strategy(TailHedgingStrategy::SafeHaven {
allocation: dec!(0.10),
});
let cost = hedger.calculate_hedging_cost();
assert!(cost > dec!(0));
let scenario = BlackSwanScenario::crypto_winter();
let protection = hedger.calculate_protection(&scenario);
assert!(protection > dec!(0));
}
#[test]
fn test_optimal_hedge_ratio() {
let mut hedger = TailRiskHedger::new(dec!(100000));
hedger.add_strategy(TailHedgingStrategy::PutOptions {
strike_ratio: dec!(0.80),
coverage: dec!(1.0),
});
let expected_return = dec!(0.10);
let volatility = dec!(0.20);
let ratio = hedger.optimal_hedge_ratio(expected_return, volatility);
assert!(ratio >= dec!(0));
}
}