From 4b75d2ef01b43d8f3e68eecfaf4faa7e09be3c69 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Wed, 22 Jan 2025 17:10:09 -0500 Subject: [PATCH] Refactor SetHoldings to return a List --- Algorithm/QCAlgorithm.Trading.cs | 44 ++++++++++++------- Tests/Algorithm/AlgorithmTradingTests.cs | 55 ++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 16 deletions(-) diff --git a/Algorithm/QCAlgorithm.Trading.cs b/Algorithm/QCAlgorithm.Trading.cs index dcd8262df3e9..db3610cd53af 100644 --- a/Algorithm/QCAlgorithm.Trading.cs +++ b/Algorithm/QCAlgorithm.Trading.cs @@ -1326,14 +1326,16 @@ public void SetMaximumOrders(int max) /// True will liquidate existing holdings /// Tag the order with a short string. /// The order properties to use. Defaults to + /// A list of order tickets. /// [DocumentationAttribute(TradingAndOrders)] - public void SetHoldings(List targets, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) + public List SetHoldings(List targets, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) { + var orderTickets = new List(); //If they triggered a liquidate if (liquidateExistingHoldings) { - Liquidate(GetSymbolsToLiquidate(targets.Select(t => t.Symbol)), tag: tag, orderProperties: orderProperties); + orderTickets = Liquidate(GetSymbolsToLiquidate(targets.Select(t => t.Symbol)), tag: tag, orderProperties: orderProperties); } foreach (var portfolioTarget in targets @@ -1341,8 +1343,10 @@ public void SetHoldings(List targets, bool liquidateExistingHol .Select(target => new PortfolioTarget(target.Symbol, CalculateOrderQuantity(target.Symbol, target.Quantity))) .OrderTargetsByMarginImpact(this, targetIsDelta: true)) { - SetHoldingsImpl(portfolioTarget.Symbol, portfolioTarget.Quantity, false, tag, orderProperties); + var tickets = SetHoldingsImpl(portfolioTarget.Symbol, portfolioTarget.Quantity, false, tag, orderProperties); + orderTickets.AddRange(tickets); } + return orderTickets; } /// @@ -1353,11 +1357,12 @@ public void SetHoldings(List targets, bool liquidateExistingHol /// liquidate existing holdings if necessary to hold this stock /// Tag the order with a short string. /// The order properties to use. Defaults to + /// A list of order tickets. /// [DocumentationAttribute(TradingAndOrders)] - public void SetHoldings(Symbol symbol, double percentage, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) + public List SetHoldings(Symbol symbol, double percentage, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) { - SetHoldings(symbol, percentage.SafeDecimalCast(), liquidateExistingHoldings, tag, orderProperties); + return SetHoldings(symbol, percentage.SafeDecimalCast(), liquidateExistingHoldings, tag, orderProperties); } /// @@ -1368,11 +1373,12 @@ public void SetHoldings(Symbol symbol, double percentage, bool liquidateExisting /// bool liquidate existing holdings if necessary to hold this stock /// Tag the order with a short string. /// The order properties to use. Defaults to + /// A list of order tickets. /// [DocumentationAttribute(TradingAndOrders)] - public void SetHoldings(Symbol symbol, float percentage, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) + public List SetHoldings(Symbol symbol, float percentage, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) { - SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag, orderProperties); + return SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag, orderProperties); } /// @@ -1383,11 +1389,12 @@ public void SetHoldings(Symbol symbol, float percentage, bool liquidateExistingH /// bool liquidate existing holdings if necessary to hold this stock /// Tag the order with a short string. /// The order properties to use. Defaults to + /// A list of order tickets. /// [DocumentationAttribute(TradingAndOrders)] - public void SetHoldings(Symbol symbol, int percentage, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) + public List SetHoldings(Symbol symbol, int percentage, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) { - SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag, orderProperties); + return SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag, orderProperties); } /// @@ -1401,22 +1408,24 @@ public void SetHoldings(Symbol symbol, int percentage, bool liquidateExistingHol /// bool flag to clean all existing holdings before setting new faction. /// Tag the order with a short string. /// The order properties to use. Defaults to + /// A list of order tickets. /// [DocumentationAttribute(TradingAndOrders)] - public void SetHoldings(Symbol symbol, decimal percentage, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) + public List SetHoldings(Symbol symbol, decimal percentage, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) { - SetHoldingsImpl(symbol, CalculateOrderQuantity(symbol, percentage), liquidateExistingHoldings, tag, orderProperties); + return SetHoldingsImpl(symbol, CalculateOrderQuantity(symbol, percentage), liquidateExistingHoldings, tag, orderProperties); } /// /// Set holdings implementation, which uses order quantities (delta) not percentage nor target final quantity /// - private void SetHoldingsImpl(Symbol symbol, decimal orderQuantity, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) + private List SetHoldingsImpl(Symbol symbol, decimal orderQuantity, bool liquidateExistingHoldings = false, string tag = null, IOrderProperties orderProperties = null) { + var orderTickets = new List(); //If they triggered a liquidate if (liquidateExistingHoldings) { - Liquidate(GetSymbolsToLiquidate([symbol]), tag: tag, orderProperties: orderProperties); + orderTickets = Liquidate(GetSymbolsToLiquidate([symbol]), tag: tag, orderProperties: orderProperties); } tag ??= ""; @@ -1435,19 +1444,22 @@ private void SetHoldingsImpl(Symbol symbol, decimal orderQuantity, bool liquidat if (!Securities.TryGetValue(symbol, out security)) { Error($"{symbol} not found in portfolio. Request this data when initializing the algorithm."); - return; + return orderTickets; } //Check whether the exchange is open to send a market order. If not, send a market on open order instead + OrderTicket ticket; if (security.Exchange.ExchangeOpen) { - MarketOrder(symbol, quantity, false, tag, orderProperties); + ticket = MarketOrder(symbol, quantity, false, tag, orderProperties); } else { - MarketOnOpenOrder(symbol, quantity, tag, orderProperties); + ticket = MarketOnOpenOrder(symbol, quantity, tag, orderProperties); } + orderTickets.Add(ticket); } + return orderTickets; } /// diff --git a/Tests/Algorithm/AlgorithmTradingTests.cs b/Tests/Algorithm/AlgorithmTradingTests.cs index 1a3732a20327..f30ce4f969c9 100644 --- a/Tests/Algorithm/AlgorithmTradingTests.cs +++ b/Tests/Algorithm/AlgorithmTradingTests.cs @@ -31,6 +31,7 @@ using QuantConnect.Data; using QuantConnect.Indicators; using Python.Runtime; +using QuantConnect.Algorithm.Framework.Portfolio; namespace QuantConnect.Tests.Algorithm { @@ -1320,6 +1321,51 @@ public void SetHoldings_Long_ToZero_RoundOff() // Assert.AreEqual(2500, actual); //} + [TestCaseSource(nameof(SetHoldingReturnsOrderTicketsTestCases))] + public void TestSetHoldingReturnsOrderTickets(IEnumerable symbols, bool liquidateExistingHoldings, int expectedOrderTickets, string tag) + { + symbols = symbols.ToList(); + // Initialize the algorithm and add equities to the portfolio + var algo = GetAlgorithm(out _, 1, 0); + var appl = algo.AddEquity("AAPL"); + var spy = algo.AddEquity("SPY"); + var ibm = algo.AddEquity("IBM"); + + // Update prices and set initial holdings for the equities + Update(appl, 100); + Update(spy, 200); + Update(ibm, 300); + appl.Holdings.SetHoldings(25, 3); + spy.Holdings.SetHoldings(25, 3); + ibm.Holdings.SetHoldings(25, 3); + + List orderTickets; + if (symbols.Count() > 1) + { + // Handle multiple symbols by creating portfolio targets + var portfolioTargets = new List(); + foreach (var symbol in symbols) + { + portfolioTargets.Add(new PortfolioTarget(symbol, 0.5m)); + } + orderTickets = algo.SetHoldings(portfolioTargets, liquidateExistingHoldings, tag); + } + else + { + // Handle a single symbol or no symbols + if (symbols.Any()) + { + orderTickets = algo.SetHoldings(symbols.First(), 1, liquidateExistingHoldings, tag); + } + else + { + orderTickets = algo.SetHoldings(new List(), liquidateExistingHoldings, tag); + } + } + // Assert the expected number of order tickets + Assert.AreEqual(expectedOrderTickets, orderTickets.Count); + } + [Test] public void OrderQuantityConversionTest() { @@ -1821,5 +1867,14 @@ private bool HasSufficientBuyingPowerForOrder(decimal orderQuantity, Security se new object[] { Language.CSharp, null, false, null }, new object[] { Language.Python, null, false, null } }; + private static object[] SetHoldingReturnsOrderTicketsTestCases = + { + new object[] { new List(), true, 3, "(Empty, true)"}, + new object[] { new List(), false, 0, "(Empty, false)" }, + new object[] { new List() { Symbols.IBM }, true, 3, "(OneSymbol, true)" }, + new object[] { new List() { Symbols.IBM }, false, 1, "(OneSymbol, false)" }, + new object[] { new List() { Symbols.AAPL, Symbols.SPY }, true, 3, "(MultipleSymbols, true)" }, + new object[] { new List() { Symbols.AAPL, Symbols.SPY }, false, 2, "(MultipleSymbols, false)" }, + }; } }