Building a Crypto Trading Bot from Scratch - 17: Backtesting
Testing Strategies on Historical Data
Your bot is complete. It runs in paper trading mode. The monitoring dashboard shows everything working correctly. You’re ready to go live with real money... right?
Still wrong.
Paper trading proves your bot works. It doesn’t prove your strategy works.
You could have a perfectly functional bot running a terrible strategy. The bot executes orders flawlessly, manages risk correctly, and tracks positions accurately, but the strategy loses money because the trading logic is flawed.
This is where backtesting comes in.
Backtesting means running your strategy on historical data to see how it would have performed in the past. If your EMA crossover strategy claims it can make 10% per month, backtesting shows you what would have actually happened if you’d been trading it for the past year.
In this article, I’ll show you how I built a comprehensive backtesting engine that:
Simulates trading on historical data without look-ahead bias
Applies realistic slippage and fees
Enforces the same risk rules as live trading
Generates detailed performance metrics
Helps you understand if your strategy has any edge at all
Let me be clear: backtesting doesn’t guarantee future profits. Past performance doesn’t predict future results. But it’s still the best tool we have for validating strategies before risking real money.
Why Backtesting Matters
My First Strategy (That I Didn’t Backtest)
Early in my trading journey, I had what I thought was a brilliant idea: “Buy when RSI goes below 30, sell when it goes above 70.” Classic oversold/overbought strategy.
I paper traded it for 3 days. It made 2 profitable trades out of 3. I thought, “67% win rate! This is great!”
Then I deployed it with real money. It lost 15% in two weeks.
What went wrong? I never backtested it on a full market cycle. My 3-day paper trading sample happened to catch a bouncing market. But when I backtested it over 6 months of historical data, I discovered:
Win rate was actually 45% (not 67%)
Average loss was 2x larger than average win
It performed terribly in trending markets
After fees, it was net negative
If I’d run a backtest first, I would have saved myself $300 and a lot of stress.
What Backtesting Reveals
A good backtest tells you:
Historical Performance: How would this strategy have done over the past year?
Win Rate: What percentage of trades are profitable?
Risk Metrics: What’s the worst drawdown? How volatile are returns?
Edge: After fees and slippage, does this strategy actually make money?
Market Regime Sensitivity: Does it work in trends? Ranges? Volatility?
Without backtesting, you’re flying blind. With backtesting, you have data.
The Backtesting Challenge: Avoiding Look-Ahead Bias
The biggest mistake in backtesting is look-ahead bias: using information that wouldn’t have been available at the time of the trade.
Example of Look-Ahead Bias
# WRONG - Look-ahead bias!
def bad_backtest(candles):
for i in range(len(candles)):
current_candle = candles[i]
next_candle = candles[i + 1] # FUTURE DATA!
# If price goes up next candle, buy now
if next_candle.close > current_candle.close:
buy(current_candle)
This looks at future data (the next candle) to make current decisions. Of course it looks profitable—you’re trading with perfect foresight! But you can’t do this in real trading.
The Right Way: Event-Driven Backtesting
# CORRECT - No look-ahead bias
def good_backtest(candles):
for i in range(len(candles)):
# Only use data up to and including current candle
historical_window = candles[0:i+1]
# Generate signal based on historical data only
signal = strategy.generate_signal(historical_window)
# Execute trade at current candle's close
if signal.signal_type == SignalType.BUY:
buy(candles[i].close)
This processes candles chronologically, one at a time, using only data that would have been available at that point in time. This is how the bot trades live, so this is how we should backtest.
The BacktestEngine Class
I built a BacktestEngine that simulates the full trading loop on historical data:



