DEV Community

IO
IO

Posted on

"Just Automate It" — When an Engineer Builds a Trading Bot

I Automated My Investments. Then Reality Hit.

When I first got serious about this, my brain immediately went to the place every software engineer's brain goes: if the rules are clear, it can be automated.

That's genuinely how I think about everything. If a process has defined inputs, defined outputs, and a logic chain connecting them, it's a candidate for code. Investing felt like it should qualify. Buy low, sell high. Use indicators to tell you when to buy and when to sell. Repeat. Where exactly is the human judgment required here?

Turns out I was asking the wrong question, but I didn't know that yet.


The Golden Cross and Why It Seemed Obvious

I started reading about technical analysis. Not books, just the usual rabbit hole. Wikipedia, Investopedia, a few StackOverflow threads where someone asked a similar question. The concepts that kept surfacing were moving averages.

The idea is simple. A moving average smooths out the noise in a price series by averaging closing prices over some window. The 50-day simple moving average of SPY on any given day is just the average closing price over the previous 50 days. As new days come in, the window slides forward. That's all it is.

The interesting part is what happens when you put two of them together. A short-period moving average (say, 50 days) and a long-period one (200 days). The short one reacts faster to recent price changes. The long one reacts slowly. When the fast line crosses above the slow line, that's called a golden cross. Supposedly bullish. When it crosses below, that's a death cross. Supposedly bearish.

The logic felt clean to me. You're essentially comparing recent momentum against longer-term momentum. If short-term prices are rising faster than the long-term average, things might be accelerating. That made intuitive sense.

I also read about EMA, the exponential moving average, which weights recent prices more heavily instead of treating all days in the window equally. The formula is recursive. Each day's EMA is a blend of the current price and yesterday's EMA, controlled by a smoothing factor based on the window size. More responsive than SMA, potentially less lag on signals. I made a mental note to try both.

And then I found RSI, the relative strength index. The math there is a bit more involved but the idea is elegant. You look at the average gains versus average losses over a 14-day window, compute a ratio, and scale it to a 0-100 range. Values above 70 are considered "overbought." Below 30 is "oversold." The theory is that extreme values mean a reversal is coming.

I immediately thought about using RSI as a filter. Only act on golden cross signals if RSI isn't already overbought. That felt like I was being smart about it. Adding constraints. Reducing false positives.


Building the First Bot

I pulled historical data using yfinance. If you haven't used it, it's a Python library that pulls from Yahoo Finance. Free, easy, works fine for daily OHLCV data going back decades. I grabbed SPY going back to 2000. That gave me two decades of data including the dot-com crash, the 2008 housing crisis, and the 2020 COVID drop.

I used pandas for the data manipulation. Computing rolling means is literally a one-liner in pandas. The SMA is just df['close'].rolling(window=50).mean(). For EMA there's a built-in .ewm() method. These are the easy parts.

The backtesting logic itself isn't much more complex. You iterate through the data chronologically. At each row, you check: did the 50-day cross above the 200-day yesterday? If yes, buy. Did it cross below? If yes, sell. At the end you compare what that strategy would have returned versus just buying and holding SPY the whole time.

My first pass used SMA crossovers, no RSI filter. I ran it and stared at the results.

The backtest showed something like 340% total return over 20 years. Buy and hold over the same period was around 280%. I was beating the market.

I remember sitting there in my apartment at maybe 11pm on a Tuesday, running the numbers again because I didn't trust them. They kept coming back the same. I added the RSI filter. The drawdowns got smaller. The return stayed competitive. Sharpe ratio went up.

This is the thing about backtesting. When it works, it feels like you discovered something real. You can see every trade laid out chronologically. There's that one trade in March 2009 where the strategy got you back in near the bottom. There's that clean exit before the 2020 crash. The chart of your portfolio equity curves nicely upward. You can point to specific moments and say, look, the signal worked here.

It genuinely looks like evidence. That's what makes it so dangerous.


What Backtesting Actually Is

I'll be honest about what I did and didn't understand at this point.

I understood that backtesting was simulated. Obviously the trades didn't really happen. But I thought that if the rules were deterministic and applied consistently to historical data, the results would be predictive of future results, assuming the underlying market dynamics didn't fundamentally change.

That sounds reasonable. It's mostly wrong, for a few reasons I hadn't fully appreciated.

The big one is lookahead bias. It's surprisingly easy to accidentally let future information contaminate past decisions in your backtest. For example, if you compute an indicator using the full dataset and then simulate trades, you've cheated without meaning to. I was careful about this one because I'd read about it.

The one I missed was overfitting. When you backtest a strategy and then tweak it to improve the results, then backtest again, then tweak again, you're fitting the strategy to the historical noise. The 14-day RSI window, the 50/200-day crossover. Why 50 and 200? Why not 45 and 180? Why not 60 and 250? I tried a few combinations and picked the ones that looked best. I didn't fully register that I was doing this. I thought I was just validating that the classic parameters worked. But the act of checking was itself a form of fitting.

There's also transaction costs. In my first backtest I didn't account for them at all. When I added a $5 commission per trade and a couple basis points of slippage, the edge shrank. Still positive, still convincing, but smaller.

And survivorship bias doesn't apply to SPY directly, but if I'd been testing on individual stocks I'd have been pulling data only for stocks that still existed. The ones that went to zero aren't in the historical dataset. Your backtests automatically avoid them.

I understood most of these things intellectually. I thought I'd been careful. I had no idea what I was walking into.


The Decision to Go Live

I set a budget. Not my whole portfolio, just a portion I could afford to lose. This felt like responsible risk management. I told myself it was tuition.

I connected my broker's API, which is straightforward with alpaca-trade-api if you're using Alpaca for US equities. They have a paper trading environment where you can run your bot with fake money against real market conditions. I did about two weeks of paper trading. Everything looked fine.

Then I pushed the go-live commit.

I remember watching the first real trade execute. SPY, market order, real money. It felt different from looking at backtest output in a pandas dataframe. There was a small adrenaline hit I hadn't expected. I'm a pretty calm person normally. I wasn't calm.

The bot ran on a cron job on a cheap DigitalOcean droplet. Every day after close it would pull the latest data, recompute the indicators, check for crossover signals, and place orders if needed. I'd get a Slack notification when a trade executed. I kept checking Slack.

At that point I genuinely believed I'd done the work. The code was clean, the logic was sound, the backtest results were strong. The gap between that confidence and what was about to happen was pretty wide.


The first few weeks were fine. Not great, not bad. The market was doing its thing and my bot was doing its thing and they weren't interacting in any dramatic way. That was actually the most misleading part. Nothing obviously wrong.

Then the choppiness started.


Next: I'll walk through what actually happened when the strategy started generating signals in a sideways market. The whipsaws, the false signals, and the moment I opened my brokerage statement and did the math on what the fees were adding up to. Spoiler: the backtest did not mention any of this.

Top comments (0)