Source code for backtrader.indicators.contrib.xma_range_bands_indicator

#!/usr/bin/env python
"""Functional-test indicators migrated to contrib.

Generated from a single functional strategy module to preserve file-local
helper functions and constants without cross-test name collisions.
"""

from .. import (
    EMA,
    SMA,
    Indicator,
    SmoothedMovingAverage,
    WeightedMovingAverage,
)

__all__ = [
    "XMARangeBandsIndicator",
]


def resolve_ma_class(name):
    """Map a moving-average name to its backtrader indicator class.

    Args:
        name: Moving-average mode name (e.g. ``'sma'``, ``'ema'``, ``'smma'``).

    Returns:
        The matching backtrader moving-average indicator class, defaulting to
        the weighted moving average for unrecognized names.
    """
    mode = str(name).lower()
    if mode in {"sma", "mode_sma"}:
        return SMA
    if mode in {"ema", "mode_ema"}:
        return EMA
    if mode in {"smma", "mode_smma"}:
        return SmoothedMovingAverage
    return WeightedMovingAverage


def resolve_price_line(data, mode):
    """Map an applied-price mode to a price line built from the data feed.

    Args:
        data: The data feed providing open/high/low/close lines.
        mode: Applied-price mode name (e.g. ``'price_close'``, ``'median'``,
            ``'typical'``, ``'weighted'``).

    Returns:
        A line expression for the requested applied price, defaulting to close.
    """
    price_mode = str(mode).lower()
    if price_mode in {"price_open", "open"}:
        return data.open
    if price_mode in {"price_high", "high"}:
        return data.high
    if price_mode in {"price_low", "low"}:
        return data.low
    if price_mode in {"price_median", "median"}:
        return (data.high + data.low) / 2.0
    if price_mode in {"price_typical", "typical"}:
        return (data.high + data.low + data.close) / 3.0
    if price_mode in {"price_weighted", "weighted"}:
        return (data.high + data.low + data.close + data.close) / 4.0
    if price_mode in {"price_simpl", "simpl"}:
        return (data.open + data.close) / 2.0
    if price_mode in {"price_quarter", "quarter"}:
        return (data.high + data.low + data.open + data.close) / 4.0
    if price_mode in {"price_trendfollow0", "trendfollow0"}:
        return (data.high + data.low + data.close + data.close) / 4.0
    if price_mode in {"price_trendfollow1", "trendfollow1"}:
        return (data.high + data.low + data.open + data.close + data.close) / 5.0
    return data.close


[docs] class XMARangeBandsIndicator(Indicator): """Moving-average midline with range-scaled upper and lower bands.""" lines = ( "mid", "upper", "lower", ) params = ( ("ma_method1", "sma"), ("length1", 100), ("phase1", 15), ("ma_method2", "jjma"), ("length2", 20), ("phase2", 100), ("deviation", 2.0), ("ipc", "price_close"), ("price_shift", 0), ) def __init__(self): """Build the midline MA and range MA, then derive the band lines.""" price_line = resolve_price_line(self.data, self.p.ipc) ma_cls_1 = resolve_ma_class(self.p.ma_method1) ma_cls_2 = resolve_ma_class(self.p.ma_method2) self._base = ma_cls_1(price_line, period=self.p.length1) bar_range = self.data.high - self.data.low self._range_ma = ma_cls_2(bar_range, period=self.p.length2) self.lines.mid = self._base + self.p.price_shift self.lines.upper = self.lines.mid + self._range_ma * self.p.deviation self.lines.lower = self.lines.mid - self._range_ma * self.p.deviation self.addminperiod(max(self.p.length1, self.p.length2) + 3)