The failure
On 2026-05-07 I deployed $200 of real capital to a Kraken account from a live-trading engine I'd been paper-testing for months. On 2026-06-16 a routine audit surfaced the fact that the account had bled to $146.31 — down 26.85% over 19 trades — and no alert had ever fired.
Six weeks of silent bleeding invisible to every automated check.
This post is the honest engineering breakdown of exactly why, and what changed. If you run any live trading infrastructure, the pattern here is embarrassingly common and the fix is boring: watchdogs must be pointed at the real capital, not paper.
The pattern
The live-trading engine wrote its own peak/drawdown counters to live-trading-state.json:
{
"balance": -53.69,
"peak": 0,
"drawdownPct": 0,
"openPositions": [],
"closedTrades": [ ... 19 trade records ... ]
}
Two things went wrong.
The peak field stayed at 0 forever. The code that was supposed to bump peak when
balance > peak never ran. That meant drawdownPct (computed as (peak - balance) / peak)
was permanently 0, because peak = 0. A negative balance with a zero drawdown reads as
"no problem" to any alerting rule keyed on drawdown.
The portfolio watchdog was pointed at the wrong file. I had built a nice unified
watchdog for the paper components — qb-state.json, forge-qb-state.json, and so on.
The aggregator in src/worker/portfolio/aggregator.js iterated those state files and
raised alerts on drawdown, low win rate, and stale-activity. It never read
live-trading-state.json. So even if the peak math had worked, no alerting layer
would have consumed it.
Two independent failures. Both invisible in isolation. Compounding into a null result: the loss was written to disk in perfect detail, and nothing looked at it.
What the closed-trades file contained the whole time
19 trades | 6 wins | 31.6% WR
sum(pnl_usd) = -$53.69
Trade timestamps span 2026-05-12 through 2026-05-27
The information required to fire an alert was on disk since day one. The aggregator just wasn't asked to look.
The fix (2026-06-16)
Three separate changes had to land together:
Wire live-trading into the aggregator. Added a
readLiveTrading()component reader alongsidereadBaselineQb,readForgeQb,readGridBot,readPersonalQb. Same output schema, same watchdog rules.Recompute peak/drawdown from
closedTradescumsum instead of trusting the in-file counter. Even if the writer is broken, the trade record is authoritative — so derive everything from that at aggregator time.Add a critical alert for the specific failure mode. New watchdog rule:
if peak_balance_usd === 0 && current_balance_usd !== starting_balance_usd: fire critical. If the peak counter is broken, ALARM. Don't wait for the secondary metrics that depend on it.
Plus one behavioral change: the live engine was paused via
PAUSE_LIVE_TRADING file the same day. No real capital re-deploys until:
- The watchdog is verified end-to-end via a synthetic loss test
- 90+ days of clean paper forward-test data on the strategy that would trade
- The peak math is verified against a synthetic scenario
The -26.85% is the whole data point. Adding more real capital to test whether
it was luck is exactly the trap the honesty positioning exists to prevent.
The generalization
This wasn't a "we forgot to add monitoring" story. There WAS monitoring. It just watched the wrong file. The pattern I keep running into with my own infrastructure:
Monitoring that was added when a component was paper-only doesn't automatically extend when the component goes live. The paper version and the live version write to different files with slightly different schemas. The watchdog was configured against the paper file. Nobody updated it when the live file appeared.
If you're running any home-grown trading infrastructure, three checks worth doing right now:
- Grep your alerting layer for the paths it actually reads. Confirm the live state file is one of them.
- Read the state file yourself and compute peak/drawdown from the trade log. Compare to what the state file says the peak is. If they differ, your writer is broken and your alerts are lying to you.
- Add a "detector of detector failure" rule. If
peak = 0andbalance != start, fire regardless of other metrics. The metrics that depend on a broken input will silently swallow the fault.
Why publish this
The dashboards that would have caught this were the ones I built. The alerts that didn't fire were the ones I wrote. The engine that lost money was my engine. Publishing the specific failure mode is what "we publish our losses" has to mean if the positioning is going to hold up to any real scrutiny.
The full disclosure lives on /proof under "Real capital lessons." The larger context is in the flagship post 93,265 crypto backtests, 1,676 passed — really ~35 parent lineages — this loss is one of the four caveats in the "What we don't fully trust" section.
If your live trading has been quiet for weeks, that might be great and it might be this. Worth a check.
Comments and pushback welcome at support@stratproof.com or @stratproof on X.