Skip to content

Commit 46430ec

Browse files
committed
Run the in-run analyses during algorithm warm-up, skipping only the speed sampling
1 parent cf30151 commit 46430ec

3 files changed

Lines changed: 33 additions & 60 deletions

File tree

Engine/Results/Analysis/InRunResultsAnalyzer.cs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,16 @@ public class InRunResultsAnalyzer : ResultsAnalyzer
4444

4545
private readonly AlgorithmSpeedTracker _speed = new();
4646

47-
private readonly QCAlgorithm _algorithm;
48-
4947
/// <summary>
5048
/// The number of order events already consumed by previous runs. The order events
51-
/// in the result passed to <see cref="Run(Result, IReadOnlyList{string}, int, int)"/>
49+
/// in the result passed to <see cref="Run(Result, IReadOnlyList{string}, System.Nullable{AlgorithmSpeedSample}, int, int)"/>
5250
/// are expected to start at this position.
5351
/// </summary>
5452
public int OrderEventsPosition { get; private set; }
5553

5654
/// <summary>
5755
/// The number of log entries already consumed by previous runs. The logs passed to
58-
/// <see cref="Run(Result, IReadOnlyList{string}, int, int)"/> are expected to start
56+
/// <see cref="Run(Result, IReadOnlyList{string}, System.Nullable{AlgorithmSpeedSample}, int, int)"/> are expected to start
5957
/// at this position.
6058
/// </summary>
6159
public int LogsPosition { get; private set; }
@@ -76,14 +74,13 @@ public class InRunResultsAnalyzer : ResultsAnalyzer
7674
/// <summary>
7775
/// Initializes a new instance of the <see cref="InRunResultsAnalyzer"/> class.
7876
/// The instance is expected to be kept alive for the duration of the backtest,
79-
/// receiving fresh data on each <see cref="Run(Result, IReadOnlyList{string}, int, int)"/> call.
77+
/// receiving fresh data on each <see cref="Run(Result, IReadOnlyList{string}, System.Nullable{AlgorithmSpeedSample}, int, int)"/> call.
8078
/// </summary>
8179
/// <param name="algorithm">The algorithm instance used for history requests and settings.</param>
8280
/// <param name="language">The programming language the algorithm is written in.</param>
8381
public InRunResultsAnalyzer(QCAlgorithm algorithm, Language language)
8482
: base(null, algorithm, language, null)
8583
{
86-
_algorithm = algorithm;
8784
}
8885

