[Python] How to preserve state across bars in @sec_context in TakeProfit's Indie?

Discussão em 'Python' iniciado por Stack, Abril 28, 2025.

  1. Stack

    Stack Membro Participativo

    I'm trying to port an indicator from Pine Script to TakeProfit's Indie V5. I use @sec_context to get higher-timeframe values, similar to request.security() in Pine.

    In PineScript, I can use var to keep a persistent value between HTF bars. In Indie, I want to do the same — preserve MutSeriesF values like up1_htf, dn1_htf, etc., across HTF bars.

    What I tried:

    I created a secondary context class like this:

    from indie import SecContext, MutSeriesF

    class AndeanHTFContext(SecContext):
    def __init__(self):
    self.up1_htf = self.new_mut_series_f(self.close[0])
    self.dn1_htf = self.new_mut_series_f(self.close[0])
    self.up2_htf = self.new_mut_series_f(self.close[0] ** 2)
    self.dn2_htf = self.new_mut_series_f(self.close[0] ** 2)

    def calc(self, length: int, atrLength: int, volumeLength: int):
    # update state with new values
    ...
    return bull, bear


    Then in my main class:

    @indicator(...)
    @param.time_frame('htf', default='60m')
    class Main(MainContext):
    def __init__(self, htf):
    self.bull_htf, self.bear_htf = self.calc_on(AndeanHTFContext, time_frame=htf)


    The problem: Indie gives this error:


    Error: 12:23 declare ctx parameter of __init__ method of secondary context as an indicator input parameter by applying the corresponding @indie.param decorator to Main
    ​

    But SecContext doesn’t seem to allow a ctx or any input to __init__ — it's supposed to be empty.

    If I try to pass a Context object manually, or use self in __init__, it still errors out.

    What I want" A pattern to initialize persistent series once, and use them in @sec_context, just like Pine's var inside request.security().

    How do I keep MutSeriesF values between HTF bars and avoid resetting them on every HTF calc call?

    Here is a full indicator code:

    # indie:lang_version = 5
    from math import sqrt, isnan, nan
    from indie import (
    indicator, param, color, plot, SecContext,
    MainContext, MutSeriesF, TimeFrame, sec_context, param_ref, Context
    )
    from indie.algorithms import Atr, Sma, Ema
    from indie.plot import marker_style, marker_position


    class AndeanHTFContext(SecContext):
    def __init__(self, ctx: Context):
    self.ctx = ctx
    self.up1_htf = self.new_mut_series_f(ctx.close[0])
    self.dn1_htf = self.new_mut_series_f(ctx.close[0])
    self.up2_htf = self.new_mut_series_f(ctx.close[0] ** 2)
    self.dn2_htf = self.new_mut_series_f(ctx.close[0] ** 2)

    def calc(self, length: int, atrLength: int, volumeLength: int) -> tuple[float, float]:
    atr_series = Atr.new(atrLength)
    atr_now = atr_series[0]
    atr_ref = Sma.new(atr_series, atrLength)[0]
    alpha = 2 / (length + 1)
    alpha = max(min(alpha * (atr_now / atr_ref), 1), 0.001)

    vol = self.ctx.volume[0]
    avgVol = Sma.new(self.ctx.volume, volumeLength)[0]
    volFactor = vol / avgVol if avgVol != 0 else 1.0

    c = self.ctx.close[0]
    o = self.ctx.open[0]
    c2 = c * c
    o2 = o * o

    self.up1_htf[0] = max(c, o, self.up1_htf[1] - alpha * (self.up1_htf[1] - c)) if not isnan(self.up1_htf[1]) else c
    self.dn1_htf[0] = min(c, o, self.dn1_htf[1] + alpha * (c - self.dn1_htf[1])) if not isnan(self.dn1_htf[1]) else c
    self.up2_htf[0] = max(c2, o2, self.up2_htf[1] - alpha * (self.up2_htf[1] - c2)) if not isnan(self.up2_htf[1]) else c2
    self.dn2_htf[0] = min(c2, o2, self.dn2_htf[1] + alpha * (c2 - self.dn2_htf[1])) if not isnan(self.dn2_htf[1]) else c2

    bull = sqrt(max(self.dn2_htf[0] - self.dn1_htf[0] * self.dn1_htf[0], 0)) * volFactor
    bear = sqrt(max(self.up2_htf[0] - self.up1_htf[0] * self.up1_htf[0], 0)) * volFactor
    return bull, bear

    @sec_context
    @param_ref('length')
    @param_ref('atrLength')
    @param_ref('volumeLength')
    def AndeanHTF(self, length, atrLength, volumeLength):
    if self.bar_index == 0:
    self._htf_initialized = self.new_mut_series_f(0.0)
    self._htf_ctx = AndeanHTFContext(self)
    self._htf_initialized[0] = 1.0
    return self._htf_ctx.calc(length, atrLength, volumeLength)

    @indicator("Enhanced Andean Oscillator 2025", overlay_main_pane=False)

    @param.int('length', default=25, min=1, title='Base Length')
    @param.int('signalLength', default=9, min=1, title='Signal Smoothing Length')
    @param.str('htf', default='60m', title='Higher Timeframe')
    @param.float('deadZone', default=0.05, min=0.0, title='Dead Zone Threshold')
    @param.int('atrLength', default=14, min=1, title='ATR Length')
    @param.int('volumeLength', default=20, min=1, title='Volume Lookback')
    @param.int('slopeLookback', default=2, min=1, title='Slope Confirmation Bars')
    @plot.line(title='Bullish', color=color.GREEN, line_width=2)
    @plot.line(title='Bearish', color=color.RED, line_width=2)
    @plot.line(title='Signal', color=color.YELLOW)
    @plot.marker(title='Long Entry', color=color.GREEN, style=marker_style.LABEL, position=marker_position.ABOVE, size=5)
    @plot.marker(title='Short Entry', color=color.RED, style=marker_style.LABEL, position=marker_position.BELOW, size=5)
    @plot.marker(title='Long Exit', color=color.GREEN, style=marker_style.CROSS, position=marker_position.ABOVE, size=5)
    @plot.marker(title='Short Exit', color=color.RED, style=marker_style.CROSS, position=marker_position.BELOW, size=5)

    class Main(MainContext):
    def __init__(self, htf):
    self.htf = TimeFrame.from_str(htf)
    self.bull_htf, self.bear_htf = self.calc_on(AndeanHTF, time_frame=self.htf)

    self.up1 = self.new_mut_series_f(0.0)
    self.dn1 = self.new_mut_series_f(0.0)
    self.up2 = self.new_mut_series_f(0.0)
    self.dn2 = self.new_mut_series_f(0.0)
    self.bullSeries = self.new_mut_series_f(0.0)
    self.bearSeries = self.new_mut_series_f(0.0)
    self.inLong = self.new_mut_series_f(0.0)
    self.inShort = self.new_mut_series_f(0.0)

    def calc(self, length, signalLength, htf, deadZone, atrLength, volumeLength, slopeLookback):
    atr_series = Atr.new(atrLength)
    atr_now = atr_series[0]
    atr_ref = Sma.new(atr_series, atrLength)[0]
    alpha = 2 / (length + 1)
    alpha = max(min(alpha * (atr_now / atr_ref), 1), 0.001)

    vol = self.volume[0]
    avgVol = Sma.new(self.volume, volumeLength)[0]
    volFactor = vol / avgVol if avgVol != 0 else 1.0

    c = self.close[0]
    o = self.open[0]
    c2 = c * c
    o2 = o * o

    self.up1[0] = max(c, o, self.up1[1] - alpha * (self.up1[1] - c)) if not isnan(self.up1[1]) else c
    self.dn1[0] = min(c, o, self.dn1[1] + alpha * (c - self.dn1[1])) if not isnan(self.dn1[1]) else c
    self.up2[0] = max(c2, o2, self.up2[1] - alpha * (self.up2[1] - c2)) if not isnan(self.up2[1]) else c2
    self.dn2[0] = min(c2, o2, self.dn2[1] + alpha * (c2 - self.dn2[1])) if not isnan(self.dn2[1]) else c2

    bull = sqrt(max(self.dn2[0] - self.dn1[0] * self.dn1[0], 0)) * volFactor
    bear = sqrt(max(self.up2[0] - self.up1[0] * self.up1[0], 0)) * volFactor

    self.bullSeries[0] = bull
    self.bearSeries[0] = bear

    signal1 = Ema.new(MutSeriesF.new(max(bull, bear)), signalLength)[0]
    signal = Ema.new(MutSeriesF.new(signal1), signalLength)[0]

    diff = bull - bear
    inDeadZone = abs(diff) < deadZone
    slopeBull = bull - self.bullSeries[slopeLookback]
    slopeBear = bear - self.bearSeries[slopeLookback]
    validSlope = slopeBull > 0 and slopeBear < 0
    htf_ok = self.bull_htf[0] > self.bear_htf[0]

    longCond = bull > bear + deadZone and not inDeadZone and validSlope and htf_ok
    shortCond = bear > bull + deadZone and not inDeadZone and slopeBear > 0 and slopeBull < 0 and not htf_ok

    exitLong = bull < signal
    exitShort = bear < signal

    self.inLong[0] = 1.0 if longCond else (0.0 if exitLong and self.inLong[1] > 0 else self.inLong[1])
    self.inShort[0] = 1.0 if shortCond else (0.0 if exitShort and self.inShort[1] > 0 else self.inShort[1])

    longEntryMarker = plot.Marker(bull, text="▲") if longCond else plot.Marker(nan)
    shortEntryMarker = plot.Marker(bear, text="▼") if shortCond else plot.Marker(nan)
    longExitMarker = plot.Marker(bull) if self.inLong[1] > 0 and exitLong else plot.Marker(nan)
    shortExitMarker = plot.Marker(bear) if self.inShort[1] > 0 and exitShort else plot.Marker(nan)

    return bull, bear, signal, longEntryMarker, shortEntryMarker, longExitMarker, shortExitMarker

    Continue reading...

Compartilhe esta Página