From c96bb0c6b039c40618bdfdbb4270ed0eab1ae77b Mon Sep 17 00:00:00 2001 From: Romazes Date: Wed, 10 Jan 2024 19:57:50 +0200 Subject: [PATCH 01/19] feat: new `TradingDaysPerYear` prop in IAlgorithmSettings --- Common/AlgorithmConfiguration.cs | 12 +++++++++-- Common/AlgorithmSettings.cs | 13 ++++++++++++ Common/Interfaces/IAlgorithmSettings.cs | 13 ++++++++++++ Common/Statistics/AlgorithmPerformance.cs | 6 ++++-- Common/Statistics/StatisticsBuilder.cs | 20 ++++++++++++------- Engine/Results/BaseResultsHandler.cs | 3 ++- Research/QuantBook.cs | 2 +- .../Statistics/PortfolioStatisticsTests.cs | 2 +- .../Statistics/StatisticsBuilderTests.cs | 3 ++- 9 files changed, 59 insertions(+), 15 deletions(-) diff --git a/Common/AlgorithmConfiguration.cs b/Common/AlgorithmConfiguration.cs index 0109efe244a9..c265bb4895bd 100644 --- a/Common/AlgorithmConfiguration.cs +++ b/Common/AlgorithmConfiguration.cs @@ -81,13 +81,20 @@ public class AlgorithmConfiguration [JsonConverter(typeof(DateTimeJsonConverter), DateFormat.UI)] public DateTime EndDate; + /// + /// Number of trading days per year for Algorithm's portfolio statistics. + /// + [JsonProperty(PropertyName = "TradingDaysPerYear")] + public int TradingDaysPerYear; + /// /// Initializes a new instance of the class /// public AlgorithmConfiguration(string accountCurrency, BrokerageName brokerageName, AccountType accountType, IReadOnlyDictionary parameters, - DateTime startDate, DateTime endDate, DateTime? outOfSampleMaxEndDate, int outOfSampleDays = 0) + DateTime startDate, DateTime endDate, DateTime? outOfSampleMaxEndDate, int outOfSampleDays = 0, int tradingDaysPerYear = 0) { OutOfSampleMaxEndDate = outOfSampleMaxEndDate; + TradingDaysPerYear = tradingDaysPerYear; OutOfSampleDays = outOfSampleDays; AccountCurrency = accountCurrency; BrokerageName = brokerageName; @@ -120,7 +127,8 @@ public static AlgorithmConfiguration Create(IAlgorithm algorithm, BacktestNodePa algorithm.StartDate, algorithm.EndDate, backtestNodePacket?.OutOfSampleMaxEndDate, - backtestNodePacket?.OutOfSampleDays ?? 0); + backtestNodePacket?.OutOfSampleDays ?? 0, + algorithm.Settings.TradingDaysPerYear); } } } diff --git a/Common/AlgorithmSettings.cs b/Common/AlgorithmSettings.cs index 1997450c6e09..e321d6899650 100644 --- a/Common/AlgorithmSettings.cs +++ b/Common/AlgorithmSettings.cs @@ -119,6 +119,19 @@ public Resolution? WarmUpResolution } } + /// + /// Number of trading days per year for this Algorithm's portfolio statistics. + /// + /// Effect on + /// , + /// , + /// , + /// , + /// , + /// . + /// + public int TradingDaysPerYear { get; set; } + /// /// Initializes a new instance of the class /// diff --git a/Common/Interfaces/IAlgorithmSettings.cs b/Common/Interfaces/IAlgorithmSettings.cs index 0c11ded14845..28419b85beb5 100644 --- a/Common/Interfaces/IAlgorithmSettings.cs +++ b/Common/Interfaces/IAlgorithmSettings.cs @@ -93,5 +93,18 @@ public interface IAlgorithmSettings /// /// This allows improving the warmup speed by setting it to a lower resolution than the one added in the algorithm Resolution? WarmupResolution { get; set; } + + /// + /// Number of trading days per year for this Algorithm's portfolio statistics. + /// + /// Effect on + /// , + /// , + /// , + /// , + /// , + /// . + /// + int TradingDaysPerYear { get; set; } } } diff --git a/Common/Statistics/AlgorithmPerformance.cs b/Common/Statistics/AlgorithmPerformance.cs index f32ca9e07fcb..d9f091acf131 100644 --- a/Common/Statistics/AlgorithmPerformance.cs +++ b/Common/Statistics/AlgorithmPerformance.cs @@ -52,6 +52,7 @@ public class AlgorithmPerformance /// Number of winning transactions /// Number of losing transactions /// The risk free interest rate model to use + /// The number of trading days per year public AlgorithmPerformance( List trades, SortedDictionary profitLoss, @@ -62,12 +63,13 @@ public AlgorithmPerformance( decimal startingCapital, int winningTransactions, int losingTransactions, - IRiskFreeInterestRateModel riskFreeInterestRateModel) + IRiskFreeInterestRateModel riskFreeInterestRateModel, + int tradingDaysPerYear) { TradeStatistics = new TradeStatistics(trades); PortfolioStatistics = new PortfolioStatistics(profitLoss, equity, portfolioTurnover, listPerformance, listBenchmark, startingCapital, - riskFreeInterestRateModel, winCount: winningTransactions, lossCount: losingTransactions); + riskFreeInterestRateModel, tradingDaysPerYear, winningTransactions, losingTransactions); ClosedTrades = trades; } diff --git a/Common/Statistics/StatisticsBuilder.cs b/Common/Statistics/StatisticsBuilder.cs index bc12bf2ef273..5295919940ef 100644 --- a/Common/Statistics/StatisticsBuilder.cs +++ b/Common/Statistics/StatisticsBuilder.cs @@ -46,6 +46,7 @@ public static class StatisticsBuilder /// The transaction manager to get number of winning and losing transactions /// /// The risk free interest rate model to use + /// The number of trading days per year /// Returns a object public static StatisticsResults Generate( List trades, @@ -60,7 +61,8 @@ public static StatisticsResults Generate( CapacityEstimate estimatedStrategyCapacity, string accountCurrencySymbol, SecurityTransactionManager transactions, - IRiskFreeInterestRateModel riskFreeInterestRateModel) + IRiskFreeInterestRateModel riskFreeInterestRateModel, + int tradingDaysPerYear) { var equity = ChartPointToDictionary(pointsEquity); @@ -68,9 +70,9 @@ public static StatisticsResults Generate( var lastDate = equity.Keys.LastOrDefault().Date; var totalPerformance = GetAlgorithmPerformance(firstDate, lastDate, trades, profitLoss, equity, pointsPerformance, pointsBenchmark, - pointsPortfolioTurnover, startingCapital, transactions, riskFreeInterestRateModel); + pointsPortfolioTurnover, startingCapital, transactions, riskFreeInterestRateModel, tradingDaysPerYear); var rollingPerformances = GetRollingPerformances(firstDate, lastDate, trades, profitLoss, equity, pointsPerformance, pointsBenchmark, - pointsPortfolioTurnover, startingCapital, transactions, riskFreeInterestRateModel); + pointsPortfolioTurnover, startingCapital, transactions, riskFreeInterestRateModel, tradingDaysPerYear); var summary = GetSummary(totalPerformance, estimatedStrategyCapacity, totalFees, totalTransactions, accountCurrencySymbol); return new StatisticsResults(totalPerformance, rollingPerformances, summary); @@ -92,6 +94,7 @@ public static StatisticsResults Generate( /// The transaction manager to get number of winning and losing transactions /// /// The risk free interest rate model to use + /// The number of trading days per year /// The algorithm performance private static AlgorithmPerformance GetAlgorithmPerformance( DateTime fromDate, @@ -104,7 +107,8 @@ private static AlgorithmPerformance GetAlgorithmPerformance( List pointsPortfolioTurnover, decimal startingCapital, SecurityTransactionManager transactions, - IRiskFreeInterestRateModel riskFreeInterestRateModel) + IRiskFreeInterestRateModel riskFreeInterestRateModel, + int tradingDaysPerYear) { var periodEquity = new SortedDictionary(equity.Where(x => x.Key.Date >= fromDate && x.Key.Date < toDate.AddDays(1)).ToDictionary(x => x.Key, y => y.Value)); @@ -141,7 +145,7 @@ private static AlgorithmPerformance GetAlgorithmPerformance( var runningCapital = equity.Count == periodEquity.Count ? startingCapital : periodEquity.Values.FirstOrDefault(); return new AlgorithmPerformance(periodTrades, periodProfitLoss, periodEquity, portfolioTurnover, listPerformance, listBenchmark, - runningCapital, periodWinCount, periodLossCount, riskFreeInterestRateModel); + runningCapital, periodWinCount, periodLossCount, riskFreeInterestRateModel, tradingDaysPerYear); } /// @@ -160,6 +164,7 @@ private static AlgorithmPerformance GetAlgorithmPerformance( /// The transaction manager to get number of winning and losing transactions /// /// The risk free interest rate model to use + /// The number of trading days per year /// A dictionary with the rolling performances private static Dictionary GetRollingPerformances( DateTime firstDate, @@ -172,7 +177,8 @@ private static Dictionary GetRollingPerformances( List pointsPortfolioTurnover, decimal startingCapital, SecurityTransactionManager transactions, - IRiskFreeInterestRateModel riskFreeInterestRateModel) + IRiskFreeInterestRateModel riskFreeInterestRateModel, + int tradingDaysPerYear) { var rollingPerformances = new Dictionary(); @@ -185,7 +191,7 @@ private static Dictionary GetRollingPerformances( { var key = $"M{monthPeriod}_{period.EndDate.ToStringInvariant("yyyyMMdd")}"; var periodPerformance = GetAlgorithmPerformance(period.StartDate, period.EndDate, trades, profitLoss, equity, pointsPerformance, - pointsBenchmark, pointsPortfolioTurnover, startingCapital, transactions, riskFreeInterestRateModel); + pointsBenchmark, pointsPortfolioTurnover, startingCapital, transactions, riskFreeInterestRateModel, tradingDaysPerYear); rollingPerformances[key] = periodPerformance; } } diff --git a/Engine/Results/BaseResultsHandler.cs b/Engine/Results/BaseResultsHandler.cs index b1afa93a761c..6cae9d822424 100644 --- a/Engine/Results/BaseResultsHandler.cs +++ b/Engine/Results/BaseResultsHandler.cs @@ -848,7 +848,8 @@ protected StatisticsResults GenerateStatisticsResults(Dictionary statisticsResults = StatisticsBuilder.Generate(trades, profitLoss, equity.Values, performance.Values, benchmark.Values, portfolioTurnover.Values, StartingPortfolioValue, Algorithm.Portfolio.TotalFees, totalTransactions, - estimatedStrategyCapacity, AlgorithmCurrencySymbol, Algorithm.Transactions, Algorithm.RiskFreeInterestRateModel); + estimatedStrategyCapacity, AlgorithmCurrencySymbol, Algorithm.Transactions, Algorithm.RiskFreeInterestRateModel, + Algorithm.Settings.TradingDaysPerYear); } statisticsResults.AddCustomSummaryStatistics(_customSummaryStatistics); diff --git a/Research/QuantBook.cs b/Research/QuantBook.cs index 6d0f1e3b8646..ea33660ed0c9 100644 --- a/Research/QuantBook.cs +++ b/Research/QuantBook.cs @@ -769,7 +769,7 @@ public PyDict GetPortfolioStatistics(PyObject dataFrame) var startingCapital = Convert.ToDecimal(dictEquity.FirstOrDefault().Value); // Compute portfolio statistics - var stats = new PortfolioStatistics(profitLoss, equity, new(), listPerformance, listBenchmark, startingCapital, RiskFreeInterestRateModel); + var stats = new PortfolioStatistics(profitLoss, equity, new(), listPerformance, listBenchmark, startingCapital, RiskFreeInterestRateModel, Settings.TradingDaysPerYear); result.SetItem("Average Win (%)", Convert.ToDouble(stats.AverageWinRate * 100).ToPython()); result.SetItem("Average Loss (%)", Convert.ToDouble(stats.AverageLossRate * 100).ToPython()); diff --git a/Tests/Common/Statistics/PortfolioStatisticsTests.cs b/Tests/Common/Statistics/PortfolioStatisticsTests.cs index 2662a2a0aacc..7605bda96f24 100644 --- a/Tests/Common/Statistics/PortfolioStatisticsTests.cs +++ b/Tests/Common/Statistics/PortfolioStatisticsTests.cs @@ -37,7 +37,7 @@ public void ITMOptionAssignment([Values] bool win) var lossCount = trades.Count - winCount; var statistics = new PortfolioStatistics(profitLoss, new SortedDictionary(), new SortedDictionary(), new List { 0, 0 }, new List { 0, 0 }, 100000, - new InterestRateProvider(), winCount: winCount, lossCount: lossCount); + new InterestRateProvider(), 252, winCount, lossCount); if (win) { diff --git a/Tests/Common/Statistics/StatisticsBuilderTests.cs b/Tests/Common/Statistics/StatisticsBuilderTests.cs index 8903e13281eb..75457d2ae211 100644 --- a/Tests/Common/Statistics/StatisticsBuilderTests.cs +++ b/Tests/Common/Statistics/StatisticsBuilderTests.cs @@ -77,7 +77,8 @@ public void MisalignedValues_ShouldThrow_DuringGeneration() new QuantConnect.Securities.SecurityTransactionManager( null, new QuantConnect.Securities.SecurityManager(new TimeKeeper(DateTime.UtcNow))), - new InterestRateProvider()); + new InterestRateProvider(), + 252); }, "Misaligned values provided, but we still generate statistics"); } } From 99c33846aa6c174eb26709587c941cf2132d2828 Mon Sep 17 00:00:00 2001 From: Romazes Date: Wed, 10 Jan 2024 22:05:45 +0200 Subject: [PATCH 02/19] feat: getTradingDayPerYear by BrokerageModel feat: init algo tradingDaysPerYear in brokerage|BacktestingSetupHandler refactor: nullable `TradingDayPerYear` --- Common/AlgorithmConfiguration.cs | 2 +- Common/AlgorithmSettings.cs | 2 +- Common/Interfaces/IAlgorithmSettings.cs | 2 +- Engine/Results/BaseResultsHandler.cs | 2 +- Engine/Setup/BacktestingSetupHandler.cs | 3 ++ Engine/Setup/BaseSetupHandler.cs | 39 ++++++++++++++++++++----- Engine/Setup/BrokerageSetupHandler.cs | 3 ++ Research/QuantBook.cs | 2 +- 8 files changed, 43 insertions(+), 12 deletions(-) diff --git a/Common/AlgorithmConfiguration.cs b/Common/AlgorithmConfiguration.cs index c265bb4895bd..425a402ab0e9 100644 --- a/Common/AlgorithmConfiguration.cs +++ b/Common/AlgorithmConfiguration.cs @@ -128,7 +128,7 @@ public static AlgorithmConfiguration Create(IAlgorithm algorithm, BacktestNodePa algorithm.EndDate, backtestNodePacket?.OutOfSampleMaxEndDate, backtestNodePacket?.OutOfSampleDays ?? 0, - algorithm.Settings.TradingDaysPerYear); + algorithm.Settings.TradingDaysPerYear ?? 0); } } } diff --git a/Common/AlgorithmSettings.cs b/Common/AlgorithmSettings.cs index e321d6899650..4e4ec0ee93d7 100644 --- a/Common/AlgorithmSettings.cs +++ b/Common/AlgorithmSettings.cs @@ -130,7 +130,7 @@ public Resolution? WarmUpResolution /// , /// . /// - public int TradingDaysPerYear { get; set; } + public int? TradingDaysPerYear { get; set; } /// /// Initializes a new instance of the class diff --git a/Common/Interfaces/IAlgorithmSettings.cs b/Common/Interfaces/IAlgorithmSettings.cs index 28419b85beb5..fa4f715f8de5 100644 --- a/Common/Interfaces/IAlgorithmSettings.cs +++ b/Common/Interfaces/IAlgorithmSettings.cs @@ -105,6 +105,6 @@ public interface IAlgorithmSettings /// , /// . /// - int TradingDaysPerYear { get; set; } + int? TradingDaysPerYear { get; set; } } } diff --git a/Engine/Results/BaseResultsHandler.cs b/Engine/Results/BaseResultsHandler.cs index 6cae9d822424..881b2a0add5b 100644 --- a/Engine/Results/BaseResultsHandler.cs +++ b/Engine/Results/BaseResultsHandler.cs @@ -849,7 +849,7 @@ protected StatisticsResults GenerateStatisticsResults(Dictionary statisticsResults = StatisticsBuilder.Generate(trades, profitLoss, equity.Values, performance.Values, benchmark.Values, portfolioTurnover.Values, StartingPortfolioValue, Algorithm.Portfolio.TotalFees, totalTransactions, estimatedStrategyCapacity, AlgorithmCurrencySymbol, Algorithm.Transactions, Algorithm.RiskFreeInterestRateModel, - Algorithm.Settings.TradingDaysPerYear); + Algorithm.Settings.TradingDaysPerYear.Value); } statisticsResults.AddCustomSummaryStatistics(_customSummaryStatistics); diff --git a/Engine/Setup/BacktestingSetupHandler.cs b/Engine/Setup/BacktestingSetupHandler.cs index fa1a40a1b8b8..2d8c834aca5c 100644 --- a/Engine/Setup/BacktestingSetupHandler.cs +++ b/Engine/Setup/BacktestingSetupHandler.cs @@ -208,6 +208,9 @@ public bool Setup(SetupHandlerParameters parameters) // after we call initialize BaseSetupHandler.LoadBacktestJobCashAmount(algorithm, job); + // after algorithm was initialized we have validated trading days per year for our great portfolio statistics + algorithm.Settings.TradingDaysPerYear ??= BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel); + // finalize initialization algorithm.PostInitialize(); } diff --git a/Engine/Setup/BaseSetupHandler.cs b/Engine/Setup/BaseSetupHandler.cs index 0bf5ee467891..a43269078f90 100644 --- a/Engine/Setup/BaseSetupHandler.cs +++ b/Engine/Setup/BaseSetupHandler.cs @@ -15,19 +15,20 @@ */ using System; -using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; -using QuantConnect.AlgorithmFactory; -using QuantConnect.Configuration; using QuantConnect.Data; -using QuantConnect.Data.UniverseSelection; +using QuantConnect.Util; +using QuantConnect.Logging; +using QuantConnect.Packets; using QuantConnect.Interfaces; +using QuantConnect.Brokerages; +using System.Collections.Generic; +using QuantConnect.Configuration; +using QuantConnect.AlgorithmFactory; using QuantConnect.Lean.Engine.DataFeeds; +using QuantConnect.Data.UniverseSelection; using QuantConnect.Lean.Engine.DataFeeds.WorkScheduling; -using QuantConnect.Logging; -using QuantConnect.Packets; -using QuantConnect.Util; using HistoryRequest = QuantConnect.Data.HistoryRequest; namespace QuantConnect.Lean.Engine.Setup @@ -231,5 +232,29 @@ public static Dictionary> GetConfiguredDataFeeds() return null; } + + /// + /// Gets the number of trading days per year based on the specified brokerage model. + /// + /// The brokerage model for which to determine the trading days. + /// + /// The number of trading days per year. For specific brokerages (Coinbase, Binance, Bitfinex, Bybit, FTX, Kraken), + /// the value is 365. For other brokerages, the default value is 252. + /// + public static int GetBrokerageTradingDayPerYear(IBrokerageModel brokerageModel) + { + switch (brokerageModel) + { + case CoinbaseBrokerageModel: + case BinanceBrokerageModel: + case BitfinexBrokerageModel: + case BybitBrokerageModel: + case FTXBrokerageModel: + case KrakenBrokerageModel: + return 365; + default: + return 252; + } + } } } diff --git a/Engine/Setup/BrokerageSetupHandler.cs b/Engine/Setup/BrokerageSetupHandler.cs index b06fd71c36ce..2f6e5fc97ce0 100644 --- a/Engine/Setup/BrokerageSetupHandler.cs +++ b/Engine/Setup/BrokerageSetupHandler.cs @@ -323,6 +323,9 @@ public bool Setup(SetupHandlerParameters parameters) return false; } + // after algorithm was initialized we have validated trading days per year for our great portfolio statistics + algorithm.Settings.TradingDaysPerYear ??= BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel); + //Finalize Initialization algorithm.PostInitialize(); diff --git a/Research/QuantBook.cs b/Research/QuantBook.cs index ea33660ed0c9..4966d2911a8b 100644 --- a/Research/QuantBook.cs +++ b/Research/QuantBook.cs @@ -769,7 +769,7 @@ public PyDict GetPortfolioStatistics(PyObject dataFrame) var startingCapital = Convert.ToDecimal(dictEquity.FirstOrDefault().Value); // Compute portfolio statistics - var stats = new PortfolioStatistics(profitLoss, equity, new(), listPerformance, listBenchmark, startingCapital, RiskFreeInterestRateModel, Settings.TradingDaysPerYear); + var stats = new PortfolioStatistics(profitLoss, equity, new(), listPerformance, listBenchmark, startingCapital, RiskFreeInterestRateModel, Settings.TradingDaysPerYear.Value); result.SetItem("Average Win (%)", Convert.ToDouble(stats.AverageWinRate * 100).ToPython()); result.SetItem("Average Loss (%)", Convert.ToDouble(stats.AverageLossRate * 100).ToPython()); From fcf21c6d9f50ae29123cc3ffe498554cf35d62c4 Mon Sep 17 00:00:00 2001 From: Romazes Date: Wed, 10 Jan 2024 22:12:46 +0200 Subject: [PATCH 03/19] remove: hardcoded value:252 in statistics --- Common/Statistics/PortfolioStatistics.cs | 6 +++--- Common/Statistics/Statistics.cs | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Common/Statistics/PortfolioStatistics.cs b/Common/Statistics/PortfolioStatistics.cs index 33596db8dc5b..3c10ca20a95d 100644 --- a/Common/Statistics/PortfolioStatistics.cs +++ b/Common/Statistics/PortfolioStatistics.cs @@ -182,7 +182,7 @@ public PortfolioStatistics( List listBenchmark, decimal startingCapital, IRiskFreeInterestRateModel riskFreeInterestRateModel, - int tradingDaysPerYear = 252, + int tradingDaysPerYear, int? winCount = null, int? lossCount = null) { @@ -273,7 +273,7 @@ public PortfolioStatistics( TreynorRatio = Beta == 0 ? 0 : (annualPerformance - riskFreeRate) / Beta; // deannualize a 1 sharpe ratio - var benchmarkSharpeRatio = 1.0d / Math.Sqrt(252); + var benchmarkSharpeRatio = 1.0d / Math.Sqrt(tradingDaysPerYear); ProbabilisticSharpeRatio = Statistics.ProbabilisticSharpeRatio(listPerformance, benchmarkSharpeRatio).SafeDecimalCast(); } @@ -313,7 +313,7 @@ private static decimal DrawdownPercent(SortedDictionary equit /// Trading days per year for the assets in portfolio /// May be inaccurate for forex algorithms with more trading days in a year /// Double annual performance percentage - private static decimal GetAnnualPerformance(List performance, int tradingDaysPerYear = 252) + private static decimal GetAnnualPerformance(List performance, int tradingDaysPerYear) { return Statistics.AnnualPerformance(performance, tradingDaysPerYear).SafeDecimalCast(); } diff --git a/Common/Statistics/Statistics.cs b/Common/Statistics/Statistics.cs index 2fe20b844693..2683647ecb41 100644 --- a/Common/Statistics/Statistics.cs +++ b/Common/Statistics/Statistics.cs @@ -84,7 +84,7 @@ public static decimal CompoundingAnnualPerformance(decimal startingCapital, deci /// Trading days per year for the assets in portfolio /// May be unaccurate for forex algorithms with more trading days in a year /// Double annual performance percentage - public static double AnnualPerformance(List performance, double tradingDaysPerYear = 252) + public static double AnnualPerformance(List performance, double tradingDaysPerYear) { return Math.Pow((performance.Average() + 1), tradingDaysPerYear) - 1; } @@ -96,7 +96,7 @@ public static double AnnualPerformance(List performance, double tradingD /// /// Invokes the variance extension in the MathNet Statistics class /// Annual variance value - public static double AnnualVariance(List performance, double tradingDaysPerYear = 252) + public static double AnnualVariance(List performance, double tradingDaysPerYear) { var variance = performance.Variance(); return variance.IsNaNOrZero() ? 0 : variance * tradingDaysPerYear; @@ -112,7 +112,7 @@ public static double AnnualVariance(List performance, double tradingDays /// Feasibly the trading days per year can be fetched from the dictionary of performance which includes the date-times to get the range; if is more than 1 year data. /// /// Value for annual standard deviation - public static double AnnualStandardDeviation(List performance, double tradingDaysPerYear = 252) + public static double AnnualStandardDeviation(List performance, double tradingDaysPerYear) { return Math.Sqrt(AnnualVariance(performance, tradingDaysPerYear)); } @@ -125,7 +125,7 @@ public static double AnnualStandardDeviation(List performance, double tr /// Minimum acceptable return /// Invokes the variance extension in the MathNet Statistics class /// Annual variance value - public static double AnnualDownsideVariance(List performance, double tradingDaysPerYear = 252, double minimumAcceptableReturn = 0) + public static double AnnualDownsideVariance(List performance, double tradingDaysPerYear, double minimumAcceptableReturn = 0) { return AnnualVariance(performance.Where(ret => ret < minimumAcceptableReturn).ToList(), tradingDaysPerYear); } @@ -137,7 +137,7 @@ public static double AnnualDownsideVariance(List performance, double tra /// Number of trading days for the assets in portfolio to get annualize standard deviation. /// Minimum acceptable return /// Value for annual downside standard deviation - public static double AnnualDownsideStandardDeviation(List performance, double tradingDaysPerYear = 252, double minimumAcceptableReturn = 0) + public static double AnnualDownsideStandardDeviation(List performance, double tradingDaysPerYear, double minimumAcceptableReturn = 0) { return Math.Sqrt(AnnualDownsideVariance(performance, tradingDaysPerYear, minimumAcceptableReturn)); } @@ -150,7 +150,7 @@ public static double AnnualDownsideStandardDeviation(List performance, d /// Double collection of benchmark daily performance values /// Number of trading days per year /// Value for tracking error - public static double TrackingError(List algoPerformance, List benchmarkPerformance, double tradingDaysPerYear = 252) + public static double TrackingError(List algoPerformance, List benchmarkPerformance, double tradingDaysPerYear) { // Un-equal lengths will blow up other statistics, but this will handle the case here if (algoPerformance.Count() != benchmarkPerformance.Count()) @@ -201,7 +201,7 @@ public static decimal SharpeRatio(decimal averagePerformance, decimal standardDe /// The risk free rate /// Trading days per year for the assets in portfolio /// Value for sharpe ratio - public static double SharpeRatio(List algoPerformance, double riskFreeRate, double tradingDaysPerYear=252) + public static double SharpeRatio(List algoPerformance, double riskFreeRate, double tradingDaysPerYear) { return SharpeRatio(AnnualPerformance(algoPerformance, tradingDaysPerYear), AnnualStandardDeviation(algoPerformance, tradingDaysPerYear), riskFreeRate); } @@ -215,7 +215,7 @@ public static double SharpeRatio(List algoPerformance, double riskFreeRa /// Trading days per year for the assets in portfolio /// Minimum acceptable return for Sortino ratio calculation /// Value for Sortino ratio - public static double SortinoRatio(List algoPerformance, double riskFreeRate, double tradingDaysPerYear = 252, double minimumAcceptableReturn = 0) + public static double SortinoRatio(List algoPerformance, double riskFreeRate, double tradingDaysPerYear, double minimumAcceptableReturn = 0) { return SharpeRatio(AnnualPerformance(algoPerformance, tradingDaysPerYear), AnnualDownsideStandardDeviation(algoPerformance, tradingDaysPerYear, minimumAcceptableReturn), riskFreeRate); } From 7ce7fbb2025b45b45a09d8486e0132b876902f3e Mon Sep 17 00:00:00 2001 From: Romazes Date: Wed, 10 Jan 2024 23:58:08 +0200 Subject: [PATCH 04/19] test: `algorithm.Settings.TradingDaysPerYear` for different brokerage fix: tests calculation of `AnnualPerformance()` --- Tests/Algorithm/AlgorithmSettingsTest.cs | 41 ++++++++++++++++++- .../Statistics/AnnualPerformanceTests.cs | 23 ++++++++--- Tests/Common/Statistics/TrackingErrorTests.cs | 21 +++++++--- 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/Tests/Algorithm/AlgorithmSettingsTest.cs b/Tests/Algorithm/AlgorithmSettingsTest.cs index d6315be5a824..5f6eeec9f82e 100644 --- a/Tests/Algorithm/AlgorithmSettingsTest.cs +++ b/Tests/Algorithm/AlgorithmSettingsTest.cs @@ -14,11 +14,13 @@ */ using System; using NUnit.Framework; +using QuantConnect.Util; using QuantConnect.Algorithm; +using QuantConnect.Brokerages; using QuantConnect.Data.Market; -using QuantConnect.Tests.Common.Securities; using QuantConnect.Tests.Engine.DataFeeds; -using QuantConnect.Util; +using QuantConnect.Tests.Common.Securities; +using QuantConnect.Lean.Engine.Setup; namespace QuantConnect.Tests.Algorithm { @@ -77,6 +79,41 @@ public void DefaultValueOfSetHoldingsBufferWorksCorrectly() Assert.AreEqual(4986m, actual); } + [TestCase(BrokerageName.FTX, 365)] + [TestCase(BrokerageName.RBI, 252)] + [TestCase(BrokerageName.Eze, 252)] + [TestCase(BrokerageName.Axos, 252)] + [TestCase(BrokerageName.Samco, 252)] + [TestCase(BrokerageName.FTXUS, 365)] + [TestCase(BrokerageName.Bybit, 365)] + [TestCase(BrokerageName.Kraken, 365)] + [TestCase(BrokerageName.Exante, 252)] + [TestCase(BrokerageName.Binance, 365)] + [TestCase(BrokerageName.Default, 252)] + [TestCase(BrokerageName.Zerodha, 252)] + [TestCase(BrokerageName.Bitfinex, 365)] + [TestCase(BrokerageName.Wolverine, 252)] + [TestCase(BrokerageName.TDAmeritrade, 252)] + [TestCase(BrokerageName.FxcmBrokerage, 252)] + [TestCase(BrokerageName.OandaBrokerage, 252)] + [TestCase(BrokerageName.BinanceFutures, 365)] + [TestCase(BrokerageName.TradierBrokerage, 252)] + [TestCase(BrokerageName.BinanceCoinFutures, 365)] + [TestCase(BrokerageName.TradingTechnologies, 252)] + [TestCase(BrokerageName.QuantConnectBrokerage, 252)] + [TestCase(BrokerageName.Coinbase, 365, AccountType.Cash)] + [TestCase(BrokerageName.BinanceUS, 365, AccountType.Cash)] + [TestCase(BrokerageName.InteractiveBrokersBrokerage, 252)] + public void ReturnUniqueTradingDayPerYearDependOnBrokerageName(BrokerageName brokerageName, int expectedTradingDayPerYear, AccountType accountType = AccountType.Margin) + { + var algorithm = new QCAlgorithm(); + algorithm.SetBrokerageModel(brokerageName, accountType); + + algorithm.Settings.TradingDaysPerYear ??= BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel); + + Assert.AreEqual(expectedTradingDayPerYear, algorithm.Settings.TradingDaysPerYear.Value); + } + private FakeOrderProcessor InitializeAndGetFakeOrderProcessor(QCAlgorithm algo) { algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo)); diff --git a/Tests/Common/Statistics/AnnualPerformanceTests.cs b/Tests/Common/Statistics/AnnualPerformanceTests.cs index 1741bf38b0b9..3461301ff55b 100644 --- a/Tests/Common/Statistics/AnnualPerformanceTests.cs +++ b/Tests/Common/Statistics/AnnualPerformanceTests.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -18,6 +18,8 @@ using QuantConnect.Util; using System; using QuantConnect.Data.Market; +using QuantConnect.Algorithm; +using QuantConnect.Lean.Engine.Setup; namespace QuantConnect.Tests.Common.Statistics { @@ -26,9 +28,18 @@ public class AnnualPerformanceTests { private List _spy = new List(); + /// + /// Instance of QC Algorithm. + /// Use to get for clear calculation in + /// + private QCAlgorithm _algorithm; + [SetUp] public void GetSPY() { + _algorithm = new QCAlgorithm(); + _algorithm.Settings.TradingDaysPerYear = BaseSetupHandler.GetBrokerageTradingDayPerYear(_algorithm.BrokerageModel); + var symbol = Symbol.Create("SPY", SecurityType.Equity, Market.USA); var path = LeanData.GenerateZipFilePath(Globals.DataFolder, symbol, new DateTime(2020, 3, 1), Resolution.Daily, TickType.Trade); var config = new QuantConnect.Data.SubscriptionDataConfig(typeof(TradeBar), symbol, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, false, false, false); @@ -55,8 +66,8 @@ public void TotalMarketPerformance() { performance.Add((double)((_spy[i].Close / _spy[i - 1].Close) - 1)); } - - var result = QuantConnect.Statistics.Statistics.AnnualPerformance(performance); + + var result = QuantConnect.Statistics.Statistics.AnnualPerformance(performance, _algorithm.Settings.TradingDaysPerYear.Value); Assert.AreEqual(0.082859685889996371, result); } @@ -76,7 +87,7 @@ public void BearMarketPerformance() performance.Add((double)((_spy[i].Close / _spy[i - 1].Close) - 1)); } - var result = QuantConnect.Statistics.Statistics.AnnualPerformance(performance); + var result = QuantConnect.Statistics.Statistics.AnnualPerformance(performance, _algorithm.Settings.TradingDaysPerYear.Value); Assert.AreEqual(-0.41546561808009674, result); } @@ -96,7 +107,7 @@ public void BullMarketPerformance() performance.Add((double)((_spy[i].Close / _spy[i - 1].Close) - 1)); } - var result = QuantConnect.Statistics.Statistics.AnnualPerformance(performance); + var result = QuantConnect.Statistics.Statistics.AnnualPerformance(performance, _algorithm.Settings.TradingDaysPerYear.Value); Assert.AreEqual(0.19741738320179447, result); } @@ -118,7 +129,7 @@ public void AllZeros() { var performance = Enumerable.Repeat(0.0, 252).ToList(); - var result = QuantConnect.Statistics.Statistics.AnnualPerformance(performance); + var result = QuantConnect.Statistics.Statistics.AnnualPerformance(performance, _algorithm.Settings.TradingDaysPerYear.Value); Assert.AreEqual(0.0, result); } diff --git a/Tests/Common/Statistics/TrackingErrorTests.cs b/Tests/Common/Statistics/TrackingErrorTests.cs index 74640c846e63..14a9e30654d3 100644 --- a/Tests/Common/Statistics/TrackingErrorTests.cs +++ b/Tests/Common/Statistics/TrackingErrorTests.cs @@ -18,6 +18,8 @@ using NUnit.Framework; using QuantConnect.Util; using QuantConnect.Data.Market; +using QuantConnect.Algorithm; +using QuantConnect.Lean.Engine.Setup; namespace QuantConnect.Tests.Common.Statistics { @@ -29,9 +31,18 @@ public class TrackingErrorTests private List _spyPerformance = new List(); private List _aaplPerformance = new List(); + /// + /// Instance of QC Algorithm. + /// Use to get for clear calculation in + /// + private QCAlgorithm _algorithm; + [OneTimeSetUp] public void GetData() { + _algorithm = new QCAlgorithm(); + _algorithm.Settings.TradingDaysPerYear = BaseSetupHandler.GetBrokerageTradingDayPerYear(_algorithm.BrokerageModel); + var spy = Symbol.Create("SPY", SecurityType.Equity, Market.USA); var spyPath = LeanData.GenerateZipFilePath(Globals.DataFolder, spy, new DateTime(2020, 3, 1), Resolution.Daily, TickType.Trade); var spyConfig = new QuantConnect.Data.SubscriptionDataConfig(typeof(TradeBar), spy, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, false, false, false); @@ -82,7 +93,7 @@ public void Delete() [Test] public void OneYearPerformance() { - var result = QuantConnect.Statistics.Statistics.TrackingError(_aaplPerformance.Take(252).ToList(), _spyPerformance.Take(252).ToList()); + var result = QuantConnect.Statistics.Statistics.TrackingError(_aaplPerformance.Take(252).ToList(), _spyPerformance.Take(252).ToList(), _algorithm.Settings.TradingDaysPerYear.Value); Assert.AreEqual(0.52780899407691173, result); } @@ -91,7 +102,7 @@ public void OneYearPerformance() public void TotalPerformance() { // This might seem arbitrary, but there's 1 missing date vs. AAPL for SPY data, and it happens to be at line 5555 for date 2020-01-31 - var result = QuantConnect.Statistics.Statistics.TrackingError(_aaplPerformance.Take(5555).ToList(), _spyPerformance.Take(5555).ToList()); + var result = QuantConnect.Statistics.Statistics.TrackingError(_aaplPerformance.Take(5555).ToList(), _spyPerformance.Take(5555).ToList(), _algorithm.Settings.TradingDaysPerYear.Value); Assert.AreEqual(0.43074391577621751d, result, 0.00001); } @@ -104,7 +115,7 @@ public void IdenticalPerformance() var benchmarkPerformance = Enumerable.Repeat(random.NextDouble(), 252).ToList(); var algoPerformance = benchmarkPerformance.Select(element => element).ToList(); - var result = QuantConnect.Statistics.Statistics.TrackingError(algoPerformance, benchmarkPerformance); + var result = QuantConnect.Statistics.Statistics.TrackingError(algoPerformance, benchmarkPerformance, _algorithm.Settings.TradingDaysPerYear.Value); Assert.AreEqual(0.0, result); } @@ -124,7 +135,7 @@ public void DifferentPerformance() algoPerformance.Add((baseReturn * 2) + 2); } - var result = QuantConnect.Statistics.Statistics.TrackingError(algoPerformance, benchmarkPerformance); + var result = QuantConnect.Statistics.Statistics.TrackingError(algoPerformance, benchmarkPerformance, _algorithm.Settings.TradingDaysPerYear.Value); Assert.AreEqual(0.0, result); } @@ -135,7 +146,7 @@ public void AllZeros() var benchmarkPerformance = Enumerable.Repeat(0.0, 252).ToList(); var algoPerformance = Enumerable.Repeat(0.0, 252).ToList(); - var result = QuantConnect.Statistics.Statistics.TrackingError(algoPerformance, benchmarkPerformance); + var result = QuantConnect.Statistics.Statistics.TrackingError(algoPerformance, benchmarkPerformance, _algorithm.Settings.TradingDaysPerYear.Value); Assert.AreEqual(0.0, result); } From 892a10055d21d1ec53d3a312bead886c3faf6f72 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 11 Jan 2024 01:26:11 +0200 Subject: [PATCH 05/19] fix: Report generator by tradingDayPerYear --- Report/ReportElements/RollingSharpeReportElement.cs | 10 +++++----- Report/ReportElements/SharpeRatioReportElement.cs | 7 +++++-- Report/ReportElements/SortinoRatioReportElement.cs | 5 ++++- Report/Rolling.cs | 5 +++-- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Report/ReportElements/RollingSharpeReportElement.cs b/Report/ReportElements/RollingSharpeReportElement.cs index 3ba6bcb5f331..c0b85a6949a4 100644 --- a/Report/ReportElements/RollingSharpeReportElement.cs +++ b/Report/ReportElements/RollingSharpeReportElement.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -52,10 +52,10 @@ public override string Render() var backtestSeries = new Series(backtestPoints); var liveSeries = new Series(livePoints); - var backtestRollingSharpeSixMonths = Rolling.Sharpe(backtestSeries, 6).DropMissing(); - var backtestRollingSharpeTwelveMonths = Rolling.Sharpe(backtestSeries, 12).DropMissing(); - var liveRollingSharpeSixMonths = Rolling.Sharpe(liveSeries, 6).DropMissing(); - var liveRollingSharpeTwelveMonths = Rolling.Sharpe(liveSeries, 12).DropMissing(); + var backtestRollingSharpeSixMonths = Rolling.Sharpe(backtestSeries, 6, _backtest.AlgorithmConfiguration.TradingDaysPerYear).DropMissing(); + var backtestRollingSharpeTwelveMonths = Rolling.Sharpe(backtestSeries, 12, _backtest.AlgorithmConfiguration.TradingDaysPerYear).DropMissing(); + var liveRollingSharpeSixMonths = Rolling.Sharpe(liveSeries, 6, _live.AlgorithmConfiguration.TradingDaysPerYear).DropMissing(); + var liveRollingSharpeTwelveMonths = Rolling.Sharpe(liveSeries, 12, _live.AlgorithmConfiguration.TradingDaysPerYear).DropMissing(); var base64 = ""; using (Py.GIL()) diff --git a/Report/ReportElements/SharpeRatioReportElement.cs b/Report/ReportElements/SharpeRatioReportElement.cs index a2195aba6cfe..b400031e492d 100644 --- a/Report/ReportElements/SharpeRatioReportElement.cs +++ b/Report/ReportElements/SharpeRatioReportElement.cs @@ -17,6 +17,7 @@ using QuantConnect.Packets; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; namespace QuantConnect.Report.ReportElements @@ -82,7 +83,8 @@ public override string Render() return "-"; } - var annualPerformance = Statistics.Statistics.AnnualPerformance(trailingPerformance); + var annualPerformance = Statistics.Statistics.AnnualPerformance(trailingPerformance, + Convert.ToDouble(BacktestResult?.AlgorithmConfiguration?.TradingDaysPerYear, CultureInfo.InvariantCulture)); var liveResultValue = Statistics.Statistics.SharpeRatio(annualPerformance, annualStandardDeviation, 0.0); Result = liveResultValue; return liveResultValue.ToString("F2"); @@ -95,7 +97,8 @@ public override string Render() /// Annual standard deviation. public virtual double GetAnnualStandardDeviation(List trailingPerformance) { - return Statistics.Statistics.AnnualStandardDeviation(trailingPerformance); + return Statistics.Statistics.AnnualStandardDeviation(trailingPerformance, + Convert.ToDouble(BacktestResult?.AlgorithmConfiguration?.TradingDaysPerYear, CultureInfo.InvariantCulture)); } } } diff --git a/Report/ReportElements/SortinoRatioReportElement.cs b/Report/ReportElements/SortinoRatioReportElement.cs index 64b296fed0cf..d3b9a5698141 100644 --- a/Report/ReportElements/SortinoRatioReportElement.cs +++ b/Report/ReportElements/SortinoRatioReportElement.cs @@ -13,6 +13,8 @@ * limitations under the License. */ +using System; +using System.Globalization; using QuantConnect.Packets; using System.Collections.Generic; @@ -44,7 +46,8 @@ public SortinoRatioReportElement(string name, string key, BacktestResult backtes /// Annual downside standard deviation. public override double GetAnnualStandardDeviation(List trailingPerformance) { - return Statistics.Statistics.AnnualDownsideStandardDeviation(trailingPerformance); + return Statistics.Statistics.AnnualDownsideStandardDeviation(trailingPerformance, + Convert.ToDouble(BacktestResult?.AlgorithmConfiguration?.TradingDaysPerYear, CultureInfo.InvariantCulture)); } } } diff --git a/Report/Rolling.cs b/Report/Rolling.cs index ce5aecda976c..4581b5c78f2f 100644 --- a/Report/Rolling.cs +++ b/Report/Rolling.cs @@ -76,8 +76,9 @@ public static Series Beta(SortedList perform /// /// Equity curve to calculate rolling sharpe for /// Number of months to calculate the rolling period for + /// The number of trading days per year to increase result of Annual statistics /// Rolling sharpe ratio - public static Series Sharpe(Series equityCurve, int months) + public static Series Sharpe(Series equityCurve, int months, int tradingDayPerYear) { var riskFreeRate = (double)_interestRateProvider.GetAverageRiskFreeRate(equityCurve.Keys); if (equityCurve.IsEmpty) @@ -103,7 +104,7 @@ public static Series Sharpe(Series equityCur rollingSharpeData.Add( new KeyValuePair( date, - Statistics.Statistics.SharpeRatio(algoPerformanceLookback.Values.ToList(), riskFreeRate) + Statistics.Statistics.SharpeRatio(algoPerformanceLookback.Values.ToList(), riskFreeRate, tradingDayPerYear) ) ); } From 73a5b83c4d33c8fc5780fd9f9ecb6fe2cb462064 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 11 Jan 2024 01:54:12 +0200 Subject: [PATCH 06/19] fix: hardcoded value in PortfolioStatisticsTests --- Tests/Common/Statistics/PortfolioStatisticsTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/Common/Statistics/PortfolioStatisticsTests.cs b/Tests/Common/Statistics/PortfolioStatisticsTests.cs index 7605bda96f24..f3483fc59c62 100644 --- a/Tests/Common/Statistics/PortfolioStatisticsTests.cs +++ b/Tests/Common/Statistics/PortfolioStatisticsTests.cs @@ -19,6 +19,8 @@ using NUnit.Framework; using QuantConnect.Data; using QuantConnect.Statistics; +using QuantConnect.Brokerages; +using QuantConnect.Lean.Engine.Setup; namespace QuantConnect.Tests.Common.Statistics { @@ -35,9 +37,10 @@ public void ITMOptionAssignment([Values] bool win) var profitLoss = new SortedDictionary(trades.ToDictionary(x => x.ExitTime, x => x.ProfitLoss)); var winCount = trades.Count(x => x.IsWin); var lossCount = trades.Count - winCount; + var tradingDayPerYears = BaseSetupHandler.GetBrokerageTradingDayPerYear(new DefaultBrokerageModel()); var statistics = new PortfolioStatistics(profitLoss, new SortedDictionary(), new SortedDictionary(), new List { 0, 0 }, new List { 0, 0 }, 100000, - new InterestRateProvider(), 252, winCount, lossCount); + new InterestRateProvider(), tradingDayPerYears, winCount, lossCount); if (win) { From b7d6853f9ec0995756e417b1cc46c1cd4fd768ff Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 11 Jan 2024 02:03:27 +0200 Subject: [PATCH 07/19] fix: tradingDayPerYear in AnnualPerformanceTest remove: extra spacing --- Tests/Common/Statistics/AnnualPerformanceTests.cs | 2 +- Tests/Common/Statistics/StatisticsBuilderTests.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/Common/Statistics/AnnualPerformanceTests.cs b/Tests/Common/Statistics/AnnualPerformanceTests.cs index 3461301ff55b..f70fa626c080 100644 --- a/Tests/Common/Statistics/AnnualPerformanceTests.cs +++ b/Tests/Common/Statistics/AnnualPerformanceTests.cs @@ -66,7 +66,7 @@ public void TotalMarketPerformance() { performance.Add((double)((_spy[i].Close / _spy[i - 1].Close) - 1)); } - + var result = QuantConnect.Statistics.Statistics.AnnualPerformance(performance, _algorithm.Settings.TradingDaysPerYear.Value); Assert.AreEqual(0.082859685889996371, result); diff --git a/Tests/Common/Statistics/StatisticsBuilderTests.cs b/Tests/Common/Statistics/StatisticsBuilderTests.cs index 75457d2ae211..a5ee78aaa98b 100644 --- a/Tests/Common/Statistics/StatisticsBuilderTests.cs +++ b/Tests/Common/Statistics/StatisticsBuilderTests.cs @@ -19,6 +19,8 @@ using NUnit.Framework; using QuantConnect.Data; using QuantConnect.Statistics; +using QuantConnect.Brokerages; +using QuantConnect.Lean.Engine.Setup; namespace QuantConnect.Tests.Common.Statistics { @@ -78,7 +80,7 @@ public void MisalignedValues_ShouldThrow_DuringGeneration() null, new QuantConnect.Securities.SecurityManager(new TimeKeeper(DateTime.UtcNow))), new InterestRateProvider(), - 252); + BaseSetupHandler.GetBrokerageTradingDayPerYear(new DefaultBrokerageModel())); }, "Misaligned values provided, but we still generate statistics"); } } From b2b96efad78919f60269f0292e3c68d4c30866be Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 11 Jan 2024 16:43:19 +0200 Subject: [PATCH 08/19] feat: backwards compatibility for TradingDaysPerYear --- Common/AlgorithmConfiguration.cs | 3 ++- Engine/Results/BaseResultsHandler.cs | 6 ++++-- Report/ReportElements/RollingSharpeReportElement.cs | 9 +++++---- Research/QuantBook.cs | 5 ++++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Common/AlgorithmConfiguration.cs b/Common/AlgorithmConfiguration.cs index 425a402ab0e9..a7400bcbf0a5 100644 --- a/Common/AlgorithmConfiguration.cs +++ b/Common/AlgorithmConfiguration.cs @@ -128,7 +128,8 @@ public static AlgorithmConfiguration Create(IAlgorithm algorithm, BacktestNodePa algorithm.EndDate, backtestNodePacket?.OutOfSampleMaxEndDate, backtestNodePacket?.OutOfSampleDays ?? 0, - algorithm.Settings.TradingDaysPerYear ?? 0); + // use value = 252 like default for backwards compatibility + algorithm?.Settings?.TradingDaysPerYear ?? 252); } } } diff --git a/Engine/Results/BaseResultsHandler.cs b/Engine/Results/BaseResultsHandler.cs index 881b2a0add5b..87fb017cf3db 100644 --- a/Engine/Results/BaseResultsHandler.cs +++ b/Engine/Results/BaseResultsHandler.cs @@ -846,10 +846,12 @@ protected StatisticsResults GenerateStatisticsResults(Dictionary portfolioTurnover = new Series(); } + // use value = 252 like default for backwards compatibility + var tradingDaysPerYear = Algorithm.Settings.TradingDaysPerYear.HasValue ? Algorithm.Settings.TradingDaysPerYear.Value : 252; + statisticsResults = StatisticsBuilder.Generate(trades, profitLoss, equity.Values, performance.Values, benchmark.Values, portfolioTurnover.Values, StartingPortfolioValue, Algorithm.Portfolio.TotalFees, totalTransactions, - estimatedStrategyCapacity, AlgorithmCurrencySymbol, Algorithm.Transactions, Algorithm.RiskFreeInterestRateModel, - Algorithm.Settings.TradingDaysPerYear.Value); + estimatedStrategyCapacity, AlgorithmCurrencySymbol, Algorithm.Transactions, Algorithm.RiskFreeInterestRateModel, tradingDaysPerYear); } statisticsResults.AddCustomSummaryStatistics(_customSummaryStatistics); diff --git a/Report/ReportElements/RollingSharpeReportElement.cs b/Report/ReportElements/RollingSharpeReportElement.cs index c0b85a6949a4..51f903b04af8 100644 --- a/Report/ReportElements/RollingSharpeReportElement.cs +++ b/Report/ReportElements/RollingSharpeReportElement.cs @@ -52,10 +52,11 @@ public override string Render() var backtestSeries = new Series(backtestPoints); var liveSeries = new Series(livePoints); - var backtestRollingSharpeSixMonths = Rolling.Sharpe(backtestSeries, 6, _backtest.AlgorithmConfiguration.TradingDaysPerYear).DropMissing(); - var backtestRollingSharpeTwelveMonths = Rolling.Sharpe(backtestSeries, 12, _backtest.AlgorithmConfiguration.TradingDaysPerYear).DropMissing(); - var liveRollingSharpeSixMonths = Rolling.Sharpe(liveSeries, 6, _live.AlgorithmConfiguration.TradingDaysPerYear).DropMissing(); - var liveRollingSharpeTwelveMonths = Rolling.Sharpe(liveSeries, 12, _live.AlgorithmConfiguration.TradingDaysPerYear).DropMissing(); + // use value = 252 like default for backwards compatibility + var backtestRollingSharpeSixMonths = Rolling.Sharpe(backtestSeries, 6, _backtest.AlgorithmConfiguration?.TradingDaysPerYear ?? 252).DropMissing(); + var backtestRollingSharpeTwelveMonths = Rolling.Sharpe(backtestSeries, 12, _backtest.AlgorithmConfiguration?.TradingDaysPerYear ?? 252).DropMissing(); + var liveRollingSharpeSixMonths = Rolling.Sharpe(liveSeries, 6, _live?.AlgorithmConfiguration?.TradingDaysPerYear ?? 252).DropMissing(); + var liveRollingSharpeTwelveMonths = Rolling.Sharpe(liveSeries, 12, _live?.AlgorithmConfiguration?.TradingDaysPerYear ?? 252).DropMissing(); var base64 = ""; using (Py.GIL()) diff --git a/Research/QuantBook.cs b/Research/QuantBook.cs index 4966d2911a8b..29a882e31d72 100644 --- a/Research/QuantBook.cs +++ b/Research/QuantBook.cs @@ -768,8 +768,11 @@ public PyDict GetPortfolioStatistics(PyObject dataFrame) // Gets the startting capital var startingCapital = Convert.ToDecimal(dictEquity.FirstOrDefault().Value); + // use value = 252 like default for backwards compatibility + var tradingDaysPerYear = Settings.TradingDaysPerYear.HasValue ? Settings.TradingDaysPerYear.Value : 252; + // Compute portfolio statistics - var stats = new PortfolioStatistics(profitLoss, equity, new(), listPerformance, listBenchmark, startingCapital, RiskFreeInterestRateModel, Settings.TradingDaysPerYear.Value); + var stats = new PortfolioStatistics(profitLoss, equity, new(), listPerformance, listBenchmark, startingCapital, RiskFreeInterestRateModel, tradingDaysPerYear); result.SetItem("Average Win (%)", Convert.ToDouble(stats.AverageWinRate * 100).ToPython()); result.SetItem("Average Loss (%)", Convert.ToDouble(stats.AverageLossRate * 100).ToPython()); From 287afbb11da3453ac8ca6419e7e31b2c983db8b6 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 11 Jan 2024 16:44:01 +0200 Subject: [PATCH 09/19] feat: several UTest for AlgoSetting's TradingDaysPerYear prop --- Tests/Algorithm/AlgorithmSettingsTest.cs | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Tests/Algorithm/AlgorithmSettingsTest.cs b/Tests/Algorithm/AlgorithmSettingsTest.cs index 5f6eeec9f82e..ce34064350f0 100644 --- a/Tests/Algorithm/AlgorithmSettingsTest.cs +++ b/Tests/Algorithm/AlgorithmSettingsTest.cs @@ -114,6 +114,36 @@ public void ReturnUniqueTradingDayPerYearDependOnBrokerageName(BrokerageName bro Assert.AreEqual(expectedTradingDayPerYear, algorithm.Settings.TradingDaysPerYear.Value); } + [TestCase(BrokerageName.Bybit, 202)] + [TestCase(BrokerageName.InteractiveBrokersBrokerage, 404)] + public void ReturnCustomTradingDayPerYearIndependentlyFromBrokerageName(BrokerageName brokerageName, int customTradingDayPerYear) + { + var algorithm = new QCAlgorithm(); + algorithm.SetBrokerageModel(brokerageName); + algorithm.Settings.TradingDaysPerYear = customTradingDayPerYear; + + // duplicate: make sure that custom value is assigned + algorithm.Settings.TradingDaysPerYear ??= BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel); + + Assert.AreNotEqual(BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel), algorithm.Settings.TradingDaysPerYear); + } + + [TestCase(null, 252)] + [TestCase(404, 404)] + public void ReturnTradingDayPerYearWithoutSetBrokerage(int? customTradingDayPerYear = null, int expectedTradingDayPerYear = 0) + { + var algorithm = new QCAlgorithm(); + + if (customTradingDayPerYear.HasValue) + { + algorithm.Settings.TradingDaysPerYear = customTradingDayPerYear.Value; + } + + algorithm.Settings.TradingDaysPerYear ??= BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel); + + Assert.AreEqual(expectedTradingDayPerYear, algorithm.Settings.TradingDaysPerYear); + } + private FakeOrderProcessor InitializeAndGetFakeOrderProcessor(QCAlgorithm algo) { algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo)); From af9f9ceb0e1ac4921a1158dbac96f72a2ec65ded Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 11 Jan 2024 18:32:55 +0200 Subject: [PATCH 10/19] fix: existed algo + test --- .../FractionalQuantityRegressionAlgorithm.cs | 14 +++++++------- Tests/Common/AlgorithmConfigurationTests.cs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Algorithm.CSharp/FractionalQuantityRegressionAlgorithm.cs b/Algorithm.CSharp/FractionalQuantityRegressionAlgorithm.cs index 8256ef26692c..f6795f4839cc 100644 --- a/Algorithm.CSharp/FractionalQuantityRegressionAlgorithm.cs +++ b/Algorithm.CSharp/FractionalQuantityRegressionAlgorithm.cs @@ -115,18 +115,18 @@ private void DataConsolidated(object sender, TradeBar e) {"Drawdown", "5.500%"}, {"Expectancy", "1.339"}, {"Net Profit", "13.775%"}, - {"Sharpe Ratio", "3.289"}, - {"Sortino Ratio", "7.697"}, - {"Probabilistic Sharpe Ratio", "61.758%"}, + {"Sharpe Ratio", "4.906"}, + {"Sortino Ratio", "11.482"}, + {"Probabilistic Sharpe Ratio", "63.428%"}, {"Loss Rate", "33%"}, {"Win Rate", "67%"}, {"Profit-Loss Ratio", "2.51"}, {"Alpha", "0"}, {"Beta", "0"}, - {"Annual Standard Deviation", "0.379"}, - {"Annual Variance", "0.144"}, - {"Information Ratio", "3.309"}, - {"Tracking Error", "0.379"}, + {"Annual Standard Deviation", "0.456"}, + {"Annual Variance", "0.208"}, + {"Information Ratio", "4.922"}, + {"Tracking Error", "0.456"}, {"Treynor Ratio", "0"}, {"Total Fees", "$2650.41"}, {"Estimated Strategy Capacity", "$30000.00"}, diff --git a/Tests/Common/AlgorithmConfigurationTests.cs b/Tests/Common/AlgorithmConfigurationTests.cs index 93f140493319..4c6351f439f9 100644 --- a/Tests/Common/AlgorithmConfigurationTests.cs +++ b/Tests/Common/AlgorithmConfigurationTests.cs @@ -63,7 +63,7 @@ public void JsonSerialization() var serialized = JsonConvert.SerializeObject(algorithmConfiguration); - Assert.AreEqual($"{{\"AccountCurrency\":\"GBP\",\"Brokerage\":32,\"AccountType\":1,\"Parameters\":{{\"a\":\"A\",\"b\":\"B\"}},\"OutOfSampleMaxEndDate\":\"2023-01-01T00:00:00\",\"OutOfSampleDays\":30,\"StartDate\":\"1998-01-01 00:00:00\",\"EndDate\":\"{algorithm.EndDate.ToString(DateFormat.UI)}\"}}", serialized); + Assert.AreEqual($"{{\"AccountCurrency\":\"GBP\",\"Brokerage\":32,\"AccountType\":1,\"Parameters\":{{\"a\":\"A\",\"b\":\"B\"}},\"OutOfSampleMaxEndDate\":\"2023-01-01T00:00:00\",\"OutOfSampleDays\":30,\"StartDate\":\"1998-01-01 00:00:00\",\"EndDate\":\"{algorithm.EndDate.ToString(DateFormat.UI)}\",\"TradingDaysPerYear\":252}}", serialized); } private static TestCaseData[] AlgorithmConfigurationTestCases => new[] From 0507875e1e8ce9fde936f9c647564fb577af1ba7 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 11 Jan 2024 18:33:47 +0200 Subject: [PATCH 11/19] feat: regression algo with checking of tradingDayPerYear in OnEndOfAlgorithm() --- ...ptoYearMarketTradingRegressionAlgorithm.cs | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 Algorithm.CSharp/CoinbaseCryptoYearMarketTradingRegressionAlgorithm.cs diff --git a/Algorithm.CSharp/CoinbaseCryptoYearMarketTradingRegressionAlgorithm.cs b/Algorithm.CSharp/CoinbaseCryptoYearMarketTradingRegressionAlgorithm.cs new file mode 100644 index 000000000000..f6e77a07bc97 --- /dev/null +++ b/Algorithm.CSharp/CoinbaseCryptoYearMarketTradingRegressionAlgorithm.cs @@ -0,0 +1,141 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using QuantConnect.Data; +using QuantConnect.Brokerages; +using QuantConnect.Interfaces; +using System.Collections.Generic; +using QuantConnect.Securities.Crypto; + +namespace QuantConnect.Algorithm.CSharp +{ + public class CoinbaseCryptoYearMarketTradingRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition + { + /// + /// The Average amount of day in year + /// + /// Regardless of calendar + private const int _daysInYear = 365; + + /// + /// Flag prevents same order + /// + private bool _isBuy; + + /// + /// Trading security + /// + private Crypto _BTCUSD; + + /// + /// Initialize the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized. + /// + /// In fact, you can assign custom value for + public override void Initialize() + { + SetStartDate(2015, 01, 13); + SetEndDate(2016, 02, 03); + + SetCash(100000); + + // Setup brokerage for current algorithm + SetBrokerageModel(BrokerageName.Coinbase, AccountType.Cash); + + _BTCUSD = AddCrypto("BTCUSD", Resolution.Daily, Market.Coinbase); + } + + /// + /// Data Event Handler: receiving all subscription data in a single event + /// + /// The current slice of data keyed by symbol string + public override void OnData(Slice slice) + { + if (!_isBuy) + { + MarketOrder(_BTCUSD, 1); + _isBuy = true; + } + else + { + Liquidate(); + _isBuy = false; + } + } + + /// + /// End of algorithm run event handler. This method is called at the end of a backtest or live trading operation. Intended for closing out logs. + /// + public override void OnEndOfAlgorithm() + { + if (Settings.TradingDaysPerYear != _daysInYear) + { + throw new Exception("The Algorithm was using invalid `TradingDaysPerYear` for this brokerage. The ExpectedStatistics is wrong."); + } + } + + /// + /// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. + /// + public bool CanRunLocally => true; + + /// + /// This is used by the regression test system to indicate which languages this algorithm is written in. + /// + public Language[] Languages { get; } = { Language.CSharp }; + + /// + /// Data Points count of all time slices of algorithm + /// + public long DataPoints => 673; + + /// + /// Data Points count of the algorithm history + /// + public int AlgorithmHistoryDataPoints => 43; + + /// + /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm + /// + public Dictionary ExpectedStatistics => new Dictionary + { + {"Total Trades", "388"}, + {"Average Win", "0.01%"}, + {"Average Loss", "-0.01%"}, + {"Compounding Annual Return", "-0.597%"}, + {"Drawdown", "0.700%"}, + {"Expectancy", "-0.400"}, + {"Net Profit", "-0.634%"}, + {"Sharpe Ratio", "-7.126"}, + {"Sortino Ratio", "-7.337"}, + {"Probabilistic Sharpe Ratio", "0.000%"}, + {"Loss Rate", "66%"}, + {"Win Rate", "34%"}, + {"Profit-Loss Ratio", "0.79"}, + {"Alpha", "0"}, + {"Beta", "0"}, + {"Annual Standard Deviation", "0.002"}, + {"Annual Variance", "0"}, + {"Information Ratio", "-3.086"}, + {"Tracking Error", "0.002"}, + {"Treynor Ratio", "0"}, + {"Total Fees", "$331.31"}, + {"Estimated Strategy Capacity", "$71000.00"}, + {"Lowest Capacity Asset", "BTCUSD 2XR"}, + {"Portfolio Turnover", "0.29%"}, + {"OrderListHash", "3833cb6d62095f129d078c8ccd57a0a6"} + }; + } +} From e2f43a3512ffb9ccd5775823197782906dad8521 Mon Sep 17 00:00:00 2001 From: Romazes Date: Fri, 12 Jan 2024 19:26:35 +0200 Subject: [PATCH 12/19] refactor: GetBrokerageTradingDayPerYear -> SetBrokerage... in BaseSetupHandler refactor: carry out tradingDayPerYear at the top level and pass variable --- Engine/Results/BaseResultsHandler.cs | 8 +++-- Engine/Setup/BacktestingSetupHandler.cs | 4 +-- Engine/Setup/BaseSetupHandler.cs | 29 ++++++++++--------- Engine/Setup/BrokerageSetupHandler.cs | 4 +-- Report/Report.cs | 10 +++++-- .../RollingSharpeReportElement.cs | 17 +++++++---- .../SharpeRatioReportElement.cs | 20 ++++++++----- .../SortinoRatioReportElement.cs | 11 +++---- Research/QuantBook.cs | 8 +++-- Tests/Algorithm/AlgorithmSettingsTest.cs | 14 ++++----- .../Statistics/AnnualPerformanceTests.cs | 2 +- .../Statistics/PortfolioStatisticsTests.cs | 11 ++++--- .../Statistics/StatisticsBuilderTests.cs | 10 +++++-- Tests/Common/Statistics/TrackingErrorTests.cs | 2 +- 14 files changed, 91 insertions(+), 59 deletions(-) diff --git a/Engine/Results/BaseResultsHandler.cs b/Engine/Results/BaseResultsHandler.cs index 87fb017cf3db..7a8be092f440 100644 --- a/Engine/Results/BaseResultsHandler.cs +++ b/Engine/Results/BaseResultsHandler.cs @@ -26,6 +26,7 @@ using QuantConnect.Data.UniverseSelection; using QuantConnect.Indicators; using QuantConnect.Interfaces; +using QuantConnect.Lean.Engine.Setup; using QuantConnect.Lean.Engine.TransactionHandlers; using QuantConnect.Logging; using QuantConnect.Orders; @@ -846,12 +847,13 @@ protected StatisticsResults GenerateStatisticsResults(Dictionary portfolioTurnover = new Series(); } - // use value = 252 like default for backwards compatibility - var tradingDaysPerYear = Algorithm.Settings.TradingDaysPerYear.HasValue ? Algorithm.Settings.TradingDaysPerYear.Value : 252; + // call method to set tradingDayPerYear for Algorithm (use: backwards compatibility) + BaseSetupHandler.SetBrokerageTradingDayPerYear(Algorithm); statisticsResults = StatisticsBuilder.Generate(trades, profitLoss, equity.Values, performance.Values, benchmark.Values, portfolioTurnover.Values, StartingPortfolioValue, Algorithm.Portfolio.TotalFees, totalTransactions, - estimatedStrategyCapacity, AlgorithmCurrencySymbol, Algorithm.Transactions, Algorithm.RiskFreeInterestRateModel, tradingDaysPerYear); + estimatedStrategyCapacity, AlgorithmCurrencySymbol, Algorithm.Transactions, Algorithm.RiskFreeInterestRateModel, + Algorithm.Settings.TradingDaysPerYear.Value); } statisticsResults.AddCustomSummaryStatistics(_customSummaryStatistics); diff --git a/Engine/Setup/BacktestingSetupHandler.cs b/Engine/Setup/BacktestingSetupHandler.cs index 2d8c834aca5c..53a1ed0910e6 100644 --- a/Engine/Setup/BacktestingSetupHandler.cs +++ b/Engine/Setup/BacktestingSetupHandler.cs @@ -208,8 +208,8 @@ public bool Setup(SetupHandlerParameters parameters) // after we call initialize BaseSetupHandler.LoadBacktestJobCashAmount(algorithm, job); - // after algorithm was initialized we have validated trading days per year for our great portfolio statistics - algorithm.Settings.TradingDaysPerYear ??= BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel); + // after algorithm was initialized, should set trading days per year for our great portfolio statistics + BaseSetupHandler.SetBrokerageTradingDayPerYear(algorithm); // finalize initialization algorithm.PostInitialize(); diff --git a/Engine/Setup/BaseSetupHandler.cs b/Engine/Setup/BaseSetupHandler.cs index a43269078f90..d9000ffe40b6 100644 --- a/Engine/Setup/BaseSetupHandler.cs +++ b/Engine/Setup/BaseSetupHandler.cs @@ -234,27 +234,30 @@ public static Dictionary> GetConfiguredDataFeeds() } /// - /// Gets the number of trading days per year based on the specified brokerage model. + /// Set the number of trading days per year based on the specified brokerage model. /// - /// The brokerage model for which to determine the trading days. + /// The algorithm instance /// /// The number of trading days per year. For specific brokerages (Coinbase, Binance, Bitfinex, Bybit, FTX, Kraken), /// the value is 365. For other brokerages, the default value is 252. /// - public static int GetBrokerageTradingDayPerYear(IBrokerageModel brokerageModel) + public static void SetBrokerageTradingDayPerYear(IAlgorithm algorithm) { - switch (brokerageModel) + if (algorithm == null) { - case CoinbaseBrokerageModel: - case BinanceBrokerageModel: - case BitfinexBrokerageModel: - case BybitBrokerageModel: - case FTXBrokerageModel: - case KrakenBrokerageModel: - return 365; - default: - return 252; + throw new ArgumentNullException(nameof(algorithm)); } + + algorithm.Settings.TradingDaysPerYear ??= algorithm.BrokerageModel switch + { + CoinbaseBrokerageModel + or BinanceBrokerageModel + or BitfinexBrokerageModel + or BybitBrokerageModel + or FTXBrokerageModel + or KrakenBrokerageModel => 365, + _ => 252 + }; } } } diff --git a/Engine/Setup/BrokerageSetupHandler.cs b/Engine/Setup/BrokerageSetupHandler.cs index 2f6e5fc97ce0..7d10f5d92a0d 100644 --- a/Engine/Setup/BrokerageSetupHandler.cs +++ b/Engine/Setup/BrokerageSetupHandler.cs @@ -323,8 +323,8 @@ public bool Setup(SetupHandlerParameters parameters) return false; } - // after algorithm was initialized we have validated trading days per year for our great portfolio statistics - algorithm.Settings.TradingDaysPerYear ??= BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel); + // after algorithm was initialized, should set trading days per year for our great portfolio statistics + BaseSetupHandler.SetBrokerageTradingDayPerYear(algorithm); //Finalize Initialization algorithm.PostInitialize(); diff --git a/Report/Report.cs b/Report/Report.cs index f05da1f1612a..aa09931624bf 100644 --- a/Report/Report.cs +++ b/Report/Report.cs @@ -67,6 +67,10 @@ public Report(string name, string description, string version, BacktestResult ba var backtestConfiguration = backtest?.AlgorithmConfiguration; var liveConfiguration = live?.AlgorithmConfiguration; + // Earlier we use constant's value tradingDaysPerYear = 252 + // backtestConfiguration?.TradingDaysPerYear equal liveConfiguration?.TradingDaysPerYear + var tradingDayPerYear = backtestConfiguration?.TradingDaysPerYear ?? 252; + Log.Trace($"QuantConnect.Report.Report(): Processing backtesting orders"); var backtestPortfolioInTime = PortfolioLooper.FromOrders(backtestCurve, backtestOrders, backtestConfiguration).ToList(); Log.Trace($"QuantConnect.Report.Report(): Processing live orders"); @@ -119,8 +123,8 @@ public Report(string name, string description, string version, BacktestResult ba new CAGRReportElement("cagr kpi", ReportKey.CAGR, backtest, live), new TurnoverReportElement("turnover kpi", ReportKey.Turnover, backtest, live), new MaxDrawdownReportElement("max drawdown kpi", ReportKey.MaxDrawdown, backtest, live), - new SharpeRatioReportElement("sharpe kpi", ReportKey.SharpeRatio, backtest, live), - new SortinoRatioReportElement("sortino kpi", ReportKey.SortinoRatio, backtest, live), + new SharpeRatioReportElement("sharpe kpi", ReportKey.SharpeRatio, backtest, live, tradingDayPerYear), + new SortinoRatioReportElement("sortino kpi", ReportKey.SortinoRatio, backtest, live, tradingDayPerYear), new PSRReportElement("psr kpi", ReportKey.PSR, backtest, live), new InformationRatioReportElement("ir kpi", ReportKey.InformationRatio, backtest, live), new MarketsReportElement("markets kpi", ReportKey.Markets, backtest, live), @@ -136,7 +140,7 @@ public Report(string name, string description, string version, BacktestResult ba new DrawdownReportElement("drawdown plot", ReportKey.Drawdown, backtest, live), new DailyReturnsReportElement("daily returns plot", ReportKey.DailyReturns, backtest, live), new RollingPortfolioBetaReportElement("rolling beta to equities plot", ReportKey.RollingBeta, backtest, live), - new RollingSharpeReportElement("rolling sharpe ratio plot", ReportKey.RollingSharpe, backtest, live), + new RollingSharpeReportElement("rolling sharpe ratio plot", ReportKey.RollingSharpe, backtest, live, tradingDayPerYear), new LeverageUtilizationReportElement("leverage plot", ReportKey.LeverageUtilization, backtest, live, backtestPortfolioInTime, livePortfolioInTime), new ExposureReportElement("exposure plot", ReportKey.Exposure, backtest, live, backtestPortfolioInTime, livePortfolioInTime) }; diff --git a/Report/ReportElements/RollingSharpeReportElement.cs b/Report/ReportElements/RollingSharpeReportElement.cs index 51f903b04af8..3455552e7bb6 100644 --- a/Report/ReportElements/RollingSharpeReportElement.cs +++ b/Report/ReportElements/RollingSharpeReportElement.cs @@ -26,6 +26,11 @@ internal sealed class RollingSharpeReportElement : ChartReportElement private LiveResult _live; private BacktestResult _backtest; + /// + /// The number of trading days per year to get better result of statistics + /// + private int _tradingDaysPerYear; + /// /// Create a new plot of the rolling sharpe ratio /// @@ -33,12 +38,14 @@ internal sealed class RollingSharpeReportElement : ChartReportElement /// Location of injection /// Backtest result object /// Live result object - public RollingSharpeReportElement(string name, string key, BacktestResult backtest, LiveResult live) + /// The number of trading days per year to get better result of statistics + public RollingSharpeReportElement(string name, string key, BacktestResult backtest, LiveResult live, int tradingDaysPerYear) { _live = live; _backtest = backtest; Name = name; Key = key; + _tradingDaysPerYear = tradingDaysPerYear; } /// @@ -53,10 +60,10 @@ public override string Render() var liveSeries = new Series(livePoints); // use value = 252 like default for backwards compatibility - var backtestRollingSharpeSixMonths = Rolling.Sharpe(backtestSeries, 6, _backtest.AlgorithmConfiguration?.TradingDaysPerYear ?? 252).DropMissing(); - var backtestRollingSharpeTwelveMonths = Rolling.Sharpe(backtestSeries, 12, _backtest.AlgorithmConfiguration?.TradingDaysPerYear ?? 252).DropMissing(); - var liveRollingSharpeSixMonths = Rolling.Sharpe(liveSeries, 6, _live?.AlgorithmConfiguration?.TradingDaysPerYear ?? 252).DropMissing(); - var liveRollingSharpeTwelveMonths = Rolling.Sharpe(liveSeries, 12, _live?.AlgorithmConfiguration?.TradingDaysPerYear ?? 252).DropMissing(); + var backtestRollingSharpeSixMonths = Rolling.Sharpe(backtestSeries, 6, _tradingDaysPerYear).DropMissing(); + var backtestRollingSharpeTwelveMonths = Rolling.Sharpe(backtestSeries, 12, _tradingDaysPerYear).DropMissing(); + var liveRollingSharpeSixMonths = Rolling.Sharpe(liveSeries, 6, _tradingDaysPerYear).DropMissing(); + var liveRollingSharpeTwelveMonths = Rolling.Sharpe(liveSeries, 12, _tradingDaysPerYear).DropMissing(); var base64 = ""; using (Py.GIL()) diff --git a/Report/ReportElements/SharpeRatioReportElement.cs b/Report/ReportElements/SharpeRatioReportElement.cs index b400031e492d..baa2216fac89 100644 --- a/Report/ReportElements/SharpeRatioReportElement.cs +++ b/Report/ReportElements/SharpeRatioReportElement.cs @@ -24,6 +24,11 @@ namespace QuantConnect.Report.ReportElements { public class SharpeRatioReportElement : ReportElement { + /// + /// The number of trading days per year to get better result of statistics + /// + protected double _tradingDaysPerYear; + /// /// Live result object /// @@ -46,12 +51,14 @@ public class SharpeRatioReportElement : ReportElement /// Location of injection /// Backtest result object /// Live result object - public SharpeRatioReportElement(string name, string key, BacktestResult backtest, LiveResult live) + /// The number of trading days per year to get better result of statistics + public SharpeRatioReportElement(string name, string key, BacktestResult backtest, LiveResult live, int tradingDaysPerYear) { LiveResult = live; BacktestResult = backtest; Name = name; Key = key; + _tradingDaysPerYear = Convert.ToDouble(tradingDaysPerYear, CultureInfo.InvariantCulture); } /// @@ -77,14 +84,13 @@ public override string Render() .Values .ToList(); - var annualStandardDeviation = trailingPerformance.Count < 7 ? 0 : GetAnnualStandardDeviation(trailingPerformance); + var annualStandardDeviation = trailingPerformance.Count < 7 ? 0 : GetAnnualStandardDeviation(trailingPerformance, _tradingDaysPerYear); if (annualStandardDeviation <= 0) { return "-"; } - var annualPerformance = Statistics.Statistics.AnnualPerformance(trailingPerformance, - Convert.ToDouble(BacktestResult?.AlgorithmConfiguration?.TradingDaysPerYear, CultureInfo.InvariantCulture)); + var annualPerformance = Statistics.Statistics.AnnualPerformance(trailingPerformance, _tradingDaysPerYear); var liveResultValue = Statistics.Statistics.SharpeRatio(annualPerformance, annualStandardDeviation, 0.0); Result = liveResultValue; return liveResultValue.ToString("F2"); @@ -94,11 +100,11 @@ public override string Render() /// Get annual standard deviation /// /// The performance for the last period + /// The number of trading days per year to get better result of statistics /// Annual standard deviation. - public virtual double GetAnnualStandardDeviation(List trailingPerformance) + public virtual double GetAnnualStandardDeviation(List trailingPerformance, double tradingDaysPerYear) { - return Statistics.Statistics.AnnualStandardDeviation(trailingPerformance, - Convert.ToDouble(BacktestResult?.AlgorithmConfiguration?.TradingDaysPerYear, CultureInfo.InvariantCulture)); + return Statistics.Statistics.AnnualStandardDeviation(trailingPerformance, tradingDaysPerYear); } } } diff --git a/Report/ReportElements/SortinoRatioReportElement.cs b/Report/ReportElements/SortinoRatioReportElement.cs index d3b9a5698141..ab5dc4ad61fb 100644 --- a/Report/ReportElements/SortinoRatioReportElement.cs +++ b/Report/ReportElements/SortinoRatioReportElement.cs @@ -34,8 +34,9 @@ internal sealed class SortinoRatioReportElement : SharpeRatioReportElement /// Location of injection /// Backtest result object /// Live result object - public SortinoRatioReportElement(string name, string key, BacktestResult backtest, LiveResult live) - : base(name, key, backtest, live) + /// The number of trading days per year to get better result of statistics + public SortinoRatioReportElement(string name, string key, BacktestResult backtest, LiveResult live, int tradingDaysPerYear) + : base(name, key, backtest, live, tradingDaysPerYear) { } @@ -43,11 +44,11 @@ public SortinoRatioReportElement(string name, string key, BacktestResult backtes /// Get annual standard deviation /// /// The performance for the last period + /// The number of trading days per year to get better result of statistics /// Annual downside standard deviation. - public override double GetAnnualStandardDeviation(List trailingPerformance) + public override double GetAnnualStandardDeviation(List trailingPerformance, double tradingDaysPerYear) { - return Statistics.Statistics.AnnualDownsideStandardDeviation(trailingPerformance, - Convert.ToDouble(BacktestResult?.AlgorithmConfiguration?.TradingDaysPerYear, CultureInfo.InvariantCulture)); + return Statistics.Statistics.AnnualDownsideStandardDeviation(trailingPerformance, tradingDaysPerYear); } } } diff --git a/Research/QuantBook.cs b/Research/QuantBook.cs index 29a882e31d72..3b3e17a98e93 100644 --- a/Research/QuantBook.cs +++ b/Research/QuantBook.cs @@ -36,6 +36,7 @@ using QuantConnect.Packets; using System.Threading.Tasks; using QuantConnect.Data.UniverseSelection; +using QuantConnect.Lean.Engine.Setup; namespace QuantConnect.Research { @@ -768,11 +769,12 @@ public PyDict GetPortfolioStatistics(PyObject dataFrame) // Gets the startting capital var startingCapital = Convert.ToDecimal(dictEquity.FirstOrDefault().Value); - // use value = 252 like default for backwards compatibility - var tradingDaysPerYear = Settings.TradingDaysPerYear.HasValue ? Settings.TradingDaysPerYear.Value : 252; + // call method to set tradingDayPerYear for Algorithm (use: backwards compatibility) + BaseSetupHandler.SetBrokerageTradingDayPerYear(algorithm: this); // Compute portfolio statistics - var stats = new PortfolioStatistics(profitLoss, equity, new(), listPerformance, listBenchmark, startingCapital, RiskFreeInterestRateModel, tradingDaysPerYear); + var stats = new PortfolioStatistics(profitLoss, equity, new(), listPerformance, listBenchmark, startingCapital, RiskFreeInterestRateModel, + Settings.TradingDaysPerYear.Value); result.SetItem("Average Win (%)", Convert.ToDouble(stats.AverageWinRate * 100).ToPython()); result.SetItem("Average Loss (%)", Convert.ToDouble(stats.AverageLossRate * 100).ToPython()); diff --git a/Tests/Algorithm/AlgorithmSettingsTest.cs b/Tests/Algorithm/AlgorithmSettingsTest.cs index ce34064350f0..da785e027d05 100644 --- a/Tests/Algorithm/AlgorithmSettingsTest.cs +++ b/Tests/Algorithm/AlgorithmSettingsTest.cs @@ -109,23 +109,23 @@ public void ReturnUniqueTradingDayPerYearDependOnBrokerageName(BrokerageName bro var algorithm = new QCAlgorithm(); algorithm.SetBrokerageModel(brokerageName, accountType); - algorithm.Settings.TradingDaysPerYear ??= BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel); + BaseSetupHandler.SetBrokerageTradingDayPerYear(algorithm); Assert.AreEqual(expectedTradingDayPerYear, algorithm.Settings.TradingDaysPerYear.Value); } - [TestCase(BrokerageName.Bybit, 202)] - [TestCase(BrokerageName.InteractiveBrokersBrokerage, 404)] - public void ReturnCustomTradingDayPerYearIndependentlyFromBrokerageName(BrokerageName brokerageName, int customTradingDayPerYear) + [TestCase(BrokerageName.Bybit, 202, 365)] + [TestCase(BrokerageName.InteractiveBrokersBrokerage, 404, 252)] + public void ReturnCustomTradingDayPerYearIndependentlyFromBrokerageName(BrokerageName brokerageName, int customTradingDayPerYear, int expectedDefaultTradingDayPerYearForBrokerage) { var algorithm = new QCAlgorithm(); algorithm.SetBrokerageModel(brokerageName); algorithm.Settings.TradingDaysPerYear = customTradingDayPerYear; // duplicate: make sure that custom value is assigned - algorithm.Settings.TradingDaysPerYear ??= BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel); + BaseSetupHandler.SetBrokerageTradingDayPerYear(algorithm); - Assert.AreNotEqual(BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel), algorithm.Settings.TradingDaysPerYear); + Assert.AreNotEqual(expectedDefaultTradingDayPerYearForBrokerage, algorithm.Settings.TradingDaysPerYear); } [TestCase(null, 252)] @@ -139,7 +139,7 @@ public void ReturnTradingDayPerYearWithoutSetBrokerage(int? customTradingDayPerY algorithm.Settings.TradingDaysPerYear = customTradingDayPerYear.Value; } - algorithm.Settings.TradingDaysPerYear ??= BaseSetupHandler.GetBrokerageTradingDayPerYear(algorithm.BrokerageModel); + BaseSetupHandler.SetBrokerageTradingDayPerYear(algorithm); Assert.AreEqual(expectedTradingDayPerYear, algorithm.Settings.TradingDaysPerYear); } diff --git a/Tests/Common/Statistics/AnnualPerformanceTests.cs b/Tests/Common/Statistics/AnnualPerformanceTests.cs index f70fa626c080..c31e8fe6f97b 100644 --- a/Tests/Common/Statistics/AnnualPerformanceTests.cs +++ b/Tests/Common/Statistics/AnnualPerformanceTests.cs @@ -38,7 +38,7 @@ public class AnnualPerformanceTests public void GetSPY() { _algorithm = new QCAlgorithm(); - _algorithm.Settings.TradingDaysPerYear = BaseSetupHandler.GetBrokerageTradingDayPerYear(_algorithm.BrokerageModel); + BaseSetupHandler.SetBrokerageTradingDayPerYear(_algorithm); var symbol = Symbol.Create("SPY", SecurityType.Equity, Market.USA); var path = LeanData.GenerateZipFilePath(Globals.DataFolder, symbol, new DateTime(2020, 3, 1), Resolution.Daily, TickType.Trade); diff --git a/Tests/Common/Statistics/PortfolioStatisticsTests.cs b/Tests/Common/Statistics/PortfolioStatisticsTests.cs index f3483fc59c62..70f1c04e17c2 100644 --- a/Tests/Common/Statistics/PortfolioStatisticsTests.cs +++ b/Tests/Common/Statistics/PortfolioStatisticsTests.cs @@ -19,8 +19,6 @@ using NUnit.Framework; using QuantConnect.Data; using QuantConnect.Statistics; -using QuantConnect.Brokerages; -using QuantConnect.Lean.Engine.Setup; namespace QuantConnect.Tests.Common.Statistics { @@ -30,6 +28,12 @@ class PortfolioStatisticsTests private const decimal TradeFee = 2; private readonly DateTime _startTime = new DateTime(2015, 08, 06, 15, 30, 0); + /// + /// TradingDaysPerYear: Use like backward compatibility + /// + /// + protected const int _tradingDaysPerYear = 252; + [Test] public void ITMOptionAssignment([Values] bool win) { @@ -37,10 +41,9 @@ public void ITMOptionAssignment([Values] bool win) var profitLoss = new SortedDictionary(trades.ToDictionary(x => x.ExitTime, x => x.ProfitLoss)); var winCount = trades.Count(x => x.IsWin); var lossCount = trades.Count - winCount; - var tradingDayPerYears = BaseSetupHandler.GetBrokerageTradingDayPerYear(new DefaultBrokerageModel()); var statistics = new PortfolioStatistics(profitLoss, new SortedDictionary(), new SortedDictionary(), new List { 0, 0 }, new List { 0, 0 }, 100000, - new InterestRateProvider(), tradingDayPerYears, winCount, lossCount); + new InterestRateProvider(), _tradingDaysPerYear, winCount, lossCount); if (win) { diff --git a/Tests/Common/Statistics/StatisticsBuilderTests.cs b/Tests/Common/Statistics/StatisticsBuilderTests.cs index a5ee78aaa98b..12843664fcb1 100644 --- a/Tests/Common/Statistics/StatisticsBuilderTests.cs +++ b/Tests/Common/Statistics/StatisticsBuilderTests.cs @@ -19,14 +19,18 @@ using NUnit.Framework; using QuantConnect.Data; using QuantConnect.Statistics; -using QuantConnect.Brokerages; -using QuantConnect.Lean.Engine.Setup; namespace QuantConnect.Tests.Common.Statistics { [TestFixture] public class StatisticsBuilderTests { + /// + /// TradingDaysPerYear: Use like backward compatibility + /// + /// + protected const int _tradingDaysPerYear = 252; + [Test] public void MisalignedValues_ShouldThrow_DuringGeneration() { @@ -80,7 +84,7 @@ public void MisalignedValues_ShouldThrow_DuringGeneration() null, new QuantConnect.Securities.SecurityManager(new TimeKeeper(DateTime.UtcNow))), new InterestRateProvider(), - BaseSetupHandler.GetBrokerageTradingDayPerYear(new DefaultBrokerageModel())); + _tradingDaysPerYear); }, "Misaligned values provided, but we still generate statistics"); } } diff --git a/Tests/Common/Statistics/TrackingErrorTests.cs b/Tests/Common/Statistics/TrackingErrorTests.cs index 14a9e30654d3..cf7c9edc364a 100644 --- a/Tests/Common/Statistics/TrackingErrorTests.cs +++ b/Tests/Common/Statistics/TrackingErrorTests.cs @@ -41,7 +41,7 @@ public class TrackingErrorTests public void GetData() { _algorithm = new QCAlgorithm(); - _algorithm.Settings.TradingDaysPerYear = BaseSetupHandler.GetBrokerageTradingDayPerYear(_algorithm.BrokerageModel); + BaseSetupHandler.SetBrokerageTradingDayPerYear(_algorithm); var spy = Symbol.Create("SPY", SecurityType.Equity, Market.USA); var spyPath = LeanData.GenerateZipFilePath(Globals.DataFolder, spy, new DateTime(2020, 3, 1), Resolution.Daily, TickType.Trade); From 4f7c57e662e5fafc8bb4d66534987624e1ee301f Mon Sep 17 00:00:00 2001 From: Romazes Date: Tue, 16 Jan 2024 00:19:09 +0200 Subject: [PATCH 13/19] remove: default valuine in test param remove: extra comment. --- Report/ReportElements/RollingSharpeReportElement.cs | 1 - Tests/Algorithm/AlgorithmSettingsTest.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Report/ReportElements/RollingSharpeReportElement.cs b/Report/ReportElements/RollingSharpeReportElement.cs index 3455552e7bb6..21c12a32bed4 100644 --- a/Report/ReportElements/RollingSharpeReportElement.cs +++ b/Report/ReportElements/RollingSharpeReportElement.cs @@ -59,7 +59,6 @@ public override string Render() var backtestSeries = new Series(backtestPoints); var liveSeries = new Series(livePoints); - // use value = 252 like default for backwards compatibility var backtestRollingSharpeSixMonths = Rolling.Sharpe(backtestSeries, 6, _tradingDaysPerYear).DropMissing(); var backtestRollingSharpeTwelveMonths = Rolling.Sharpe(backtestSeries, 12, _tradingDaysPerYear).DropMissing(); var liveRollingSharpeSixMonths = Rolling.Sharpe(liveSeries, 6, _tradingDaysPerYear).DropMissing(); diff --git a/Tests/Algorithm/AlgorithmSettingsTest.cs b/Tests/Algorithm/AlgorithmSettingsTest.cs index da785e027d05..61af70291c77 100644 --- a/Tests/Algorithm/AlgorithmSettingsTest.cs +++ b/Tests/Algorithm/AlgorithmSettingsTest.cs @@ -128,9 +128,9 @@ public void ReturnCustomTradingDayPerYearIndependentlyFromBrokerageName(Brokerag Assert.AreNotEqual(expectedDefaultTradingDayPerYearForBrokerage, algorithm.Settings.TradingDaysPerYear); } - [TestCase(null, 252)] + [TestCase(252, null)] [TestCase(404, 404)] - public void ReturnTradingDayPerYearWithoutSetBrokerage(int? customTradingDayPerYear = null, int expectedTradingDayPerYear = 0) + public void ReturnTradingDayPerYearWithoutSetBrokerage(int expectedTradingDayPerYear, int? customTradingDayPerYear = null) { var algorithm = new QCAlgorithm(); From 4a3a3528da5161cd0897d2390ef1619daddf96c9 Mon Sep 17 00:00:00 2001 From: Romazes Date: Tue, 16 Jan 2024 16:21:15 +0200 Subject: [PATCH 14/19] fix: missed init TradingDaysPerYear in report.portfolioAlgo fix: hardcoded value to tradingDaysPerYear --- Report/PortfolioLooper/PortfolioLooper.cs | 4 ++++ .../PortfolioLooper/PortfolioLooperAlgorithm.cs | 3 ++- Report/Report.cs | 4 ++-- Report/ReportElements/PSRReportElement.cs | 13 ++++++++++--- .../RollingPortfolioBetaReportElement.cs | 15 +++++++++++---- Report/ReportElements/SharpeRatioReportElement.cs | 2 +- 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Report/PortfolioLooper/PortfolioLooper.cs b/Report/PortfolioLooper/PortfolioLooper.cs index 92335f57c5aa..b9c80deddd38 100644 --- a/Report/PortfolioLooper/PortfolioLooper.cs +++ b/Report/PortfolioLooper/PortfolioLooper.cs @@ -124,6 +124,10 @@ private PortfolioLooper(double startingCash, List orders, Resolution reso // Initialize the algorithm before adding any securities Algorithm.Initialize(); + + // after algorithm was initialized, should set trading days per year for our great portfolio statistics + BaseSetupHandler.SetBrokerageTradingDayPerYear(Algorithm); + Algorithm.PostInitialize(); // Initializes all the proper Securities from the orders provided by the user diff --git a/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs b/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs index ac5cd035ecaf..aea86c52e9a0 100644 --- a/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs +++ b/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -88,6 +88,7 @@ public override void Initialize() { SetAccountCurrency(_algorithmConfiguration.AccountCurrency); SetBrokerageModel(_algorithmConfiguration.BrokerageName, _algorithmConfiguration.AccountType); + Settings.TradingDaysPerYear = _algorithmConfiguration.TradingDaysPerYear; } SetCash(_startingCash); diff --git a/Report/Report.cs b/Report/Report.cs index aa09931624bf..727106eaec99 100644 --- a/Report/Report.cs +++ b/Report/Report.cs @@ -125,7 +125,7 @@ public Report(string name, string description, string version, BacktestResult ba new MaxDrawdownReportElement("max drawdown kpi", ReportKey.MaxDrawdown, backtest, live), new SharpeRatioReportElement("sharpe kpi", ReportKey.SharpeRatio, backtest, live, tradingDayPerYear), new SortinoRatioReportElement("sortino kpi", ReportKey.SortinoRatio, backtest, live, tradingDayPerYear), - new PSRReportElement("psr kpi", ReportKey.PSR, backtest, live), + new PSRReportElement("psr kpi", ReportKey.PSR, backtest, live, tradingDayPerYear), new InformationRatioReportElement("ir kpi", ReportKey.InformationRatio, backtest, live), new MarketsReportElement("markets kpi", ReportKey.Markets, backtest, live), new TradesPerDayReportElement("trades per day kpi", ReportKey.TradesPerDay, backtest, live), @@ -139,7 +139,7 @@ public Report(string name, string description, string version, BacktestResult ba new AssetAllocationReportElement("asset allocation over time pie chart", ReportKey.AssetAllocation, backtest, live, backtestPortfolioInTime, livePortfolioInTime), new DrawdownReportElement("drawdown plot", ReportKey.Drawdown, backtest, live), new DailyReturnsReportElement("daily returns plot", ReportKey.DailyReturns, backtest, live), - new RollingPortfolioBetaReportElement("rolling beta to equities plot", ReportKey.RollingBeta, backtest, live), + new RollingPortfolioBetaReportElement("rolling beta to equities plot", ReportKey.RollingBeta, backtest, live, tradingDayPerYear), new RollingSharpeReportElement("rolling sharpe ratio plot", ReportKey.RollingSharpe, backtest, live, tradingDayPerYear), new LeverageUtilizationReportElement("leverage plot", ReportKey.LeverageUtilization, backtest, live, backtestPortfolioInTime, livePortfolioInTime), new ExposureReportElement("exposure plot", ReportKey.Exposure, backtest, live, backtestPortfolioInTime, livePortfolioInTime) diff --git a/Report/ReportElements/PSRReportElement.cs b/Report/ReportElements/PSRReportElement.cs index 7c23163c0785..3208aa1a615b 100644 --- a/Report/ReportElements/PSRReportElement.cs +++ b/Report/ReportElements/PSRReportElement.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -25,6 +25,11 @@ internal sealed class PSRReportElement : ReportElement private LiveResult _live; private BacktestResult _backtest; + /// + /// The number of trading days per year to get better result of statistics + /// + private int _tradingDaysPerYear; + /// /// Estimate the PSR of the strategy. /// @@ -32,12 +37,14 @@ internal sealed class PSRReportElement : ReportElement /// Location of injection /// Backtest result object /// Live result object - public PSRReportElement(string name, string key, BacktestResult backtest, LiveResult live) + /// The number of trading days per year to get better result of statistics + public PSRReportElement(string name, string key, BacktestResult backtest, LiveResult live, int tradingDaysPerYear) { _live = live; _backtest = backtest; Name = name; Key = key; + _tradingDaysPerYear = tradingDaysPerYear; } /// @@ -69,7 +76,7 @@ public override string Render() var sixMonthsBefore = equityCurvePerformance.LastKey() - TimeSpan.FromDays(180); - var benchmarkSharpeRatio = 1.0d / Math.Sqrt(252); + var benchmarkSharpeRatio = 1.0d / Math.Sqrt(_tradingDaysPerYear); psr = Statistics.Statistics.ProbabilisticSharpeRatio( equityCurvePerformance .Where(kvp => kvp.Key >= sixMonthsBefore) diff --git a/Report/ReportElements/RollingPortfolioBetaReportElement.cs b/Report/ReportElements/RollingPortfolioBetaReportElement.cs index 126a883fe3a5..9f2fcb1d3a87 100644 --- a/Report/ReportElements/RollingPortfolioBetaReportElement.cs +++ b/Report/ReportElements/RollingPortfolioBetaReportElement.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -24,6 +24,11 @@ internal sealed class RollingPortfolioBetaReportElement : ChartReportElement private LiveResult _live; private BacktestResult _backtest; + /// + /// The number of trading days per year to get better result of statistics + /// + private int _tradingDaysPerYear; + /// /// Create a new plot of the rolling portfolio beta to equities /// @@ -31,12 +36,14 @@ internal sealed class RollingPortfolioBetaReportElement : ChartReportElement /// Location of injection /// Backtest result object /// Live result object - public RollingPortfolioBetaReportElement(string name, string key, BacktestResult backtest, LiveResult live) + /// The number of trading days per year to get better result of statistics + public RollingPortfolioBetaReportElement(string name, string key, BacktestResult backtest, LiveResult live, int tradingDaysPerYear) { _live = live; _backtest = backtest; Name = name; Key = key; + _tradingDaysPerYear = tradingDaysPerYear; } /// @@ -56,7 +63,7 @@ public override string Render() var liveList = new PyList(); var backtestRollingBetaSixMonths = Rolling.Beta(backtestPoints, backtestBenchmarkPoints, windowSize: 22 * 6); - var backtestRollingBetaTwelveMonths = Rolling.Beta(backtestPoints, backtestBenchmarkPoints, windowSize: 252); + var backtestRollingBetaTwelveMonths = Rolling.Beta(backtestPoints, backtestBenchmarkPoints, windowSize: _tradingDaysPerYear); backtestList.Append(backtestRollingBetaSixMonths.Keys.ToList().ToPython()); backtestList.Append(backtestRollingBetaSixMonths.Values.ToList().ToPython()); @@ -64,7 +71,7 @@ public override string Render() backtestList.Append(backtestRollingBetaTwelveMonths.Values.ToList().ToPython()); var liveRollingBetaSixMonths = Rolling.Beta(livePoints, liveBenchmarkPoints, windowSize: 22 * 6); - var liveRollingBetaTwelveMonths = Rolling.Beta(livePoints, liveBenchmarkPoints, windowSize: 252); + var liveRollingBetaTwelveMonths = Rolling.Beta(livePoints, liveBenchmarkPoints, windowSize: _tradingDaysPerYear); liveList.Append(liveRollingBetaSixMonths.Keys.ToList().ToPython()); liveList.Append(liveRollingBetaSixMonths.Values.ToList().ToPython()); diff --git a/Report/ReportElements/SharpeRatioReportElement.cs b/Report/ReportElements/SharpeRatioReportElement.cs index baa2216fac89..16a7a70911c9 100644 --- a/Report/ReportElements/SharpeRatioReportElement.cs +++ b/Report/ReportElements/SharpeRatioReportElement.cs @@ -27,7 +27,7 @@ public class SharpeRatioReportElement : ReportElement /// /// The number of trading days per year to get better result of statistics /// - protected double _tradingDaysPerYear; + private double _tradingDaysPerYear; /// /// Live result object From edd96c8f64fe89dc00bdaac53f6b4d511cf8fcca Mon Sep 17 00:00:00 2001 From: Romazes Date: Tue, 16 Jan 2024 19:46:51 +0200 Subject: [PATCH 15/19] fix: nullable TradingDaysPerYear in AlgorithmConfiguration feat: UTest for PortfolioLooperAlgo feat: add advanced description --- Common/AlgorithmConfiguration.cs | 4 +-- Common/Interfaces/IAlgorithmSettings.cs | 23 ++++++++----- .../PortfolioLooperAlgorithm.cs | 1 + Tests/Report/PortfolioLooperAlgorithmTests.cs | 34 +++++++++++++++++++ 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/Common/AlgorithmConfiguration.cs b/Common/AlgorithmConfiguration.cs index a7400bcbf0a5..035b53128320 100644 --- a/Common/AlgorithmConfiguration.cs +++ b/Common/AlgorithmConfiguration.cs @@ -85,13 +85,13 @@ public class AlgorithmConfiguration /// Number of trading days per year for Algorithm's portfolio statistics. /// [JsonProperty(PropertyName = "TradingDaysPerYear")] - public int TradingDaysPerYear; + public int? TradingDaysPerYear; /// /// Initializes a new instance of the class /// public AlgorithmConfiguration(string accountCurrency, BrokerageName brokerageName, AccountType accountType, IReadOnlyDictionary parameters, - DateTime startDate, DateTime endDate, DateTime? outOfSampleMaxEndDate, int outOfSampleDays = 0, int tradingDaysPerYear = 0) + DateTime startDate, DateTime endDate, DateTime? outOfSampleMaxEndDate, int outOfSampleDays = 0, int? tradingDaysPerYear = 0) { OutOfSampleMaxEndDate = outOfSampleMaxEndDate; TradingDaysPerYear = tradingDaysPerYear; diff --git a/Common/Interfaces/IAlgorithmSettings.cs b/Common/Interfaces/IAlgorithmSettings.cs index fa4f715f8de5..a2558ef0b4a5 100644 --- a/Common/Interfaces/IAlgorithmSettings.cs +++ b/Common/Interfaces/IAlgorithmSettings.cs @@ -95,15 +95,22 @@ public interface IAlgorithmSettings Resolution? WarmupResolution { get; set; } /// - /// Number of trading days per year for this Algorithm's portfolio statistics. + /// Gets or sets the number of trading days per year for this Algorithm's portfolio statistics. /// - /// Effect on - /// , - /// , - /// , - /// , - /// , - /// . + /// + /// This property affects the calculation of various portfolio statistics, including: + /// - + /// - + /// - + /// - + /// - + /// - . + /// + /// The default values are: + /// - Cryptocurrency Exchanges: 365 days + /// - Traditional Stock Exchanges: 252 days + /// + /// Users can also set a custom value for this property. /// int? TradingDaysPerYear { get; set; } } diff --git a/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs b/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs index aea86c52e9a0..ec88c00c0ebf 100644 --- a/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs +++ b/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs @@ -88,6 +88,7 @@ public override void Initialize() { SetAccountCurrency(_algorithmConfiguration.AccountCurrency); SetBrokerageModel(_algorithmConfiguration.BrokerageName, _algorithmConfiguration.AccountType); + // assign custom value Settings.TradingDaysPerYear = _algorithmConfiguration.TradingDaysPerYear; } diff --git a/Tests/Report/PortfolioLooperAlgorithmTests.cs b/Tests/Report/PortfolioLooperAlgorithmTests.cs index 3e684bde9d48..daa714d55052 100644 --- a/Tests/Report/PortfolioLooperAlgorithmTests.cs +++ b/Tests/Report/PortfolioLooperAlgorithmTests.cs @@ -22,6 +22,7 @@ using System; using System.Collections.Generic; using System.Linq; +using QuantConnect.Lean.Engine.Setup; namespace QuantConnect.Tests.Report { @@ -122,5 +123,38 @@ public void SetsTheRightAlgorithmConfiguration(string currency, BrokerageName br Assert.AreEqual(brokerageName, BrokerageModel.GetBrokerageName(algorithm.BrokerageModel)); Assert.AreEqual(accountType, algorithm.BrokerageModel.AccountType); } + + [TestCase(null, null, null, 252)] + [TestCase(BrokerageName.Default, AccountType.Cash, null, 252)] + [TestCase(BrokerageName.Coinbase, AccountType.Cash, null, 365)] + [TestCase(BrokerageName.Binance, AccountType.Cash, 200, 200)] + [TestCase(BrokerageName.TDAmeritrade, AccountType.Cash, 404, 404)] + public void ValidateTradingDaysPerYearPropertyInPortfolioLooperAlgorithm( + BrokerageName? brokerageName, AccountType accountType, int? customTradingDaysPerYear, int expectedTradingDaysPerYear) + { + AlgorithmConfiguration algorithmConfiguration = default; + if (brokerageName.HasValue) + { + algorithmConfiguration = new AlgorithmConfiguration() + { + AccountCurrency = "USD", + BrokerageName = brokerageName.Value, + AccountType = accountType, + }; + + if (customTradingDaysPerYear.HasValue) + { + algorithmConfiguration.TradingDaysPerYear = customTradingDaysPerYear.Value; + } + } + + var algorithm = CreateAlgorithm(Enumerable.Empty(), algorithmConfiguration); + + algorithm.Initialize(); + + BaseSetupHandler.SetBrokerageTradingDayPerYear(algorithm); + + Assert.AreEqual(expectedTradingDaysPerYear, algorithm.Settings.TradingDaysPerYear); + } } } From 0abf3158d1cff3b9c7496395b6f6b53171a1382f Mon Sep 17 00:00:00 2001 From: Romazes Date: Tue, 16 Jan 2024 21:55:58 +0200 Subject: [PATCH 16/19] feat: test PortfolioStatistics with different tradingDaysPerYear --- .../Statistics/PortfolioStatisticsTests.cs | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/Tests/Common/Statistics/PortfolioStatisticsTests.cs b/Tests/Common/Statistics/PortfolioStatisticsTests.cs index 70f1c04e17c2..ca040bd19849 100644 --- a/Tests/Common/Statistics/PortfolioStatisticsTests.cs +++ b/Tests/Common/Statistics/PortfolioStatisticsTests.cs @@ -37,13 +37,7 @@ class PortfolioStatisticsTests [Test] public void ITMOptionAssignment([Values] bool win) { - var trades = CreateITMOptionAssignment(win); - var profitLoss = new SortedDictionary(trades.ToDictionary(x => x.ExitTime, x => x.ProfitLoss)); - var winCount = trades.Count(x => x.IsWin); - var lossCount = trades.Count - winCount; - var statistics = new PortfolioStatistics(profitLoss, new SortedDictionary(), - new SortedDictionary(), new List { 0, 0 }, new List { 0, 0 }, 100000, - new InterestRateProvider(), _tradingDaysPerYear, winCount, lossCount); + var statistics = GetPortfolioStatistics(win, _tradingDaysPerYear, new List { 0, 0 }, new List { 0, 0 }); if (win) { @@ -61,6 +55,53 @@ public void ITMOptionAssignment([Values] bool win) Assert.AreEqual(1.4673913043478260869565217388m, statistics.ProfitLossRatio); } + + public static IEnumerable StatisticsCases + { + get + { + yield return new TestCaseData(202, 0.00589787137120101M, 0.0767976000354244M, -3.0952570635188M, 0.167632655086644M, 0.252197874915608M); + yield return new TestCaseData(252, 0.00735774052248839M, 0.0857772727620108M, -3.3486737318423M, 0.187233350684845M, 0.257146306116665M); + yield return new TestCaseData(365, 0.0106570448043979M, 0.103232963748978M, -3.75507953923657M, 0.225335372429895M, 0.264390639112978M); + } + } + + [TestCaseSource(nameof(StatisticsCases))] + public void ITMOptionAssignmentWithDifferentTradingDaysPerYearValue( + int tradingDaysPerYear, decimal expectedAnnualVariance, decimal expectedAnnualStandardDeviation, + decimal expectedSharpeRatio, decimal expectedTrackingError, decimal expectedProbabilisticSharpeRatio) + { + var listPerformance = new List { -0.009025132, 0.003653969, 0, 0 }; + var listBenchmark = new List { -0.011587791300935783, 0.00054375782787618543, 0.022165997700413956, 0.006263266301918822 }; + + var statistics = GetPortfolioStatistics(true, tradingDaysPerYear, listPerformance, listBenchmark); + + Assert.AreEqual(expectedAnnualVariance, statistics.AnnualVariance); + Assert.AreEqual(expectedAnnualStandardDeviation, statistics.AnnualStandardDeviation); + Assert.AreEqual(expectedSharpeRatio, statistics.SharpeRatio); + Assert.AreEqual(expectedTrackingError, statistics.TrackingError); + Assert.AreEqual(expectedProbabilisticSharpeRatio, statistics.ProbabilisticSharpeRatio); + } + + /// + /// Initialize and return Portfolio Statistics depends on input data + /// + /// create profitable trade or not + /// amount days per year for brokerage (e.g. crypto exchange use 365 days) + /// The list of algorithm performance values + /// The list of benchmark values + /// The class represents a set of statistics calculated from equity and benchmark samples + private PortfolioStatistics GetPortfolioStatistics(bool win, int tradingDaysPerYear, List listPerformance, List listBenchmark) + { + var trades = CreateITMOptionAssignment(win); + var profitLoss = new SortedDictionary(trades.ToDictionary(x => x.ExitTime, x => x.ProfitLoss)); + var winCount = trades.Count(x => x.IsWin); + var lossCount = trades.Count - winCount; + return new PortfolioStatistics(profitLoss, new SortedDictionary(), + new SortedDictionary(), listPerformance, listBenchmark, 100000, + new InterestRateProvider(), tradingDaysPerYear, winCount, lossCount); + } + private List CreateITMOptionAssignment(bool win) { var time = _startTime; From aea54e3912196468747f1cae7bb02f5e9dbf7b6f Mon Sep 17 00:00:00 2001 From: Romazes Date: Wed, 17 Jan 2024 00:18:25 +0200 Subject: [PATCH 17/19] revert: PortfolioLooper with TradingDaysPerYear implementation --- Report/PortfolioLooper/PortfolioLooper.cs | 3 -- .../PortfolioLooperAlgorithm.cs | 2 -- Tests/Report/PortfolioLooperAlgorithmTests.cs | 34 ------------------- 3 files changed, 39 deletions(-) diff --git a/Report/PortfolioLooper/PortfolioLooper.cs b/Report/PortfolioLooper/PortfolioLooper.cs index b9c80deddd38..16b4fba69012 100644 --- a/Report/PortfolioLooper/PortfolioLooper.cs +++ b/Report/PortfolioLooper/PortfolioLooper.cs @@ -125,9 +125,6 @@ private PortfolioLooper(double startingCash, List orders, Resolution reso // Initialize the algorithm before adding any securities Algorithm.Initialize(); - // after algorithm was initialized, should set trading days per year for our great portfolio statistics - BaseSetupHandler.SetBrokerageTradingDayPerYear(Algorithm); - Algorithm.PostInitialize(); // Initializes all the proper Securities from the orders provided by the user diff --git a/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs b/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs index ec88c00c0ebf..6c28fb7781e5 100644 --- a/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs +++ b/Report/PortfolioLooper/PortfolioLooperAlgorithm.cs @@ -88,8 +88,6 @@ public override void Initialize() { SetAccountCurrency(_algorithmConfiguration.AccountCurrency); SetBrokerageModel(_algorithmConfiguration.BrokerageName, _algorithmConfiguration.AccountType); - // assign custom value - Settings.TradingDaysPerYear = _algorithmConfiguration.TradingDaysPerYear; } SetCash(_startingCash); diff --git a/Tests/Report/PortfolioLooperAlgorithmTests.cs b/Tests/Report/PortfolioLooperAlgorithmTests.cs index daa714d55052..3e684bde9d48 100644 --- a/Tests/Report/PortfolioLooperAlgorithmTests.cs +++ b/Tests/Report/PortfolioLooperAlgorithmTests.cs @@ -22,7 +22,6 @@ using System; using System.Collections.Generic; using System.Linq; -using QuantConnect.Lean.Engine.Setup; namespace QuantConnect.Tests.Report { @@ -123,38 +122,5 @@ public void SetsTheRightAlgorithmConfiguration(string currency, BrokerageName br Assert.AreEqual(brokerageName, BrokerageModel.GetBrokerageName(algorithm.BrokerageModel)); Assert.AreEqual(accountType, algorithm.BrokerageModel.AccountType); } - - [TestCase(null, null, null, 252)] - [TestCase(BrokerageName.Default, AccountType.Cash, null, 252)] - [TestCase(BrokerageName.Coinbase, AccountType.Cash, null, 365)] - [TestCase(BrokerageName.Binance, AccountType.Cash, 200, 200)] - [TestCase(BrokerageName.TDAmeritrade, AccountType.Cash, 404, 404)] - public void ValidateTradingDaysPerYearPropertyInPortfolioLooperAlgorithm( - BrokerageName? brokerageName, AccountType accountType, int? customTradingDaysPerYear, int expectedTradingDaysPerYear) - { - AlgorithmConfiguration algorithmConfiguration = default; - if (brokerageName.HasValue) - { - algorithmConfiguration = new AlgorithmConfiguration() - { - AccountCurrency = "USD", - BrokerageName = brokerageName.Value, - AccountType = accountType, - }; - - if (customTradingDaysPerYear.HasValue) - { - algorithmConfiguration.TradingDaysPerYear = customTradingDaysPerYear.Value; - } - } - - var algorithm = CreateAlgorithm(Enumerable.Empty(), algorithmConfiguration); - - algorithm.Initialize(); - - BaseSetupHandler.SetBrokerageTradingDayPerYear(algorithm); - - Assert.AreEqual(expectedTradingDaysPerYear, algorithm.Settings.TradingDaysPerYear); - } } } From f6016c61abbf4f2ca61f8b58a884b913e6f82e71 Mon Sep 17 00:00:00 2001 From: Romazes Date: Wed, 17 Jan 2024 00:19:52 +0200 Subject: [PATCH 18/19] revert: nullable TradingDaysPerYear in AlgoConfiguration remove: SetBrokerageTradingDayPerYear in BaseResultHandler --- Common/AlgorithmConfiguration.cs | 6 ++++-- Engine/Results/BaseResultsHandler.cs | 8 +++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Common/AlgorithmConfiguration.cs b/Common/AlgorithmConfiguration.cs index 035b53128320..29849f16a52b 100644 --- a/Common/AlgorithmConfiguration.cs +++ b/Common/AlgorithmConfiguration.cs @@ -85,13 +85,13 @@ public class AlgorithmConfiguration /// Number of trading days per year for Algorithm's portfolio statistics. /// [JsonProperty(PropertyName = "TradingDaysPerYear")] - public int? TradingDaysPerYear; + public int TradingDaysPerYear; /// /// Initializes a new instance of the class /// public AlgorithmConfiguration(string accountCurrency, BrokerageName brokerageName, AccountType accountType, IReadOnlyDictionary parameters, - DateTime startDate, DateTime endDate, DateTime? outOfSampleMaxEndDate, int outOfSampleDays = 0, int? tradingDaysPerYear = 0) + DateTime startDate, DateTime endDate, DateTime? outOfSampleMaxEndDate, int outOfSampleDays = 0, int tradingDaysPerYear = 0) { OutOfSampleMaxEndDate = outOfSampleMaxEndDate; TradingDaysPerYear = tradingDaysPerYear; @@ -109,6 +109,8 @@ public AlgorithmConfiguration(string accountCurrency, BrokerageName brokerageNam /// public AlgorithmConfiguration() { + // use default value for backwards compatibility + TradingDaysPerYear = 252; } /// diff --git a/Engine/Results/BaseResultsHandler.cs b/Engine/Results/BaseResultsHandler.cs index 7a8be092f440..96041ffd6075 100644 --- a/Engine/Results/BaseResultsHandler.cs +++ b/Engine/Results/BaseResultsHandler.cs @@ -847,13 +847,11 @@ protected StatisticsResults GenerateStatisticsResults(Dictionary portfolioTurnover = new Series(); } - // call method to set tradingDayPerYear for Algorithm (use: backwards compatibility) - BaseSetupHandler.SetBrokerageTradingDayPerYear(Algorithm); - statisticsResults = StatisticsBuilder.Generate(trades, profitLoss, equity.Values, performance.Values, benchmark.Values, portfolioTurnover.Values, StartingPortfolioValue, Algorithm.Portfolio.TotalFees, totalTransactions, - estimatedStrategyCapacity, AlgorithmCurrencySymbol, Algorithm.Transactions, Algorithm.RiskFreeInterestRateModel, - Algorithm.Settings.TradingDaysPerYear.Value); + estimatedStrategyCapacity, AlgorithmCurrencySymbol, Algorithm.Transactions, Algorithm.RiskFreeInterestRateModel, + Algorithm.Settings.TradingDaysPerYear.Value // already set in Brokerage|Backtesting-SetupHandler classes + ); } statisticsResults.AddCustomSummaryStatistics(_customSummaryStatistics); From 11a79512406f3c4f51086a59ec634f471233bc05 Mon Sep 17 00:00:00 2001 From: Romazes Date: Wed, 17 Jan 2024 00:31:54 +0200 Subject: [PATCH 19/19] style: remove empty row --- Report/PortfolioLooper/PortfolioLooper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Report/PortfolioLooper/PortfolioLooper.cs b/Report/PortfolioLooper/PortfolioLooper.cs index 16b4fba69012..92335f57c5aa 100644 --- a/Report/PortfolioLooper/PortfolioLooper.cs +++ b/Report/PortfolioLooper/PortfolioLooper.cs @@ -124,7 +124,6 @@ private PortfolioLooper(double startingCash, List orders, Resolution reso // Initialize the algorithm before adding any securities Algorithm.Initialize(); - Algorithm.PostInitialize(); // Initializes all the proper Securities from the orders provided by the user