diff --git a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs new file mode 100644 index 000000000000..67cd539d57e6 --- /dev/null +++ b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs @@ -0,0 +1,166 @@ +/* + * 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.Algorithm.Framework.Portfolio.SignalExports; +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 + { + private SignalExportManagerTest _signalExportManagerTest; + private Symbol _equity; + private Symbol _index; + + public virtual bool IsTradable { get; set; } = true; + + public override void Initialize() + { + SetStartDate(2013, 10, 7); + SetEndDate(2013, 10, 7); + + _index = AddIndex("SPX").Symbol; + _equity = AddEquity("SPY").Symbol; + _signalExportManagerTest = new SignalExportManagerTest(this); + 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 (IsTradable) + { + 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); + RemoveSecurity(_index); + + AssertIndexIsNotTradable(); + + AddSecurity(_index); + IsTradable = false; + } + + AssertIndexIsNotTradable(); + } + + private void AssertIndexIsNotTradable() + { + if (Securities[_index].IsTradable) + { + throw new RegressionTestException($"Index {_index} has already been removed and should be tradable no more"); + } + } + + /// + /// 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 }; + + /// + /// 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"} + }; + + private class SignalExportManagerTest: SignalExportManager + { + public SignalExportManagerTest(IAlgorithm algorithm) : base(algorithm) + { + } + + public void GetPortfolioTargetsFromPortfolio(out PortfolioTarget[] portfolioTargets) + { + base.GetPortfolioTargets(out portfolioTargets); + } + } + } +} diff --git a/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs new file mode 100644 index 000000000000..29a5ed16d397 --- /dev/null +++ b/Algorithm.CSharp/IndexSecurityIsNotTradableRegressionAlgorithm.cs @@ -0,0 +1,27 @@ +/* + * 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 bool IsTradable { get; set; } + } +} diff --git a/Common/Extensions.cs b/Common/Extensions.cs index 97670e5d54f2..15c7c7961421 100644 --- a/Common/Extensions.cs +++ b/Common/Extensions.cs @@ -3824,10 +3824,15 @@ public static void ProcessSecurityChanges(this IAlgorithm algorithm, SecurityCha { foreach (var security in securityChanges.AddedSecurities) { - security.IsTradable = true; - // 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; } var activeSecurities = algorithm.UniverseManager.ActiveSecurities; diff --git a/Common/Securities/Index/Index.cs b/Common/Securities/Index/Index.cs index a1ab74d577d2..d00d354e3512 100644 --- a/Common/Securities/Index/Index.cs +++ b/Common/Securities/Index/Index.cs @@ -26,6 +26,25 @@ 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 + /// + internal bool ManualSetIsTradable { get; set; } + /// /// Constructor for the INDEX security /// diff --git a/Common/Securities/Security.cs b/Common/Securities/Security.cs index 480c31848d52..7a9e025dd218 100644 --- a/Common/Securities/Security.cs +++ b/Common/Securities/Security.cs @@ -416,7 +416,12 @@ IMarginInterestRateModel marginInterestRateModel _subscriptionsBag = new (); QuoteCurrency = quoteCurrency; SymbolProperties = symbolProperties; - IsTradable = true; + + if (Symbol.SecurityType != SecurityType.Index) + { + IsTradable = true; + } + Cache = cache; Exchange = exchange; DataFilter = dataFilter; 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) {