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