I’ve been trading for a while now and figured a machine could pick stocks better than I can (admittedly a low bar). I tried my hand at options day trading and paid the price, as most people do. Barber et al. 2014 found that less than 1% of day traders reliably make money after fees. I thought I could beat those odds. Thought.
I did a tonne of research on this topic, and there’s a lot out there. There are also a lot of people posting their ‘winning’ strategies online, most of which are not winning at all. A lot of them may work for a small window of time, but fall apart during certain market conditions. I wanted something that could be resilient across less ideal times, while performing vs SPY as well as possible. As a retail trader, you can’t compete with the big firms on cost or speed; they have effectively $0 commissions, servers next to the exchange and near 0% margin interest. All of this had to be taken into account. On a small book, day trading could easily chew up any gains in commission costs alone.
So, back in March I decided to build an automated system to handle a bunch of my trading for me and to keep my emotions out of it. It started as a weekend experiment and turned into one of the most involved projects I’ve built so far. It’s a fully automated swing trading bot that picks S&P 500 stocks using machine learning, places its own orders through IBKR, and has been trading real money since the end of June.
Every trading day it pulls the current S&P 500 list and daily price history, then calculates a set of technical indicators for each stock. Those feed a machine learning model that scores every stock in the index on how likely it is to outperform over the next little while. Getting the question the model answers right turned out to matter a lot more than which model I used; my first version was barely better than a coin flip, and reframing the problem is what made it actually useful. I went through a lot of iteration to get this right.
On a regular schedule the bot buys the stocks the model is most confident in and sells whatever dropped off the list. In between, it watches the portfolio during market hours with stop losses, puts that cash back to work in the next best pick, and has a kill switch that stops new buying if the account takes a big enough hit. The model also retrains itself every month so it keeps up with the market. Tuning of all these parameters took a lot of time and endless backtesting.
The whole thing runs in Docker on my home server, the same box that hosts this website. There are actually two copies running side by side, one on an IBKR paper account where I test changes, and one on the live account, each with its own IB Gateway container. Everything feeds into Prometheus and a Grafana dashboard, and it sends me Discord messages for every trade, a daily P&L summary against the S&P, and a weekly report. Keeping IB Gateway logged in unattended turned out to be one of the more annoying parts, between IBKR’s daily reset and two factor authentication.


The most interesting part has been the backtesting, mostly because of how wrong my early numbers were. My first walk-forward backtests showed returns in the thousands of percent. A big chunk of that turned out to be survivorship bias; if you backtest on today’s S&P 500 list, you’re only ever testing companies that did well enough to still be in the index. Once I rebuilt the backtest to use the index as it actually was on each date, the honest number came down to somewhere in the mid 20s percent per year, with a drawdown of 40% or more being something I should expect at some point. Still good, but a very different picture!
One test I’m really glad I ran was swapping the model’s picks for random ones while keeping everything else identical. Random picks actually lost to the market, while the real model beat it comfortably. So the stock selection is doing the real work, and all the risk management on its own just amplifies whatever you feed it, good or bad.
Since then I’ve tested over 20 ideas to squeeze out additional performance, from extra data sources to fancier models (including a deep learning model trained on my GPU). Not a single one made it in. Most looked great on the first run and fell apart once I checked them across different samples of the data.
Following insider buying (company executives buying their own stock) looked like the best signal I’d ever found, until I realised almost half of the signals came from a single company and the gains were mostly rebounds after market crashes. In other words it was really a market timing bet, and that turned out to be a pattern. Every idea that tried to time the market, whether that was cutting back when things looked shaky or reacting to news like earnings and analyst downgrades, did worse than just sticking with the stock picks. The edge is in choosing which stocks to own, not when to be in the market. Adding Canadian stocks as a second portfolio looked great too, but the entire edge turned out to be the same survivorship bias problem again. The deep learning model actually did worse than picking stocks at random. Kind of humbling, but it also tells me the simple version is pretty well tuned.
The other big lesson is how much of a real trading system is plumbing rather than strategy. Things like making sure a retry can never accidentally turn a sell into a short position, not trading on market holidays (it happily queued up 18 orders on Labour Day before I caught that one), and recording the price an order actually filled at rather than the price the bot expected. Thankfully nothing has cost me much yet, and these days it mostly runs itself.

Live account return vs the S&P 500
Cumulative % return on the current strategy, updated every trading day
I’ve also set myself a rule ahead of time for when to pull the plug. If it falls far enough behind the S&P over a rolling two years, it gets cut back and goes back to paper trading. Apparently two bad years in a row can be completely normal for a strategy like this, so I’m going to give it some time. Obviously none of this is financial advice! I’ll post an update once there’s enough of a track record to say whether it’s actually working.









