Run a reduced results analysis periodically during backtests#9632
Open
jhonabreul wants to merge 12 commits into
Open
Run a reduced results analysis periodically during backtests#9632jhonabreul wants to merge 12 commits into
jhonabreul wants to merge 12 commits into
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.
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.
ResultsAnalyzermakes its analysis set overridable (GetAnalyses) and lets subclasses skip building the equity/benchmark curves (RequiresEquityCurves), avoiding a benchmark history request on every run. The analyses are stateless, so they are created once and reused across runs.InRunResultsAnalyzerruns only cheap, snapshot-based analyses. It is kept alive for the duration of the backtest and runs incrementally: each run analyzes only the order events and log lines produced since the previous run, and merges the new findings into the accumulated ones (stream-based findings keep the first sample and total the counts; state-based findings are replaced on every run).BacktestingResultHandlerinvokes it on the periodic intermediate result updates against a thread-safe snapshot of the current backtest state, with a small time budget (1s) since it runs on the result handler thread. Only the charts the in-run analyses read (InRunResultsAnalyzer.RequiredCharts, currently the portfolio margin chart) are cloned into the snapshot.PortfolioValueIsNotPositiveAnalysisskips when they are withheld.AlgorithmSpeedAnalysistracks the algorithm's speed so the user can decide to stop a slow backtest early:BacktestingResultHandlerfeeds anAlgorithmSpeedSampleof the engine counters (data points from thePerformanceTrackingTool, history data points, processed/total days, wall-clock elapsed) into the analyzer on each run;AlgorithmSpeedTrackeraccumulates the samples and 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, with progress and projected remaining time).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).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 backtests through the Launcher and verified the in-run analysis findings are produced on the intermediate updates and delivered to the messaging handler in their own result packet.
Stress-tested the in-run analyzer with an instrumented build (local-only timing instrumentation, not part of this PR) logging each cycle's elapsed time and delta sizes — worst observed cycle: 365 ms against the 1 s budget.
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:
Worst observed cycle: 365 ms against the 1 s budget, on a workload an order of magnitude beyond a realistic algorithm. Elapsed time is delta-dominated (~190k events ≈ 250–310 ms) with a mild ~+60 ms per 100k accumulated orders from the state-based analyses; extrapolating to ~600k accumulated orders with a maximal delta lands around 500–600 ms. The analyzer's time-limit trace never appeared in the logs.
Unit tests (44, all passing):
InRunResultsAnalyzerTests— incremental position advancement (including on time-limit-truncated runs), stream-based finding accumulation (first sample kept, counts totaled), state-based finding replacement and dropping, weight ranking, the max-findings cap, speed samples being tracked only when provided (none are taken during warm-up), theRequiredChartscontract, and the analysis set being created once and reused across runs.ResultsAnalyzerTests— overridable analysis set, skipped equity-curve construction, weight-ordered execution, the time-limit and max-failures early exits, and analysis instance reuse across runs.PortfolioValueIsNotPositiveAnalysisTests— no findings when the statistics are withheld (warm-up / no equity samples yet), the non-positive ending equity flag, and no actionable finding on positive equity.AlgorithmSpeedTrackerTests— cumulative/windowed rate math, history data point share, remaining-time estimation, and sample validation.AlgorithmSpeedAnalysisTests— each sub-finding's threshold, the warm-up grace span, the two-consecutive-window hysteresis, and the suppression of data-point findings when the counters aren't wired in.Types of changes
Checklist:
bug-<issue#>-<description>orfeature-<issue#>-<description>