Agent skill

drawdown-guardrails-pattern

Consistent drawdown control pattern for trading systems - backtests, live trading, and training

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/testing/drawdown-guardrails-pattern

SKILL.md

Drawdown Guardrails Pattern - Research Notes

Experiment Overview

Item Details
Date 2025-12-16
Goal Implement consistent drawdown controls across all trading system components
Environment Python 3.10, PyTorch, custom trading system
Status Success

Context

Trading systems need drawdown protection at multiple layers:

  1. Backtesting - Avoid selecting high-drawdown strategies during model evaluation
  2. Live Trading - Halt or reduce risk when equity drops
  3. Training - Penalize models that learn high-return/high-drawdown behavior

Without consistent guardrails, a model might backtest well but blow up in production.

Verified Workflow

1. Configuration (Single Source of Truth)

python
# Define limits once, reference everywhere
MAX_DRAWDOWN_PCT = 0.15      # 15% - halt trading
DRAWDOWN_WARNING_PCT = 0.10  # 10% - reduce position sizes
DRAWDOWN_SIZING_SCALE = 0.5  # 50% position size in warning zone

2. Drawdown Tracking State

python
@dataclass
class DrawdownState:
    peak_equity: float
    current_drawdown_pct: float = 0.0
    drawdown_warning_triggered: bool = False
    drawdown_halt_triggered: bool = False  # Once triggered, stays triggered

def update_drawdown_tracking(self, current_equity: float):
    """Update drawdown state - call after every equity calculation."""
    # Update peak (high water mark)
    if current_equity > self.peak_equity:
        self.peak_equity = current_equity

    # Calculate current drawdown
    if self.peak_equity > 0:
        self.current_drawdown_pct = (self.peak_equity - current_equity) / self.peak_equity

    # Update flags
    self.drawdown_warning_triggered = self.current_drawdown_pct >= DRAWDOWN_WARNING_PCT

    # Halt flag is sticky - once triggered, stays triggered until manual reset
    if self.current_drawdown_pct >= MAX_DRAWDOWN_PCT:
        self.drawdown_halt_triggered = True

3. Guardrail Check Function

python
def check_drawdown_guardrails(self) -> Tuple[bool, str, float]:
    """
    Returns: (can_trade, reason, position_scale)
    """
    # Check halt condition
    if self.drawdown_halt_triggered:
        return False, f"max_drawdown_{self.current_drawdown_pct:.1%}", 0.0

    # Check warning condition
    if self.drawdown_warning_triggered:
        return True, f"drawdown_warning_{self.current_drawdown_pct:.1%}", DRAWDOWN_SIZING_SCALE

    # Normal operation
    return True, "ok", 1.0

4. Integration Points

Backtest Engine

python
for bar_idx in range(start_idx, end_idx):
    # Calculate equity BEFORE new trades
    equity = self._calculate_equity(current_price)

    # Update drawdown tracking
    self._update_drawdown_tracking(equity)
    can_trade, reason, position_scale = self._check_drawdown_guardrails()

    # Only process signals if allowed
    if can_trade:
        self._process_signal(..., position_scale=position_scale)
    # Even if halted, existing positions can still exit via stop-loss/TP

Live Trading Loop

python
while running:
    # Check drawdown before any trading
    can_trade, reason, position_scale = profit_tracker.check_trading_conditions(
        max_drawdown_pct=0.15
    )

    if not can_trade:
        logger.warning(f"TRADING HALTED: {reason}")
        # Allow exits but no new entries
        continue

    # Pass scale to trading function
    new_state = decide_and_trade(..., position_scale=position_scale)

Training Validation

python
# Compute fitness with drawdown penalty
drawdown_penalty = config.drawdown_penalty_weight * max_drawdown
fitness_score = sharpe_ratio * max(0.0, 1.0 - drawdown_penalty)

# Early stopping on excessive drawdown
if max_drawdown > config.max_drawdown_threshold:
    print(f"DRAWDOWN EARLY STOP: {max_drawdown:.1%}")
    should_stop = True

Failed Attempts (Critical)

Attempt Why it Failed Lesson Learned
Only checking drawdown on trade entry Positions can gap down overnight Check on every bar/loop
Resetting halt flag when drawdown recovers Creates "trading whipsaw" behavior Halt should be sticky until manual reset
Using absolute dollar drawdown Not comparable across account sizes Always use percentage of peak
Linear position scaling 0-100% Too aggressive reduction at small drawdowns Use threshold-based (warning zone)
Not allowing exits when halted Positions stuck, can't cut losses Always allow stop-loss exits
Drawdown from initial capital Misses profit drawdowns Track from peak equity (high water mark)

Final Parameters

python
# Recommended guardrail configuration
@dataclass
class DrawdownConfig:
    max_drawdown_pct: float = 0.15      # 15% - matches common hedge fund limits
    drawdown_warning_pct: float = 0.10  # 10% - early warning
    drawdown_sizing_scale: float = 0.5  # 50% in warning zone
    halt_on_max_drawdown: bool = True   # Hard stop at max
    drawdown_penalty_weight: float = 2.0  # Training penalty multiplier

Key Insights

  • Drawdown from peak, not initial - Track high water mark, not starting capital
  • Halt flag should be sticky - Don't auto-resume trading after drawdown recovery
  • Allow exits even when halted - Can still cut losses, just no new entries
  • Position scaling is gradual - Warning zone reduces size, halt zone blocks entirely
  • Consistent across all layers - Same 15% limit in backtest, live, and training
  • Training penalty prevents selection - Models that drawdown heavily get lower fitness
  • Log when triggered - Visibility into when guardrails activate

Testing Checklist

python
# Verify these scenarios:
1. [ ] Drawdown hits 10% -> position sizes reduced 50%
2. [ ] Drawdown hits 15% -> new entries blocked
3. [ ] Recovery to 5% drawdown -> halt flag STILL active (sticky)
4. [ ] Stop-loss orders execute even when halted
5. [ ] Training early-stops on 15%+ drawdown
6. [ ] Backtest metrics show drawdown correctly

References

  • CLAUDE.md: "Max drawdown trigger: 15%"
  • Hedge fund risk management: 15-20% typical max drawdown limits
  • Kelly criterion: Fractional Kelly (0.25-0.5x) to reduce drawdown

Didn't find tool you were looking for?

Be as detailed as possible for better results