@ziongotoptions
Not at home now, but I will load it later tonight. Thanks!
@ziongotoptions
@ziongotoptions Thanks for your effort of trying to help. Not sure if it's my computer does not have enough memory resource, it keeps spinning awfully long time. Unfortunately, I wont be able to use this indicator until I get a new computer, (I am a big fan of Volume Profile TPO .@ziongotoptions
Not at home now, but I will load it later tonight. Thanks!
You’re welcome it’s possible, I usually wait 2 minutes and then I go to edit studies, I just click the apply button and that usually helps it load faster also might be better to try it with 1 of the studies not both that I sent@ziongotoptions Thanks for your effort of trying to help. Not sure if it's my computer does not have enough memory resource, it keeps spinning awfully long time. Unfortunately, I wont be able to use this indicator until I get a new computer, (I am a big fan of Volume Profile TPO .
@ziongotoptions Thanks for your kindnessYou’re welcome it’s possible, I usually wait 2 minutes and then I go to edit studies, I just click the apply button and that usually helps it load faster also might be better to try it with 1 of the studies not both that I sent
# Depth of strikes to scan
# Todo: this only from ATM options to x levels ITM.
input StrikeDepth = 5;
# Call Open Interest
plot CallOpenInterest =
fold CallOIIndex = 0 to StrikeDepth
with cOI
do if IsNaN(
open_interest(
Concat(
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Concat("C", AsPrice(centerStrike - strikeSpacing * CallOIIndex ))
)
)
)
then 0
else cOI + open_interest(
Concat(
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Concat("C", AsPrice(centerStrike - strikeSpacing * CallOIIndex ))
)
)
;
CallOpenInterest.SetHiding(!ShowOI);
CallOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE);
CallOpenInterest.SetDefaultColor(GlobalColor("Calls"));
AddLabel(ShowOI, "CallOI: " + CallOpenInterest, GlobalColor("Calls"));
"series is the expiry starting at 1 and raising by one for each next expiry"
#hint: Options Hacker \n This study lets you scan the options chain for a series and given depth. \n<b>Warning: Setting the StrikeDepth to large values requires significant processing power, and will result in slow loading times.</b>
#-----------------------------------------------------------------------------------------------------------------#
# Settings
# Colors
DefineGlobalColor("Call", Color.GREEN);
DefineGlobalColor("Put", Color.RED);
DefineGlobalColor("CallCloud",Color.DARK_GREEN);
DefineGlobalColor("PutCloud",Color.DARK_RED);
#hint Mode: The mode to select an option symbol. \n AUTO will use the option series input code. This will not always work, set to MANUAL to use the center strike and spacing inputs to manually determine the option symbol.
input Mode = {default AUTO, MANUAL};
#hint Series: The option expiration series to search. \n This value is used to determine the option symbol, and for the input to the SeriesVolatility(series = x) function where series is the expiry starting at 1 and raising by one for each next expiry.
input Series = {
default Weekly,
Month1,
Month2,
Month3,
Month4,
Month5,
Month6,
Month7,
Month8,
Month9
};
#hint DataType: The type of option data to show.
input DataType = {default OPEN_INTEREST, VOLUME};
#hint StrikeDepth: The level of depth to search a series. The search starts at the center strike price, and goes both ITM and OTM.
input StrikeDepth = 5;
#hint CenterStrikeOffset: The offset to use when calculating the center strike based on close price. \n Examples: \n 1 = nearest $1 interval \n 10 = nearest $10 interval.
input CenterStrikeOffset = 1.0;
#hint MaxStrikeSpacing: The maximum dollar amount between two adjacent contracts.
input MaxStrikeSpacing = 25;
#hint ManualCenterStrike: The starting price to use when in MANUAL mode.
input ManualCenterStrike = 440;
#hint ManualStrikeSpacing: The dollar amount between two adjacent contracts to use when in MANUAL mode.
input ManualStrikeSpacing = 1.0;
#hint ShowStrikeInfo: Show the strike info labels.
input ShowStrikeInfo = yes;
#hint ShowLabels: Show the open interest labels.
input ShowLabels = yes;
#hint ShowClouds: Show the open interest clouds.
input ShowClouds = no;
#hint ShowLines: Show the open interest lines.
input ShowLines = yes;
#hint ShowGreeks: Show the estimated Greek calculation labels for the latest bar.
input ShowGreeks = yes;
#-----------------------------------------------------------------------------------------------------------------#
# Date, Symbol, and Strike
# OptionSeries is the expiry starting at 1 and raising by one for each next expiry.
def OptionSeries;
switch (Series) {
case Weekly:
OptionSeries = 1;
case Month1:
OptionSeries = 2;
case Month2:
OptionSeries = 3;
case Month3:
OptionSeries = 4;
case Month4:
OptionSeries = 5;
case Month5:
OptionSeries = 6;
case Month6:
OptionSeries = 7;
case Month7:
OptionSeries = 8;
case Month8:
OptionSeries = 9;
case Month9:
OptionSeries = 10;
};
# Open price at Regular Trading Hours
def RTHopen = open(period = AggregationPeriod.DAY);
# Current year, month, and day
def CurrentYear = GetYear();
def CurrentMonth = GetMonth();
def CurrentDayOfMonth = GetDayOfMonth(GetYYYYMMDD());
# Get the first day of this month. 1 (Monday) to 7 (Sunday)
def FirstDayThisMonth = GetDayOfWeek((CurrentYear * 10000) + (CurrentMonth * 100) + 1);
# Get the first friday of this month
def FirstFridayDOM =
if FirstDayThisMonth < 6 then 6 - FirstDayThisMonth
else if FirstDayThisMonth == 6 then 7
else 6
;
# Get the second, third, and fourth fridays of this month
def SecondFridayDOM = FirstFridayDOM + 7;
def ThirdFridayDOM = FirstFridayDOM + 14;
def FourthFridayDOM = FirstFridayDOM + 21;
# Pick up all Fridays of the current month for weekly options
def RollDOM = FirstFridayDOM + 21;
def ExpMonth1 =
if RollDOM > CurrentDayOfMonth then CurrentMonth + OptionSeries - 1
else CurrentMonth + OptionSeries
;
# Options month input
def ExpMonth2 = if ExpMonth1 > 12 then ExpMonth1 - 12 else ExpMonth1;
# Options year input
def ExpYear = if ExpMonth1 > 12 then CurrentYear + 1 else CurrentYear;
# First friday expiry calc
def ExpDay1DOW = GetDayOfWeek(ExpYear * 10000 + ExpMonth2 * 100 + 1);
def ExpFirstFridayDOM =
if ExpDay1DOW < 6 then 6 - ExpDay1DOW
else if ExpDay1DOW == 6 then 7
else 6
;
# Options code day of month input
def ExpDOM =
if CurrentDayOfMonth < ExpFirstFridayDOM -1 then FirstFridayDOM
else if between(CurrentDayOfMonth, ExpFirstFridayDOM, SecondFridayDOM - 1) then SecondFridayDOM
else if between(CurrentDayOfMonth, SecondFridayDOM, ThirdFridayDOM - 1) then ThirdFridayDOM
else if between(CurrentDayOfMonth, ThirdFridayDOM, FourthFridayDOM - 1) then FourthFridayDOM
else ExpFirstFridayDOM
;
# Centerstrike
def CenterStrike =
if (Mode == Mode.AUTO and !IsNaN(close)) then Round(close / CenterStrikeOffset, 0) * CenterStrikeOffset
else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualCenterStrike
else CenterStrike[1]
;
# Strike Spacing
def StrikeSpacingC =
fold i = 1 to MaxStrikeSpacing
with spacing = 0
do if DataType == DataType.OPEN_INTEREST and !IsNaN(
open_interest(
Concat(
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Concat("C", AsPrice(CenterStrike + (MaxStrikeSpacing - i)))
)
)
)
then MaxStrikeSpacing - i
else spacing
;
def StrikeSpacing =
if (Mode == Mode.AUTO and !IsNaN(close)) then StrikeSpacingC
else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualStrikeSpacing
else StrikeSpacing[1]
;
#-----------------------------------------------------------------------------------------------------------------#
# Option Chain
# Call Open Interest
def ITMCallOpenInterest =
fold itmCallOIIndex = 0 to StrikeDepth
with itmCOI
do if IsNaN(
open_interest(
Concat(
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Concat("C", AsPrice(CenterStrike - strikeSpacing * itmCallOIIndex))
)
)
)
then 0
else itmCOI + open_interest(
Concat(
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Concat("C", AsPrice(CenterStrike - strikeSpacing * itmCallOIIndex))
)
)
;
def OTMCallOpenInterest =
fold otmCallOIIndex = 0 to StrikeDepth
with otmCOI
do if IsNaN(
open_interest(
Concat(
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Concat("C", AsPrice(CenterStrike + strikeSpacing * otmCallOIIndex))
)
)
)
then 0
else otmCOI + open_interest(
Concat(
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Concat("C", AsPrice(CenterStrike + strikeSpacing * otmCallOIIndex))
)
)
;
# Put Open Interest
def ITMPutOpenInterest =
fold itmPutOIIndex = 0 to StrikeDepth
with itmPOI
do if IsNaN(
open_interest(
Concat(
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Concat("P", AsPrice(CenterStrike - strikeSpacing * itmPutOIIndex))
)
)
)
then 0
else itmPOI + open_interest(
Concat(
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Concat("P", AsPrice(CenterStrike - strikeSpacing * itmPutOIIndex))
)
)
;
def OTMPutOpenInterest =
fold otmPutOIIndex = 0 to StrikeDepth
with otmPOI
do if IsNaN(
open_interest(
Concat(
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Concat("P", AsPrice(CenterStrike + strikeSpacing * otmPutOIIndex))
)
)
)
then 0
else otmPOI + open_interest(
Concat(
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Concat("P", AsPrice(centerStrike + strikeSpacing * otmPutOIIndex))
)
)
;
#-----------------------------------------------------------------------------------------------------------------#
# Greeks Calculations
#
# K - Option strike price
# N - Standard normal cumulative distribution function
# r - Risk free interest rate
# IV - Volatility of the underlying
# S - Price of the underlying
# t - Time to option's expiry
#
# Delta = N(d1)
# d1 = (ln(S/K) + (r + (sqr(IV)/2))t) / (? (sqrt(t)))
# Gamma = (d2) / S(IV(sqrt(t)))
# d2 = e -(sqr(d1) / 2) / sqrt(2*pi)
# Theta = ((S d2))IV) / 2 sqrt(t)) - (rK e(rt)N(d4))
# where phi(d3) = (exp(-(sqr(x)/2))) / (2 * sqrt(t))
# where d4 = d1 - IV(sqrt(t))
# Vega = S phi(d1) Sqrt(t)
# TODO: Figure out how to dynamically get this value
input DayToExpiry = 6;
# Get the implied volatility for calculations
# Input: series is the expiry starting at 1 and raising by 1 for each next expiry
def IV = SeriesVolatility(series = OptionSeries);
def K = CenterStrike;
def S = close;
def r = GetInterestRate();
def t = DayToExpiry / 365;
def d1 = (Log(S / K) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t));
script N {
input data = 1;
def a = AbsValue(data);
def b1 = .31938153;
def b2 = -.356563782;
def b3 = 1.781477937;
def b4 = -1.821255978;
def b5 = 1.330274429;
def b6 = .2316419;
def e = 1 / (1 + b6 * a);
def i = 1 - 1 / Sqrt(2 * Double.Pi) * Exp(-Power(a, 2) / 2) *
(b1 * e + b2 * e * e + b3 *
Power(e, 3) + b4 * Power(e, 4) + b5 * Power(e, 5));
plot CND = if data < 0
then 1 - i
else i;
}
# TODO: These values don't quite line up
# My background on options pricing models is not very good
# Delta
def Delta = N(d1);
# Gamma
def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi);
def Gamma = d2 / (S * (IV * Sqrt(t)));
# Theta
def Theta = -(-(S*d2*IV*(.5000)/
(2*sqrt(t)))-
(r*(exp(-r*t)*K))*N(d2)+(S*N(d1)*(.5000)))/365;
# (.5000) variant less than .5 e(X/t)
# Vega
def Vega = (S*d2*sqrt(t))/100;
#-----------------------------------------------------------------------------------------------------------------#
# Visuals
# Current Option Expiry Label
AddLabel(
ShowStrikeInfo,
Concat(".",
Concat(GetSymbolPart(),
Concat(
Concat(ExpYear - 2000,
if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
else Concat("", ExpMonth2)
),
if ExpDOM <= 9 then Concat("0", ExpDOM)
else Concat("", ExpDOM)
)
)
),
Color.GRAY
);
# Center Strike Label
AddLabel(ShowStrikeInfo, "Center Strike: " + AsDollars(CenterStrike), Color.GRAY);
# Chain Depth Label
AddLabel(ShowStrikeInfo, "Chain Depth: +/-" + StrikeDepth, Color.GRAY);
# Strike Spacing Label
AddLabel(ShowStrikeInfo and StrikeSpacing, "Strike Spacing: " + StrikeSpacing, Color.GRAY);
# Call Open Interest
plot CallOpenInterest = ItMCallOpenInterest + OTMCallOpenInterest;
CallOpenInterest.SetHiding(!ShowLines);
CallOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE);
CallOpenInterest.SetDefaultColor(GlobalColor("Call"));
AddLabel(ShowLabels, "CallOI: " + CallOpenInterest, GlobalColor("Call"));
# Put Open Interest
plot PutOpenInterest = -(ITMPutOpenInterest + OTMPutOpenInterest); # Make negative to flip under axis
PutOpenInterest.SetHiding(!ShowLines);
PutOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE);
PutOpenInterest.SetDefaultColor(GlobalColor("Put"));
AddLabel(ShowLabels, "PutOI: " + (ITMPutOpenInterest + OTMPutOpenInterest), GlobalColor("Put"));
# Create a center line
plot ZeroLine = 0;
ZeroLine.SetDefaultColor(Color.WHITE);
# Create Clouds
AddCloud(if ShowClouds then CallOpenInterest else Double.NaN, if ShowClouds then Zeroline else Double.NaN, GlobalColor("CallCloud"), GlobalColor("PutCloud"));
AddCloud(if ShowClouds then Zeroline else Double.NaN, if ShowClouds then PutOpenInterest else Double.NaN, GlobalColor("PutCloud"), GlobalColor("CallCloud"));
# Hull Moving Average of Put Open Interest
plot PutVolumeAverage = hullmovingavg(PutOpenInterest);
PutVolumeAverage.SetHiding(!ShowLines);
PutVolumeAverage.setdefaultcolor(color.ORANGE);
PutVolumeAverage.setstyle(curve.MEDIUM_DASH);
# Hull Moving Average of Call Open Interest
plot CallVolumeAverage = hullmovingavg(CallOpenInterest);
CallVolumeAverage.SetHiding(!ShowLines);
CallVolumeAverage.setdefaultcolor(color.LIGHT_GREEN);
CallVolumeAverage.setstyle(curve.MEDIUM_DASH);
# Color Gradient of Total Average Open Interest
plot TotalVolumeAverage = average(CallOpenInterest + PutOpenInterest);
TotalVolumeAverage.SetHiding(!ShowLines);
def colornormlength = 14;
TotalVolumeAverage.setlineweight(3);
TotalVolumeAverage.definecolor("Default", Color.YELLOW);
TotalVolumeAverage.definecolor("Highest", Color.GREEN);
TotalVolumeAverage.DefineColor("Lowest", Color.RED);
TotalVolumeAverage.AssignNormGradientColor(colorNormLength, TotalVolumeAverage.Color("Lowest"), TotalVolumeAverage.Color("Highest"));
AddCloud(if ShowClouds then TotalVolumeAverage else Double.NaN, if ShowClouds then Zeroline else Double.NaN, color.GREEN, color.RED);
# Greeks
AddLabel(ShowGreeks, "Delta = " + Delta, Color.WHITE);
AddLabel(ShowGreeks, "Gamma = " + Gamma, Color.WHITE);
AddLabel(ShowGreeks, "Theta = " + theta, Color.WHITE);
AddLabel(ShowGreeks, "Vega = " + Vega, Color.WHITE);
Okay, uploading what I have so far. I heavily commented everything and renamed a lot of things, trying my best to understand it all and how thinkscript works in general. It would be 100x easier to do this if we could just dynamically assign a string to a variable. That whole 'series' thing had me stumped for a bit but I found a snippet from Mobius which helps to understand:
"series is the expiry starting at 1 and raising by one for each next expiry"
Using that logic, I setup a dropdown switch to select that instead of a number.
Note this is only setup for open interest right now, the DataType switch does nothing yet.
Change all the open_interest() function calls to volume() for option volume instead.
Code:#hint: Options Hacker \n This study lets you scan the options chain for a series and given depth. \n<b>Warning: Setting the StrikeDepth to large values requires significant processing power, and will result in slow loading times.</b> #-----------------------------------------------------------------------------------------------------------------# # Settings # Colors DefineGlobalColor("Call", Color.GREEN); DefineGlobalColor("Put", Color.RED); DefineGlobalColor("CallCloud",Color.DARK_GREEN); DefineGlobalColor("PutCloud",Color.DARK_RED); #hint Mode: The mode to select an option symbol. \n AUTO will use the option series input code. This will not always work, set to MANUAL to use the center strike and spacing inputs to manually determine the option symbol. input Mode = {default AUTO, MANUAL}; #hint Series: The option expiration series to search. \n This value is used to determine the option symbol, and for the input to the SeriesVolatility(series = x) function where series is the expiry starting at 1 and raising by one for each next expiry. input Series = { default Weekly, Month1, Month2, Month3, Month4, Month5, Month6, Month7, Month8, Month9 }; #hint DataType: The type of option data to show. input DataType = {default OPEN_INTEREST, VOLUME}; #hint StrikeDepth: The level of depth to search a series. The search starts at the center strike price, and goes both ITM and OTM. input StrikeDepth = 5; #hint CenterStrikeOffset: The offset to use when calculating the center strike based on close price. \n Examples: \n 1 = nearest $1 interval \n 10 = nearest $10 interval. input CenterStrikeOffset = 1.0; #hint MaxStrikeSpacing: The maximum dollar amount between two adjacent contracts. input MaxStrikeSpacing = 25; #hint ManualCenterStrike: The starting price to use when in MANUAL mode. input ManualCenterStrike = 440; #hint ManualStrikeSpacing: The dollar amount between two adjacent contracts to use when in MANUAL mode. input ManualStrikeSpacing = 1.0; #hint ShowStrikeInfo: Show the strike info labels. input ShowStrikeInfo = yes; #hint ShowLabels: Show the open interest labels. input ShowLabels = yes; #hint ShowClouds: Show the open interest clouds. input ShowClouds = no; #hint ShowLines: Show the open interest lines. input ShowLines = yes; #hint ShowGreeks: Show the estimated Greek calculation labels for the latest bar. input ShowGreeks = yes; #-----------------------------------------------------------------------------------------------------------------# # Date, Symbol, and Strike # OptionSeries is the expiry starting at 1 and raising by one for each next expiry. def OptionSeries; switch (Series) { case Weekly: OptionSeries = 1; case Month1: OptionSeries = 2; case Month2: OptionSeries = 3; case Month3: OptionSeries = 4; case Month4: OptionSeries = 5; case Month5: OptionSeries = 6; case Month6: OptionSeries = 7; case Month7: OptionSeries = 8; case Month8: OptionSeries = 9; case Month9: OptionSeries = 10; }; # Open price at Regular Trading Hours def RTHopen = open(period = AggregationPeriod.DAY); # Current year, month, and day def CurrentYear = GetYear(); def CurrentMonth = GetMonth(); def CurrentDayOfMonth = GetDayOfMonth(GetYYYYMMDD()); # Get the first day of this month. 1 (Monday) to 7 (Sunday) def FirstDayThisMonth = GetDayOfWeek((CurrentYear * 10000) + (CurrentMonth * 100) + 1); # Get the first friday of this month def FirstFridayDOM = if FirstDayThisMonth < 6 then 6 - FirstDayThisMonth else if FirstDayThisMonth == 6 then 7 else 6 ; # Get the second, third, and fourth fridays of this month def SecondFridayDOM = FirstFridayDOM + 7; def ThirdFridayDOM = FirstFridayDOM + 14; def FourthFridayDOM = FirstFridayDOM + 21; # Pick up all Fridays of the current month for weekly options def RollDOM = FirstFridayDOM + 21; def ExpMonth1 = if RollDOM > CurrentDayOfMonth then CurrentMonth + OptionSeries - 1 else CurrentMonth + OptionSeries ; # Options month input def ExpMonth2 = if ExpMonth1 > 12 then ExpMonth1 - 12 else ExpMonth1; # Options year input def ExpYear = if ExpMonth1 > 12 then CurrentYear + 1 else CurrentYear; # First friday expiry calc def ExpDay1DOW = GetDayOfWeek(ExpYear * 10000 + ExpMonth2 * 100 + 1); def ExpFirstFridayDOM = if ExpDay1DOW < 6 then 6 - ExpDay1DOW else if ExpDay1DOW == 6 then 7 else 6 ; # Options code day of month input def ExpDOM = if CurrentDayOfMonth < ExpFirstFridayDOM -1 then FirstFridayDOM else if between(CurrentDayOfMonth, ExpFirstFridayDOM, SecondFridayDOM - 1) then SecondFridayDOM else if between(CurrentDayOfMonth, SecondFridayDOM, ThirdFridayDOM - 1) then ThirdFridayDOM else if between(CurrentDayOfMonth, ThirdFridayDOM, FourthFridayDOM - 1) then FourthFridayDOM else ExpFirstFridayDOM ; # Centerstrike def CenterStrike = if (Mode == Mode.AUTO and !IsNaN(close)) then Round(close / CenterStrikeOffset, 0) * CenterStrikeOffset else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualCenterStrike else CenterStrike[1] ; # Strike Spacing def StrikeSpacingC = fold i = 1 to MaxStrikeSpacing with spacing = 0 do if DataType == DataType.OPEN_INTEREST and !IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike + (MaxStrikeSpacing - i))) ) ) ) then MaxStrikeSpacing - i else spacing ; def StrikeSpacing = if (Mode == Mode.AUTO and !IsNaN(close)) then StrikeSpacingC else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualStrikeSpacing else StrikeSpacing[1] ; #-----------------------------------------------------------------------------------------------------------------# # Option Chain # Call Open Interest def ITMCallOpenInterest = fold itmCallOIIndex = 0 to StrikeDepth with itmCOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike - strikeSpacing * itmCallOIIndex)) ) ) ) then 0 else itmCOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike - strikeSpacing * itmCallOIIndex)) ) ) ; def OTMCallOpenInterest = fold otmCallOIIndex = 0 to StrikeDepth with otmCOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike + strikeSpacing * otmCallOIIndex)) ) ) ) then 0 else otmCOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike + strikeSpacing * otmCallOIIndex)) ) ) ; # Put Open Interest def ITMPutOpenInterest = fold itmPutOIIndex = 0 to StrikeDepth with itmPOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(CenterStrike - strikeSpacing * itmPutOIIndex)) ) ) ) then 0 else itmPOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(CenterStrike - strikeSpacing * itmPutOIIndex)) ) ) ; def OTMPutOpenInterest = fold otmPutOIIndex = 0 to StrikeDepth with otmPOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(CenterStrike + strikeSpacing * otmPutOIIndex)) ) ) ) then 0 else otmPOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(centerStrike + strikeSpacing * otmPutOIIndex)) ) ) ; #-----------------------------------------------------------------------------------------------------------------# # Greeks Calculations # # K - Option strike price # N - Standard normal cumulative distribution function # r - Risk free interest rate # IV - Volatility of the underlying # S - Price of the underlying # t - Time to option's expiry # # Delta = N(d1) # d1 = (ln(S/K) + (r + (sqr(IV)/2))t) / (? (sqrt(t))) # Gamma = (d2) / S(IV(sqrt(t))) # d2 = e -(sqr(d1) / 2) / sqrt(2*pi) # Theta = ((S d2))IV) / 2 sqrt(t)) - (rK e(rt)N(d4)) # where phi(d3) = (exp(-(sqr(x)/2))) / (2 * sqrt(t)) # where d4 = d1 - IV(sqrt(t)) # Vega = S phi(d1) Sqrt(t) # TODO: Figure out how to dynamically get this value input DayToExpiry = 6; # Get the implied volatility for calculations # Input: series is the expiry starting at 1 and raising by 1 for each next expiry def IV = SeriesVolatility(series = OptionSeries); def K = CenterStrike; def S = close; def r = GetInterestRate(); def t = DayToExpiry / 365; def d1 = (Log(S / K) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)); script N { input data = 1; def a = AbsValue(data); def b1 = .31938153; def b2 = -.356563782; def b3 = 1.781477937; def b4 = -1.821255978; def b5 = 1.330274429; def b6 = .2316419; def e = 1 / (1 + b6 * a); def i = 1 - 1 / Sqrt(2 * Double.Pi) * Exp(-Power(a, 2) / 2) * (b1 * e + b2 * e * e + b3 * Power(e, 3) + b4 * Power(e, 4) + b5 * Power(e, 5)); plot CND = if data < 0 then 1 - i else i; } # TODO: These values don't quite line up # My background on options pricing models is not very good # Delta def Delta = N(d1); # Gamma def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi); def Gamma = d2 / (S * (IV * Sqrt(t))); # Theta def Theta = -(-(S*d2*IV*(.5000)/ (2*sqrt(t)))- (r*(exp(-r*t)*K))*N(d2)+(S*N(d1)*(.5000)))/365; # (.5000) variant less than .5 e(X/t) # Vega def Vega = (S*d2*sqrt(t))/100; #-----------------------------------------------------------------------------------------------------------------# # Visuals # Current Option Expiry Label AddLabel( ShowStrikeInfo, Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Color.GRAY ); # Center Strike Label AddLabel(ShowStrikeInfo, "Center Strike: " + AsDollars(CenterStrike), Color.GRAY); # Chain Depth Label AddLabel(ShowStrikeInfo, "Chain Depth: +/-" + StrikeDepth, Color.GRAY); # Strike Spacing Label AddLabel(ShowStrikeInfo and StrikeSpacing, "Strike Spacing: " + StrikeSpacing, Color.GRAY); # Call Open Interest plot CallOpenInterest = ItMCallOpenInterest + OTMCallOpenInterest; CallOpenInterest.SetHiding(!ShowLines); CallOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE); CallOpenInterest.SetDefaultColor(GlobalColor("Call")); AddLabel(ShowLabels, "CallOI: " + CallOpenInterest, GlobalColor("Call")); # Put Open Interest plot PutOpenInterest = -(ITMPutOpenInterest + OTMPutOpenInterest); # Make negative to flip under axis PutOpenInterest.SetHiding(!ShowLines); PutOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE); PutOpenInterest.SetDefaultColor(GlobalColor("Put")); AddLabel(ShowLabels, "PutOI: " + (ITMPutOpenInterest + OTMPutOpenInterest), GlobalColor("Put")); # Create a center line plot ZeroLine = 0; ZeroLine.SetDefaultColor(Color.WHITE); # Create Clouds AddCloud(if ShowClouds then CallOpenInterest else Double.NaN, if ShowClouds then Zeroline else Double.NaN, GlobalColor("CallCloud"), GlobalColor("PutCloud")); AddCloud(if ShowClouds then Zeroline else Double.NaN, if ShowClouds then PutOpenInterest else Double.NaN, GlobalColor("PutCloud"), GlobalColor("CallCloud")); # Hull Moving Average of Put Open Interest plot PutVolumeAverage = hullmovingavg(PutOpenInterest); PutVolumeAverage.SetHiding(!ShowLines); PutVolumeAverage.setdefaultcolor(color.ORANGE); PutVolumeAverage.setstyle(curve.MEDIUM_DASH); # Hull Moving Average of Call Open Interest plot CallVolumeAverage = hullmovingavg(CallOpenInterest); CallVolumeAverage.SetHiding(!ShowLines); CallVolumeAverage.setdefaultcolor(color.LIGHT_GREEN); CallVolumeAverage.setstyle(curve.MEDIUM_DASH); # Color Gradient of Total Average Open Interest plot TotalVolumeAverage = average(CallOpenInterest + PutOpenInterest); TotalVolumeAverage.SetHiding(!ShowLines); def colornormlength = 14; TotalVolumeAverage.setlineweight(3); TotalVolumeAverage.definecolor("Default", Color.YELLOW); TotalVolumeAverage.definecolor("Highest", Color.GREEN); TotalVolumeAverage.DefineColor("Lowest", Color.RED); TotalVolumeAverage.AssignNormGradientColor(colorNormLength, TotalVolumeAverage.Color("Lowest"), TotalVolumeAverage.Color("Highest")); AddCloud(if ShowClouds then TotalVolumeAverage else Double.NaN, if ShowClouds then Zeroline else Double.NaN, color.GREEN, color.RED); # Greeks AddLabel(ShowGreeks, "Delta = " + Delta, Color.WHITE); AddLabel(ShowGreeks, "Gamma = " + Gamma, Color.WHITE); AddLabel(ShowGreeks, "Theta = " + theta, Color.WHITE); AddLabel(ShowGreeks, "Vega = " + Vega, Color.WHITE);
# Strike Spacing
def StrikeSpacingC =
fold i = 1 to MaxStrikeSpacing
with spacing = 0
do if DataType == DataType.OPEN_INTEREST and !IsNaN(
open_interest(
("." + GetSymbolPart()) +
(ExpYear - 2000) +
# need the "" + to convert number to text. can't have 2 different data types as T/F values for if-then
(if ExpMonth2 <= 9 then ("0" + ExpMonth2) else ("" + ExpMonth2) ) +
(if ExpDOM <= 9 then ("0" + ExpDOM) else ("" + ExpDOM) ) +
("C" + AsPrice(CenterStrike + (MaxStrikeSpacing - i)) )
)
) then MaxStrikeSpacing - i
else spacing;
# original code
# open_interest(
# Concat(
# Concat(".",
# Concat(GetSymbolPart(),
# Concat(
# Concat(ExpYear - 2000,
# if ExpMonth2 <= 9 then Concat("0", ExpMonth2)
# else Concat("", ExpMonth2)
# ),
# if ExpDOM <= 9 then Concat("0", ExpDOM)
# else Concat("", ExpDOM)
# )
# )
# ),
# Concat("C", AsPrice(CenterStrike + (MaxStrikeSpacing - i)))
# )
# )
# )
# then MaxStrikeSpacing - i
# else spacing
Thanks! That means a lot actually.a week here and you made this? you are talented.
thank you for posting the code in #106
def OptionExpiryDate = ExpYear * 10000 + ExpMonth2 * 100 + ExpDOM + 1;
Addlabel(ShowStrikeInfo, "ATM Put: " + ("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(0 + CenterStrike), Color.LIGHT_RED);
Addlabel(ShowStrikeInfo, "ATM Call: " + ("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(0 + CenterStrike), Color.LIGHT_GREEN);
plot test = OptionExpiryDate - 20000001; # For debugging date
# Total Put Open Interest for selected chain depth and expiry series
def TotalPutOpenInterest =
fold poiIndex = -StrikeDepth to StrikeDepth
with poi = 0
do
#if IsNAN(poi + open_interest(Concat(Concat(Concat(".", GetSymbol()), Concat(AsPrice(OptionExpiryDate - 20000001), "P")), poiIndex + CenterStrike)))
if IsNaN(poi + open_interest(("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(poiIndex + CenterStrike)))
then 0
else poi + open_interest(("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(poiIndex + CenterStrike))
;
# Total Call Open Interest for selected chain depth and expiry series
def TotalCallOpenInterest =
fold coiIndex = -StrikeDepth to StrikeDepth
with coi = 0
do
#if IsNAN(coi + open_interest(Concat(Concat(Concat(".", GetSymbol()), Concat(AsPrice(OptionExpiryDate - 20000001), "C")), coiIndex + CenterStrike)))
if IsNaN(coi + open_interest(("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(coiIndex + CenterStrike)))
then 0
else coi + open_interest(("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(coiIndex + CenterStrike))
;
it's great having a software engineer help with this, I definitely like what you've done so far with calculating OI and the greeks, it won't be completely accurate given these are theoretical calculations and there are bound to be nuisances but I definitely think you're on the right track. This can be used to calculate total volume and open interest but correct me if I'm wrong, the greek values are only for the center strike correct?Okay, uploading what I have so far. I heavily commented everything and renamed a lot of things, trying my best to understand it all and how thinkscript works in general. It would be 100x easier to do this if we could just dynamically assign a string to a variable. That whole 'series' thing had me stumped for a bit but I found a snippet from Mobius which helps to understand:
"series is the expiry starting at 1 and raising by one for each next expiry"
Using that logic, I setup a dropdown switch to select that instead of a number.
Note this is only setup for open interest right now, the DataType switch does nothing yet.
Change all the open_interest() function calls to volume() for option volume instead.
Code:#hint: Options Hacker \n This study lets you scan the options chain for a series and given depth. \n<b>Warning: Setting the StrikeDepth to large values requires significant processing power, and will result in slow loading times.</b> #-----------------------------------------------------------------------------------------------------------------# # Settings # Colors DefineGlobalColor("Call", Color.GREEN); DefineGlobalColor("Put", Color.RED); DefineGlobalColor("CallCloud",Color.DARK_GREEN); DefineGlobalColor("PutCloud",Color.DARK_RED); #hint Mode: The mode to select an option symbol. \n AUTO will use the option series input code. This will not always work, set to MANUAL to use the center strike and spacing inputs to manually determine the option symbol. input Mode = {default AUTO, MANUAL}; #hint Series: The option expiration series to search. \n This value is used to determine the option symbol, and for the input to the SeriesVolatility(series = x) function where series is the expiry starting at 1 and raising by one for each next expiry. input Series = { default Weekly, Month1, Month2, Month3, Month4, Month5, Month6, Month7, Month8, Month9 }; #hint DataType: The type of option data to show. input DataType = {default OPEN_INTEREST, VOLUME}; #hint StrikeDepth: The level of depth to search a series. The search starts at the center strike price, and goes both ITM and OTM. input StrikeDepth = 5; #hint CenterStrikeOffset: The offset to use when calculating the center strike based on close price. \n Examples: \n 1 = nearest $1 interval \n 10 = nearest $10 interval. input CenterStrikeOffset = 1.0; #hint MaxStrikeSpacing: The maximum dollar amount between two adjacent contracts. input MaxStrikeSpacing = 25; #hint ManualCenterStrike: The starting price to use when in MANUAL mode. input ManualCenterStrike = 440; #hint ManualStrikeSpacing: The dollar amount between two adjacent contracts to use when in MANUAL mode. input ManualStrikeSpacing = 1.0; #hint ShowStrikeInfo: Show the strike info labels. input ShowStrikeInfo = yes; #hint ShowLabels: Show the open interest labels. input ShowLabels = yes; #hint ShowClouds: Show the open interest clouds. input ShowClouds = no; #hint ShowLines: Show the open interest lines. input ShowLines = yes; #hint ShowGreeks: Show the estimated Greek calculation labels for the latest bar. input ShowGreeks = yes; #-----------------------------------------------------------------------------------------------------------------# # Date, Symbol, and Strike # OptionSeries is the expiry starting at 1 and raising by one for each next expiry. def OptionSeries; switch (Series) { case Weekly: OptionSeries = 1; case Month1: OptionSeries = 2; case Month2: OptionSeries = 3; case Month3: OptionSeries = 4; case Month4: OptionSeries = 5; case Month5: OptionSeries = 6; case Month6: OptionSeries = 7; case Month7: OptionSeries = 8; case Month8: OptionSeries = 9; case Month9: OptionSeries = 10; }; # Open price at Regular Trading Hours def RTHopen = open(period = AggregationPeriod.DAY); # Current year, month, and day def CurrentYear = GetYear(); def CurrentMonth = GetMonth(); def CurrentDayOfMonth = GetDayOfMonth(GetYYYYMMDD()); # Get the first day of this month. 1 (Monday) to 7 (Sunday) def FirstDayThisMonth = GetDayOfWeek((CurrentYear * 10000) + (CurrentMonth * 100) + 1); # Get the first friday of this month def FirstFridayDOM = if FirstDayThisMonth < 6 then 6 - FirstDayThisMonth else if FirstDayThisMonth == 6 then 7 else 6 ; # Get the second, third, and fourth fridays of this month def SecondFridayDOM = FirstFridayDOM + 7; def ThirdFridayDOM = FirstFridayDOM + 14; def FourthFridayDOM = FirstFridayDOM + 21; # Pick up all Fridays of the current month for weekly options def RollDOM = FirstFridayDOM + 21; def ExpMonth1 = if RollDOM > CurrentDayOfMonth then CurrentMonth + OptionSeries - 1 else CurrentMonth + OptionSeries ; # Options month input def ExpMonth2 = if ExpMonth1 > 12 then ExpMonth1 - 12 else ExpMonth1; # Options year input def ExpYear = if ExpMonth1 > 12 then CurrentYear + 1 else CurrentYear; # First friday expiry calc def ExpDay1DOW = GetDayOfWeek(ExpYear * 10000 + ExpMonth2 * 100 + 1); def ExpFirstFridayDOM = if ExpDay1DOW < 6 then 6 - ExpDay1DOW else if ExpDay1DOW == 6 then 7 else 6 ; # Options code day of month input def ExpDOM = if CurrentDayOfMonth < ExpFirstFridayDOM -1 then FirstFridayDOM else if between(CurrentDayOfMonth, ExpFirstFridayDOM, SecondFridayDOM - 1) then SecondFridayDOM else if between(CurrentDayOfMonth, SecondFridayDOM, ThirdFridayDOM - 1) then ThirdFridayDOM else if between(CurrentDayOfMonth, ThirdFridayDOM, FourthFridayDOM - 1) then FourthFridayDOM else ExpFirstFridayDOM ; # Centerstrike def CenterStrike = if (Mode == Mode.AUTO and !IsNaN(close)) then Round(close / CenterStrikeOffset, 0) * CenterStrikeOffset else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualCenterStrike else CenterStrike[1] ; # Strike Spacing def StrikeSpacingC = fold i = 1 to MaxStrikeSpacing with spacing = 0 do if DataType == DataType.OPEN_INTEREST and !IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike + (MaxStrikeSpacing - i))) ) ) ) then MaxStrikeSpacing - i else spacing ; def StrikeSpacing = if (Mode == Mode.AUTO and !IsNaN(close)) then StrikeSpacingC else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualStrikeSpacing else StrikeSpacing[1] ; #-----------------------------------------------------------------------------------------------------------------# # Option Chain # Call Open Interest def ITMCallOpenInterest = fold itmCallOIIndex = 0 to StrikeDepth with itmCOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike - strikeSpacing * itmCallOIIndex)) ) ) ) then 0 else itmCOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike - strikeSpacing * itmCallOIIndex)) ) ) ; def OTMCallOpenInterest = fold otmCallOIIndex = 0 to StrikeDepth with otmCOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike + strikeSpacing * otmCallOIIndex)) ) ) ) then 0 else otmCOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike + strikeSpacing * otmCallOIIndex)) ) ) ; # Put Open Interest def ITMPutOpenInterest = fold itmPutOIIndex = 0 to StrikeDepth with itmPOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(CenterStrike - strikeSpacing * itmPutOIIndex)) ) ) ) then 0 else itmPOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(CenterStrike - strikeSpacing * itmPutOIIndex)) ) ) ; def OTMPutOpenInterest = fold otmPutOIIndex = 0 to StrikeDepth with otmPOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(CenterStrike + strikeSpacing * otmPutOIIndex)) ) ) ) then 0 else otmPOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(centerStrike + strikeSpacing * otmPutOIIndex)) ) ) ; #-----------------------------------------------------------------------------------------------------------------# # Greeks Calculations # # K - Option strike price # N - Standard normal cumulative distribution function # r - Risk free interest rate # IV - Volatility of the underlying # S - Price of the underlying # t - Time to option's expiry # # Delta = N(d1) # d1 = (ln(S/K) + (r + (sqr(IV)/2))t) / (? (sqrt(t))) # Gamma = (d2) / S(IV(sqrt(t))) # d2 = e -(sqr(d1) / 2) / sqrt(2*pi) # Theta = ((S d2))IV) / 2 sqrt(t)) - (rK e(rt)N(d4)) # where phi(d3) = (exp(-(sqr(x)/2))) / (2 * sqrt(t)) # where d4 = d1 - IV(sqrt(t)) # Vega = S phi(d1) Sqrt(t) # TODO: Figure out how to dynamically get this value input DayToExpiry = 6; # Get the implied volatility for calculations # Input: series is the expiry starting at 1 and raising by 1 for each next expiry def IV = SeriesVolatility(series = OptionSeries); def K = CenterStrike; def S = close; def r = GetInterestRate(); def t = DayToExpiry / 365; def d1 = (Log(S / K) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)); script N { input data = 1; def a = AbsValue(data); def b1 = .31938153; def b2 = -.356563782; def b3 = 1.781477937; def b4 = -1.821255978; def b5 = 1.330274429; def b6 = .2316419; def e = 1 / (1 + b6 * a); def i = 1 - 1 / Sqrt(2 * Double.Pi) * Exp(-Power(a, 2) / 2) * (b1 * e + b2 * e * e + b3 * Power(e, 3) + b4 * Power(e, 4) + b5 * Power(e, 5)); plot CND = if data < 0 then 1 - i else i; } # TODO: These values don't quite line up # My background on options pricing models is not very good # Delta def Delta = N(d1); # Gamma def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi); def Gamma = d2 / (S * (IV * Sqrt(t))); # Theta def Theta = -(-(S*d2*IV*(.5000)/ (2*sqrt(t)))- (r*(exp(-r*t)*K))*N(d2)+(S*N(d1)*(.5000)))/365; # (.5000) variant less than .5 e(X/t) # Vega def Vega = (S*d2*sqrt(t))/100; #-----------------------------------------------------------------------------------------------------------------# # Visuals # Current Option Expiry Label AddLabel( ShowStrikeInfo, Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Color.GRAY ); # Center Strike Label AddLabel(ShowStrikeInfo, "Center Strike: " + AsDollars(CenterStrike), Color.GRAY); # Chain Depth Label AddLabel(ShowStrikeInfo, "Chain Depth: +/-" + StrikeDepth, Color.GRAY); # Strike Spacing Label AddLabel(ShowStrikeInfo and StrikeSpacing, "Strike Spacing: " + StrikeSpacing, Color.GRAY); # Call Open Interest plot CallOpenInterest = ItMCallOpenInterest + OTMCallOpenInterest; CallOpenInterest.SetHiding(!ShowLines); CallOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE); CallOpenInterest.SetDefaultColor(GlobalColor("Call")); AddLabel(ShowLabels, "CallOI: " + CallOpenInterest, GlobalColor("Call")); # Put Open Interest plot PutOpenInterest = -(ITMPutOpenInterest + OTMPutOpenInterest); # Make negative to flip under axis PutOpenInterest.SetHiding(!ShowLines); PutOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE); PutOpenInterest.SetDefaultColor(GlobalColor("Put")); AddLabel(ShowLabels, "PutOI: " + (ITMPutOpenInterest + OTMPutOpenInterest), GlobalColor("Put")); # Create a center line plot ZeroLine = 0; ZeroLine.SetDefaultColor(Color.WHITE); # Create Clouds AddCloud(if ShowClouds then CallOpenInterest else Double.NaN, if ShowClouds then Zeroline else Double.NaN, GlobalColor("CallCloud"), GlobalColor("PutCloud")); AddCloud(if ShowClouds then Zeroline else Double.NaN, if ShowClouds then PutOpenInterest else Double.NaN, GlobalColor("PutCloud"), GlobalColor("CallCloud")); # Hull Moving Average of Put Open Interest plot PutVolumeAverage = hullmovingavg(PutOpenInterest); PutVolumeAverage.SetHiding(!ShowLines); PutVolumeAverage.setdefaultcolor(color.ORANGE); PutVolumeAverage.setstyle(curve.MEDIUM_DASH); # Hull Moving Average of Call Open Interest plot CallVolumeAverage = hullmovingavg(CallOpenInterest); CallVolumeAverage.SetHiding(!ShowLines); CallVolumeAverage.setdefaultcolor(color.LIGHT_GREEN); CallVolumeAverage.setstyle(curve.MEDIUM_DASH); # Color Gradient of Total Average Open Interest plot TotalVolumeAverage = average(CallOpenInterest + PutOpenInterest); TotalVolumeAverage.SetHiding(!ShowLines); def colornormlength = 14; TotalVolumeAverage.setlineweight(3); TotalVolumeAverage.definecolor("Default", Color.YELLOW); TotalVolumeAverage.definecolor("Highest", Color.GREEN); TotalVolumeAverage.DefineColor("Lowest", Color.RED); TotalVolumeAverage.AssignNormGradientColor(colorNormLength, TotalVolumeAverage.Color("Lowest"), TotalVolumeAverage.Color("Highest")); AddCloud(if ShowClouds then TotalVolumeAverage else Double.NaN, if ShowClouds then Zeroline else Double.NaN, color.GREEN, color.RED); # Greeks AddLabel(ShowGreeks, "Delta = " + Delta, Color.WHITE); AddLabel(ShowGreeks, "Gamma = " + Gamma, Color.WHITE); AddLabel(ShowGreeks, "Theta = " + theta, Color.WHITE); AddLabel(ShowGreeks, "Vega = " + Vega, Color.WHITE);
I found this article it should help with knowing how to calculate the gamma at each strike, aswell as zero gamma line etc https://perfiliev.co.uk/market-commentary/how-to-calculate-gamma-exposure-and-zero-gamma-level/Okay, uploading what I have so far. I heavily commented everything and renamed a lot of things, trying my best to understand it all and how thinkscript works in general. It would be 100x easier to do this if we could just dynamically assign a string to a variable. That whole 'series' thing had me stumped for a bit but I found a snippet from Mobius which helps to understand:
"series is the expiry starting at 1 and raising by one for each next expiry"
Using that logic, I setup a dropdown switch to select that instead of a number.
Note this is only setup for open interest right now, the DataType switch does nothing yet.
Change all the open_interest() function calls to volume() for option volume instead.
Code:#hint: Options Hacker \n This study lets you scan the options chain for a series and given depth. \n<b>Warning: Setting the StrikeDepth to large values requires significant processing power, and will result in slow loading times.</b> #-----------------------------------------------------------------------------------------------------------------# # Settings # Colors DefineGlobalColor("Call", Color.GREEN); DefineGlobalColor("Put", Color.RED); DefineGlobalColor("CallCloud",Color.DARK_GREEN); DefineGlobalColor("PutCloud",Color.DARK_RED); #hint Mode: The mode to select an option symbol. \n AUTO will use the option series input code. This will not always work, set to MANUAL to use the center strike and spacing inputs to manually determine the option symbol. input Mode = {default AUTO, MANUAL}; #hint Series: The option expiration series to search. \n This value is used to determine the option symbol, and for the input to the SeriesVolatility(series = x) function where series is the expiry starting at 1 and raising by one for each next expiry. input Series = { default Weekly, Month1, Month2, Month3, Month4, Month5, Month6, Month7, Month8, Month9 }; #hint DataType: The type of option data to show. input DataType = {default OPEN_INTEREST, VOLUME}; #hint StrikeDepth: The level of depth to search a series. The search starts at the center strike price, and goes both ITM and OTM. input StrikeDepth = 5; #hint CenterStrikeOffset: The offset to use when calculating the center strike based on close price. \n Examples: \n 1 = nearest $1 interval \n 10 = nearest $10 interval. input CenterStrikeOffset = 1.0; #hint MaxStrikeSpacing: The maximum dollar amount between two adjacent contracts. input MaxStrikeSpacing = 25; #hint ManualCenterStrike: The starting price to use when in MANUAL mode. input ManualCenterStrike = 440; #hint ManualStrikeSpacing: The dollar amount between two adjacent contracts to use when in MANUAL mode. input ManualStrikeSpacing = 1.0; #hint ShowStrikeInfo: Show the strike info labels. input ShowStrikeInfo = yes; #hint ShowLabels: Show the open interest labels. input ShowLabels = yes; #hint ShowClouds: Show the open interest clouds. input ShowClouds = no; #hint ShowLines: Show the open interest lines. input ShowLines = yes; #hint ShowGreeks: Show the estimated Greek calculation labels for the latest bar. input ShowGreeks = yes; #-----------------------------------------------------------------------------------------------------------------# # Date, Symbol, and Strike # OptionSeries is the expiry starting at 1 and raising by one for each next expiry. def OptionSeries; switch (Series) { case Weekly: OptionSeries = 1; case Month1: OptionSeries = 2; case Month2: OptionSeries = 3; case Month3: OptionSeries = 4; case Month4: OptionSeries = 5; case Month5: OptionSeries = 6; case Month6: OptionSeries = 7; case Month7: OptionSeries = 8; case Month8: OptionSeries = 9; case Month9: OptionSeries = 10; }; # Open price at Regular Trading Hours def RTHopen = open(period = AggregationPeriod.DAY); # Current year, month, and day def CurrentYear = GetYear(); def CurrentMonth = GetMonth(); def CurrentDayOfMonth = GetDayOfMonth(GetYYYYMMDD()); # Get the first day of this month. 1 (Monday) to 7 (Sunday) def FirstDayThisMonth = GetDayOfWeek((CurrentYear * 10000) + (CurrentMonth * 100) + 1); # Get the first friday of this month def FirstFridayDOM = if FirstDayThisMonth < 6 then 6 - FirstDayThisMonth else if FirstDayThisMonth == 6 then 7 else 6 ; # Get the second, third, and fourth fridays of this month def SecondFridayDOM = FirstFridayDOM + 7; def ThirdFridayDOM = FirstFridayDOM + 14; def FourthFridayDOM = FirstFridayDOM + 21; # Pick up all Fridays of the current month for weekly options def RollDOM = FirstFridayDOM + 21; def ExpMonth1 = if RollDOM > CurrentDayOfMonth then CurrentMonth + OptionSeries - 1 else CurrentMonth + OptionSeries ; # Options month input def ExpMonth2 = if ExpMonth1 > 12 then ExpMonth1 - 12 else ExpMonth1; # Options year input def ExpYear = if ExpMonth1 > 12 then CurrentYear + 1 else CurrentYear; # First friday expiry calc def ExpDay1DOW = GetDayOfWeek(ExpYear * 10000 + ExpMonth2 * 100 + 1); def ExpFirstFridayDOM = if ExpDay1DOW < 6 then 6 - ExpDay1DOW else if ExpDay1DOW == 6 then 7 else 6 ; # Options code day of month input def ExpDOM = if CurrentDayOfMonth < ExpFirstFridayDOM -1 then FirstFridayDOM else if between(CurrentDayOfMonth, ExpFirstFridayDOM, SecondFridayDOM - 1) then SecondFridayDOM else if between(CurrentDayOfMonth, SecondFridayDOM, ThirdFridayDOM - 1) then ThirdFridayDOM else if between(CurrentDayOfMonth, ThirdFridayDOM, FourthFridayDOM - 1) then FourthFridayDOM else ExpFirstFridayDOM ; # Centerstrike def CenterStrike = if (Mode == Mode.AUTO and !IsNaN(close)) then Round(close / CenterStrikeOffset, 0) * CenterStrikeOffset else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualCenterStrike else CenterStrike[1] ; # Strike Spacing def StrikeSpacingC = fold i = 1 to MaxStrikeSpacing with spacing = 0 do if DataType == DataType.OPEN_INTEREST and !IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike + (MaxStrikeSpacing - i))) ) ) ) then MaxStrikeSpacing - i else spacing ; def StrikeSpacing = if (Mode == Mode.AUTO and !IsNaN(close)) then StrikeSpacingC else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualStrikeSpacing else StrikeSpacing[1] ; #-----------------------------------------------------------------------------------------------------------------# # Option Chain # Call Open Interest def ITMCallOpenInterest = fold itmCallOIIndex = 0 to StrikeDepth with itmCOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike - strikeSpacing * itmCallOIIndex)) ) ) ) then 0 else itmCOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike - strikeSpacing * itmCallOIIndex)) ) ) ; def OTMCallOpenInterest = fold otmCallOIIndex = 0 to StrikeDepth with otmCOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike + strikeSpacing * otmCallOIIndex)) ) ) ) then 0 else otmCOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("C", AsPrice(CenterStrike + strikeSpacing * otmCallOIIndex)) ) ) ; # Put Open Interest def ITMPutOpenInterest = fold itmPutOIIndex = 0 to StrikeDepth with itmPOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(CenterStrike - strikeSpacing * itmPutOIIndex)) ) ) ) then 0 else itmPOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(CenterStrike - strikeSpacing * itmPutOIIndex)) ) ) ; def OTMPutOpenInterest = fold otmPutOIIndex = 0 to StrikeDepth with otmPOI do if IsNaN( open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(CenterStrike + strikeSpacing * otmPutOIIndex)) ) ) ) then 0 else otmPOI + open_interest( Concat( Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Concat("P", AsPrice(centerStrike + strikeSpacing * otmPutOIIndex)) ) ) ; #-----------------------------------------------------------------------------------------------------------------# # Greeks Calculations # # K - Option strike price # N - Standard normal cumulative distribution function # r - Risk free interest rate # IV - Volatility of the underlying # S - Price of the underlying # t - Time to option's expiry # # Delta = N(d1) # d1 = (ln(S/K) + (r + (sqr(IV)/2))t) / (? (sqrt(t))) # Gamma = (d2) / S(IV(sqrt(t))) # d2 = e -(sqr(d1) / 2) / sqrt(2*pi) # Theta = ((S d2))IV) / 2 sqrt(t)) - (rK e(rt)N(d4)) # where phi(d3) = (exp(-(sqr(x)/2))) / (2 * sqrt(t)) # where d4 = d1 - IV(sqrt(t)) # Vega = S phi(d1) Sqrt(t) # TODO: Figure out how to dynamically get this value input DayToExpiry = 6; # Get the implied volatility for calculations # Input: series is the expiry starting at 1 and raising by 1 for each next expiry def IV = SeriesVolatility(series = OptionSeries); def K = CenterStrike; def S = close; def r = GetInterestRate(); def t = DayToExpiry / 365; def d1 = (Log(S / K) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)); script N { input data = 1; def a = AbsValue(data); def b1 = .31938153; def b2 = -.356563782; def b3 = 1.781477937; def b4 = -1.821255978; def b5 = 1.330274429; def b6 = .2316419; def e = 1 / (1 + b6 * a); def i = 1 - 1 / Sqrt(2 * Double.Pi) * Exp(-Power(a, 2) / 2) * (b1 * e + b2 * e * e + b3 * Power(e, 3) + b4 * Power(e, 4) + b5 * Power(e, 5)); plot CND = if data < 0 then 1 - i else i; } # TODO: These values don't quite line up # My background on options pricing models is not very good # Delta def Delta = N(d1); # Gamma def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi); def Gamma = d2 / (S * (IV * Sqrt(t))); # Theta def Theta = -(-(S*d2*IV*(.5000)/ (2*sqrt(t)))- (r*(exp(-r*t)*K))*N(d2)+(S*N(d1)*(.5000)))/365; # (.5000) variant less than .5 e(X/t) # Vega def Vega = (S*d2*sqrt(t))/100; #-----------------------------------------------------------------------------------------------------------------# # Visuals # Current Option Expiry Label AddLabel( ShowStrikeInfo, Concat(".", Concat(GetSymbolPart(), Concat( Concat(ExpYear - 2000, if ExpMonth2 <= 9 then Concat("0", ExpMonth2) else Concat("", ExpMonth2) ), if ExpDOM <= 9 then Concat("0", ExpDOM) else Concat("", ExpDOM) ) ) ), Color.GRAY ); # Center Strike Label AddLabel(ShowStrikeInfo, "Center Strike: " + AsDollars(CenterStrike), Color.GRAY); # Chain Depth Label AddLabel(ShowStrikeInfo, "Chain Depth: +/-" + StrikeDepth, Color.GRAY); # Strike Spacing Label AddLabel(ShowStrikeInfo and StrikeSpacing, "Strike Spacing: " + StrikeSpacing, Color.GRAY); # Call Open Interest plot CallOpenInterest = ItMCallOpenInterest + OTMCallOpenInterest; CallOpenInterest.SetHiding(!ShowLines); CallOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE); CallOpenInterest.SetDefaultColor(GlobalColor("Call")); AddLabel(ShowLabels, "CallOI: " + CallOpenInterest, GlobalColor("Call")); # Put Open Interest plot PutOpenInterest = -(ITMPutOpenInterest + OTMPutOpenInterest); # Make negative to flip under axis PutOpenInterest.SetHiding(!ShowLines); PutOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE); PutOpenInterest.SetDefaultColor(GlobalColor("Put")); AddLabel(ShowLabels, "PutOI: " + (ITMPutOpenInterest + OTMPutOpenInterest), GlobalColor("Put")); # Create a center line plot ZeroLine = 0; ZeroLine.SetDefaultColor(Color.WHITE); # Create Clouds AddCloud(if ShowClouds then CallOpenInterest else Double.NaN, if ShowClouds then Zeroline else Double.NaN, GlobalColor("CallCloud"), GlobalColor("PutCloud")); AddCloud(if ShowClouds then Zeroline else Double.NaN, if ShowClouds then PutOpenInterest else Double.NaN, GlobalColor("PutCloud"), GlobalColor("CallCloud")); # Hull Moving Average of Put Open Interest plot PutVolumeAverage = hullmovingavg(PutOpenInterest); PutVolumeAverage.SetHiding(!ShowLines); PutVolumeAverage.setdefaultcolor(color.ORANGE); PutVolumeAverage.setstyle(curve.MEDIUM_DASH); # Hull Moving Average of Call Open Interest plot CallVolumeAverage = hullmovingavg(CallOpenInterest); CallVolumeAverage.SetHiding(!ShowLines); CallVolumeAverage.setdefaultcolor(color.LIGHT_GREEN); CallVolumeAverage.setstyle(curve.MEDIUM_DASH); # Color Gradient of Total Average Open Interest plot TotalVolumeAverage = average(CallOpenInterest + PutOpenInterest); TotalVolumeAverage.SetHiding(!ShowLines); def colornormlength = 14; TotalVolumeAverage.setlineweight(3); TotalVolumeAverage.definecolor("Default", Color.YELLOW); TotalVolumeAverage.definecolor("Highest", Color.GREEN); TotalVolumeAverage.DefineColor("Lowest", Color.RED); TotalVolumeAverage.AssignNormGradientColor(colorNormLength, TotalVolumeAverage.Color("Lowest"), TotalVolumeAverage.Color("Highest")); AddCloud(if ShowClouds then TotalVolumeAverage else Double.NaN, if ShowClouds then Zeroline else Double.NaN, color.GREEN, color.RED); # Greeks AddLabel(ShowGreeks, "Delta = " + Delta, Color.WHITE); AddLabel(ShowGreeks, "Gamma = " + Gamma, Color.WHITE); AddLabel(ShowGreeks, "Theta = " + theta, Color.WHITE); AddLabel(ShowGreeks, "Vega = " + Vega, Color.WHITE);
Yes only for the ATM strike right nowit's great having a software engineer help with this, I definitely like what you've done so far with calculating OI and the greeks, it won't be completely accurate given these are theoretical calculations and there are bound to be nuisances but I definitely think you're on the right track. This can be used to calculate total volume and open interest but correct me if I'm wrong, the greek values are only for the center strike correct?
Thanks, I did see that already, just need to figure out how perform that calculation inside the fold for each contract.I found this article it should help with knowing how to calculate the gamma at each strike, aswell as zero gamma line etc https://perfiliev.co.uk/market-commentary/how-to-calculate-gamma-exposure-and-zero-gamma-level/
Great work and thank you for sharing! you should also follow Dr. Harlin @stephenharlinmd, right up your alley.Thanks, I did see that already, just need to figure out how perform that calculation inside the fold for each contract.
Oh that is super interesting! In case anyone wants to follow along this led me to:Great work and thank you for sharing! you should also follow Dr. Harlin @stephenharlinmd, right up your alley.
#hint: Options Hacker \n This study lets you scan the options chain for a series and given depth. \n<b>Warning: Setting the StrikeDepth to large values requires significant processing power, and will result in slow loading times.</b>
#-----------------------------------------------------------------------------------------------------------------#
# Settings
# Colors
DefineGlobalColor("Call", Color.GREEN);
DefineGlobalColor("Put", Color.RED);
DefineGlobalColor("CallCloud",Color.DARK_GREEN);
DefineGlobalColor("PutCloud",Color.DARK_RED);
#hint Mode: The mode to select an option symbol. \n AUTO will try to find the option symbol based on the Series and StrikeDepth inputs. \n MANUAL allow an override of the AUTO behavior by using the ManualCenterStrike and ManualStrikeSpacing inputs to determine the option symbol.
input Mode = {default AUTO, MANUAL};
#hint Series: The option expiration series to search. \n This value is used to determine the option symbol.
input Series = {
default Weekly,
Month1,
Month2,
Month3,
Month4,
Month5,
Month6,
Month7,
Month8,
Month9
};
#hint DataType: The type of option data to show.
input DataType = {default OpenInterest, Volume, GammaExposure};
#hint StrikeDepth: The level of depth to search a series. (+/- this far from ATM)
input StrikeDepth = 10;
#hint CenterStrikeOffset: The offset to use when calculating the center strike based on close price. \n Examples: \n 1 = nearest $1 interval \n 10 = nearest $10 interval.
input CenterStrikeOffset = 1.0;
#hint MaxStrikeSpacing: The maximum dollar amount between two adjacent contracts.
input MaxStrikeSpacing = 25;
#hint ManualCenterStrike: The starting price to use when in MANUAL mode.
input ManualCenterStrike = 440;
#hint ManualStrikeSpacing: The dollar amount between two adjacent contracts to use when in MANUAL mode.
input ManualStrikeSpacing = 1.0;
#hint ShowStrikeInfo: Show the strike info labels.
input ShowStrikeInfo = yes;
#hint ShowLabels: Show the open interest labels.
input ShowLabels = yes;
#hint ShowClouds: Show the open interest clouds.
input ShowClouds = no;
#hint ShowLines: Show the open interest lines.
input ShowLines = yes;
#hint ShowGreeks: Show the estimated Greek calculation labels for the latest bar.
input ShowGreeks = yes;
#-----------------------------------------------------------------------------------------------------------------#
# Date, Symbol, and Strike
# OptionSeries is the expiry starting at 1 and raising by one for each next expiry.
def OptionSeries;
switch (Series) {
case Weekly:
OptionSeries = 1;
case Month1:
OptionSeries = 2;
case Month2:
OptionSeries = 3;
case Month3:
OptionSeries = 4;
case Month4:
OptionSeries = 5;
case Month5:
OptionSeries = 6;
case Month6:
OptionSeries = 7;
case Month7:
OptionSeries = 8;
case Month8:
OptionSeries = 9;
case Month9:
OptionSeries = 10;
};
# Open price at Regular Trading Hours
def RTHopen = open(period = AggregationPeriod.DAY);
# Current year, month, day, and date
def CurrentYear = GetYear(); # number of current bar in CST
def CurrentMonth = GetMonth(); # 1 - 12
def CurrentDay = GetDay(); # 1 - 365 (366 for leap year)
def CurrentDate = GetYYYYMMDD(); # date of the current bar in the YYYYMMDD
# Current day of this month
def CurrentDayOfMonth = GetDayOfMonth(CurrentDate);
# Get the first day of this month - 1 (Monday) to 7 (Sunday)
def FirstDayThisMonth = GetDayOfWeek((CurrentYear * 10000) + (CurrentMonth * 100) + 1);
# Get the first upcoming friday
def FirstUpcomingFriday =
if FirstDayThisMonth < 6 then 6 - FirstDayThisMonth
else if FirstDayThisMonth == 6 then 7
else 6
;
# Get the second, third, and fourth upcoming fridays
def SecondUpcomingFriday = FirstUpcomingFriday + 7;
def ThirdUpcomingFriday = FirstUpcomingFriday + 14;
def FourthUpcomingFriday = FirstUpcomingFriday + 21;
# Pick up all Fridays of the current month for weekly options
def RollDOM = FirstUpcomingFriday + 21;
def ExpMonth1 =
if RollDOM > CurrentDayOfMonth then CurrentMonth + OptionSeries - 1
else CurrentMonth + OptionSeries
;
# Options month input
def ExpMonth2 = if ExpMonth1 > 12 then ExpMonth1 - 12 else ExpMonth1;
# Options year input
def ExpYear = if ExpMonth1 > 12 then CurrentYear + 1 else CurrentYear;
# First friday expiry calc
def ExpDay1DOW = GetDayOfWeek(ExpYear * 10000 + ExpMonth2 * 100 + 1);
def ExpFirstFridayDOM =
if ExpDay1DOW < 6 then 6 - ExpDay1DOW
else if ExpDay1DOW == 6 then 7
else 6
;
# Options code day of month input
def ExpDOM =
if CurrentDayOfMonth < ExpFirstFridayDOM -1 then FirstUpcomingFriday
else if between(CurrentDayOfMonth, ExpFirstFridayDOM, SecondUpcomingFriday - 1) then SecondUpcomingFriday
else if between(CurrentDayOfMonth, SecondUpcomingFriday, ThirdUpcomingFriday - 1) then ThirdUpcomingFriday
else if between(CurrentDayOfMonth, ThirdUpcomingFriday, FourthUpcomingFriday - 1) then FourthUpcomingFriday
else ExpFirstFridayDOM
;
# Centerstrike
def CenterStrike =
if (Mode == Mode.AUTO and !IsNaN(close)) then Round(close / CenterStrikeOffset, 0) * CenterStrikeOffset
else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualCenterStrike
else CenterStrike[1]
;
# This is still some voodoo to me ...
def OptionExpiryDate = ExpYear * 10000 + ExpMonth2 * 100 + ExpDOM + 1;
# Strike Spacing
def StrikeSpacingC =
fold i = 1 to MaxStrikeSpacing
with spacing = 0
do if DataType == DataType.OpenInterest or DataType == DataType.GammaExposure and !IsNaN(
open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (MaxStrikeSpacing - i)))
)
then MaxStrikeSpacing - i
else if DataType == DataType.Volume and !IsNaN(
volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (MaxStrikeSpacing - i)))
)
then MaxStrikeSpacing - i
else spacing
;
def StrikeSpacing =
if (Mode == Mode.AUTO and !IsNaN(close)) then StrikeSpacingC
else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualStrikeSpacing
else StrikeSpacing[1]
;
# Testing ---------
#plot Date = GetYYYYMMDD();
#plot Expiry = (OptionExpiryDate);
#plot DTE = AbsValue(CountTradingDays(Date, Expiry));
#-----------------------------------------------------------------------------------------------------------------#
# Option Chain Data Gathering
# Total Put Open Interest for selected chain depth and expiry series
def TotalPutOpenInterest =
fold poiIndex = -(StrikeDepth) to StrikeDepth
with poi = 0
do
if DataType == DataType.OpenInterest and !IsNaN(
open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * poiIndex)))
)
then poi + open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * poiIndex)))
else 0
;
# Total Call Open Interest for selected chain depth and expiry series
def TotalCallOpenInterest =
fold coiIndex = -(StrikeDepth) to StrikeDepth
with coi = 0
do
if DataType == DataType.OpenInterest and !IsNaN(
open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * coiIndex)))
)
then coi + open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * coiIndex)))
else 0
;
# Total Put Volume for selected chain depth and expiry series
def TotalPutVolume =
fold pvIndex = -(StrikeDepth) to StrikeDepth
with pv = 0
do
if DataType == DataType.Volume and !IsNaN(
volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + StrikeSpacing * pvIndex))
)
then pv + volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + StrikeSpacing * pvIndex))
else 0
;
# Total Call Open Interest for selected chain depth and expiry series
def TotalCallVolume =
fold cvIndex = -(StrikeDepth) to StrikeDepth
with cv = 0
do
if DataType == DataType.Volume and !IsNaN(
volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + StrikeSpacing * cvIndex))
)
then cv + volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + StrikeSpacing * cvIndex))
else 0
;
#-----------------------------------------------------------------------------------------------------------------#
# Greeks Calculations
#
# K - Option strike price
# N - Standard normal cumulative distribution function
# r - Risk free interest rate
# IV - Volatility of the underlying
# S - Price of the underlying
# t - Time to option's expiry
#
# d1 = (ln(S/K) + (r + (sqr(IV)/2))t) / (? (sqrt(t)))
# d2 = e -(sqr(d1) / 2) / sqrt(2*pi)
#
# Delta = N(d1)
# Gamma = (d2) / S(IV(sqrt(t)))
# Theta = ((S d2))IV) / 2 sqrt(t)) - (rK e(rt)N(d4))
# where phi(d3) = (exp(-(sqr(x)/2))) / (2 * sqrt(t))
# where d4 = d1 - IV(sqrt(t))
# Vega = S phi(d1) Sqrt(t)
# TODO: Figure out how to dynamically get this value
def DayToExpiry = AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1);
# Get the implied volatility for calculations
# Input: series is the expiry starting at 1 and raising by 1 for each next expiry
def IV = SeriesVolatility(series = OptionSeries);
def K = CenterStrike;
def S = close;
def r = GetInterestRate();
def t = (DayToExpiry / 365);
def d1 = (Log(S / K) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t));
def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi);
script N {
input data = 1;
def a = AbsValue(data);
def b1 = .31938153;
def b2 = -.356563782;
def b3 = 1.781477937;
def b4 = -1.821255978;
def b5 = 1.330274429;
def b6 = .2316419;
def e = 1 / (1 + b6 * a);
def i = 1 - 1 / Sqrt(2 * Double.Pi) * Exp(-Power(a, 2) / 2) *
(b1 * e + b2 * e * e + b3 *
Power(e, 3) + b4 * Power(e, 4) + b5 * Power(e, 5));
plot CND = if data < 0
then 1 - i
else i;
}
# TODO: These values don't quite line up
# My background on options pricing models is not very good
# Delta
def Delta = N(d1);
# Gamma
def Gamma = d2 / (S * (IV * Sqrt(t)));
# Theta
def Theta = -(-(S*d2*IV*(.5000)/
(2*sqrt(t)))-
(r*(exp(-r*t)*K))*N(d2)+(S*N(d1)*(.5000)))/365;
# (.5000) variant less than .5 e(X/t)
# Vega
def Vega = (S*d2*sqrt(t))/100;
script SpotGamma {
input DTE = 0;
input K = 0;
input Series = 0;
def S = close;
def t = (DTE / 365);
def IV = SeriesVolatility(series = Series);
def d1 = (Log(S / K) + ((GetInterestRate() + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t));
# Delta
def Delta = N(d1);
# Gamma
def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi);
def Gamma = d2 / (S * (IV * Sqrt(t)));
plot SpotGamma = Gamma;
}
# Total Put Gamma Exposure for selected chain depth and expiry series
def TotalPutGammaExposure =
fold pgexIndex = -(StrikeDepth) to StrikeDepth
with pgex = 0
do
if DataType == DataType.GammaExposure and !IsNaN(
open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * pgexIndex)))
)
then pgex +
open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * pgexIndex))) *
Sqr(OptionPrice((CenterStrike + (StrikeSpacing * pgexIndex)), yes, AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), close, SeriesVolatility(series = OptionSeries), no, 0.0, GetInterestRate())) * 0.01 *
#SpotGamma(AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), (CenterStrike + (StrikeSpacing * pgexIndex)), OptionSeries) * # Honestly why cant I put the index inside here :(
(Exp(-(Sqr((Log(close / (CenterStrike + (StrikeSpacing * pgexIndex))) + ((GetInterestRate() + (Sqr(SeriesVolatility(series = OptionSeries)) / 2)) * (AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))) / (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365)))) / 2)) / Sqrt(2 * Double.Pi) / (close * (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))))) *
100 *
-1
else 0
;
# Total Call Gamma Exposure for selected chain depth and expiry series
def TotalCallGammaExposure =
fold cgexIndex = -(StrikeDepth) to StrikeDepth
with cgex = 0
do
if DataType == DataType.GammaExposure and !IsNaN(
open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * cgexIndex)))
)
then cgex +
open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * cgexIndex))) *
Sqr(OptionPrice((CenterStrike + (StrikeSpacing * cgexIndex)), no, AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), close, SeriesVolatility(series = OptionSeries), no, 0.0, GetInterestRate())) * 0.01 *
#SpotGamma(AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), (CenterStrike + (StrikeSpacing * 0)), OptionSeries) * # Honestly why cant I put the index inside here :(
(Exp(-(Sqr((Log(close / (CenterStrike + (StrikeSpacing * cgexIndex))) + ((GetInterestRate() + (Sqr(SeriesVolatility(series = OptionSeries)) / 2)) * (AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))) / (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365)))) / 2)) / Sqrt(2 * Double.Pi) / (close * (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))))) *
100
else 0
;
plot GammaExposure = TotalPutGammaExposure + TotalCallGammaExposure;
#-----------------------------------------------------------------------------------------------------------------#
# Visuals
# Selected DataType
AddLabel(yes, DataType, Color.LIGHT_GRAY);
# Selected Series
AddLabel(yes, Series, Color.LIGHT_GRAY);
# Center Strike Label
AddLabel(ShowStrikeInfo, "Center Strike: " + AsDollars(CenterStrike), Color.LIGHT_GRAY);
# Chain Depth Label
AddLabel(ShowStrikeInfo, "Strike Depth: +/-" + StrikeDepth, Color.LIGHT_GRAY);
# Strike Spacing Label
AddLabel(ShowStrikeInfo, "Strike Spacing: " + AsDollars(StrikeSpacing), Color.LIGHT_GRAY);
# Current ATM Options Labels
Addlabel(ShowStrikeInfo, "ATM Put: " + ("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike), Color.LIGHT_RED);
Addlabel(ShowStrikeInfo, "ATM Call: " + ("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike), Color.LIGHT_GREEN);
# Create a center line
plot ZeroLine = 0;
ZeroLine.SetDefaultColor(Color.WHITE);
# Call Open Interest
plot CallOpenInterest = TotalCallOpenInterest;
CallOpenInterest.SetHiding(!ShowLines and DataType == DataType.OpenInterest);
CallOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE);
CallOpenInterest.SetDefaultColor(GlobalColor("Call"));
AddLabel(ShowLabels and DataType == DataType.OpenInterest, "CallOI: " + CallOpenInterest, GlobalColor("Call"));
# Put Open Interest
plot PutOpenInterest = -(TotalPutOpenInterest); # Make negative to flip under axis
PutOpenInterest.SetHiding(!ShowLines and DataType == DataType.OpenInterest);
PutOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE);
PutOpenInterest.SetDefaultColor(GlobalColor("Put"));
AddLabel(ShowLabels and DataType == DataType.OpenInterest, "PutOI: " + TotalPutOpenInterest, GlobalColor("Put"));
# Create Clouds for Open Interest
AddCloud(
if ShowClouds and DataType == DataType.OpenInterest then CallOpenInterest else Double.NaN,
if ShowClouds and DataType == DataType.OpenInterest then Zeroline else Double.NaN,
GlobalColor("CallCloud"), GlobalColor("PutCloud")
);
AddCloud(
if ShowClouds and DataType == DataType.OpenInterest then Zeroline else Double.NaN,
if ShowClouds and DataType == DataType.OpenInterest then PutOpenInterest else Double.NaN,
GlobalColor("PutCloud"), GlobalColor("CallCloud")
);
# Hull Moving Average of Put Open Interest
plot PutOpenInterestAverage = hullmovingavg(PutOpenInterest);
PutOpenInterestAverage.SetHiding(!ShowLines and DataType == DataType.OpenInterest);
PutOpenInterestAverage.SetDefaultColor(Color.ORANGE);
PutOpenInterestAverage.SetStyle(Curve.MEDIUM_DASH);
# Hull Moving Average of Call Open Interest
plot CallOpenInterestAverage = hullmovingavg(CallOpenInterest);
CallOpenInterestAverage.SetHiding(!ShowLines and DataType == DataType.OpenInterest);
CallOpenInterestAverage.SetDefaultColor(Color.LIGHT_GREEN);
CallOpenInterestAverage.SetStyle(Curve.MEDIUM_DASH);
# Color Gradient of Total Average Open Interest
#plot TotalOpenInterestAverage = average(CallOpenInterest + PutOpenInterest);
#TotalOpenInterestAverage.SetHiding(!ShowLines and DataType == DataType.OpenInterest);
#TotalOpenInterestAverage.SetLineWeight(3);
#TotalOpenInterestAverage.DefineColor("Default", Color.YELLOW);
#TotalOpenInterestAverage.DefineColor("Highest", Color.GREEN);
#TotalOpenInterestAverage.DefineColor("Lowest", Color.RED);
#TotalOpenInterestAverage.AssignNormGradientColor(14, TotalOpenInterestAverage.Color("Lowest"), TotalOpenInterestAverage.Color("Highest"));
#AddCloud(
# if ShowClouds and DataType == DataType.OpenInterest then TotalOpenInterestAverage else Double.NaN,
# if ShowClouds and DataType == DataType.OpenInterest then Zeroline else Double.NaN,
# color.GREEN, color.RED
#);
# Call Volume
plot CallVolume = TotalCallVolume;
CallVolume.SetHiding(!ShowLines and DataType == DataType.Volume);
CallVolume.SetPaintingStrategy(PaintingStrategy.LINE);
CallVolume.SetDefaultColor(GlobalColor("Call"));
AddLabel(ShowLabels and DataType == DataType.Volume, "CallVol: " + CallVolume, GlobalColor("Call"));
# Put Volume
plot PutVolume = -(TotalPutVolume); # Make negative to flip under axis
PutVolume.SetHiding(!ShowLines and DataType == DataType.Volume);
PutVolume.SetPaintingStrategy(PaintingStrategy.LINE);
PutVolume.SetDefaultColor(GlobalColor("Put"));
AddLabel(ShowLabels and DataType == DataType.Volume, "PutVol: " + TotalPutVolume, GlobalColor("Put"));
# Create Clouds for Volume
AddCloud(
if ShowClouds and DataType == DataType.Volume then CallVolume else Double.NaN,
if ShowClouds and DataType == DataType.Volume then Zeroline else Double.NaN,
GlobalColor("CallCloud"), GlobalColor("PutCloud")
);
AddCloud(
if ShowClouds and DataType == DataType.Volume then Zeroline else Double.NaN,
if ShowClouds and DataType == DataType.Volume then PutVolume else Double.NaN,
GlobalColor("PutCloud"), GlobalColor("CallCloud")
);
# Hull Moving Average of Put Volume
plot PutVolumeAverage = hullmovingavg(PutVolume);
PutVolumeAverage.SetHiding(!ShowLines and DataType == DataType.Volume);
PutVolumeAverage.SetDefaultColor(Color.ORANGE);
PutVolumeAverage.SetStyle(Curve.MEDIUM_DASH);
# Hull Moving Average of Call Volume
plot CallVolumeAverage = hullmovingavg(CallVolume);
CallVolumeAverage.SetHiding(!ShowLines and DataType == DataType.Volume);
CallVolumeAverage.SetDefaultColor(Color.LIGHT_GREEN);
CallVolumeAverage.SetStyle(Curve.MEDIUM_DASH);
# Color Gradient of Total Average Volume
#plot TotalVolumeAverage = average(CallVolume + PutVolume);
#TotalVolumeAverage.SetHiding(!ShowLines and DataType == DataType.Volume);
#TotalVolumeAverage.SetLineWeight(3);
#TotalVolumeAverage.DefineColor("Default", Color.YELLOW);
#TotalVolumeAverage.DefineColor("Highest", Color.GREEN);
#TotalVolumeAverage.DefineColor("Lowest", Color.RED);
#TotalVolumeAverage.AssignNormGradientColor(14, TotalVolumeAverage.Color("Lowest"), TotalVolumeAverage.Color("Highest"));
#AddCloud(
# if ShowClouds and DataType == DataType.Volume then TotalVolumeAverage else Double.NaN,
# if ShowClouds and DataType == DataType.Volume then Zeroline else Double.NaN,
# Color.GREEN, Color.RED
#);
# Greeks Labels
AddLabel(ShowGreeks, "Delta: " + Delta, Color.WHITE);
AddLabel(ShowGreeks, "Gamma: " + Gamma, Color.WHITE);
AddLabel(ShowGreeks, "Theta: " + Theta, Color.WHITE);
AddLabel(ShowGreeks, "Vega: " + Vega, Color.WHITE);
I think squeeze @SqueezeMetrics provides a good explaination for Gamma Exposure (GEX) https://squeezemetrics.com/monitor/docsHere is an updated version.
I've massively simplified the fold logic in my opinion. (I think there was a bug in the old code anyways where I was adding ATM data twice)
I've also added gamma exposure based on the math here: https://perfiliev.co.uk/market-commentary/how-to-calculate-gamma-exposure-and-zero-gamma-level/
Although I'm not sure its correct, as the math is quite complicated, and my background is not in finance.
I'm also not sure how to interpret the data just yet ...
It doesn't make it any easier that I realized you can't call a custom script inside the fold using the index of the fold
As a workaround, I had to try and implement the formula for deriving the spot gamma in a single line within the fold, as part of the larger calculation for the spot gamma exposure at that level. This is the mobius Greeks formula (which uses Black Scholes), using the OptionPrice() and SeriesVolatility() as inputs.
Some Notes:
Screenshot:
- Ideally to get the 'Total Market Gamma Exposure' we would need to perform this calculation on all strikes, for every expiry, for every day. I have only tested going +/- 5 to 10 strikes out, for weekly series so far, and it already takes quite a bit of time. For reference I'm running an i9-9900k with 64GB DDR4 RAM, Running this at 5 strike depth weekly on SPY is taking around 15 -20 minutes (hard to tell when its actually done running so I'm just going by when my pc fans/water cooler fans stop spinning at max rpm lol)
- For number 1 to work, we would need to further modify the date/strike logic to be able to pick up more than just next series expiries. I also don't know how this effects the SeriesVolatility function.
- Maybe another approach would be to modify this so that we can pick up the first couple close to the money strikes, and then skip to picking up a couple way out of the money strikes to get a better overall picture.
Code:
Code:#hint: Options Hacker \n This study lets you scan the options chain for a series and given depth. \n<b>Warning: Setting the StrikeDepth to large values requires significant processing power, and will result in slow loading times.</b> #-----------------------------------------------------------------------------------------------------------------# # Settings # Colors DefineGlobalColor("Call", Color.GREEN); DefineGlobalColor("Put", Color.RED); DefineGlobalColor("CallCloud",Color.DARK_GREEN); DefineGlobalColor("PutCloud",Color.DARK_RED); #hint Mode: The mode to select an option symbol. \n AUTO will try to find the option symbol based on the Series and StrikeDepth inputs. \n MANUAL allow an override of the AUTO behavior by using the ManualCenterStrike and ManualStrikeSpacing inputs to determine the option symbol. input Mode = {default AUTO, MANUAL}; #hint Series: The option expiration series to search. \n This value is used to determine the option symbol. input Series = { default Weekly, Month1, Month2, Month3, Month4, Month5, Month6, Month7, Month8, Month9 }; #hint DataType: The type of option data to show. input DataType = {default OpenInterest, Volume, GammaExposure}; #hint StrikeDepth: The level of depth to search a series. (+/- this far from ATM) input StrikeDepth = 10; #hint CenterStrikeOffset: The offset to use when calculating the center strike based on close price. \n Examples: \n 1 = nearest $1 interval \n 10 = nearest $10 interval. input CenterStrikeOffset = 1.0; #hint MaxStrikeSpacing: The maximum dollar amount between two adjacent contracts. input MaxStrikeSpacing = 25; #hint ManualCenterStrike: The starting price to use when in MANUAL mode. input ManualCenterStrike = 440; #hint ManualStrikeSpacing: The dollar amount between two adjacent contracts to use when in MANUAL mode. input ManualStrikeSpacing = 1.0; #hint ShowStrikeInfo: Show the strike info labels. input ShowStrikeInfo = yes; #hint ShowLabels: Show the open interest labels. input ShowLabels = yes; #hint ShowClouds: Show the open interest clouds. input ShowClouds = no; #hint ShowLines: Show the open interest lines. input ShowLines = yes; #hint ShowGreeks: Show the estimated Greek calculation labels for the latest bar. input ShowGreeks = yes; #-----------------------------------------------------------------------------------------------------------------# # Date, Symbol, and Strike # OptionSeries is the expiry starting at 1 and raising by one for each next expiry. def OptionSeries; switch (Series) { case Weekly: OptionSeries = 1; case Month1: OptionSeries = 2; case Month2: OptionSeries = 3; case Month3: OptionSeries = 4; case Month4: OptionSeries = 5; case Month5: OptionSeries = 6; case Month6: OptionSeries = 7; case Month7: OptionSeries = 8; case Month8: OptionSeries = 9; case Month9: OptionSeries = 10; }; # Open price at Regular Trading Hours def RTHopen = open(period = AggregationPeriod.DAY); # Current year, month, day, and date def CurrentYear = GetYear(); # number of current bar in CST def CurrentMonth = GetMonth(); # 1 - 12 def CurrentDay = GetDay(); # 1 - 365 (366 for leap year) def CurrentDate = GetYYYYMMDD(); # date of the current bar in the YYYYMMDD # Current day of this month def CurrentDayOfMonth = GetDayOfMonth(CurrentDate); # Get the first day of this month - 1 (Monday) to 7 (Sunday) def FirstDayThisMonth = GetDayOfWeek((CurrentYear * 10000) + (CurrentMonth * 100) + 1); # Get the first upcoming friday def FirstUpcomingFriday = if FirstDayThisMonth < 6 then 6 - FirstDayThisMonth else if FirstDayThisMonth == 6 then 7 else 6 ; # Get the second, third, and fourth upcoming fridays def SecondUpcomingFriday = FirstUpcomingFriday + 7; def ThirdUpcomingFriday = FirstUpcomingFriday + 14; def FourthUpcomingFriday = FirstUpcomingFriday + 21; # Pick up all Fridays of the current month for weekly options def RollDOM = FirstUpcomingFriday + 21; def ExpMonth1 = if RollDOM > CurrentDayOfMonth then CurrentMonth + OptionSeries - 1 else CurrentMonth + OptionSeries ; # Options month input def ExpMonth2 = if ExpMonth1 > 12 then ExpMonth1 - 12 else ExpMonth1; # Options year input def ExpYear = if ExpMonth1 > 12 then CurrentYear + 1 else CurrentYear; # First friday expiry calc def ExpDay1DOW = GetDayOfWeek(ExpYear * 10000 + ExpMonth2 * 100 + 1); def ExpFirstFridayDOM = if ExpDay1DOW < 6 then 6 - ExpDay1DOW else if ExpDay1DOW == 6 then 7 else 6 ; # Options code day of month input def ExpDOM = if CurrentDayOfMonth < ExpFirstFridayDOM -1 then FirstUpcomingFriday else if between(CurrentDayOfMonth, ExpFirstFridayDOM, SecondUpcomingFriday - 1) then SecondUpcomingFriday else if between(CurrentDayOfMonth, SecondUpcomingFriday, ThirdUpcomingFriday - 1) then ThirdUpcomingFriday else if between(CurrentDayOfMonth, ThirdUpcomingFriday, FourthUpcomingFriday - 1) then FourthUpcomingFriday else ExpFirstFridayDOM ; # Centerstrike def CenterStrike = if (Mode == Mode.AUTO and !IsNaN(close)) then Round(close / CenterStrikeOffset, 0) * CenterStrikeOffset else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualCenterStrike else CenterStrike[1] ; # This is still some voodoo to me ... def OptionExpiryDate = ExpYear * 10000 + ExpMonth2 * 100 + ExpDOM + 1; # Strike Spacing def StrikeSpacingC = fold i = 1 to MaxStrikeSpacing with spacing = 0 do if DataType == DataType.OpenInterest or DataType == DataType.GammaExposure and !IsNaN( open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (MaxStrikeSpacing - i))) ) then MaxStrikeSpacing - i else if DataType == DataType.Volume and !IsNaN( volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (MaxStrikeSpacing - i))) ) then MaxStrikeSpacing - i else spacing ; def StrikeSpacing = if (Mode == Mode.AUTO and !IsNaN(close)) then StrikeSpacingC else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualStrikeSpacing else StrikeSpacing[1] ; # Testing --------- #plot Date = GetYYYYMMDD(); #plot Expiry = (OptionExpiryDate); #plot DTE = AbsValue(CountTradingDays(Date, Expiry)); #-----------------------------------------------------------------------------------------------------------------# # Option Chain Data Gathering # Total Put Open Interest for selected chain depth and expiry series def TotalPutOpenInterest = fold poiIndex = -(StrikeDepth) to StrikeDepth with poi = 0 do if DataType == DataType.OpenInterest and !IsNaN( open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * poiIndex))) ) then poi + open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * poiIndex))) else 0 ; # Total Call Open Interest for selected chain depth and expiry series def TotalCallOpenInterest = fold coiIndex = -(StrikeDepth) to StrikeDepth with coi = 0 do if DataType == DataType.OpenInterest and !IsNaN( open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * coiIndex))) ) then coi + open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * coiIndex))) else 0 ; # Total Put Volume for selected chain depth and expiry series def TotalPutVolume = fold pvIndex = -(StrikeDepth) to StrikeDepth with pv = 0 do if DataType == DataType.Volume and !IsNaN( volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + StrikeSpacing * pvIndex)) ) then pv + volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + StrikeSpacing * pvIndex)) else 0 ; # Total Call Open Interest for selected chain depth and expiry series def TotalCallVolume = fold cvIndex = -(StrikeDepth) to StrikeDepth with cv = 0 do if DataType == DataType.Volume and !IsNaN( volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + StrikeSpacing * cvIndex)) ) then cv + volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + StrikeSpacing * cvIndex)) else 0 ; #-----------------------------------------------------------------------------------------------------------------# # Greeks Calculations # # K - Option strike price # N - Standard normal cumulative distribution function # r - Risk free interest rate # IV - Volatility of the underlying # S - Price of the underlying # t - Time to option's expiry # # d1 = (ln(S/K) + (r + (sqr(IV)/2))t) / (? (sqrt(t))) # d2 = e -(sqr(d1) / 2) / sqrt(2*pi) # # Delta = N(d1) # Gamma = (d2) / S(IV(sqrt(t))) # Theta = ((S d2))IV) / 2 sqrt(t)) - (rK e(rt)N(d4)) # where phi(d3) = (exp(-(sqr(x)/2))) / (2 * sqrt(t)) # where d4 = d1 - IV(sqrt(t)) # Vega = S phi(d1) Sqrt(t) # TODO: Figure out how to dynamically get this value def DayToExpiry = AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1); # Get the implied volatility for calculations # Input: series is the expiry starting at 1 and raising by 1 for each next expiry def IV = SeriesVolatility(series = OptionSeries); def K = CenterStrike; def S = close; def r = GetInterestRate(); def t = (DayToExpiry / 365); def d1 = (Log(S / K) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)); def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi); script N { input data = 1; def a = AbsValue(data); def b1 = .31938153; def b2 = -.356563782; def b3 = 1.781477937; def b4 = -1.821255978; def b5 = 1.330274429; def b6 = .2316419; def e = 1 / (1 + b6 * a); def i = 1 - 1 / Sqrt(2 * Double.Pi) * Exp(-Power(a, 2) / 2) * (b1 * e + b2 * e * e + b3 * Power(e, 3) + b4 * Power(e, 4) + b5 * Power(e, 5)); plot CND = if data < 0 then 1 - i else i; } # TODO: These values don't quite line up # My background on options pricing models is not very good # Delta def Delta = N(d1); # Gamma def Gamma = d2 / (S * (IV * Sqrt(t))); # Theta def Theta = -(-(S*d2*IV*(.5000)/ (2*sqrt(t)))- (r*(exp(-r*t)*K))*N(d2)+(S*N(d1)*(.5000)))/365; # (.5000) variant less than .5 e(X/t) # Vega def Vega = (S*d2*sqrt(t))/100; script SpotGamma { input DTE = 0; input K = 0; input Series = 0; def S = close; def t = (DTE / 365); def IV = SeriesVolatility(series = Series); def d1 = (Log(S / K) + ((GetInterestRate() + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)); # Delta def Delta = N(d1); # Gamma def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi); def Gamma = d2 / (S * (IV * Sqrt(t))); plot SpotGamma = Gamma; } # Total Put Gamma Exposure for selected chain depth and expiry series def TotalPutGammaExposure = fold pgexIndex = -(StrikeDepth) to StrikeDepth with pgex = 0 do if DataType == DataType.GammaExposure and !IsNaN( open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * pgexIndex))) ) then pgex + open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * pgexIndex))) * Sqr(OptionPrice((CenterStrike + (StrikeSpacing * pgexIndex)), yes, AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), close, SeriesVolatility(series = OptionSeries), no, 0.0, GetInterestRate())) * 0.01 * #SpotGamma(AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), (CenterStrike + (StrikeSpacing * pgexIndex)), OptionSeries) * # Honestly why cant I put the index inside here :( (Exp(-(Sqr((Log(close / (CenterStrike + (StrikeSpacing * pgexIndex))) + ((GetInterestRate() + (Sqr(SeriesVolatility(series = OptionSeries)) / 2)) * (AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))) / (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365)))) / 2)) / Sqrt(2 * Double.Pi) / (close * (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))))) * 100 * -1 else 0 ; # Total Call Gamma Exposure for selected chain depth and expiry series def TotalCallGammaExposure = fold cgexIndex = -(StrikeDepth) to StrikeDepth with cgex = 0 do if DataType == DataType.GammaExposure and !IsNaN( open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * cgexIndex))) ) then cgex + open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * cgexIndex))) * Sqr(OptionPrice((CenterStrike + (StrikeSpacing * cgexIndex)), no, AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), close, SeriesVolatility(series = OptionSeries), no, 0.0, GetInterestRate())) * 0.01 * #SpotGamma(AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), (CenterStrike + (StrikeSpacing * 0)), OptionSeries) * # Honestly why cant I put the index inside here :( (Exp(-(Sqr((Log(close / (CenterStrike + (StrikeSpacing * cgexIndex))) + ((GetInterestRate() + (Sqr(SeriesVolatility(series = OptionSeries)) / 2)) * (AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))) / (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365)))) / 2)) / Sqrt(2 * Double.Pi) / (close * (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))))) * 100 else 0 ; plot GammaExposure = TotalPutGammaExposure + TotalCallGammaExposure; #-----------------------------------------------------------------------------------------------------------------# # Visuals # Selected DataType AddLabel(yes, DataType, Color.LIGHT_GRAY); # Selected Series AddLabel(yes, Series, Color.LIGHT_GRAY); # Center Strike Label AddLabel(ShowStrikeInfo, "Center Strike: " + AsDollars(CenterStrike), Color.LIGHT_GRAY); # Chain Depth Label AddLabel(ShowStrikeInfo, "Strike Depth: +/-" + StrikeDepth, Color.LIGHT_GRAY); # Strike Spacing Label AddLabel(ShowStrikeInfo, "Strike Spacing: " + AsDollars(StrikeSpacing), Color.LIGHT_GRAY); # Current ATM Options Labels Addlabel(ShowStrikeInfo, "ATM Put: " + ("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike), Color.LIGHT_RED); Addlabel(ShowStrikeInfo, "ATM Call: " + ("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike), Color.LIGHT_GREEN); # Create a center line plot ZeroLine = 0; ZeroLine.SetDefaultColor(Color.WHITE); # Call Open Interest plot CallOpenInterest = TotalCallOpenInterest; CallOpenInterest.SetHiding(!ShowLines and DataType == DataType.OpenInterest); CallOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE); CallOpenInterest.SetDefaultColor(GlobalColor("Call")); AddLabel(ShowLabels and DataType == DataType.OpenInterest, "CallOI: " + CallOpenInterest, GlobalColor("Call")); # Put Open Interest plot PutOpenInterest = -(TotalPutOpenInterest); # Make negative to flip under axis PutOpenInterest.SetHiding(!ShowLines and DataType == DataType.OpenInterest); PutOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE); PutOpenInterest.SetDefaultColor(GlobalColor("Put")); AddLabel(ShowLabels and DataType == DataType.OpenInterest, "PutOI: " + TotalPutOpenInterest, GlobalColor("Put")); # Create Clouds for Open Interest AddCloud( if ShowClouds and DataType == DataType.OpenInterest then CallOpenInterest else Double.NaN, if ShowClouds and DataType == DataType.OpenInterest then Zeroline else Double.NaN, GlobalColor("CallCloud"), GlobalColor("PutCloud") ); AddCloud( if ShowClouds and DataType == DataType.OpenInterest then Zeroline else Double.NaN, if ShowClouds and DataType == DataType.OpenInterest then PutOpenInterest else Double.NaN, GlobalColor("PutCloud"), GlobalColor("CallCloud") ); # Hull Moving Average of Put Open Interest plot PutOpenInterestAverage = hullmovingavg(PutOpenInterest); PutOpenInterestAverage.SetHiding(!ShowLines and DataType == DataType.OpenInterest); PutOpenInterestAverage.SetDefaultColor(Color.ORANGE); PutOpenInterestAverage.SetStyle(Curve.MEDIUM_DASH); # Hull Moving Average of Call Open Interest plot CallOpenInterestAverage = hullmovingavg(CallOpenInterest); CallOpenInterestAverage.SetHiding(!ShowLines and DataType == DataType.OpenInterest); CallOpenInterestAverage.SetDefaultColor(Color.LIGHT_GREEN); CallOpenInterestAverage.SetStyle(Curve.MEDIUM_DASH); # Color Gradient of Total Average Open Interest #plot TotalOpenInterestAverage = average(CallOpenInterest + PutOpenInterest); #TotalOpenInterestAverage.SetHiding(!ShowLines and DataType == DataType.OpenInterest); #TotalOpenInterestAverage.SetLineWeight(3); #TotalOpenInterestAverage.DefineColor("Default", Color.YELLOW); #TotalOpenInterestAverage.DefineColor("Highest", Color.GREEN); #TotalOpenInterestAverage.DefineColor("Lowest", Color.RED); #TotalOpenInterestAverage.AssignNormGradientColor(14, TotalOpenInterestAverage.Color("Lowest"), TotalOpenInterestAverage.Color("Highest")); #AddCloud( # if ShowClouds and DataType == DataType.OpenInterest then TotalOpenInterestAverage else Double.NaN, # if ShowClouds and DataType == DataType.OpenInterest then Zeroline else Double.NaN, # color.GREEN, color.RED #); # Call Volume plot CallVolume = TotalCallVolume; CallVolume.SetHiding(!ShowLines and DataType == DataType.Volume); CallVolume.SetPaintingStrategy(PaintingStrategy.LINE); CallVolume.SetDefaultColor(GlobalColor("Call")); AddLabel(ShowLabels and DataType == DataType.Volume, "CallVol: " + CallVolume, GlobalColor("Call")); # Put Volume plot PutVolume = -(TotalPutVolume); # Make negative to flip under axis PutVolume.SetHiding(!ShowLines and DataType == DataType.Volume); PutVolume.SetPaintingStrategy(PaintingStrategy.LINE); PutVolume.SetDefaultColor(GlobalColor("Put")); AddLabel(ShowLabels and DataType == DataType.Volume, "PutVol: " + TotalPutVolume, GlobalColor("Put")); # Create Clouds for Volume AddCloud( if ShowClouds and DataType == DataType.Volume then CallVolume else Double.NaN, if ShowClouds and DataType == DataType.Volume then Zeroline else Double.NaN, GlobalColor("CallCloud"), GlobalColor("PutCloud") ); AddCloud( if ShowClouds and DataType == DataType.Volume then Zeroline else Double.NaN, if ShowClouds and DataType == DataType.Volume then PutVolume else Double.NaN, GlobalColor("PutCloud"), GlobalColor("CallCloud") ); # Hull Moving Average of Put Volume plot PutVolumeAverage = hullmovingavg(PutVolume); PutVolumeAverage.SetHiding(!ShowLines and DataType == DataType.Volume); PutVolumeAverage.SetDefaultColor(Color.ORANGE); PutVolumeAverage.SetStyle(Curve.MEDIUM_DASH); # Hull Moving Average of Call Volume plot CallVolumeAverage = hullmovingavg(CallVolume); CallVolumeAverage.SetHiding(!ShowLines and DataType == DataType.Volume); CallVolumeAverage.SetDefaultColor(Color.LIGHT_GREEN); CallVolumeAverage.SetStyle(Curve.MEDIUM_DASH); # Color Gradient of Total Average Volume #plot TotalVolumeAverage = average(CallVolume + PutVolume); #TotalVolumeAverage.SetHiding(!ShowLines and DataType == DataType.Volume); #TotalVolumeAverage.SetLineWeight(3); #TotalVolumeAverage.DefineColor("Default", Color.YELLOW); #TotalVolumeAverage.DefineColor("Highest", Color.GREEN); #TotalVolumeAverage.DefineColor("Lowest", Color.RED); #TotalVolumeAverage.AssignNormGradientColor(14, TotalVolumeAverage.Color("Lowest"), TotalVolumeAverage.Color("Highest")); #AddCloud( # if ShowClouds and DataType == DataType.Volume then TotalVolumeAverage else Double.NaN, # if ShowClouds and DataType == DataType.Volume then Zeroline else Double.NaN, # Color.GREEN, Color.RED #); # Greeks Labels AddLabel(ShowGreeks, "Delta: " + Delta, Color.WHITE); AddLabel(ShowGreeks, "Gamma: " + Gamma, Color.WHITE); AddLabel(ShowGreeks, "Theta: " + Theta, Color.WHITE); AddLabel(ShowGreeks, "Vega: " + Vega, Color.WHITE);
HAH, no problem at all, I actually did read that one already too! Ideally I'd like to get something like that going but I think it is far beyond the capabilities of TOS. Would have to utilize the api and build out the system in a fast compiled language I think. The problem for me is historical options data is not cheap ... I'm working to get a system together to start streaming and storing the api feed going forward but can't test unless market is open gonna be a long trial and error process I think.I think squeeze @SqueezeMetrics provides a good explaination for Gamma Exposure (GEX) https://squeezemetrics.com/monitor/docs
check out his work as well, sorry for digging the rabbit hole even deeper lol, nice work!
I really like what youve done so far , but are you sure gamma exposure is a datatype i cant any information on that. the reason i ask is because the total gamma exposure should be in the hundreds of millions if not billions, I think you're forgetting to multiply by 100 because each option contract size is 100 sharesHere is an updated version.
I've massively simplified the fold logic in my opinion. (I think there was a bug in the old code anyways where I was adding ATM data twice)
I've also added gamma exposure based on the math here: https://perfiliev.co.uk/market-commentary/how-to-calculate-gamma-exposure-and-zero-gamma-level/
Although I'm not sure its correct, as the math is quite complicated, and my background is not in finance.
I'm also not sure how to interpret the data just yet ...
It doesn't make it any easier that I realized you can't call a custom script inside the fold using the index of the fold
As a workaround, I had to try and implement the formula for deriving the spot gamma in a single line within the fold, as part of the larger calculation for the spot gamma exposure at that level. This is the mobius Greeks formula (which uses Black Scholes), using the OptionPrice() and SeriesVolatility() as inputs.
Some Notes:
Screenshot:
- Ideally to get the 'Total Market Gamma Exposure' we would need to perform this calculation on all strikes, for every expiry, for every day. I have only tested going +/- 5 to 10 strikes out, for weekly series so far, and it already takes quite a bit of time. For reference I'm running an i9-9900k with 64GB DDR4 RAM, Running this at 5 strike depth weekly on SPY is taking around 15 -20 minutes (hard to tell when its actually done running so I'm just going by when my pc fans/water cooler fans stop spinning at max rpm lol)
- For number 1 to work, we would need to further modify the date/strike logic to be able to pick up more than just next series expiries. I also don't know how this effects the SeriesVolatility function.
- Maybe another approach would be to modify this so that we can pick up the first couple close to the money strikes, and then skip to picking up a couple way out of the money strikes to get a better overall picture.
Code:
Code:#hint: Options Hacker \n This study lets you scan the options chain for a series and given depth. \n<b>Warning: Setting the StrikeDepth to large values requires significant processing power, and will result in slow loading times.</b> #-----------------------------------------------------------------------------------------------------------------# # Settings # Colors DefineGlobalColor("Call", Color.GREEN); DefineGlobalColor("Put", Color.RED); DefineGlobalColor("CallCloud",Color.DARK_GREEN); DefineGlobalColor("PutCloud",Color.DARK_RED); #hint Mode: The mode to select an option symbol. \n AUTO will try to find the option symbol based on the Series and StrikeDepth inputs. \n MANUAL allow an override of the AUTO behavior by using the ManualCenterStrike and ManualStrikeSpacing inputs to determine the option symbol. input Mode = {default AUTO, MANUAL}; #hint Series: The option expiration series to search. \n This value is used to determine the option symbol. input Series = { default Weekly, Month1, Month2, Month3, Month4, Month5, Month6, Month7, Month8, Month9 }; #hint DataType: The type of option data to show. input DataType = {default OpenInterest, Volume, GammaExposure}; #hint StrikeDepth: The level of depth to search a series. (+/- this far from ATM) input StrikeDepth = 10; #hint CenterStrikeOffset: The offset to use when calculating the center strike based on close price. \n Examples: \n 1 = nearest $1 interval \n 10 = nearest $10 interval. input CenterStrikeOffset = 1.0; #hint MaxStrikeSpacing: The maximum dollar amount between two adjacent contracts. input MaxStrikeSpacing = 25; #hint ManualCenterStrike: The starting price to use when in MANUAL mode. input ManualCenterStrike = 440; #hint ManualStrikeSpacing: The dollar amount between two adjacent contracts to use when in MANUAL mode. input ManualStrikeSpacing = 1.0; #hint ShowStrikeInfo: Show the strike info labels. input ShowStrikeInfo = yes; #hint ShowLabels: Show the open interest labels. input ShowLabels = yes; #hint ShowClouds: Show the open interest clouds. input ShowClouds = no; #hint ShowLines: Show the open interest lines. input ShowLines = yes; #hint ShowGreeks: Show the estimated Greek calculation labels for the latest bar. input ShowGreeks = yes; #-----------------------------------------------------------------------------------------------------------------# # Date, Symbol, and Strike # OptionSeries is the expiry starting at 1 and raising by one for each next expiry. def OptionSeries; switch (Series) { case Weekly: OptionSeries = 1; case Month1: OptionSeries = 2; case Month2: OptionSeries = 3; case Month3: OptionSeries = 4; case Month4: OptionSeries = 5; case Month5: OptionSeries = 6; case Month6: OptionSeries = 7; case Month7: OptionSeries = 8; case Month8: OptionSeries = 9; case Month9: OptionSeries = 10; }; # Open price at Regular Trading Hours def RTHopen = open(period = AggregationPeriod.DAY); # Current year, month, day, and date def CurrentYear = GetYear(); # number of current bar in CST def CurrentMonth = GetMonth(); # 1 - 12 def CurrentDay = GetDay(); # 1 - 365 (366 for leap year) def CurrentDate = GetYYYYMMDD(); # date of the current bar in the YYYYMMDD # Current day of this month def CurrentDayOfMonth = GetDayOfMonth(CurrentDate); # Get the first day of this month - 1 (Monday) to 7 (Sunday) def FirstDayThisMonth = GetDayOfWeek((CurrentYear * 10000) + (CurrentMonth * 100) + 1); # Get the first upcoming friday def FirstUpcomingFriday = if FirstDayThisMonth < 6 then 6 - FirstDayThisMonth else if FirstDayThisMonth == 6 then 7 else 6 ; # Get the second, third, and fourth upcoming fridays def SecondUpcomingFriday = FirstUpcomingFriday + 7; def ThirdUpcomingFriday = FirstUpcomingFriday + 14; def FourthUpcomingFriday = FirstUpcomingFriday + 21; # Pick up all Fridays of the current month for weekly options def RollDOM = FirstUpcomingFriday + 21; def ExpMonth1 = if RollDOM > CurrentDayOfMonth then CurrentMonth + OptionSeries - 1 else CurrentMonth + OptionSeries ; # Options month input def ExpMonth2 = if ExpMonth1 > 12 then ExpMonth1 - 12 else ExpMonth1; # Options year input def ExpYear = if ExpMonth1 > 12 then CurrentYear + 1 else CurrentYear; # First friday expiry calc def ExpDay1DOW = GetDayOfWeek(ExpYear * 10000 + ExpMonth2 * 100 + 1); def ExpFirstFridayDOM = if ExpDay1DOW < 6 then 6 - ExpDay1DOW else if ExpDay1DOW == 6 then 7 else 6 ; # Options code day of month input def ExpDOM = if CurrentDayOfMonth < ExpFirstFridayDOM -1 then FirstUpcomingFriday else if between(CurrentDayOfMonth, ExpFirstFridayDOM, SecondUpcomingFriday - 1) then SecondUpcomingFriday else if between(CurrentDayOfMonth, SecondUpcomingFriday, ThirdUpcomingFriday - 1) then ThirdUpcomingFriday else if between(CurrentDayOfMonth, ThirdUpcomingFriday, FourthUpcomingFriday - 1) then FourthUpcomingFriday else ExpFirstFridayDOM ; # Centerstrike def CenterStrike = if (Mode == Mode.AUTO and !IsNaN(close)) then Round(close / CenterStrikeOffset, 0) * CenterStrikeOffset else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualCenterStrike else CenterStrike[1] ; # This is still some voodoo to me ... def OptionExpiryDate = ExpYear * 10000 + ExpMonth2 * 100 + ExpDOM + 1; # Strike Spacing def StrikeSpacingC = fold i = 1 to MaxStrikeSpacing with spacing = 0 do if DataType == DataType.OpenInterest or DataType == DataType.GammaExposure and !IsNaN( open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (MaxStrikeSpacing - i))) ) then MaxStrikeSpacing - i else if DataType == DataType.Volume and !IsNaN( volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (MaxStrikeSpacing - i))) ) then MaxStrikeSpacing - i else spacing ; def StrikeSpacing = if (Mode == Mode.AUTO and !IsNaN(close)) then StrikeSpacingC else if (Mode == Mode.MANUAL and !IsNaN(close)) then ManualStrikeSpacing else StrikeSpacing[1] ; # Testing --------- #plot Date = GetYYYYMMDD(); #plot Expiry = (OptionExpiryDate); #plot DTE = AbsValue(CountTradingDays(Date, Expiry)); #-----------------------------------------------------------------------------------------------------------------# # Option Chain Data Gathering # Total Put Open Interest for selected chain depth and expiry series def TotalPutOpenInterest = fold poiIndex = -(StrikeDepth) to StrikeDepth with poi = 0 do if DataType == DataType.OpenInterest and !IsNaN( open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * poiIndex))) ) then poi + open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * poiIndex))) else 0 ; # Total Call Open Interest for selected chain depth and expiry series def TotalCallOpenInterest = fold coiIndex = -(StrikeDepth) to StrikeDepth with coi = 0 do if DataType == DataType.OpenInterest and !IsNaN( open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * coiIndex))) ) then coi + open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * coiIndex))) else 0 ; # Total Put Volume for selected chain depth and expiry series def TotalPutVolume = fold pvIndex = -(StrikeDepth) to StrikeDepth with pv = 0 do if DataType == DataType.Volume and !IsNaN( volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + StrikeSpacing * pvIndex)) ) then pv + volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + StrikeSpacing * pvIndex)) else 0 ; # Total Call Open Interest for selected chain depth and expiry series def TotalCallVolume = fold cvIndex = -(StrikeDepth) to StrikeDepth with cv = 0 do if DataType == DataType.Volume and !IsNaN( volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + StrikeSpacing * cvIndex)) ) then cv + volume(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + StrikeSpacing * cvIndex)) else 0 ; #-----------------------------------------------------------------------------------------------------------------# # Greeks Calculations # # K - Option strike price # N - Standard normal cumulative distribution function # r - Risk free interest rate # IV - Volatility of the underlying # S - Price of the underlying # t - Time to option's expiry # # d1 = (ln(S/K) + (r + (sqr(IV)/2))t) / (? (sqrt(t))) # d2 = e -(sqr(d1) / 2) / sqrt(2*pi) # # Delta = N(d1) # Gamma = (d2) / S(IV(sqrt(t))) # Theta = ((S d2))IV) / 2 sqrt(t)) - (rK e(rt)N(d4)) # where phi(d3) = (exp(-(sqr(x)/2))) / (2 * sqrt(t)) # where d4 = d1 - IV(sqrt(t)) # Vega = S phi(d1) Sqrt(t) # TODO: Figure out how to dynamically get this value def DayToExpiry = AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1); # Get the implied volatility for calculations # Input: series is the expiry starting at 1 and raising by 1 for each next expiry def IV = SeriesVolatility(series = OptionSeries); def K = CenterStrike; def S = close; def r = GetInterestRate(); def t = (DayToExpiry / 365); def d1 = (Log(S / K) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)); def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi); script N { input data = 1; def a = AbsValue(data); def b1 = .31938153; def b2 = -.356563782; def b3 = 1.781477937; def b4 = -1.821255978; def b5 = 1.330274429; def b6 = .2316419; def e = 1 / (1 + b6 * a); def i = 1 - 1 / Sqrt(2 * Double.Pi) * Exp(-Power(a, 2) / 2) * (b1 * e + b2 * e * e + b3 * Power(e, 3) + b4 * Power(e, 4) + b5 * Power(e, 5)); plot CND = if data < 0 then 1 - i else i; } # TODO: These values don't quite line up # My background on options pricing models is not very good # Delta def Delta = N(d1); # Gamma def Gamma = d2 / (S * (IV * Sqrt(t))); # Theta def Theta = -(-(S*d2*IV*(.5000)/ (2*sqrt(t)))- (r*(exp(-r*t)*K))*N(d2)+(S*N(d1)*(.5000)))/365; # (.5000) variant less than .5 e(X/t) # Vega def Vega = (S*d2*sqrt(t))/100; script SpotGamma { input DTE = 0; input K = 0; input Series = 0; def S = close; def t = (DTE / 365); def IV = SeriesVolatility(series = Series); def d1 = (Log(S / K) + ((GetInterestRate() + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)); # Delta def Delta = N(d1); # Gamma def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi); def Gamma = d2 / (S * (IV * Sqrt(t))); plot SpotGamma = Gamma; } # Total Put Gamma Exposure for selected chain depth and expiry series def TotalPutGammaExposure = fold pgexIndex = -(StrikeDepth) to StrikeDepth with pgex = 0 do if DataType == DataType.GammaExposure and !IsNaN( open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * pgexIndex))) ) then pgex + open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike + (StrikeSpacing * pgexIndex))) * Sqr(OptionPrice((CenterStrike + (StrikeSpacing * pgexIndex)), yes, AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), close, SeriesVolatility(series = OptionSeries), no, 0.0, GetInterestRate())) * 0.01 * #SpotGamma(AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), (CenterStrike + (StrikeSpacing * pgexIndex)), OptionSeries) * # Honestly why cant I put the index inside here :( (Exp(-(Sqr((Log(close / (CenterStrike + (StrikeSpacing * pgexIndex))) + ((GetInterestRate() + (Sqr(SeriesVolatility(series = OptionSeries)) / 2)) * (AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))) / (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365)))) / 2)) / Sqrt(2 * Double.Pi) / (close * (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))))) * 100 * -1 else 0 ; # Total Call Gamma Exposure for selected chain depth and expiry series def TotalCallGammaExposure = fold cgexIndex = -(StrikeDepth) to StrikeDepth with cgex = 0 do if DataType == DataType.GammaExposure and !IsNaN( open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * cgexIndex))) ) then cgex + open_interest(("." + GetSymbolPart()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike + (StrikeSpacing * cgexIndex))) * Sqr(OptionPrice((CenterStrike + (StrikeSpacing * cgexIndex)), no, AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), close, SeriesVolatility(series = OptionSeries), no, 0.0, GetInterestRate())) * 0.01 * #SpotGamma(AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1), (CenterStrike + (StrikeSpacing * 0)), OptionSeries) * # Honestly why cant I put the index inside here :( (Exp(-(Sqr((Log(close / (CenterStrike + (StrikeSpacing * cgexIndex))) + ((GetInterestRate() + (Sqr(SeriesVolatility(series = OptionSeries)) / 2)) * (AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))) / (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365)))) / 2)) / Sqrt(2 * Double.Pi) / (close * (SeriesVolatility(series = OptionSeries) * Sqrt((AbsValue(CountTradingDays(CurrentDate, OptionExpiryDate) - 1) / 365))))) * 100 else 0 ; plot GammaExposure = TotalPutGammaExposure + TotalCallGammaExposure; #-----------------------------------------------------------------------------------------------------------------# # Visuals # Selected DataType AddLabel(yes, DataType, Color.LIGHT_GRAY); # Selected Series AddLabel(yes, Series, Color.LIGHT_GRAY); # Center Strike Label AddLabel(ShowStrikeInfo, "Center Strike: " + AsDollars(CenterStrike), Color.LIGHT_GRAY); # Chain Depth Label AddLabel(ShowStrikeInfo, "Strike Depth: +/-" + StrikeDepth, Color.LIGHT_GRAY); # Strike Spacing Label AddLabel(ShowStrikeInfo, "Strike Spacing: " + AsDollars(StrikeSpacing), Color.LIGHT_GRAY); # Current ATM Options Labels Addlabel(ShowStrikeInfo, "ATM Put: " + ("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "P" + AsPrice(CenterStrike), Color.LIGHT_RED); Addlabel(ShowStrikeInfo, "ATM Call: " + ("." + GetSymbol()) + AsPrice(OptionExpiryDate - 20000001) + "C" + AsPrice(CenterStrike), Color.LIGHT_GREEN); # Create a center line plot ZeroLine = 0; ZeroLine.SetDefaultColor(Color.WHITE); # Call Open Interest plot CallOpenInterest = TotalCallOpenInterest; CallOpenInterest.SetHiding(!ShowLines and DataType == DataType.OpenInterest); CallOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE); CallOpenInterest.SetDefaultColor(GlobalColor("Call")); AddLabel(ShowLabels and DataType == DataType.OpenInterest, "CallOI: " + CallOpenInterest, GlobalColor("Call")); # Put Open Interest plot PutOpenInterest = -(TotalPutOpenInterest); # Make negative to flip under axis PutOpenInterest.SetHiding(!ShowLines and DataType == DataType.OpenInterest); PutOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE); PutOpenInterest.SetDefaultColor(GlobalColor("Put")); AddLabel(ShowLabels and DataType == DataType.OpenInterest, "PutOI: " + TotalPutOpenInterest, GlobalColor("Put")); # Create Clouds for Open Interest AddCloud( if ShowClouds and DataType == DataType.OpenInterest then CallOpenInterest else Double.NaN, if ShowClouds and DataType == DataType.OpenInterest then Zeroline else Double.NaN, GlobalColor("CallCloud"), GlobalColor("PutCloud") ); AddCloud( if ShowClouds and DataType == DataType.OpenInterest then Zeroline else Double.NaN, if ShowClouds and DataType == DataType.OpenInterest then PutOpenInterest else Double.NaN, GlobalColor("PutCloud"), GlobalColor("CallCloud") ); # Hull Moving Average of Put Open Interest plot PutOpenInterestAverage = hullmovingavg(PutOpenInterest); PutOpenInterestAverage.SetHiding(!ShowLines and DataType == DataType.OpenInterest); PutOpenInterestAverage.SetDefaultColor(Color.ORANGE); PutOpenInterestAverage.SetStyle(Curve.MEDIUM_DASH); # Hull Moving Average of Call Open Interest plot CallOpenInterestAverage = hullmovingavg(CallOpenInterest); CallOpenInterestAverage.SetHiding(!ShowLines and DataType == DataType.OpenInterest); CallOpenInterestAverage.SetDefaultColor(Color.LIGHT_GREEN); CallOpenInterestAverage.SetStyle(Curve.MEDIUM_DASH); # Color Gradient of Total Average Open Interest #plot TotalOpenInterestAverage = average(CallOpenInterest + PutOpenInterest); #TotalOpenInterestAverage.SetHiding(!ShowLines and DataType == DataType.OpenInterest); #TotalOpenInterestAverage.SetLineWeight(3); #TotalOpenInterestAverage.DefineColor("Default", Color.YELLOW); #TotalOpenInterestAverage.DefineColor("Highest", Color.GREEN); #TotalOpenInterestAverage.DefineColor("Lowest", Color.RED); #TotalOpenInterestAverage.AssignNormGradientColor(14, TotalOpenInterestAverage.Color("Lowest"), TotalOpenInterestAverage.Color("Highest")); #AddCloud( # if ShowClouds and DataType == DataType.OpenInterest then TotalOpenInterestAverage else Double.NaN, # if ShowClouds and DataType == DataType.OpenInterest then Zeroline else Double.NaN, # color.GREEN, color.RED #); # Call Volume plot CallVolume = TotalCallVolume; CallVolume.SetHiding(!ShowLines and DataType == DataType.Volume); CallVolume.SetPaintingStrategy(PaintingStrategy.LINE); CallVolume.SetDefaultColor(GlobalColor("Call")); AddLabel(ShowLabels and DataType == DataType.Volume, "CallVol: " + CallVolume, GlobalColor("Call")); # Put Volume plot PutVolume = -(TotalPutVolume); # Make negative to flip under axis PutVolume.SetHiding(!ShowLines and DataType == DataType.Volume); PutVolume.SetPaintingStrategy(PaintingStrategy.LINE); PutVolume.SetDefaultColor(GlobalColor("Put")); AddLabel(ShowLabels and DataType == DataType.Volume, "PutVol: " + TotalPutVolume, GlobalColor("Put")); # Create Clouds for Volume AddCloud( if ShowClouds and DataType == DataType.Volume then CallVolume else Double.NaN, if ShowClouds and DataType == DataType.Volume then Zeroline else Double.NaN, GlobalColor("CallCloud"), GlobalColor("PutCloud") ); AddCloud( if ShowClouds and DataType == DataType.Volume then Zeroline else Double.NaN, if ShowClouds and DataType == DataType.Volume then PutVolume else Double.NaN, GlobalColor("PutCloud"), GlobalColor("CallCloud") ); # Hull Moving Average of Put Volume plot PutVolumeAverage = hullmovingavg(PutVolume); PutVolumeAverage.SetHiding(!ShowLines and DataType == DataType.Volume); PutVolumeAverage.SetDefaultColor(Color.ORANGE); PutVolumeAverage.SetStyle(Curve.MEDIUM_DASH); # Hull Moving Average of Call Volume plot CallVolumeAverage = hullmovingavg(CallVolume); CallVolumeAverage.SetHiding(!ShowLines and DataType == DataType.Volume); CallVolumeAverage.SetDefaultColor(Color.LIGHT_GREEN); CallVolumeAverage.SetStyle(Curve.MEDIUM_DASH); # Color Gradient of Total Average Volume #plot TotalVolumeAverage = average(CallVolume + PutVolume); #TotalVolumeAverage.SetHiding(!ShowLines and DataType == DataType.Volume); #TotalVolumeAverage.SetLineWeight(3); #TotalVolumeAverage.DefineColor("Default", Color.YELLOW); #TotalVolumeAverage.DefineColor("Highest", Color.GREEN); #TotalVolumeAverage.DefineColor("Lowest", Color.RED); #TotalVolumeAverage.AssignNormGradientColor(14, TotalVolumeAverage.Color("Lowest"), TotalVolumeAverage.Color("Highest")); #AddCloud( # if ShowClouds and DataType == DataType.Volume then TotalVolumeAverage else Double.NaN, # if ShowClouds and DataType == DataType.Volume then Zeroline else Double.NaN, # Color.GREEN, Color.RED #); # Greeks Labels AddLabel(ShowGreeks, "Delta: " + Delta, Color.WHITE); AddLabel(ShowGreeks, "Gamma: " + Gamma, Color.WHITE); AddLabel(ShowGreeks, "Theta: " + Theta, Color.WHITE); AddLabel(ShowGreeks, "Vega: " + Vega, Color.WHITE);
Let’s now calculate the total gamma contribution from each option. The formula is:
Option’s Gamma * Contract Size * Open Interest * Spot Price * (-1 if puts)
This gives the total option’s change in delta per ONE POINT move in the index. To convert into percent, we must multiply by how many points 1% is. Hence, we multiply by 1% * Spot Price, giving the final formula:
Option’s Gamma * Contract Size * Open Interest * Spot Price ^ 2 * 0.01
To calculate the GEX (in shares*) of all call options at a particular strike price in a contract:
GEX = Г · OI · 100Where Г is the option's gamma, OI is the open interest in the particular option strike, and 100 is the adjustment from option contracts to shares of the underlying.
In the case of put options:
GEX = Г · OI · (-100)
See my latest response, this is currently showing the percent gamma exposure. I did multiply by 100 for each contract.I really like what youve done so far , but are you sure gamma exposure is a datatype i cant any information on that. the reason i ask is because the total gamma exposure should be in the hundreds of millions if not billions, I think you're forgetting to multiply by 100 because each option contract size is 100 shares
Join useThinkScript to post your question to a community of 21,000+ developers and traders.
Thread starter | Similar threads | Forum | Replies | Date |
---|---|---|---|---|
S | Unusual Options Metric (Option Chain Column) For ThinkOrSwim | Indicators | 55 | |
Unusual Option Activity Scanner for ThinkorSwim | Indicators | 61 | ||
FlowAlgo Dark pool and Option Flow for ThinkorSwim | Indicators | 23 | ||
Option Greeks Calculation Labels for ThinkorSwim | Indicators | 5 |
Start a new thread and receive assistance from our community.
useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.
We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.
If you are new, or just looking for guidance, here are some helpful links to get you started.