Source code for backtrader.indicators.contrib.f2a_ao_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 (
    ExponentialMovingAverage,
    Indicator,
)

__all__ = [
    "F2aAOIndicator",
]


[docs] class F2aAOIndicator(Indicator): """F2a_AO arrow indicator from a fast/slow/filter EMA system. Builds fast, slow and filter EMAs of a weighted price and emits a ``buy`` arrow below the bar when the fast-minus-slow spread turns up with filter confirmation, or a ``sell`` arrow above the bar on the bearish turn, using an internal latch so arrows alternate direction. """ lines = ("sell", "buy") params = ( ("ma_filtr", 3), ("ma_fast", 13), ("ma_slow", 144), ) def __init__(self): """Build the fast/slow/filter EMAs and set the minimum period.""" series = ( self.data.close * 5.0 + self.data.open * 2.0 + self.data.high + self.data.low ) / 9.0 self._fast = ExponentialMovingAverage(series, period=max(1, int(self.p.ma_fast))) self._slow = ExponentialMovingAverage(series, period=max(1, int(self.p.ma_slow))) self._filter = ExponentialMovingAverage(series, period=max(1, int(self.p.ma_filtr))) self.addminperiod(max(int(self.p.ma_slow), int(self.p.ma_fast), int(self.p.ma_filtr)) + 20) self._trend = 0
[docs] def next(self): """Compute buy/sell arrows for the current bar (event-driven mode). Detects a confirmed turn in the fast-minus-slow spread and plots a buy arrow below the low or a sell arrow above the high, offset by half the recent average range, toggling the internal trend latch. """ value1_0 = float(self._fast[0]) - float(self._slow[0]) value1_1 = float(self._fast[-1]) - float(self._slow[-1]) value1_2 = float(self._fast[-2]) - float(self._slow[-2]) current = float(self._filter[0]) prev = float(self._filter[-1]) avg_range = 0.0 for count in range(10): avg_range += abs(float(self.data.high[-count]) - float(self.data.low[-count])) range_value = avg_range / 10.0 self.lines.buy[0] = 0.0 self.lines.sell[0] = 0.0 if self._trend <= 0: if value1_0 > value1_1 and current >= prev and value1_1 <= value1_2: self.lines.buy[0] = float(self.data.low[0]) - range_value * 0.5 self._trend = 1 if self._trend >= 0: if value1_0 < value1_1 and current <= prev and value1_1 >= value1_2: self.lines.sell[0] = float(self.data.high[0]) + range_value * 0.5 self._trend = -1
[docs] def once(self, start, end): """Compute buy/sell arrows over a range of bars (vectorized mode). Vectorized equivalent of ``next`` used under ``runonce``: iterates the arrays from ``start`` to ``end``, detecting confirmed spread turns and writing buy/sell arrow values while maintaining the trend latch. Args: start: First bar index to process. end: Stop index (exclusive) for processing. """ fast = self._fast.array slow = self._slow.array filtr = self._filter.array high = self.data.high.array low = self.data.low.array buy = self.lines.buy.array sell = self.lines.sell.array trend = 0 for i in range(start, end): buy[i] = 0.0 sell[i] = 0.0 if i < 12: continue value1_0 = float(fast[i]) - float(slow[i]) value1_1 = float(fast[i - 1]) - float(slow[i - 1]) value1_2 = float(fast[i - 2]) - float(slow[i - 2]) current = float(filtr[i]) prev = float(filtr[i - 1]) if not all(math.isfinite(v) for v in (value1_0, value1_1, value1_2, current, prev)): continue avg_range = 0.0 valid_ranges = 0 for count in range(10): idx = i - count bar_range = abs(float(high[idx]) - float(low[idx])) if math.isfinite(bar_range): avg_range += bar_range valid_ranges += 1 range_value = avg_range / valid_ranges if valid_ranges else 0.0 if trend <= 0 and value1_0 > value1_1 and current >= prev and value1_1 <= value1_2: buy[i] = float(low[i]) - range_value * 0.5 trend = 1 if trend >= 0 and value1_0 < value1_1 and current <= prev and value1_1 >= value1_2: sell[i] = float(high[i]) + range_value * 0.5 trend = -1 self._trend = trend