use rust_decimal::Decimal;
use crate::MetricsError;
pub fn max_drawdown(equity: &[Decimal]) -> Result<Decimal, MetricsError> {
if equity.len() < 2 {
return Err(MetricsError::InsufficientData {
required: 2,
actual: equity.len(),
});
}
let dd_series = drawdown_series(equity)?;
Ok(dd_series.into_iter().min().unwrap_or(Decimal::ZERO))
}
pub fn drawdown_series(equity: &[Decimal]) -> Result<Vec<Decimal>, MetricsError> {
if equity.is_empty() {
return Err(MetricsError::InsufficientData {
required: 1,
actual: 0,
});
}
let mut peak = equity[0];
let mut drawdowns = Vec::with_capacity(equity.len());
for &value in equity {
if value > peak {
peak = value;
}
if peak == Decimal::ZERO {
drawdowns.push(Decimal::ZERO);
} else {
let dd = ((value - peak) / peak) * Decimal::from(100);
drawdowns.push(dd);
}
}
Ok(drawdowns)
}
pub fn max_drawdown_duration(equity: &[Decimal]) -> Result<usize, MetricsError> {
if equity.len() < 2 {
return Err(MetricsError::InsufficientData {
required: 2,
actual: equity.len(),
});
}
let mut peak = equity[0];
let mut peak_idx = 0;
let mut max_duration = 0;
let mut current_duration = 0;
for (i, &value) in equity.iter().enumerate() {
if value >= peak {
if current_duration > max_duration {
max_duration = current_duration;
}
peak = value;
peak_idx = i;
current_duration = 0;
} else {
current_duration = i - peak_idx;
}
}
if current_duration > max_duration {
max_duration = current_duration;
}
Ok(max_duration)
}
pub fn recovery_time(equity: &[Decimal]) -> Result<Option<usize>, MetricsError> {
if equity.len() < 2 {
return Err(MetricsError::InsufficientData {
required: 2,
actual: equity.len(),
});
}
let Some((trough_idx, trough_peak)) = find_max_drawdown_trough(equity) else {
return Ok(Some(0));
};
for (i, &value) in equity.iter().enumerate().skip(trough_idx + 1) {
if value >= trough_peak {
return Ok(Some(i - trough_idx));
}
}
Ok(None)
}
fn find_max_drawdown_trough(equity: &[Decimal]) -> Option<(usize, Decimal)> {
let mut peak = equity[0];
let mut max_dd = Decimal::ZERO;
let mut max_dd_trough_idx = 0;
let mut max_dd_peak = equity[0];
for (i, &value) in equity.iter().enumerate() {
if value > peak {
peak = value;
}
if peak != Decimal::ZERO {
let dd = (value - peak) / peak;
if dd < max_dd {
max_dd = dd;
max_dd_trough_idx = i;
max_dd_peak = peak;
}
}
}
if max_dd == Decimal::ZERO {
None
} else {
Some((max_dd_trough_idx, max_dd_peak))
}
}
#[cfg(test)]
#[path = "drawdown_tests.rs"]
mod tests;