Back to portfolio

April 2026

I Built a 27,000-Line Trading Bot and It Lost Every Dollar

A few months ago I wrote about building AI bots that run 24/7 on a Raspberry Pi. That post was optimistic. The bot was paper-trading, the returns looked strong, and I was planning to go live with real money. This post is the follow-up I owe you: the bot was broken the entire time, and I didn't know it.

Polybot is officially retired. I killed the process on my Pi this week after it had been running for 27 days straight doing absolutely nothing — spinning through scan cycles, failing every API call, and finding zero trades. This is the post-mortem.

The numbers that fooled me

On paper, Polybot looked incredible. Starting from $1,000 in simulated capital, it reported $2,402 — a 140% return over a few weeks. It was winning 50% of its trades, and the winners were big: buying NO on markets like "Will Haiti win the 2026 FIFA World Cup?" at what appeared to be $0.50 and watching it resolve to $0.99. The AI analysis was detailed and confident. The Telegram alerts were rolling in. I was watching a machine print money.

Except it wasn't.

One line of code, everything wrong

Polymarket's Gamma API returns a field called outcomePrices. It looks like this:

'["0.001", "0.999"]'

That's a JSON string, not a list. My parser treated it as a list and called float(outcomes[0]). The first character of the string is [, so float("[") threw a ValueError. The exception was silently caught, and the price defaulted to $0.50.

Every single market — hundreds of them — entered the bot's analysis pipeline at a phantom $0.50 price regardless of the actual market price. A market where NO was already trading at $0.999 showed up as $0.50 in the bot's world. The AI would analyze it, correctly determine that NO was the right side, and the bot would "buy" at $0.50. Then the position monitor would see the real price ($0.999) and trigger an instant take-profit exit.

The fix was one line: wrap the string in json.loads() before indexing. The exact same parsing pattern was already used ten lines below for a different field — clobTokenIds. Someone had fixed it there and missed outcomePrices.

The irony: the AI analysis was often correct. Haiti probably won't win the World Cup. But that doesn't matter if the entry price is a hallucination. You can be right about the direction and still lose everything if you're wrong about your cost basis.

Why I didn't catch it sooner

This is the part that's worth reflecting on, because the technical bug is boring. A missing json.loads() call is a five-minute fix. The interesting question is: how did I watch this bot for weeks and not notice?

I trusted the P&L number. The bot tracked its own performance. It said +140%, and I believed it. I never independently verified the entry prices against the exchange. I was checking the dashboard, reading the Telegram alerts, reviewing the AI reasoning — all of which looked legitimate — but I never asked the most basic question: "Is the price the bot thinks it paid actually the price the market is trading at?"

The wins were plausible. Betting NO on absurd outcomes is a real strategy. "Will Haiti win the World Cup?" at 50/50 is a mispricing — just not a mispricing that actually exists on Polymarket, because the real NO price is already $0.999. The bot found markets where the thesis was correct but the opportunity was already gone. The phantom pricing made it look like the opportunity was still there.

Silent exception handling hid the problem. The parser caught the ValueError and defaulted to $0.50 without logging a warning. No error in the logs, no Telegram alert, no indication that anything was wrong. The bot looked healthy because the failure mode was invisible.

What happened after the fix

Once the bot parsed real prices, the results were immediate and brutal. Every meme market that used to look like free money — FIFA longshots, celebrity pardons, absurd political outcomes — was already correctly priced at $0.99 NO. The extreme-price filter blocked them all. The bot went from finding 5-10 "opportunities" per cycle to finding zero.

I let it run for another month. In that time it executed exactly zero trades. The AI analysis kept running (until the DeepSeek API balance hit $0), kept evaluating markets, and kept concluding there was nothing mispriced enough to act on.

The thesis — that prediction markets misprice obvious outcomes and an AI bot can exploit that — was wrong. Or more precisely: the mispricings I was targeting don't exist at real prices. By the time a market has enough liquidity to trade, the crowd has already priced it correctly.

The total cost

About $140 in API fees over the project's lifetime (DeepSeek, OpenAI, Anthropic). Zero real trading losses because I never left paper mode — which, in retrospect, was the single best decision I made on this project. If I had funded it based on the phantom returns, I would have lost real money on trades that couldn't have been executed at those prices anyway.

What I'd actually keep

The bot is dead but the engineering isn't wasted. I built and battle-tested:

None of that goes away because the trading edge wasn't there. The architecture pattern — poll, filter, analyze, act, monitor — transfers to any domain where you're watching for events and responding to them. The risk management code is reusable anywhere you're sizing uncertain bets. The deployment setup is running other services on that same Pi right now.

What I actually learned

Verify your data at the boundary. The bug lived at the boundary between an external API and my internal model. I trusted the API response format without validating it. A single assertion — assert isinstance(outcomes, list) — would have caught this on the first scan cycle.

Never trust your own P&L. If a system is tracking its own performance, that system's bugs will infect its performance numbers. Independent verification isn't optional. For a trading bot, that means comparing your recorded entry prices against the exchange's trade history.

Silent failures are the most dangerous kind. An exception that crashes the bot is annoying but obvious. An exception that gets caught, defaults to a plausible value, and lets execution continue is invisible and can run for weeks before anyone notices. Default values in catch blocks should be treated with extreme suspicion.

Markets are efficient. This sounds like a textbook platitude until you spend months building a system to exploit inefficiencies and discover they don't exist at the scale you need. Prediction markets on Polymarket have enough informed participants that the low-hanging fruit — absurd outcomes priced at 50/50 — simply doesn't happen. The prices that look wrong are already right.

Paper trading saved me. I set a rule: 50 clean trades before going live. I never hit 50 because the trades weren't clean — they were phantoms. That rule, which felt overly conservative at the time, prevented me from losing real money on a fundamentally broken system.

Closing it out

Polybot ran from February to April 2026. It grew to 27,000 lines of Python across 30+ modules. It had a three-tier detection pipeline, a multi-model AI ensemble, WebSocket price feeds, whale tracking, arbitrage scanning, a Telegram control interface, a Streamlit dashboard, and a full test suite. It was, architecturally, some of the best code I've written.

And it never made a single real dollar.

I'm not embarrassed by that. Building something ambitious, discovering it doesn't work, diagnosing exactly why, and making the call to shut it down — that's the whole job. Not every project ships. The ones that don't still teach you things that the ones that do never will.

The code is on GitHub if you want to poke around. The Pi is running other things now. And the next project has the benefit of every mistake this one made.