Overall Statistics
Total Trades
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Net Profit
0%
Sharpe Ratio
0
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
0
Tracking Error
0
Treynor Ratio
0
Total Fees
$0.00
using System;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;

namespace QuantConnect.Algorithm.CSharp
{
    /// <summary>
    /// Fixed moneyness volatility skew for a given (single) underlying.
    /// </summary>
    public class OptionsHistoryAlgorithm : QCAlgorithm
    {
        private const string UnderlyingTicker = "GLD";
        public readonly Symbol Underlying = QuantConnect.Symbol.Create(UnderlyingTicker, SecurityType.Equity, Market.USA);
        public readonly Symbol OptionSymbol = QuantConnect.Symbol.Create(UnderlyingTicker, SecurityType.Option, Market.USA);
        
        Resolution resolution = Resolution.Minute; //default
        private int tradingDayCount = 0;
        private bool debugLog = true; // more detailed logging
        
        // selected expiration
        private int optionExpDays = 30;
        
        // simple moneyness and option types for fixed strikes to calculate skew
        private OptionRight opttype1 = OptionRight.Put;
        private decimal mny1 = 0.90m; // < 100%
        private OptionRight opttype2 = OptionRight.Call;
        private decimal mny2 = 1.10m; // >= 100%
        
        // filter (mny +/- mnyAllowedWindow), increase for sparse strikes
        private decimal mnyAllowedWindow = 0.02m; //0.01m;
        
