Engineering

The Bot That Had Never Made a Dollar

Three hundred and forty tests. Ninety-five percent coverage. Five docs, a runbook, a watchdog, and a launchd plist. In three weeks it had never placed a real trade. This is what we learned when we finally pulled the data instead of writing another fix.

April 7, 2026
9 min read
#engineering#trading bots#testing
The Bot That Had Never Made a Dollar⊕ zoom
Share

Hermes had the cleanest test suite in the ecosystem.

Three hundred and forty tests. Ninety-five percent line coverage. Five documentation files. A runbook that walked a human through cold-start recovery. A watchdog that restarted the process on stale logs. A launchd plist with KeepAlive=true. Two passing health checks. A CI pipeline that went green on every PR.

And in three weeks on live infrastructure, Hermes had never placed a single real trade.

The Metric Nobody Was Watching

We had metrics for everything. Test pass rate. Coverage percentage. Process uptime. Log freshness. CI duration. The dashboard was a wall of green.

There was one metric we weren't tracking: last real trade timestamp.

Because we weren't tracking it, we didn't notice it was stuck at null. The bot had been running for weeks. It had ingested thousands of markets. It had computed scores on every candidate. And every single score had fallen short of the threshold, silently, without an alert, without a log line that said "hey, nothing cleared the bar today." The system was behaving exactly the way a healthy system with no tradable opportunities should behave. Except it wasn't healthy. It was broken in a way no test could see.

This is the gap between "tests pass" and "system works." Tests measure code integrity. They do not measure signal quality. A bot can have 95% coverage and 0% operational value, and the two numbers won't talk to each other unless you force them to.

The Moment That Changed the Session

I was two paragraphs into writing the spec for a scoring-engine refactor when Knox stopped me.

"Pull the 64 signals first."

I almost pushed back. The refactor was clearly the right direction. Why spend twenty minutes on a database query when I could spend those twenty minutes writing the fix? The answer, of course, is that the fix I was about to write was based on an assumption I had not verified. I thought I knew what was wrong. I was guessing. And when you are guessing about a scoring system, the distribution of the stored component values will tell you in thirty seconds whether you are right or completely wrong.

So I pulled the 64 most recent signals. Four component scores each: Grok, Perplexity, Calibrator, News. I dumped them into a SQL query. And the answer hit me in one row of output.

Calibrator zero rate
96.2%
on 64 recent signals

The calibrator — a 25-point load-bearing component with a hard gate at 70 — returned exactly 0 on 96.2% of all signals. Not "returned a low score." Returned zero. On sixty-one out of sixty-four signals, a quarter of the scoring rubric was dead weight. The threshold was effectively 70 out of 75 instead of 70 out of 100. Of course nothing was clearing it.

The refactor spec I had been writing would have been the wrong fix. I was about to rebalance weights based on a theory about how the engine should behave, without ever checking how it actually behaved. The twenty minutes I resented spending on the data query would have saved an afternoon of building the wrong solution. That pause — pull the data before designing the fix — was the hinge of the session.

Why the Calibrator Was Silent

The calibrator was not broken. That's the subtle part. It was not throwing exceptions. It was not logging warnings. It was not emitting alerts. Its unit tests passed. Its integration tests passed. Its code coverage was fine.

It was silently returning zero because its data sources — Metaculus and Manifold — don't carry matching political prediction questions for most of the markets Hermes was scoring. The component correctly returned 0 when it couldn't find a match, which was semantically right and operationally catastrophic. A dead component is indistinguishable from a healthy one without fire-rate tracking. The only signal that something was wrong was the absence of trades — and absence is the hardest thing for a test suite to detect.

The InDecision Framework calls this the silent-failure mode: a component whose default output is zero, whose zero is semantically valid, whose lack of output triggers no alert, and whose contribution to the final score is load-bearing. Every condition has to be true at once. When they all are, the system will run clean and produce nothing for as long as you let it.

WARNING

A scoring component whose 24-hour fire rate drops below 20% should auto-alert. Silence is not health. If your calibrator is silently returning zero, your threshold is effectively your ceiling minus that calibrator's max contribution — and nothing will ever clear it.

The Fix Was Arithmetic

Here is the part that still feels too easy.

Once I knew the distribution, the fix was a SQL query, not a code change. The component values were already stored in the database. Every score was deterministic. Rebalancing the weights did not require re-running any API calls. I rescaled the component contributions arithmetically from stored values:

Grok:       30 → 35
Perplexity: 30 → 50
Calibrator: 25 → 20
News:       15 → 15   (unchanged)

Threshold stayed at 70. I ran the rebalance simulation against the 64 stored signals. Zero signals had cleared the old threshold. Eight signals cleared the new one. The ranking changed. The top candidates made sense. I wrote the code, CodeRabbit reviewed it, CI went green, and PR #28 merged.

Two hours from "pull the data" to "bot clearing threshold on real markets." An afternoon of building the wrong thing, saved by twenty minutes of SQL.

What 340 Tests Cannot Tell You

The lesson I carried out of this is not "write fewer tests." Hermes still has three hundred and forty tests and I would not delete one of them. The lesson is that test coverage and operational health are orthogonal metrics. You can have either without the other. You need both, and they must be tracked separately, and they must trigger different alerts when they diverge.

Every scoring system in our ecosystem — Hermes, Foresight, Shiva, Leverage, the Tesseract Intelligence signal engine — now has the same set of questions applied to it:

  1. What is the fire rate of every component? If a component drops below 20% fire rate over 24 hours, it alerts.
  2. What is the last real output? Last trade, last signal, last published score — tracked as a visible metric with a staleness threshold.
  3. Does the distribution of stored component values match what we assumed? If not, the assumption is wrong, not the code.

Hermes had the cleanest test suite in the ecosystem. That is still true. What it did not have was a metric that could distinguish "no opportunities exist" from "a dead component has pinned the threshold to the ceiling." Both states look identical from the outside. Only fire-rate tracking can tell them apart.

Closing Note

A perfect test suite is a necessary condition for a working production system. It is not a sufficient one. A bot that has never placed a real trade is not a working bot, regardless of its CI badge.

The finish line is not green checks. The finish line is a running system that produces output you can act on. Every bot in the ecosystem now tracks its "last real trade" timestamp alongside its test coverage, and any bot where the timestamp is older than its runbook's expected cadence gets flagged before the next session starts.

Hermes placed its first real trade the same day I pulled the 64 signals. The bot that had never made a dollar was three weeks old, 340 tests deep, and one SQL query away from going live.

Go deeper in the AcademyApplied

Testing discipline and quality gates are covered in the Quality Engineering track. 7 lessons.

Start the Quality Engineering track →

Explore the Invictus Labs Ecosystem

// Join the Network

Follow the Signal

If this was useful, follow along. Daily intelligence across AI, crypto, and strategy — before the mainstream catches on.

No spam. Unsubscribe anytime.

Share
// More SignalsAll Posts →