Risk Management
Drawdown Limits and Kill Switches
Every strategy has a statistical validity boundary. It is the point at which the historical edge that justified deploying the strategy no longer applies to current market conditions — regime shifted, the inefficiency compressed, or the parameters were overfit to a period that has ended. Most algorithms have no way to detect when they have crossed it. They keep trading.
This is not a fringe risk. It is the default behavior of any strategy without explicit circuit breakers. The market changes; the strategy does not know. Losses accumulate. The trader watches, waits, and tells themselves it will come back — because there is no automatic mechanism forcing a stop and a review.
Drawdown limits are that mechanism. Building them is unglamorous. No backtest improves when you add a kill switch. But the function of a kill switch is not to improve returns — it is to stop a strategy from destroying capital after the conditions that generated those returns have changed.
Trade-Level Stops vs Portfolio-Level Circuit Breakers
These are two different problems requiring two different solutions, and conflating them is one of the more common gaps in retail algo infrastructure.
A trade-level stop answers the question: how much can this single position lose before it is closed? It is per-position max loss — a stop-loss on the individual trade. If you are long 100 shares of something and you are willing to lose $200 on that position, that is your trade-level stop. It belongs in the order logic.
A portfolio-level circuit breaker answers a different question: how much can the entire account lose today, or this week, before all trading halts regardless of individual position state? It does not care which trade caused the loss. It monitors total account drawdown and halts the system when a threshold is breached.
Both are necessary. Trade-level stops limit the damage from any single bad entry. Portfolio-level circuit breakers limit the damage from a sequence of bad trades — which is the actual failure mode when a strategy stops working. A strategy that enters drawdown due to regime change will generate losing trade after losing trade. Trade-level stops will close each one at its maximum loss. Without a portfolio circuit breaker, the strategy opens the next trade immediately after each stop-out. The losses stack.
Consider size-appropriate numbers to understand the interaction. If your trade-level stop is 0.5% per trade and your strategy fires 8 trades on a bad day, trade-level stops alone allow a 4% single-day portfolio loss before you even notice. A 2% daily circuit breaker halts the system after the fourth trade. The difference is not hypothetical — it is the difference between a recoverable drawdown and a forced account review at the wrong time.
What a Portfolio Circuit Breaker Does
The mechanics are straightforward: the system tracks cumulative realized losses against two rolling thresholds — a daily max and a weekly max. When either threshold is breached, the system sets a halted flag and refuses to open new positions. Existing positions may be managed per their own exit logic, or you can wire the circuit breaker to close all open positions as well, depending on strategy type.
Three points determine whether a circuit breaker is actually useful:
It halts unconditionally. A circuit breaker that generates an alert but does not prevent order submission is a notification, not a circuit breaker. The halt must be enforced in code before any new order is sent.
It requires human action to resume. Not a timer. Not an automatic reset at midnight. A manual_resume() call that requires a deliberate human action — keyboard, CLI command, explicit function call. This is the mechanism that forces review before trading resumes. Automatic resets defeat the purpose.
It does not reset the weekly counter on daily reset. Daily loss resets at the end of each trading day. Weekly loss accumulates through the week. Both must breach their respective thresholds to halt from that dimension, but the weekly counter does not zero out each morning. A strategy that loses 1.5% every day on a bad week should not escape the weekly circuit breaker because no single day crossed the daily limit.
Setting the Thresholds
Thresholds calibrated from the backtest — specifically from the worst drawdown the strategy ever produced on out-of-sample data.
The logic is: if the strategy's worst historical out-of-sample drawdown was 12%, then a live drawdown of 8% already exceeds what the strategy ever produced on legitimate data. At 8%, you are no longer inside the strategy's known statistical behavior. The circuit breaker should trigger before you exit the envelope, not after.
A workable starting formula:
- Daily circuit breaker: 40–50% of the average daily loss in the worst backtest week
- Weekly circuit breaker: 65–70% of the worst single-week drawdown in the backtest
Setting the circuit breaker tighter than the backtest worst case is not pessimism — it is the correct calibration. The backtest worst case is the floor of known behavior. Any drawdown worse than that has no precedent in the data you used to validate the strategy. You cannot tell the difference between a temporary rough patch and a fundamentally broken strategy at that point. You should not be trading at that point.
For strategies in the 1–3% annual Sharpe range with controlled volatility, daily limits of 1.5–2% and weekly limits of 4–5% are common starting points. Adjust proportionally based on strategy volatility. Higher-frequency strategies with tighter per-trade variance can use tighter thresholds.
Position sizing feeds directly into this calibration. If you are sizing positions such that a full day of stop-outs would cost 4%, a 2% daily circuit breaker means you stop trading after losing approximately half of that. The circuit breaker threshold and the position sizing must be set together. See position sizing for algo traders for the full treatment of how drawdown tolerance constrains position size.
The Psychology Trap
The kill switch has a specific failure mode that is not technical. It is human.
When a strategy enters drawdown — the exact conditions where a circuit breaker should fire — traders disable it. The reasoning is reliable: the strategy worked before; this is a rough patch; disabling the stop now locks in the loss; it will come back. This reasoning is seductive, often sounds analytical, and is systematically wrong.
The mechanism behind it is sunk cost combined with conviction. The trader built the strategy, trusts the backtest, and has emotional ownership of the edge thesis. Accepting that the strategy has crossed its validity boundary requires accepting that the edge may no longer exist — which means the work spent building and validating it was wasted. This is cognitively uncomfortable enough that traders prefer to reinterpret the current drawdown as noise rather than signal.
The result: the circuit breaker gets disabled at the precise moment its function is to trigger. The strategy continues trading in conditions where its edge may not exist. Losses compound.
There is one structural protection against this: make the circuit breaker hard to disable. Not difficult in a technical sense — a single CLI command should always be enough to resume. Hard in a social or process sense:
Write the resume condition before going live. Define in writing, before you deploy, the specific conditions under which you will manually resume after a circuit breaker fires. "After the circuit breaker fires, I will halt for 24 hours, review the last 20 trades against the edge thesis, and resume only if no structural breakdown is apparent." This rule cannot be changed during drawdown. If you change it during drawdown, you are in the psychology trap.
Separate the resume key from the trading machine. If manual resume requires sending a command from a different machine, logging into a different interface, or calling a teammate — that friction is a feature. The harder the override, the less likely the emotional state of drawdown produces it.
Log every resume. Timestamp, reason, account state at resume. The log exists not for compliance but for your own post-mortem. When you review a bad period six months later, you will have a record of every time the circuit breaker fired and whether it was overridden.
Python Implementation
This implementation covers both a trade-level stop and a portfolio-level circuit breaker as a single composable class.
class CircuitBreaker:
def __init__(self, daily_limit: float = 0.02, weekly_limit: float = 0.05):
self.daily_limit = daily_limit
self.weekly_limit = weekly_limit
self.daily_loss = 0.0
self.weekly_loss = 0.0
self.halted = False
def record_trade_pnl(self, pnl_pct: float) -> bool:
if self.halted:
return False
self.daily_loss += min(0, pnl_pct)
self.weekly_loss += min(0, pnl_pct)
if abs(self.daily_loss) >= self.daily_limit or abs(self.weekly_loss) >= self.weekly_limit:
self.halted = True
return False
return True
def reset_daily(self):
self.daily_loss = 0.0
def manual_resume(self): # requires explicit human action
self.halted = False
self.weekly_loss = 0.0
record_trade_pnl returns True if trading should continue and False if the system should halt. The return value is the mechanism — your order submission logic should check it before sending any order.
pnl_pct is a signed float: positive values are ignored by min(0, pnl_pct) because the circuit breaker only tracks losses. Wins do not reduce the loss counter. This is intentional: a winning trade after a string of losers does not prove the strategy has recovered — it proves one trade went the right way.
reset_daily clears the daily counter at the end of each trading session. It does not touch the weekly counter. If you call reset_daily at market close every day, the weekly counter accumulates correctly through the week.
manual_resume also resets the weekly counter, because a manual review and resume decision implies the trader has determined that the strategy is operating correctly. If the strategy is not operating correctly, manual_resume should not be called regardless of the counter value.
For production use: extend this with logging, alert dispatch (email, Telegram, SMS), and a can_trade() method that checks both the halted flag and any open-position count limits. Wire record_trade_pnl to your post-fill callback so that every completed trade is immediately evaluated.
Trade-level stop logic sits outside this class — in the entry logic itself, as a stop-loss order sent at position open. The circuit breaker receives the realized PnL after that stop fires, not before.
The Oyamori Approach
Oyamori treats circuit breakers as first-class infrastructure, not optional add-ons. Every strategy deployed through the platform includes configurable daily and weekly drawdown limits. These are set during strategy configuration and cannot be changed mid-session — any modification requires going through the review workflow first.
The resume gate requires explicit confirmation. When a circuit breaker fires, the strategy enters a halted state that shows in the dashboard. Resuming requires a deliberate action through the CLI or dashboard interface, with a timestamp logged. The system does not restart trading automatically under any condition.
Thresholds are calibrated against the strategy's out-of-sample performance data during onboarding. The platform surfaces the worst observed drawdown period so traders can set circuit breakers at appropriate fractions of that maximum rather than guessing.