        // enforcing/not identical strikes between front and back contracts
        private bool forceSameStrikes = true;
        
        
        public override void Initialize()
        {
            SetStartDate(2011, 8, 31);
            SetEndDate(2011, 11, 1);
            
            SetCash(1000000);

            var equity = AddEquity(UnderlyingTicker, resolution);
            var option = AddOption(UnderlyingTicker, resolution);

            equity.SetDataNormalizationMode(DataNormalizationMode.Raw);
            
            // specify pricing model to get IV calculated
            option.PriceModel = OptionPriceModels.CrankNicolsonFD();
            //option.EnableGreekApproximation = true;
            
            // option chain pre-filtering: mny1 < 1, mny2 >= 1
            option.SetFilter(universe => from symbol in universe
              .Expiration(TimeSpan.Zero, TimeSpan.FromDays(optionExpDays + optionExpDays))
              where
                (symbol.ID.OptionRight == opttype1 && // strike < underlying
                 universe.Underlying.Price - symbol.ID.StrikePrice <= (1.0m - mny1 + mnyAllowedWindow)*universe.Underlying.Price &&
                 universe.Underlying.Price - symbol.ID.StrikePrice >= (1.0m - mny1 - mnyAllowedWindow)*universe.Underlying.Price
                ) || (
                 symbol.ID.OptionRight == opttype2 && // strike >= underlying
                 symbol.ID.StrikePrice - universe.Underlying.Price <= (mny2 - 1.0m + mnyAllowedWindow)*universe.Underlying.Price &&
                 symbol.ID.StrikePrice - universe.Underlying.Price >= (mny2 - 1.0m - mnyAllowedWindow)*universe.Underlying.Price
                )
              select symbol);
            
            // daily log at a specific time
            if (debugLog)
            {
            	Log("Time, UnderPrice, strmny1, strmny2, expFront, expBack, iv1front, iv1back, iv2front, iv2back, skew");
            }
            else
            {
            	Log("Time, UnderPrice, iv1interp, iv2interp, skew");
            }
        }
        
        
        // Trading day counter that may be reset in some other event handler logic.
        public override void OnEndOfDay()
        {
        	tradingDayCount++;
        }
        
        
        // Data updates for registered tickers.
        public override void OnData(Slice slice)
        {
            // Specific time of each trading day
            if (tradingDayCount > 0 && Time.TimeOfDay >= System.TimeSpan.Parse("15:59:00"))
            {
            	if (slice != null)
            	{
            		OptionSelLog(slice);
            		
            		// current trading day specific-time updates are complete
            		tradingDayCount = 0;
            	}
            }
        }
        
        
        // Selected option characteristics
        private void OptionSelLog(Slice slice)
        {
        	if (slice == null) { return; }
        	
        	OptionChain chain;
        	bool useFrontContract = true;
        	bool useBackContract = true;
        	
        	if (slice.OptionChains.TryGetValue(OptionSymbol, out chain))
        	{
        		// identify nearest expiration
        		var ofront = chain
        		  .Where(x => x.Right == opttype1)
        		  .Where(x => (x.Expiry - Time).Days < optionExpDays) // front
        		  .OrderBy(x => Math.Abs((x.Expiry - Time).Days - optionExpDays))
        		  .FirstOrDefault();
        		if (ofront == null)
        		{
        			Log("No matching front expiration");
        			return;
        		}
        		var expFront = ofront.Expiry;
        		var expBack = expFront;
        		var oback = ofront;
        		var o1front = ofront;
        		var o1back = ofront;
        		var o2front = ofront;
        		var o2back = ofront;
        		
        		if (Math.Abs((expFront - Time).Days) < 7)
        		{
        			// front expiration is too close;
        			// use next expiration values
        			useFrontContract = false;
        		}
        		
        		if (Math.Abs((expFront - Time).Days) > 21)
        		{
        			// back contracts would be too far ahead;
        			// use front expiration values
        			useBackContract = false;
        		}
        		
        		if (!useFrontContract)
        		{
        			// identify next closest expiration in place of the front contract
        			ofront = chain
        			  .Where(x => x.Right == opttype1)
        			  .Where(x => x.Expiry > expFront)
        		      .OrderBy(x => Math.Abs((x.Expiry - Time).Days - optionExpDays))
        		      .FirstOrDefault();
        		    if (ofront == null)
        		    {
        		    	Log("No matching second nearest expiration");
        		    	return;
        		    }
        		    expFront = ofront.Expiry;
        		    expBack = ofront.Expiry;
        		    useBackContract = false; // already used it as nearest suitable expiration
        		}
        		
        		// identify closest contracts to fixed strikes
        		o1front = chain
        		  .Where(x => x.Expiry == expFront)
        		  .Where(x => x.Right == opttype1)
        		  .Where(x => x.BidPrice > 0.01m && x.AskPrice > 0.01m) // non-zero IV
        		  .OrderBy(x => Math.Abs(mny1*chain.Underlying.Price - x.Strike))
        		  .FirstOrDefault();
        		if (o1front == null)
        		{
        			Log("No matching contract for mny1");
        			return;
        		}
        		o2front = chain
        		  .Where(x => x.Expiry == expFront)
        		  .Where(x => x.Right == opttype2)
        		  .Where(x => x.BidPrice > 0.01m && x.AskPrice > 0.01m) // non-zero IV
        		  .OrderBy(x => Math.Abs(mny2*chain.Underlying.Price - x.Strike))
        		  .FirstOrDefault();
        		if (o2front == null)
        		{
        			Log("No matching contract for mny2");
        			return;
        		}
        		
        		// for now, in case !useBackContract
        		o1back = o1front;
        		o2back = o2front;
        		
        		if (useBackContract)
        		{
        			// identify next closest expiration
        			oback = chain
        			  .Where(x => x.Right == opttype1)
        			  .Where(x => (x.Expiry - Time).Days > optionExpDays) // back
        		      .OrderBy(x => Math.Abs((x.Expiry - Time).Days - optionExpDays))
        		      .FirstOrDefault();
        		    if (oback == null)
        		    {
        		    	Log("No matching back contract");
        		    	return;
        		    }
        		    expBack = oback.Expiry;
        		    
        		    if (forceSameStrikes)
        		    {
        		    	// enforcing identical strikes (to front ones) on back contracts
        		    	o1back = chain
        		          .Where(x => x.Expiry == expBack)
        		          .Where(x => x.Right == opttype1)
        		          .Where(x => x.BidPrice > 0.01m && x.AskPrice > 0.01m) // non-zero IV
        		          .Where(x => Math.Abs(x.Strike - o1front.Strike) < 0.01m) // match the strikes
        		          .FirstOrDefault();
        		        o2back = chain
        		          .Where(x => x.Expiry == expBack)
        		          .Where(x => x.Right == opttype2)
        		          .Where(x => x.BidPrice > 0.01m && x.AskPrice > 0.01m) // non-zero IV
        		          .Where(x => Math.Abs(x.Strike - o2front.Strike) < 0.01m) // match the strikes
        		          .FirstOrDefault();
        		    }
        		    else
        		    {
        		    	o1back = chain
        		          .Where(x => x.Expiry == expBack)
        		          .Where(x => x.Right == opttype1)
        		          .Where(x => x.BidPrice > 0.01m && x.AskPrice > 0.01m) // non-zero IV
        		          .OrderBy(x => Math.Abs(mny1*chain.Underlying.Price - x.Strike))
        		          .FirstOrDefault();
        		        o2back = chain
        		          .Where(x => x.Expiry == expBack)
        		          .Where(x => x.Right == opttype2)
        		          .Where(x => x.BidPrice > 0.01m && x.AskPrice > 0.01m) // non-zero IV
        		          .OrderBy(x => Math.Abs(mny2*chain.Underlying.Price - x.Strike))
        		          .FirstOrDefault();
        		    }
        		    
        		    if (o1back == null)
        		    {
        		    	Log("No matching back contract for mny1");
        		    	
        		    	Log(string.Format("Could not find strike = {0}", o1front.Strike));
        		    	foreach (var contract in chain)
        		    	{
        		    		Log(String.Format(@"Expiry={0},Bid={1} Ask={2} Last={3} OI={4} IV={5:0.000} Type={6}, Strike={7}",
                             contract.Expiry,
                             contract.BidPrice,
                             contract.AskPrice,
                             contract.LastPrice,
                             contract.OpenInterest,
                             contract.ImpliedVolatility,
                             contract.Right,
                             contract.Strike
                            ));
        		    	}
        		    	
        		    	return;
        		    }
        		    if (o2back == null)
        		    {
        		    	Log("No matching back contract for mny2");
        		    	
        		    	Log(string.Format("Could not find strike = {0}", o2front.Strike));
        		    	foreach (var contract in chain)
        		    	{
        		    		Log(String.Format(@"Expiry={0},Bid={1} Ask={2} Last={3} OI={4} IV={5:0.000} Type={6}, Strike={7}",
                             contract.Expiry,
                             contract.BidPrice,
                             contract.AskPrice,
                             contract.LastPrice,
                             contract.OpenInterest,
                             contract.ImpliedVolatility,
                             contract.Right,
                             contract.Strike
                            ));
        		    	}
        		    	
        		    	return;
        		    }
        		}
        		else
        		{
        			// using front contract only
        			expBack = expFront;
        			o1back = o1front;
        			o2back = o2front;
        		}
        		
        		// interpolate IV between nearest front and back contracts
        		var iv1 = InterpTwoExpLinear(
        			(expBack - Time).Days - optionExpDays,
        			optionExpDays - (expFront - Time).Days,
        			o1front.ImpliedVolatility, o1back.ImpliedVolatility);
        		var iv2 = InterpTwoExpLinear(
        			(expBack - Time).Days - optionExpDays,
        			optionExpDays - (expFront - Time).Days,
        			o2front.ImpliedVolatility, o2back.ImpliedVolatility);
        		
        		// fixed strike skew as abs. difference of implied volatilities
        		var skew = iv1 - iv2;
        		
        		if (debugLog)
        		{
        			// csv: UnderPrice, strmny1, strmny2, expFront, expBack, iv1front, iv1back, iv2front, iv2back, skew
        			Log(string.Format(@", {0}, {1}, {2}, {3}, {4}, {5:0.0000}, {6:0.0000}, {7:0.0000}, {8:0.0000}, {9:0.0000}",
        			      o1front.UnderlyingLastPrice,
        			      o1front.Strike,
        			      o2front.Strike,
        			      expFront,
        			      expBack,
        			      o1front.ImpliedVolatility,
        			      o1back.ImpliedVolatility,
        			      o2front.ImpliedVolatility,
        			      o2back.ImpliedVolatility,
        			      skew
        			    ));
        		}
        		else
        		{
        			//csv: UnderPrice, iv1interp, iv2interp, skew
        			Log(string.Format(@", {0}, {1:0.0000}, {2:0.0000}, {3:0.0000}",
        			      o1front.UnderlyingLastPrice, iv1, iv2, skew));
        		}
        	}
        }
        
        
        // linear time-interpolation (days-differences used) between two values
        private decimal InterpTwoExpLinear(int t2mtx, int txmt1, decimal v1, decimal v2)
        {
        	if (t2mtx + txmt1 < 1)
        	{
        		// t2 - t1 < 1 Day: no interpolation needed
        		return 0.50m * (v1 + v2);
        	}
        	
        	return (v1*t2mtx + v2*txmt1) / (t2mtx + txmt1);
        }
        
    }
}