Source code for backtrader.indicators.contrib.stalin_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__ = [
    "StalinIndicator",
]


[docs] class StalinIndicator(Indicator): """Reconstructs Stalin indicator from its MQ5 source. Uses Fast/Slow MA crossover with optional RSI filter. BU() fires buy arrow at low if flat distance check passes. BD() fires sell arrow at high if flat distance check passes. Optional Confirm parameter adds a price-distance confirmation step. """ lines = ("buy_arrow", "sell_arrow") params = ( ("ma_method", "ema"), ("fast", 14), ("slow", 21), ("rsi_period", 17), ("confirm", 0), ("flat", 0), ("point", 0.0001), ) def __init__(self): """Prepare cached MA/RSI parameters and state for BU/BD arrow generation.""" self._fast = int(self.p.fast) self._slow = int(self.p.slow) self._rsi = int(self.p.rsi_period) self._confirm2 = float(self.p.confirm) * float(self.p.point) self._flat2 = float(self.p.flat) * float(self.p.point) self._e1 = 0.0 # last buy arrow price self._e2 = 0.0 # last sell arrow price self._iup = 0.0 # pending buy confirmation price self._idn = 0.0 # pending sell confirmation price self.addminperiod(max(self._fast, self._slow, self._rsi if self._rsi > 0 else 1) + 3) def _calc_ema(self, period, ago): k = 2.0 / (period + 1) val = float(self.data.close[-(ago + period - 1)]) for i in range(ago + period - 2, ago - 1, -1): val = float(self.data.close[-i]) * k + val * (1 - k) if i >= 0 else val return val def _calc_lwma(self, period, ago): total = 0.0 wsum = 0.0 for i in range(period): w = float(period - i) total += float(self.data.close[-(ago + i)]) * w wsum += w return total / wsum if wsum > 0 else 0.0 def _calc_sma(self, period, ago): total = 0.0 for i in range(period): total += float(self.data.close[-(ago + i)]) return total / period def _calc_ma(self, period, ago): method = str(self.p.ma_method).lower() if method == "lwma": return self._calc_lwma(period, ago) if method == "sma": return self._calc_sma(period, ago) return self._calc_ema(period, ago) def _calc_rsi(self, period, ago): gains = 0.0 losses = 0.0 for i in range(period): idx = ago + i c = float(self.data.close[-idx]) cp = float(self.data.close[-(idx + 1)]) diff = c - cp if diff > 0: gains += diff else: losses -= diff avg_gain = gains / period avg_loss = losses / period if avg_loss == 0: return 100.0 rs = avg_gain / avg_loss return 100.0 - (100.0 / (1.0 + rs))
[docs] def next(self): """Compute MA crossover and optional RSI filters, then emit buy/sell arrows.""" fast_ma_0 = self._calc_ma(self._fast, 0) slow_ma_0 = self._calc_ma(self._slow, 0) fast_ma_1 = self._calc_ma(self._fast, 1) slow_ma_1 = self._calc_ma(self._slow, 1) use_rsi = self._rsi > 0 rsi_val = self._calc_rsi(self._rsi, 0) if use_rsi else 50.0 buy_val = 0.0 sell_val = 0.0 cur_low = float(self.data.low[0]) cur_high = float(self.data.high[0]) cur_close = float(self.data.close[0]) flat2 = self._flat2 confirm2 = self._confirm2 # MA crossover buy signal if (not use_rsi) or (fast_ma_1 < slow_ma_1 and fast_ma_0 > slow_ma_0 and rsi_val > 50): if not confirm2: # BU: fire buy arrow if flat distance passes if cur_low >= (self._e1 + flat2) or cur_low <= (self._e1 - flat2): buy_val = cur_low self._e1 = cur_low else: self._iup = cur_low self._idn = 0.0 # MA crossover sell signal if (not use_rsi) or (fast_ma_1 > slow_ma_1 and fast_ma_0 < slow_ma_0 and rsi_val < 50): if not confirm2: if cur_high >= (self._e2 + flat2) or cur_high <= (self._e2 - flat2): sell_val = cur_high self._e2 = cur_high else: self._idn = cur_high self._iup = 0.0 # Confirm pending buy if self._iup and cur_high - self._iup >= confirm2 and cur_close <= cur_high: if cur_low >= (self._e1 + flat2) or cur_low <= (self._e1 - flat2): buy_val = cur_low self._e1 = cur_low self._iup = 0.0 # Confirm pending sell if self._idn and self._idn - cur_low >= confirm2 and float(self.data.open[0]) >= cur_close: if cur_high >= (self._e2 + flat2) or cur_high <= (self._e2 - flat2): sell_val = cur_high self._e2 = cur_high self._idn = 0.0 self.lines.buy_arrow[0] = buy_val self.lines.sell_arrow[0] = sell_val