Source code for backtrader.indicators.contrib.rvi_histogram_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 Indicator

__all__ = [
    "RVIHistogramIndicator",
]


class _RollingWeightedAverage4:
    """Symmetric 4-bar weighted average (weights 1-2-2-1) over a rolling window."""

    def __init__(self):
        """Initialize the rolling value buffer."""
        self.values = []

    def update(self, value):
        """Add a value and return the 4-bar weighted average.

        Args:
            value: The new value to incorporate.

        Returns:
            The weighted average once four values are buffered, else 0.0.
        """
        self.values.append(float(value))
        if len(self.values) > 4:
            self.values.pop(0)
        if len(self.values) < 4:
            return 0.0
        return (self.values[3] + 2.0 * self.values[2] + 2.0 * self.values[1] + self.values[0]) / 6.0


class _RollingSimpleAverage:
    """Simple moving average over a fixed-length rolling window."""

    def __init__(self, period):
        """Initialize the rolling buffer.

        Args:
            period: Window length (clamped to at least 1).
        """
        self.period = max(int(period), 1)
        self.values = []

    def update(self, value):
        """Add a value and return the current simple moving average.

        Args:
            value: The new value to incorporate.

        Returns:
            The simple moving average of the buffered values.
        """
        self.values.append(float(value))
        if len(self.values) > self.period:
            self.values.pop(0)
        if not self.values:
            return 0.0
        return sum(self.values) / len(self.values)


[docs] class RVIHistogramIndicator(Indicator): """Relative Vigor Index with a smoothed signal line and color state.""" lines = ("main", "signal", "hist_base", "color_state") params = ( ("rvi_period", 14), ("high_level", 0.3), ("low_level", -0.3), ) def __init__(self): """Build the weighted/simple averagers used by the RVI computation.""" self._co_avg4 = _RollingWeightedAverage4() self._hl_avg4 = _RollingWeightedAverage4() self._num_sma = _RollingSimpleAverage(self.p.rvi_period) self._den_sma = _RollingSimpleAverage(self.p.rvi_period) self._main_avg4 = _RollingWeightedAverage4() self.addminperiod(int(self.p.rvi_period) + 6)
[docs] def next(self): """Compute the RVI main/signal lines and the level-based color state.""" co = float(self.data.close[0]) - float(self.data.open[0]) hl = float(self.data.high[0]) - float(self.data.low[0]) weighted_co = self._co_avg4.update(co) weighted_hl = self._hl_avg4.update(hl) num = self._num_sma.update(weighted_co) den = self._den_sma.update(weighted_hl) main = num / den if abs(den) > 1e-12 else 0.0 signal = self._main_avg4.update(main) color = 1.0 if main > float(self.p.high_level): color = 0.0 elif main < float(self.p.low_level): color = 2.0 self.lines.main[0] = main self.lines.signal[0] = signal self.lines.hist_base[0] = 0.0 self.lines.color_state[0] = color