Skip to content

Call the python reset from PythonIndicator.Reset - #9698

Open
mkzung wants to merge 1 commit into
QuantConnect:masterfrom
mkzung:bug-9697-python-indicator-reset
Open

Call the python reset from PythonIndicator.Reset#9698
mkzung wants to merge 1 commit into
QuantConnect:masterfrom
mkzung:bug-9697-python-indicator-reset

Conversation

@mkzung

@mkzung mkzung commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Description

PythonIndicator never overrode Reset(), so a reset defined in python was never called and _isReady stayed set.

Related Issue

#9697

Motivation and Context

indicator_history resets before it replays, so it returned values mixed with whatever the indicator had already seen. Same class as #9686, #9687, #9688 and #9694, one level up.

Requires Documentation Change

No.

How Has This Been Tested?

Added ResetClearsTheStateHeldInPython to PythonIndicatorTests. It runs in all six python fixtures, both wrapping paths and both naming conventions, and fails on master at 100.75 against 100. The two duck-typed fixtures gained the reset they were missing.

Full suite, --filter "TestCategory!=TravisExclude&TestCategory!=ResearchRegressionTests", run on pristine master and on this branch:

master      36614 tests, 3 failed
this branch 36620 tests, 3 failed

Same three, and they fail on master as well: CommanCallback(Python), ThreadSafety, ZipBytesReturnsByteArrayWithCorrectLength. The extra six are the new test in its six fixtures.

One note on the guard: GetMethod throws when the attribute is missing altogether and returns null only when it resolves to C#, so an unguarded call breaks every python indicator that has no reset. The first version of this did exactly that, and only the full run caught it.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • My code follows the code style of this project.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • My branch follows the naming convention bug-<issue#>-<description>

@Martin-Molinero Martin-Molinero left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @mkzung! Thanks, leaving a few comments

Fix works for the plain non-inheriting case, but two confirmed regressions: segfault via super().reset() in inheriting classes, and uncaught AttributeError from the HasAttr/GetPythonMethod name asymmetry. Resolving the reset method once in SetIndicator + a reentrancy guard would cover most findings — details inline.

{
using (Py.GIL())
{
_indicatorWrapper.GetMethod(nameof(Reset), pythonOnly: true)?.Invoke().Dispose();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For inheriting classes _indicatorWrapper wraps the instance itself, so a subclass reset() calling super().reset() (the pre-PR correct pattern) recurses: C# Reset() → python reset() → CLR binding → C# Reset()... Reproduced on this branch: ~995 frames, fatal 0xC0000005. A reentrancy guard is needed here.

public override void Reset()
{
// GetMethod throws when the attribute is absent, and returns null when it is CSharp
if (_indicatorWrapper != null && _indicatorWrapper.HasAttr(nameof(Reset)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HasAttr is snake-case tolerant but GetPythonMethod falls back to PascalCase-only GetAttr("Reset") — so self.reset = False (non-method, no Reset) passes the guard then throws an uncaught AttributeError; worked before this PR. Also, a non-bound-method callable (self.Reset = lambda: ...) is silently skipped. Suggest resolving the reset method once in SetIndicator, like _pythonIsReadyProperty — also removes the per-call HasAttr GIL round-trip and avoids caching null under "Reset" in _pythonMethods (keyed by name only, ignores pythonOnly).

_indicatorWrapper.GetMethod(nameof(Reset), pythonOnly: true)?.Invoke().Dispose();
}
}
_isReady = false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the python reset() raises, _isReady = false / base.Reset() are skipped → half-reset indicator. Run them before the invoke or in a finally.

indicator.Reset();
indicator.Update(new IndicatorDataPoint(reference, 100m));

Assert.AreEqual(100m, indicator.Current.Value);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also assert IsReady is false (or Samples == 0) after Reset() — dropping _isReady = false wouldn't fail this test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants