diff --git a/Algorithm.CSharp/AddOptionContractExpiresRegressionAlgorithm.cs b/Algorithm.CSharp/AddOptionContractExpiresRegressionAlgorithm.cs
new file mode 100644
index 000000000000..9f1a3a946c5b
--- /dev/null
+++ b/Algorithm.CSharp/AddOptionContractExpiresRegressionAlgorithm.cs
@@ -0,0 +1,164 @@
+/*
+ * 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.Linq;
+using QuantConnect.Data;
+using QuantConnect.Interfaces;
+using System.Collections.Generic;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// We add an option contract using and place a trade and wait till it expires
+ /// later will liquidate the resulting equity position and assert both option and underlying get removed
+ ///
+ public class AddOptionContractExpiresRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
+ {
+ private DateTime _expiration = new DateTime(2014, 06, 21);
+ private Symbol _option;
+ private Symbol _twx;
+ private bool _traded;
+
+ public override void Initialize()
+ {
+ SetStartDate(2014, 06, 05);
+ SetEndDate(2014, 06, 30);
+
+ _twx = QuantConnect.Symbol.Create("TWX", SecurityType.Equity, Market.USA);
+
+ AddUniverse("my-daily-universe-name", time => new List { "AAPL" });
+ }
+
+ public override void OnData(Slice data)
+ {
+ if (_option == null)
+ {
+ var option = OptionChainProvider.GetOptionContractList(_twx, Time)
+ .OrderBy(symbol => symbol.ID.Symbol)
+ .FirstOrDefault(optionContract => optionContract.ID.Date == _expiration
+ && optionContract.ID.OptionRight == OptionRight.Call
+ && optionContract.ID.OptionStyle == OptionStyle.American);
+ if (option != null)
+ {
+ _option = AddOptionContract(option).Symbol;
+ }
+ }
+
+ if (_option != null && Securities[_option].Price != 0 && !_traded)
+ {
+ _traded = true;
+ Buy(_option, 1);
+
+ foreach (var symbol in new [] { _option, _option.Underlying })
+ {
+ var config = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(symbol).ToList();
+
+ if (!config.Any())
+ {
+ throw new Exception($"Was expecting configurations for {symbol}");
+ }
+ if (config.Any(dataConfig => dataConfig.DataNormalizationMode != DataNormalizationMode.Raw))
+ {
+ throw new Exception($"Was expecting DataNormalizationMode.Raw configurations for {symbol}");
+ }
+ }
+ }
+
+ if (Time.Date > _expiration)
+ {
+ if (SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(_option).Any())
+ {
+ throw new Exception($"Unexpected configurations for {_option} after it has been delisted");
+ }
+
+ if (Securities[_twx].Invested)
+ {
+ if (!SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(_twx).Any())
+ {
+ throw new Exception($"Was expecting configurations for {_twx}");
+ }
+
+ // first we liquidate the option exercised position
+ Liquidate(_twx);
+ }
+ }
+ else if (Time.Date > _expiration && !Securities[_twx].Invested)
+ {
+ if (SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(_twx).Any())
+ {
+ throw new Exception($"Unexpected configurations for {_twx} after it has been liquidated");
+ }
+ }
+ }
+
+ ///
+ /// 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 bool CanRunLocally { get; } = true;
+
+ ///
+ /// This is used by the regression test system to indicate which languages this algorithm is written in.
+ ///
+ public Language[] Languages { get; } = { Language.CSharp, Language.Python };
+
+ ///
+ /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
+ ///
+ public Dictionary ExpectedStatistics => new Dictionary
+ {
+ {"Total Trades", "3"},
+ {"Average Win", "2.94%"},
+ {"Average Loss", "-2.98%"},
+ {"Compounding Annual Return", "-1.749%"},
+ {"Drawdown", "0.200%"},
+ {"Expectancy", "-0.006"},
+ {"Net Profit", "-0.124%"},
+ {"Sharpe Ratio", "3.589"},
+ {"Probabilistic Sharpe Ratio", "90.473%"},
+ {"Loss Rate", "50%"},
+ {"Win Rate", "50%"},
+ {"Profit-Loss Ratio", "0.99"},
+ {"Alpha", "0.007"},
+ {"Beta", "-0.001"},
+ {"Annual Standard Deviation", "0.002"},
+ {"Annual Variance", "0"},
+ {"Information Ratio", "-2.913"},
+ {"Tracking Error", "0.057"},
+ {"Treynor Ratio", "-4.947"},
+ {"Total Fees", "$2.00"},
+ {"Fitness Score", "0.001"},
+ {"Kelly Criterion Estimate", "0"},
+ {"Kelly Criterion Probability Value", "0"},
+ {"Sortino Ratio", "-0.946"},
+ {"Return Over Maximum Drawdown", "-10.498"},
+ {"Portfolio Turnover", "0.007"},
+ {"Total Insights Generated", "0"},
+ {"Total Insights Closed", "0"},
+ {"Total Insights Analysis Completed", "0"},
+ {"Long Insight Count", "0"},
+ {"Short Insight Count", "0"},
+ {"Long/Short Ratio", "100%"},
+ {"Estimated Monthly Alpha Value", "$0"},
+ {"Total Accumulated Estimated Alpha Value", "$0"},
+ {"Mean Population Estimated Insight Value", "$0"},
+ {"Mean Population Direction", "0%"},
+ {"Mean Population Magnitude", "0%"},
+ {"Rolling Averaged Population Direction", "0%"},
+ {"Rolling Averaged Population Magnitude", "0%"},
+ {"OrderListHash", "1123164511"}
+ };
+ }
+}
diff --git a/Algorithm.CSharp/AddOptionContractFromUniverseRegressionAlgorithm.cs b/Algorithm.CSharp/AddOptionContractFromUniverseRegressionAlgorithm.cs
new file mode 100644
index 000000000000..43f74c3f6145
--- /dev/null
+++ b/Algorithm.CSharp/AddOptionContractFromUniverseRegressionAlgorithm.cs
@@ -0,0 +1,216 @@
+/*
+ * 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.Data.UniverseSelection;
+using QuantConnect.Interfaces;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// We add an option contract using and place a trade, the underlying
+ /// gets deselected from the universe selection but should still be present since we manually added the option contract.
+ /// Later we call and expect both option and underlying to be removed.
+ ///
+ public class AddOptionContractFromUniverseRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
+ {
+ private DateTime _expiration = new DateTime(2014, 06, 21);
+ private SecurityChanges _securityChanges = SecurityChanges.None;
+ private Symbol _option;
+ private Symbol _aapl;
+ private Symbol _twx;
+ private bool _traded;
+
+ public override void Initialize()
+ {
+ _twx = QuantConnect.Symbol.Create("TWX", SecurityType.Equity, Market.USA);
+ _aapl = QuantConnect.Symbol.Create("AAPL", SecurityType.Equity, Market.USA);
+ UniverseSettings.Resolution = Resolution.Minute;
+ UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
+
+ SetStartDate(2014, 06, 05);
+ SetEndDate(2014, 06, 09);
+
+ AddUniverse(enumerable => new[] { Time.Date <= new DateTime(2014, 6, 5) ? _twx : _aapl },
+ enumerable => new[] { Time.Date <= new DateTime(2014, 6, 5) ? _twx : _aapl });
+ }
+
+ public override void OnData(Slice data)
+ {
+ if (_option != null && Securities[_option].Price != 0 && !_traded)
+ {
+ _traded = true;
+ Buy(_option, 1);
+ }
+
+ if (Time.Date > new DateTime(2014, 6, 5))
+ {
+ if (Time < new DateTime(2014, 6, 6, 14, 0, 0))
+ {
+ var configs = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(_twx);
+ // assert underlying still there after the universe selection removed it, still used by the manually added option contract
+ if (!configs.Any())
+ {
+ throw new Exception($"Was expecting configurations for {_twx}" +
+ $" even after it has been deselected from coarse universe because we still have the option contract.");
+ }
+ }
+ else if (Time == new DateTime(2014, 6, 6, 14, 0, 0))
+ {
+ // liquidate & remove the option
+ RemoveOptionContract(_option);
+ }
+ // assert underlying was finally removed
+ else if(Time > new DateTime(2014, 6, 6, 14, 0, 0))
+ {
+ foreach (var symbol in new[] { _option, _option.Underlying })
+ {
+ var configs = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(symbol);
+ if (configs.Any())
+ {
+ throw new Exception($"Unexpected configuration for {symbol} after it has been deselected from coarse universe and option contract is removed.");
+ }
+ }
+ }
+ }
+ }
+
+ public override void OnSecuritiesChanged(SecurityChanges changes)
+ {
+ if (_securityChanges.RemovedSecurities.Intersect(changes.RemovedSecurities).Any())
+ {
+ throw new Exception($"SecurityChanges.RemovedSecurities intersect {changes.RemovedSecurities}. We expect no duplicate!");
+ }
+ if (_securityChanges.AddedSecurities.Intersect(changes.AddedSecurities).Any())
+ {
+ throw new Exception($"SecurityChanges.AddedSecurities intersect {changes.RemovedSecurities}. We expect no duplicate!");
+ }
+ // keep track of all removed and added securities
+ _securityChanges += changes;
+
+ if (changes.AddedSecurities.Any(security => security.Symbol.SecurityType == SecurityType.Option))
+ {
+ return;
+ }
+
+ foreach (var addedSecurity in changes.AddedSecurities)
+ {
+ var option = OptionChainProvider.GetOptionContractList(addedSecurity.Symbol, Time)
+ .OrderBy(symbol => symbol.ID.Symbol)
+ .First(optionContract => optionContract.ID.Date == _expiration
+ && optionContract.ID.OptionRight == OptionRight.Call
+ && optionContract.ID.OptionStyle == OptionStyle.American);
+ AddOptionContract(option);
+
+ foreach (var symbol in new[] { option, option.Underlying })
+ {
+ var config = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(symbol).ToList();
+
+ if (!config.Any())
+ {
+ throw new Exception($"Was expecting configurations for {symbol}");
+ }
+ if (config.Any(dataConfig => dataConfig.DataNormalizationMode != DataNormalizationMode.Raw))
+ {
+ throw new Exception($"Was expecting DataNormalizationMode.Raw configurations for {symbol}");
+ }
+ }
+
+ // just keep the first we got
+ if (_option == null)
+ {
+ _option = option;
+ }
+ }
+ }
+
+ public override void OnEndOfAlgorithm()
+ {
+ if (SubscriptionManager.Subscriptions.Any(dataConfig => dataConfig.Symbol == _twx || dataConfig.Symbol.Underlying == _twx))
+ {
+ throw new Exception($"Was NOT expecting any configurations for {_twx} or it's options, since we removed the contract");
+ }
+
+ if (SubscriptionManager.Subscriptions.All(dataConfig => dataConfig.Symbol != _aapl))
+ {
+ throw new Exception($"Was expecting configurations for {_aapl}");
+ }
+ if (SubscriptionManager.Subscriptions.All(dataConfig => dataConfig.Symbol.Underlying != _aapl))
+ {
+ throw new Exception($"Was expecting options configurations for {_aapl}");
+ }
+ }
+
+ ///
+ /// 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 bool CanRunLocally { get; } = true;
+
+ ///
+ /// This is used by the regression test system to indicate which languages this algorithm is written in.
+ ///
+ public Language[] Languages { get; } = { Language.CSharp, Language.Python };
+
+ ///
+ /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
+ ///
+ public Dictionary ExpectedStatistics => new Dictionary
+ {
+ {"Total Trades", "2"},
+ {"Average Win", "0%"},
+ {"Average Loss", "-0.23%"},
+ {"Compounding Annual Return", "-15.596%"},
+ {"Drawdown", "0.200%"},
+ {"Expectancy", "-1"},
+ {"Net Profit", "-0.232%"},
+ {"Sharpe Ratio", "-7.739"},
+ {"Probabilistic Sharpe Ratio", "1.216%"},
+ {"Loss Rate", "100%"},
+ {"Win Rate", "0%"},
+ {"Profit-Loss Ratio", "0"},
+ {"Alpha", "0.027"},
+ {"Beta", "-0.174"},
+ {"Annual Standard Deviation", "0.006"},
+ {"Annual Variance", "0"},
+ {"Information Ratio", "-11.586"},
+ {"Tracking Error", "0.042"},
+ {"Treynor Ratio", "0.286"},
+ {"Total Fees", "$2.00"},
+ {"Fitness Score", "0"},
+ {"Kelly Criterion Estimate", "0"},
+ {"Kelly Criterion Probability Value", "0"},
+ {"Sortino Ratio", "-19.883"},
+ {"Return Over Maximum Drawdown", "-67.224"},
+ {"Portfolio Turnover", "0.014"},
+ {"Total Insights Generated", "0"},
+ {"Total Insights Closed", "0"},
+ {"Total Insights Analysis Completed", "0"},
+ {"Long Insight Count", "0"},
+ {"Short Insight Count", "0"},
+ {"Long/Short Ratio", "100%"},
+ {"Estimated Monthly Alpha Value", "$0"},
+ {"Total Accumulated Estimated Alpha Value", "$0"},
+ {"Mean Population Estimated Insight Value", "$0"},
+ {"Mean Population Direction", "0%"},
+ {"Mean Population Magnitude", "0%"},
+ {"Rolling Averaged Population Direction", "0%"},
+ {"Rolling Averaged Population Magnitude", "0%"},
+ {"OrderListHash", "721476625"}
+ };
+ }
+}
diff --git a/Algorithm.CSharp/CoarseFineOptionUniverseChainRegressionAlgorithm.cs b/Algorithm.CSharp/CoarseFineOptionUniverseChainRegressionAlgorithm.cs
new file mode 100644
index 000000000000..b21a1d691a80
--- /dev/null
+++ b/Algorithm.CSharp/CoarseFineOptionUniverseChainRegressionAlgorithm.cs
@@ -0,0 +1,189 @@
+/*
+ * 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.Data.UniverseSelection;
+using QuantConnect.Interfaces;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Demonstration of how to chain a coarse and fine universe selection with an option chain universe selection model
+ /// that will add and remove an for each symbol selected on fine
+ ///
+ public class CoarseFineOptionUniverseChainRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
+ {
+ // initialize our changes to nothing
+ private SecurityChanges _changes = SecurityChanges.None;
+ private int _optionCount;
+ private Symbol _lastEquityAdded;
+ private Symbol _aapl;
+ private Symbol _twx;
+
+ public override void Initialize()
+ {
+ _twx = QuantConnect.Symbol.Create("TWX", SecurityType.Equity, Market.USA);
+ _aapl = QuantConnect.Symbol.Create("AAPL", SecurityType.Equity, Market.USA);
+ UniverseSettings.Resolution = Resolution.Minute;
+
+ SetStartDate(2014, 06, 05);
+ SetEndDate(2014, 06, 06);
+
+ var selectionUniverse = AddUniverse(enumerable => new[] { Time.Date <= new DateTime(2014, 6, 5) ? _twx : _aapl },
+ enumerable => new[] { Time.Date <= new DateTime(2014, 6, 5) ? _twx : _aapl });
+
+ AddUniverseOptions(selectionUniverse, universe =>
+ {
+ if (universe.Underlying == null)
+ {
+ throw new Exception("Underlying data point is null! This shouldn't happen, each OptionChainUniverse handles and should provide this");
+ }
+ return universe.IncludeWeeklys()
+ .FrontMonth()
+ .Contracts(universe.Take(5));
+ });
+ }
+
+ public override void OnData(Slice data)
+ {
+ // if we have no changes, do nothing
+ if (_changes == SecurityChanges.None ||
+ _changes.AddedSecurities.Any(security => security.Price == 0))
+ {
+ return;
+ }
+
+ // liquidate removed securities
+ foreach (var security in _changes.RemovedSecurities)
+ {
+ if (security.Invested)
+ {
+ Liquidate(security.Symbol);
+ }
+ }
+
+ foreach (var security in _changes.AddedSecurities)
+ {
+ if (!security.Symbol.HasUnderlying)
+ {
+ _lastEquityAdded = security.Symbol;
+ }
+ else
+ {
+ // options added should all match prev added security
+ if (security.Symbol.Underlying != _lastEquityAdded)
+ {
+ throw new Exception($"Unexpected symbol added {security.Symbol}");
+ }
+
+ _optionCount++;
+ }
+
+ SetHoldings(security.Symbol, 0.05m);
+
+ var config = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(security.Symbol).ToList();
+
+ if (!config.Any())
+ {
+ throw new Exception($"Was expecting configurations for {security.Symbol}");
+ }
+ if (config.Any(dataConfig => dataConfig.DataNormalizationMode != DataNormalizationMode.Raw))
+ {
+ throw new Exception($"Was expecting DataNormalizationMode.Raw configurations for {security.Symbol}");
+ }
+ }
+ _changes = SecurityChanges.None;
+ }
+
+ public override void OnSecuritiesChanged(SecurityChanges changes)
+ {
+ _changes += changes;
+ }
+
+ public override void OnEndOfAlgorithm()
+ {
+ var config = SubscriptionManager.Subscriptions.ToList();
+ if (config.Any(dataConfig => dataConfig.Symbol == _twx || dataConfig.Symbol.Underlying == _twx))
+ {
+ throw new Exception($"Was NOT expecting any configurations for {_twx} or it's options, since coarse/fine should have deselected it");
+ }
+
+ if (_optionCount == 0)
+ {
+ throw new Exception("Option universe chain did not add any option!");
+ }
+ }
+
+ ///
+ /// 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 bool CanRunLocally { get; } = true;
+
+ ///
+ /// This is used by the regression test system to indicate which languages this algorithm is written in.
+ ///
+ public Language[] Languages { get; } = { Language.CSharp, Language.Python };
+
+ ///
+ /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
+ ///
+ public Dictionary ExpectedStatistics => new Dictionary
+ {
+ {"Total Trades", "13"},
+ {"Average Win", "0.65%"},
+ {"Average Loss", "-0.05%"},
+ {"Compounding Annual Return", "3216040423556140000000000%"},
+ {"Drawdown", "0.500%"},
+ {"Expectancy", "1.393"},
+ {"Net Profit", "32.840%"},
+ {"Sharpe Ratio", "7.14272222483913E+15"},
+ {"Probabilistic Sharpe Ratio", "0%"},
+ {"Loss Rate", "83%"},
+ {"Win Rate", "17%"},
+ {"Profit-Loss Ratio", "13.36"},
+ {"Alpha", "2.59468989671647E+16"},
+ {"Beta", "67.661"},
+ {"Annual Standard Deviation", "3.633"},
+ {"Annual Variance", "13.196"},
+ {"Information Ratio", "7.24987266907741E+15"},
+ {"Tracking Error", "3.579"},
+ {"Treynor Ratio", "383485597312030"},
+ {"Total Fees", "$13.00"},
+ {"Fitness Score", "0.232"},
+ {"Kelly Criterion Estimate", "0"},
+ {"Kelly Criterion Probability Value", "0"},
+ {"Sortino Ratio", "79228162514264337593543950335"},
+ {"Return Over Maximum Drawdown", "79228162514264337593543950335"},
+ {"Portfolio Turnover", "0.232"},
+ {"Total Insights Generated", "0"},
+ {"Total Insights Closed", "0"},
+ {"Total Insights Analysis Completed", "0"},
+ {"Long Insight Count", "0"},
+ {"Short Insight Count", "0"},
+ {"Long/Short Ratio", "100%"},
+ {"Estimated Monthly Alpha Value", "$0"},
+ {"Total Accumulated Estimated Alpha Value", "$0"},
+ {"Mean Population Estimated Insight Value", "$0"},
+ {"Mean Population Direction", "0%"},
+ {"Mean Population Magnitude", "0%"},
+ {"Rolling Averaged Population Direction", "0%"},
+ {"Rolling Averaged Population Magnitude", "0%"},
+ {"OrderListHash", "1630141557"}
+ };
+ }
+}
diff --git a/Algorithm.CSharp/OptionChainedAndUniverseSelectionRegressionAlgorithm.cs b/Algorithm.CSharp/OptionChainedAndUniverseSelectionRegressionAlgorithm.cs
new file mode 100644
index 000000000000..57327f61ae72
--- /dev/null
+++ b/Algorithm.CSharp/OptionChainedAndUniverseSelectionRegressionAlgorithm.cs
@@ -0,0 +1,142 @@
+/*
+ * 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.Linq;
+using QuantConnect.Data;
+using QuantConnect.Interfaces;
+using System.Collections.Generic;
+using QuantConnect.Algorithm.Framework.Selection;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Regression algorithm making sure that the added universe selection does not remove the option chain during it's daily refresh
+ ///
+ public class OptionChainedAndUniverseSelectionRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
+ {
+ private Symbol _aaplOption;
+
+ public override void Initialize()
+ {
+ UniverseSettings.Resolution = Resolution.Minute;
+
+ SetStartDate(2014, 06, 05);
+ SetEndDate(2014, 06, 09);
+
+ _aaplOption = AddOption("AAPL").Symbol;
+ AddUniverseSelection(new DailyUniverseSelectionModel("MyCustomSelectionModel", time => new[] { "AAPL" }, this));
+ }
+
+ public override void OnData(Slice data)
+ {
+ if (!Portfolio.Invested)
+ {
+ Buy("AAPL", 1);
+ }
+ }
+
+ public override void OnEndOfAlgorithm()
+ {
+ var config = SubscriptionManager.Subscriptions.ToList();
+ if (config.All(dataConfig => dataConfig.Symbol != "AAPL"))
+ {
+ throw new Exception("Was expecting configurations for AAPL");
+ }
+ if (config.All(dataConfig => dataConfig.Symbol.SecurityType != SecurityType.Option))
+ {
+ throw new Exception($"Was expecting configurations for {_aaplOption}");
+ }
+ }
+
+ private class DailyUniverseSelectionModel : CustomUniverseSelectionModel
+ {
+ private DateTime _lastRefresh;
+ private IAlgorithm _algorithm;
+
+ public DailyUniverseSelectionModel(string name, Func> selector, IAlgorithm algorithm) : base(name, selector)
+ {
+ _algorithm = algorithm;
+ }
+
+ public override DateTime GetNextRefreshTimeUtc()
+ {
+ if (_lastRefresh != _algorithm.Time.Date)
+ {
+ _lastRefresh = _algorithm.Time.Date;
+ return DateTime.MinValue;
+ }
+ return DateTime.MaxValue;
+ }
+ }
+
+ ///
+ /// 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 bool CanRunLocally { get; } = true;
+
+ ///
+ /// This is used by the regression test system to indicate which languages this algorithm is written in.
+ ///
+ public Language[] Languages { get; } = { Language.CSharp };
+
+ ///
+ /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
+ ///
+ public Dictionary ExpectedStatistics => new Dictionary
+ {
+ {"Total Trades", "1"},
+ {"Average Win", "0%"},
+ {"Average Loss", "0%"},
+ {"Compounding Annual Return", "0.678%"},
+ {"Drawdown", "3.700%"},
+ {"Expectancy", "0"},
+ {"Net Profit", "0.009%"},
+ {"Sharpe Ratio", "7.969"},
+ {"Probabilistic Sharpe Ratio", "0%"},
+ {"Loss Rate", "0%"},
+ {"Win Rate", "0%"},
+ {"Profit-Loss Ratio", "0"},
+ {"Alpha", "0.046"},
+ {"Beta", "-0.032"},
+ {"Annual Standard Deviation", "0.001"},
+ {"Annual Variance", "0"},
+ {"Information Ratio", "-24.461"},
+ {"Tracking Error", "0.044"},
+ {"Treynor Ratio", "-0.336"},
+ {"Total Fees", "$1.00"},
+ {"Fitness Score", "0.003"},
+ {"Kelly Criterion Estimate", "0"},
+ {"Kelly Criterion Probability Value", "0"},
+ {"Sortino Ratio", "79228162514264337593543950335"},
+ {"Return Over Maximum Drawdown", "79228162514264337593543950335"},
+ {"Portfolio Turnover", "0.003"},
+ {"Total Insights Generated", "0"},
+ {"Total Insights Closed", "0"},
+ {"Total Insights Analysis Completed", "0"},
+ {"Long Insight Count", "0"},
+ {"Short Insight Count", "0"},
+ {"Long/Short Ratio", "100%"},
+ {"Estimated Monthly Alpha Value", "$0"},
+ {"Total Accumulated Estimated Alpha Value", "$0"},
+ {"Mean Population Estimated Insight Value", "$0"},
+ {"Mean Population Direction", "0%"},
+ {"Mean Population Magnitude", "0%"},
+ {"Rolling Averaged Population Direction", "0%"},
+ {"Rolling Averaged Population Magnitude", "0%"},
+ {"OrderListHash", "-1779427412"}
+ };
+ }
+}
diff --git a/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj b/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj
index 27f277c8d004..e72f29310f7c 100644
--- a/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj
+++ b/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj
@@ -142,10 +142,14 @@
Properties\SharedAssemblyInfo.cs
+
+
+
+
@@ -459,4 +463,4 @@
-->
-
+
\ No newline at end of file
diff --git a/Algorithm.CSharp/UniverseSelectionRegressionAlgorithm.cs b/Algorithm.CSharp/UniverseSelectionRegressionAlgorithm.cs
index fd32809cb090..21f12fa5930e 100644
--- a/Algorithm.CSharp/UniverseSelectionRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/UniverseSelectionRegressionAlgorithm.cs
@@ -172,32 +172,32 @@ private void AssertQuantity(Security security, int expected)
///
public Dictionary ExpectedStatistics => new Dictionary
{
- {"Total Trades", "5"},
+ {"Total Trades", "4"},
{"Average Win", "0.64%"},
{"Average Loss", "0%"},
- {"Compounding Annual Return", "-74.197%"},
- {"Drawdown", "6.600%"},
+ {"Compounding Annual Return", "-56.577%"},
+ {"Drawdown", "3.800%"},
{"Expectancy", "0"},
- {"Net Profit", "-6.115%"},
- {"Sharpe Ratio", "-2.281"},
- {"Probabilistic Sharpe Ratio", "11.870%"},
+ {"Net Profit", "-3.811%"},
+ {"Sharpe Ratio", "-2.773"},
+ {"Probabilistic Sharpe Ratio", "13.961%"},
{"Loss Rate", "0%"},
{"Win Rate", "100%"},
{"Profit-Loss Ratio", "0"},
- {"Alpha", "-0.684"},
- {"Beta", "-0.113"},
- {"Annual Standard Deviation", "0.292"},
- {"Annual Variance", "0.085"},
- {"Information Ratio", "-1.606"},
- {"Tracking Error", "0.312"},
- {"Treynor Ratio", "5.866"},
- {"Total Fees", "$5.00"},
- {"Fitness Score", "0.017"},
+ {"Alpha", "-0.504"},
+ {"Beta", "-0.052"},
+ {"Annual Standard Deviation", "0.179"},
+ {"Annual Variance", "0.032"},
+ {"Information Ratio", "-1.599"},
+ {"Tracking Error", "0.207"},
+ {"Treynor Ratio", "9.508"},
+ {"Total Fees", "$4.00"},
+ {"Fitness Score", "0.008"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
- {"Sortino Ratio", "-2.584"},
- {"Return Over Maximum Drawdown", "-11.287"},
- {"Portfolio Turnover", "0.177"},
+ {"Sortino Ratio", "-3.791"},
+ {"Return Over Maximum Drawdown", "-14.846"},
+ {"Portfolio Turnover", "0.136"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},
{"Total Insights Analysis Completed", "0"},
@@ -211,7 +211,7 @@ private void AssertQuantity(Security security, int expected)
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
- {"OrderListHash", "-1386253041"}
+ {"OrderListHash", "1484950465"}
};
}
}
diff --git a/Algorithm.Framework/Selection/OptionUniverseSelectionModel.cs b/Algorithm.Framework/Selection/OptionUniverseSelectionModel.cs
index a141ba719744..39e206e5472e 100644
--- a/Algorithm.Framework/Selection/OptionUniverseSelectionModel.cs
+++ b/Algorithm.Framework/Selection/OptionUniverseSelectionModel.cs
@@ -15,12 +15,9 @@
using System;
using System.Collections.Generic;
-using QuantConnect.Data;
-using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
-using QuantConnect.Securities.Option;
namespace QuantConnect.Algorithm.Framework.Selection
{
@@ -107,53 +104,11 @@ public override IEnumerable CreateUniverses(QCAlgorithm algorithm)
// prevent creating duplicate option chains -- one per underlying
if (uniqueUnderlyingSymbols.Add(optionSymbol.Underlying))
{
- yield return CreateOptionChain(algorithm, optionSymbol);
+ yield return algorithm.CreateOptionChain(optionSymbol, Filter, _universeSettings);
}
}
}
- ///
- /// Creates the canonical chain security for a given symbol
- ///
- /// The algorithm instance to create universes for
- /// Symbol of the option
- /// Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed
- /// Performs extra initialization (such as setting models) after we create a new security object
- /// for the given symbol
- [Obsolete("This method is obsolete because SecurityInitializer is obsolete and will not be used.")]
- protected virtual Option CreateOptionChainSecurity(QCAlgorithm algorithm, Symbol symbol, UniverseSettings settings, ISecurityInitializer initializer)
- {
- return CreateOptionChainSecurity(
- algorithm.SubscriptionManager.SubscriptionDataConfigService,
- symbol,
- settings,
- algorithm.Securities);
- }
-
- ///
- /// Creates the canonical chain security for a given symbol
- ///
- /// The service used to create new
- /// Symbol of the option
- /// Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed
- /// Used to create new
- /// for the given symbol
- protected virtual Option CreateOptionChainSecurity(
- ISubscriptionDataConfigService subscriptionDataConfigService,
- Symbol symbol,
- UniverseSettings settings,
- SecurityManager securityManager)
- {
- var config = subscriptionDataConfigService.Add(
- typeof(ZipEntryName),
- symbol,
- settings.Resolution,
- settings.FillForward,
- settings.ExtendedMarketHours,
- false);
- return (Option)securityManager.CreateSecurity(symbol, config, settings.Leverage, false);
- }
-
///
/// Defines the option chain universe filter
///
@@ -162,55 +117,5 @@ protected virtual OptionFilterUniverse Filter(OptionFilterUniverse filter)
// NOP
return filter;
}
-
- ///
- /// Creates a for a given symbol
- ///
- /// The algorithm instance to create universes for
- /// Symbol of the option
- /// for the given symbol
- private OptionChainUniverse CreateOptionChain(QCAlgorithm algorithm, Symbol symbol)
- {
- if (symbol.SecurityType != SecurityType.Option)
- {
- throw new ArgumentException("CreateOptionChain requires an option symbol.");
- }
-
- // rewrite non-canonical symbols to be canonical
- var market = symbol.ID.Market;
- var underlying = symbol.Underlying;
- if (!symbol.IsCanonical())
- {
- var alias = $"?{underlying.Value}";
- symbol = Symbol.Create(underlying.Value, SecurityType.Option, market, alias);
- }
-
- // resolve defaults if not specified
- var settings = _universeSettings ?? algorithm.UniverseSettings;
-
- // create canonical security object, but don't duplicate if it already exists
- Security security;
- Option optionChain;
- if (!algorithm.Securities.TryGetValue(symbol, out security))
- {
- optionChain = CreateOptionChainSecurity(
- algorithm.SubscriptionManager.SubscriptionDataConfigService,
- symbol,
- settings,
- algorithm.Securities);
- }
- else
- {
- optionChain = (Option)security;
- }
-
- // set the option chain contract filter function
- optionChain.SetFilter(Filter);
-
- // force option chain security to not be directly tradable AFTER it's configured to ensure it's not overwritten
- optionChain.IsTradable = false;
-
- return new OptionChainUniverse(optionChain, settings, algorithm.LiveMode);
- }
}
}
\ No newline at end of file
diff --git a/Algorithm.Python/AddOptionContractExpiresRegressionAlgorithm.py b/Algorithm.Python/AddOptionContractExpiresRegressionAlgorithm.py
new file mode 100644
index 000000000000..4f6c9e47507b
--- /dev/null
+++ b/Algorithm.Python/AddOptionContractExpiresRegressionAlgorithm.py
@@ -0,0 +1,67 @@
+# 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.
+
+from clr import AddReference
+AddReference("System")
+AddReference("QuantConnect.Algorithm")
+AddReference("QuantConnect.Common")
+
+from System import *
+from QuantConnect import *
+from QuantConnect.Algorithm import *
+from datetime import *
+
+###
+### We add an option contract using 'QCAlgorithm.AddOptionContract' and place a trade, the underlying
+### gets deselected from the universe selection but should still be present since we manually added the option contract.
+### Later we call 'QCAlgorithm.RemoveOptionContract' and expect both option and underlying to be removed.
+###
+class AddOptionContractExpiresRegressionAlgorithm(QCAlgorithm):
+ def Initialize(self):
+ '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
+
+ self.SetStartDate(2014, 6, 5)
+ self.SetEndDate(2014, 6, 30)
+
+ self._expiration = datetime(2014, 6, 21)
+ self._option = None
+ self._traded = False
+
+ self._twx = Symbol.Create("TWX", SecurityType.Equity, Market.USA)
+
+ self.AddUniverse("my-daily-universe-name", self.Selector)
+
+ def Selector(self, time):
+ return [ "AAPL" ]
+
+ def OnData(self, data):
+ '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
+
+ Arguments:
+ data: Slice object keyed by symbol containing the stock data
+ '''
+ if self._option == None:
+ options = self.OptionChainProvider.GetOptionContractList(self._twx, self.Time)
+ options = sorted(options, key=lambda x: x.ID.Symbol)
+
+ option = next((option for option in options if option.ID.Date == self._expiration and option.ID.OptionRight == OptionRight.Call and option.ID.OptionStyle == OptionStyle.American), None)
+ if option != None:
+ self._option = self.AddOptionContract(option).Symbol;
+
+ if self._option != None and self.Securities[self._option].Price != 0 and not self._traded:
+ self._traded = True;
+ self.Buy(self._option, 1);
+
+ if self.Time > self._expiration and self.Securities[self._twx].Invested:
+ # we liquidate the option exercised position
+ self.Liquidate(self._twx);
\ No newline at end of file
diff --git a/Algorithm.Python/AddOptionContractFromUniverseRegressionAlgorithm.py b/Algorithm.Python/AddOptionContractFromUniverseRegressionAlgorithm.py
new file mode 100644
index 000000000000..878cc5208daa
--- /dev/null
+++ b/Algorithm.Python/AddOptionContractFromUniverseRegressionAlgorithm.py
@@ -0,0 +1,87 @@
+# 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.
+
+from clr import AddReference
+AddReference("System")
+AddReference("QuantConnect.Algorithm")
+AddReference("QuantConnect.Common")
+
+from System import *
+from QuantConnect import *
+from QuantConnect.Algorithm import *
+from datetime import *
+
+###
+### We add an option contract using 'QCAlgorithm.AddOptionContract' and place a trade, the underlying
+### gets deselected from the universe selection but should still be present since we manually added the option contract.
+### Later we call 'QCAlgorithm.RemoveOptionContract' and expect both option and underlying to be removed.
+###
+class AddOptionContractFromUniverseRegressionAlgorithm(QCAlgorithm):
+ def Initialize(self):
+ '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
+
+ self.SetStartDate(2014, 6, 5)
+ self.SetEndDate(2014, 6, 9)
+
+ self._expiration = datetime(2014, 6, 21)
+ self._securityChanges = None
+ self._option = None
+ self._traded = False
+
+ self._twx = Symbol.Create("TWX", SecurityType.Equity, Market.USA)
+ self._aapl = Symbol.Create("AAPL", SecurityType.Equity, Market.USA)
+ self.UniverseSettings.Resolution = Resolution.Minute
+ self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw
+
+ self.AddUniverse(self.Selector, self.Selector)
+
+ def Selector(self, fundamental):
+ if self.Time <= datetime(2014, 6, 5):
+ return [ self._twx ]
+ return [ self._aapl ]
+
+ def OnData(self, data):
+ '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
+
+ Arguments:
+ data: Slice object keyed by symbol containing the stock data
+ '''
+ if self._option != None and self.Securities[self._option].Price != 0 and not self._traded:
+ self._traded = True;
+ self.Buy(self._option, 1);
+
+ if self.Time == datetime(2014, 6, 6, 14, 0, 0):
+ # liquidate & remove the option
+ self.RemoveOptionContract(self._option)
+
+ def OnSecuritiesChanged(self, changes):
+ # keep track of all removed and added securities
+ if self._securityChanges == None:
+ self._securityChanges = changes
+ else:
+ self._securityChanges.op_Addition(self._securityChanges, changes)
+
+ if any(security.Symbol.SecurityType == SecurityType.Option for security in changes.AddedSecurities):
+ return
+
+ for addedSecurity in changes.AddedSecurities:
+ options = self.OptionChainProvider.GetOptionContractList(addedSecurity.Symbol, self.Time)
+ options = sorted(options, key=lambda x: x.ID.Symbol)
+
+ option = next((option for option in options if option.ID.Date == self._expiration and option.ID.OptionRight == OptionRight.Call and option.ID.OptionStyle == OptionStyle.American), None)
+
+ self.AddOptionContract(option)
+
+ # just keep the first we got
+ if self._option == None:
+ self._option = option
\ No newline at end of file
diff --git a/Algorithm.Python/CoarseFineOptionUniverseChainRegressionAlgorithm.py b/Algorithm.Python/CoarseFineOptionUniverseChainRegressionAlgorithm.py
new file mode 100644
index 000000000000..b80491b603c0
--- /dev/null
+++ b/Algorithm.Python/CoarseFineOptionUniverseChainRegressionAlgorithm.py
@@ -0,0 +1,99 @@
+# 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.
+
+from clr import AddReference
+AddReference("System.Core")
+AddReference("System.Collections")
+AddReference("QuantConnect.Common")
+AddReference("QuantConnect.Algorithm")
+
+from System import *
+from QuantConnect import *
+from QuantConnect.Algorithm import *
+from QuantConnect.Data.UniverseSelection import *
+from datetime import *
+
+###
+### Demonstration of how to chain a coarse and fine universe selection with an option chain universe selection model
+### that will add and remove an'OptionChainUniverse' for each symbol selected on fine
+###
+class CoarseFineOptionUniverseChainRegressionAlgorithm(QCAlgorithm):
+
+ def Initialize(self):
+ '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
+
+ self.SetStartDate(2014,6,5) #Set Start Date
+ self.SetEndDate(2014,6,6) #Set End Date
+
+ self.UniverseSettings.Resolution = Resolution.Minute
+ self._twx = Symbol.Create("TWX", SecurityType.Equity, Market.USA)
+ self._aapl = Symbol.Create("AAPL", SecurityType.Equity, Market.USA)
+ self._lastEquityAdded = None
+ self._changes = None
+ self._optionCount = 0
+
+ universe = self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
+
+ self.AddUniverseOptions(universe, self.OptionFilterFunction)
+
+ def OptionFilterFunction(self, universe):
+ universe.IncludeWeeklys().FrontMonth()
+
+ contracts = list()
+ for symbol in universe:
+ if len(contracts) == 5:
+ break
+ contracts.append(symbol)
+ return universe.Contracts(contracts)
+
+ def CoarseSelectionFunction(self, coarse):
+ if self.Time <= datetime(2014,6,5):
+ return [ self._twx ]
+ return [ self._aapl ]
+
+ def FineSelectionFunction(self, fine):
+ if self.Time <= datetime(2014,6,5):
+ return [ self._twx ]
+ return [ self._aapl ]
+
+ def OnData(self, data):
+ if self._changes == None or any(security.Price == 0 for security in self._changes.AddedSecurities):
+ return
+
+ # liquidate removed securities
+ for security in self._changes.RemovedSecurities:
+ if security.Invested:
+ self.Liquidate(security.Symbol);
+
+ for security in self._changes.AddedSecurities:
+ if not security.Symbol.HasUnderlying:
+ self._lastEquityAdded = security.Symbol;
+ else:
+ # options added should all match prev added security
+ if security.Symbol.Underlying != self._lastEquityAdded:
+ raise ValueError(f"Unexpected symbol added {security.Symbol}")
+ self._optionCount += 1
+
+ self.SetHoldings(security.Symbol, 0.05)
+ self._changes = None
+
+ # this event fires whenever we have changes to our universe
+ def OnSecuritiesChanged(self, changes):
+ if self._changes == None:
+ self._changes = changes
+ return
+ self._changes = self._changes.op_Addition(self._changes, changes)
+
+ def OnEndOfAlgorithm(self):
+ if self._optionCount == 0:
+ raise ValueError("Option universe chain did not add any option!")
diff --git a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
index 83191dacc352..3b076a935c99 100644
--- a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
+++ b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
@@ -46,6 +46,8 @@
+
+
@@ -83,6 +85,7 @@
+
diff --git a/Algorithm/QCAlgorithm.Framework.cs b/Algorithm/QCAlgorithm.Framework.cs
index 05b947192f5e..b311ec5f5145 100644
--- a/Algorithm/QCAlgorithm.Framework.cs
+++ b/Algorithm/QCAlgorithm.Framework.cs
@@ -71,7 +71,9 @@ public void FrameworkPostInitialize()
{
foreach (var universe in UniverseSelection.CreateUniverses(this))
{
- AddUniverse(universe);
+ // on purpose we don't call 'AddUniverse' here so that these universes don't get registered as user added
+ // this is so that later during 'UniverseSelection.CreateUniverses' we wont remove them from UniverseManager
+ _pendingUniverseAdditions.Add(universe);
}
if (DebugMode)
@@ -94,8 +96,7 @@ public void OnFrameworkData(Slice slice)
foreach (var ukvp in UniverseManager)
{
var universeSymbol = ukvp.Key;
- var qcUserDefined = UserDefinedUniverse.CreateSymbol(ukvp.Value.SecurityType, ukvp.Value.Market);
- if (universeSymbol.Equals(qcUserDefined))
+ if (_userAddedUniverses.Contains(universeSymbol))
{
// prevent removal of qc algorithm created user defined universes
continue;
diff --git a/Algorithm/QCAlgorithm.Python.cs b/Algorithm/QCAlgorithm.Python.cs
index 1b9dd08744f9..2c372cc5ae61 100644
--- a/Algorithm/QCAlgorithm.Python.cs
+++ b/Algorithm/QCAlgorithm.Python.cs
@@ -230,22 +230,22 @@ private Security AddDataImpl(Type dataType, Symbol symbol, Resolution? resolutio
/// will be executed on day changes in the NewYork time zone (
///
/// Defines an initial coarse selection
- public void AddUniverse(PyObject pyObject)
+ public Universe AddUniverse(PyObject pyObject)
{
Func, object> coarseFunc;
Universe universe;
if (pyObject.TryConvert(out universe))
{
- AddUniverse(universe);
+ return AddUniverse(universe);
}
else if (pyObject.TryConvert(out universe, allowPythonDerivative: true))
{
- AddUniverse(new UniversePythonWrapper(pyObject));
+ return AddUniverse(new UniversePythonWrapper(pyObject));
}
else if (pyObject.TryConvertToDelegate(out coarseFunc))
{
- AddUniverse(coarseFunc.ConvertToUniverseSelectionSymbolDelegate());
+ return AddUniverse(coarseFunc.ConvertToUniverseSelectionSymbolDelegate());
}
else
{
@@ -262,7 +262,7 @@ public void AddUniverse(PyObject pyObject)
///
/// Defines an initial coarse selection or a universe
/// Defines a more detailed selection with access to more data
- public void AddUniverse(PyObject pyObject, PyObject pyfine)
+ public Universe AddUniverse(PyObject pyObject, PyObject pyfine)
{
Func, object> coarseFunc;
Func, object> fineFunc;
@@ -270,11 +270,11 @@ public void AddUniverse(PyObject pyObject, PyObject pyfine)
if (pyObject.TryConvert(out universe) && pyfine.TryConvertToDelegate(out fineFunc))
{
- AddUniverse(universe, fineFunc.ConvertToUniverseSelectionSymbolDelegate());
+ return AddUniverse(universe, fineFunc.ConvertToUniverseSelectionSymbolDelegate());
}
else if (pyObject.TryConvertToDelegate(out coarseFunc) && pyfine.TryConvertToDelegate(out fineFunc))
{
- AddUniverse(coarseFunc.ConvertToUniverseSelectionSymbolDelegate(),
+ return AddUniverse(coarseFunc.ConvertToUniverseSelectionSymbolDelegate(),
fineFunc.ConvertToUniverseSelectionSymbolDelegate());
}
else
@@ -293,10 +293,10 @@ public void AddUniverse(PyObject pyObject, PyObject pyfine)
/// A unique name for this universe
/// The resolution this universe should be triggered on
/// Function delegate that accepts a DateTime and returns a collection of string symbols
- public void AddUniverse(string name, Resolution resolution, PyObject pySelector)
+ public Universe AddUniverse(string name, Resolution resolution, PyObject pySelector)
{
var selector = pySelector.ConvertToDelegate>();
- AddUniverse(name, resolution, selector.ConvertToUniverseSelectionStringDelegate());
+ return AddUniverse(name, resolution, selector.ConvertToUniverseSelectionStringDelegate());
}
///
@@ -305,10 +305,10 @@ public void AddUniverse(string name, Resolution resolution, PyObject pySelector)
///
/// A unique name for this universe
/// Function delegate that accepts a DateTime and returns a collection of string symbols
- public void AddUniverse(string name, PyObject pySelector)
+ public Universe AddUniverse(string name, PyObject pySelector)
{
var selector = pySelector.ConvertToDelegate>();
- AddUniverse(name, selector.ConvertToUniverseSelectionStringDelegate());
+ return AddUniverse(name, selector.ConvertToUniverseSelectionStringDelegate());
}
///
@@ -320,10 +320,10 @@ public void AddUniverse(string name, PyObject pySelector)
/// The market of the universe
/// The subscription settings used for securities added from this universe
/// Function delegate that accepts a DateTime and returns a collection of string symbols
- public void AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject pySelector)
+ public Universe AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject pySelector)
{
var selector = pySelector.ConvertToDelegate>();
- AddUniverse(securityType, name, resolution, market, universeSettings, selector.ConvertToUniverseSelectionStringDelegate());
+ return AddUniverse(securityType, name, resolution, market, universeSettings, selector.ConvertToUniverseSelectionStringDelegate());
}
///
@@ -334,9 +334,9 @@ public void AddUniverse(SecurityType securityType, string name, Resolution resol
/// The data type
/// A unique name for this universe
/// Function delegate that performs selection on the universe data
- public void AddUniverse(PyObject T, string name, PyObject selector)
+ public Universe AddUniverse(PyObject T, string name, PyObject selector)
{
- AddUniverse(T.CreateType(), SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
+ return AddUniverse(T.CreateType(), SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
}
///
@@ -348,9 +348,9 @@ public void AddUniverse(PyObject T, string name, PyObject selector)
/// A unique name for this universe
/// The epected resolution of the universe data
/// Function delegate that performs selection on the universe data
- public void AddUniverse(PyObject T, string name, Resolution resolution, PyObject selector)
+ public Universe AddUniverse(PyObject T, string name, Resolution resolution, PyObject selector)
{
- AddUniverse(T.CreateType(), SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
+ return AddUniverse(T.CreateType(), SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
}
///
@@ -363,9 +363,9 @@ public void AddUniverse(PyObject T, string name, Resolution resolution, PyObject
/// The epected resolution of the universe data
/// The settings used for securities added by this universe
/// Function delegate that performs selection on the universe data
- public void AddUniverse(PyObject T, string name, Resolution resolution, UniverseSettings universeSettings, PyObject selector)
+ public Universe AddUniverse(PyObject T, string name, Resolution resolution, UniverseSettings universeSettings, PyObject selector)
{
- AddUniverse(T.CreateType(), SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
+ return AddUniverse(T.CreateType(), SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
}
///
@@ -377,9 +377,9 @@ public void AddUniverse(PyObject T, string name, Resolution resolution, Universe
/// A unique name for this universe
/// The settings used for securities added by this universe
/// Function delegate that performs selection on the universe data
- public void AddUniverse(PyObject T, string name, UniverseSettings universeSettings, PyObject selector)
+ public Universe AddUniverse(PyObject T, string name, UniverseSettings universeSettings, PyObject selector)
{
- AddUniverse(T.CreateType(), SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
+ return AddUniverse(T.CreateType(), SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
}
///
@@ -392,9 +392,9 @@ public void AddUniverse(PyObject T, string name, UniverseSettings universeSettin
/// The epected resolution of the universe data
/// The market for selected symbols
/// Function delegate that performs selection on the universe data
- public void AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, PyObject selector)
+ public Universe AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, PyObject selector)
{
- AddUniverse(T.CreateType(), securityType, name, resolution, market, UniverseSettings, selector);
+ return AddUniverse(T.CreateType(), securityType, name, resolution, market, UniverseSettings, selector);
}
///
@@ -407,9 +407,9 @@ public void AddUniverse(PyObject T, SecurityType securityType, string name, Reso
/// The market for selected symbols
/// The subscription settings to use for newly created subscriptions
/// Function delegate that performs selection on the universe data
- public void AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject selector)
+ public Universe AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject selector)
{
- AddUniverse(T.CreateType(), securityType, name, resolution, market, universeSettings, selector);
+ return AddUniverse(T.CreateType(), securityType, name, resolution, market, universeSettings, selector);
}
///
@@ -422,7 +422,7 @@ public void AddUniverse(PyObject T, SecurityType securityType, string name, Reso
/// The market for selected symbols
/// The subscription settings to use for newly created subscriptions
/// Function delegate that performs selection on the universe data
- public void AddUniverse(Type dataType, SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject pySelector)
+ public Universe AddUniverse(Type dataType, SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject pySelector)
{
var marketHoursDbEntry = MarketHoursDatabase.GetEntry(market, name, securityType);
var dataTimeZone = marketHoursDbEntry.DataTimeZone;
@@ -432,7 +432,7 @@ public void AddUniverse(Type dataType, SecurityType securityType, string name, R
var selector = pySelector.ConvertToDelegate, object>>();
- AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer, baseDatas =>
+ return AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer, baseDatas =>
{
var result = selector(baseDatas);
return ReferenceEquals(result, Universe.Unchanged)
@@ -442,6 +442,30 @@ public void AddUniverse(Type dataType, SecurityType securityType, string name, R
));
}
+ ///
+ /// Creates a new universe selection model and adds it to the algorithm. This universe selection model will chain to the security
+ /// changes of a given selection output and create a new for each of them
+ ///
+ /// The universe we want to chain an option universe selection model too
+ /// The option filter universe to use
+ public void AddUniverseOptions(PyObject universe, PyObject optionFilter)
+ {
+ Func convertedOptionChain;
+ Universe universeToChain;
+
+ if (universe.TryConvert(out universeToChain) && optionFilter.TryConvertToDelegate(out convertedOptionChain))
+ {
+ AddUniverseOptions(universeToChain, convertedOptionChain);
+ }
+ else
+ {
+ using (Py.GIL())
+ {
+ throw new ArgumentException($"QCAlgorithm.AddChainedEquityOptionUniverseSelectionModel: {universe.Repr()} or {optionFilter.Repr()} is not a valid argument.");
+ }
+ }
+ }
+
///
/// Registers the consolidator to receive automatic updates as well as configures the indicator to receive updates
/// from the consolidator.
diff --git a/Algorithm/QCAlgorithm.Universe.cs b/Algorithm/QCAlgorithm.Universe.cs
index debdff3a6351..f1c460ba297f 100644
--- a/Algorithm/QCAlgorithm.Universe.cs
+++ b/Algorithm/QCAlgorithm.Universe.cs
@@ -16,6 +16,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using QuantConnect.Algorithm.Selection;
using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
@@ -32,6 +33,8 @@ public partial class QCAlgorithm
private readonly object _pendingUniverseAdditionsLock = new object();
private readonly List _pendingUserDefinedUniverseSecurityAdditions = new List();
private readonly List _pendingUniverseAdditions = new List();
+ // this is so that later during 'UniverseSelection.CreateUniverses' we wont remove these user universes from the UniverseManager
+ private readonly HashSet _userAddedUniverses = new HashSet();
///
/// Gets universe manager which holds universes keyed by their symbol
@@ -179,11 +182,13 @@ public UniverseDefinitions Universe
/// Adds the universe to the algorithm
///
/// The universe to be added
- public void AddUniverse(Universe universe)
+ public Universe AddUniverse(Universe universe)
{
// The universe will be added at the end of time step, same as the AddData user defined universes.
// This is required to be independent of the start and end date set during initialize
_pendingUniverseAdditions.Add(universe);
+ _userAddedUniverses.Add(universe.Configuration.Symbol);
+ return universe;
}
///
@@ -194,9 +199,9 @@ public void AddUniverse(Universe universe)
/// The data type
/// A unique name for this universe
/// Function delegate that performs selection on the universe data
- public void AddUniverse(string name, Func, IEnumerable> selector)
+ public Universe AddUniverse(string name, Func, IEnumerable> selector)
{
- AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
+ return AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
}
///
@@ -207,9 +212,9 @@ public void AddUniverse(string name, Func, IEnumerable
/// The data type
/// A unique name for this universe
/// Function delegate that performs selection on the universe data
- public void AddUniverse(string name, Func, IEnumerable> selector)
+ public Universe AddUniverse(string name, Func, IEnumerable> selector)
{
- AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
+ return AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
}
///
@@ -221,9 +226,9 @@ public void AddUniverse(string name, Func, IEnumerable
/// A unique name for this universe
/// The settings used for securities added by this universe
/// Function delegate that performs selection on the universe data
- public void AddUniverse(string name, UniverseSettings universeSettings, Func, IEnumerable> selector)
+ public Universe AddUniverse(string name, UniverseSettings universeSettings, Func, IEnumerable> selector)
{
- AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
+ return AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
}
///
@@ -235,9 +240,9 @@ public void AddUniverse(string name, UniverseSettings universeSettings, Func<
/// A unique name for this universe
/// The settings used for securities added by this universe
/// Function delegate that performs selection on the universe data
- public void AddUniverse(string name, UniverseSettings universeSettings, Func, IEnumerable> selector)
+ public Universe AddUniverse(string name, UniverseSettings universeSettings, Func, IEnumerable> selector)
{
- AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
+ return AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
}
///
@@ -249,9 +254,9 @@ public void AddUniverse(string name, UniverseSettings universeSettings, Func<
/// A unique name for this universe
/// The epected resolution of the universe data
/// Function delegate that performs selection on the universe data
- public void AddUniverse(string name, Resolution resolution, Func, IEnumerable> selector)
+ public Universe AddUniverse(string name, Resolution resolution, Func, IEnumerable> selector)
{
- AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
+ return AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
}
///
@@ -263,9 +268,9 @@ public void AddUniverse(string name, Resolution resolution, FuncA unique name for this universe
/// The epected resolution of the universe data
/// Function delegate that performs selection on the universe data
- public void AddUniverse(string name, Resolution resolution, Func, IEnumerable> selector)
+ public Universe AddUniverse(string name, Resolution resolution, Func, IEnumerable> selector)
{
- AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
+ return AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
}
///
@@ -278,9 +283,9 @@ public void AddUniverse(string name, Resolution resolution, FuncThe epected resolution of the universe data
/// The settings used for securities added by this universe
/// Function delegate that performs selection on the universe data
- public void AddUniverse(string name, Resolution resolution, UniverseSettings universeSettings, Func, IEnumerable> selector)
+ public Universe AddUniverse(string name, Resolution resolution, UniverseSettings universeSettings, Func, IEnumerable> selector)
{
- AddUniverse(SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
+ return AddUniverse(SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
}
///
@@ -293,9 +298,9 @@ public void AddUniverse(string name, Resolution resolution, UniverseSettings
/// The epected resolution of the universe data
/// The settings used for securities added by this universe
/// Function delegate that performs selection on the universe data
- public void AddUniverse(string name, Resolution resolution, UniverseSettings universeSettings, Func, IEnumerable> selector)
+ public Universe AddUniverse(string name, Resolution resolution, UniverseSettings universeSettings, Func, IEnumerable> selector)
{
- AddUniverse(SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
+ return AddUniverse(SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
}
///
@@ -308,9 +313,9 @@ public void AddUniverse(string name, Resolution resolution, UniverseSettings
/// The epected resolution of the universe data
/// The market for selected symbols
/// Function delegate that performs selection on the universe data
- public void AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, Func, IEnumerable> selector)
+ public Universe AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, Func, IEnumerable> selector)
{
- AddUniverse(securityType, name, resolution, market, UniverseSettings, selector);
+ return AddUniverse(securityType, name, resolution, market, UniverseSettings, selector);
}
///
@@ -323,9 +328,9 @@ public void AddUniverse(SecurityType securityType, string name, Resolution re
/// The epected resolution of the universe data
/// The market for selected symbols
/// Function delegate that performs selection on the universe data
- public void AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, Func, IEnumerable> selector)
+ public Universe AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, Func, IEnumerable> selector)
{
- AddUniverse(securityType, name, resolution, market, UniverseSettings, selector);
+ return AddUniverse(securityType, name, resolution, market, UniverseSettings, selector);
}
///
@@ -338,14 +343,14 @@ public void AddUniverse(SecurityType securityType, string name, Resolution re
/// The market for selected symbols
/// The subscription settings to use for newly created subscriptions
/// Function delegate that performs selection on the universe data
- public void AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func, IEnumerable> selector)
+ public Universe AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func, IEnumerable> selector)
{
var marketHoursDbEntry = MarketHoursDatabase.GetEntry(market, name, securityType);
var dataTimeZone = marketHoursDbEntry.DataTimeZone;
var exchangeTimeZone = marketHoursDbEntry.ExchangeHours.TimeZone;
var symbol = QuantConnect.Symbol.Create(name, securityType, market, baseDataType: typeof(T));
var config = new SubscriptionDataConfig(typeof(T), symbol, resolution, dataTimeZone, exchangeTimeZone, false, false, true, true, isFilteredSubscription: false);
- AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer, d => selector(d.OfType())));
+ return AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer, d => selector(d.OfType())));
}
///
@@ -358,14 +363,14 @@ public void AddUniverse(SecurityType securityType, string name, Resolution re
/// The market for selected symbols
/// The subscription settings to use for newly created subscriptions
/// Function delegate that performs selection on the universe data
- public void AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func, IEnumerable> selector)
+ public Universe AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func, IEnumerable> selector)
{
var marketHoursDbEntry = MarketHoursDatabase.GetEntry(market, name, securityType);
var dataTimeZone = marketHoursDbEntry.DataTimeZone;
var exchangeTimeZone = marketHoursDbEntry.ExchangeHours.TimeZone;
var symbol = QuantConnect.Symbol.Create(name, securityType, market, baseDataType: typeof(T));
var config = new SubscriptionDataConfig(typeof(T), symbol, resolution, dataTimeZone, exchangeTimeZone, false, false, true, true, isFilteredSubscription: false);
- AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer,
+ return AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer,
d => selector(d.OfType()).Select(x => QuantConnect.Symbol.Create(x, securityType, market, baseDataType: typeof(T))))
);
}
@@ -375,9 +380,9 @@ public void AddUniverse(SecurityType securityType, string name, Resolution re
/// will be executed on day changes in the NewYork time zone (
///
/// Defines an initial coarse selection
- public void AddUniverse(Func, IEnumerable> selector)
+ public Universe AddUniverse(Func, IEnumerable> selector)
{
- AddUniverse(new CoarseFundamentalUniverse(UniverseSettings, SecurityInitializer, selector));
+ return AddUniverse(new CoarseFundamentalUniverse(UniverseSettings, SecurityInitializer, selector));
}
///
@@ -386,11 +391,11 @@ public void AddUniverse(Func, IEnumerable
///
/// Defines an initial coarse selection
/// Defines a more detailed selection with access to more data
- public void AddUniverse(Func, IEnumerable> coarseSelector, Func, IEnumerable> fineSelector)
+ public Universe AddUniverse(Func, IEnumerable> coarseSelector, Func, IEnumerable> fineSelector)
{
var coarse = new CoarseFundamentalUniverse(UniverseSettings, SecurityInitializer, coarseSelector);
- AddUniverse(new FineFundamentalFilteredUniverse(coarse, fineSelector));
+ return AddUniverse(new FineFundamentalFilteredUniverse(coarse, fineSelector));
}
///
@@ -399,9 +404,9 @@ public void AddUniverse(Func, IEnumerable
///
/// The universe to be filtered with fine fundamental selection
/// Defines a more detailed selection with access to more data
- public void AddUniverse(Universe universe, Func, IEnumerable> fineSelector)
+ public Universe AddUniverse(Universe universe, Func, IEnumerable> fineSelector)
{
- AddUniverse(new FineFundamentalFilteredUniverse(universe, fineSelector));
+ return AddUniverse(new FineFundamentalFilteredUniverse(universe, fineSelector));
}
///
@@ -410,9 +415,9 @@ public void AddUniverse(Universe universe, Func, IE
///
/// A unique name for this universe
/// Function delegate that accepts a DateTime and returns a collection of string symbols
- public void AddUniverse(string name, Func> selector)
+ public Universe AddUniverse(string name, Func> selector)
{
- AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
+ return AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
}
///
@@ -422,9 +427,9 @@ public void AddUniverse(string name, Func> selecto
/// A unique name for this universe
/// The resolution this universe should be triggered on
/// Function delegate that accepts a DateTime and returns a collection of string symbols
- public void AddUniverse(string name, Resolution resolution, Func> selector)
+ public Universe AddUniverse(string name, Resolution resolution, Func> selector)
{
- AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
+ return AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
}
///
@@ -436,14 +441,25 @@ public void AddUniverse(string name, Resolution resolution, FuncThe market of the universe
/// The subscription settings used for securities added from this universe
/// Function delegate that accepts a DateTime and returns a collection of string symbols
- public void AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func> selector)
+ public Universe AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func> selector)
{
var marketHoursDbEntry = MarketHoursDatabase.GetEntry(market, name, securityType);
var dataTimeZone = marketHoursDbEntry.DataTimeZone;
var exchangeTimeZone = marketHoursDbEntry.ExchangeHours.TimeZone;
var symbol = QuantConnect.Symbol.Create(name, securityType, market);
var config = new SubscriptionDataConfig(typeof(CoarseFundamental), symbol, resolution, dataTimeZone, exchangeTimeZone, false, false, true, isFilteredSubscription: false);
- AddUniverse(new UserDefinedUniverse(config, universeSettings, resolution.ToTimeSpan(), selector));
+ return AddUniverse(new UserDefinedUniverse(config, universeSettings, resolution.ToTimeSpan(), selector));
+ }
+
+ ///
+ /// Creates a new universe selection model and adds it to the algorithm. This universe selection model will chain to the security
+ /// changes of a given selection output and create a new for each of them
+ ///
+ /// The universe we want to chain an option universe selection model too
+ /// The option filter universe to use
+ public void AddUniverseOptions(Universe universe, Func optionFilter)
+ {
+ AddUniverseSelection(new OptionChainedUniverseSelectionModel(universe, optionFilter));
}
///
@@ -501,7 +517,8 @@ private void AddToUserDefinedUniverse(
TimeSpan.Zero),
QuantConnect.Time.MaxTimeSpan,
new List());
- _pendingUniverseAdditions.Add(universe);
+
+ AddUniverse(universe);
}
}
}
diff --git a/Algorithm/QCAlgorithm.cs b/Algorithm/QCAlgorithm.cs
index 107a4f12b8f5..4cdaaa39965d 100644
--- a/Algorithm/QCAlgorithm.cs
+++ b/Algorithm/QCAlgorithm.cs
@@ -45,6 +45,7 @@
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Risk;
using QuantConnect.Algorithm.Framework.Selection;
+using QuantConnect.Algorithm.Selection;
using QuantConnect.Storage;
namespace QuantConnect.Algorithm
@@ -1499,7 +1500,8 @@ public Security AddSecurity(Symbol symbol, Resolution? resolution = null, bool f
{
universe = new FuturesChainUniverse((Future)security, settings);
}
- _pendingUniverseAdditions.Add(universe);
+
+ AddUniverse(universe);
}
return security;
}
@@ -1609,7 +1611,7 @@ public Future AddFutureContract(Symbol symbol, Resolution? resolution = null, bo
/// The new security
public Option AddOptionContract(Symbol symbol, Resolution? resolution = null, bool fillDataForward = true, decimal leverage = Security.NullLeverage)
{
- var configs = SubscriptionManager.SubscriptionDataConfigService.Add(symbol, resolution, fillDataForward);
+ var configs = SubscriptionManager.SubscriptionDataConfigService.Add(symbol, resolution, fillDataForward, dataNormalizationMode:DataNormalizationMode.Raw);
var option = (Option)Securities.CreateSecurity(symbol, configs, leverage);
// add underlying if not present
@@ -1643,8 +1645,26 @@ public Option AddOptionContract(Symbol symbol, Resolution? resolution = null, bo
equity.RefreshDataNormalizationModeProperty();
option.Underlying = equity;
+ Securities.Add(option);
+
+ // get or create the universe
+ var universeSymbol = OptionContractUniverse.CreateSymbol(symbol.ID.Market, symbol.Underlying.SecurityType);
+ Universe universe;
+ if (!UniverseManager.TryGetValue(universeSymbol, out universe))
+ {
+ universe = _pendingUniverseAdditions.FirstOrDefault(u => u.Configuration.Symbol == universeSymbol)
+ ?? AddUniverse(new OptionContractUniverse(new SubscriptionDataConfig(configs.First(), symbol: universeSymbol), UniverseSettings));
+ }
- AddToUserDefinedUniverse(option, configs);
+ // update the universe
+ var optionUniverse = universe as OptionContractUniverse;
+ if (optionUniverse != null)
+ {
+ foreach (var subscriptionDataConfig in configs.Concat(underlyingConfigs))
+ {
+ optionUniverse.Add(subscriptionDataConfig);
+ }
+ }
return option;
}
@@ -1691,6 +1711,17 @@ public Crypto AddCrypto(string ticker, Resolution? resolution = null, string mar
return AddSecurity(SecurityType.Crypto, ticker, resolution, market, fillDataForward, leverage, false);
}
+ ///
+ /// Removes the security with the specified symbol. This will cancel all
+ /// open orders and then liquidate any existing holdings
+ ///
+ /// The symbol of the security to be removed
+ /// Sugar syntax for
+ public bool RemoveOptionContract(Symbol symbol)
+ {
+ return RemoveSecurity(symbol);
+ }
+
///
/// Removes the security with the specified symbol. This will cancel all
/// open orders and then liquidate any existing holdings
@@ -1746,6 +1777,7 @@ public bool RemoveSecurity(Symbol symbol)
// finally, dispose and remove the canonical security from the universe manager
UniverseManager.Remove(symbol);
+ _userAddedUniverses.Remove(symbol);
}
}
else
diff --git a/Algorithm/QuantConnect.Algorithm.csproj b/Algorithm/QuantConnect.Algorithm.csproj
index 3a115938ce9d..1ef461346336 100644
--- a/Algorithm/QuantConnect.Algorithm.csproj
+++ b/Algorithm/QuantConnect.Algorithm.csproj
@@ -179,6 +179,8 @@
+
+
diff --git a/Algorithm/Selection/OptionChainedUniverseSelectionModel.cs b/Algorithm/Selection/OptionChainedUniverseSelectionModel.cs
new file mode 100644
index 000000000000..eeea258dcc42
--- /dev/null
+++ b/Algorithm/Selection/OptionChainedUniverseSelectionModel.cs
@@ -0,0 +1,83 @@
+/*
+ * 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.Linq;
+using QuantConnect.Interfaces;
+using QuantConnect.Securities;
+using System.Collections.Generic;
+using QuantConnect.Data.UniverseSelection;
+using QuantConnect.Algorithm.Framework.Selection;
+
+namespace QuantConnect.Algorithm.Selection
+{
+ ///
+ /// This universe selection model will chain to the security changes of a given selection
+ /// output and create a new for each of them
+ ///
+ public class OptionChainedUniverseSelectionModel : UniverseSelectionModel
+ {
+ private DateTime _nextRefreshTimeUtc;
+ private IEnumerable _currentSymbols;
+ private readonly UniverseSettings _universeSettings;
+ private readonly Func _optionFilter;
+
+ ///
+ /// Gets the next time the framework should invoke the `CreateUniverses` method to refresh the set of universes.
+ ///
+ public override DateTime GetNextRefreshTimeUtc() => _nextRefreshTimeUtc;
+
+ ///
+ /// Creates a new instance of
+ ///
+ /// The universe we want to chain to
+ /// The option filter universe to use
+ /// Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed
+ public OptionChainedUniverseSelectionModel(Universe universe,
+ Func optionFilter,
+ UniverseSettings universeSettings = null)
+ {
+ _optionFilter = optionFilter;
+ _universeSettings = universeSettings;
+ _nextRefreshTimeUtc = DateTime.MaxValue;
+
+ _currentSymbols = Enumerable.Empty();
+ universe.SelectionChanged += (sender, args) =>
+ {
+ // the universe we were watching changed, this will trigger a call to CreateUniverses
+ _nextRefreshTimeUtc = DateTime.MinValue;
+
+ _currentSymbols = ((Universe.SelectionEventArgs)args).CurrentSelection
+ .Select(symbol => Symbol.Create(symbol.Value, SecurityType.Option, symbol.ID.Market, $"?{symbol.Value}"))
+ .ToList();
+ };
+ }
+
+ ///
+ /// Creates the universes for this algorithm. Called once after
+ ///
+ /// The algorithm instance to create universes for
+ /// The universes to be used by the algorithm
+ public override IEnumerable CreateUniverses(QCAlgorithm algorithm)
+ {
+ _nextRefreshTimeUtc = DateTime.MaxValue;
+
+ foreach (var optionSymbol in _currentSymbols)
+ {
+ yield return algorithm.CreateOptionChain(optionSymbol, _optionFilter, _universeSettings);
+ }
+ }
+ }
+}
diff --git a/Algorithm/Selection/OptionContractUniverse.cs b/Algorithm/Selection/OptionContractUniverse.cs
new file mode 100644
index 000000000000..8f3c9d039369
--- /dev/null
+++ b/Algorithm/Selection/OptionContractUniverse.cs
@@ -0,0 +1,97 @@
+/*
+ * 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.Linq;
+using QuantConnect.Data;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using QuantConnect.Data.UniverseSelection;
+
+namespace QuantConnect.Algorithm.Selection
+{
+ ///
+ /// This universe will hold single option contracts and their underlying, managing removals and additions
+ ///
+ public class OptionContractUniverse : UserDefinedUniverse
+ {
+ private readonly HashSet _symbols;
+
+ ///
+ /// Creates a new empty instance
+ ///
+ /// The universe configuration to use
+ /// The universe settings to use
+ public OptionContractUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings)
+ : base(configuration, universeSettings, Time.EndOfTimeTimeSpan,
+ // Argument isn't used since we override 'SelectSymbols'
+ Enumerable.Empty())
+ {
+ _symbols = new HashSet();
+ }
+
+ ///
+ /// Returns the symbols defined by the user for this universe
+ ///
+ /// The current utc time
+ /// The symbols to remain in the universe
+ /// The data that passes the filter
+ public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data)
+ {
+ return _symbols;
+ }
+
+ ///
+ /// Event invocator for the event
+ ///
+ /// The notify collection changed event arguments
+ protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
+ {
+ if (args.Action == NotifyCollectionChangedAction.Remove)
+ {
+ var removedSymbol = (Symbol)args.OldItems[0];
+ _symbols.Remove(removedSymbol);
+
+ // the option has been removed! This can happen when the user manually removed the option contract we remove the underlying
+ if (removedSymbol.SecurityType == SecurityType.Option)
+ {
+ Remove(removedSymbol.Underlying);
+ }
+ }
+ else if (args.Action == NotifyCollectionChangedAction.Add)
+ {
+ // QCAlgorithm.AddOptionContract will add both underlying and option contract
+ _symbols.Add((Symbol)args.NewItems[0]);
+ }
+
+ base.OnCollectionChanged(args);
+ }
+
+ ///
+ /// Creates a user defined universe symbol
+ ///
+ /// The market
+ /// The underlying option security type
+ /// A symbol for user defined universe of the specified security type and market
+ public static Symbol CreateSymbol(string market, SecurityType securityType)
+ {
+ var ticker = $"qc-universe-optioncontract-{securityType.SecurityTypeToLower()}-{market.ToLowerInvariant()}";
+ var underlying = Symbol.Create(ticker, securityType, market);
+ var sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlying.ID, market, 0, 0, 0);
+
+ return new Symbol(sid, ticker);
+ }
+ }
+}
diff --git a/Common/Data/UniverseSelection/FineFundamentalFilteredUniverse.cs b/Common/Data/UniverseSelection/FineFundamentalFilteredUniverse.cs
index 6345e3e596c3..c05170b5e557 100644
--- a/Common/Data/UniverseSelection/FineFundamentalFilteredUniverse.cs
+++ b/Common/Data/UniverseSelection/FineFundamentalFilteredUniverse.cs
@@ -40,6 +40,7 @@ public FineFundamentalFilteredUniverse(Universe universe, Func OnSelectionChanged(((SelectionEventArgs) args).CurrentSelection);
}
///
@@ -52,6 +53,7 @@ public FineFundamentalFilteredUniverse(Universe universe, PyObject fineSelector)
{
var func = fineSelector.ConvertToDelegate, Symbol[]>>();
FineFundamentalUniverse = new FineFundamentalUniverse(universe.UniverseSettings, universe.SecurityInitializer, func);
+ FineFundamentalUniverse.SelectionChanged += (sender, args) => OnSelectionChanged(((SelectionEventArgs)args).CurrentSelection);
}
///
diff --git a/Common/Data/UniverseSelection/FuturesChainUniverse.cs b/Common/Data/UniverseSelection/FuturesChainUniverse.cs
index 6776f3ce1919..75620779ef74 100644
--- a/Common/Data/UniverseSelection/FuturesChainUniverse.cs
+++ b/Common/Data/UniverseSelection/FuturesChainUniverse.cs
@@ -18,6 +18,7 @@
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data.Market;
+using QuantConnect.Interfaces;
using QuantConnect.Securities;
using QuantConnect.Securities.Future;
using QuantConnect.Util;
@@ -29,8 +30,6 @@ namespace QuantConnect.Data.UniverseSelection
///
public class FuturesChainUniverse : Universe
{
- private static readonly IReadOnlyList dataTypes = new[] { TickType.Quote, TickType.Trade, TickType.OpenInterest };
-
private readonly UniverseSettings _universeSettings;
private DateTime _cacheDate;
@@ -150,5 +149,30 @@ public override bool CanRemoveMember(DateTime utcTime, Security security)
}
return false;
}
+
+ ///
+ /// Gets the subscription requests to be added for the specified security
+ ///
+ /// The security to get subscriptions for
+ /// The current time in utc. This is the frontier time of the algorithm
+ /// The max end time
+ /// Instance which implements interface
+ /// All subscriptions required by this security
+ public override IEnumerable GetSubscriptionRequests(Security security, DateTime currentTimeUtc, DateTime maximumEndTimeUtc,
+ ISubscriptionDataConfigService subscriptionService)
+ {
+ if (Future.Symbol.Underlying == security.Symbol)
+ {
+ Future.Underlying = security;
+ }
+ else
+ {
+ // set the underlying security and pricing model from the canonical security
+ var future = (Future)security;
+ future.Underlying = Future.Underlying;
+ }
+
+ return base.GetSubscriptionRequests(security, currentTimeUtc, maximumEndTimeUtc, subscriptionService);
+ }
}
}
diff --git a/Common/Data/UniverseSelection/OptionChainUniverse.cs b/Common/Data/UniverseSelection/OptionChainUniverse.cs
index 92682fd313f8..c45a4abc361e 100644
--- a/Common/Data/UniverseSelection/OptionChainUniverse.cs
+++ b/Common/Data/UniverseSelection/OptionChainUniverse.cs
@@ -54,7 +54,7 @@ public OptionChainUniverse(Option option,
{
Option = option;
_underlyingSymbol = new[] { Option.Symbol.Underlying };
- _universeSettings = universeSettings;
+ _universeSettings = new UniverseSettings(universeSettings) { DataNormalizationMode = DataNormalizationMode.Raw };
_liveMode = liveMode;
_optionFilterUniverse = new OptionFilterUniverse();
}
@@ -75,7 +75,7 @@ public OptionChainUniverse(Option option,
{
Option = option;
_underlyingSymbol = new[] { Option.Symbol.Underlying };
- _universeSettings = universeSettings;
+ _universeSettings = new UniverseSettings(universeSettings) { DataNormalizationMode = DataNormalizationMode.Raw };
_liveMode = liveMode;
_optionFilterUniverse = new OptionFilterUniverse();
}
@@ -244,18 +244,20 @@ public override bool CanRemoveMember(DateTime utcTime, Security security)
public override IEnumerable GetSubscriptionRequests(Security security, DateTime currentTimeUtc, DateTime maximumEndTimeUtc,
ISubscriptionDataConfigService subscriptionService)
{
- var result = subscriptionService.Add(security.Symbol, UniverseSettings.Resolution,
- UniverseSettings.FillForward,
- UniverseSettings.ExtendedMarketHours,
- // force raw data normalization mode for underlying
- dataNormalizationMode: DataNormalizationMode.Raw);
+ if (Option.Symbol.Underlying == security.Symbol)
+ {
+ Option.Underlying = security;
+ security.SetDataNormalizationMode(DataNormalizationMode.Raw);
+ }
+ else
+ {
+ // set the underlying security and pricing model from the canonical security
+ var option = (Option)security;
+ option.Underlying = Option.Underlying;
+ option.PriceModel = Option.PriceModel;
+ }
- return result.Select(config => new SubscriptionRequest(isUniverseSubscription: false,
- universe: this,
- security: security,
- configuration: config,
- startTimeUtc: currentTimeUtc,
- endTimeUtc: maximumEndTimeUtc));
+ return base.GetSubscriptionRequests(security, currentTimeUtc, maximumEndTimeUtc, subscriptionService);
}
}
}
\ No newline at end of file
diff --git a/Common/Data/UniverseSelection/Universe.cs b/Common/Data/UniverseSelection/Universe.cs
index 0d945da28eb4..2e4b011688e8 100644
--- a/Common/Data/UniverseSelection/Universe.cs
+++ b/Common/Data/UniverseSelection/Universe.cs
@@ -51,6 +51,11 @@ public virtual ConcurrentDictionary Securities
private set;
}
+ ///
+ /// Event fired when the universe selection has changed
+ ///
+ public event EventHandler SelectionChanged;
+
///
/// Gets the security type of this universe
///
@@ -190,6 +195,7 @@ public IEnumerable PerformSelection(DateTime utcTime, BaseDataCollection
// select empty set of symbols after dispose requested
if (DisposeRequested)
{
+ OnSelectionChanged();
return Enumerable.Empty();
}
@@ -206,6 +212,8 @@ public IEnumerable PerformSelection(DateTime utcTime, BaseDataCollection
{
return Unchanged;
}
+
+ OnSelectionChanged(selections);
return selections;
}
@@ -339,11 +347,20 @@ public virtual void SetSecurityInitializer(ISecurityInitializer securityInitiali
///
/// Marks this universe as disposed and ready to remove all child subscriptions
///
- public void Dispose()
+ public virtual void Dispose()
{
DisposeRequested = true;
}
+ ///
+ /// Event invocator for the event
+ ///
+ /// The current universe selection
+ protected void OnSelectionChanged(HashSet selection = null)
+ {
+ SelectionChanged?.Invoke(this, new SelectionEventArgs(selection ?? new HashSet()));
+ }
+
///
/// Provides a value to indicate that no changes should be made to the universe.
/// This value is intended to be returned by reference via
@@ -394,5 +411,24 @@ public Member(DateTime added, Security security)
Security = security;
}
}
+
+ ///
+ /// Event fired when the universe selection changes
+ ///
+ public class SelectionEventArgs : EventArgs
+ {
+ ///
+ /// The current universe selection
+ ///
+ public HashSet CurrentSelection { get; }
+
+ ///
+ /// Creates a new instance
+ ///
+ public SelectionEventArgs(HashSet currentSelection)
+ {
+ CurrentSelection = currentSelection;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Common/Data/UniverseSelection/UniverseSettings.cs b/Common/Data/UniverseSelection/UniverseSettings.cs
index 65ae5506c2b2..6aab09ef8d6f 100644
--- a/Common/Data/UniverseSelection/UniverseSettings.cs
+++ b/Common/Data/UniverseSelection/UniverseSettings.cs
@@ -74,5 +74,18 @@ public UniverseSettings(Resolution resolution, decimal leverage, bool fillForwar
MinimumTimeInUniverse = minimumTimeInUniverse;
DataNormalizationMode = dataNormalizationMode;
}
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ public UniverseSettings(UniverseSettings universeSettings)
+ {
+ Resolution = universeSettings.Resolution;
+ Leverage = universeSettings.Leverage;
+ FillForward = universeSettings.FillForward;
+ ExtendedMarketHours = universeSettings.ExtendedMarketHours;
+ MinimumTimeInUniverse = universeSettings.MinimumTimeInUniverse;
+ DataNormalizationMode = universeSettings.DataNormalizationMode;
+ }
}
}
\ No newline at end of file
diff --git a/Common/Data/UniverseSelection/UserDefinedUniverse.cs b/Common/Data/UniverseSelection/UserDefinedUniverse.cs
index dae655270b10..57fe47d51600 100644
--- a/Common/Data/UniverseSelection/UserDefinedUniverse.cs
+++ b/Common/Data/UniverseSelection/UserDefinedUniverse.cs
@@ -239,7 +239,7 @@ public bool Remove(Symbol symbol)
///
/// Returns the symbols defined by the user for this universe
///
- /// The curren utc time
+ /// The current utc time
/// The symbols to remain in the universe
/// The data that passes the filter
public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data)
@@ -278,8 +278,7 @@ public virtual IEnumerable GetTriggerTimes(DateTime startTimeUtc, Date
/// The notify collection changed event arguments
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
- var handler = CollectionChanged;
- if (handler != null) handler(this, e);
+ CollectionChanged?.Invoke(this, e);
}
///
diff --git a/Common/Extensions.cs b/Common/Extensions.cs
index 0ef1a554b990..920939dba681 100644
--- a/Common/Extensions.cs
+++ b/Common/Extensions.cs
@@ -48,6 +48,8 @@
using Timer = System.Timers.Timer;
using static QuantConnect.StringExtensions;
using System.Runtime.CompilerServices;
+using QuantConnect.Data.Auxiliary;
+using QuantConnect.Securities.Option;
namespace QuantConnect
{
@@ -2272,13 +2274,69 @@ public static OrderDirection GetOrderDirection(decimal quantity)
var sign = Math.Sign(quantity);
switch (sign)
{
- case 1: return OrderDirection.Buy;
- case 0: return OrderDirection.Hold;
+ case 1: return OrderDirection.Buy;
+ case 0: return OrderDirection.Hold;
case -1: return OrderDirection.Sell;
- default: throw new ApplicationException(
- $"The skies are falling and the oceans are rising! Math.Sign({quantity}) returned {sign} :/"
- );
+ default:
+ throw new ApplicationException(
+ $"The skies are falling and the oceans are rising! Math.Sign({quantity}) returned {sign} :/"
+ );
+ }
+ }
+
+ ///
+ /// Creates a for a given symbol
+ ///
+ /// The algorithm instance to create universes for
+ /// Symbol of the option
+ /// The option filter to use
+ /// The universe settings, will use algorithm settings if null
+ /// for the given symbol
+ public static OptionChainUniverse CreateOptionChain(this IAlgorithm algorithm, Symbol symbol, Func filter, UniverseSettings universeSettings = null)
+ {
+ if (symbol.SecurityType != SecurityType.Option)
+ {
+ throw new ArgumentException("CreateOptionChain requires an option symbol.");
}
+
+ // rewrite non-canonical symbols to be canonical
+ var market = symbol.ID.Market;
+ var underlying = symbol.Underlying;
+ if (!symbol.IsCanonical())
+ {
+ var alias = $"?{underlying.Value}";
+ symbol = Symbol.Create(underlying.Value, SecurityType.Option, market, alias);
+ }
+
+ // resolve defaults if not specified
+ var settings = universeSettings ?? algorithm.UniverseSettings;
+
+ // create canonical security object, but don't duplicate if it already exists
+ Security security;
+ Option optionChain;
+ if (!algorithm.Securities.TryGetValue(symbol, out security))
+ {
+ var config = algorithm.SubscriptionManager.SubscriptionDataConfigService.Add(
+ typeof(ZipEntryName),
+ symbol,
+ settings.Resolution,
+ settings.FillForward,
+ settings.ExtendedMarketHours,
+ false);
+ optionChain = (Option)algorithm.Securities.CreateSecurity(symbol, config, settings.Leverage, false);
+ }
+ else
+ {
+ optionChain = (Option)security;
+ }
+
+ // set the option chain contract filter function
+ optionChain.SetFilter(filter);
+
+ // force option chain security to not be directly tradable AFTER it's configured to ensure it's not overwritten
+ optionChain.IsTradable = false;
+
+ return new OptionChainUniverse(optionChain, settings, algorithm.LiveMode);
}
}
}
diff --git a/Common/Securities/Option/OptionFilterUniverse.cs b/Common/Securities/Option/OptionFilterUniverse.cs
index 9ad1d50015b2..e290ff45107c 100644
--- a/Common/Securities/Option/OptionFilterUniverse.cs
+++ b/Common/Securities/Option/OptionFilterUniverse.cs
@@ -19,6 +19,7 @@
using QuantConnect.Data;
using System.Linq;
using System.Collections;
+using Python.Runtime;
using QuantConnect.Securities.Option;
namespace QuantConnect.Securities
@@ -342,6 +343,17 @@ public OptionFilterUniverse Expiration(int minExpiryDays, int maxExpiryDays)
return Expiration(TimeSpan.FromDays(minExpiryDays), TimeSpan.FromDays(maxExpiryDays));
}
+ ///
+ /// Explicitly sets the selected contract symbols for this universe.
+ /// This overrides and and all other methods of selecting symbols assuming it is called last.
+ ///
+ /// The option contract symbol objects to select
+ public OptionFilterUniverse Contracts(PyObject contracts)
+ {
+ _allSymbols = contracts.ConvertToSymbolEnumerable();
+ return this;
+ }
+
///
/// Explicitly sets the selected contract symbols for this universe.
/// This overrides and and all other methods of selecting symbols assuming it is called last.
diff --git a/Engine/AlgorithmManager.cs b/Engine/AlgorithmManager.cs
index 6b643260d58f..6ffe70b29033 100644
--- a/Engine/AlgorithmManager.cs
+++ b/Engine/AlgorithmManager.cs
@@ -1003,13 +1003,22 @@ private static void HandleDelistedSymbols(IAlgorithm algorithm, Delistings newDe
security.IsTradable = false;
security.IsDelisted = true;
+ // the subscription are getting removed from the data feed because they end
// remove security from all universes
foreach (var ukvp in algorithm.UniverseManager)
{
var universe = ukvp.Value;
if (universe.ContainsMember(security.Symbol))
{
- universe.RemoveMember(algorithm.UtcTime, security);
+ var userUniverse = universe as UserDefinedUniverse;
+ if (userUniverse != null)
+ {
+ userUniverse.Remove(security.Symbol);
+ }
+ else
+ {
+ universe.RemoveMember(algorithm.UtcTime, security);
+ }
}
}
diff --git a/Engine/DataFeeds/DataManager.cs b/Engine/DataFeeds/DataManager.cs
index 0da10f139d71..321ceb9e9013 100644
--- a/Engine/DataFeeds/DataManager.cs
+++ b/Engine/DataFeeds/DataManager.cs
@@ -179,7 +179,9 @@ public bool AddSubscription(SubscriptionRequest request)
if (DataFeedSubscriptions.TryGetValue(request.Configuration, out subscription))
{
// duplicate subscription request
- return subscription.AddSubscriptionRequest(request);
+ subscription.AddSubscriptionRequest(request);
+ // only result true if the existing subscription is internal, we actually added something from the users perspective
+ return subscription.Configuration.IsInternalFeed;
}
// before adding the configuration to the data feed let's assert it's valid
@@ -259,6 +261,14 @@ public bool RemoveSubscription(SubscriptionDataConfig configuration, Universe un
return true;
}
}
+ else if (universe != null)
+ {
+ // a universe requested removal of a subscription which wasn't present anymore, this can happen when a subscription ends
+ // it will get removed from the data feed subscription list, but the configuration will remain until the universe removes it
+ // why? the effect I found is that the fill models are using these subscriptions to determine which data they could use
+ SubscriptionDataConfig config;
+ _subscriptionManagerSubscriptions.TryRemove(configuration, out config);
+ }
return false;
}
@@ -342,6 +352,7 @@ public SubscriptionDataConfig SubscriptionManagerGetOrAdd(SubscriptionDataConfig
/// The owning the configuration to remove
private void RemoveSubscriptionDataConfig(Subscription subscription)
{
+ // the subscription could of ended but might still be part of the universe
if (subscription.RemovedFromUniverse.Value)
{
SubscriptionDataConfig config;
diff --git a/Engine/DataFeeds/SubscriptionSynchronizer.cs b/Engine/DataFeeds/SubscriptionSynchronizer.cs
index 20501546582b..9c39d872d0e6 100644
--- a/Engine/DataFeeds/SubscriptionSynchronizer.cs
+++ b/Engine/DataFeeds/SubscriptionSynchronizer.cs
@@ -237,8 +237,7 @@ public IEnumerable Sync(IEnumerable subscriptions,
///
protected virtual void OnSubscriptionFinished(Subscription subscription)
{
- var handler = SubscriptionFinished;
- if (handler != null) handler(this, subscription);
+ SubscriptionFinished?.Invoke(this, subscription);
}
///
diff --git a/Engine/DataFeeds/UniverseSelection.cs b/Engine/DataFeeds/UniverseSelection.cs
index 6f2c55d95c45..91b7f580fe57 100644
--- a/Engine/DataFeeds/UniverseSelection.cs
+++ b/Engine/DataFeeds/UniverseSelection.cs
@@ -26,8 +26,6 @@
using QuantConnect.Securities;
using QuantConnect.Util;
using QuantConnect.Data.Fundamental;
-using QuantConnect.Securities.Future;
-using QuantConnect.Securities.Option;
namespace QuantConnect.Lean.Engine.DataFeeds
{
@@ -318,12 +316,10 @@ public SecurityChanges ApplyUniverseSelection(Universe universe, DateTime dateTi
security = _securityService.CreateSecurity(symbol, configs, universe.UniverseSettings.Leverage, symbol.ID.SecurityType == SecurityType.Option);
pendingAdditions.Add(symbol, security);
-
- SetUnderlyingSecurity(universe, security);
}
var addedSubscription = false;
-
+ var dataFeedAdded = false;
foreach (var request in universe.GetSubscriptionRequests(security, dateTimeUtc, algorithmEndDateUtc,
_algorithm.SubscriptionManager.SubscriptionDataConfigService))
{
@@ -341,7 +337,9 @@ public SecurityChanges ApplyUniverseSelection(Universe universe, DateTime dateTi
_dataManager.RemoveSubscription(toRemove);
}
- _dataManager.AddSubscription(request);
+ // 'dataFeedAdded' will help us notify the user for security changes only once per non internal subscription
+ // for example two universes adding the sample configuration, we don't want two notifications
+ dataFeedAdded = _dataManager.AddSubscription(request);
// only update our security changes if we actually added data
if (!request.IsUniverseSubscription)
@@ -356,7 +354,7 @@ public SecurityChanges ApplyUniverseSelection(Universe universe, DateTime dateTi
{
var addedMember = universe.AddMember(dateTimeUtc, security);
- if (addedMember)
+ if (addedMember && dataFeedAdded)
{
additions.Add(security);
}
@@ -495,36 +493,5 @@ private void RemoveSecurityFromUniverse(
SymbolCache.TryRemove(member.Symbol);
}
}
-
- ///
- /// This method sets the underlying security for and
- ///
- private void SetUnderlyingSecurity(Universe universe, Security security)
- {
- var optionChainUniverse = universe as OptionChainUniverse;
- var futureChainUniverse = universe as FuturesChainUniverse;
- if (optionChainUniverse != null)
- {
- if (!security.Symbol.HasUnderlying)
- {
- // create the underlying w/ raw mode
- security.SetDataNormalizationMode(DataNormalizationMode.Raw);
- optionChainUniverse.Option.Underlying = security;
- }
- else
- {
- // set the underlying security and pricing model from the canonical security
- var option = (Option)security;
- option.Underlying = optionChainUniverse.Option.Underlying;
- option.PriceModel = optionChainUniverse.Option.PriceModel;
- }
- }
- else if (futureChainUniverse != null)
- {
- // set the underlying security and pricing model from the canonical security
- var future = (Future)security;
- future.Underlying = futureChainUniverse.Future.Underlying;
- }
- }
}
}
\ No newline at end of file
diff --git a/Tests/Engine/DataFeeds/MockDataFeed.cs b/Tests/Engine/DataFeeds/MockDataFeed.cs
index 584c4aae4095..280678c59bd8 100644
--- a/Tests/Engine/DataFeeds/MockDataFeed.cs
+++ b/Tests/Engine/DataFeeds/MockDataFeed.cs
@@ -14,6 +14,7 @@
*
*/
+using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.Results;
@@ -23,6 +24,7 @@ namespace QuantConnect.Lean.Engine.DataFeeds
{
public class MockDataFeed : IDataFeed
{
+ private List _dummyData = new List();
public bool IsActive { get; }
public void Initialize(
@@ -41,7 +43,10 @@ IDataChannelProvider channelProvider
public Subscription CreateSubscription(SubscriptionRequest request)
{
- return null;
+ var offsetProvider = new TimeZoneOffsetProvider(request.Configuration.ExchangeTimeZone,
+ request.StartTimeUtc,
+ request.EndTimeUtc);
+ return new Subscription(request, _dummyData.GetEnumerator(), offsetProvider);
}
public void RemoveSubscription(Subscription subscription)