[Python] How to improve performance of gradient-style oscillator [closed]

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

  1. Stack

    Stack Membro Participativo

    I am creating an oscillator with gradient-style zone lines. It works perfect visually and gives useful signals, but unfortunately, performance is some-times poor when applied to long timeframes or lots of symbols — sometimes even crashing the chart.

    I’ve already optimized the indicator by reducing the number of zone lines from 10 to 6, but it's still not light enough.

    Here’s what the indicator does:

    • Calculates a VWAP-based oscillator with optional mean reversion or volume normalization
    • Applies smoothing with EMA
    • Computes adaptive midline and standard deviation bands
    • Draws 6 gradient zone lines above and below the midline, with increasing opacity (like heat levels)

    I'm looking for optimization suggestions that preserve the gradient design and dynamic logic, but reduce the computational load. Ideas I'm considering:

    • Is the standard deviation and midline recalculation too heavy per candle?
    • Could any of the EMA/SMA/StdDev calls be cached or reused?
    • Is the manual VWAP loop a performance bottleneck in Indie?
    • Are there alternative lightweight ways to simulate fading zone bands?

    I'm not an expert in TakeProfit platform and Indie yet, so maybe I'm missing some native tricks or better ways to handle repetitive drawing logic.

    Full code:

    # indie:lang_version = 5
    from math import nan, isclose
    from indie import indicator, param, plot, color, MutSeriesF, source, MainContext
    from indie.algorithms import Sma, Ema, StdDev, Lowest, Highest

    # === Indicator Declaration & Inputs ===
    @indicator("Dynamic Volume Profile Oscillator lite", overlay_main_pane=False)
    @param.source("price_src", default=source.CLOSE, title="Price Source")
    @param.int("lookback", default=50, min=10, title="Lookback Period")
    @param.int("smoothing", default=5, min=1, max=50, title="Smoothing Length")
    @param.float("sensitivity", default=1.0, min=0.1, max=5.0, step=0.1, title="Sensitivity")
    @param.bool("mean_reversion", default=True, title="Mean Reversion Mode")
    @param.bool("use_adaptive_midline", default=True, title="Use Adaptive Midline")
    @param.int("midline_period", default=50, min=10, max=200, title="Adaptive Midline Period")
    @param.float("zone_width", default=1.5, min=0.5, max=3.0, step=0.1, title="Zone Width Multiplier")

    # Plot base + zones with 6-level gradient
    @plot.line('osc', color=color.AQUA, title="Oscillator")
    @plot.line('fast_signal', color=color.FUCHSIA, title="Fast Signal")
    @plot.line('slow_signal', color=color.SILVER, title="Slow Signal")
    @plot.line('midline', color=color.WHITE(0.4), title="Adaptive Midline")

    @plot.line('upper_zone1', color=color.FUCHSIA(0.15), title="Upper Zone 1")
    @plot.line('upper_zone2', color=color.FUCHSIA(0.25), title="Upper Zone 2")
    @plot.line('upper_zone3', color=color.FUCHSIA(0.5), title="Upper Zone 3")
    @plot.line('upper_zone4', color=color.FUCHSIA(0.65), title="Upper Zone 4")
    @plot.line('upper_zone5', color=color.FUCHSIA(0.85), title="Upper Zone 5")
    @plot.line('upper_zone6', color=color.FUCHSIA(1.0), title="Upper Zone 6")

    @plot.line('lower_zone1', color=color.AQUA(0.15), title="Lower Zone 1")
    @plot.line('lower_zone2', color=color.AQUA(0.25), title="Lower Zone 2")
    @plot.line('lower_zone3', color=color.AQUA(0.5), title="Lower Zone 3")
    @plot.line('lower_zone4', color=color.AQUA(0.65), title="Lower Zone 4")
    @plot.line('lower_zone5', color=color.AQUA(0.85), title="Lower Zone 5")
    @plot.line('lower_zone6', color=color.AQUA(1.0), title="Lower Zone 6")

    class Main(MainContext):
    def calc(self, price_src, lookback, smoothing, sensitivity, mean_reversion,
    use_adaptive_midline, midline_period, zone_width):

    vwap_sum = 0.0
    volume_sum = 0.0
    for i in range(lookback):
    vwap_sum += price_src * self.volume
    volume_sum += self.volume
    vwap_level = vwap_sum / volume_sum if not isclose(volume_sum, 0) else price_src[0]

    price_dev = 0.0
    for i in range(lookback):
    price_dev += abs(price_src - vwap_level) * (self.volume / volume_sum) if not isclose(volume_sum, 0) else 0

    osc_raw = 50.0
    if mean_reversion:
    if not isclose(price_dev * sensitivity, 0):
    osc_raw = 50 + ((price_src[0] - vwap_level) / (price_dev * sensitivity)) * 25
    else:
    sma_vol = Sma.new(self.volume, smoothing)
    min_vol = Lowest.new(sma_vol, lookback)[0]
    max_vol = Highest.new(sma_vol, lookback)[0]
    vol_range = max_vol - min_vol
    if not isclose(vol_range, 0):
    vol_position = (sma_vol[0] - min_vol) / vol_range
    osc_raw = min(100, max(0, vol_position * 100))

    osc_series = MutSeriesF.new(osc_raw)
    osc = Ema.new(osc_series, smoothing)[0]
    fast_signal = Ema.new(osc_series, 5)[0]
    slow_signal = Ema.new(osc_series, 15)[0]

    midline_series = MutSeriesF.new(osc)
    midline = Sma.new(midline_series, midline_period)[0] if use_adaptive_midline else 50.0
    stdev = StdDev.new(midline_series, midline_period)[0] * zone_width

    # Build zone levels
    upper_zones: list[float] = [0.0] * 6
    lower_zones: list[float] = [0.0] * 6
    for i in range(6):
    scale = 1 - i * 0.15
    upper_zones = midline + stdev * scale
    lower_zones = midline - stdev * scale

    return (
    osc,
    fast_signal,
    slow_signal,
    midline,
    upper_zones[0], upper_zones[1], upper_zones[2],
    upper_zones[3], upper_zones[4], upper_zones[5],
    lower_zones[0], lower_zones[1], lower_zones[2],
    lower_zones[3], lower_zones[4], lower_zones[5],
    )

    Continue reading...

Compartilhe esta Página