Source code for backtrader.indicators.sma

#!/usr/bin/env python
"""SMA Indicator Module - Simple Moving Average.

This module provides the SMA (Simple Moving Average) indicator for
calculating the non-weighted average of the last n periods.

Classes:
    MovingAverageSimple: SMA indicator (alias: SMA).

Example:
    class MyStrategy(bt.Strategy):
        def __init__(self):
            self.sma = bt.indicators.SMA(self.data.close, period=20)

        def next(self):
            if self.data.close[0] > self.sma[0]:
                self.buy()
            elif self.data.close[0] < self.sma[0]:
                self.sell()
"""

import math

from ..utils.log_message import get_logger
from .mabase import MovingAverageBase

logger = get_logger(__name__)


# Moving average indicator
[docs] class MovingAverageSimple(MovingAverageBase): """ Non-weighted average of the last n periods Formula: - movav = Sum(data, period) / period See also: - http://en.wikipedia.org/wiki/Moving_average#Simple_moving_average """ alias = ( "SMA", "SimpleMovingAverage", ) lines = ("sma",) def __init__(self): """Initialize the SMA indicator.""" # Before super to ensure mixins (right-hand side in subclassing) # can see the assignment operation and operate on the line super().__init__()
[docs] def nextstart(self): """Initialize on first call after minperiod is met.""" # Delegate to next() — at this point we have exactly enough data self.next()
[docs] def next(self): """Calculate SMA for the current bar. Recalculates from scratch each bar using sum of the last 'period' values to avoid floating-point drift from incremental updates. """ try: period = self.p.period prices = [float(self.data[i]) for i in range(1 - period, 1)] self.lines.sma[0] = math.fsum(prices) / period except (ValueError, TypeError, IndexError): logger.debug("SMA next() failed", exc_info=True) self.lines.sma[0] = float("nan")
[docs] def once(self, start, end): """Batch calculation for runonce mode.""" try: # If data source is a LinesOperation, ensure its once() is called first if hasattr(self.data, "once") and hasattr(self.data, "operation"): try: self.data.once(start, end) except Exception as e: logger.debug("data.once() failed in SMA: %s", e) dst = self.lines[0].array src = self.data.array period = self.p.period actual_end = min(end, len(src)) # Ensure destination array is large enough, pre-fill with NaN while len(dst) < end: dst.append(float("nan")) # Pre-fill warmup period with NaN for i in range(min(period - 1, len(src))): dst[i] = float("nan") calc_start = max(period - 1, start) fsum = math.fsum # Cache function reference nan_val = float("nan") for i in range(calc_start, actual_end): start_idx = i - period + 1 end_idx = i + 1 if end_idx <= len(src): window = src[start_idx:end_idx] # NaN check: only NaN != NaN (faster than isinstance + isnan) has_nan = False for v in window: if v != v: has_nan = True break if has_nan: dst[i] = nan_val else: dst[i] = fsum(window) / period else: dst[i] = nan_val except Exception: logger.debug("SMA once() failed, falling back to once_via_next", exc_info=True) super().once_via_next(start, end)
SMA = MovingAverageSimple