From 607bf1edb1013d44c99739b460ffab622c666895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Andr=C3=A9s=20Marino=20Rojas?= <47573394+Marinovsky@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:19:11 -0500 Subject: [PATCH 01/10] Add unit test --- .../Portfolio/SignalExportTargetTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Tests/Algorithm/Framework/Portfolio/SignalExportTargetTests.cs b/Tests/Algorithm/Framework/Portfolio/SignalExportTargetTests.cs index d5d03e953b7b..7ce78489d496 100644 --- a/Tests/Algorithm/Framework/Portfolio/SignalExportTargetTests.cs +++ b/Tests/Algorithm/Framework/Portfolio/SignalExportTargetTests.cs @@ -23,6 +23,7 @@ using QuantConnect.Tests.Engine.DataFeeds; using System; using System.Collections.Generic; +using System.Linq; namespace QuantConnect.Tests.Algorithm.Framework.Portfolio { @@ -278,6 +279,24 @@ public void SignalExportManagerGetsCorrectPortfolioTargetArray(SecurityType secu Assert.AreEqual(quantity, targetQuantity, 1); } + [Test] + public void SignalExportManagerIgnoresIndexSecurities() + { + var algorithm = new AlgorithmStub(true); + algorithm.SetFinishedWarmingUp(); + algorithm.SetCash(100000); + + var security = algorithm.AddIndexOption("SPX", "SPXW"); + security.SetMarketPrice(new Tick(new DateTime(2022, 01, 04), security.Symbol, 144.80m, 144.82m)); + security.Holdings.SetHoldings(144.81m, 10); + + var signalExportManagerHandler = new SignalExportManagerHandler(algorithm); + var result = signalExportManagerHandler.GetPortfolioTargets(out PortfolioTarget[] portfolioTargets); + + Assert.IsTrue(result); + Assert.IsFalse(portfolioTargets.Where(x => x.Symbol.SecurityType == SecurityType.Index).Any()); + } + [TestCaseSource(nameof(SignalExportManagerSkipsNonTradeableFuturesTestCase))] public void SignalExportManagerSkipsNonTradeableFutures(IEnumerable symbols, int expectedNumberOfTargets) { From 25d112b65dd7e94c64a92bbfbf691013ea0603cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Andr=C3=A9s=20Marino=20Rojas?= <47573394+Marinovsky@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:33:34 -0500 Subject: [PATCH 02/10] First potential solution --- .../SignalExports/SignalExportManager.cs | 3 ++- Common/Extensions.cs | 9 ++++++++- Common/Securities/Index/Index.cs | 5 +++++ Tests/Common/Util/ExtensionsTests.cs | 16 ++++++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs index 426cd5849df1..cf95c168258a 100644 --- a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs +++ b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs @@ -151,7 +151,8 @@ private IEnumerable GetPortfolioTargets(decimal totalPortfolioV // Skip non-tradeable securities except canonical futures as some signal providers // like Collective2 accept them. // See https://collective2.com/api-docs/latest#Basic_submitsignal_format - if (!security.IsTradable && !security.Symbol.IsCanonical()) + if ((!security.IsTradable && !security.Symbol.IsCanonical()) || + ((security is Index) && QuantConnect.Securities.Index.Index.ManualSetIsTradable)) { continue; } diff --git a/Common/Extensions.cs b/Common/Extensions.cs index 97670e5d54f2..16e176ab657e 100644 --- a/Common/Extensions.cs +++ b/Common/Extensions.cs @@ -3824,7 +3824,14 @@ public static void ProcessSecurityChanges(this IAlgorithm algorithm, SecurityCha { foreach (var security in securityChanges.AddedSecurities) { - security.IsTradable = true; + if (!(security is QuantConnect.Securities.Index.Index)) + { + security.IsTradable = true; + } + else if (security.IsTradable) + { + QuantConnect.Securities.Index.Index.ManualSetIsTradable = true; + } // uses TryAdd, so don't need to worry about duplicates here algorithm.Securities.Add(security); diff --git a/Common/Securities/Index/Index.cs b/Common/Securities/Index/Index.cs index a1ab74d577d2..16afb2a5711f 100644 --- a/Common/Securities/Index/Index.cs +++ b/Common/Securities/Index/Index.cs @@ -26,6 +26,11 @@ namespace QuantConnect.Securities.Index /// public class Index : Security { + /// + /// Field to check if the user has manually set IsTradable field to true + /// + public static bool ManualSetIsTradable { get; set; } + /// /// Constructor for the INDEX security /// diff --git a/Tests/Common/Util/ExtensionsTests.cs b/Tests/Common/Util/ExtensionsTests.cs index a2877f99a1a1..ab09cf256cc4 100644 --- a/Tests/Common/Util/ExtensionsTests.cs +++ b/Tests/Common/Util/ExtensionsTests.cs @@ -40,6 +40,7 @@ using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Tests.Brokerages; +using QuantConnect.Tests.Engine.DataFeeds; namespace QuantConnect.Tests.Common.Util { @@ -2060,6 +2061,21 @@ public void TryGetDecimalFromCsv_ReturnsDecimalValue(int index, decimal expected Assert.AreEqual(expectedValue, result); } + [TestCase(false)] + [TestCase(true)] + public void IndexSecuritiesCanBeTradable(bool isTradable) + { + var algo = new AlgorithmStub(); + var symbol = Symbol.Create("SPX", SecurityType.Index, Market.USA); + var config = new SubscriptionDataConfig(typeof(TradeBar), symbol, Resolution.Minute, TimeZones.Utc, TimeZones.NewYork, true, true, true); + var symbolProperties = new SymbolProperties("S&P 500 index", "USD", 1, 1, 1, string.Empty); + var index = new QuantConnect.Securities.Index.Index(SecurityExchangeHours.AlwaysOpen(config.DataTimeZone), new Cash("USD", 0, 0), config, symbolProperties, ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null); + index.IsTradable = isTradable; + var securityChanges = SecurityChanges.Create(new List() { index }, new List(), new List(), new List()); + algo.ProcessSecurityChanges(securityChanges); + Assert.AreEqual(isTradable, QuantConnect.Securities.Index.Index.ManualSetIsTradable); + } + private PyObject ConvertToPyObject(object value) { using (Py.GIL()) From dd0c63cbd82f89de08ba8f7d6fd36e54786bcccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Andr=C3=A9s=20Marino=20Rojas?= <47573394+Marinovsky@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:07:52 -0500 Subject: [PATCH 03/10] Add regression tests --- ...ecurityCanBeTradableRegressionAlgorithm.cs | 125 ++++++++++++++++++ ...ecurityIsNotTradableRegressionAlgorithm.cs | 29 ++++ .../SignalExports/SignalExportManager.cs | 9 +- Common/Securities/Index/Index.cs | 2 +- Tests/Common/Util/ExtensionsTests.cs | 15 --- 5 files changed, 161 insertions(+), 19 deletions(-) create mode 100644 Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs create mode 100644 Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs diff --git a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs new file mode 100644 index 000000000000..cb679adb20b6 --- /dev/null +++ b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs @@ -0,0 +1,125 @@ +/* + * 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 QuantConnect.Algorithm.Framework.Portfolio; +using QuantConnect.Data; +using QuantConnect.Interfaces; +using QuantConnect.Securities; +using System.Collections.Generic; +using System.Linq; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// Regression algorithm to test we can manually set index securities to be tradable without breaking + /// SignalExportManager + /// + public class IndexSecurityCanBeTradableRegressionAlgorithm: QCAlgorithm, IRegressionAlgorithmDefinition + { + QuantConnect.Securities.Index.Index _index; + Security _equity; + + public override void Initialize() + { + SetStartDate(2013, 10, 7); + SetEndDate(2013, 10, 7); + + _index = AddIndex("SPX"); + _equity = AddEquity("SPY"); + SetUpIndexSecurity(); + } + + public virtual void SetUpIndexSecurity() + { + Securities[_index.Symbol].IsTradable = true; + } + + public override void OnData(Slice slice) + { + PortfolioTarget[] targets; + SignalExport.GetPortfolioTargets(out targets); + + if (targets.Where(x => x.Symbol.SecurityType == SecurityType.Index).Any()) + { + throw new RegressionTestException($"Index is not a tradable security, so no portfolio target with index security type should have been created"); + } + if (!Portfolio.Invested) + { + SetHoldings(_equity.Symbol, 1); + RemoveSecurity(_index.Symbol); + } + } + + /// + /// 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 virtual bool CanRunLocally { get; } = true; + + /// + /// This is used by the regression test system to indicate which languages this algorithm is written in. + /// + public virtual List Languages { get; } = new() { Language.CSharp, Language.Python }; + + /// + /// Data Points count of all timeslices of algorithm + /// + public virtual long DataPoints => 796; + + /// + /// Data Points count of the algorithm history + /// + public virtual int AlgorithmHistoryDataPoints => 0; + + /// + /// Final status of the algorithm + /// + public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed; + + /// + /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm + /// + public virtual Dictionary ExpectedStatistics => new Dictionary + { + {"Total Orders", "1"}, + {"Average Win", "0%"}, + {"Average Loss", "0%"}, + {"Compounding Annual Return", "0%"}, + {"Drawdown", "0%"}, + {"Expectancy", "0"}, + {"Start Equity", "100000"}, + {"End Equity", "99978.71"}, + {"Net Profit", "0%"}, + {"Sharpe Ratio", "0"}, + {"Sortino Ratio", "0"}, + {"Probabilistic Sharpe Ratio", "0%"}, + {"Loss Rate", "0%"}, + {"Win Rate", "0%"}, + {"Profit-Loss Ratio", "0"}, + {"Alpha", "0"}, + {"Beta", "0"}, + {"Annual Standard Deviation", "0"}, + {"Annual Variance", "0"}, + {"Information Ratio", "0"}, + {"Tracking Error", "0"}, + {"Treynor Ratio", "0"}, + {"Total Fees", "$3.44"}, + {"Estimated Strategy Capacity", "$56000000.00"}, + {"Lowest Capacity Asset", "SPY R735QTJ8XC9X"}, + {"Portfolio Turnover", "99.63%"}, + {"OrderListHash", "3da9fa60bf95b9ed148b95e02e0cfc9e"} + }; + } +} diff --git a/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs new file mode 100644 index 000000000000..4824018a0acc --- /dev/null +++ b/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs @@ -0,0 +1,29 @@ +/* + * 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. + * +*/ + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// Regression algorithm to test SignalExportManager still works as expected even when index securities + /// are not marked as tradable + /// + public class IndexSecurityIsNotTradableRegressionAlgorithm: IndexSecurityCanBeTradableRegressionAlgorithm + { + public override void SetUpIndexSecurity() + { + } + } +} diff --git a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs index cf95c168258a..82d10a004021 100644 --- a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs +++ b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs @@ -87,7 +87,7 @@ public bool SetTargetPortfolioFromPortfolio() /// /// An array of portfolio targets from the algorithm's Portfolio /// True if TotalPortfolioValue was bigger than zero, false otherwise - protected bool GetPortfolioTargets(out PortfolioTarget[] targets) + public bool GetPortfolioTargets(out PortfolioTarget[] targets) { var totalPortfolioValue = _algorithm.Portfolio.TotalPortfolioValue; if (totalPortfolioValue <= 0) @@ -151,8 +151,11 @@ private IEnumerable GetPortfolioTargets(decimal totalPortfolioV // Skip non-tradeable securities except canonical futures as some signal providers // like Collective2 accept them. // See https://collective2.com/api-docs/latest#Basic_submitsignal_format - if ((!security.IsTradable && !security.Symbol.IsCanonical()) || - ((security is Index) && QuantConnect.Securities.Index.Index.ManualSetIsTradable)) + if (!security.IsTradable && !security.Symbol.IsCanonical()) + { + continue; + } + else if ((security is QuantConnect.Securities.Index.Index) && QuantConnect.Securities.Index.Index.ManualSetIsTradable) { continue; } diff --git a/Common/Securities/Index/Index.cs b/Common/Securities/Index/Index.cs index 16afb2a5711f..2db78299f781 100644 --- a/Common/Securities/Index/Index.cs +++ b/Common/Securities/Index/Index.cs @@ -29,7 +29,7 @@ public class Index : Security /// /// Field to check if the user has manually set IsTradable field to true /// - public static bool ManualSetIsTradable { get; set; } + internal static bool ManualSetIsTradable { get; set; } /// /// Constructor for the INDEX security diff --git a/Tests/Common/Util/ExtensionsTests.cs b/Tests/Common/Util/ExtensionsTests.cs index ab09cf256cc4..735273dbc117 100644 --- a/Tests/Common/Util/ExtensionsTests.cs +++ b/Tests/Common/Util/ExtensionsTests.cs @@ -2061,21 +2061,6 @@ public void TryGetDecimalFromCsv_ReturnsDecimalValue(int index, decimal expected Assert.AreEqual(expectedValue, result); } - [TestCase(false)] - [TestCase(true)] - public void IndexSecuritiesCanBeTradable(bool isTradable) - { - var algo = new AlgorithmStub(); - var symbol = Symbol.Create("SPX", SecurityType.Index, Market.USA); - var config = new SubscriptionDataConfig(typeof(TradeBar), symbol, Resolution.Minute, TimeZones.Utc, TimeZones.NewYork, true, true, true); - var symbolProperties = new SymbolProperties("S&P 500 index", "USD", 1, 1, 1, string.Empty); - var index = new QuantConnect.Securities.Index.Index(SecurityExchangeHours.AlwaysOpen(config.DataTimeZone), new Cash("USD", 0, 0), config, symbolProperties, ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null); - index.IsTradable = isTradable; - var securityChanges = SecurityChanges.Create(new List() { index }, new List(), new List(), new List()); - algo.ProcessSecurityChanges(securityChanges); - Assert.AreEqual(isTradable, QuantConnect.Securities.Index.Index.ManualSetIsTradable); - } - private PyObject ConvertToPyObject(object value) { using (Py.GIL()) From 80fa5cd86e6f5710920f8b9318775694b8de2846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Andr=C3=A9s=20Marino=20Rojas?= <47573394+Marinovsky@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:09:51 -0500 Subject: [PATCH 04/10] Nit change --- Tests/Common/Util/ExtensionsTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/Common/Util/ExtensionsTests.cs b/Tests/Common/Util/ExtensionsTests.cs index 735273dbc117..a2877f99a1a1 100644 --- a/Tests/Common/Util/ExtensionsTests.cs +++ b/Tests/Common/Util/ExtensionsTests.cs @@ -40,7 +40,6 @@ using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Tests.Brokerages; -using QuantConnect.Tests.Engine.DataFeeds; namespace QuantConnect.Tests.Common.Util { From ee6e21de5d68c7685482f59c76a1ee6f3b42ed12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Andr=C3=A9s=20Marino=20Rojas?= <47573394+Marinovsky@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:25:58 -0500 Subject: [PATCH 05/10] Address requested changes --- ...ecurityCanBeTradableRegressionAlgorithm.cs | 35 +++++++++++++++++-- .../SignalExports/SignalExportManager.cs | 2 +- Common/Extensions.cs | 4 +-- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs index cb679adb20b6..7452bb63b66e 100644 --- a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs +++ b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs @@ -15,6 +15,7 @@ */ using QuantConnect.Algorithm.Framework.Portfolio; +using QuantConnect.Algorithm.Framework.Portfolio.SignalExports; using QuantConnect.Data; using QuantConnect.Interfaces; using QuantConnect.Securities; @@ -29,7 +30,8 @@ namespace QuantConnect.Algorithm.CSharp /// public class IndexSecurityCanBeTradableRegressionAlgorithm: QCAlgorithm, IRegressionAlgorithmDefinition { - QuantConnect.Securities.Index.Index _index; + Securities.Index.Index _index; + SignalExportManagerTest _signalExportManager; Security _equity; public override void Initialize() @@ -39,6 +41,7 @@ public override void Initialize() _index = AddIndex("SPX"); _equity = AddEquity("SPY"); + _signalExportManager = new SignalExportManagerTest(this); SetUpIndexSecurity(); } @@ -50,7 +53,7 @@ public virtual void SetUpIndexSecurity() public override void OnData(Slice slice) { PortfolioTarget[] targets; - SignalExport.GetPortfolioTargets(out targets); + _signalExportManager.GetPortfolioTargetsFromPortfolio(out targets); if (targets.Where(x => x.Symbol.SecurityType == SecurityType.Index).Any()) { @@ -60,6 +63,20 @@ public override void OnData(Slice slice) { SetHoldings(_equity.Symbol, 1); RemoveSecurity(_index.Symbol); + + AssertIndexIsNotTradable(); + + AddSecurity(_index.Symbol); + } + + AssertIndexIsNotTradable(); + } + + private void AssertIndexIsNotTradable() + { + if (Securities[_index.Symbol].IsTradable) + { + throw new RegressionTestException($"Index {_index} has already been removed and should be tradable no more"); } } @@ -71,7 +88,7 @@ public override void OnData(Slice slice) /// /// This is used by the regression test system to indicate which languages this algorithm is written in. /// - public virtual List Languages { get; } = new() { Language.CSharp, Language.Python }; + public virtual List Languages { get; } = new() { Language.CSharp }; /// /// Data Points count of all timeslices of algorithm @@ -121,5 +138,17 @@ public override void OnData(Slice slice) {"Portfolio Turnover", "99.63%"}, {"OrderListHash", "3da9fa60bf95b9ed148b95e02e0cfc9e"} }; + + private class SignalExportManagerTest: SignalExportManager + { + public SignalExportManagerTest(IAlgorithm algorithm) : base(algorithm) + { + } + + public void GetPortfolioTargetsFromPortfolio(out PortfolioTarget[] portfolioTargets) + { + base.GetPortfolioTargets(out portfolioTargets); + } + } } } diff --git a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs index 82d10a004021..c77d5e2d16c3 100644 --- a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs +++ b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs @@ -87,7 +87,7 @@ public bool SetTargetPortfolioFromPortfolio() /// /// An array of portfolio targets from the algorithm's Portfolio /// True if TotalPortfolioValue was bigger than zero, false otherwise - public bool GetPortfolioTargets(out PortfolioTarget[] targets) + protected bool GetPortfolioTargets(out PortfolioTarget[] targets) { var totalPortfolioValue = _algorithm.Portfolio.TotalPortfolioValue; if (totalPortfolioValue <= 0) diff --git a/Common/Extensions.cs b/Common/Extensions.cs index 16e176ab657e..9bf4222d0b6c 100644 --- a/Common/Extensions.cs +++ b/Common/Extensions.cs @@ -3824,13 +3824,13 @@ public static void ProcessSecurityChanges(this IAlgorithm algorithm, SecurityCha { foreach (var security in securityChanges.AddedSecurities) { - if (!(security is QuantConnect.Securities.Index.Index)) + if (!(security is Securities.Index.Index)) { security.IsTradable = true; } else if (security.IsTradable) { - QuantConnect.Securities.Index.Index.ManualSetIsTradable = true; + Securities.Index.Index.ManualSetIsTradable = true; } // uses TryAdd, so don't need to worry about duplicates here From b6f5fec66bd094f26c61e6e321520d96c316e3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Andr=C3=A9s=20Marino=20Rojas?= <47573394+Marinovsky@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:46:50 -0500 Subject: [PATCH 06/10] Nit change --- .../IndexSecurityCanBeTradableRegressionAlgorithm.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs index 7452bb63b66e..18a921399ae1 100644 --- a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs +++ b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs @@ -52,8 +52,7 @@ public virtual void SetUpIndexSecurity() public override void OnData(Slice slice) { - PortfolioTarget[] targets; - _signalExportManager.GetPortfolioTargetsFromPortfolio(out targets); + _signalExportManager.GetPortfolioTargetsFromPortfolio(out PortfolioTarget[] targets); if (targets.Where(x => x.Symbol.SecurityType == SecurityType.Index).Any()) { From a088af88b2b2bce078c4deb14e78e3dd00ae89c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Andr=C3=A9s=20Marino=20Rojas?= <47573394+Marinovsky@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:01:47 -0500 Subject: [PATCH 07/10] Nit change --- ...ecurityCanBeTradableRegressionAlgorithm.cs | 24 +++++++++---------- .../SignalExports/SignalExportManager.cs | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs index 18a921399ae1..673c4d01fb36 100644 --- a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs +++ b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs @@ -30,29 +30,29 @@ namespace QuantConnect.Algorithm.CSharp /// public class IndexSecurityCanBeTradableRegressionAlgorithm: QCAlgorithm, IRegressionAlgorithmDefinition { - Securities.Index.Index _index; - SignalExportManagerTest _signalExportManager; - Security _equity; + private SignalExportManagerTest _signalExportManagerTest; + private Symbol _equity; + private Symbol _index; public override void Initialize() { SetStartDate(2013, 10, 7); SetEndDate(2013, 10, 7); - _index = AddIndex("SPX"); - _equity = AddEquity("SPY"); - _signalExportManager = new SignalExportManagerTest(this); + _index = AddIndex("SPX").Symbol; + _equity = AddEquity("SPY").Symbol; + _signalExportManagerTest = new SignalExportManagerTest(this); SetUpIndexSecurity(); } public virtual void SetUpIndexSecurity() { - Securities[_index.Symbol].IsTradable = true; + Securities[_index].IsTradable = true; } public override void OnData(Slice slice) { - _signalExportManager.GetPortfolioTargetsFromPortfolio(out PortfolioTarget[] targets); + _signalExportManagerTest.GetPortfolioTargetsFromPortfolio(out PortfolioTarget[] targets); if (targets.Where(x => x.Symbol.SecurityType == SecurityType.Index).Any()) { @@ -60,12 +60,12 @@ public override void OnData(Slice slice) } if (!Portfolio.Invested) { - SetHoldings(_equity.Symbol, 1); - RemoveSecurity(_index.Symbol); + SetHoldings(_equity, 1); + RemoveSecurity(_index); AssertIndexIsNotTradable(); - AddSecurity(_index.Symbol); + AddSecurity(_index); } AssertIndexIsNotTradable(); @@ -73,7 +73,7 @@ public override void OnData(Slice slice) private void AssertIndexIsNotTradable() { - if (Securities[_index.Symbol].IsTradable) + if (Securities[_index].IsTradable) { throw new RegressionTestException($"Index {_index} has already been removed and should be tradable no more"); } diff --git a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs index c77d5e2d16c3..3c43ede91580 100644 --- a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs +++ b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs @@ -155,7 +155,7 @@ private IEnumerable GetPortfolioTargets(decimal totalPortfolioV { continue; } - else if ((security is QuantConnect.Securities.Index.Index) && QuantConnect.Securities.Index.Index.ManualSetIsTradable) + else if ((security is Securities.Index.Index) && Securities.Index.Index.ManualSetIsTradable) { continue; } From 8cd03e04e18246127bc71f793327c29cce471e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Andr=C3=A9s=20Marino=20Rojas?= <47573394+Marinovsky@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:48:25 -0500 Subject: [PATCH 08/10] Improve implementation --- ...exSecurityCanBeTradableRegressionAlgorithm.cs | 16 +++++++++++++--- .../SignalExports/SignalExportManager.cs | 4 ---- Common/Extensions.cs | 9 +++------ Common/Securities/Index/Index.cs | 14 ++++++++++++++ 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs index 673c4d01fb36..14c0c93bde6e 100644 --- a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs +++ b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs @@ -53,11 +53,21 @@ public virtual void SetUpIndexSecurity() public override void OnData(Slice slice) { _signalExportManagerTest.GetPortfolioTargetsFromPortfolio(out PortfolioTarget[] targets); - - if (targets.Where(x => x.Symbol.SecurityType == SecurityType.Index).Any()) + if (Securities[_index].IsTradable) { - throw new RegressionTestException($"Index is not a tradable security, so no portfolio target with index security type should have been created"); + if (!targets.Where(x => x.Symbol.SecurityType == SecurityType.Index).Any()) + { + throw new RegressionTestException($"Index {_index} is marked as tradable security, but no portfolio target with index security type was created"); + } } + else + { + if (targets.Where(x => x.Symbol.SecurityType == SecurityType.Index).Any()) + { + throw new RegressionTestException($"Index is not a tradable security, so no portfolio target with index security type should have been created"); + } + } + if (!Portfolio.Invested) { SetHoldings(_equity, 1); diff --git a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs index 3c43ede91580..426cd5849df1 100644 --- a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs +++ b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs @@ -155,10 +155,6 @@ private IEnumerable GetPortfolioTargets(decimal totalPortfolioV { continue; } - else if ((security is Securities.Index.Index) && Securities.Index.Index.ManualSetIsTradable) - { - continue; - } var marginParameters = MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security, holding.Quantity); var adjustedPercent = security.BuyingPowerModel.GetMaintenanceMargin(marginParameters) / totalPortfolioValue; diff --git a/Common/Extensions.cs b/Common/Extensions.cs index 9bf4222d0b6c..fbb5a2df97bc 100644 --- a/Common/Extensions.cs +++ b/Common/Extensions.cs @@ -3824,14 +3824,11 @@ public static void ProcessSecurityChanges(this IAlgorithm algorithm, SecurityCha { foreach (var security in securityChanges.AddedSecurities) { - if (!(security is Securities.Index.Index)) + if (security.Type == SecurityType.Index && !Securities.Index.Index.ManualSetIsTradable) { - security.IsTradable = true; - } - else if (security.IsTradable) - { - Securities.Index.Index.ManualSetIsTradable = true; + continue; } + security.IsTradable = true; // uses TryAdd, so don't need to worry about duplicates here algorithm.Securities.Add(security); diff --git a/Common/Securities/Index/Index.cs b/Common/Securities/Index/Index.cs index 2db78299f781..630ff37f42de 100644 --- a/Common/Securities/Index/Index.cs +++ b/Common/Securities/Index/Index.cs @@ -26,6 +26,20 @@ namespace QuantConnect.Securities.Index /// public class Index : Security { + private bool _isTradable; + + /// + /// Gets or sets whether or not this security should be considered tradable + /// + public override bool IsTradable { + get => _isTradable; + set + { + if (value) ManualSetIsTradable = true; + _isTradable = value; + } + } + /// /// Field to check if the user has manually set IsTradable field to true /// From 06f58a1488290c69c847b94aca85d008a78564b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Andr=C3=A9s=20Marino=20Rojas?= <47573394+Marinovsky@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:02:28 -0500 Subject: [PATCH 09/10] Fix bugs --- ...xSecurityCanBeTradableRegressionAlgorithm.cs | 17 ++++++++++------- ...xSecurityIsNotTradableRegressionAlgorithm.cs | 4 +--- Common/Extensions.cs | 2 +- Common/Securities/Index/Index.cs | 5 ----- Common/Securities/Security.cs | 12 +++++++++++- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs index 14c0c93bde6e..67cd539d57e6 100644 --- a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs +++ b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs @@ -34,6 +34,8 @@ public class IndexSecurityCanBeTradableRegressionAlgorithm: QCAlgorithm, IRegres private Symbol _equity; private Symbol _index; + public virtual bool IsTradable { get; set; } = true; + public override void Initialize() { SetStartDate(2013, 10, 7); @@ -42,18 +44,18 @@ public override void Initialize() _index = AddIndex("SPX").Symbol; _equity = AddEquity("SPY").Symbol; _signalExportManagerTest = new SignalExportManagerTest(this); - SetUpIndexSecurity(); - } - - public virtual void SetUpIndexSecurity() - { - Securities[_index].IsTradable = true; + Securities[_index].IsTradable = IsTradable; } public override void OnData(Slice slice) { + if (IsTradable != Securities[_index].IsTradable) + { + throw new RegressionTestException($"Index.IsTradable should be {IsTradable}, but was {Securities[_index].IsTradable}"); + } + _signalExportManagerTest.GetPortfolioTargetsFromPortfolio(out PortfolioTarget[] targets); - if (Securities[_index].IsTradable) + if (IsTradable) { if (!targets.Where(x => x.Symbol.SecurityType == SecurityType.Index).Any()) { @@ -76,6 +78,7 @@ public override void OnData(Slice slice) AssertIndexIsNotTradable(); AddSecurity(_index); + IsTradable = false; } AssertIndexIsNotTradable(); diff --git a/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs index 4824018a0acc..c88aeabd6e01 100644 --- a/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs +++ b/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs @@ -22,8 +22,6 @@ namespace QuantConnect.Algorithm.CSharp /// public class IndexSecurityIsNotTradableRegressionAlgorithm: IndexSecurityCanBeTradableRegressionAlgorithm { - public override void SetUpIndexSecurity() - { - } + public override bool IsTradable { get; set; } = false; } } diff --git a/Common/Extensions.cs b/Common/Extensions.cs index fbb5a2df97bc..b0ac20e5e512 100644 --- a/Common/Extensions.cs +++ b/Common/Extensions.cs @@ -3824,7 +3824,7 @@ public static void ProcessSecurityChanges(this IAlgorithm algorithm, SecurityCha { foreach (var security in securityChanges.AddedSecurities) { - if (security.Type == SecurityType.Index && !Securities.Index.Index.ManualSetIsTradable) + if (security.Type == SecurityType.Index && !security.ManualSetIsTradable) { continue; } diff --git a/Common/Securities/Index/Index.cs b/Common/Securities/Index/Index.cs index 630ff37f42de..da194d43fe86 100644 --- a/Common/Securities/Index/Index.cs +++ b/Common/Securities/Index/Index.cs @@ -40,11 +40,6 @@ public override bool IsTradable { } } - /// - /// Field to check if the user has manually set IsTradable field to true - /// - internal static bool ManualSetIsTradable { get; set; } - /// /// Constructor for the INDEX security /// diff --git a/Common/Securities/Security.cs b/Common/Securities/Security.cs index 480c31848d52..47b55f4b53c2 100644 --- a/Common/Securities/Security.cs +++ b/Common/Securities/Security.cs @@ -55,6 +55,11 @@ public class Security : DynamicObject, ISecurityPrice /// Just use a list + lock, not concurrent bag, avoid garbage it creates for features we don't need here. See https://github.com/dotnet/runtime/issues/23103 private readonly List _subscriptionsBag; + /// + /// Field to check if the user has manually set IsTradable field to true + /// + internal bool ManualSetIsTradable { get; set; } + /// /// This securities /// @@ -416,7 +421,12 @@ IMarginInterestRateModel marginInterestRateModel _subscriptionsBag = new (); QuoteCurrency = quoteCurrency; SymbolProperties = symbolProperties; - IsTradable = true; + + if (Symbol.SecurityType != SecurityType.Index) + { + IsTradable = true; + } + Cache = cache; Exchange = exchange; DataFilter = dataFilter; From 1c04484733201fafa92cd4c186eaa36416267f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Andr=C3=A9s=20Marino=20Rojas?= <47573394+Marinovsky@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:57:38 -0500 Subject: [PATCH 10/10] Address requested changes --- .../IndexSecurityIsNotTradableRegressionAlgorithm.cs | 2 +- Common/Extensions.cs | 9 +++++---- Common/Securities/Index/Index.cs | 5 +++++ Common/Securities/Security.cs | 5 ----- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs index c88aeabd6e01..29a5ed16d397 100644 --- a/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs +++ b/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs @@ -22,6 +22,6 @@ namespace QuantConnect.Algorithm.CSharp /// public class IndexSecurityIsNotTradableRegressionAlgorithm: IndexSecurityCanBeTradableRegressionAlgorithm { - public override bool IsTradable { get; set; } = false; + public override bool IsTradable { get; set; } } } diff --git a/Common/Extensions.cs b/Common/Extensions.cs index b0ac20e5e512..15c7c7961421 100644 --- a/Common/Extensions.cs +++ b/Common/Extensions.cs @@ -3824,14 +3824,15 @@ public static void ProcessSecurityChanges(this IAlgorithm algorithm, SecurityCha { foreach (var security in securityChanges.AddedSecurities) { - if (security.Type == SecurityType.Index && !security.ManualSetIsTradable) + // uses TryAdd, so don't need to worry about duplicates here + algorithm.Securities.Add(security); + + if (security.Type == SecurityType.Index && !(security as Securities.Index.Index).ManualSetIsTradable) { continue; } - security.IsTradable = true; - // uses TryAdd, so don't need to worry about duplicates here - algorithm.Securities.Add(security); + security.IsTradable = true; } var activeSecurities = algorithm.UniverseManager.ActiveSecurities; diff --git a/Common/Securities/Index/Index.cs b/Common/Securities/Index/Index.cs index da194d43fe86..d00d354e3512 100644 --- a/Common/Securities/Index/Index.cs +++ b/Common/Securities/Index/Index.cs @@ -40,6 +40,11 @@ public override bool IsTradable { } } + /// + /// Field to check if the user has manually set IsTradable field to true + /// + internal bool ManualSetIsTradable { get; set; } + /// /// Constructor for the INDEX security /// diff --git a/Common/Securities/Security.cs b/Common/Securities/Security.cs index 47b55f4b53c2..7a9e025dd218 100644 --- a/Common/Securities/Security.cs +++ b/Common/Securities/Security.cs @@ -55,11 +55,6 @@ public class Security : DynamicObject, ISecurityPrice /// Just use a list + lock, not concurrent bag, avoid garbage it creates for features we don't need here. See https://github.com/dotnet/runtime/issues/23103 private readonly List _subscriptionsBag; - /// - /// Field to check if the user has manually set IsTradable field to true - /// - internal bool ManualSetIsTradable { get; set; } - /// /// This securities ///