18 using System.Collections.Generic;
38 private const double Samples = 4000;
39 private const double MinimumSamplePeriod = 4;
42 private DateTime _nextUpdate;
43 private DateTime _nextS3Update;
44 private string _errorMessage;
45 private int _daysProcessedFrontier;
46 private readonly HashSet<string> _chartSeriesExceededDataPoints;
47 private readonly HashSet<string> _chartSeriesCount;
48 private bool _chartSeriesCountExceededError;
58 private DateTime _nextSample;
59 private string _algorithmId;
60 private int _projectId;
75 _chartSeriesExceededDataPoints =
new();
76 _chartSeriesCount =
new();
91 if (_job ==
null)
throw new Exception(
"BacktestingResultHandler.Constructor(): Submitted Job type invalid.");
92 base.Initialize(parameters);
102 protected override void Run()
117 if (
Messages.TryDequeue(out packet))
128 catch (Exception err)
131 Algorithm.SetRuntimeError(err,
"ResultHandler");
134 Log.
Trace(
"BacktestingResultHandler.Run(): Ending Thread...");
140 private void Update()
150 var utcNow = DateTime.UtcNow;
151 if (utcNow <= _nextUpdate || _progressMonitor.
ProcessedDays < _daysProcessedFrontier)
return;
161 _nextUpdate = utcNow.AddSeconds(3);
163 catch (Exception err)
165 Log.
Error(err,
"Can't update variables");
168 var deltaCharts =
new Dictionary<string, Chart>();
170 var performanceCharts =
new Dictionary<string, Chart>();
175 foreach (var kvp
in Charts)
177 var chart = kvp.Value;
180 var updates = chart.GetUpdates();
181 if (!updates.IsEmpty())
183 deltaCharts.Add(chart.Name, updates);
189 performanceCharts[kvp.Key] = chart.Clone();
203 var progress = _progressMonitor.
Progress;
206 if (utcNow > _nextS3Update)
210 const int maxOrders = 100;
217 new Dictionary<string, string>(),
219 new Dictionary<string, AlgorithmPerformance>(),
225 _nextS3Update = DateTime.UtcNow.AddSeconds(30);
229 var splitPackets =
SplitPackets(deltaCharts, deltaOrders, runtimeStatistics, progress, serverStatistics);
231 foreach (var backtestingPacket
in splitPackets)
239 catch (Exception err)
248 public virtual IEnumerable<BacktestResultPacket>
SplitPackets(Dictionary<string, Chart> deltaCharts, Dictionary<int, Order> deltaOrders, SortedDictionary<string, string> runtimeStatistics, decimal progress, Dictionary<string, string> serverStatistics)
251 var splitPackets =
new List<BacktestResultPacket>();
252 foreach (var chart
in deltaCharts.Values)
256 Charts =
new Dictionary<string, Chart>
264 if (deltaOrders.Count > 0)
294 var key = $
"{AlgorithmId}.json";
300 result.Results.Charts.ToDictionary(x => x.Key, x => x.Value.Clone()),
301 result.Results.Orders,
302 result.Results.ProfitLoss,
303 result.Results.Statistics,
304 result.Results.RuntimeStatistics,
305 result.Results.RollingWindow,
307 result.Results.TotalPerformance,
308 result.Results.AlgorithmConfiguration,
309 result.Results.State));
324 Log.
Error(
"BacktestingResultHandler.StoreResult(): Result Null.");
327 catch (Exception err)
340 var endTime = DateTime.UtcNow;
346 var charts =
new Dictionary<string, Chart>(
Charts);
355 foreach (var ap
in statisticsResults.RollingPerformances.Values)
357 ap.ClosedTrades.Clear();
363 statisticsResults.RollingPerformances, orderEvents, statisticsResults.TotalPerformance,
380 SaveResults($
"{AlgorithmId}-summary.json", CreateResultSummary(result));
389 Log.
Trace(
"BacktestingResultHandler.SendAnalysisResult(): Processed final packet");
391 catch (Exception err)
417 var resampleMinutes = totalMinutes < MinimumSamplePeriod * Samples ? MinimumSamplePeriod : totalMinutes / Samples;
419 Log.
Trace(
"BacktestingResultHandler(): Sample Period Set: " + resampleMinutes.ToStringInvariant(
"00.00"));
422 var types =
new List<SecurityType>();
425 var security = kvp.Value;
427 if (!types.Contains(security.Type)) types.Add(security.Type);
494 :
"Algorithm Initialization: " + message;
496 base.AddToLogStore(messageToLog);
516 public virtual void ErrorMessage(
string message,
string stacktrace =
"")
518 if (message == _errorMessage)
return;
521 _errorMessage = message;
529 public virtual void RuntimeError(
string message,
string stacktrace =
"")
533 _errorMessage = message;
568 if (!
Charts.TryGetValue(chartName, out chart))
570 chart =
new Chart(chartName);
571 Charts.AddOrUpdate(chartName, chart);
576 if (!chart.Series.TryGetValue(seriesName, out series))
579 chart.Series.Add(seriesName, series);
583 if (series.Values.Count == 0 || value.
Time > series.Values[series.Values.Count - 1].Time
599 var roundedCapacity = _capacityEstimate.
Capacity;
611 foreach (var update
in updates)
615 if (!
Charts.TryGetValue(update.Name, out chart))
617 chart =
new Chart(update.Name);
618 Charts.AddOrUpdate(update.Name, chart);
622 foreach (var series
in update.Series.Values)
627 _chartSeriesCount.Add(series.Name);
629 else if (!_chartSeriesCount.Contains(series.Name))
632 if(!_chartSeriesCountExceededError)
634 _chartSeriesCountExceededError =
true;
635 DebugMessage($
"Exceeded maximum chart series count for organization tier, new series will be ignored. Limit is currently set at {_job.Controls.MaximumChartSeries}. https://qnt.co/docs-charting-quotas");
640 if (series.Values.Count > 0)
642 var thisSeries = chart.TryAddAndGetSeries(series.Name, series, forceAddNew:
false);
645 var dataPoint = series.ConsolidateChartPoints();
646 if (dataPoint !=
null)
648 thisSeries.AddPoint(dataPoint);
653 var values = thisSeries.Values;
657 values.AddRange(series.Values);
659 else if (!_chartSeriesExceededDataPoints.Contains(chart.Name + series.Name))
661 _chartSeriesExceededDataPoints.Add(chart.Name + series.Name);
662 DebugMessage($
"Exceeded maximum data points per series for organization tier, chart update skipped. Chart Name {update.Name}. Series name {series.Name}. https://qnt.co/docs-charting-quotas" +
663 $
"Limit is currently set at {_job.Controls.MaximumDataPointsPerChartSeries}");
680 Log.
Trace(
"BacktestingResultHandler.Exit(): starting...");
687 Log.
Trace(
"BacktestingResultHandler.Exit(): Saving logs...");
688 var logLocation =
SaveLogs(_algorithmId, copy);
689 SystemDebugMessage(
"Your log was successfully created and can be retrieved from: " + logLocation);
755 if (time > _nextSample || forceProcess)
824 Charts =
new Dictionary<string, Chart>(),
827 TotalPerformance =
new()
840 var samplePeriod = Math.Min(7, series.Values.Count / 100);
841 if (samplePeriod > 1)
843 var sampler =
new SeriesSampler(TimeSpan.FromDays(samplePeriod));
846 var chartClone = chart.CloneEmpty();
847 chartClone.AddSeries(equity);
852 Log.
Trace($
"BacktestingResultHandler.CreateResultSummary(): '{StrategyEquityKey}' chart not found");