Source code for backtrader.indicators.contrib.as_ctrend_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__ = [
"ASCtrendIndicator",
]
def _wpr(highs, lows, close, period):
"""Return Williams %R for the last bar given arrays of length >= period."""
hh = max(highs[-period:])
ll = min(lows[-period:])
if hh == ll:
return 0.0
return -100.0 * (hh - close) / (hh - ll)
[docs]
class ASCtrendIndicator(Indicator):
"""Reconstructs ASCtrend from its MQ5 source.
Outputs:
- buy_arrow : non-zero price level when a buy arrow fires
- sell_arrow: non-zero price level when a sell arrow fires
"""
lines = ("buy_arrow", "sell_arrow")
params = (("risk", 4),)
def __init__(self):
"""Derive %R thresholds/periods from ``risk`` and reserve warm-up bars."""
self._x1 = 67 + int(self.p.risk)
self._x2 = 33 - int(self.p.risk)
self._wpr_periods = [3, 4, 3 + int(self.p.risk) * 2]
self._value10 = 2 # default WPR index
min_period = max(3 + int(self.p.risk) * 2, 4) + 1
# need enough history for ATR-style range calc (10 bars) + WPR look-back
self.addminperiod(max(min_period, 12))
# -- helpers operating on self.data (signal-timeframe feed) --
def _get_wpr_val(self, period_idx, ago):
"""Compute WPR(period) at bar shifted by -ago from current."""
period = self._wpr_periods[period_idx]
n = len(self.data)
idx = n - 1 - ago
if idx < period:
return 0.0
highs = [float(self.data.high.array[i]) for i in range(idx - period + 1, idx + 1)]
lows = [float(self.data.low.array[i]) for i in range(idx - period + 1, idx + 1)]
close_val = float(self.data.close.array[idx])
return _wpr(highs, lows, close_val, period)
[docs]
def next(self):
"""Emit ASCtrend buy/sell arrows from the %R band transitions per bar."""
risk = int(self.p.risk)
x1 = self._x1
x2 = self._x2
# --- ATR-style average range (10 bars) ---
total_range = 0.0
for i in range(1, 11):
hi = float(self.data.high[-i])
lo = float(self.data.low[-i])
prev_close = float(self.data.close[-(i + 1)]) if len(self.data) > i + 1 else lo
true_range = max(hi - lo, abs(hi - prev_close), abs(prev_close - lo))
total_range += true_range
avg_range = total_range / 10.0
half_range = avg_range * 0.5
# --- MRO1 / MRO2: look back for WPR threshold breach ---
value10 = self._value10
value11 = value10
# MRO1: check if WPR(3) crossed > x1 recently
mro1 = -1
for k in range(1, risk * 2 + 1):
if len(self.data) <= k:
break
w = 100.0 - abs(self._get_wpr_val(0, k)) # WPR_Handle[0] period=3
if w > x1:
mro1 = k
break
# MRO2: check if WPR(4) crossed < x2 recently
mro2 = -1
for k in range(1, risk * 2 + 1):
if len(self.data) <= k:
break
w = 100.0 - abs(self._get_wpr_val(1, k)) # WPR_Handle[1] period=4
if w < x2:
mro2 = k
break
if mro1 > -1:
value11 = 0
else:
value11 = value10
if mro2 > -1:
value11 = 1
else:
value11 = value10
# Current WPR value with the selected period
wpr_raw = self._get_wpr_val(value11, 0)
value2 = 100.0 - abs(wpr_raw)
buy_val = 0.0
sell_val = 0.0
cur_high = float(self.data.high[0])
cur_low = float(self.data.low[0])
if value2 < x2:
# look back for transition from neutral zone to >x1
iii = 1
vel = 0.0
while len(self.data) > iii:
vel = 100.0 - abs(self._get_wpr_val(value11, iii))
if x2 <= vel <= x1:
iii += 1
else:
break
if vel > x1:
sell_val = cur_high + half_range
if value2 > x1:
iii = 1
vel = 0.0
while len(self.data) > iii:
vel = 100.0 - abs(self._get_wpr_val(value11, iii))
if x2 <= vel <= x1:
iii += 1
else:
break
if vel < x2:
buy_val = cur_low - half_range
self.lines.buy_arrow[0] = buy_val
self.lines.sell_arrow[0] = sell_val