DDM Drawdown Management
The math of not blowing up — and why it's asymmetric
Learning Objectives
- •Understand the brutal asymmetry of drawdown recovery
- •Learn systematic drawdown management protocols
- •See how DD-triggered risk scaling works in practice
- •Internalize why preventing drawdowns beats chasing returns
Explain Like I'm 5
A 50% drawdown needs a 100% gain to recover. A 10% drawdown only needs 11%. The math is brutally asymmetric — losses hurt way more than gains help. That's why any serious system needs dedicated drawdown control. Making less money is always better than risking a blowup.
Think of It This Way
Think of a race car driver. The fastest driver isn't the one who pushes hardest on every turn — it's the one who knows when to brake. Pushing too hard means crashing. Conservative in the dangerous spots, aggressive on the straightaways. Drawdown protection is your anti-lock braking system.
1The Recovery Asymmetry
Recovery Needed vs Drawdown Size
2DD-Triggered Risk Scaling
3Risk Zones Visualized
Risk Allocation by Drawdown Zone
4After Drawdown, Returns Tend to Improve
5The Psychology Problem
Key Formulas
Drawdown
How far current equity (E_t) has fallen from its high water mark (HWM_t). A 5% DD means you're 5% below your peak.
Recovery Needed
The return needed to recover from drawdown DD. 10% DD needs 11.1% recovery. 50% DD needs 100%. The asymmetry gets worse fast.
Hands-On Code
DD-Triggered Risk Scaling
import numpy as np
class DrawdownRiskScaler:
"""Automatically reduce risk as drawdown increases."""
ZONES = {
'NORMAL': {'range': (0.00, 0.04), 'risk': 0.0030},
'CAUTION': {'range': (0.04, 0.06), 'risk': 0.0025},
'DANGER': {'range': (0.06, 0.08), 'risk': 0.0020},
'CRITICAL': {'range': (0.08, 0.10), 'risk': 0.0015},
}
def __init__(self, starting_balance):
self.hwm = starting_balance
def get_zone(self, balance):
self.hwm = max(self.hwm, balance)
dd = (self.hwm - balance) / self.hwm
for zone, params in self.ZONES.items():
low, high = params['range']
if low <= dd < high:
return zone, dd, params['risk']
return 'HALT', dd, 0.0
def position_size(self, balance, atr):
zone, dd, risk_pct = self.get_zone(balance)
size = (balance * risk_pct) / atr
print(f"Zone: {zone} | DD: {dd:.2%} | Risk: {risk_pct:.2%}")
return sizeSimple, automatic, no discretion needed. The system detects drawdown and adjusts. This is what keeps a production system alive during bad stretches.
Knowledge Check
Q1.A 20% drawdown requires what recovery to break even?
Q2.Why start at full risk instead of starting conservative?
Assignment
Implement DD-triggered risk scaling and backtest it against fixed risk sizing. Compare total return, max drawdown, and Sharpe ratio. The adaptive version should show similar returns with meaningfully lower max drawdown.