Run a reduced results analysis periodically during backtests - #9632
Merged
Martin-Molinero merged 33 commits intoAug 6, 2026
Merged
Conversation
Makes the ResultsAnalyzer analysis set overridable and adds an InRunResultsAnalyzer that the BacktestingResultHandler runs on the periodic intermediate result updates, against a thread-safe snapshot of the current backtest state. The in-run set only includes cheap, snapshot-based analyses that detect error conditions early (order response errors, margin calls, stale fills, margin usage, non-positive equity), leaving curve and statistics based analyses to the final analysis. The equity and benchmark curves are skipped for in-run analysis, avoiding benchmark history requests on every update.
Instead of rescanning the full, monotonically growing order event and log collections on every periodic run, the InRunResultsAnalyzer is now kept alive for the duration of the backtest and tracks the positions already consumed, so each run only receives and scans the new entries. Findings from stream-scanning analyses are accumulated across runs (first sample kept, counts totaled), while state-based analyses (portfolio value, take profit/stop loss orders, margin usage) are recomputed against the full current state and replaced on every run.
…nd cache the unwrapped algorithm instance
jhonabreul
marked this pull request as ready for review
July 22, 2026 21:20
…n-run analyses read The analyses are stateless, so the base analyzer now creates them once and reuses them; the in-run findings ranking reads the same cached set instead of re-instantiating the analyses to look up their weights. InRunResultsAnalyzer.RequiredCharts lists the charts its analyses read so the result handler only needs to clone those into the analyzed snapshot. Also reverts the benchmark history end trim to the current algorithm time: the in-run analyzer never builds the equity curves, so the trim only ran in the final analysis, where it could drop the last daily benchmark bar. And documents that state-based findings drop when a time-limit truncated run skips their analysis.
…ty sample Equity is not sampled while the algorithm warms up, so the intermediate statistics are all-zero defaults that flagged a false non-positive portfolio value finding for the whole warm-up period (and on the first cycle before any sample). The handler now withholds them until the algorithm is done warming up and the equity series has samples, and the portfolio value analysis skips when they are withheld. The handler also now clones only the charts the in-run analyses read instead of deep-cloning every chart on each cycle.
11 tasks
… timeout analysis
The analyzer now pulls the backtest data it needs through the new IInRunAnalysisDataProvider interface, implemented by the result handler, keeping its incremental stream consumption, snapshot assembly, and statistics-withholding rules private.
…yses Each analysis now declares whether it can run while the backtest is in progress (RunsInRun) and whether it is state-based (IsStateBased), so the in-run analyzer derives its set by filtering the final analysis set instead of maintaining a duplicated list and a name-based state-based set.
A single ResultsAnalyzer now serves both analysis modes through named factory methods: CreateForFinalAnalysis builds a one-shot instance for the completed backtest, and CreateForInRunAnalysis builds the long-lived instance that runs the in-run capable analyses incrementally against the data pulled from its provider.
…as a completion-log fallback
…ad of pulling the backtest data through the provider
…eed counters and slices the logs itself
Martin-Molinero
approved these changes
Aug 5, 2026
…ting by serialized signature
This was referenced Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Runs a reduced suite of the backtest results analyses periodically while the backtest is still running, so error conditions (order response errors, margin calls, stale fills, non-positive equity, high margin usage) surface to the user early instead of only after the backtest completes.
The analyzer:
ResultsAnalyzerserves both modes through named factories:CreateForFinalAnalysisruns the full set once against the completed result;CreateForInRunAnalysisbuilds the long-lived in-run instance.BaseResultsAnalysis.RunsInRun, default true) and whether it reads current state instead of the order event and log streams (IsStateBased). The in-run set is the final set filtered byRunsInRun, so it cannot drift from it.The in-run flow:
BacktestingResultHandlerinvokes the analyzer on the periodic intermediate result updates, with a 1s time budget since it runs on the result handler thread, handing it the intermediateBacktestResultit already builds for storage.New
AlgorithmSpeedAnalysis, tracking the algorithm's speed so the user can decide to stop a slow backtest early:AlgorithmSpeedTrackerthat computes cumulative and recent-window rates, calendar-days-per-second, projected remaining time, and history-request share.SlowExecution(recent pace below the 40k data points/s benchmark),LongProjectedRuntime(over an hour left at the recent pace, or stalled calendar progress),ThroughputDegradation(recent pace below half the early-run baseline),HistoryRequestLoad(most processed data points served by history requests).SendFinalResulthands the final instance the in-run tracker, completed with one last sample (CompleteSpeedTracking). A completed backtest omits the remaining-time projection and self-suppresses the long-runtime finding; an aborted run still reflects its final pace.ExecutionSpeedAnalysis: when the tracked metrics cannot measure the speed, the engine's completion log line is parsed for the whole-run average as a fallback, with the same threshold (SlowDataPointsPerSecond) and minimum-runtime rule. The line only exists once the backtest ends, so the fallback only fires on the final analysis. The finding is now namedAlgorithmSpeedAnalysis / SlowExecutioninstead ofExecutionSpeedAnalysis.New
SingleTimeLoopTimeoutRuntimeErrorAnalysis(final only, fatal weight 100):Isolatortimeout terminations — a single time loop over its per-loop limit, the run outliving the maximum allowed runtime, and "Operation was canceled" when code keeps running after a stop request — since they share the same root cause and advice.Related Issue
N/A
Motivation and Context
Users currently only get the results analysis findings when a backtest finishes. For long backtests, surfacing failures like margin calls or persistent order errors while the run is in progress lets the user stop it early instead of waiting for completion.
Requires Documentation Change
N/A
How Has This Been Tested?
Ran a backtest through the Launcher with a packet-capturing messaging handler and an algorithm deliberately triggering order response errors: the findings arrived in their own result packets during the run (including one produced during warm-up, with no false non-positive-equity finding), and the final result carried them plus the final-only findings (
AlgorithmSpeedAnalysis / SlowExecution,StatisticalSignificanceOfDailyReturnsAnalysis).Stress-tested the in-run analyzer with an instrumented build (local-only timing instrumentation, not part of this PR) — worst observed cycle: 365 ms against the 1 s budget. These numbers predate the switch to analyzing the truncated intermediate result: deltas were unbounded then, while cycles are now capped at the last 100 orders and order events, so they are a conservative upper bound on the current design's cost.
Stress test setup and per-cycle numbers
Baseline run — 4 minute-resolution symbols, ~30 orders per time step: a cycle processing a delta of 31,489 order events + 764 log lines against 14,818 accumulated orders took 85 ms.
Heavy run — 133 minute-resolution symbols over a month, ~75 orders per time step (~30k orders/day) with fills, cancellations, rejections, and a log line per bar:
Reproduced each of the timeout runtime errors with temporary local algorithms and verified the final result contains the corresponding finding with the error message as its sample.
Unit tests (75, all passing):
ResultsAnalyzerInRunTests— the in-run mode, driven through intermediate results carrying truncated order event windows plus the accumulated logs: watermark dedup (each event analyzed exactly once, in order, advancing on truncated runs), evicted events being missed, accumulation vs state-based replacement, the reported-occurrences cap, weight ranking and the findings cap, the snapshot contents, statistics withholding, speed sample tracking and the final hand-off, and the in-run subset.ResultsAnalyzerTests— the mode-independent core: analysis set overriding and reuse, weight-ordered execution, the time-limit and max-failures early exits, skipped equity curves, the pinned final-only and state-based declarations, and the in-run entry points throwing on a final-analysis instance.PortfolioValueIsNotPositiveAnalysisTests— withheld statistics, the non-positive ending equity flag, no finding on positive equity.AlgorithmSpeedTrackerTests— rate math, history data point share, remaining-time estimation, sample validation.AlgorithmSpeedAnalysisTests— each sub-finding's threshold, the warm-up grace span, the two-window hysteresis, unwired counters, the completed-run projection, and the completion-log fallback (fires without tracked metrics, ignores fast/short/absent lines, superseded by conclusive metrics).SingleTimeLoopTimeoutRuntimeErrorAnalysisTests— each timeout message shape, state and log-fallback detection, no false positives, language-aware solution formatting.Types of changes
Checklist:
bug-<issue#>-<description>orfeature-<issue#>-<description>