Building an AI trading platform

You’ve seen the reel. Someone opens Claude or ChatGPT, types a prompt or two, and by the end of the video they’ve got a dashboard with a green line going up and to the right — an AI trading bot that’s supposedly about to make them rich. It’s one of those success stories that spreads well precisely because it’s so easy to believe: the model is smart, the market has numbers in it, surely the model can just look at the numbers and know what to buy.
I’ve been building one of these for real since April. It does not look like the reel.
What’s actually running
It’s a Python/LangGraph state machine — a linear pipeline of 14 specialised agents that executes one “trading cycle” at a time, triggered by Cloud Scheduler. Some agents are plain deterministic code (fetch data, compute technical indicators, size a position); some are LLM calls (macro regime, sentiment, a bull/bear debate per symbol, the actual buy/sell/hold decision). Every cycle runs through the same gauntlet: macro outlook → universe scan → data and fundamentals → sentiment and technical signals → per-symbol analyst debate → a strategy decision → portfolio sizing — and only then does it hit a control plane, eleven hard-coded rules that can block a trade outright regardless of what the LLM wanted (max position size, max daily loss, sector concentration, cash reserve, market hours, and so on). If that passes, a risk agent gets a second veto. Only then does anything get submitted to Alpaca. A reflection step runs afterwards and is supposed to learn something from what just happened.
It’s paper trading. It has been the whole time.
The bugs nobody screenshots
The gap between “the dashboard looks like this” and “the thing is actually sound” turned out to be enormous, and almost none of it is visible from a screenshot.
The one I fixed earlier this week: the portfolio manager would sometimes submit a full stop-loss SELL on a stock and a small strategy top-up BUY on the same symbol in the same cycle. The stop-loss logic only knew to suppress other trades for a couple of specific trigger types; a plain stop-loss sell didn’t count, so the sizing code still saw the position as held and happily topped it back up minutes after gutting it. To make it worse, the execution log kept trades keyed by symbol, so the BUY silently overwrote the SELL in the trade history — the stop-loss exit that actually mattered never even showed up in the fill records used for the win rate.
Then there’s the one I’d call the classic: a position doing well would get trimmed back down by a fixed percentage, and then trimmed again the next cycle, and the one after that, forever — the position sizing logic thought it was rebalancing toward a target, the take-profit logic thought it was banking gains, and neither of them agreed on what the target actually was. One winning stock got sold down in eight separate tranches during an 85% run, a little bit each time, never long enough to actually let it run. And the reflection agent — the part meant to learn from outcomes — graded every one of those sells as a win, because it only compared the exit price to the entry price, not to what happened afterward. It was teaching itself “selling this stock was correct” while the stock kept climbing for weeks. The lesson was actively making the next decision worse.
None of that shows up in a screenshot of a green equity curve. It shows up when you go read the trade log line by line and ask why the system keeps doing something a person would obviously not do.
Fixing it with AI, on repeat
Every cycle gets logged to SQLite — every trade, every control-plane violation, a full JSON snapshot of the state. That log is the actual product, more than the trading itself. Periodically I’d hand chunks of that history to Claude and ask it to look for patterns instead of individual trades: why does this stock keep getting sold in small pieces, why did the win rate look great while the account sat mostly in cash, why does the equity curve have a staircase shape. That’s how most of the fixes above got found — not by watching the dashboard live, but by treating months of trade history as a dataset to interrogate.
It’s been almost 80 commits in under three months, and a large share of them are exactly this shape: a production trade sequence that looks wrong, a root cause in the sizing or the prompt or the reflection logic, a fix, tests, repeat. Grading exits against the counterfactual of just holding was its own fix. So was making the re-entry logic remember that it had just sold something before buying it back at a worse price a few weeks later. So was making sure the LLM’s SELL rationale had to point at something actually broken about the position instead of “it’s up a lot, let’s take profits” — which sounds prudent and was wrong four times out of five in practice.
I also got to run a chunk of this trade-history digging through Fable 5 during its promotion period, and it was genuinely fantastic at it — sharper at pulling the actual pattern out of a few hundred cycles of trades than anything else I’d pointed at the same logs. It also burned through tokens at a rate I hadn’t seen before. Great trade while the promo lasted; I wouldn’t want that meter running at full price for this kind of open-ended digging.
Where it stands now
It hasn’t touched real money. LIVE_TRADING defaults to false, and there’s a control-plane rule that refuses to place a live order unless that flag is explicitly set — which is exactly the kind of thing you want as a hard gate rather than something you just remember to check.
I think I have something close to a complete system now — real risk gates, position sizing that accounts for volatility and market regime, a learning loop that grades itself honestly instead of congratulating itself. But “complete” is doing some work in that sentence, given that the same-cycle round-trip bug shipped just days before I sat down to write this. I’d rather find the next one of these on paper than with real money, so I’m not in a rush. But I do want to flip that flag before too long.