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