
ENGINEERING
SEPTEMBER 1, 2026
302 tests found six bugs in my trading bot, and no edge
6 min read
Four of the six were the same shape and none was visible by reading the code. Then the suite went green and the bots still had nothing.
I run an algorithmic trading system. Multi-asset, several strategies, live for
months. Until recently it had **zero tests**.
It now has 302. They found six real bugs, and they did not make the system
profitable — because those are two entirely different claims, and keeping them
apart is most of what this post is about.
Everything below is **paper trading**. Simulated money, no live capital. That
matters for the second half.
## Why does money code end up untested?
Not laziness. Three things, and they compound.
**Testing it has side effects.** The first thing I discovered was that writing
these tests fired seven real circuit-breaker alerts at my own phone — the halt
function sends a Telegram message. Another module appends to a live operational
log. Another writes rows into the database a running system reads. A test suite
that pages you, pollutes your logs and mutates production is a suite you run
once and never again.
**Nothing forces the issue.** There is no compiler error for "this strategy is
subtly wrong." The system ran for months, opened positions, closed them, and
reported numbers. Nothing about that experience says *write tests*.
**And the numbers looked plausible.** This is the trap. A trading system that is
quietly broken does not throw. It produces a P&L, and the P&L looks like a P&L.
## The six bugs
Four of the six were **the same shape**: logic written for long positions,
applied to shorts.
**Every short was rejected before it could open.** The risk manager validated
stops with one check — a stop above the entry price is invalid. That is correct
for a long and exactly backwards for a short, whose stop belongs above entry.
Every short signal was refused as "invalid stop loss." The strategies had been
generating them for months.
**The exit path could not tell a short from a long.** The position dictionary
handed to the exit check simply had no `direction` key, so every position was
monitored as a long. Fixing the first bug alone would have opened shorts that
closed themselves on the next tick, paying a round trip in fees each time. They
had to ship together.
**Open shorts were marked backwards.** Portfolio value used the long formula for
every position, so a short winning $200 showed the portfolio $200 *down*. That
number feeds the circuit breaker and position sizing — a profitable short could
trip a drawdown halt, and a losing one could hide a real one.
**The correlation gate blocked hedges.** It compared the absolute value of price
correlation with no knowledge of direction, so two longs at −0.9 — a textbook
hedge — were refused as "too correlated." Concentration is a property of
*exposure*, not price.
The other two were different but rhyme with the first four: a drawdown halt that
said "STOP until manual review" and silently cleared on restart, because the
flag was written to disk and never read back. And expired options booked at zero
P&L regardless of premium, which recorded **$1,301** of losses as break-even and
turned a trade that lost $872 into one that appeared to make $430.
## Why reading the code would never have found them
I want to be precise about this, because "write tests" is cheap advice.
Every one of these bugs is *invisible at the call site*. The stop check reads as
obviously correct until you hold a short in your head. The missing dictionary key
is an absence — there is nothing on screen to notice. The mark-to-market line is
a plausible multiplication.
What found them was not scrutiny. It was **asserting things that have to be
true**:
- Marking a position to a price, then closing at that price, must give nearly
the same portfolio value.
- A short whose price falls must be worth more, not less.
- Risk taken must never exceed the configured cap.
That mark-and-close assertion is what caught the inverted sign. It is three
lines, it encodes no cleverness, and before the fix the two numbers differed by
twice the P&L in opposite directions.
## The part that matters more
The suite went green. All six bugs fixed, regression tests pinning each one.
**And not one bot has a demonstrated edge.**
On a deflated-Sharpe test across the fleet — which adjusts for how many
strategies you tried before finding one that looked good — *not one bot clears
PSR(0) ≥ 0.95*. Every record is statistically consistent with luck once the
trial count is accounted for.
That is not a disappointing result. It is the correct one, arrived at honestly,
and it is what most people running a bot fleet do not check. If you try twenty
strategies and keep the two that look good, you have not found an edge. You have
found the two that got lucky, and the maths for that is well understood and
routinely ignored.
So the honest status of my system is two separate sentences:
> The code is tested and does what it is supposed to. No strategy has a proven
> edge.
Conflating those is the easiest way to lie to yourself. **A green test suite is
a statement about the code, nothing more.** It says the machinery works. It says
nothing whatsoever about whether the idea does.
## What I would tell anyone testing a system that touches money
**Isolate first, or you will not run them twice.** Before the first assertion,
stub the alerting, the database and the operational logs. My suite runs in three
seconds and touches nothing — that property is why it still gets run.
**Assert invariants, not examples.** Examples pin today's behaviour; invariants
catch the case you did not imagine. The interesting bugs live at the boundary
where two correct-looking rules interact.
**Write the tests before you trust the numbers.** I had months of P&L from a
system with a sign error in it. The reports were confident and wrong, and no
amount of staring at the dashboard would have said so.
**Then test the edge separately, and harder.** Correctness is the cheap half.
---
If you have a system where the failures are quiet — money moving, reports that
look plausible, nothing throwing — the useful question is not "is the code
right" but "what would it look like if it weren't."
**[Run the free website check](/website-audit)** — unrelated to trading, and a
fair sample of the same instinct: a public endpoint that drives headless Chromium
at whatever URL a stranger supplies, with the boundary and the tests that implies.
If you want another pair of eyes on the parts of your system that fail without
telling you, that is the work I do. [Tell me what you are running](/contact) —
and if it turns out your reports are fine, I will say so.
Read More
MORE ARTICLES

Engineering
Engineering
Two ECS failures that produce no useful error
A readiness probe blocked by the task's own IAM role, and readonlyRootFilesystem silently killing ECS Exec. Both look like broken infrastructure.

Engineering
Engineering
How do you know when your AI feature is wrong?
Most teams ship an AI feature and have no answer past spot-checking. What an evaluation harness actually contains, and the failure it usually misses.

Engineering
Engineering
Don't put a model where you need a reproducible answer
Three systems where I deliberately chose regex and a lookup table over an LLM, and the two questions that decide which one a problem needs.

Engineering
Engineering
CLAUDE.md as production infrastructure
What a coding agent's context file has to contain when the repos it touches are live, and the day I found the file preventing disasters had no backup.