Python – Metatrader – Backtesting part 1

YouTube Video Thumbnail

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

Scroll to Top