From dafad3153b4b1e1dae3b93523a051d98042be78c Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 4 Apr 2025 17:36:38 -0400 Subject: [PATCH 1/4] Initialize security after addition and re-addition --- .../RawPricesUniverseRegressionAlgorithm.cs | 52 +++++++++--------- ...nReAdditionForEquityRegressionAlgorithm.cs | 54 +++++++++++++++++-- ...rManuallyAddedOptionRegressionAlgorithm.cs | 37 ++++++++++++- ...ionForSelectedOptionRegressionAlgorithm.cs | 30 ++++++++++- Common/Securities/Security.cs | 6 +++ Common/Securities/SecurityService.cs | 16 ++++-- 6 files changed, 160 insertions(+), 35 deletions(-) diff --git a/Algorithm.CSharp/RawPricesUniverseRegressionAlgorithm.cs b/Algorithm.CSharp/RawPricesUniverseRegressionAlgorithm.cs index 7d354a6566d3..34e67e815928 100644 --- a/Algorithm.CSharp/RawPricesUniverseRegressionAlgorithm.cs +++ b/Algorithm.CSharp/RawPricesUniverseRegressionAlgorithm.cs @@ -91,12 +91,12 @@ public override void OnSecuritiesChanged(SecurityChanges changes) /// /// Data Points count of all timeslices of algorithm /// - public long DataPoints => 135; + public long DataPoints => 156; /// /// Data Points count of the algorithm history /// - public int AlgorithmHistoryDataPoints => 30; + public int AlgorithmHistoryDataPoints => 150; /// /// Final status of the algorithm @@ -108,33 +108,33 @@ public override void OnSecuritiesChanged(SecurityChanges changes) /// public Dictionary ExpectedStatistics => new Dictionary { - {"Total Orders", "12"}, - {"Average Win", "0.34%"}, - {"Average Loss", "-0.14%"}, - {"Compounding Annual Return", "4.586%"}, - {"Drawdown", "0.700%"}, - {"Expectancy", "0.158"}, + {"Total Orders", "57"}, + {"Average Win", "0.18%"}, + {"Average Loss", "-0.24%"}, + {"Compounding Annual Return", "-47.380%"}, + {"Drawdown", "2.500%"}, + {"Expectancy", "-0.352"}, {"Start Equity", "50000"}, - {"End Equity", "50090.17"}, - {"Net Profit", "0.180%"}, - {"Sharpe Ratio", "5.991"}, - {"Sortino Ratio", "0"}, - {"Probabilistic Sharpe Ratio", "99.393%"}, - {"Loss Rate", "67%"}, - {"Win Rate", "33%"}, - {"Profit-Loss Ratio", "2.47"}, - {"Alpha", "0.17"}, - {"Beta", "0.029"}, - {"Annual Standard Deviation", "0.028"}, - {"Annual Variance", "0.001"}, - {"Information Ratio", "2.734"}, - {"Tracking Error", "0.098"}, - {"Treynor Ratio", "5.803"}, + {"End Equity", "48726.48"}, + {"Net Profit", "-2.547%"}, + {"Sharpe Ratio", "-3.372"}, + {"Sortino Ratio", "-3.889"}, + {"Probabilistic Sharpe Ratio", "10.352%"}, + {"Loss Rate", "63%"}, + {"Win Rate", "37%"}, + {"Profit-Loss Ratio", "0.75"}, + {"Alpha", "-0.208"}, + {"Beta", "0.815"}, + {"Annual Standard Deviation", "0.086"}, + {"Annual Variance", "0.007"}, + {"Information Ratio", "-4.871"}, + {"Tracking Error", "0.039"}, + {"Treynor Ratio", "-0.357"}, {"Total Fees", "$0.00"}, - {"Estimated Strategy Capacity", "$99000000.00"}, + {"Estimated Strategy Capacity", "$230000000.00"}, {"Lowest Capacity Asset", "AIG R735QTJ8XC9X"}, - {"Portfolio Turnover", "15.96%"}, - {"OrderListHash", "d915ae36ce856457b32ebbfce4581281"} + {"Portfolio Turnover", "77.40%"}, + {"OrderListHash", "4fb8ffbdfd2cce69ac28b0d0992d7198"} }; } } diff --git a/Algorithm.CSharp/SecurityInitializationOnReAdditionForEquityRegressionAlgorithm.cs b/Algorithm.CSharp/SecurityInitializationOnReAdditionForEquityRegressionAlgorithm.cs index 17b65b89028a..d3aee1d9fe77 100644 --- a/Algorithm.CSharp/SecurityInitializationOnReAdditionForEquityRegressionAlgorithm.cs +++ b/Algorithm.CSharp/SecurityInitializationOnReAdditionForEquityRegressionAlgorithm.cs @@ -27,6 +27,8 @@ namespace QuantConnect.Algorithm.CSharp /// It asserts that the securities are marked as non-tradable when removed and that they are tradable when re-added. /// It also asserts that the algorithm receives the correct security changed events for the added and removed securities. /// + /// Additionally, it tests that the security is initialized after every addition, and no more. + /// /// This specific algorithm tests this behavior for equities. /// public class SecurityInitializationOnReAdditionForEquityRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition @@ -34,6 +36,7 @@ public class SecurityInitializationOnReAdditionForEquityRegressionAlgorithm : QC private Security _security; private Queue _tradableDates; private bool _securityWasRemoved; + private Dictionary _securityInializationCounts = new(); protected virtual DateTime StartTimeToUse => new DateTime(2013, 10, 05); @@ -44,7 +47,20 @@ public override void Initialize() SetStartDate(StartTimeToUse); SetEndDate(EndTimeToUse); - _security = AddSecurity(); + var seeder = new FuncSecuritySeeder((security) => + { + if (!_securityInializationCounts.TryGetValue(security, out var count)) + { + count = 0; + } + _securityInializationCounts[security] = count + 1; + + Debug($"[{Time}] Seeding {security.Symbol}"); + return GetLastKnownPrices(security); + }); + SetSecurityInitializer(security => seeder.SeedSecurity(security)); + + _security = AddSecurityImpl(); _tradableDates = new(QuantConnect.Time.EachTradeableDay(_security.Exchange.Hours, StartDate, EndDate)); @@ -61,6 +77,9 @@ public override void Initialize() return; } + // Before we remove the security let's check that it was not initialized again + AssertSecurityInitializationCount(_securityInializationCounts, _security); + // Remove the security every day Debug($"[{Time}] Removing the security"); _securityWasRemoved = RemoveSecurity(_security.Symbol); @@ -77,6 +96,21 @@ public override void Initialize() }); } + private Security AddSecurityImpl() + { + _securityInializationCounts.Clear(); + var security = AddSecurity(); + + if (_security != null && !ReferenceEquals(_security, security)) + { + throw new RegressionTestException($"Expected the security to be the same as the original security"); + } + + AssertSecurityInitializationCount(_securityInializationCounts, security); + + return security; + } + protected virtual Security AddSecurity() { return AddEquity("SPY"); @@ -100,7 +134,7 @@ public override void OnSecuritiesChanged(SecurityChanges changes) // Add the security back Debug($"[{Time}] Re-adding the security"); - var reAddedSecurity = AddSecurity(); + var reAddedSecurity = AddSecurityImpl(); if (!ReferenceEquals(reAddedSecurity, _security)) { @@ -126,6 +160,20 @@ public override void OnEndOfAlgorithm() } } + protected virtual void AssertSecurityInitializationCount(Dictionary securityInializationCounts, Security security) + { + if (securityInializationCounts.Count != 1) + { + throw new RegressionTestException($"Expected only one security to be initialized. Got {securityInializationCounts.Count}"); + } + + if (!securityInializationCounts.TryGetValue(security, out var count) || count != 1) + { + throw new RegressionTestException($"Expected the security to be initialized once and once only, " + + $"but was initialized {count} times"); + } + } + /// /// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. /// @@ -144,7 +192,7 @@ public override void OnEndOfAlgorithm() /// /// Data Points count of the algorithm history /// - public virtual int AlgorithmHistoryDataPoints => 0; + public virtual int AlgorithmHistoryDataPoints => 3848; /// /// Final status of the algorithm diff --git a/Algorithm.CSharp/SecurityInitializationOnReAdditionForManuallyAddedOptionRegressionAlgorithm.cs b/Algorithm.CSharp/SecurityInitializationOnReAdditionForManuallyAddedOptionRegressionAlgorithm.cs index a3ae45a33db8..765de125d2f7 100644 --- a/Algorithm.CSharp/SecurityInitializationOnReAdditionForManuallyAddedOptionRegressionAlgorithm.cs +++ b/Algorithm.CSharp/SecurityInitializationOnReAdditionForManuallyAddedOptionRegressionAlgorithm.cs @@ -24,6 +24,8 @@ namespace QuantConnect.Algorithm.CSharp /// It asserts that the securities are marked as non-tradable when removed and that they are tradable when re-added. /// It also asserts that the algorithm receives the correct security changed events for the added and removed securities. /// + /// Additionally, it tests that the security is initialized after every addition, and no more. + /// /// This specific algorithm tests this behavior for manually added option contracts. /// public class SecurityInitializationOnReAdditionForManuallyAddedOptionRegressionAlgorithm : SecurityInitializationOnReAdditionForEquityRegressionAlgorithm @@ -36,15 +38,48 @@ public class SecurityInitializationOnReAdditionForManuallyAddedOptionRegressionA 342.9m, new DateTime(2014, 07, 19)); + private int _securityAdditionsCount; + protected override DateTime StartTimeToUse => new DateTime(2014, 06, 04); protected override DateTime EndTimeToUse => new DateTime(2014, 06, 20); protected override Security AddSecurity() { + _securityAdditionsCount++; return AddOptionContract(_optionContractSymbol, Resolution.Daily); } + protected override void AssertSecurityInitializationCount(Dictionary securityInializationCounts, Security security) + { + // The first time the contract is added, the underlying equity will be added and initialized as well. + // The following times the contract is added, the underlying equity will not be added again. + var expectedSecuritiesInitialized = 1; + if (_securityAdditionsCount == 1) + { + expectedSecuritiesInitialized = 2; + } + + if (securityInializationCounts.Count != expectedSecuritiesInitialized) + { + throw new RegressionTestException($"Expected {expectedSecuritiesInitialized} security to be initialized. " + + $"Got {securityInializationCounts.Count}"); + } + + if (!securityInializationCounts.TryGetValue(security, out var count) || count != 1) + { + throw new RegressionTestException($"Expected the option contract to be initialized once and once only, " + + $"but was initialized {count} times"); + } + + if (expectedSecuritiesInitialized == 2 && + !securityInializationCounts.TryGetValue(Securities[security.Symbol.Underlying], out count) || count != 1) + { + throw new RegressionTestException($"Expected the underlying security to be initialized once and once only, " + + $"but was initialized {count} times"); + } + } + /// /// Data Points count of all timeslices of algorithm /// @@ -53,7 +88,7 @@ protected override Security AddSecurity() /// /// Data Points count of the algorithm history /// - public override int AlgorithmHistoryDataPoints => 0; + public override int AlgorithmHistoryDataPoints => 5; /// /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm diff --git a/Algorithm.CSharp/SecurityInitializationOnReAdditionForSelectedOptionRegressionAlgorithm.cs b/Algorithm.CSharp/SecurityInitializationOnReAdditionForSelectedOptionRegressionAlgorithm.cs index 72b3a758048b..3eb6a426424f 100644 --- a/Algorithm.CSharp/SecurityInitializationOnReAdditionForSelectedOptionRegressionAlgorithm.cs +++ b/Algorithm.CSharp/SecurityInitializationOnReAdditionForSelectedOptionRegressionAlgorithm.cs @@ -28,6 +28,8 @@ namespace QuantConnect.Algorithm.CSharp /// It asserts that the securities are marked as non-tradable when removed and that they are tradable when re-added. /// It also asserts that the algorithm receives the correct security changed events for the added and removed securities. /// + /// Additionally, it tests that the security is initialized after every addition, and no more. + /// /// This specific algorithm tests this behavior for option contracts that are selected, deselected and re-selected. /// public class SecurityInitializationOnReAdditionForSelectedOptionRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition @@ -37,12 +39,31 @@ public class SecurityInitializationOnReAdditionForSelectedOptionRegressionAlgori private bool _selectSingle; private int _selectionsCount; + private Dictionary _securityInializationCounts = new(); + public override void Initialize() { SetStartDate(2014, 06, 04); SetEndDate(2014, 06, 20); SetCash(100000); + var seeder = new FuncSecuritySeeder((security) => + { + if (security is Option option) + { + if (!_securityInializationCounts.TryGetValue(security, out var count)) + { + count = 0; + } + _securityInializationCounts[security] = count + 1; + } + + Debug($"[{Time}] Seeding {security.Symbol}"); + return GetLastKnownPrices(security); + }); + + SetSecurityInitializer(security => seeder.SeedSecurity(security)); + var equitySymbol = QuantConnect.Symbol.Create("AAPL", SecurityType.Equity, Market.USA); _contractsToSelect = new List() @@ -55,6 +76,7 @@ public override void Initialize() option.SetFilter(u => u.Contracts(contracts => { _selectionsCount++; + _securityInializationCounts.Clear(); List selected; if (_selectSingle) @@ -91,6 +113,12 @@ public override void OnSecuritiesChanged(SecurityChanges changes) } } + var addedContracts = changes.AddedSecurities.OfType