Skip to content

fix: snapshot the charts for the in-run results analysis - #9848

Merged
Martin-Molinero merged 4 commits into
QuantConnect:masterfrom
Romazes:bug-in-run-analysis-chart-snapshot
Sep 30, 2026
Merged

Martin-Molinero merged 4 commits into
QuantConnect:masterfrom
Romazes:bug-in-run-analysis-chart-snapshot

Conversation

@Romazes

@Romazes Romazes commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator

Description

The in-run results analysis received the handler's live charts. PortfolioMarginUsageAnalysis enumerates the "Portfolio Margin" chart while the algorithm thread keeps sampling it at midnight, so the enumeration failed:

BacktestingResultHandler.RunInRunResultsAnalysis(): Error running in-run backtest analysis System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.Collections.Generic.List`1.Enumerator.MoveNext()
   at System.Linq.Enumerable.CastICollectionIterator`1.MoveNext()
   at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector)
   at QuantConnect.Lean.Engine.Results.Analysis.Analyses.PortfolioMarginUsageAnalysis.<>c.<Run>b__10_0(KeyValuePair`2 kvp) in Engine/Results/Analysis/Analyses/PortfolioMarginUsageAnalysis.cs:line 66

BacktestingResultHandler.Update() now clones the charts under ChartLock before building the intermediate result, the way the live handler does for its packets, so the in-run analyses and the stored result read a consistent copy. The ResultsAnalyzer.Run doc comment that accepted the torn read is updated.

Side effect: StoreResult no longer strips single-point series from the live margin chart during the run, only from the copy. Stored files and sent packets are unchanged.

Related PR(s)

#9632 added the in-run analysis.

Related Issue

N/A

Motivation and Context

The handler catches the exception and logs it as an error, so the backtest completes, but the error line lands on stderr and the cloud shows it in the backtest error box. Users saw a fatal-looking error on a completed backtest.

Requires Documentation Change

No

How Has This Been Tested?

  • New BacktestingResultHandlerTests.InRunAnalysisRunsWhileTheAlgorithmSamplesTheCharts: a 100,000-point margin chart, a task re-sampling its last point under the chart lock, and the handler's update loop running the in-run analysis. Fails on master with the stack above, passes with the fix, 3 reruns.
  • BacktestingResultHandlerTests, ResultsAnalyzerInRunTests and ResultsAnalyzerTests: 41 of 41 passed.
  • The race is timing based: a cloud backtest with 20 equities, hourly, 1998 to 2024, buy and hold, did not hit it in one run, while the unit test hits it every time.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Refactor (non-breaking change which improves implementation)
  • Performance (non-breaking change which improves performance. Please add associated performance test and results)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Non-functional change (xml comments/documentation/etc)

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> or feature-<issue#>-<description>

- clone the charts under the chart lock before building the intermediate backtest result
- the margin usage analysis enumerated the live chart while the algorithm thread sampled it
- add a handler test that samples the margin chart while the in-run analysis runs
@Romazes Romazes self-assigned this Sep 30, 2026
Romazes and others added 3 commits September 30, 2026 16:11
- reset the private first store time by reflection so the first update stores at once
- add the protected virtual InitialResultStoreDelay property, 5 seconds by default
- the in-run analysis test overrides it to zero instead of resetting the private field by reflection
The in-run and final results hand StoreResult a copy of the charts taken
under the chart lock, so it stores that copy instead of cloning the charts
a second time. The final result takes its copy too, since the algorithm
thread can still be sampling when it was stopped for exceeding a limit.

The single point portfolio margin series are now removed from the stored
result, like the live handler does, instead of from the charts the
algorithm keeps sampling.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Martin-Molinero
Martin-Molinero merged commit 570a11a into QuantConnect:master Sep 30, 2026
7 of 8 checks passed
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