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...