Skip to content

Max Drawdown (MDD)

The deepest peak-to-trough decline within a trailing window. Operates on an equity-curve sample (or any non-negative value series). Output is the magnitude of the worst drawdown as a non-negative fraction (0.20 = 20% drop from peak). Amortised O(1) per update via a monotonically-decreasing deque.

Quick reference

ItemValue
FamilyRisk / Performance
Input typef64 — one equity-curve sample per update
Output typef64
Output range[0, 1] (fraction of peak)
Default parametersperiod required
Warmup period1 (running peak emits from first input)
InterpretationWorst drawdown over the rolling window

Formula

peak_t      = running max of equity over the trailing period bars
drawdown_t  = (equity_t - peak_t) / peak_t                  (negative)
MaxDrawdown = -min(drawdown_t over window)                  (positive magnitude)

A monotonically rising equity curve has MDD = 0. Setting period ≥ the number of bars you will ever feed makes the metric effectively cumulative — the running peak is never dropped from the window. See crates/wickra-core/src/indicators/max_drawdown.rs.

Parameters

NameTypeDefaultConstraintDescription
periodusizenone> 0Rolling window length. Use very large value for cumulative MDD.

Inputs / Outputs

Indicator<Input = f64, Output = f64>. Python / Node: 1-D output, each value the MDD over the trailing window ending at that bar.

Warmup

warmup_period() == 1. Emits from the first input (MDD = 0).

Edge cases

  • Monotonically rising input. MDD = 0 throughout.
  • Recovery to new peak. Once equity exceeds the prior peak, the running peak updates and subsequent drawdowns are measured from the new peak.
  • Negative equity. Possible with leveraged products; this indicator assumes positive equity for the fractional drawdown to be meaningful.
  • Reset. Clears the rolling deque.

Examples

Rust

rust
use wickra::{BatchExt, Indicator, MaxDrawdown};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let equity = vec![100.0, 110.0, 100.0, 95.0, 88.0, 90.0, 92.0, 95.0, 100.0, 105.0, 106.0];
    let mut mdd = MaxDrawdown::new(10)?;
    let out = mdd.batch(&equity);
    println!("MDD at last bar: {:?}", out.last());  // ~0.20 (110 -> 88)
    Ok(())
}

Python

python
import numpy as np
import wickra as ta

equity = np.array([100, 110, 100, 95, 88, 90, 92, 95, 100, 105, 106], dtype=float)
mdd = ta.MaxDrawdown(10)
print(mdd.batch(equity))

Node

javascript
const wickra = require('wickra');
const mdd = new wickra.MaxDrawdown(10);
console.log(mdd.batch([100, 110, 100, 95, 88, 90, 92, 95, 100, 105, 106]));

Streaming

rust
use wickra::{Indicator, MaxDrawdown};

let mut mdd = MaxDrawdown::new(252).unwrap();
let equity_stream: Vec<f64> = Vec::new(); // your equity-curve feed
for equity in equity_stream {
    let dd = mdd.update(equity).unwrap();
    if dd > 0.10 { /* warning: > 10% drawdown */ }
}

Interpretation

  • MDD measures pain. A 20% MDD means the strategy lost 20% from its peak before recovering (or before the window slid past the drawdown).
  • Combine with Calmar. Return / MDD = Calmar.
  • Window choice. Use period = 252 for "trailing one-year MDD" on daily returns; a much larger period for cumulative history.

Common pitfalls

  • Misinterpreting magnitude. Output is positive (e.g. 0.15 = 15% drop). Don't confuse with a signed drawdown series.
  • Period too short. A period = 20 MDD only "remembers" the last 20 bars; a deeper drawdown earlier disappears from the window. Use longer windows for risk reporting.
  • Cumulative confusion. If you want all-time MDD, pick a period larger than your total sample length — the running peak never gets dropped.

References

  • Standard portfolio-management measure; documented in Magdon-Ismail & Atiya, Maximum Drawdown, Risk Magazine, October 2004 — formal statistical treatment.

See also