Daily loss stop and drawdown halt
The two circuit breakers of the risk engine, how they are measured, when they fire and clear, and why only a person can resume after a drawdown halt.
The risk engine has two circuit breakers. Both refuse new entries. Neither ever blocks an exit. Both are in RiskEngine::observe (crates/zunder-risk/src/engine.rs) and run today in Zunder’s testnet runner.
| Daily loss stop | Drawdown halt | |
|---|---|---|
| Measured from | equity at the start of the UTC day | the highest equity seen |
| Default | 6% | 25% |
| Fires when | (day start − equity) / day start ≥ 6% | (peak − equity) / peak ≥ 25% |
| State | halted_for_day | stopped |
| Clears | by itself, at the first observation of the next UTC day | only when a person resumes |
| Then | flatten, refuse entries | flatten, refuse entries |
Try it
Section titled “Try it”Move the losses and see which breaker fires. The real engine judges each position of the sliders.
The daily stop clears at the first observation of the next UTC day. The drawdown halt clears only when a person resumes (RiskEngine::resume_after_review); nothing does it automatically.
How the engine observes
Section titled “How the engine observes”Every few seconds, and always before sizing, the caller tells the engine the account’s equity. The engine then:
- Rolls the day forward if a new UTC day has begun. The new day starts from the last equity of the old day, so a gap at midnight counts as the new day’s loss. A late observation from an earlier day never clears today’s halt.
- Raises the peak if equity is higher.
- Checks the drawdown first, then the day’s loss.
When the state is no longer active, the caller has to flatten. Zunder’s runner closes every position and cancels every order that could open one. The halt is written to the journal before anything is closed.
Example: the daily loss stop
Section titled “Example: the daily loss stop”Equity at 00:00 UTC: 2,000. Default 6%, so the stop fires at a loss of 120.
| Time (UTC) | Equity | Day’s loss | State |
|---|---|---|---|
| 09:00 | 1,950 | 2.5% | active |
| 13:00 | 1,890 | 5.5% | active |
| 15:30 | 1,880 | 6.0% | halted_for_day |
| 18:00 | 1,910 | 4.5% | still halted |
| next day 00:00 | 1,910 | new day starts at 1,910 | active |
A recovery during the day does not clear the halt. The next day starts from 1,910, not from 2,000.
Example: the drawdown halt
Section titled “Example: the drawdown halt”The peak was 2,600. Default 25%, so the halt fires at 1,950.
(2,600 − 1,950) / 2,600 = 650 / 2,600 = 25% → stoppedFrom here nothing opens, today or any later day, until a person resumes.
Only a person resumes
Section titled “Only a person resumes”RiskEngine::resume_after_review is the only way out of a drawdown halt. It does nothing unless the engine is stopped. It restarts the peak from the current equity, so the next halt is measured from there.
Nothing in Zunder calls it automatically. That is a hard rule of the project (CLAUDE.md, hard rule 2): “never call it automatically”. In Zunder’s runner, a person runs zunder-runner journal-resume --note "..." with the runner stopped, and the note goes into the journal.
Example. The halt fired at 1,950. You look at the trades, find a bug in the bot, fix it, and resume at 1,940. The new peak is 1,940. The next halt fires at 1,940 × 0.75 = 1,455.
Restarts do not clear anything
Section titled “Restarts do not clear anything”The engine’s state is kept in the journal. A restart restores it. A test (400 random paths with random restarts) shows that a restored engine is never less strict than one that kept running (docs/decisions.md, 5 Oct 2026, “The risk engine persists and tracks positions”).
Limits
Section titled “Limits”- They measure equity, not intentions. A loss inside an open position counts as soon as equity shows it. Zunder’s runner reads equity from the venue on every poll.
- They act after the fact. The stop fires at the first observation at or past the threshold. A fast move between two observations can overshoot it. The stops resting on the venue are what limits each position in between.
- Withdrawals count as losses. Taking money off the account lowers equity. Stop the bot first, or expect a halt.
This page as plain Markdown, for people and LLMs: /docs/concepts/circuit-breakers.md