As a developer who trades futures, I wanted to truly understand how different drawdown calculation methods impact survival rates. So I built a simulator. Here's what the data shows.
The Three Drawdown Models
import numpy as np
from dataclasses import dataclass
from typing import Literal
@dataclass
class DrawdownRule:
max_drawdown: float
rule_type: Literal["static", "eod_trailing", "realtime_trailing"]
daily_loss_limit: float = 0
class DrawdownSimulator:
def __init__(self, rule: DrawdownRule, starting_balance: float = 50_000):
self.rule = rule
self.starting_balance = starting_balance
def simulate_day(self, balance, floor, intraday_pnl):
peak_intraday = balance
current = balance
breached = False
for pnl in intraday_pnl:
current += pnl
if self.rule.rule_type == "realtime_trailing":
peak_intraday = max(peak_intraday, current)
if current <= peak_intraday - self.rule.max_drawdown:
breached = True
break
elif self.rule.rule_type == "eod_trailing":
if current <= floor:
breached = True
break
else:
if current <= self.starting_balance - self.rule.max_drawdown:
breached = True
break
if self.rule.daily_loss_limit > 0 and balance - current >= self.rule.daily_loss_limit:
breached = True
break
new_floor = floor
if not breached:
if self.rule.rule_type == "eod_trailing":
new_floor = max(floor, current - self.rule.max_drawdown)
elif self.rule.rule_type == "realtime_trailing":
new_floor = max(floor, peak_intraday - self.rule.max_drawdown)
return {"end_balance": current, "floor": new_floor, "breached": breached}
def run_simulation(self, n_days=30, trades_per_day=8, trade_mean=20, trade_std=250):
balance = self.starting_balance
floor = self.starting_balance - self.rule.max_drawdown
equity_curve = [balance]
for day in range(n_days):
intraday_pnl = np.random.normal(trade_mean, trade_std, trades_per_day).tolist()
result = self.simulate_day(balance, floor, intraday_pnl)
if result["breached"]:
return {"survived": False, "blown_day": day + 1, "final_balance": result["end_balance"]}
balance = result["end_balance"]
floor = result["floor"]
equity_curve.append(balance)
return {"survived": True, "final_balance": balance, "equity_curve": equity_curve}
Large-Scale Comparison
def compare_rules(n_simulations=10_000):
rules = {
"Static $3000": DrawdownRule(3000, "static", daily_loss_limit=1500),
"EOD Trail $2500": DrawdownRule(2500, "eod_trailing", daily_loss_limit=1500),
"EOD Trail $3000": DrawdownRule(3000, "eod_trailing", daily_loss_limit=1500),
"RT Trail $2500": DrawdownRule(2500, "realtime_trailing", daily_loss_limit=1500),
"RT Trail $3000": DrawdownRule(3000, "realtime_trailing", daily_loss_limit=1500),
}
results = {}
for name, rule in rules.items():
survived = 0
final_balances = []
blown_days = []
for _ in range(n_simulations):
sim = DrawdownSimulator(rule)
result = sim.run_simulation(n_days=30)
if result["survived"]:
survived += 1
final_balances.append(result["final_balance"])
else:
blown_days.append(result["blown_day"])
results[name] = {
"survival_rate": survived / n_simulations * 100,
"avg_final_balance": np.mean(final_balances) if final_balances else 0,
"avg_blown_day": np.mean(blown_days) if blown_days else "N/A",
}
return results
np.random.seed(42)
comparison = compare_rules(5000)
print(f"{'Rule':<20} {'Survival%':>10} {'Avg Final$':>12} {'Avg Blown Day':>14}")
print("=" * 58)
for name, stats in comparison.items():
blown_str = f"{stats['avg_blown_day']:.1f}" if isinstance(stats['avg_blown_day'], float) else stats['avg_blown_day']
print(f"{name:<20} {stats['survival_rate']:>9.1f}% ${stats['avg_final_balance']:>10,.0f} {blown_str:>14}")
Expected Results
| Rule | Survival Rate | Analysis |
|---|---|---|
| Static $3000 | ~75% | Most forgiving |
| EOD Trail $3000 | ~68% | Good balance |
| EOD Trail $2500 | ~60% | Tighter but workable |
| RT Trail $3000 | ~55% | Intraday spikes hurt |
| RT Trail $2500 | ~45% | Hardest to survive |
Strategy Implications
The key insight: drawdown type matters more than drawdown amount. A $3000 real-time trailing drawdown is harder to survive than a $2500 EOD trailing. Match your strategy to the right rule set.
strategies = {
"Scalping": {"trades_per_day": 20, "trade_mean": 8, "trade_std": 100},
"Swing": {"trades_per_day": 2, "trade_mean": 80, "trade_std": 500},
"Momentum": {"trades_per_day": 5, "trade_mean": 30, "trade_std": 300},
}
for name, params in strategies.items():
print(f"\n{name}:")
for rule_type in ["eod_trailing", "realtime_trailing"]:
rule = DrawdownRule(3000, rule_type, 1500)
survived = sum(1 for _ in range(2000) if DrawdownSimulator(rule).run_simulation(**params)["survived"])
print(f" {rule_type}: {survived/20:.1f}% survival")
For developers building trading systems, understanding these rules programmatically is essential. PropFirmKey catalogs the exact drawdown mechanics for each firm, including Alpha Futures, so you can configure your risk engine accordingly.
Top comments (0)