forked from QuantConnect/Lean
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OptionChain and OptionContract improvements (QuantConnect#4804)
* OptionChain and OptionContract improvements - QCAlgorithm.AddUniverse will return the added Universe instance. - Adding new OptionChainedUniverseSelectionModel will monitor a Universe changes and will spwan new OptionChainUniverse from it's selections. Adding regression test Py/C#. - Adding new OptionContractUniverse that will own option contracts and their underlying symbol. Adding regression test - Fix double notification for security changes, bug seen in updated UniverseSelectionRegressionAlgorithm - Remove UniverseSelection special handling for Option and Future chains - Fix DataManager not removing SubscriptionDataConfigs for Subscriptions which finished before being removed from the universe - Refactor detection of user added Universe so that they do not get removed after calling the UniverseSelectionModel * Add check for option underlying price is set * Address reviews - Adding python regression algorithm for `AddOptionContractFromUniverseRegressionAlgorithm` and `AddOptionContractExpiresRegressionAlgorithm` - Rename QCAlgorithm new api method to `AddChainedOptionUniverse` * Fix universe refresh bug - Fix bug where a universe selection refresh would cause option or future chain universes from being removed. Adding regression algorithm reproducing the issue. * Rename new option universe Algorithm API method - Rename new option universe Algorith API method from AddChainedOptionUniverse to AddUniverseOptions - Rebase and update regression test order hash because of option expiration message changed
- Loading branch information
1 parent
58241bd
commit c058c5d
Showing
31 changed files
with
1,523 additions
and
254 deletions.
There are no files selected for viewing
164 changes: 164 additions & 0 deletions
164
Algorithm.CSharp/AddOptionContractExpiresRegressionAlgorithm.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
{ | ||
/// <summary> | ||
/// We add an option contract using <see cref="QCAlgorithm.AddOptionContract"/> and place a trade and wait till it expires | ||
/// later will liquidate the resulting equity position and assert both option and underlying get removed | ||
/// </summary> | ||
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<string> { "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"); | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. | ||
/// </summary> | ||
public bool CanRunLocally { get; } = true; | ||
|
||
/// <summary> | ||
/// This is used by the regression test system to indicate which languages this algorithm is written in. | ||
/// </summary> | ||
public Language[] Languages { get; } = { Language.CSharp, Language.Python }; | ||
|
||
/// <summary> | ||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm | ||
/// </summary> | ||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string> | ||
{ | ||
{"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"} | ||
}; | ||
} | ||
} |
216 changes: 216 additions & 0 deletions
216
Algorithm.CSharp/AddOptionContractFromUniverseRegressionAlgorithm.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
{ | ||
/// <summary> | ||
/// We add an option contract using <see cref="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 <see cref="QCAlgorithm.RemoveOptionContract"/> and expect both option and underlying to be removed. | ||
/// </summary> | ||
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}"); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. | ||
/// </summary> | ||
public bool CanRunLocally { get; } = true; | ||
|
||
/// <summary> | ||
/// This is used by the regression test system to indicate which languages this algorithm is written in. | ||
/// </summary> | ||
public Language[] Languages { get; } = { Language.CSharp, Language.Python }; | ||
|
||
/// <summary> | ||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm | ||
/// </summary> | ||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string> | ||
{ | ||
{"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"} | ||
}; | ||
} | ||
} |
Oops, something went wrong.