Summary
The official documentation for custom Python indicators is incomplete and misleading. The current property on PythonIndicator instances does not work unless manually managed, yet the documentation never mentions this requirement.
Reproducible Example
Here's a complete, self-contained example demonstrating the issue:
from datetime import datetime
from collections import deque
from QuantConnect import Resolution
from QuantConnect.Data.Market import QuoteBar
from QuantConnect.Indicators import PythonIndicator
from QuantConnect.Research import QuantBook
# 1. Define custom indicator following the official documentation
class CustomSimpleMovingAverage(PythonIndicator):
def __init__(self, name, period):
self.name = name
self.warm_up_period = period
self.time = datetime.min
self.value = 0
self.queue = deque(maxlen=period)
def update(self, input) -> bool:
self.queue.appendleft(input.value)
count = len(self.queue)
self.time = input.time
self.value = sum(self.queue) / count
return count == self.queue.maxlen
# 2. Get some real data
qb = QuantBook()
xauusd = qb.add_cfd("XAUUSD").symbol
history = qb.history[QuoteBar](xauusd, datetime(
2025, 1, 1), datetime(2025, 1, 3), Resolution.MINUTE)
history_list = list(history)
print(f"History length: {len(history_list)}")
# 3. Create and update the indicator
custom_sma = CustomSimpleMovingAverage("My SMA", 2)
# Update with first 3 bars
custom_sma.update(history_list[0])
custom_sma.update(history_list[1])
custom_sma.update(history_list[2])
# 4. Check the values
# Works: shows actual calculated value
print(f"self.value: {custom_sma.value}")
# Works: shows actual timestamp
print(f"self.time: {custom_sma.time}")
# BROKEN: shows "0001-01-01T00:00:00 - 0"
print(f"self.current: {custom_sma.current}")
print(f"self.current.value: {custom_sma.current.value}") # BROKEN: shows 0
Expected Output:
History length: 1731
self.value: 2625.1475
self.time: 2025-01-01 18:06:00
self.current: 2025-01-01T18:06:00 - 2625.1475
self.current.value: 2625.1475Actual Output:
History length: 1731
self.value: 2625.1475
self.time: 2025-01-01 18:06:00
self.current: 0001-01-01T00:00:00 - 0
self.current.value: 0.0
The Root Cause
In C#, custom indicators override ComputeNextValue(), and the base class's Update() method automatically manages the Current property (see IndicatorBase source, also Custom Indicators docs when switched code examples to C# mode):
// C# - the INTENDED way
protected override decimal ComputeNextValue(IndicatorDataPoint input)
{
_window.Add(input.Value);
return _window.Sum() / _window.Size; // Base class sets Current automatically
}In Python, the documentation tells us to override update() directly, which bypasses the entire indicator framework. According to the docs, we manually set self.value and self.time, but .current (which is an IndicatorDataPoint object) is never updated. Furthermore, looking at PythonIndicator sources I am getting an impression that it is attempting to hook up into the existing window update logic, but the docs are telling us to override this attempt completely, suggesting a bug or inconsistency.
Debugging the update method as per documentation by inserting traceback.print_stack() probe suggests that indeed, the IndicatorBase framework methods are never called as per example.
Documentation Problems
- Official docs example - Shows setting self.value but never mentions .current
- Examples use .current.value elsewhere - e.g., if self.custom_mfi.current.value > 50:
- No explanation of the difference between Python and C# approaches
- RollingWindow size defaults - Also undocumented
Questions
- Is this a bug or intended behavior? Should PythonIndicator automatically sync self.value → self.current?
- What's the proper way to make .current work? Do we manually create IndicatorDataPoint objects?
- Why does Python bypass the indicator framework? Is there a technical reason?
- Can the documentation be updated? A complete, working example would help thousands of users.
Derek Melchin
Thanks, we'll update the docs
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Anatolii Kmetiuk
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!