rusty_backtest/features/
volatility.rs1use super::TradeTick;
6use rust_decimal::prelude::ToPrimitive;
7
8#[must_use]
12pub fn calculate_realized_volatility(trades: &[TradeTick]) -> f64 {
13 if trades.len() < 2 {
14 return 0.0;
15 }
16
17 let mut returns = Vec::with_capacity(trades.len() - 1);
19 for i in 1..trades.len() {
20 if let (Some(prev_price), Some(curr_price)) =
21 (trades[i - 1].price.to_f64(), trades[i].price.to_f64())
22 && prev_price > 0.0
23 && curr_price > 0.0
24 {
25 returns.push((curr_price / prev_price).ln());
26 }
27 }
28
29 if returns.is_empty() {
30 return 0.0;
31 }
32
33 let mean = returns.iter().sum::<f64>() / returns.len() as f64;
35
36 if returns.len() == 1 {
38 return returns[0].abs(); }
40
41 let variance =
42 returns.iter().map(|&r| (r - mean).powi(2)).sum::<f64>() / (returns.len() - 1) as f64;
43
44 variance.sqrt()
45}
46
47pub struct VolatilityEstimator {
49 decay_factor: f64,
50 current_volatility: f64,
51 is_initialized: bool,
52}
53
54impl VolatilityEstimator {
55 #[must_use]
57 pub const fn new(decay_factor: f64) -> Self {
58 Self {
59 decay_factor,
60 current_volatility: 0.0,
61 is_initialized: false,
62 }
63 }
64
65 #[must_use]
67 pub fn update(&mut self, return_value: f64) -> f64 {
68 if !self.is_initialized {
69 self.current_volatility = return_value.abs();
70 self.is_initialized = true;
71 } else {
72 self.current_volatility = self.decay_factor * return_value.abs()
74 + (1.0 - self.decay_factor) * self.current_volatility;
75 }
76
77 self.current_volatility
78 }
79
80 pub const fn get_volatility(&self) -> f64 {
82 self.current_volatility
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::features::TradeSide;
90 use rust_decimal_macros::dec;
91
92 #[test]
93 fn test_realized_volatility_zero_for_single_trade() {
94 let trades = vec![TradeTick {
95 timestamp_ns: 1000000000,
96 symbol: "BTC-USD".into(),
97 side: TradeSide::Buy,
98 price: dec!(50000),
99 quantity: dec!(1.0),
100 }];
101
102 let volatility = calculate_realized_volatility(&trades);
103 assert_eq!(volatility, 0.0);
104 }
105
106 #[test]
107 fn test_volatility_estimator() {
108 let mut estimator = VolatilityEstimator::new(0.1);
109
110 let vol1 = estimator.update(0.01);
112 assert_eq!(vol1, 0.01);
113
114 let vol2 = estimator.update(0.02);
116 assert!((vol2 - (0.1 * 0.02 + 0.9 * 0.01)).abs() < 1e-10);
117 }
118}