@@ -79,6 +79,8 @@ public class LiveTradingResultHandler : BaseResultsHandler, IResultHandler
7979 private bool _userExchangeIsOpen ;
8080 private DateTime _lastChartSampleLogicCheck ;
8181 private readonly Dictionary < string , SecurityExchangeHours > _exchangeHours ;
82+ protected readonly SortedDictionary < DateTime , decimal > _dailyEquityClose = new ( ) ;
83+ protected readonly object _dailyEquityCloseLock = new ( ) ;
8284
8385
8486 /// <summary>
@@ -371,22 +373,78 @@ protected virtual void SetNextStatusUpdate()
371373 /// <summary>
372374 /// Removes chart series points older than their retention window: 10 days for performance charts, 2 days for all others.
373375 /// </summary>
376+ public override void Sample ( DateTime time )
377+ {
378+ base . Sample ( time ) ;
379+ if ( ! Algorithm . IsWarmingUp )
380+ {
381+ lock ( _dailyEquityCloseLock )
382+ {
383+ _dailyEquityClose [ time . Date ] = GetPortfolioValue ( ) ;
384+ }
385+ }
386+ }
387+
388+ protected override StatisticsResults GenerateStatisticsResults ( Dictionary < string , Chart > charts ,
389+ SortedDictionary < DateTime , decimal > profitLoss = null , CapacityEstimate estimatedStrategyCapacity = null )
390+ {
391+ List < KeyValuePair < DateTime , decimal > > historicalCloses ;
392+ lock ( _dailyEquityCloseLock )
393+ {
394+ historicalCloses = _dailyEquityClose . ToList ( ) ;
395+ }
396+
397+ if ( historicalCloses . Count > 0 &&
398+ charts . TryGetValue ( StrategyEquityKey , out var strategyEquity ) &&
399+ strategyEquity . Series . TryGetValue ( EquityKey , out var equitySeries ) )
400+ {
401+ var existingDates = new HashSet < DateTime > ( equitySeries . Values . Select ( v => v . Time . Date ) ) ;
402+ var toInject = historicalCloses
403+ . Where ( kvp => ! existingDates . Contains ( kvp . Key ) )
404+ . Select ( kvp => ( ISeriesPoint ) new Candlestick ( kvp . Key , kvp . Value , kvp . Value , kvp . Value , kvp . Value ) )
405+ . ToList ( ) ;
406+
407+ if ( toInject . Count > 0 )
408+ {
409+ var mergedEquity = ( CandlestickSeries ) equitySeries . Clone ( ) ;
410+ mergedEquity . Values . InsertRange ( 0 , toInject ) ;
411+
412+ var mergedChart = new Chart ( StrategyEquityKey ) ;
413+ foreach ( var kvp in strategyEquity . Series )
414+ {
415+ mergedChart . Series [ kvp . Key ] = kvp . Key == EquityKey ? mergedEquity : kvp . Value ;
416+ }
417+
418+ charts = new Dictionary < string , Chart > ( charts ) { [ StrategyEquityKey ] = mergedChart } ;
419+ }
420+ }
421+
422+ return base . GenerateStatisticsResults ( charts , profitLoss , estimatedStrategyCapacity ) ;
423+ }
424+
425+ /// <summary>
426+ /// Removes chart series points older than their retention window: 2 years for daily statistics series
427+ /// (Return, Benchmark), 2 days for all others.
428+ /// </summary>
374429 protected virtual void TrimCharts ( DateTime utcNow )
375430 {
431+ var defaultLimit = utcNow . AddDays ( - 2 ) ;
432+ var dailyStatsLimit = utcNow . AddDays ( - 730 ) ;
433+
376434 lock ( ChartLock )
377435 {
378436 foreach ( var chart in Charts )
379437 {
380- var timeLimitUtc = AlgorithmPerformanceCharts . Contains ( chart . Key )
381- ? utcNow . AddDays ( - 10 )
382- : utcNow . AddDays ( - 2 ) ;
383-
384438 foreach ( var series in chart . Value . Series )
385439 {
386- series . Value . Values =
387- ( from v in series . Value . Values
388- where v . Time > timeLimitUtc
389- select v ) . ToList ( ) ;
440+ var isDailyStatsSeries =
441+ ( chart . Key == StrategyEquityKey && series . Key == ReturnKey ) ||
442+ ( chart . Key == BenchmarkKey && series . Key == BenchmarkKey ) ;
443+
444+ var timeLimitUtc = isDailyStatsSeries ? dailyStatsLimit : defaultLimit ;
445+ series . Value . Values = series . Value . Values
446+ . Where ( v => v . Time > timeLimitUtc )
447+ . ToList ( ) ;
390448 }
391449 }
392450 }
0 commit comments