I spent a few months building an automated trading system: five bots wired together in n8n, a couple of local LLMs making the calls, Trading 212 for execution, Discord for the play-by-play. Every morning my phone lit up with green ticks. Position Closed ✅. Rebalanced ✅. Trade executed ✅. Then I actually read the account. Demo account realised P&L: −£424. Live account balance: £4.83. No bot-initiated trade on it in months. One bot still trading — and it was selling a position at a loss every single morning to dump the cash into a tracker fund. The bots weren't losing money because the strategy was bad. They were losing money because almost none of them did what the Discord messages said they did, and nothing in the system was capable of noticing. This is the autopsy. Every bug below shares one signature, and it's the only lesson that matters: the system reported success at exactly the point it was failing. Not financial advice. This is a debugging story about automation, and the reason it's worth your time is that the failure mode isn't specific to trading — it's specific to any pipeline that reports its own results. Bug 1: the sell order that sold a string The swing-trading bot's "close position" step POSTed this as the order body: "=$json.ticker" If you've used n8n you already see it. That leading = marks an expression, but there are no {{ }} around the field — so instead of interpolating the ticker symbol, it sent the literal seven characters $json.ticker to the broker's API. Every sell was rejected. Not some. Every sell the bot ever attempted, for months. Here's the part that turns a bug into a disaster. The node was configured with onError: continueRegularOutput and alwaysOutputData: true. So when the broker rejected the order, the workflow carried on to the next step as if it had succeeded — and the next step posted to Discord: Position Closed ✅ I have a Discord channel full of hundreds of successful sales that never happened. The positions from April and May were still open in July. The "+£324 unrealised gain" I'd been feeling good about was just two stocks the bot had bought and was now structurally incapable of selling. Lesson: an error handler that swallows the error and continues is not error handling. It's a machine for manufacturing false confidence. Bug 2: the circuit breaker that computed whether to stop, and then didn't The same bot had a safety system I was quite proud of. Four rules: stop trading after a 15% drawdown, three losses in a row, six trades in a day, or a 5% daily loss. The node dutifully computed two variables — circuitBreakerTriggered and pauseReason — on every run. Nothing read them. No downstream node referenced either variable. The circuit breaker was a function that calculated the correct answer and then threw it in the bin. All four safety rules were unreachable code. I had built the smoke detector and never connected the battery, and because it lit up on the dashboard when it computed a trigger, I believed it was armed. Bug 3: the LLM that was fed its own source code The "autonomous trader" asked a local model (qwen2.5:7b) what to buy. When I finally logged the exact prompt going over the wire, the model wasn't receiving a prompt. It was receiving ~2.4 KB of copy-pasted JavaScript — the source of a Code node someone had pasted into the prompt field, ${cash} and ${ownedTickers} and all, as literal text. So the model never saw the research, never saw the portfolio, never saw how much cash was available. It was pattern-matching against raw JS and emitting plausible trades into the void. And the only guard rails were amount > 0 and amount <= cash — applied per trade, not in aggregate. Nothing stopped it proposing five trades that each spent 100% of the cash. The circuit breaker here read a file off an NFS mount with cat ... 2>/dev/null — so if the mount was down, the check returned empty and the breaker failed open. The one condition under which you most want a safety system to hold is the one under which it silently disabled itself. Bug 4: the "rebalancer" with no rebalancing in it This is the only bot that was still trading, and it's the one that did the measurable damage. Its name promised portfolio rebalancing — target allocations, drift thresholds, trim-the-winners logic. There were no target allocations. No drift thresholds. No rebalancing algorithm of any kind. What it actually did: Ask the LLM for suggested amounts. Discard the amounts, keep only their ratios. Pro-rate those ratios across 100% of free cash. Sell a whole position to raise that cash first. So every weekday at 07:30 — the cron said weekly; it was set to daily — it liquidated a holding and shovelled the proceeds into a single tracker fund. The realised losses on those forced sales, from the logs: −£7.70, −£1.87, −£114.82, −£35.26, −£6.84. And one step (Calculate Quantity) would throw after the sells had already executed, with no error handler on any of its 27 nodes — so on a bad run it would sell everything and then strand the cash, silently. Bug 5 and friends: the ones that never touched reality at all Two more bots rounded out the set: The daily briefing bot fetched account status from /equity/account/summary — not a real endpoint on the broker's API. So every "your portfolio is worth £X" message it ever sent read £0.00, and I'd stopped noticing. The market scanner filtered for cheap stocks assuming prices came back in pounds. The London exchange quotes in pence (GBX). Its price * 100 maths meant it only ever admitted sub-1p stocks, so its watchlist was permanently empty — and when the price feed (an unauthenticated Yahoo scrape, the actual oracle for three bots' order sizing) had an outage, a null fell through to the else branch and the bot cheerfully reported "market tide AGAINST you 🔴". An outage looked exactly like a bearish signal. Oh, and the workflow JSON had a live API key and a Discord webhook token sitting in it in plaintext. Which I found while writing this. Rotate your secrets. The one lesson Look at what these bugs have in common. It isn't the language, or n8n, or LLMs, or trading. It's this: Every single failure was silent, and every single one reported success. The rejected sell announced "Position Closed ✅." The disconnected circuit breaker lit up as if armed. The unfed model returned confident trades. The endpoint-that-doesn't-exist reported a tidy £0.00. The price-feed outage rendered as a market signal. I didn't lose £424 because I made bad trades. I lost it because I built a system that could not tell me it was broken, and then I trusted the only thing it could tell me — that everything was fine. Green ticks are not evidence. They're the cheapest thing in the entire pipeline to fake, and a system under no pressure to be honest will fake them by default. If you take one thing from this into your own automation, make it this: The most important question about any automated system is not "does it work?" It's "if it stopped working right now, how would I find out?" If the honest answer is "a message would stop arriving" — you're fine, absence is loud. If the answer is "the messages would keep coming and they'd all say ✅" — you have already built my trading bots. You just haven't read the account yet. What "fails loudly" actually looks like I threw the whole thing away and rebuilt it as the most boring system I could: plain Python, under systemd timers, no visual workflow canvas, no LLM in the decision path. It makes far fewer decisions than the bots did. The difference isn't the cleverness — it's that every part of it is built to tell me when it's broken: A missed run pages me within the hour. I tested that by deliberately breaking it, because — and this is the whole point of the article — a healthy system reports ok under both the correct schedule and the broken one, so a quiet week proves nothing. You have to make it fail on purpose and watch the alert arrive. (Mine didn't, the first time. The detector had a 24-hour blind spot. I only found it by faking a dead run.) The signals are a pure function of the indicators, not an LLM's free text parsed with a regex. Same inputs, same output, every time — auditable, and it can't quietly drift. It prints its real state, including the ugly parts. When the price feed is down it says "data unavailable," not "market tide against you." An outage looks like an outage. It's covered by a small pile of tests that assert the arithmetic, so a refactor can't silently reintroduce a price * 100 unit bug. It is dramatically less impressive to look at than five bots and a wall of green Discord ticks. It is also the first version I actually trust, precisely because it is willing to tell me it failed. That trade — clever-but-lying for boring-but-honest — was worth every penny of the £424 it cost to learn. I wrote the full forensic breakdown up as a short guide — every node, every rejected order, and how I rebuilt each piece to fail loudly — plus a checklist you can run against anything you leave unattended. It's *pay-what-you-want, including free*. Take it, use the checklist, pay nothing if you like. Nothing here is financial advice or a recommendation to trade. It's a debugging story; the numbers are my own account's, warts and all.