From 5b54403a2d61c28ae3f06c7970cbe200ec7fd501 Mon Sep 17 00:00:00 2001 From: Martin Molinero Date: Thu, 1 Oct 2026 10:35:59 -0300 Subject: [PATCH] Allow setting summary statistics during Initialize SetSummaryStatistic threw a null reference when called from Initialize because the statistics service is attached after setup runs. Keep the statistics set before the service is available and apply them once it's attached. Co-Authored-By: Claude Opus 5.5 (1M context) --- Algorithm.CSharp/StatisticsResultsAlgorithm.cs | 8 ++++++++ Algorithm.Python/StatisticsResultsAlgorithm.py | 6 ++++++ Algorithm/QCAlgorithm.Plotting.cs | 8 ++++++++ Algorithm/QCAlgorithm.cs | 11 +++++++++++ 4 files changed, 33 insertions(+) diff --git a/Algorithm.CSharp/StatisticsResultsAlgorithm.cs b/Algorithm.CSharp/StatisticsResultsAlgorithm.cs index 51b01fab4d92..102127f5f9a0 100644 --- a/Algorithm.CSharp/StatisticsResultsAlgorithm.cs +++ b/Algorithm.CSharp/StatisticsResultsAlgorithm.cs @@ -32,6 +32,7 @@ public class StatisticsResultsAlgorithm : QCAlgorithm, IRegressionAlgorithmDefin { private const string MostTradedSecurityStatistic = "Most Traded Security"; private const string MostTradedSecurityTradeCountStatistic = "Most Traded Security Trade Count"; + private const string InitializeStatistic = "Initialize Statistic"; private Symbol _spy; @@ -61,6 +62,9 @@ public override void Initialize() _fastIbmEma = EMA(_spy, 10, Resolution.Minute); _slowIbmEma = EMA(_spy, 30, Resolution.Minute); + + // Custom summary statistics can also be set during initialization + SetSummaryStatistic(InitializeStatistic, 1); } public override void OnData(Slice slice) @@ -152,6 +156,10 @@ public override void OnEndOfAlgorithm() { throw new RegressionTestException($"Statistic {MostTradedSecurityTradeCountStatistic} should be in the summary statistics"); } + if (!statistics.TryGetValue(InitializeStatistic, out var initializeStatistic) || initializeStatistic != "1") + { + throw new RegressionTestException($"Statistic {InitializeStatistic} set during initialization should be in the summary statistics"); + } var mostTradeSecurityKvp = _tradeCounts.MaxBy(kvp => kvp.Value); CheckMostTradedSecurityStatistic(statistics, mostTradeSecurityKvp.Key, mostTradeSecurityKvp.Value); } diff --git a/Algorithm.Python/StatisticsResultsAlgorithm.py b/Algorithm.Python/StatisticsResultsAlgorithm.py index 44da587710e0..b2b6f8817499 100644 --- a/Algorithm.Python/StatisticsResultsAlgorithm.py +++ b/Algorithm.Python/StatisticsResultsAlgorithm.py @@ -22,6 +22,7 @@ class StatisticsResultsAlgorithm(QCAlgorithm): most_traded_security_statistic = "Most Traded Security" most_traded_security_trade_count_statistic = "Most Traded Security Trade Count" + initialize_statistic = "Initialize Statistic" def initialize(self): self.set_start_date(2013, 10, 7) @@ -39,6 +40,9 @@ def initialize(self): self.trade_counts = {self.spy: 0, self.ibm: 0} + # Custom summary statistics can also be set during initialization + self.set_summary_statistic(StatisticsResultsAlgorithm.initialize_statistic, 1) + def on_data(self, data: Slice): if not self.slow_spy_ema.is_ready: return @@ -97,6 +101,8 @@ def on_end_of_algorithm(self): raise AssertionError(f"Statistic {StatisticsResultsAlgorithm.most_traded_security_statistic} should be in the summary statistics") if StatisticsResultsAlgorithm.most_traded_security_trade_count_statistic not in statistics: raise AssertionError(f"Statistic {StatisticsResultsAlgorithm.most_traded_security_trade_count_statistic} should be in the summary statistics") + if StatisticsResultsAlgorithm.initialize_statistic not in statistics or statistics[StatisticsResultsAlgorithm.initialize_statistic] != "1": + raise AssertionError(f"Statistic {StatisticsResultsAlgorithm.initialize_statistic} set during initialization should be in the summary statistics") most_trade_security, most_trade_security_trade_count = self.get_most_trade_security() self.check_most_traded_security_statistic(statistics, most_trade_security, most_trade_security_trade_count) diff --git a/Algorithm/QCAlgorithm.Plotting.cs b/Algorithm/QCAlgorithm.Plotting.cs index a8c68999dd4b..beb974539379 100644 --- a/Algorithm/QCAlgorithm.Plotting.cs +++ b/Algorithm/QCAlgorithm.Plotting.cs @@ -503,6 +503,14 @@ public void SetSummaryStatistic(string name, string value) throw new ArgumentException($"'{name}' is a reserved statistic name."); } + if (_statisticsService == null) + { + // the statistics service is attached after Initialize, keep the statistic until then + _pendingSummaryStatistics ??= new(); + _pendingSummaryStatistics[name] = value; + return; + } + _statisticsService.SetSummaryStatistic(name, value); } diff --git a/Algorithm/QCAlgorithm.cs b/Algorithm/QCAlgorithm.cs index d6987b6c1fcc..b820cfe31f1b 100644 --- a/Algorithm/QCAlgorithm.cs +++ b/Algorithm/QCAlgorithm.cs @@ -121,6 +121,8 @@ public partial class QCAlgorithm : MarshalByRefObject, IAlgorithm private ConcurrentQueue _logMessages = new ConcurrentQueue(); private ConcurrentQueue _errorMessages = new ConcurrentQueue(); private IStatisticsService _statisticsService; + // summary statistics set before the statistics service is attached, e.g. during Initialize + private Dictionary _pendingSummaryStatistics; private IBrokerageModel _brokerageModel; private bool _sentBroadcastCommandsDisabled; @@ -3914,6 +3916,15 @@ public void SetStatisticsService(IStatisticsService statisticsService) if (_statisticsService == null) { _statisticsService = statisticsService; + + if (_pendingSummaryStatistics != null) + { + foreach (var (name, value) in _pendingSummaryStatistics) + { + _statisticsService.SetSummaryStatistic(name, value); + } + _pendingSummaryStatistics = null; + } } }