Source code for backtrader.indicators.mabase
#!/usr/bin/env python
"""Moving Average Base Module - Core moving average infrastructure.
This module provides the base classes and registration system for all
moving average indicators in backtrader.
Classes:
MovingAverage: Placeholder for all moving average types.
MovAv: Alias for MovingAverage.
MovingAverageBase: Base class for moving average indicators.
Example:
class MyStrategy(bt.Strategy):
def __init__(self):
self.sma = bt.indicators.SMA(self.data.close, period=20)
self.ema = bt.indicators.EMA(self.data.close, period=12)
# Or using MovAv wrapper
self.wma = bt.indicators.MovAv.WMA(self.data.close, period=15)
def next(self):
if self.data.close[0] > self.sma[0]:
self.buy()
elif self.data.close[0] < self.sma[0]:
self.sell()
"""
from . import Indicator
# Moving average class, used to set indicator names
[docs]
class MovingAverage:
"""MovingAverage (alias MovAv)
A placeholder to gather all Moving Average Types in a single place.
Instantiating a SimpleMovingAverage can be achieved as follows::
sma = MovingAverage.Simple(self.data, period)
Or using the shorter aliases::
sma = MovAv.SMA(self.data, period)
or with the full (forwards and backwards) names:
sma = MovAv.SimpleMovingAverage(self.data, period)
sma = MovAv.MovingAverageSimple(self.data, period)
"""
# Storage for moving average classes
_movavs: list = []
[docs]
@classmethod
def register(cls, regcls):
"""Register a moving average class with the placeholder.
Args:
regcls: The moving average class to register.
Sets the class name and aliases as attributes on the placeholder
for easy access (e.g., MovAv.SMA, MovAv.EMA).
"""
# If indicator doesn't have _notregister or _notregister value is False, continue to register, otherwise return directly
if getattr(regcls, "_notregister", False):
return
# Add indicator class to be calculated
cls._movavs.append(regcls)
# Class name, and set class name as cls attribute, attribute value is the specific class
clsname = regcls.__name__
setattr(cls, clsname, regcls)
# Specific indicator alias, if indicator starts with MovingAverage, use latter value as alias, if ends with MovingAverage, use former value as alias
# If obtained alias is not empty string, then also set alias as attribute, attribute value is this class
clsalias = ""
if clsname.endswith("MovingAverage"):
clsalias = clsname.split("MovingAverage")[0]
elif clsname.startswith("MovingAverage"):
clsalias = clsname.split("MovingAverage")[1]
if clsalias:
setattr(cls, clsalias, regcls)
# CRITICAL FIX: Process the alias attribute if it exists
# Many indicators define their own aliases like alias = ("SMA", "SimpleMovingAverage")
if hasattr(regcls, "alias"):
aliases = regcls.alias
# Support both tuple and single string
if isinstance(aliases, str):
aliases = (aliases,)
# Register each alias
for alias_name in aliases:
if alias_name and isinstance(alias_name, str):
setattr(cls, alias_name, regcls)
# Alias for moving average
[docs]
class MovAv(MovingAverage):
"""Alias for MovingAverage.
Provides a shorter name for accessing moving average types.
"""
# alias
# Base class for moving average, add parameters and plot settings - refactored to remove metaclass
[docs]
class MovingAverageBase(Indicator):
"""Base class for all moving average indicators.
Provides common initialization with minimum period management and
automatic registration with the MovingAverage placeholder.
Attributes:
params: Default period parameter (30).
plotinfo: Default to plot on main chart (subplot=False).
"""
# Parameters
params = (("period", 30),)
# Plot on main chart by default
plotinfo = {"subplot": False}
def __init__(self):
"""Initialize moving average and set minimum period"""
super().__init__()
# CRITICAL FIX: Inherit minperiod from data source BEFORE adding own period
# This ensures nested indicators (like EMA applied to MACD line) properly accumulate minperiods
if hasattr(self, "datas") and self.datas:
data_minperiods = [getattr(d, "_minperiod", 1) for d in self.datas if d is not None]
if data_minperiods:
data_max = max(data_minperiods)
if data_max > self._minperiod:
self._minperiod = data_max
# CRITICAL FIX: Set the minimum period based on the period parameter
# This ensures the indicator doesn't start calculating until enough data is available
self.addminperiod(self.p.period)
[docs]
def __init_subclass__(cls, **kwargs):
"""Register moving average classes automatically"""
super().__init_subclass__(**kwargs)
# Register any MovingAverage with the placeholder to allow the automatic
# creation of envelopes and oscillators
MovingAverage.register(cls)