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

