A paper-trading bot, a 0% win rate, and the two bugs behind it.
Nov 10, 2026 · Voytek

On November 5, my dashboard told me Agent 7 had a 0% win rate and a −6.77% return after 608 trades.
Agent 7 is the paper trader in ARB NETWORK, my 7-agent DeFi signal system on a Raspberry Pi 5. No real money moves. But zero wins in 608 trades is the kind of number that makes you stare at the ceiling, so I went looking for what I had broken.
The answer wasn’t what I expected. And the corrected number I ended up with, I trust even less.
The Pi fell over first
The hunt started because the whole system had just crashed. An Ubuntu upgrade to 26.04.1 replaced the system Python with 3.14.4, and my services run from a virtual environment (.venv) built on the old one. Everything that needed Flask died with a ModuleNotFoundError.
Agents 1, 2, 3, 5 and the dashboard crash-looped for about 80 minutes. The restart counter hit roughly 463. Agents 4 and 7 stayed up the whole time.
The fix was to rebuild the .venv, and that surfaced a second problem. My requirements.txt had a Windows-only pin, pywin32==311, sitting in a file meant for a Linux box. I installed from a Linux-filtered copy, then regenerated requirements.txt with pip freeze on the Pi. All seven agents came back, and no data was lost.
The numbers that didn’t add up
With everything running again, I stopped trusting the dashboard and went to the source: execution_log.json, the file Agent 7 actually writes. The log said total P&L was +$192.30. The dashboard said I was down 6.77%. Those two can’t both be right.
I ran a short script that groups every closed trade by outcome and exit reason. Two groups came back, and they add up exactly to the log’s total. Every number in this post comes from the November 5 log, and Agent 7 has kept trading since.
PARTIAL (exit reason GAP_PERSISTS_PARTIAL): 431 trades, +$595.90
LOSS (exit reason NO_CURRENT_DATA): 177 trades, −$403.60
Total: 608 trades, +$192.30
The log agreed with itself. The dashboard was the odd one out.
Two bugs, both mine
Bug one: ROI measured cash, not equity. The dashboard computes (bankroll − 1000) / 1000. But bankroll_usd is cash only, because the executor subtracts each position’s size the moment it opens. Five open positions held $260.01. Cash of $932.29 plus $260.01 in open positions is $1,192.30, which is exactly $1,000 plus the $192.30 of closed profit. Money that was working was being counted as money that was gone.
Bug two: the win counter could never move. Agent 7 only ever writes two outcomes to the log, PARTIAL and LOSS. The code increments winning_trades only for an outcome called WIN, which it never writes. So wins sat at zero forever, and the win rate was wins divided by total trades. Zero divided by 608 is a very confident-looking zero.
Where I was wrong
I had two theories about the rest, and both were wrong.
Theory one: the reboot caused the losses. Two of the losses I looked at were stamped at the same second, right after the restart, while the feeder agents were down. That looked like a smoking gun. It wasn’t. All 177 losses share one exit reason, NO_CURRENT_DATA, and they are spread across daily batches going back to June 25, about five a day. That is 29% of all trades, every day, not one bad night.
Theory two: a broken cooldown. Agent 7 reopened three positions in the same tokens in the same minute it closed them. I assumed the cooldown was failing. It isn’t. The cooldown is 4 hours, set when a position opens, and positions are held for 24 hours. By the time a trade closes, the cooldown expired long ago, so re-entry right after a close is allowed by design. If I want a gap after exit, the cooldown has to be set at close. That’s a design decision, not a bug.
Why the corrected number is still soft
Fixing the arithmetic gives me about +19.23% at cost, on paper. I don’t trust that either.
Start with the 177 losses. Two I pulled were −$2.73 on a $54.56 position and −$2.87 on $57.43. Both are exactly 5% of position size. In the code, when a YIELD signal’s token is missing from the signal bus at exit time, the trade is graded a LOSS at a flat −5%. That is a penalty for missing data, not a price move. Across 177 trades it averages about −$2.28 each.
There’s a bigger problem underneath. All 177 losses are YIELD signals, and every ARB trade closed PARTIAL. Each strategy produces exactly one outcome, every time. That isn’t a track record, it’s a grading rule. For scale, a 200% APY earns about 0.55% a day, so a flat 5% penalty swamps any real signal. Agent 7 isn’t measuring anything real yet.
Now the winning side. The 431 PARTIAL trades add up to +$595.90, but that profit is modeled from each signal’s expected net profit and how long the gap persisted. It ignores slippage and gas, so it is probably optimistic.
So one half of my P&L is a flat penalty and the other half is a model’s guess. If I counted every PARTIAL as a win, I would get a win rate of about 71%, assuming every one of them is positive, which I haven’t verified. That is exactly the kind of flattering number I’ve learned to distrust.
What I shipped, and what is still broken
The infrastructure side is fixed. The accuracy side is diagnosed but not yet fixed.
Shipped:
Rebuilt the .venv and regenerated requirements.txt from the Pi itself.
Nightly backups run as a cron job at 3:15 AM, copying the scorecard and the execution log into a backup folder. I checked, and five nights in a row have run.
Discord crash alerts are in and tested. A small script posts to a private Discord channel whenever a service fails, using systemd’s OnFailure hook. The tradeoff: a service that fails five times in five minutes now stays down until I restart it.
Still open:
The dashboard still computes ROI from cash only. It needs to use cash plus open positions.
PARTIAL trades still count as neither wins nor losses.
Missing exit data is still graded as a flat −5% loss. These trades should be marked unknown and left out of the stats, and ideally positions shouldn’t be force-closed when data is stale.
Some rows show expires_at equal to entry_at, but the code sets it to entry plus 24 hours. I don’t know yet whether that’s an older version of the file or something in the data.
The alerts don’t cover a service that is running but stuck, or the whole Pi going down.
The backups all sit on the same SSD as the originals, so there’s no off-device copy yet.
I’m publishing before the fixes are in on purpose. Until they ship, the honest thing is to say that the dashboard is wrong and exactly how.
What I’m taking from this
A dashboard is a claim, not a fact.
I’ve been here before. My Agent 3 gap calculator once overstated gaps by 38x because it compared prices that weren’t normalized to USD. Same shape: a number on a screen, plausible enough to believe, wrong underneath. That time the number was too exciting. This time it was too gloomy. Either way, the screen sounded confident and the screen was wrong.
Three rules I’m keeping:
Keep a second source of truth the dashboard can’t touch. For Agent 7 it’s the raw log.
Reconcile on a schedule. Cash plus open positions should equal the starting balance plus closed P&L. That’s one line of math, and it caught the whole thing.
A better-looking number is not a verified number. When a fix makes your results look nicer, get more suspicious, not less.
In live events the rule was simple: it had to work, no time to fix it. A readout you can’t trust breaks that rule quietly, because everything looks fine until it isn’t.
If you’re running a bot, check one thing today: add your cash and your open positions, then compare the total to your starting balance plus closed profit. Tell me what you find, especially if it doesn’t match.
Whatever you’re building today, keep going. 🚀
— Voytek
