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/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");
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
new file mode 100644
index 000000000000..2791da71de2c
--- /dev/null
+++ b/Algorithm.CSharp/TradingNotAddedEquitiesRegressionAlgorithm.cs
@@ -0,0 +1,139 @@
+/*
+ * 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
+{
+ ///
+ /// 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
+ {
+ 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 => 11;
+
+ ///
+ /// 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..db93057f0f4f
--- /dev/null
+++ b/Algorithm.CSharp/TradingNotAddedOptionsRegressionAlgorithm.cs
@@ -0,0 +1,206 @@
+/*
+ * 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.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
+ {
+ 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();
+ }
+ }
+ }
+
+ 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 => 22;
+
+ ///
+ /// 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", "8"},
+ {"Average Win", "0%"},
+ {"Average Loss", "-0.02%"},
+ {"Compounding Annual Return", "-5.436%"},
+ {"Drawdown", "0.100%"},
+ {"Expectancy", "-1"},
+ {"Start Equity", "1000000"},
+ {"End Equity", "999327"},
+ {"Net Profit", "-0.067%"},
+ {"Sharpe Ratio", "-9.644"},
+ {"Sortino Ratio", "0"},
+ {"Probabilistic Sharpe Ratio", "1.216%"},
+ {"Loss Rate", "100%"},
+ {"Win Rate", "0%"},
+ {"Profit-Loss Ratio", "0"},
+ {"Alpha", "0"},
+ {"Beta", "0"},
+ {"Annual Standard Deviation", "0.005"},
+ {"Annual Variance", "0"},
+ {"Information Ratio", "-7.772"},
+ {"Tracking Error", "0.005"},
+ {"Treynor Ratio", "0"},
+ {"Total Fees", "$8.00"},
+ {"Estimated Strategy Capacity", "$0"},
+ {"Lowest Capacity Asset", "GOOCV WBGM92QHN8ZQ|GOOCV VP83T1ZUHROL"},
+ {"Portfolio Turnover", "0.86%"},
+ {"Drawdown Recovery", "0"},
+ {"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 f61496471dbf..2956504f09b9 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,49 @@ 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)
+ {
+ 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.IsTradable)
+ {
+ // 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);
+
+ if (!Settings.SeedInitialPrices)
+ {
+ AlgorithmUtils.SeedSecurities([security], this);
+ }
+
+ return security;
+ }
+ }
+
+ 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 },