DEV Community

Ray
Ray

Posted on

How I Added a Circuit Breaker to My AI Trading Bot (And Why Every Algo Needs One)

My TradeSight algo was generating 83% annual returns in backtests. Live paper trading? It burned through 8% of the portfolio in 3 days during a volatile session.

The problem wasn't the strategy. It was the absence of a circuit breaker.

What's a Circuit Breaker in Trading

In distributed systems, a circuit breaker stops cascading failures. If a service is down, you don't keep hammering it — you open the circuit, stop requests, and try again later.

Same idea applies to trading systems: if something is going wrong (drawdown, bad fills, market conditions), stop trading and wait.

The Failure Mode

My bot had no circuit breaker. When it hit bad trades in a choppy session, it kept entering positions, compounding losses. By the time I intervened manually, the damage was done.

The backtester never caught this because:

  1. Backtests don't simulate slippage accurately
  2. Backtests don't have intraday drawdown limits
  3. Backtests don't model cascading position entries

The Implementation

Here's the circuit breaker I added to TradeSight:

class CircuitBreaker:
    def __init__(self, max_daily_loss_pct=0.02, max_consecutive_losses=3):
        self.max_daily_loss_pct = max_daily_loss_pct
        self.max_consecutive_losses = max_consecutive_losses
        self.daily_loss = 0.0
        self.consecutive_losses = 0
        self.tripped = False
        self.trip_time = None
        self.cooldown_hours = 24

    def record_trade(self, pnl_pct: float):
        if pnl_pct < 0:
            self.daily_loss += abs(pnl_pct)
            self.consecutive_losses += 1
        else:
            self.consecutive_losses = 0

        self._check_trip()

    def _check_trip(self):
        if self.daily_loss >= self.max_daily_loss_pct:
            self._trip("Daily loss limit hit")
        elif self.consecutive_losses >= self.max_consecutive_losses:
            self._trip(f"{self.consecutive_losses} consecutive losses")

    def _trip(self, reason: str):
        self.tripped = True
        self.trip_time = datetime.now()
        logger.warning(f"CIRCUIT BREAKER TRIPPED: {reason}")
        # Alert via webhook/WhatsApp

    def is_open(self) -> bool:
        """Returns True if trading should be HALTED."""
        if not self.tripped:
            return False
        # Auto-reset after cooldown
        hours_since = (datetime.now() - self.trip_time).seconds / 3600
        if hours_since >= self.cooldown_hours:
            self.reset()
            return False
        return True

    def reset(self):
        self.tripped = False
        self.daily_loss = 0.0
        self.consecutive_losses = 0
        logger.info("Circuit breaker reset")
Enter fullscreen mode Exit fullscreen mode

Integration in the main trading loop:

async def trading_loop():
    cb = CircuitBreaker(max_daily_loss_pct=0.02, max_consecutive_losses=3)

    while True:
        if cb.is_open():
            logger.info("Circuit breaker open — skipping signal evaluation")
            await asyncio.sleep(300)
            continue

        signal = await evaluate_signal()
        if signal:
            result = await execute_trade(signal)
            cb.record_trade(result.pnl_pct)

        await asyncio.sleep(60)
Enter fullscreen mode Exit fullscreen mode

The Parameters That Actually Matter

Through paper trading, I found these thresholds work for a momentum strategy:

  • Daily loss limit: 2% — beyond this, market conditions are probably hostile
  • Consecutive losses: 3 — pattern signal, not random noise
  • Cooldown: 24 hours — reset next market day, not same session

These are conservative. If you're running higher leverage, tighten them. If you're swing trading, loosen them.

Other Guards I Added

The circuit breaker was the main one, but I also added:

Stop-loss per position: Close any single position that hits -1.5%. No exceptions.

Max concurrent positions: Hard cap at 3 open positions. Prevents over-concentration during volatile sessions.

Market hours filter: Only trade 9:45 AM – 3:30 PM ET. Skip first and last 15 minutes — high volatility, poor fills.

Post stop-loss cooldown: After a stop-loss hit on ticker X, don't re-enter X for 7 days. Prevents the ADBE-style re-entry loops I was seeing.

The Result

After adding the circuit breaker, paper trading losses in volatile sessions dropped 80%. The bot still loses on bad days — that's expected — but it stops losing compounding amounts.

The strategy is the same. The risk management is what changed the outcome.

Open Source

TradeSight is open source: github.com/rmbell09-lang/tradesight

The circuit breaker is in tradesight/risk/circuit_breaker.py. Paper trading runs via Alpaca's sandbox API — no real money needed to test.


What other risk controls have you found essential for live algo trading? I'm still iterating on the drawdown recovery logic — would love to compare notes.

Top comments (0)