From a5b0aaeb3dea38f638a4b07720b7ce54afa0796a 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, 9 Dec 2024 13:28:26 -0500 Subject: [PATCH 1/4] First approach --- .../SignalExports/SignalExportManager.cs | 6 +++++ .../Portfolio/SignalExportTargetTests.cs | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs index 4d29ed57a9d3..68850457b71a 100644 --- a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs +++ b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs @@ -16,6 +16,7 @@ using QuantConnect.Interfaces; using QuantConnect.Securities; using System.Collections.Generic; +using System.Linq; namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports { @@ -101,6 +102,10 @@ protected bool GetPortfolioTargets(out PortfolioTarget[] targets) foreach (var holding in portfolio.Values) { var security = _algorithm.Securities[holding.Symbol]; + + // Skip non-tradeable securities + if (!security.IsTradable) continue; + var marginParameters = MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security, holding.Quantity); var adjustedPercent = security.BuyingPowerModel.GetMaintenanceMargin(marginParameters) / totalPortfolioValue; // See PortfolioTarget.Percent: @@ -119,6 +124,7 @@ protected bool GetPortfolioTargets(out PortfolioTarget[] targets) ++index; } + targets = targets.Where(x => x != null).ToArray(); return true; } diff --git a/Tests/Algorithm/Framework/Portfolio/SignalExportTargetTests.cs b/Tests/Algorithm/Framework/Portfolio/SignalExportTargetTests.cs index 870f94537e36..d5d03e953b7b 100644 --- a/Tests/Algorithm/Framework/Portfolio/SignalExportTargetTests.cs +++ b/Tests/Algorithm/Framework/Portfolio/SignalExportTargetTests.cs @@ -278,6 +278,27 @@ public void SignalExportManagerGetsCorrectPortfolioTargetArray(SecurityType secu Assert.AreEqual(quantity, targetQuantity, 1); } + [TestCaseSource(nameof(SignalExportManagerSkipsNonTradeableFuturesTestCase))] + public void SignalExportManagerSkipsNonTradeableFutures(IEnumerable symbols, int expectedNumberOfTargets) + { + var algorithm = new AlgorithmStub(true); + algorithm.SetFinishedWarmingUp(); + algorithm.SetCash(100000); + + foreach (var symbol in symbols) + { + var security = algorithm.AddSecurity(symbol); + security.SetMarketPrice(new Tick(new DateTime(2022, 01, 04), security.Symbol, 144.80m, 144.82m)); + security.Holdings.SetHoldings(144.81m, 100); + } + + var signalExportManagerHandler = new SignalExportManagerHandler(algorithm); + var result = signalExportManagerHandler.GetPortfolioTargets(out PortfolioTarget[] portfolioTargets); + + Assert.IsTrue(result); + Assert.AreEqual(expectedNumberOfTargets, portfolioTargets.Length); + } + [Test] public void SignalExportManagerReturnsFalseWhenNegativeTotalPortfolioValue() { @@ -410,5 +431,11 @@ public string GetMessageSent(SignalExportTargetParameters parameters) return message; } } + + private static object[] SignalExportManagerSkipsNonTradeableFuturesTestCase = + { + new object[] { new List() { Symbols.AAPL, Symbols.SPY, Symbols.SPX }, 2 }, + new object[] { new List() { Symbols.AAPL, Symbols.SPY, Symbols.NFLX }, 3 }, + }; } } From 5d930ec1990fe6b22684eed5680b1573c4b1c32d 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, 9 Dec 2024 14:24:22 -0500 Subject: [PATCH 2/4] Fix bug --- .../Framework/Portfolio/SignalExports/SignalExportManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs index 68850457b71a..87a85fe776f3 100644 --- a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs +++ b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs @@ -104,7 +104,7 @@ protected bool GetPortfolioTargets(out PortfolioTarget[] targets) var security = _algorithm.Securities[holding.Symbol]; // Skip non-tradeable securities - if (!security.IsTradable) continue; + if (!security.IsTradable && !security.Symbol.IsCanonical()) continue; var marginParameters = MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security, holding.Quantity); var adjustedPercent = security.BuyingPowerModel.GetMaintenanceMargin(marginParameters) / totalPortfolioValue; From 1da9f6a011abda3ad58d44290e07d45ce644c0b6 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, 9 Dec 2024 17:51:26 -0500 Subject: [PATCH 3/4] Address requests --- .../SignalExports/SignalExportManager.cs | 67 ++++++++++--------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs index 87a85fe776f3..879b2c2a43ca 100644 --- a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs +++ b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs @@ -88,43 +88,15 @@ public bool SetTargetPortfolioFromPortfolio() /// True if TotalPortfolioValue was bigger than zero, false otherwise protected bool GetPortfolioTargets(out PortfolioTarget[] targets) { - var portfolio = _algorithm.Portfolio; - targets = new PortfolioTarget[portfolio.Values.Count]; - var index = 0; - - var totalPortfolioValue = portfolio.TotalPortfolioValue; + var totalPortfolioValue = _algorithm.Portfolio.TotalPortfolioValue; if (totalPortfolioValue <= 0) { _algorithm.Error("Total portfolio value was less than or equal to 0"); + targets = null; return false; } - foreach (var holding in portfolio.Values) - { - var security = _algorithm.Securities[holding.Symbol]; - - // Skip non-tradeable securities - if (!security.IsTradable && !security.Symbol.IsCanonical()) continue; - - var marginParameters = MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security, holding.Quantity); - var adjustedPercent = security.BuyingPowerModel.GetMaintenanceMargin(marginParameters) / totalPortfolioValue; - // See PortfolioTarget.Percent: - // we normalize the target buying power by the leverage so we work in the land of margin - var holdingPercent = adjustedPercent * security.BuyingPowerModel.GetLeverage(security); - - // FreePortfolioValue is used for orders not to be rejected due to volatility when using SetHoldings and CalculateOrderQuantity - // Then, we need to substract its value from the TotalPortfolioValue and obtain again the holding percentage for our holding - var adjustedHoldingPercent = (holdingPercent * totalPortfolioValue) / _algorithm.Portfolio.TotalPortfolioValueLessFreeBuffer; - if (holding.Quantity < 0) - { - adjustedHoldingPercent *= -1; - } - - targets[index] = new PortfolioTarget(holding.Symbol, adjustedHoldingPercent); - ++index; - } - - targets = targets.Where(x => x != null).ToArray(); + targets = GetPortfolioTargets(totalPortfolioValue).ToArray(); return true; } @@ -168,5 +140,38 @@ public bool SetTargetPortfolio(params PortfolioTarget[] portfolioTargets) return result; } + + private IEnumerable GetPortfolioTargets(decimal totalPortfolioValue) + { + foreach (var holding in _algorithm.Portfolio.Values) + { + var security = _algorithm.Securities[holding.Symbol]; + + // 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()) + { + _algorithm.Debug($"Security {security} is neither tradeable nor canonical"); + continue; + } + + var marginParameters = MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security, holding.Quantity); + var adjustedPercent = security.BuyingPowerModel.GetMaintenanceMargin(marginParameters) / totalPortfolioValue; + // See PortfolioTarget.Percent: + // we normalize the target buying power by the leverage so we work in the land of margin + var holdingPercent = adjustedPercent * security.BuyingPowerModel.GetLeverage(security); + + // FreePortfolioValue is used for orders not to be rejected due to volatility when using SetHoldings and CalculateOrderQuantity + // Then, we need to substract its value from the TotalPortfolioValue and obtain again the holding percentage for our holding + var adjustedHoldingPercent = (holdingPercent * totalPortfolioValue) / _algorithm.Portfolio.TotalPortfolioValueLessFreeBuffer; + if (holding.Quantity < 0) + { + adjustedHoldingPercent *= -1; + } + + yield return new PortfolioTarget(holding.Symbol, adjustedHoldingPercent); + } + } } } From 6aabecd5c54143039366b191e1989aa1d306a829 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, 10 Dec 2024 14:59:49 -0500 Subject: [PATCH 4/4] Address requests --- .../Framework/Portfolio/SignalExports/SignalExportManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs index 879b2c2a43ca..426cd5849df1 100644 --- a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs +++ b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs @@ -16,6 +16,7 @@ using QuantConnect.Interfaces; using QuantConnect.Securities; using System.Collections.Generic; +using System; using System.Linq; namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports @@ -92,7 +93,7 @@ protected bool GetPortfolioTargets(out PortfolioTarget[] targets) if (totalPortfolioValue <= 0) { _algorithm.Error("Total portfolio value was less than or equal to 0"); - targets = null; + targets = Array.Empty(); return false; } @@ -152,7 +153,6 @@ private IEnumerable GetPortfolioTargets(decimal totalPortfolioV // See https://collective2.com/api-docs/latest#Basic_submitsignal_format if (!security.IsTradable && !security.Symbol.IsCanonical()) { - _algorithm.Debug($"Security {security} is neither tradeable nor canonical"); continue; }