When my bot crashes, I find out in seconds. The bugs that cost me the most time, and some money, never crashed at all. Nothing errored. A number just stopped arriving, or a function said it had done something it hadn't.
"Quiet failure is the house failure mode" is now the first line in the notes file my coding assistant reads before touching the bot. Here are the seven that earned it that place.
1. A strategy that could never take a trade
I ported an EMA trend strategy from an open-source collection. It ran every day for weeks, alongside a hundred others, and took zero trades. Not a bad strategy — an impossible one.
The warmup guard said "don't trade until you have 52 bars of history". The history buffer was capped at 50 bars. The condition could never become true.
Reviewing it turned up more: the port kept 3 of the original's 14 entry conditions and used different moving-average periods on a different timeframe. It was not the strategy I thought I was testing.
What catches it: a daily check that every enabled strategy has either taken a trade or at least evaluated a signal recently. A strategy with zero activity for a week is a bug until proven otherwise.
2. A signal that fired and an order that was never placed
On 18 August one strategy produced a correct SENSEX signal. No order reached the broker.
The strategy called engine.enter(). The engine's method is called buy(). Python raised an AttributeError, a broad try/except caught it, and the handler logged it at debug level — which nobody reads during market hours.
What catches it: never swallow an exception on an order path. Log order failures at warning or above, and after each session compare the bot's list of intended orders with the broker's order book.
3. A data feed that recorded zero rows for a whole session
I record option-chain data around the close for both NIFTY and SENSEX. On 19 August SENSEX recorded 379 rows. NIFTY recorded zero. No exception, no log line.
The code built option symbols by hand. Zerodha names weekly expiries like NIFTY2682524050CE and monthly ones like NIFTY26AUG24050CE. That week the nearest expiry was a monthly, so every symbol pointed at an instrument that didn't exist, every quote came back empty, and the recorder wrote nothing. It would have repeated every month, silently, forever.
What catches it: never construct broker symbols; look them up from the cached instrument dump. And a coverage check: every expected source must write a non-zero row count for the last session.
4. A setting that said "done" and changed nothing
The dashboard lets me change a strategy's quantity. For one whole family of strategies, the call returned success, the dashboard showed the new number, and every new trade kept using the old quantity.
Those strategies read their size once at startup into a private field. The setter updated a different attribute. A second bug in the same path showed the quantity as zero in the dashboard because the config key used a different suffix.
What catches it: a test that checks the effect, not the return value — change the setting, then assert the next order uses it.
5. A zero that moved the day's low by 24,538 points
Before the first tick arrives, the bot's current price defaults to 0.0. One code path updated the running day-low from that value. The day's low became 0, the day's range became about 24,538 points against a normal budget of around 83, and every strategy's direction score flipped.
This one happened after the close, so no trades were affected that day. That distinction matters: "the bug exists" and "the bug bit today" decide whether you fix it tonight or this week.
What catches it: guard running minimums and maximums against zero and None before updating them.
6. Replayed trades written into the live trade log
My replay engine re-runs past sessions through the live strategy code. Some strategies write their own trades to the database. During replay, they wrote replayed trades into the live trade log, mixing history that never happened with history that did.
What catches it: the replay worker now replaces the database write functions with no-ops before any strategy is imported, and reports how many writes it blocked.
7. A field that was wrong for 47.5% of trades
Every trade records the nearest key price level and whether the trade was heading toward it. The original calculation measured distance to the nearest level in either direction. For 1,790 of 3,768 trades, the "nearest level" was behind the entry, not ahead of it.
Nothing traded on this field directly, but I had been reading analysis built on it. I replaced it with a direction-aware distance and backfilled the history.
What catches it: spot-check derived fields by hand against a chart before trusting any analysis built on them.
The pattern
Every one of these passed every test I had, because my tests asked "did it run?" and not "did it produce what it should?". The fix is the same each time:
- Check outcomes, not return values.
- Count things. Trades per strategy, rows per feed, orders per signal. A zero is an alarm.
- Never log an order-path failure below warning.
- Look things up; don't construct them.
- Guard defaults. A
0.0placeholder is a real number to every piece of code downstream.
I would rather the bot crash loudly at 9:16 than run smoothly all day doing nothing.