Source code for backtrader.indicators.deviation

#!/usr/bin/env python
"""Deviation Indicator Module - Standard deviation and mean deviation.

This module provides deviation indicators for measuring data dispersion.

Classes:
    StandardDeviation: Standard deviation indicator (alias: StdDev).
    MeanDeviation: Mean absolute deviation (alias: MeanDev).

Example:
    class MyStrategy(bt.Strategy):
        def __init__(self):
            self.sma = bt.indicators.SMA(self.data.close, period=20)
            self.stddev = bt.indicators.StdDev(self.data.close, period=20)
            self.upper_band = self.sma + 2 * self.stddev
            self.lower_band = self.sma - 2 * self.stddev

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

import math

from . import Indicator, MovAv


[docs] class StandardDeviation(Indicator): """ Calculates the standard deviation of the passed data for a given period Note: - If 2 datas are provided as parameters, the second is considered to be the mean of the first - ``safepow`` (default: False) If this parameter is True, the standard deviation will be calculated as pow (abs(meansq - sqmean), 0.5) to safeguard for possible negative results of ``meansq - sqmean`` caused by the floating point representation. Formula: - meansquared = SimpleMovingAverage(pow (data, 2), period) - squaredmean = pow(SimpleMovingAverage(data, period), 2) - stddev = pow(meansquared - squaredmean, 0.5) # square root See: - http://en.wikipedia.org/wiki/Standard_deviation """ alias = ("StdDev",) lines = ("stddev",) params = ( ("period", 20), ("movav", MovAv.Simple), ("safepow", True), ) def _plotlabel(self): plabels = [self.p.period] plabels += [self.p.movav] * self.p.notdefault("movav") return plabels def __init__(self): """Initialize the Standard Deviation indicator.""" super().__init__() self.addminperiod(self.p.period) self._use_external_mean = len(self.datas) > 1 self._mean_prev = None self._meansq_prev = None def _movav_kind(self): movav = self.p.movav names = {getattr(movav, "__name__", "")} aliases = getattr(movav, "alias", ()) if isinstance(aliases, str): names.add(aliases) else: names.update(aliases) if names.intersection( { "SmoothedMovingAverage", "SMMA", "WilderMA", "MovingAverageSmoothed", "MovingAverageWilder", "ModifiedMovingAverage", "Smoothed", } ): return "smoothed" if names.intersection( {"ExponentialMovingAverage", "EMA", "MovingAverageExponential", "Exponential"} ): return "exponential" return "simple" def _finish(self, meansq, mean): diff = meansq - mean * mean if self.p.safepow: diff = abs(diff) return math.sqrt(max(0.0, diff))
[docs] def next(self): """Calculate standard deviation for the current bar.""" period = self.p.period if len(self) < period: self.lines.stddev[0] = float("nan") return kind = self._movav_kind() values = [float(self.data[i]) for i in range(1 - period, 1)] if any(value != value for value in values): self.lines.stddev[0] = float("nan") return if kind == "smoothed": alpha = 1.0 / period elif kind == "exponential": alpha = 2.0 / (1.0 + period) else: alpha = None if alpha is None or self._meansq_prev is None: meansq = math.fsum(value * value for value in values) / period else: meansq = ( self._meansq_prev * (1.0 - alpha) + float(self.data[0]) * float(self.data[0]) * alpha ) if self._use_external_mean: mean = float(self.data1[0]) elif alpha is None or self._mean_prev is None: mean = math.fsum(values) / period else: mean = self._mean_prev * (1.0 - alpha) + float(self.data[0]) * alpha self._meansq_prev = meansq if not self._use_external_mean: self._mean_prev = mean self.lines.stddev[0] = self._finish(meansq, mean)
[docs] def once(self, start, end): """Calculate standard deviation in runonce mode.""" darray = self.data.array larray = self.lines.stddev.array period = self.p.period actual_end = min(end, len(darray)) nan_val = float("nan") while len(larray) < end: larray.append(nan_val) for i in range(min(period - 1, len(larray), actual_end)): larray[i] = nan_val if self._use_external_mean: if hasattr(self.data1, "once"): self.data1.once(0, end) mean_array = self.data1.array else: mean_array = None kind = self._movav_kind() if kind == "smoothed": alpha = 1.0 / period elif kind == "exponential": alpha = 2.0 / (1.0 + period) else: alpha = None if alpha is None: for i in range(period - 1, actual_end): start_idx = i - period + 1 end_idx = i + 1 window = darray[start_idx:end_idx] if len(window) != period or any(value != value for value in window): larray[i] = nan_val continue meansq = math.fsum(value * value for value in window) / period if mean_array is not None: if i >= len(mean_array) or mean_array[i] != mean_array[i]: larray[i] = nan_val continue mean = mean_array[i] else: mean = math.fsum(window) / period larray[i] = self._finish(meansq, mean) return prev_mean = None prev_meansq = None for i in range(period - 1, actual_end): if i == period - 1: window = darray[0:period] if len(window) != period or any(value != value for value in window): larray[i] = nan_val continue prev_meansq = math.fsum(value * value for value in window) / period if mean_array is None: prev_mean = math.fsum(window) / period else: value = float(darray[i]) if value != value or prev_meansq is None: larray[i] = nan_val continue prev_meansq = prev_meansq * (1.0 - alpha) + value * value * alpha if mean_array is None: if prev_mean is None: larray[i] = nan_val continue prev_mean = prev_mean * (1.0 - alpha) + value * alpha if mean_array is not None: if i >= len(mean_array) or mean_array[i] != mean_array[i]: larray[i] = nan_val continue mean = mean_array[i] else: mean = prev_mean larray[i] = self._finish(prev_meansq, mean)
# Average deviation
[docs] class MeanDeviation(Indicator): """MeanDeviation (alias MeanDev) Calculates the Mean Deviation of the passed data for a given period Note: - If 2 datas are provided as parameters, the second is considered to be the mean of the first Formula: - mean = MovingAverage(data, period) (or provided mean) - absdeviation = abs (data - mean) - meandev = MovingAverage(absdeviation, period) See: - https://en.wikipedia.org/wiki/Average_absolute_deviation """ alias = ("MeanDev",) lines = ("meandev",) params = ( ("period", 20), ("movav", MovAv.Simple), ) def _plotlabel(self): plabels = [self.p.period] plabels += [self.p.movav] * self.p.notdefault("movav") return plabels def __init__(self): """Initialize the Mean Deviation indicator. Creates the mean deviation calculation using either external mean or calculated moving average. """ # CRITICAL: Match master branch behavior # If 2 datas are provided, the 2nd is considered to be the mean of the first if len(self.datas) > 1: mean = self.data1 else: mean = self.p.movav(self.data, period=self.p.period) absdev = abs(self.data - mean) self.lines.meandev = self.p.movav(absdev, period=self.p.period)
StdDev = StandardDeviation MeanDev = MeanDeviation