Overall Statistics
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>
    /// Selected volatility skew metric 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 Slice lastSlice = null;
        private bool debugLog = true; // more detailed logging
        
        // selected expiration
        // CAUTION: When changing, modify option chain pre-filtering accordingly
        private int optionExpDays = 30;
        
        // simple moneyness and option types for fixed strikes to calculate skew
        private decimal mny1 = 0.90m;
        private decimal mny2 = 1.10m;
        private decimal mnyPrefilt = 0.15m;
        private OptionRight opttype1 = OptionRight.Put;
        private OptionRight opttype2 = OptionRight.Call;

        public override void Initialize()
        {
            SetStartDate(2011, 8, 1);
            SetEndDate(2011, 9, 1);
            
            SetCash(1000000);

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

            equity.SetDataNormalizationMode(DataNormalizationMode.Raw);
            
            // specity pricing model to get IV calculated
            option.PriceModel = OptionPriceModels.CrankNicolsonFD();
            //option.EnableGreekApproximation = true;
            
            // option chain pre-filtering
            option.SetFilter(universe => from symbol in universe
                               .Expiration(TimeSpan.FromDays(optionExpDays-29), TimeSpan.FromDays(optionExpDays+29))
                             where Math.Abs(symbol.ID.StrikePrice - universe.Underlying.Price) / universe.Underlying.Price <= mnyPrefilt
                             select symbol);
            
            // daily log at a specific time
            if (debugLog)
            {
            	Log("Time, Underlying1T1, Underlying2T2, Skew, iv1, iv2, exp1, exp2");
            }
            else
            {
            	Log("Time, Underlying, Skew");
            }
            Schedule.On(DateRules.EveryDay(UnderlyingTicker), TimeRules.At(10, 0), () =>
            {
            	//Log("EveryDay 30 min after open: " + Time);
            	OptionSkewLog(lastSlice);
            });
        }

        /// <summary>
        /// Event - v3.0 DATA EVENT HANDLER: (Pattern) Basic template for user to override for receiving all subscription data in a single event
        /// </summary>
        /// <param name="slice">The current slice of data keyed by symbol string</param>
        public override void OnData(Slice slice)
        {
            lastSlice = slice;
        }
        
        // fixed-strike skew
        private void OptionSkewLog(Slice slice)
        {
        	if (slice == null) { return; }
        	
        	OptionChain chain;
        	if (slice.OptionChains.TryGetValue(OptionSymbol, out chain))
        	{
        		// identify nearest expirations
        		var ofront = chain
        		  .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;
        		//Log(expFront.ToString());
        		
        		var oback = chain
        		  .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 expiration");
        			return;
        		}
        		var expBack = oback.Expiry;
        		//Log(expBack.ToString());
        		
        		
        		// identify closest front contracts to fixed strikes
        		var 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 front contract 1");
        			return;
        		}
        		//Log(o1front.Symbol.Value);
        		
        		var 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 front contract 2");
        			return;
        		}
        		//Log(o2front.Symbol.Value);
        		
        		
        		// enforce identical strikes (to front ones) on closest back contracts
        		var 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();
        		if (o1back == null)
        		{
        			Log("No matching back contract 1");
        			return;
        		}
        		//Log(o1back.Symbol.Value);
        		
        		var 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();
        		if (o2back == null)
        		{
        			Log("No matching back contract 2");
        			return;
        		}
        		//Log(o2back.Symbol.Value);
        		
        		
        		// interpolate IV between nearest front and back contracts for each strike
        		var iv1 = InterpTwoExpLinear(
        			(expBack - Time).Days - optionExpDays,
        			optionExpDays - (expFront - Time).Days,
        			o1front.ImpliedVolatility, o1back.ImpliedVolatility);
        		//Log(iv1.ToString("##.####"));
        		//Log(o1front.ImpliedVolatility.ToString("##.####"));
        		//Log(o1back.ImpliedVolatility.ToString("##.####"));
        		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: Underlying1T1, Underlying2T2, Skew, iv1, iv2, exp1, exp2
        			Log(string.Format(", {0}, {1}, {2}, {3}, {4}, {5}, {6}",
        			      o1front.UnderlyingLastPrice,
        			      o2back.UnderlyingLastPrice,
        			      skew.ToString("##.####"),
        			      iv1, iv2, expFront, expBack
        			    ));
        		}
        		else
        		{
        			//csv: Underlying, Skew
        			Log(string.Format(", {0}, {1}", o1front.UnderlyingLastPrice, skew.ToString("##.####")));
        		}
        	}
        }
        
        // 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: should not normally be the case
        		return 0.50m * (v1 + v2);
        	}
        	
        	return (v1*t2mtx + v2*txmt1) / (t2mtx + txmt1);
        }
        
    }
}