Source code for backtrader.indicators.contrib.bezier_st_dev_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.
"""

import math

from .. import Indicator

__all__ = [
    "BezierStDevIndicator",
]


def _factorial(n):
    r = 1
    for i in range(2, n + 1):
        r *= i
    return r


def _price_series(ipc, data, ago=0):
    """Replicate MQ5 PriceSeries with Applied_price_ enum."""
    o = float(data.open[-ago])
    h = float(data.high[-ago])
    low_price = float(data.low[-ago])
    c = float(data.close[-ago])
    if ipc == 0:
        return c  # PRICE_CLOSE_
    if ipc == 1:
        return o  # PRICE_OPEN_
    if ipc == 2:
        return h  # PRICE_HIGH_
    if ipc == 3:
        return low_price  # PRICE_LOW_
    if ipc == 4:
        return (h + low_price) / 2.0  # PRICE_MEDIAN_
    if ipc == 5:
        return (h + low_price + c) / 3.0  # PRICE_TYPICAL_
    if ipc == 6:
        return (h + low_price + c + c) / 4.0  # PRICE_WEIGHTED_
    return c


[docs] class BezierStDevIndicator(Indicator): """Reconstructs Bezier_StDev indicator. Bezier curve interpolation of price over BPeriod, then StDev filter on the first derivative to generate Bulls/Bears signals. Buffers: 0=BezierLine, 1=ColorIndex, 2=BearsBuffer(sell), 3=BullsBuffer(buy). """ lines = ("bezier", "color", "bears", "bulls") params = ( ("bperiod", 8), ("t_param", 0.5), ("ipc", 6), ("dk", 2.0), ("std_period", 9), ) def __init__(self): """Cache parameters and precompute coefficients for Bezier interpolation.""" self._bp = int(self.p.bperiod) self._t = float(self.p.t_param) self._ipc = int(self.p.ipc) self._dk = float(self.p.dk) self._sp = int(self.p.std_period) # Precompute binomial coefficients n = self._bp self._binom = [_factorial(n) / (_factorial(i) * _factorial(n - i)) for i in range(n + 1)] self.addminperiod(self._bp + self._sp + 3)
[docs] def next(self): """Compute Bezier line, color, and bullish/bearish derivative filters.""" bp = self._bp t = self._t ipc = self._ipc dk = self._dk sp = self._sp # Compute Bezier for current and previous sp+1 bars bezier_vals = [] needed = sp + 2 for k in range(needed): r = 0.0 for i in range(bp + 1): ago = k + i if ago >= len(self.data): break price = _price_series(ipc, self.data, ago) r += price * self._binom[i] * (t**i) * ((1 - t) ** (bp - i)) bezier_vals.append(r) bz_cur = bezier_vals[0] self.lines.bezier[0] = bz_cur # Color if len(bezier_vals) > 1: bz_prev = bezier_vals[1] if bz_cur > bz_prev: self.lines.color[0] = 1.0 elif bz_cur < bz_prev: self.lines.color[0] = 2.0 else: self.lines.color[0] = 0.0 else: self.lines.color[0] = 0.0 # StDev filter on derivatives d_bezier = [] for i in range(sp): if i + 1 < len(bezier_vals): d_bezier.append(bezier_vals[i] - bezier_vals[i + 1]) else: d_bezier.append(0.0) mean_d = sum(d_bezier) / sp if sp > 0 else 0 var_sum = sum((d - mean_d) ** 2 for d in d_bezier) std_dev = math.sqrt(var_sum / sp) if sp > 0 else 0 dstd = d_bezier[0] if d_bezier else 0 filt = dk * std_dev bulls = 0.0 bears = 0.0 if dstd > filt: bulls = bz_cur if dstd < -filt: bears = bz_cur self.lines.bulls[0] = bulls self.lines.bears[0] = bears