From c0d7cd9400215ac6f8764fc7f7731af110aa4d5e Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 27 Feb 2026 17:47:18 -0400 Subject: [PATCH 1/5] Add securities when trading non added securities --- ...dingNotAddedEquitiesRegressionAlgorithm.cs | 137 ++++++++++++ ...adingNotAddedOptionsRegressionAlgorithm.cs | 210 ++++++++++++++++++ Algorithm/QCAlgorithm.Trading.cs | 85 +++++-- .../Future/FuturesExpiryUtilityFunctions.cs | 19 +- 4 files changed, 430 insertions(+), 21 deletions(-) create mode 100644 Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs create mode 100644 Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs diff --git a/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs b/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs new file mode 100644 index 000000000000..e86b42d35c55 --- /dev/null +++ b/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs @@ -0,0 +1,137 @@ +/* + * 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 System.Collections.Generic; +using QuantConnect.Data; +using QuantConnect.Interfaces; +using QuantConnect.Orders; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// + public class TradingNotAddedEquitiesRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition + { + private readonly Symbol _equitySymbol = QuantConnect.Symbol.Create("GOOG", SecurityType.Equity, Market.USA); + + public override void Initialize() + { + SetStartDate(2015, 12, 24); + SetEndDate(2015, 12, 24); + SetCash(1000000); + } + + protected void AssertSecurityIsAdded(Symbol symbol) + { + if (!Securities.TryGetValue(symbol, out var security) || ActiveSecurities.ContainsKey(symbol) || !security.IsTradable) + { + throw new RegressionTestException($"Contract {symbol} was not added as tradable security"); + } + } + + protected void AssertSecurityIsNotAdded(Symbol symbol) + { + if (Securities.TryGetValue(symbol, out var security) && ActiveSecurities.ContainsKey(symbol) && security.IsTradable) + { + throw new RegressionTestException($"Contract {symbol} was added as tradable security when it should not have been"); + } + } + + public override void OnData(Slice slice) + { + if (!Portfolio.Invested) + { + AssertSecurityIsNotAdded(_equitySymbol); + + var ticket = Buy(_equitySymbol, 1); + if (ticket.Status == OrderStatus.Invalid) + { + throw new RegressionTestException($"Order for {_equitySymbol} was rejected"); + } + + AssertSecurityIsAdded(_equitySymbol); + + // We are done + Quit(); + } + } + + public override void OnOrderEvent(OrderEvent orderEvent) + { + Log(orderEvent.ToString()); + } + + /// + /// 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 => 14642; + + /// + /// Data Points count of the algorithm history + /// + public virtual int AlgorithmHistoryDataPoints => 0; + + /// + /// Final status of the algorithm + /// + public virtual 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", "0"}, + {"Average Win", "0%"}, + {"Average Loss", "0%"}, + {"Compounding Annual Return", "0%"}, + {"Drawdown", "0%"}, + {"Expectancy", "0"}, + {"Start Equity", "1000000"}, + {"End Equity", "1000000"}, + {"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", "$0.00"}, + {"Estimated Strategy Capacity", "$0"}, + {"Lowest Capacity Asset", ""}, + {"Portfolio Turnover", "0%"}, + {"Drawdown Recovery", "0"}, + {"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"} + }; + } +} diff --git a/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs b/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs new file mode 100644 index 000000000000..178dab477dfb --- /dev/null +++ b/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs @@ -0,0 +1,210 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.Linq; +using QuantConnect.Data; +using QuantConnect.Interfaces; +using QuantConnect.Orders; +using QuantConnect.Securities.Option; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// + public class TradingNotAddedOptionsRegressionAlgorithm : TradingNotAddedEquitiesRegressionAlgorithm + { + private Symbol _optionSymbol; + private Symbol _deselectedContractSymbol; + private Symbol _notSelectedContractSymbol; + + public override void Initialize() + { + SetStartDate(2015, 12, 24); + SetEndDate(2015, 12, 28); + SetCash(1000000); + + var option = AddOption("GOOG"); + _optionSymbol = option.Symbol; + + // set our strike/expiry filter for this option chain + // SetFilter method accepts TimeSpan objects or integer for days. + // The following statements yield the same filtering criteria + option.SetFilter(u => u.StandardsOnly() + .Strikes(-2, +2) + .Expiration(7, 180) + .Contracts(contracts => + { + if (_deselectedContractSymbol == null) + { + var contractsList = contracts.ToList(); + _deselectedContractSymbol = contractsList.First(x => x.ID.OptionRight == OptionRight.Call && x.ID.StrikePrice == 750m && x.ID.Date.Date == new DateTime(2016, 06, 17)); + // This contract will never be selected so it's never added to the Securities collection + _notSelectedContractSymbol = contractsList.OrderByDescending(x => x.ID.Date).First(); + + return contractsList.Where(x => x != _notSelectedContractSymbol); + } + + // Filter out the contract we selected last time, we don't want it to be selected so it's marked as not tradable + return contracts.Where(x => x != _deselectedContractSymbol && x != _notSelectedContractSymbol); + })); + + // use the underlying equity as the benchmark + SetBenchmark("GOOG"); + } + + private void AssertTradeContract(Symbol symbol) + { + var ticket = Sell(symbol, 1); + if (ticket.Status == OrderStatus.Invalid) + { + throw new RegressionTestException($"Deselected contract {symbol} was not traded when it should have been"); + } + + AssertSecurityIsAdded(symbol); + + // Now let's remove it and try to trade it again, but in a strategy + RemoveSecurity(symbol); + AssertSecurityIsNotAdded(symbol); + + var strategy = OptionStrategies.Straddle(_optionSymbol, symbol.ID.StrikePrice, symbol.ID.Date); + if (strategy.OptionLegs.Count != 2 || + strategy.OptionLegs.Any(leg => leg.Symbol == null) || + !strategy.OptionLegs.Any(leg => leg.Symbol == symbol) || + !strategy.OptionLegs.Any(leg => leg.Symbol == symbol.GetMirrorOptionSymbol())) + { + throw new RegressionTestException("Option leg symbols were not set"); + } + + var tickets = Sell(strategy, 1); + if (tickets.Count == 0 || tickets.Any(x => x.Status == OrderStatus.Invalid)) + { + throw new RegressionTestException($"Deselected contract {symbol} was not traded as part of the strategy when it should have been"); + } + AssertSecurityIsAdded(symbol); + } + + public override void OnData(Slice slice) + { + if (Time.Day == 24) + { + if (_deselectedContractSymbol == null || _notSelectedContractSymbol == null) + { + throw new RegressionTestException("Trading contracts were not set"); + } + + if (!Securities.TryGetValue(_deselectedContractSymbol, out var deselectedContract)) + { + throw new RegressionTestException($"Deselected contract {_deselectedContractSymbol} is tradable"); + } + } + else if (Time.Day == 28 && !Portfolio.Invested) + { + if (slice.OptionChains.TryGetValue(_optionSymbol, out var chain)) + { + if (_deselectedContractSymbol == null || _notSelectedContractSymbol == null) + { + throw new RegressionTestException("Trading contracts were not set"); + } + + AssertSecurityIsNotAdded(_deselectedContractSymbol); + AssertSecurityIsNotAdded(_notSelectedContractSymbol); + + // Now we have _deselectedContractSymbol which was selected in a previous date but deselected for today. + // Let's trade it + AssertTradeContract(_deselectedContractSymbol); + + // Let's do the same with _notSelectedContractSymbol which was never selected + AssertTradeContract(_notSelectedContractSymbol); + + // We are done testing + Quit(); + } + } + + foreach (var kpv in slice.Bars) + { + Log($"---> OnData: {Time}, {kpv.Key.Value}, {kpv.Value.Close:0.00}"); + } + } + + public override void OnOrderEvent(OrderEvent orderEvent) + { + Log(orderEvent.ToString()); + } + + /// + /// 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 override bool CanRunLocally { get; } = true; + + /// + /// This is used by the regression test system to indicate which languages this algorithm is written in. + /// + public override List Languages { get; } = new() { Language.CSharp }; + + /// + /// Data Points count of all timeslices of algorithm + /// + public override long DataPoints => 14642; + + /// + /// Data Points count of the algorithm history + /// + public override int AlgorithmHistoryDataPoints => 0; + + /// + /// Final status of the algorithm + /// + public override AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed; + + /// + /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm + /// + public override Dictionary ExpectedStatistics => new Dictionary + { + {"Total Orders", "0"}, + {"Average Win", "0%"}, + {"Average Loss", "0%"}, + {"Compounding Annual Return", "0%"}, + {"Drawdown", "0%"}, + {"Expectancy", "0"}, + {"Start Equity", "1000000"}, + {"End Equity", "1000000"}, + {"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", "$0.00"}, + {"Estimated Strategy Capacity", "$0"}, + {"Lowest Capacity Asset", ""}, + {"Portfolio Turnover", "0%"}, + {"Drawdown Recovery", "0"}, + {"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"} + }; + } +} diff --git a/Algorithm/QCAlgorithm.Trading.cs b/Algorithm/QCAlgorithm.Trading.cs index f61496471dbf..df813c1f87cc 100644 --- a/Algorithm/QCAlgorithm.Trading.cs +++ b/Algorithm/QCAlgorithm.Trading.cs @@ -13,16 +13,17 @@ * limitations under the License. */ -using System; -using System.Linq; -using QuantConnect.Orders; +using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Interfaces; +using QuantConnect.Orders; +using QuantConnect.Orders.TimeInForces; using QuantConnect.Securities; -using System.Collections.Generic; +using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; +using System; +using System.Collections.Generic; +using System.Linq; using static QuantConnect.StringExtensions; -using QuantConnect.Algorithm.Framework.Portfolio; -using QuantConnect.Orders.TimeInForces; namespace QuantConnect.Algorithm { @@ -237,7 +238,8 @@ public OrderTicket MarketOrder(Symbol symbol, double quantity, bool asynchronous [DocumentationAttribute(TradingAndOrders)] public OrderTicket MarketOrder(Symbol symbol, decimal quantity, bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null) { - var security = Securities[symbol]; + var security = GetSecurityForOrder(symbol); + // check the exchange is open before sending a market order, if it's not open then convert it into a market on open order. // For futures and FOPs, market orders can be submitted on extended hours, so we let them through. if ((security.Type != SecurityType.Future && security.Type != SecurityType.FutureOption) && !security.Exchange.ExchangeOpen) @@ -314,7 +316,7 @@ public OrderTicket MarketOnOpenOrder(Symbol symbol, decimal quantity, bool async var properties = orderProperties ?? DefaultOrderProperties?.Clone(); InvalidateGoodTilDateTimeInForce(properties); - var security = Securities[symbol]; + var security = GetSecurityForOrder(symbol); var request = CreateSubmitOrderRequest(OrderType.MarketOnOpen, security, quantity, tag, properties, asynchronous); return SubmitOrderRequest(request); @@ -365,7 +367,7 @@ public OrderTicket MarketOnCloseOrder(Symbol symbol, decimal quantity, bool asyn var properties = orderProperties ?? DefaultOrderProperties?.Clone(); InvalidateGoodTilDateTimeInForce(properties); - var security = Securities[symbol]; + var security = GetSecurityForOrder(symbol); var request = CreateSubmitOrderRequest(OrderType.MarketOnClose, security, quantity, tag, properties, asynchronous); return SubmitOrderRequest(request); @@ -416,7 +418,7 @@ public OrderTicket LimitOrder(Symbol symbol, double quantity, decimal limitPrice [DocumentationAttribute(TradingAndOrders)] public OrderTicket LimitOrder(Symbol symbol, decimal quantity, decimal limitPrice, bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null) { - var security = Securities[symbol]; + var security = GetSecurityForOrder(symbol); var request = CreateSubmitOrderRequest(OrderType.Limit, security, quantity, tag, orderProperties ?? DefaultOrderProperties?.Clone(), asynchronous, limitPrice: limitPrice); @@ -468,7 +470,7 @@ public OrderTicket StopMarketOrder(Symbol symbol, double quantity, decimal stopP [DocumentationAttribute(TradingAndOrders)] public OrderTicket StopMarketOrder(Symbol symbol, decimal quantity, decimal stopPrice, bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null) { - var security = Securities[symbol]; + var security = GetSecurityForOrder(symbol); var request = CreateSubmitOrderRequest(OrderType.StopMarket, security, quantity, tag, orderProperties ?? DefaultOrderProperties?.Clone(), asynchronous, stopPrice: stopPrice); @@ -529,7 +531,7 @@ public OrderTicket TrailingStopOrder(Symbol symbol, double quantity, decimal tra public OrderTicket TrailingStopOrder(Symbol symbol, decimal quantity, decimal trailingAmount, bool trailingAsPercentage, bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null) { - var security = Securities[symbol]; + var security = GetSecurityForOrder(symbol); var stopPrice = Orders.TrailingStopOrder.CalculateStopPrice(security.Price, trailingAmount, trailingAsPercentage, quantity > 0 ? OrderDirection.Buy : OrderDirection.Sell); return TrailingStopOrder(symbol, quantity, stopPrice, trailingAmount, trailingAsPercentage, asynchronous, tag, orderProperties); @@ -589,7 +591,7 @@ public OrderTicket TrailingStopOrder(Symbol symbol, double quantity, decimal sto public OrderTicket TrailingStopOrder(Symbol symbol, decimal quantity, decimal stopPrice, decimal trailingAmount, bool trailingAsPercentage, bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null) { - var security = Securities[symbol]; + var security = GetSecurityForOrder(symbol); var request = CreateSubmitOrderRequest( OrderType.TrailingStop, security, @@ -655,7 +657,7 @@ public OrderTicket StopLimitOrder(Symbol symbol, double quantity, decimal stopPr public OrderTicket StopLimitOrder(Symbol symbol, decimal quantity, decimal stopPrice, decimal limitPrice, bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null) { - var security = Securities[symbol]; + var security = GetSecurityForOrder(symbol); var request = CreateSubmitOrderRequest(OrderType.StopLimit, security, quantity, tag, stopPrice: stopPrice, limitPrice: limitPrice, properties: orderProperties ?? DefaultOrderProperties?.Clone(), asynchronous: asynchronous); @@ -713,7 +715,7 @@ public OrderTicket LimitIfTouchedOrder(Symbol symbol, double quantity, decimal t public OrderTicket LimitIfTouchedOrder(Symbol symbol, decimal quantity, decimal triggerPrice, decimal limitPrice, bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null) { - var security = Securities[symbol]; + var security = GetSecurityForOrder(symbol); var request = CreateSubmitOrderRequest(OrderType.LimitIfTouched, security, quantity, tag, triggerPrice: triggerPrice, limitPrice: limitPrice, properties: orderProperties ?? DefaultOrderProperties?.Clone(), asynchronous: asynchronous); @@ -733,7 +735,7 @@ public OrderTicket LimitIfTouchedOrder(Symbol symbol, decimal quantity, decimal [DocumentationAttribute(TradingAndOrders)] public OrderTicket ExerciseOption(Symbol optionSymbol, int quantity, bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null) { - var option = (Option)Securities[optionSymbol]; + var option = (Option)GetSecurityForOrder(optionSymbol); // SubmitOrderRequest.Quantity indicates the change in holdings quantity, therefore manual exercise quantities must be negative // PreOrderChecksImpl confirms that we don't hold a short position, so we're lenient here and accept +/- quantity values @@ -902,10 +904,7 @@ private List SubmitComboOrder(List legs, decimal quantity, dec List submitRequests = new(capacity: legs.Count); foreach (var leg in legs) { - if (!Securities.TryGetValue(leg.Symbol, out var security)) - { - throw new InvalidOperationException(Invariant($"Couldn't find one of the strategy's securities in the algorithm securities list. {leg}")); - } + var security = GetSecurityForOrder(leg.Symbol); if (leg.OrderPrice.HasValue) { @@ -1205,6 +1204,52 @@ private OrderResponse PreOrderChecksImpl(SubmitOrderRequest request) return OrderResponse.Success(request); } + /// + /// Gets the security for the given symbol. + /// This method is intended to get a security that is going to be traded, so it will try to + /// add the security if it's not found in the algorithm's securities collection and it meets + /// the requirements to be added (e.g. not delisted, not expired, etc). + /// The added security will be seeded with data so that it can be traded immediately. + /// + private Security GetSecurityForOrder(Symbol symbol) + { + if (Securities.TryGetValue(symbol, out var security) && ActiveSecurities.ContainsKey(symbol)) + { + return security; + } + + if (security == null || !security.IsDelisted) + { + // Try to add and seed the security + if (!symbol.HasUnderlying || + (symbol.SecurityType.IsOption() && !OptionSymbol.IsOptionContractExpired(symbol, UtcTime)) || + (symbol.SecurityType == SecurityType.Future && !symbol.IsCanonical() && !FuturesExpiryUtilityFunctions.IsFutureContractExpired(symbol, UtcTime, MarketHoursDatabase))) + { + // Send one time warning + security = AddSecurity(symbol); + + // Just in case + if (security.IsTradable) + { + if (!Settings.SeedInitialPrices) + { + AlgorithmUtils.SeedSecurities([security], this); + } + + return security; + } + else + { + // will not be used, let's revert it + RemoveSecurity(symbol); + } + } + } + + throw new InvalidOperationException($"The symbol {symbol} is not found in the algorithm's securities collection " + + "and cannot be re-added due to it being delisted or no longer tradable."); + } + /// /// Liquidate your portfolio holdings /// diff --git a/Common/Securities/Future/FuturesExpiryUtilityFunctions.cs b/Common/Securities/Future/FuturesExpiryUtilityFunctions.cs index 5d47f6608ecf..52f568b93389 100644 --- a/Common/Securities/Future/FuturesExpiryUtilityFunctions.cs +++ b/Common/Securities/Future/FuturesExpiryUtilityFunctions.cs @@ -25,6 +25,8 @@ namespace QuantConnect.Securities.Future /// public static class FuturesExpiryUtilityFunctions { + private static readonly MarketHoursDatabase MarketHoursDatabase = MarketHoursDatabase.FromDataFolder(); + /// /// Get holiday list from the MHDB given the market and the symbol of the security /// @@ -115,7 +117,7 @@ public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable x.Date); - if(n > daysInMonth) + if (n > daysInMonth) { throw new ArgumentOutOfRangeException(nameof(n), Invariant( $"Number of days ({n}) is larger than the size of month({daysInMonth})" @@ -382,6 +384,21 @@ public static DateTime ThirdFriday(DateTime time, Symbol contract) return AddBusinessDaysIfHoliday(thirdFriday, -1, holidays); } + /// + /// Checks if the future contract is expired. + /// + public static bool IsFutureContractExpired(Symbol symbol, DateTime currentUtcTime, MarketHoursDatabase marketHoursDatabase = null) + { + var exchangeHours = (marketHoursDatabase ?? MarketHoursDatabase).GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType); + var currentTimeInExchangeTz = currentUtcTime.ConvertFromUtc(exchangeHours.TimeZone); + if (currentTimeInExchangeTz >= symbol.ID.Date) + { + return true; + } + + return false; + } + private static readonly Dictionary ExpiriesPriorMonth = new Dictionary { { Futures.Energy.ArgusLLSvsWTIArgusTradeMonth, 1 }, From b2a3a8316ef67c7eb8fc9537ae5ad14c32cee4ea Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 2 Mar 2026 10:02:12 -0400 Subject: [PATCH 2/5] Minor tests fixes --- ...olImplicitConversionRegressionAlgorithm.cs | 18 +++++----- ...dingNotAddedEquitiesRegressionAlgorithm.cs | 2 +- ...adingNotAddedOptionsRegressionAlgorithm.cs | 36 +++++++++---------- ...olImplicitConversionRegressionAlgorithm.py | 12 ++++--- Algorithm/QCAlgorithm.Trading.cs | 4 ++- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/Algorithm.CSharp/StringToSymbolImplicitConversionRegressionAlgorithm.cs b/Algorithm.CSharp/StringToSymbolImplicitConversionRegressionAlgorithm.cs index 1a946d57aa87..f00a69870e43 100644 --- a/Algorithm.CSharp/StringToSymbolImplicitConversionRegressionAlgorithm.cs +++ b/Algorithm.CSharp/StringToSymbolImplicitConversionRegressionAlgorithm.cs @@ -13,10 +13,10 @@ * limitations under the License. */ -using System; using System.Collections.Generic; using QuantConnect.Data; using QuantConnect.Interfaces; +using QuantConnect.Orders; namespace QuantConnect.Algorithm.CSharp { @@ -43,16 +43,16 @@ public override void Initialize() /// Slice object keyed by symbol containing the stock data public override void OnData(Slice slice) { - try + var ticket = MarketOrder("PEPE", 1); + + if (ticket.Status != OrderStatus.Invalid) { - MarketOrder("PEPE", 1); + throw new RegressionTestException($"Expected order to be invalid since PEPE is not a valid ticker, but was {ticket.Status}"); } - catch (Exception exception) + + if (!Portfolio.Invested) { - if (exception.Message.Contains("PEPE was not found", StringComparison.InvariantCultureIgnoreCase) && !Portfolio.Invested) - { - SetHoldings("SPY", 1); - } + SetHoldings("SPY", 1); } } @@ -69,7 +69,7 @@ public override void OnData(Slice slice) /// /// Data Points count of all timeslices of algorithm /// - public long DataPoints => 1582; + public long DataPoints => 1583; /// /// Data Points count of the algorithm history diff --git a/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs b/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs index e86b42d35c55..4843029535db 100644 --- a/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs +++ b/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs @@ -87,7 +87,7 @@ public override void OnOrderEvent(OrderEvent orderEvent) /// /// Data Points count of all timeslices of algorithm /// - public virtual long DataPoints => 14642; + public virtual long DataPoints => 11; /// /// Data Points count of the algorithm history diff --git a/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs b/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs index 178dab477dfb..3306391e1c35 100644 --- a/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs +++ b/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs @@ -165,7 +165,7 @@ public override void OnOrderEvent(OrderEvent orderEvent) /// /// Data Points count of the algorithm history /// - public override int AlgorithmHistoryDataPoints => 0; + public override int AlgorithmHistoryDataPoints => 22; /// /// Final status of the algorithm @@ -177,34 +177,34 @@ public override void OnOrderEvent(OrderEvent orderEvent) /// public override Dictionary ExpectedStatistics => new Dictionary { - {"Total Orders", "0"}, + {"Total Orders", "8"}, {"Average Win", "0%"}, - {"Average Loss", "0%"}, - {"Compounding Annual Return", "0%"}, - {"Drawdown", "0%"}, - {"Expectancy", "0"}, + {"Average Loss", "-0.02%"}, + {"Compounding Annual Return", "-5.436%"}, + {"Drawdown", "0.100%"}, + {"Expectancy", "-1"}, {"Start Equity", "1000000"}, - {"End Equity", "1000000"}, - {"Net Profit", "0%"}, - {"Sharpe Ratio", "0"}, + {"End Equity", "999327"}, + {"Net Profit", "-0.067%"}, + {"Sharpe Ratio", "-9.644"}, {"Sortino Ratio", "0"}, - {"Probabilistic Sharpe Ratio", "0%"}, - {"Loss Rate", "0%"}, + {"Probabilistic Sharpe Ratio", "1.216%"}, + {"Loss Rate", "100%"}, {"Win Rate", "0%"}, {"Profit-Loss Ratio", "0"}, {"Alpha", "0"}, {"Beta", "0"}, - {"Annual Standard Deviation", "0"}, + {"Annual Standard Deviation", "0.005"}, {"Annual Variance", "0"}, - {"Information Ratio", "0"}, - {"Tracking Error", "0"}, + {"Information Ratio", "-7.772"}, + {"Tracking Error", "0.005"}, {"Treynor Ratio", "0"}, - {"Total Fees", "$0.00"}, + {"Total Fees", "$8.00"}, {"Estimated Strategy Capacity", "$0"}, - {"Lowest Capacity Asset", ""}, - {"Portfolio Turnover", "0%"}, + {"Lowest Capacity Asset", "GOOCV WBGM92QHN8ZQ|GOOCV VP83T1ZUHROL"}, + {"Portfolio Turnover", "0.86%"}, {"Drawdown Recovery", "0"}, - {"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"} + {"OrderListHash", "fc224d8177907f98e3381d2d58067b48"} }; } } diff --git a/Algorithm.Python/StringToSymbolImplicitConversionRegressionAlgorithm.py b/Algorithm.Python/StringToSymbolImplicitConversionRegressionAlgorithm.py index fe23b230a046..307cb7c57a8a 100644 --- a/Algorithm.Python/StringToSymbolImplicitConversionRegressionAlgorithm.py +++ b/Algorithm.Python/StringToSymbolImplicitConversionRegressionAlgorithm.py @@ -31,8 +31,10 @@ def on_data(self, data): Arguments: data: Slice object keyed by symbol containing the stock data ''' - try: - self.market_order("PEPE", 1) - except Exception as exception: - if "PEPE was not found" in str(exception) and not self.portfolio.invested: - self.set_holdings("SPY", 1) + ticket = self.market_order("PEPE", 1) + + if ticket.status != OrderStatus.Invalid: + raise Exception(f"Expected order to be invalid since PEPE is not a valid ticker, but was {ticket.status}") + + if not self.portfolio.invested: + self.set_holdings("SPY", 1) diff --git a/Algorithm/QCAlgorithm.Trading.cs b/Algorithm/QCAlgorithm.Trading.cs index df813c1f87cc..c9940e48e2e3 100644 --- a/Algorithm/QCAlgorithm.Trading.cs +++ b/Algorithm/QCAlgorithm.Trading.cs @@ -1213,7 +1213,9 @@ private OrderResponse PreOrderChecksImpl(SubmitOrderRequest request) /// private Security GetSecurityForOrder(Symbol symbol) { - if (Securities.TryGetValue(symbol, out var security) && ActiveSecurities.ContainsKey(symbol)) + if (Securities.TryGetValue(symbol, out var security) && + // Let delisted securities through instead of throwing. An invalid ticket will be returned later on when trying to submit the order. + (security.IsTradable || security.IsDelisted)) { return security; } From bdb405eeea8d1d4dbd5a7b4f4d5568b22ff8614a Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 2 Mar 2026 12:31:29 -0400 Subject: [PATCH 3/5] Minor test fix --- Algorithm.CSharp/ResolutionSwitchingAlgorithm.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Algorithm.CSharp/ResolutionSwitchingAlgorithm.cs b/Algorithm.CSharp/ResolutionSwitchingAlgorithm.cs index 7370ac840662..b2c12722c11f 100644 --- a/Algorithm.CSharp/ResolutionSwitchingAlgorithm.cs +++ b/Algorithm.CSharp/ResolutionSwitchingAlgorithm.cs @@ -59,7 +59,9 @@ public override void Initialize() /// Slice object keyed by symbol containing the stock data public override void OnData(Slice slice) { - if (!Portfolio.Invested) + if (!Portfolio.Invested && + // Wait for the security to be re-added in the OnSecuritiesChanged event before trying to trade it + Securities.TryGetValue(_spy, out var security) && security.IsTradable) { MarketOrder(_spy, 651); // QTY 651 is equal to `SetHoldings(_spy, 1)` Debug("Purchased Stock"); From 3dab73b11f6bdad5c904f1a84abdc9b065b9326d Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 2 Mar 2026 13:55:34 -0400 Subject: [PATCH 4/5] Minor test fixes --- .../AddTwoAndRemoveOneOptionContractRegressionAlgorithm.cs | 5 ++++- Algorithm.CSharp/OpenInterestFuturesRegressionAlgorithm.cs | 4 ++-- Algorithm.Python/OpenInterestFuturesRegressionAlgorithm.py | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Algorithm.CSharp/AddTwoAndRemoveOneOptionContractRegressionAlgorithm.cs b/Algorithm.CSharp/AddTwoAndRemoveOneOptionContractRegressionAlgorithm.cs index 7d1807b35b3c..b1001e555cb8 100644 --- a/Algorithm.CSharp/AddTwoAndRemoveOneOptionContractRegressionAlgorithm.cs +++ b/Algorithm.CSharp/AddTwoAndRemoveOneOptionContractRegressionAlgorithm.cs @@ -72,7 +72,10 @@ public override void OnData(Slice slice) throw new RegressionTestException("No configuration for underlying was found!"); } - if (!Portfolio.Invested) + if (!Portfolio.Invested && + // This security will be liquidated due to impending split, let's not trade it again after the contract is removed. + // Trying to trade it will make the security to be re-added + Securities[_contract2].IsTradable) { Buy(_contract2, 1); } diff --git a/Algorithm.CSharp/OpenInterestFuturesRegressionAlgorithm.cs b/Algorithm.CSharp/OpenInterestFuturesRegressionAlgorithm.cs index 263ccb37a919..4dcc800c92eb 100644 --- a/Algorithm.CSharp/OpenInterestFuturesRegressionAlgorithm.cs +++ b/Algorithm.CSharp/OpenInterestFuturesRegressionAlgorithm.cs @@ -67,9 +67,9 @@ public override void OnData(Slice slice) throw new RegressionTestException($"{matched.Count}/{slice.Keys.Count} were unexpected expiry date(s): " + string.Join(", ", matched.Select(x => x.ID.Date))); } - foreach (var symbol in slice.Keys) + foreach (var symbol in slice.Keys.Where(x => !x.IsCanonical())) { - MarketOrder(symbol, 1); + var ticket = MarketOrder(symbol, 1); } } else if (Portfolio.Any(p => p.Value.Invested)) diff --git a/Algorithm.Python/OpenInterestFuturesRegressionAlgorithm.py b/Algorithm.Python/OpenInterestFuturesRegressionAlgorithm.py index 7169a238d330..5b5ffeef10c5 100644 --- a/Algorithm.Python/OpenInterestFuturesRegressionAlgorithm.py +++ b/Algorithm.Python/OpenInterestFuturesRegressionAlgorithm.py @@ -41,6 +41,8 @@ def on_data(self,data): raise AssertionError(f"{len(matched)}/{len(data.keys())} were unexpected expiry date(s): " + ", ".join(list(map(lambda x: x.id.date, matched)))) for symbol in data.keys(): + if symbol.is_canonical(): + continue self.market_order(symbol, 1) elif any(p.value.invested for p in self.portfolio): self.liquidate() From 91170c029a97e7cf8a619d0e5f9c7e37b09bd1cc Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 2 Mar 2026 17:13:18 -0400 Subject: [PATCH 5/5] Minor fixes --- .../OpenInterestFuturesRegressionAlgorithm.cs | 4 +- ...dingNotAddedEquitiesRegressionAlgorithm.cs | 2 + ...adingNotAddedOptionsRegressionAlgorithm.cs | 8 +--- .../OpenInterestFuturesRegressionAlgorithm.py | 2 - Algorithm/QCAlgorithm.Trading.cs | 37 ++++++++----------- 5 files changed, 22 insertions(+), 31 deletions(-) diff --git a/Algorithm.CSharp/OpenInterestFuturesRegressionAlgorithm.cs b/Algorithm.CSharp/OpenInterestFuturesRegressionAlgorithm.cs index 4dcc800c92eb..263ccb37a919 100644 --- a/Algorithm.CSharp/OpenInterestFuturesRegressionAlgorithm.cs +++ b/Algorithm.CSharp/OpenInterestFuturesRegressionAlgorithm.cs @@ -67,9 +67,9 @@ public override void OnData(Slice slice) throw new RegressionTestException($"{matched.Count}/{slice.Keys.Count} were unexpected expiry date(s): " + string.Join(", ", matched.Select(x => x.ID.Date))); } - foreach (var symbol in slice.Keys.Where(x => !x.IsCanonical())) + foreach (var symbol in slice.Keys) { - var ticket = MarketOrder(symbol, 1); + MarketOrder(symbol, 1); } } else if (Portfolio.Any(p => p.Value.Invested)) diff --git a/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs b/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs index 4843029535db..2791da71de2c 100644 --- a/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs +++ b/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs @@ -22,6 +22,8 @@ namespace QuantConnect.Algorithm.CSharp { /// + /// Regression algorithm asserting that equities can be traded even if they are not added to the algorithm. + /// They will be automatically added as tradable securities an seeded when an order is placed for them. /// public class TradingNotAddedEquitiesRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition { diff --git a/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs b/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs index 3306391e1c35..db93057f0f4f 100644 --- a/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs +++ b/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs @@ -18,13 +18,14 @@ using System.Collections.Generic; using System.Linq; using QuantConnect.Data; -using QuantConnect.Interfaces; using QuantConnect.Orders; using QuantConnect.Securities.Option; namespace QuantConnect.Algorithm.CSharp { /// + /// Regression algorithm asserting that options can be traded even if they are not added to the algorithm. + /// They will be automatically added as tradable securities an seeded when an order is placed for them. /// public class TradingNotAddedOptionsRegressionAlgorithm : TradingNotAddedEquitiesRegressionAlgorithm { @@ -135,11 +136,6 @@ public override void OnData(Slice slice) Quit(); } } - - foreach (var kpv in slice.Bars) - { - Log($"---> OnData: {Time}, {kpv.Key.Value}, {kpv.Value.Close:0.00}"); - } } public override void OnOrderEvent(OrderEvent orderEvent) diff --git a/Algorithm.Python/OpenInterestFuturesRegressionAlgorithm.py b/Algorithm.Python/OpenInterestFuturesRegressionAlgorithm.py index 5b5ffeef10c5..7169a238d330 100644 --- a/Algorithm.Python/OpenInterestFuturesRegressionAlgorithm.py +++ b/Algorithm.Python/OpenInterestFuturesRegressionAlgorithm.py @@ -41,8 +41,6 @@ def on_data(self,data): raise AssertionError(f"{len(matched)}/{len(data.keys())} were unexpected expiry date(s): " + ", ".join(list(map(lambda x: x.id.date, matched)))) for symbol in data.keys(): - if symbol.is_canonical(): - continue self.market_order(symbol, 1) elif any(p.value.invested for p in self.portfolio): self.liquidate() diff --git a/Algorithm/QCAlgorithm.Trading.cs b/Algorithm/QCAlgorithm.Trading.cs index c9940e48e2e3..2956504f09b9 100644 --- a/Algorithm/QCAlgorithm.Trading.cs +++ b/Algorithm/QCAlgorithm.Trading.cs @@ -1213,38 +1213,33 @@ private OrderResponse PreOrderChecksImpl(SubmitOrderRequest request) /// private Security GetSecurityForOrder(Symbol symbol) { - if (Securities.TryGetValue(symbol, out var security) && - // Let delisted securities through instead of throwing. An invalid ticket will be returned later on when trying to submit the order. - (security.IsTradable || security.IsDelisted)) + var isCanonical = symbol.IsCanonical(); + 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)) { return security; } - if (security == null || !security.IsDelisted) + if (security == null || !security.IsTradable) { - // Try to add and seed the security - if (!symbol.HasUnderlying || - (symbol.SecurityType.IsOption() && !OptionSymbol.IsOptionContractExpired(symbol, UtcTime)) || - (symbol.SecurityType == SecurityType.Future && !symbol.IsCanonical() && !FuturesExpiryUtilityFunctions.IsFutureContractExpired(symbol, UtcTime, MarketHoursDatabase))) + // Try to add and seed the security, but don't is it's a canonical symbol + if (!isCanonical && + // Indexes are not tradable by default + symbol.SecurityType != SecurityType.Index && + (!symbol.HasUnderlying || + (symbol.SecurityType.IsOption() && !OptionSymbol.IsOptionContractExpired(symbol, UtcTime)) || + (symbol.SecurityType == SecurityType.Future && !FuturesExpiryUtilityFunctions.IsFutureContractExpired(symbol, UtcTime, MarketHoursDatabase)))) { // Send one time warning security = AddSecurity(symbol); - // Just in case - if (security.IsTradable) + if (!Settings.SeedInitialPrices) { - if (!Settings.SeedInitialPrices) - { - AlgorithmUtils.SeedSecurities([security], this); - } - - return security; - } - else - { - // will not be used, let's revert it - RemoveSecurity(symbol); + AlgorithmUtils.SeedSecurities([security], this); } + + return security; } }