diff --git a/Algorithm/QCAlgorithm.Trading.cs b/Algorithm/QCAlgorithm.Trading.cs index 6bd29151e320..ddba02f0249c 100644 --- a/Algorithm/QCAlgorithm.Trading.cs +++ b/Algorithm/QCAlgorithm.Trading.cs @@ -35,6 +35,7 @@ public partial class QCAlgorithm private bool _isMarketOnOpenOrderRestrictedForFuturesWarningSent; private bool _isGtdTfiForMooAndMocOrdersValidationWarningSent; private bool _isOptionsOrderOnStockSplitWarningSent; + private bool _liquidateSymbolNotFoundWarningSent; /// /// Transaction Manager - Process transaction fills and order management. @@ -1268,7 +1269,7 @@ private OrderResponse PreOrderChecksImpl(SubmitOrderRequest request) private Security GetSecurityForOrder(Symbol symbol) { var isCanonical = symbol.IsCanonical(); - if (Securities.TryGetValue(symbol, out var security) && + if (Securities.TryGetValue(symbol, out var security) && // Let canonical and delisted securities through instead of throwing. An invalid ticket will be returned later on when trying to submit the order. (isCanonical || security.IsTradable || security.IsDelisted)) { @@ -1326,8 +1327,7 @@ public List Liquidate(Symbol symbol = null, bool asynchronous = fal IEnumerable toLiquidate; if (symbol != null) { - toLiquidate = Securities.ContainsKey(symbol) - ? new[] { symbol } : Enumerable.Empty(); + toLiquidate = new[] { symbol }; } else { @@ -1357,6 +1357,17 @@ public List Liquidate(IEnumerable symbols, bool asynchronou tag ??= "Liquidated"; foreach (var symbolToLiquidate in symbols) { + // skip symbols that have not been added to the algorithm instead of throwing + if (!Securities.ContainsKey(symbolToLiquidate)) + { + if (!_liquidateSymbolNotFoundWarningSent) + { + _liquidateSymbolNotFoundWarningSent = true; + Debug($"Warning: liquidate ignored symbol '{symbolToLiquidate}' because it has not been added to the algorithm. Add the security before liquidating it."); + } + continue; + } + // get open orders var orders = Transactions.GetOpenOrders(symbolToLiquidate); diff --git a/Tests/Algorithm/AlgorithmTradingTests.cs b/Tests/Algorithm/AlgorithmTradingTests.cs index 02e070a6ef73..831a9702e59a 100644 --- a/Tests/Algorithm/AlgorithmTradingTests.cs +++ b/Tests/Algorithm/AlgorithmTradingTests.cs @@ -1556,6 +1556,41 @@ public void LiquidateWorksAsExpected(Language language, bool? multipleSymbols, b Assert.IsTrue(limitOrderCanceled); } + [TestCase(Language.CSharp, true)] + [TestCase(Language.CSharp, false)] + [TestCase(Language.Python, true)] + [TestCase(Language.Python, false)] + public void LiquidateIgnoresSymbolsNotAddedToTheAlgorithm(Language language, bool singleSymbol) + { + var algo = GetAlgorithm(out _, 1, 0); + + // AAPL was never added to the algorithm + List liquidatedTickets = null; + if (language == Language.CSharp) + { + if (singleSymbol) + { + Assert.DoesNotThrow(() => liquidatedTickets = algo.Liquidate(Symbols.AAPL)); + } + else + { + Assert.DoesNotThrow(() => liquidatedTickets = algo.Liquidate(new List() { Symbols.AAPL })); + } + } + else + { + using (Py.GIL()) + { + var symbols = singleSymbol + ? Symbols.AAPL.ToPython() + : (new List() { Symbols.AAPL }).ToPython(); + Assert.DoesNotThrow(() => liquidatedTickets = algo.Liquidate(symbols)); + } + } + + Assert.IsEmpty(liquidatedTickets); + } + [Test] public void MarketOrdersAreSupportedForFuturesOnExtendedMarketHours() {