18 using System.Collections.Concurrent;
19 using System.Collections.Generic;
22 using System.Threading;
23 using Newtonsoft.Json;
24 using Newtonsoft.Json.Serialization;
46 private DateTime _previousPortfolioTurnoverSample;
47 private bool _packetDroppedWarning;
48 private int _logCount;
49 private ConcurrentDictionary<string, string> _customSummaryStatistics;
51 private static readonly TextWriter StandardOut = Console.Out;
52 private static readonly TextWriter StandardError = Console.Error;
54 private string _hostName;
56 private Bar _currentAlgorithmEquity;
123 ContractResolver =
new DefaultContractResolver
125 NamingStrategy =
new CamelCaseNamingStrategy
127 ProcessDictionaryKeys =
false,
128 OverrideSpecifiedNames =
true
141 if (_currentAlgorithmEquity ==
null)
143 _currentAlgorithmEquity =
new Bar();
146 return _currentAlgorithmEquity;
150 _currentAlgorithmEquity = value;
157 private Thread _updateRunner;
162 public bool IsActive => _updateRunner !=
null && _updateRunner.IsAlive;
167 public ConcurrentQueue<Packet>
Messages {
get;
set; }
172 public ConcurrentDictionary<string, Chart>
Charts {
get;
set; }
234 protected Dictionary<string, string>
State {
get;
set; }
300 Charts =
new ConcurrentDictionary<string, Chart>();
306 Messages =
new ConcurrentQueue<Packet>();
314 State =
new Dictionary<string, string>
317 [
"EndTime"] =
string.Empty,
318 [
"RuntimeError"] =
string.Empty,
319 [
"StackTrace"] =
string.Empty,
321 [
"OrderCount"] =
"0",
322 [
"InsightCount"] =
"0"
324 _previousSalesVolume =
new(2);
325 _previousSalesVolume.
Add(0);
326 _customSummaryStatistics =
new();
343 Console.SetOut(StandardOut);
344 Console.SetError(StandardError);
353 serverStatistics[
"Hostname"] = _hostName;
355 serverStatistics[
"Up Time"] = $
"{upTime.Days}d {upTime:hh\\:mm\\:ss}";
357 return serverStatistics;
367 if (orderEvents.Count <= 0)
372 var filename = $
"{AlgorithmId}-order-events.json";
375 var data = JsonConvert.SerializeObject(orderEvents, Formatting.None,
SerializerSettings);
377 File.WriteAllText(path, data);
394 if (allInsights.Count > 0)
397 var directory = Directory.GetParent(alphaResultsPath);
398 if (!directory.Exists)
402 var orderedInsights = allInsights.OrderBy(insight => insight.GeneratedTimeUtc);
403 File.WriteAllText(alphaResultsPath, JsonConvert.SerializeObject(orderedInsights, Formatting.Indented,
SerializerSettings));
411 protected virtual Dictionary<int, Order>
GetDeltaOrders(
int orderEventsStartPosition, Func<int, bool> shouldStop)
413 var deltaOrders =
new Dictionary<int, Order>();
418 if (deltaOrders.ContainsKey(orderId))
432 order.Price = order.Price.SmartRounding();
434 deltaOrders[orderId] = order;
436 if (shouldStop(deltaOrders.Count))
451 _hostName = parameters.
Job.
HostName ?? Environment.MachineName;
458 _updateRunner =
new Thread(
Run, 0) { IsBackground =
true, Name =
"Result Thread" };
459 _updateRunner.Start();
460 State[
"Hostname"] = _hostName;
466 ContractResolver =
new DefaultContractResolver
468 NamingStrategy =
new CamelCaseNamingStrategy
470 ProcessDictionaryKeys =
false,
471 OverrideSpecifiedNames =
true
480 protected abstract void Run();
505 public virtual string SaveLogs(
string id, List<LogEntry> logs)
507 var filename = $
"{id}-log.txt";
509 var logLines = logs.Select(x => x.Message);
510 File.WriteAllLines(path, logLines);
537 _updateRunner.StopSafely(TimeSpan.FromMinutes(10));
538 _updateRunner =
null;
589 public virtual void Sample(DateTime time)
607 SamplePortfolioMargin(time, currentPortfolioValue);
613 private void SamplePortfolioMargin(DateTime algorithmUtcTime, decimal currentPortfolioValue)
650 Log.
Debug(
"BaseResultsHandler.SamplePerformance(): " + time.ToShortTimeString() +
" >" + value);
671 protected virtual void SampleDrawdown(DateTime time, decimal currentPortfolioValue)
689 if (currentPortfolioValue != 0)
698 decimal todayPortfolioTurnOver;
699 if (_previousPortfolioTurnoverSample == time)
704 todayPortfolioTurnOver = (currentTotalSaleVolume - _previousSalesVolume[1]) / currentPortfolioValue;
708 todayPortfolioTurnOver = (currentTotalSaleVolume - _previousSalesVolume[0]) / currentPortfolioValue;
711 _previousSalesVolume.
Add(currentTotalSaleVolume);
712 _previousPortfolioTurnoverSample = time;
726 .OrderByDescending(x => x.TotalSaleVolume).Take(30))
738 protected virtual void SampleExposure(DateTime time, decimal currentPortfolioValue)
741 if (currentPortfolioValue == 0)
748 var shortHoldings =
new Dictionary<SecurityType, decimal>();
749 var longHoldings =
new Dictionary<SecurityType, decimal>();
753 if (!longHoldings.ContainsKey(holding.Symbol.SecurityType))
755 longHoldings.Add(holding.Symbol.SecurityType, 0);
756 shortHoldings.Add(holding.Symbol.SecurityType, 0);
759 var holdingsValue = holding.HoldingsValue;
760 if (holdingsValue == 0)
766 if (holdingsValue > 0)
768 longHoldings[holding.Symbol.SecurityType] += holdingsValue;
773 shortHoldings[holding.Symbol.SecurityType] += holdingsValue;
778 SampleExposureHelper(
PositionSide.Long, time, currentPortfolioValue, longHoldings);
779 SampleExposureHelper(
PositionSide.Short, time, currentPortfolioValue, shortHoldings);
790 private void SampleExposureHelper(
PositionSide type, DateTime time, decimal currentPortfolioValue, Dictionary<SecurityType, decimal> holdings)
792 foreach (var kvp
in holdings)
794 var ratio = Math.Round(kvp.Value / currentPortfolioValue, 4);
819 protected abstract void Sample(
string chartName,
831 var runtimeStatistics =
new SortedDictionary<string, string>();
836 runtimeStatistics.Add(pair.Key, pair.Value);
840 if (summary.ContainsKey(
"Probabilistic Sharpe Ratio"))
842 runtimeStatistics[
"Probabilistic Sharpe Ratio"] = summary[
"Probabilistic Sharpe Ratio"];
846 runtimeStatistics[
"Probabilistic Sharpe Ratio"] =
"0%";
850 runtimeStatistics[
"Fees"] = $
"-{AlgorithmCurrencySymbol}{Algorithm.Portfolio.TotalFees.ToStringInvariant("N2
")}";
852 runtimeStatistics[
"Return"] =
GetNetReturn().ToStringInvariant(
"P");
857 return runtimeStatistics;
865 State[
"RuntimeError"] = error;
866 State[
"StackTrace"] = stack;
874 if (
Algorithm ==
null || !
string.IsNullOrEmpty(
State[
"RuntimeError"]))
882 State[
"EndTime"] = endTime !=
null ? endTime.ToStringInvariant(
DateFormat.
UI) :
string.Empty;
886 State[
"LogCount"] = _logCount.ToStringInvariant();
898 SortedDictionary<DateTime, decimal> profitLoss =
null,
CapacityEstimate estimatedStrategyCapacity =
null)
901 if (profitLoss ==
null)
903 profitLoss =
new SortedDictionary<DateTime, decimal>();
912 strategyEquity.Series.TryGetValue(
EquityKey, out var equity) &&
913 strategyEquity.Series.TryGetValue(
ReturnKey, out var performance) &&
914 charts.TryGetValue(
BenchmarkKey, out var benchmarkChart) &&
915 benchmarkChart.Series.TryGetValue(
BenchmarkKey, out var benchmark))
926 portfolioTurnover =
new Series();
936 statisticsResults.AddCustomSummaryStatistics(_customSummaryStatistics);
938 catch (Exception err)
940 Log.
Error(err,
"BaseResultsHandler.GenerateStatisticsResults(): Error generating statistics packet");
943 return statisticsResults;
968 Dictionary<string, Chart> charts;
1002 private void ProcessAlgorithmLogsImpl(ConcurrentQueue<string> concurrentQueue,
PacketType packetType,
int? messageQueueLimit =
null)
1004 if (concurrentQueue.IsEmpty)
1009 var endTime = DateTime.UtcNow.AddMilliseconds(250).Ticks;
1010 var currentMessageCount = -1;
1011 while (DateTime.UtcNow.Ticks < endTime && concurrentQueue.TryDequeue(out var message))
1013 if (messageQueueLimit.HasValue)
1015 if (currentMessageCount == -1)
1018 currentMessageCount =
Messages.Count;
1020 if (currentMessageCount > messageQueueLimit)
1022 if (!_packetDroppedWarning)
1024 _packetDroppedWarning =
true;
1041 else if (packetType ==
PacketType.HandledError)
1048 currentMessageCount++;
1059 _customSummaryStatistics.AddOrUpdate(name, value);