Skip to content

Python: register_indicator fails for SimpleMovingAverage with TradeBarConsolidator — 'Indicator.value must be implemented' #9526

@AlexCatarino

Description

@AlexCatarino

Python: register_indicator fails for SimpleMovingAverage with TradeBarConsolidator — "Indicator.value must be implemented"

Summary

Calling register_indicator(symbol, SimpleMovingAverage(n), TradeBarConsolidator) in a Python algorithm now throws a NotImplementedException during backtest initialization. The same code succeeds when a live deployment was launched ~3 weeks ago, but new backtests on an identical code path fail today. This appears to be a recent regression in the backtest engine.

Error

During the algorithm initialization, the following exception has occurred:
Indicator.value must be implemented. Please implement this missing method in SimpleMovingAverage
  at initialize
    self.register_indicator(self.es, ind, self.cons_5m)
 in main.py: line 100

Reproduction (minimal)

from AlgorithmImports import *
from datetime import timedelta

class TestAlgo(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2026, 1, 1)
        self.set_end_date(2026, 6, 1)
        self.set_cash(100_000)
        future = self.add_future(Futures.Indices.SP_500_E_MINI, resolution=Resolution.MINUTE)
        future.set_filter(0, 90)
        es = future.symbol
        cons_5m = TradeBarConsolidator(timedelta(minutes=5))
        cons_5m.data_consolidated += self.on_5min_bar
        self.subscription_manager.add_consolidator(es, cons_5m)
        sma = SimpleMovingAverage(20)
        self.register_indicator(es, sma, cons_5m)  # <-- crashes here

    def on_5min_bar(self, sender, bar):
        pass

Root cause analysis

QCAlgorithm.Python.csConvertPythonIndicator() is supposed to TryConvert<IndicatorBase> the PyObject:

private IndicatorBase ConvertPythonIndicator(PyObject pyIndicator)
{
    IndicatorBase convertedIndicator;
    if (pyIndicator.TryConvert(out PythonIndicator pythonIndicator))
    {
        convertedIndicator = WrapPythonIndicator(pyIndicator, pythonIndicator);
    }
    else if (!pyIndicator.TryConvert(out convertedIndicator))
    {
        convertedIndicator = WrapPythonIndicator(pyIndicator);  // reached here for SMA
    }
    return convertedIndicator;
}

If TryConvert<IndicatorBase> now fails for SimpleMovingAverage, execution falls through to WrapPythonIndicator(pyIndicator)new PythonIndicator(pyObject)SetIndicator().

SetIndicator() checks for IsReady, Update, Value attributes. IndicatorBase has no direct Value property — only Current (which has Value). So HasAttr("Value") || HasAttr("value") returns false and throws NotImplementedException.

The name "SimpleMovingAverage" in the error (not "SMA(20)") confirms GetIndicatorName fell back to __class__.__name__, meaning the C# Name property was not reachable via the Python bindings at the time SetIndicator was called.

Notably, ExponentialMovingAverage and RelativeStrengthIndex registered earlier in the same loop succeed — the regression appears to be specific to SimpleMovingAverage (or possibly WindowIndicator<IndicatorDataPoint> subclasses).

Evidence of regression (not customer code bug)

  • A live deployment (L-21a7579fdc93f6cde6b4360fa9b91617) launched 2026-05-25 with an identical register_indicator(es, SimpleMovingAverage(20), cons_5m) call is currently running without error.
  • The only code difference between the deployed (working) version and the current (failing) version is set_start_date(2022, …) vs set_start_date(2026, …) — the register_indicator loop is byte-for-byte identical.
  • This indicates a change in the backtest engine between May 25 and June 12 broke the Python → IndicatorBase<IndicatorDataPoint> type conversion for SimpleMovingAverage.

Intercom conversation

215474661265601

Workaround (for affected customers)

Remove the SimpleMovingAverage from the register_indicator call and manually update it in the consolidator handler:

# In initialize — remove SMA from the auto-register loop
for ind in [self.ema_9, ...]:  # vol_sma excluded
    self.register_indicator(es, ind, cons_5m)

# In the consolidator handler — update SMA manually
def on_5min_bar(self, sender, bar):
    self.vol_sma.update(bar.end_time, bar.volume)  # or bar.close for price SMA
    ...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions