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;
		}

	}
}