use rust_decimal::Decimal;
use crate::MetricsError;
pub struct RollingWindow {
window_size: usize,
}
impl RollingWindow {
pub fn new(window_size: usize) -> Self {
Self { window_size }
}
pub fn returns(&self, equity: &[Decimal]) -> Result<Vec<Decimal>, MetricsError> {
if equity.len() < self.window_size {
return Err(MetricsError::InsufficientData {
required: self.window_size,
actual: equity.len(),
});
}
let mut results = Vec::with_capacity(equity.len() - self.window_size + 1);
for window in equity.windows(self.window_size) {
let start = window[0];
let end = window[self.window_size - 1];
if start == Decimal::ZERO {
results.push(Decimal::ZERO);
} else {
let ret = ((end - start) / start) * Decimal::from(100);
results.push(ret);
}
}
Ok(results)
}
pub fn volatility(&self, equity: &[Decimal]) -> Result<Vec<Decimal>, MetricsError> {
if equity.len() < self.window_size + 1 {
return Err(MetricsError::InsufficientData {
required: self.window_size + 1,
actual: equity.len(),
});
}
let returns: Vec<Decimal> = equity
.windows(2)
.filter_map(|w| {
if w[0] == Decimal::ZERO {
None
} else {
Some((w[1] - w[0]) / w[0])
}
})
.collect();
if returns.len() < self.window_size {
return Err(MetricsError::InsufficientData {
required: self.window_size,
actual: returns.len(),
});
}
let mut results = Vec::with_capacity(returns.len() - self.window_size + 1);
for window in returns.windows(self.window_size) {
let std = std_deviation(window);
results.push(std);
}
Ok(results)
}
pub fn sharpe(
&self,
equity: &[Decimal],
risk_free_rate: Decimal,
periods_per_year: u32,
) -> Result<Vec<Decimal>, MetricsError> {
if equity.len() < self.window_size + 1 {
return Err(MetricsError::InsufficientData {
required: self.window_size + 1,
actual: equity.len(),
});
}
let returns: Vec<Decimal> = equity
.windows(2)
.filter_map(|w| {
if w[0] == Decimal::ZERO {
None
} else {
Some((w[1] - w[0]) / w[0])
}
})
.collect();
if returns.len() < self.window_size {
return Err(MetricsError::InsufficientData {
required: self.window_size,
actual: returns.len(),
});
}
let period_rf = risk_free_rate / Decimal::from(periods_per_year);
let sqrt_periods = decimal_sqrt(Decimal::from(periods_per_year));
let mut results = Vec::with_capacity(returns.len() - self.window_size + 1);
for window in returns.windows(self.window_size) {
let mean_ret = mean(window);
let std = std_deviation(window);
if std == Decimal::ZERO {
results.push(Decimal::ZERO);
} else {
let sharpe = ((mean_ret - period_rf) / std) * sqrt_periods;
results.push(sharpe);
}
}
Ok(results)
}
pub fn max_drawdown(&self, equity: &[Decimal]) -> Result<Vec<Decimal>, MetricsError> {
if equity.len() < self.window_size {
return Err(MetricsError::InsufficientData {
required: self.window_size,
actual: equity.len(),
});
}
let mut results = Vec::with_capacity(equity.len() - self.window_size + 1);
for window in equity.windows(self.window_size) {
let max_dd = window_max_drawdown(window);
results.push(max_dd);
}
Ok(results)
}
}
fn window_max_drawdown(equity: &[Decimal]) -> Decimal {
if equity.is_empty() {
return Decimal::ZERO;
}
let mut peak = equity[0];
let mut max_dd = Decimal::ZERO;
for &value in equity {
if value > peak {
peak = value;
}
if peak != Decimal::ZERO {
let dd = (value - peak) / peak;
if dd < max_dd {
max_dd = dd;
}
}
}
max_dd * Decimal::from(100)
}
use crate::math::{decimal_sqrt, mean, std_deviation};
#[cfg(test)]
#[path = "rolling_tests.rs"]
mod tests;