finance_query/risk/
mod.rs1mod beta;
27mod drawdown;
28mod ratios;
29mod var;
30
31pub use self::beta::beta;
32pub use self::drawdown::max_drawdown;
33pub use self::ratios::{calmar_ratio, sharpe_ratio, sortino_ratio};
34pub use self::var::{historical_var, parametric_var};
35
36use crate::models::chart::Candle;
37use serde::{Deserialize, Serialize};
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
43#[non_exhaustive]
44pub struct RiskSummary {
45 pub var_95: f64,
47 pub var_99: f64,
49 pub parametric_var_95: f64,
51 pub sharpe: Option<f64>,
54 pub sortino: Option<f64>,
57 pub calmar: Option<f64>,
60 pub beta: Option<f64>,
62 pub max_drawdown: f64,
64 pub max_drawdown_recovery_periods: Option<u64>,
67}
68
69pub(crate) fn candles_to_returns(candles: &[Candle]) -> Vec<f64> {
71 candles
72 .windows(2)
73 .map(|w| (w[1].close - w[0].close) / w[0].close)
74 .collect()
75}
76
77pub(crate) fn compute_risk_summary(
79 candles: &[Candle],
80 benchmark_returns: Option<&[f64]>,
81) -> RiskSummary {
82 let returns = candles_to_returns(candles);
83
84 let var_95 = historical_var(&returns, 0.95).unwrap_or(0.0);
85 let var_99 = historical_var(&returns, 0.99).unwrap_or(0.0);
86 let parametric_var_95 = parametric_var(&returns, 0.95).unwrap_or(0.0);
87
88 let sharpe = sharpe_ratio(&returns, 0.0, 252.0);
89 let sortino = sortino_ratio(&returns, 0.0, 252.0);
90
91 let dd = max_drawdown(&returns);
92 let total_return = returns.iter().fold(1.0_f64, |acc, r| acc * (1.0 + r)) - 1.0;
93 let years = returns.len() as f64 / 252.0;
94 let calmar = calmar_ratio(total_return, years, dd.max_drawdown);
95
96 let beta_val = benchmark_returns.and_then(|br| beta(&returns, br));
97
98 RiskSummary {
99 var_95,
100 var_99,
101 parametric_var_95,
102 sharpe,
103 sortino,
104 calmar,
105 beta: beta_val,
106 max_drawdown: dd.max_drawdown,
107 max_drawdown_recovery_periods: dd.recovery_periods,
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 fn make_candle(close: f64) -> Candle {
116 Candle {
117 timestamp: 0,
118 open: close,
119 high: close,
120 low: close,
121 close,
122 volume: 1_000_000,
123 adj_close: None,
124 }
125 }
126
127 #[test]
128 fn test_compute_risk_summary_flat() {
129 let candles: Vec<Candle> = (0..=252).map(|_| make_candle(100.0)).collect();
131 let summary = compute_risk_summary(&candles, None);
132 assert_eq!(summary.var_95, 0.0);
133 assert_eq!(summary.max_drawdown, 0.0);
134 assert!(summary.sharpe.is_none());
135 }
136
137 #[test]
138 fn test_candles_to_returns_basic() {
139 let candles = vec![make_candle(100.0), make_candle(110.0), make_candle(99.0)];
140 let returns = candles_to_returns(&candles);
141 assert_eq!(returns.len(), 2);
142 assert!((returns[0] - 0.10).abs() < 1e-9);
143 assert!((returns[1] - (-0.1)).abs() < 0.01);
144 }
145}