diff --git a/Engine/Results/LiveTradingResultHandler.cs b/Engine/Results/LiveTradingResultHandler.cs index 954c95cd3411..9c8eb35e7315 100644 --- a/Engine/Results/LiveTradingResultHandler.cs +++ b/Engine/Results/LiveTradingResultHandler.cs @@ -336,21 +336,7 @@ private void Update() if (utcNow > _nextChartTrimming) { Log.Debug("LiveTradingResultHandler.Update(): Trimming charts"); - var timeLimitUtc = utcNow.AddDays(-2); - lock (ChartLock) - { - foreach (var chart in Charts) - { - foreach (var series in chart.Value.Series) - { - // trim data that's older than 2 days - series.Value.Values = - (from v in series.Value.Values - where v.Time > timeLimitUtc - select v).ToList(); - } - } - } + TrimCharts(utcNow); _nextChartTrimming = DateTime.UtcNow.AddMinutes(10); Log.Debug("LiveTradingResultHandler.Update(): Finished trimming charts"); } @@ -382,6 +368,30 @@ protected virtual void SetNextStatusUpdate() _nextStatusUpdate = DateTime.UtcNow.AddMinutes(10); } + /// + /// Removes chart series points older than their retention window: 10 days for performance charts, 2 days for all others. + /// + protected virtual void TrimCharts(DateTime utcNow) + { + lock (ChartLock) + { + foreach (var chart in Charts) + { + var timeLimitUtc = AlgorithmPerformanceCharts.Contains(chart.Key) + ? utcNow.AddDays(-10) + : utcNow.AddDays(-2); + + foreach (var series in chart.Value.Series) + { + series.Value.Values = + (from v in series.Value.Values + where v.Time > timeLimitUtc + select v).ToList(); + } + } + } + } + /// /// Stores the order events /// diff --git a/Tests/Engine/Results/LiveTradingResultHandlerTests.cs b/Tests/Engine/Results/LiveTradingResultHandlerTests.cs index ba3da3aac2d1..08f1c1bb6248 100644 --- a/Tests/Engine/Results/LiveTradingResultHandlerTests.cs +++ b/Tests/Engine/Results/LiveTradingResultHandlerTests.cs @@ -149,7 +149,7 @@ public void DailySampleValueBasedOnMarketHour(bool extendedMarketHoursEnabled) using var messagging = new QuantConnect.Messaging.Messaging(); var referenceDate = new DateTime(2020, 11, 25); var resultHandler = new LiveTradingResultHandler(); - resultHandler.Initialize(new (new LiveNodePacket(), messagging, api, new BacktestingTransactionHandler(), null)); + resultHandler.Initialize(new(new LiveNodePacket(), messagging, api, new BacktestingTransactionHandler(), null)); try { @@ -190,6 +190,62 @@ public void DailySampleValueBasedOnMarketHour(bool extendedMarketHoursEnabled) } } + [Test] + public void TrimChartsUsesLongerWindowForPerformanceCharts() + { + var handler = new TestableLiveTradingResultHandler(); + var utcNow = new DateTime(2020, 11, 25, 12, 0, 0, DateTimeKind.Utc); + + var benchmarkChart = new Chart(BaseResultsHandler.BenchmarkKey); + benchmarkChart.Series.Add(BaseResultsHandler.BenchmarkKey, new Series(BaseResultsHandler.BenchmarkKey)); + handler.Charts[BaseResultsHandler.BenchmarkKey] = benchmarkChart; + + // Add a custom user chart to verify it still uses the 2 day window + var customChart = new Chart("MyCustomChart"); + customChart.Series.Add("MyMetric", new Series("MyMetric")); + handler.Charts["MyCustomChart"] = customChart; + + var returnSeries = handler.Charts[BaseResultsHandler.StrategyEquityKey].Series[BaseResultsHandler.ReturnKey]; + var equitySeries = handler.Charts[BaseResultsHandler.StrategyEquityKey].Series[BaseResultsHandler.EquityKey]; + var benchmarkSeries = benchmarkChart.Series[BaseResultsHandler.BenchmarkKey]; + var customSeries = customChart.Series["MyMetric"]; + + // performance charts: 15 daily samples covering well beyond both trim windows + for (var i = 15; i >= 1; i--) + { + var t = utcNow.AddDays(-i); + returnSeries.Values.Add(new ChartPoint(t, i)); + benchmarkSeries.Values.Add(new ChartPoint(t, i)); + equitySeries.Values.Add(new Candlestick(t, 100, 110, 90, 105)); + } + + // custom chart: 5 samples to verify it uses the 2-day window + for (var i = 5; i >= 1; i--) + { + customSeries.Values.Add(new ChartPoint(utcNow.AddDays(-i), i)); + } + + handler.PublicTrimCharts(utcNow); + + // performance charts keep 10 day window + var performanceChartsCutoff = utcNow.AddDays(-10); + Assert.IsTrue(returnSeries.Values.All(v => v.Time > performanceChartsCutoff)); + Assert.IsTrue(benchmarkSeries.Values.All(v => v.Time > performanceChartsCutoff)); + Assert.IsTrue(equitySeries.Values.All(v => v.Time > performanceChartsCutoff)); + Assert.AreEqual(9, returnSeries.Values.Count); + Assert.AreEqual(9, benchmarkSeries.Values.Count); + + // other charts keep 2 day window + var otherChartsCutoff = utcNow.AddDays(-2); + Assert.IsTrue(customSeries.Values.All(v => v.Time > otherChartsCutoff)); + Assert.AreEqual(1, customSeries.Values.Count); + } + + private class TestableLiveTradingResultHandler : LiveTradingResultHandler + { + public void PublicTrimCharts(DateTime utcNow) => TrimCharts(utcNow); + } + private class TestDataFeed : IDataFeed { public bool IsActive { get; }