MA Crossover with MACD Confirmation Building Custom Strategies with Backtester

This article demonstrates how to create a custom trading strategy using the backtrader library in Python for use with “Backtester” app. The strategy combines Moving Average (MA) crossover with MACD confirmation to generate buy and sell signals. We’ll break down the code, explain its components, and show how it can be used within the backtrader framework.

Strategy Overview: MACrossoverMACDConfirmStrategy

The strategy, named MACrossoverMACDConfirmStrategy, leverages two popular technical indicators:

The strategy’s logic is as follows:

Code Breakdown

Let’s dissect the Python code, explaining each section:

1. Import Statements and Parameters:

Python

import backtrader as bt
from datetime import datetime

class MACrossoverMACDConfirmStrategy(bt.Strategy):
    params = (
        # MA Params
        ('short_ma_period', 50), # Period for the short-term MA
        ('long_ma_period', 200), # Period for the long-term MA
        ('ma_type', 'EMA'),      # Type of Moving Average: 'SMA' or 'EMA'

        # MACD Params
        ('macd_fast', 12),       # Period for the fast EMA in MACD
        ('macd_slow', 26),       # Period for the slow EMA in MACD
        ('macd_signal', 9),      # Period for the signal line EMA in MACD

        ('printlog', True),      # Enable/Disable logging
    )

2. __init__ Method:

Python

    def __init__(self):
        # Keep references to data feeds
        self.dataclose = self.datas[0].close

        # To keep track of pending orders
        self.order = None
        self.buyprice = None
        self.buycomm = None

        # --- MA Indicators ---
        ma_indicator = bt.indicators.SimpleMovingAverage
        if self.p.ma_type == 'EMA':
            ma_indicator = bt.indicators.ExponentialMovingAverage
        self.short_ma = ma_indicator(self.datas[0], period=self.p.short_ma_period)
        self.long_ma = ma_indicator(self.datas[0], period=self.p.long_ma_period)
        self.ma_crossover = bt.indicators.CrossOver(self.short_ma, self.long_ma)

        # --- MACD Indicator ---
        self.macd = bt.indicators.MACD(
            self.datas[0],
            period_me1=self.p.macd_fast,
            period_me2=self.p.macd_slow,
            period_signal=self.p.macd_signal
        )

3. log Method:

Python

    def log(self, txt, dt=None, doprint=False):
        ''' Logging function for this strategy'''
        if self.params.printlog or doprint:
            try:
                 log_dt = bt.num2date(self.datas[0].datetime[0]).date()
            except IndexError:
                 dt = dt or self.datas[0].datetime.date(0) if len(self.datas[0]) else datetime.now().date()
                 log_dt = dt
            print(f'{log_dt.isoformat()} - {txt}')

4. notify_order Method:

Python

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            return
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(f'BUY EXECUTED, Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Comm {order.executed.comm:.2f}')
            elif order.issell():
                self.log(f'SELL EXECUTED, Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Comm {order.executed.comm:.2f}')
            self.bar_executed = len(self)
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')
        self.order = None

5. notify_trade Method:

Python

    def notify_trade(self, trade):
        if not trade.isclosed:
            return
        self.log(f'OPERATION PROFIT, GROSS {trade.pnl:.2f}, NET {trade.pnlcomm:.2f}')

6. next Method:

Python

    def next(self):
        # Check if an order is pending
        if self.order:
            return

        # Check if indicators are ready
        if len(self) < self.p.long_ma_period or len(self) < (self.p.macd_slow + self.p.macd_signal):
             return

Conclusion

It is easy to create custom strategies to backtest with backtrader that you can add to the “Backtester” app. You can start with my book to learn fast or check out the strategies instruction manual for Backtester: https://www.pyquantlab.com/apps/backtester_strategies_manual.pdf let’s see the results with Backtester:

Pasted image 20250504033208.png

I changed parameters as you can see to find a combination that works better. I am updating the app to include many strategies to give you ideas and a base to build upon. Don’t miss out!