Simple Moving Average (MA) Crossover Strategy with Backtrader and PyFolio in Python
- Published on
Introduction
The Moving Average (MA) Crossover strategy is a popular technical analysis tool used by traders to identify potential shifts in market trends and generate buy or sell signals
When the long-term moving average (e.g., 20-day) crosses above the short-term moving average (e.g., 10-day), it generates a buy signal, indicating a potential upward trend. Conversely, when the short-term moving average crosses below the long-term moving average, it generates a sell signal, indicating a potential downward trend.
In this post, I'd like to show you how to implement a simple MA crossover strategy using Backtrader, and we will analyze the performance of the strategy using PyFolio.
Get Data Ready
from data_provider.yfinance import download_data_from_yahoo, read_data_from_yahoo
import talib
symbol = "AAPL"
# If you want to download the data again, uncomment the line below
# download_data_from_yahoo(symbol)
data = read_data_from_yahoo(symbol)
data["ma_10"] = talib.EMA(data["close"], timeperiod=10)
data["ma_20"] = talib.EMA(data["close"], timeperiod=20)
data = data["2020-01-01":]
data.head()Implementation
Next, I will show you how to backtest using the Moving Average (MA) Crossover strategy.
Notes:
- We pass a pandas
DataFrameto thebt.Strategyand access the technical indicator of the timestamp in thenextmethod shortly. - If
row["ma_10"] >= row["ma_20"], we callself.buyto buy; otherwise, we sell.
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
warnings.simplefilter(action='ignore', category=UserWarning)import backtrader as bt
from data_provider.yfinance import download_data_from_yahoo, read_data_from_yahoo
import talib
from backtrader.feeds import PandasData
import pandas as pd
import pyfolio as pf
class SimpleMACrossStrategy(bt.Strategy):
def __init__(self, data):
self.raw_data = data
def next(self):
data = self.data
bar_timestamp = pd.to_datetime(data.datetime.datetime())
row = self.raw_data.loc[bar_timestamp]
cur_position = self.getposition(data).size
if cur_position> 0:
if row["ma_10"] < row["ma_20"]:
self.sell(size=cur_position)
else:
if row["ma_10"] >= row["ma_20"]:
cash = self.broker.getcash()
price = data.close[0]
size = int(cash / price) * 0.98
self.buy(size=size)
def notify_order(self, order):
if order.status in [order.Completed]:
if order.isbuy():
self.log(
f"B: {order.executed.price} {order.executed.size}",
)
elif order.issell():
self.log(
f"S: {order.executed.price} {order.executed.size}",
)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log("Order Canceled/Margin/Rejected")
def log(self, txt):
bar_timestamp = pd.to_datetime(self.data.datetime.datetime())
print("%s, %s" % (bar_timestamp.isoformat(), txt))
if __name__ == "__main__":
symbol = "AAPL"
# download_data_from_yahoo(symbol)
data = read_data_from_yahoo(symbol)
data["ma_10"] = talib.EMA(data["close"], timeperiod=10)
data["ma_20"] = talib.EMA(data["close"], timeperiod=20)
data = data["2020-01-01":]
cerebro = bt.Cerebro()
cerebro.adddata(PandasData(dataname=data))
cerebro.addstrategy(SimpleMACrossStrategy, data)
cerebro.broker.setcash(10000.0)
cerebro.broker.setcommission(commission=0.005)
cerebro.addanalyzer(bt.analyzers.PyFolio, _name="pyfolio")
results = cerebro.run()
cerebro.plot(iplot=False)
strat = results[0]
pyfoliozer = strat.analyzers.getbyname("pyfolio")
returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()
# generate performance report
pf.create_full_tear_sheet(returns)
Pros, Cons
Pros of MA Crossover Strategy:
- Simple & Clear Signals: Easy to understand and provides clear visual cues for potential buy/sell points and trend direction.
- Effective Trend Identification: Helps identify and follow the prevailing market trend by smoothing out price volatility.
- Versatile & Customizable: Can be adapted to various markets, timeframes, and trading styles by adjusting MA types and periods.
Cons of MA Crossover Strategy:
- Lagging Nature: Signals appear after the price has already started moving, potentially leading to late entries or exits.
- Whipsaws in Ranging Markets: Generates many false signals (whipsaws) and performs poorly when prices are not trending clearly.
- Parameter Dependent: Performance heavily relies on the chosen MA periods, which may need optimization and can vary across assets/time.
- Often Needs Confirmation: Relying solely on crossovers can be risky; many traders use other indicators or analysis for confirmation.
Moving Average Functions
In this post, we use EMA indicator, but you should know there are many other options in ta-lib:
SMA(Simple Moving Average): Calculates the average price over a specified period, giving equal weight to all data points.EMA(Exponential Moving Average): Gives more weight to recent prices, making it more responsive to new price changes than an SMA.WMA(Weighted Moving Average): Assigns more weight to recent data points in a linear fashion; the most recent price gets the highest weight, and the weight decreases for older prices.DEMA(Double Exponential Moving Average): A more advanced moving average that aims to reduce the lag found in traditional EMAs by using two EMAs in its calculation.TEMA(Triple Exponential Moving Average): Similar to DEMA, TEMA further reduces lag and increases responsiveness by applying a triple smoothing formula involving multiple EMAs.TRIMA(Triangular Moving Average): A weighted moving average where the middle portion of the data series receives the most weight, with weights decreasing in a triangular pattern towards the beginning and end of the period.KAMA(Kaufman Adaptive Moving Average): This moving average adapts its responsiveness based on market volatility. It follows prices more closely when volatility is low and smooths out more when volatility is high.MAMA(MESA Adaptive Moving Average): Developed by John Ehlers, this is an adaptive moving average that uses a Hilbert Transform to identify cycle periods, aiming to adjust to price changes more effectively.T3(Triple Exponential Moving Average T3): Developed by Tim Tillson, this is a smoother and more responsive moving average that aims to reduce lag compared to traditional EMAs.