Overall Statistics |
Total Trades 3 Average Win 0.03% Average Loss 0% Compounding Annual Return 0.802% Drawdown 0.300% Expectancy 0 Net Profit 0.120% Sharpe Ratio 0.913 Probabilistic Sharpe Ratio 48.570% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha -0.011 Beta 0.124 Annual Standard Deviation 0.008 Annual Variance 0 Information Ratio -2.516 Tracking Error 0.055 Treynor Ratio 0.061 Total Fees $3.00 |
using System; using System.Linq; using QuantConnect.Data; using QuantConnect.Data.Market; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Option; using System.Collections.Generic; namespace QuantConnect.Algorithm.CSharp { /// <summary> /// Test dynamic adjustment to the option universe to try to improve options algo backtest performance /// Test the following sequence: /// 1. Add option universe using underlying symbol /// 2. Set universe filter to required strike range and expiration /// 3. Select option from the Slice OptionChain (eg. by Expiration, Delta) /// 4. Reduce the option universe using SetFilter to the minimum subscribed set /// 5. Add the selected option to the subscription /// 6. For next trade: /// 7. Close the current option position and unsubscribe the selected option /// 8. Repeat process from step 2 /// /// This sequence shows an issue that the minimised option universe filter is only applied after the /// monthly options expiration has occurred, instead of being applied immediately from the next Slice. /// /// </summary> public class OptionUniverseTestAlgorithm : QCAlgorithm { String _underlyingSymbolName; Symbol _equitySymbol=null; Symbol _optionContractSymbol=null; OptionContract _optionContract; Option _option = null; Option _optionUniverse; Symbol _optionUniverseSymbol=null; bool _setOptionUniverseFilterOnly=false; TimeSpan timeSpanSelect50DaysToExpiry = new TimeSpan(50, 0, 0, 0); public override void Initialize() { SetStartDate(2017, 6, 1); SetEndDate(2017, 7, 25); SetCash(100000); _underlyingSymbolName = "SPY"; var equity = AddEquity(_underlyingSymbolName, Resolution.Minute); equity.SetDataNormalizationMode(DataNormalizationMode.Raw); _equitySymbol = equity.Symbol; AddOptionUniverseForUnderlying(isSetFilterOnly:false); SetWarmup(TimeSpan.FromDays(30)); } public override void OnData(Slice slice) { if (IsWarmingUp) { return; } if (_optionContractSymbol != null && !IsMarketOpen(_optionContractSymbol)) { return; } if (_optionUniverseSymbol != null && !IsMarketOpen(_optionUniverseSymbol)) { return; } if (Time.Hour==9 && (Time.Minute==50 || Time.Minute==31 || Time.Minute==32) ) { Debug($"{Time}: OnData: slice.Count={slice.Count}"); } if (_optionContractSymbol==null && _optionUniverseSymbol==null) { AddOptionUniverseForUnderlying(isSetFilterOnly:false); return; } if (_optionContractSymbol==null && _setOptionUniverseFilterOnly) { // Update option universe filter only... AddOptionUniverseForUnderlying(isSetFilterOnly:_setOptionUniverseFilterOnly); _setOptionUniverseFilterOnly = false; return; } if (_optionContractSymbol!=null && _option==null) { // Subscribe only to the specific selected contract... Debug($"AddOptionContract:{_optionContractSymbol}"); _option = AddOptionContract(_optionContractSymbol, Resolution.Minute); _option.PriceModel = OptionPriceModels.BjerksundStensland(); Debug($"_option.Symbol={_option.Symbol}"); return; } if (_optionContractSymbol!=null) { if ((_optionContract.Expiry - Time).TotalDays < 5) { Debug($"{Time}: OnData: INFO: Sell : rollover as DTE < 5"); MarketOrder(_optionContractSymbol, -1); Debug($"RemoveSecurity:{_option.Symbol}"); RemoveSecurity(_option.Symbol); _optionContractSymbol = null; _option = null; _setOptionUniverseFilterOnly = true; return; } } if (!Portfolio.Invested && _optionContractSymbol!=null) { MarketOrder(_optionContractSymbol, 1); Debug($"{Time}: Buy: optionContractSymbol={_optionContractSymbol}"); } if (Time.Hour==15 && Time.Minute==45) { if (_optionContractSymbol != null) { foreach (var kvp in slice.OptionChains) { OptionChain chain = kvp.Value; Debug($"chain.Contracts.Count={chain.Contracts.Count}"); LogOptionChain(chain); OptionContract contract = chain .Where(x => x.Symbol == _optionContractSymbol).FirstOrDefault(); LogOptionContract(contract); } } } if (_optionUniverseSymbol!=null && !Portfolio.Invested && _optionContractSymbol==null && Time.Hour==15 && Time.Minute==45 ) { Debug($"{Time}: OnData: slice.Count={slice.Count}"); foreach (var kvp in slice) { Debug($"---> slice: {Time}, k={kvp.Key.Value}, v={kvp.Value}"); } Debug($"{Time}: slice.OptionChains.Count={slice.OptionChains.Count}"); OptionChain chain=null; if (!slice.OptionChains.TryGetValue(_optionUniverseSymbol, out chain)) { Debug($"{Time}: ERROR: No chain found for {_optionUniverseSymbol}"); return; } Debug($"{Time}: chain.Contracts.Count={chain.Contracts.Count}"); Debug($"{Time}: chain.FilteredContracts.Count={chain.FilteredContracts.Count}"); LogOptionChain(chain); // Select TimeSpan timeSpanSelect = timeSpanSelect50DaysToExpiry; _optionContract = chain .OrderBy(x => Math.Abs(timeSpanSelect.Days - x.Expiry.Subtract(Time).Days)) .Where(x => x.Right == OptionRight.Call) .FirstOrDefault(); if (_optionContract == null) { Debug($"{Time}: ERROR: Closest to {timeSpanSelect.Days} DTE Option selection failed: No option found in chain: optionContract==null"); return; } Debug($"{Time}: INFO: Closest to {timeSpanSelect.Days} DTE selection found DTE={(_optionContract.Expiry - Time)} symbol={_optionContract.Symbol}"); if ((_optionContract.Expiry - Time).TotalDays < 15) { Debug($"{Time}: INFO: Skipping Option selection as DTE<15 : found DTE={(_optionContract.Expiry - Time)} Symbol={_optionContract.Symbol} Delta={_optionContract.Greeks.Delta:0.00}" ); return; } Debug($"{Time}: INFO: Valid Option selection found DTE={(_optionContract.Expiry - Time)} Symbol={_optionContract.Symbol} Delta={_optionContract.Greeks.Delta:0.00}" ); _optionContractSymbol = _optionContract.Symbol; Debug($"{Time}: MinimiseFilterOptionUniverse"); _optionUniverse.SetFilter(0,0,TimeSpan.Zero, TimeSpan.FromDays(0)); } } // ------------------------------------------------------------------------------ void LogOptionChain(OptionChain chain) { foreach (var contract in chain) { LogOptionContract(contract); } } // ------------------------------------------------------------------------------ void LogOptionContract(OptionContract contract) { { Debug( String.Format(@"{0},Time={1} ULPrice={2} Last={3} d={4:0.000} IV={5:0.000}", contract.Symbol.Value, contract.Time, contract.UnderlyingLastPrice, contract.LastPrice, contract.Greeks.Delta, contract.ImpliedVolatility )); } } // ------------------------------------------------------------------------------ void AddOptionUniverseForUnderlying(bool isSetFilterOnly) { if (!isSetFilterOnly) { _optionUniverse = AddOption(_underlyingSymbolName); _optionUniverseSymbol = _optionUniverse.Symbol; _optionUniverse.PriceModel = OptionPriceModels.BjerksundStensland(); Debug($"{Time}: AddOption: _optionUniverseSymbol={_optionUniverseSymbol}"); } Debug($"{Time}: AddOptionUniverseForUnderlying: SetFilter"); _optionUniverse.SetFilter(u => { return u.Expiration(TimeSpan.Zero, TimeSpan.FromDays(50))//some issue requires this to be set to 80 and not 50 to get the next monthly options? .Strikes(-1, +1) .Contracts(c => c.Where(s => s.ID.OptionRight == OptionRight.Call)); }); return; } } }