Source code for backtrader.indicators.contrib.sidus_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__ = [
"SidusIndicator",
]
[docs]
class SidusIndicator(Indicator):
"""Reconstructs Sidus indicator from its MQ5 source.
Uses 4 MAs: FastEMA, SlowEMA, FastLWMA, SlowLWMA + ATR(15).
Buy arrows on: FastLWMA crosses above SlowLWMA, or SlowLWMA crosses above SlowEMA.
Sell arrows on reverse crosses.
Arrow offset = ATR * digit scaling.
"""
lines = ("buy_arrow", "sell_arrow")
params = (
("fast_ema", 18),
("slow_ema", 28),
("fast_lwma", 5),
("slow_lwma", 8),
("digit", 0),
)
def __init__(self):
"""Cache MA periods and digit scaling, and set the indicator warmup."""
self._fe = int(self.p.fast_ema)
self._se = int(self.p.slow_ema)
self._fl = int(self.p.fast_lwma)
self._sl = int(self.p.slow_lwma)
self._digit = float(10 ** int(self.p.digit)) if int(self.p.digit) > 0 else 0.0
self.addminperiod(max(self._fe, self._se, self._fl, self._sl) + 3)
def _ema(self, period, ago):
# Simple EMA approximation using close prices
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 _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 _atr(self, period, ago):
total = 0.0
for i in range(period):
idx = ago + i
h = float(self.data.high[-idx])
low_price = float(self.data.low[-idx])
if idx + 1 < len(self.data):
pc = float(self.data.close[-(idx + 1)])
tr = max(h - low_price, abs(h - pc), abs(low_price - pc))
else:
tr = h - low_price
total += tr
return total / period
[docs]
def next(self):
"""Emit buy/sell arrow offsets when the LWMA/EMA cross conditions fire."""
# Current bar (ago=0) and previous bar (ago=1)
self._ema(self._fe, 0)
slw_ema_0 = self._ema(self._se, 0)
fst_lwma_0 = self._lwma(self._fl, 0)
slw_lwma_0 = self._lwma(self._sl, 0)
fst_lwma_1 = self._lwma(self._fl, 1)
slw_lwma_1 = self._lwma(self._sl, 1)
slw_ema_1 = self._ema(self._se, 1)
atr_val = self._atr(15, 0)
rng = atr_val * 3.0
digit = self._digit
buy_val = 0.0
sell_val = 0.0
# Buy: FastLWMA crosses above SlowLWMA, or SlowLWMA crosses above SlowEMA
if fst_lwma_0 > slw_lwma_0 + digit and fst_lwma_1 <= slw_lwma_1:
buy_val = float(self.data.low[0]) - rng
if slw_lwma_0 > slw_ema_0 + digit and slw_lwma_1 <= slw_ema_1:
buy_val = float(self.data.low[0]) - rng
# Sell: FastLWMA crosses below SlowLWMA, or SlowLWMA crosses below SlowEMA
if fst_lwma_0 < slw_lwma_0 - digit and fst_lwma_1 >= slw_lwma_1:
sell_val = float(self.data.high[0]) + rng
if slw_lwma_0 < slw_ema_0 - digit and slw_lwma_1 >= slw_ema_1:
sell_val = float(self.data.high[0]) + rng
self.lines.buy_arrow[0] = buy_val
self.lines.sell_arrow[0] = sell_val