Backtesting packages for python are largely geared to proving a strategy rather than simulating trading. The difference is important if you want to later simply run your code against a broker API. To show backtesting I’m starting with the basics, just proving a strategy, or at least how to do that.
I chose backtesting.py as my package to support backtesting because I think it offers the best path to later simply run code against production, but it will take more parts in this series to get there.
I’ve done away with many of the features introduced earlier to take everything back to basics. There are only 2 files
backtest.py
classes/simple_strategy.py
You will need to have the necessary packages installed,
pip install MetaTrader5 pip install pandas pip install pandas_ta pip install backtesting
See the note later about pandas_ta
backtest.py
# Copyright 2019-2025, Orchard Forex # https://orchardforex.com import MetaTrader5 as mt5 import pandas as pd import datetime from backtesting import Backtest from classes.simple_strategy import SimpleStrategy # from backtesting import Backtest, Strategy def main(): if not mt5.initialize(): log("terminal initialisation failed") return log("MT5 successfully initialised") # get history data from mt, only using one instrument / tf for now history = pd.DataFrame(mt5.copy_rates_from_pos("EURUSD", mt5.TIMEFRAME_H1, 0, 1000)) history['time'] = pd.to_datetime(history['time'], unit='s') history.set_index('time', inplace=True) history.rename(columns={'open':'Open', 'high':'High', 'low':'Low', 'close':'Close', 'tick_volume':'Volume'}, inplace=True) print(history) test = Backtest(history, SimpleStrategy, cash=10000, hedging=True, finalize_trades=True) result = test.run() print(result) print(f'buy count = {result._strategy.buy_count}') print(f'sell count = {result._strategy.sell_count}') mt5.shutdown() return def log(msg): now = datetime.datetime.now() now_str = now.strftime('%Y.%m.%d %H:%M:%S') msg = f"{now_str} {msg}" print(msg) if __name__ == "__main__": main() # to try my untested version of pandas_ta # pip install -U git+https://github.com/OrchardForexTutorials/pandas-ta.git --no-cache-dir
simple_strategy.py
import MetaTrader5 as mt5 import pandas as pd # import pandas_ta as ta from backtesting import Strategy class SimpleStrategy(Strategy): def init(self): self.fast_ma_period = 10 self.slow_ma_period = 20 self.lot_size = 0.1 self.stop_loss_amount = 0.00100 self.take_profit_amount = 0.00150 self.buy_count = 0 self.sell_count = 0 # Calculate fast and slow moving averages self.fast_ma = self.I(SMA, self.data.Close, self.fast_ma_period) self.slow_ma = self.I(SMA, self.data.Close, self.slow_ma_period) pass def next(self): # Check for crossover if self.fast_ma[-1] < self.slow_ma[-1] and self.fast_ma[-2] >= self.slow_ma[-2]: # Fast MA crosses above Slow MA: Buy signal open_trades = sum(1 for trade in self.trades if trade.is_long) if open_trades > 0: return stop_loss_price = self.data.Close[-1]-self.stop_loss_amount take_profit_price = self.data.Close[-1]+self.take_profit_amount self.buy(size = self.lot_size, sl = stop_loss_price, tp = take_profit_price) self.buy_count += 1 elif self.slow_ma[-1] < self.fast_ma[-1] and self.slow_ma[-2] >= self.fast_ma[-2]: # Slow MA crosses above Fast MA: Sell signal open_trades = sum(1 for trade in self.trades if trade.is_short) if open_trades > 0: return stop_loss_price = self.data.Close[-1]+self.stop_loss_amount take_profit_price = self.data.Close[-1]-self.take_profit_amount self.sell(size = self.lot_size, sl = stop_loss_price, tp = take_profit_price) self.sell_count += 1 # Define Simple Moving Average (SMA) function def SMA(data, period): # return ta.sma(pd.Series(data), length = period).to_numpy() return pd.Series(data).rolling(period).mean()
Looking for a broker? Support the channel by using one of these links. We receive a commission on your trades from the broker at no cost to you:
For Australian customers without trading restrictions – Black Bull
Outside Australia – FXTrading