1use rust_decimal::Decimal;
4
5use crate::MetricsError;
6
7pub struct RollingWindow {
21 window_size: usize,
22}
23
24impl RollingWindow {
25 pub fn new(window_size: usize) -> Self {
30 Self { window_size }
31 }
32
33 pub fn returns(&self, equity: &[Decimal]) -> Result<Vec<Decimal>, MetricsError> {
37 if equity.len() < self.window_size {
38 return Err(MetricsError::InsufficientData {
39 required: self.window_size,
40 actual: equity.len(),
41 });
42 }
43
44 let mut results = Vec::with_capacity(equity.len() - self.window_size + 1);
45
46 for window in equity.windows(self.window_size) {
47 let start = window[0];
48 let end = window[self.window_size - 1];
49
50 if start == Decimal::ZERO {
51 results.push(Decimal::ZERO);
52 } else {
53 let ret = ((end - start) / start) * Decimal::from(100);
54 results.push(ret);
55 }
56 }
57
58 Ok(results)
59 }
60
61 pub fn volatility(&self, equity: &[Decimal]) -> Result<Vec<Decimal>, MetricsError> {
63 if equity.len() < self.window_size + 1 {
64 return Err(MetricsError::InsufficientData {
65 required: self.window_size + 1,
66 actual: equity.len(),
67 });
68 }
69
70 let returns: Vec<Decimal> = equity
72 .windows(2)
73 .filter_map(|w| {
74 if w[0] == Decimal::ZERO {
75 None
76 } else {
77 Some((w[1] - w[0]) / w[0])
78 }
79 })
80 .collect();
81
82 if returns.len() < self.window_size {
83 return Err(MetricsError::InsufficientData {
84 required: self.window_size,
85 actual: returns.len(),
86 });
87 }
88
89 let mut results = Vec::with_capacity(returns.len() - self.window_size + 1);
90
91 for window in returns.windows(self.window_size) {
92 let std = std_deviation(window);
93 results.push(std);
94 }
95
96 Ok(results)
97 }
98
99 pub fn sharpe(
106 &self,
107 equity: &[Decimal],
108 risk_free_rate: Decimal,
109 periods_per_year: u32,
110 ) -> Result<Vec<Decimal>, MetricsError> {
111 if equity.len() < self.window_size + 1 {
112 return Err(MetricsError::InsufficientData {
113 required: self.window_size + 1,
114 actual: equity.len(),
115 });
116 }
117
118 let returns: Vec<Decimal> = equity
119 .windows(2)
120 .filter_map(|w| {
121 if w[0] == Decimal::ZERO {
122 None
123 } else {
124 Some((w[1] - w[0]) / w[0])
125 }
126 })
127 .collect();
128
129 if returns.len() < self.window_size {
130 return Err(MetricsError::InsufficientData {
131 required: self.window_size,
132 actual: returns.len(),
133 });
134 }
135
136 let period_rf = risk_free_rate / Decimal::from(periods_per_year);
137 let sqrt_periods = decimal_sqrt(Decimal::from(periods_per_year));
138
139 let mut results = Vec::with_capacity(returns.len() - self.window_size + 1);
140
141 for window in returns.windows(self.window_size) {
142 let mean_ret = mean(window);
143 let std = std_deviation(window);
144
145 if std == Decimal::ZERO {
146 results.push(Decimal::ZERO);
147 } else {
148 let sharpe = ((mean_ret - period_rf) / std) * sqrt_periods;
149 results.push(sharpe);
150 }
151 }
152
153 Ok(results)
154 }
155
156 pub fn max_drawdown(&self, equity: &[Decimal]) -> Result<Vec<Decimal>, MetricsError> {
158 if equity.len() < self.window_size {
159 return Err(MetricsError::InsufficientData {
160 required: self.window_size,
161 actual: equity.len(),
162 });
163 }
164
165 let mut results = Vec::with_capacity(equity.len() - self.window_size + 1);
166
167 for window in equity.windows(self.window_size) {
168 let max_dd = window_max_drawdown(window);
169 results.push(max_dd);
170 }
171
172 Ok(results)
173 }
174}
175
176fn window_max_drawdown(equity: &[Decimal]) -> Decimal {
178 if equity.is_empty() {
179 return Decimal::ZERO;
180 }
181
182 let mut peak = equity[0];
183 let mut max_dd = Decimal::ZERO;
184
185 for &value in equity {
186 if value > peak {
187 peak = value;
188 }
189
190 if peak != Decimal::ZERO {
191 let dd = (value - peak) / peak;
192 if dd < max_dd {
193 max_dd = dd;
194 }
195 }
196 }
197
198 max_dd * Decimal::from(100)
199}
200
201use crate::math::{decimal_sqrt, mean, std_deviation};
202
203#[cfg(test)]
204#[path = "rolling_tests.rs"]
205mod tests;