8986
/// <summary>
@@ -94,25 +91,17 @@ public InRunResultsAnalyzer(QCAlgorithm algorithm, Language language)
9491
/// Findings from analyses scanning the order event and log streams are accumulated
9592
/// (first sample kept, counts totaled), while findings from state-based analyses are
9693
/// replaced on every run.
97-
/// While the algorithm is warming up, nothing is analyzed or consumed and no findings are reported.
9894
/// </summary>
9995
/// <param name="result">A snapshot of the current intermediate backtest result, holding only new order events.</param>
10096
/// <param name="logs">The log lines produced since the previous run.</param>
101-
/// <param name="speedSample">A sample of the engine speed counters for the algorithm speed analysis, when available.</param>
97+
/// <param name="speedSample">A sample of the engine speed counters for the algorithm speed analysis.
98+
/// Null when the counters should not be sampled, like while the algorithm warms up.</param>
10299
/// <param name="timeLimitSeconds">Wall-clock seconds allowed for the full chain before early exit.</param>
103100
/// <param name="maxFailedAnalyses">Maximum number of failing analyses to return.</param>
104101
/// <returns>The accumulated findings, ranked by analysis weight.</returns>
105102
public IReadOnlyList<QuantConnect.Analysis> Run(Result result, IReadOnlyList<string> logs, AlgorithmSpeedSample? speedSample = null,
106103
int timeLimitSeconds = 1, int maxFailedAnalyses = 10)
107104
{
108-
// Nothing is analyzed during the algorithm warm-up period: trading hasn't started, and
109-
// sampling the warm-up pace would skew the speed metrics. The positions don't advance,
110-
// so the order events and logs produced during warm-up are analyzed by the first run after it ends.
111-
if (_algorithm?.IsWarmingUp == true)
112-
{
113-
return [];
114-
}
115-
116105
SetAnalysisData(result, logs);
117106
if (speedSample.HasValue)
118107
{

Engine/Results/BacktestingResultHandler.cs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -464,13 +464,6 @@ protected void SendFinalResult()
464464
{
465465
try
466466
{
467-
// Nothing to analyze until trading starts: skip building the snapshot altogether.
468-
// The analyzer catches up on the warm-up order events and logs on the first run after warm-up ends.
469-
if (Algorithm.IsWarmingUp)
470-
{
471-
return null;
472-
}
473-
474467
if (AlgorithmInstance == null)
475468
{
476469
return null;
@@ -485,13 +478,18 @@ protected void SendFinalResult()
485478

486479
_inRunResultsAnalyzer ??= new InRunResultsAnalyzer(AlgorithmInstance, _job.Language);
487480

488-
// Sample the engine speed counters for the algorithm speed analysis
489-
var speedSample = new AlgorithmSpeedSample(
490-
DateTime.UtcNow - StartTime,
491-
PerformanceTrackingTool?.DataPoints ?? 0,
492-
PerformanceTrackingTool?.HistoryDataPoints ?? 0,
493-
_progressMonitor?.ProcessedDays ?? 0,
494-
_progressMonitor?.TotalDays ?? 0);
481+
// Sample the engine speed counters for the algorithm speed analysis, but not while the
482+
// algorithm is warming up: the warm-up pace would skew the speed metrics. The analyses
483+
// themselves do run during warm-up, so conditions like orders submitted while warming up
484+
// surface without waiting for warm-up to end
485+
AlgorithmSpeedSample? speedSample = Algorithm.IsWarmingUp
486+
? null
487+
: new AlgorithmSpeedSample(
488+
DateTime.UtcNow - StartTime,
489+
PerformanceTrackingTool?.DataPoints ?? 0,
490+
PerformanceTrackingTool?.HistoryDataPoints ?? 0,
491+
_progressMonitor?.ProcessedDays ?? 0,
492+
_progressMonitor?.TotalDays ?? 0);
495493

496494
// Only the order events and logs produced since the previous run are analyzed,
497495
// the analyzer accumulates findings across runs

Tests/Engine/Results/InRunResultsAnalyzerTests.cs

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
using System.Linq;
2020
using System.Threading;
2121
using NUnit.Framework;
22-
using QuantConnect.Algorithm;
2322
using QuantConnect.Lean.Engine.Results.Analysis;
2423
using QuantConnect.Lean.Engine.Results.Analysis.Analyses;
2524
using QuantConnect.Orders;
@@ -179,33 +178,22 @@ public void AggregatedStateBasedFindingsAreReplacedByFullName()
179178
}
180179

181180
[Test]
182-
public void NothingIsAnalyzedOrConsumedDuringAlgorithmWarmUp()
181+
public void SpeedSamplesAreTrackedOnlyWhenProvided()
183182
{
184-
// A fresh algorithm is warming up until the engine flips it
185-
var algorithm = new QCAlgorithm();
186-
var ran = false;
187-
var fake = new FakeAnalysisA(10)
188-
{
189-
Findings = () => MakeFindings(nameof(FakeAnalysisA), "sample", 1),
190-
OnRun = () => ran = true
191-
};
192-
var analyzer = new TestInRunResultsAnalyzer(algorithm, fake);
193-
194-
var findings = analyzer.Run(MakeResult(2), new[] { "log" });
183+
AlgorithmSpeedTracker speed = null;
184+
var fake = new FakeAnalysisA(10) { OnParameters = parameters => speed = parameters.Speed };
185+
var analyzer = new TestInRunResultsAnalyzer(fake);
195186

196-
Assert.IsFalse(ran);
197-
Assert.IsEmpty(findings);
198-
Assert.AreEqual(0, analyzer.OrderEventsPosition);
199-
Assert.AreEqual(0, analyzer.LogsPosition);
187+
analyzer.Run(MakeResult(1), new[] { "log" });
188+
Assert.IsNotNull(speed);
189+
Assert.AreEqual(0, speed.SampleCount);
200190

201-
// Once warm-up finishes, the analysis catches up on the unconsumed order events and logs
202-
algorithm.SetFinishedWarmingUp();
203-
findings = analyzer.Run(MakeResult(2), new[] { "log" });
191+
analyzer.Run(MakeResult(1), new[] { "log" }, new AlgorithmSpeedSample(TimeSpan.FromSeconds(30), 100, 0, 1, 10));
192+
Assert.AreEqual(1, speed.SampleCount);
204193

205-
Assert.IsTrue(ran);
206-
Assert.AreEqual("sample", findings.Single().Sample);
207-
Assert.AreEqual(2, analyzer.OrderEventsPosition);
208-
Assert.AreEqual(1, analyzer.LogsPosition);
194+
// No sample provided (e.g. while the algorithm warms up): the tracker is left untouched
195+
analyzer.Run(MakeResult(1), new[] { "log" });
196+
Assert.AreEqual(1, speed.SampleCount);
209197
}
210198

211199
[Test]
@@ -257,12 +245,7 @@ private class TestInRunResultsAnalyzer : InRunResultsAnalyzer
257245
private readonly IReadOnlyCollection<BaseResultsAnalysis> _analyses;
258246

259247
public TestInRunResultsAnalyzer(params BaseResultsAnalysis[] analyses)
260-
: this(null, analyses)
261-
{
262-
}
263-
264-
public TestInRunResultsAnalyzer(QCAlgorithm algorithm, params BaseResultsAnalysis[] analyses)
265-
: base(algorithm, Language.CSharp)
248+
: base(null, Language.CSharp)
266249
{
267250
_analyses = analyses;
268251
}
@@ -282,6 +265,8 @@ private class FakeAnalysis : BaseResultsAnalysis
282265

283266
public Action OnRun { get; set; }
284267

268+
public Action<ResultsAnalysisRunParameters> OnParameters { get; set; }
269+
285270
protected FakeAnalysis(int weight)
286271
{
287272
_weight = weight;
@@ -290,6 +275,7 @@ protected FakeAnalysis(int weight)
290275
public override IReadOnlyList<QuantConnect.Analysis> Run(ResultsAnalysisRunParameters parameters)
291276
{
292277
OnRun?.Invoke();
278+
OnParameters?.Invoke(parameters);
293279
return Findings();
294280
}
295281
}

0 commit comments

Comments
 (0)