OptionsHacker -to show Volume/OpenInterest/GammaExposure for the options chain For ThinkOrSwim

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

great stuff here @sudoshu. question on gamma exposure... i would have expected the bars to plot in the negative here. is there a reason why they are all positive?

Hmm not too sure, with it set to 'ContributionShares' or 'Contribution' i see the same, all positives. If you set it to 'ContributionPercent' it will show some negatives. Could be because the script doesn't get the full option chain (can try setting the strike depth higher but it times out at a certain point) or thats just how the math ends up working out for the scope we can see.
 
@sudoshu - Great work, thank you. Will this study work on SPX? I tried but the indicator doesn't show up.
There is an issue with indexes and futures on this script as the symbol is different in the options than the search bar in thinkorswim. For SPX you would normally type that into the search bar 'SPX' but the options chain looks like '.SPXW220920C1800' Notice the the 'W' is thrown in there.

I think this may be an easy fix but I'd like to try and find a way to accommodate others as well, might just need to do a giant if else statement ...
I will put this on the list of todo's
 
great stuff here @sudoshu. question on gamma exposure... i would have expected the bars to plot in the negative here. is there a reason why they are all positive?


I found this issue as well, the script had a bug where only the last gex calculation method has put multiplied by -1, that's why you see all positive

I took the liberty to fix this and try to add a NOPE calculation, see if anyone has an extra eye to check if I done it correctly.

Code:
#hint: Options Hacker \n This study lets you scan and perform calculations on 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>
#
# sudoshu - I would not have been able to make this work without the help, and kindness to share code from those listed below. Thank you!
#
# Credits:
#     mobius
#     ziongotoptions
#     Angrybear
#     halcyonguy
#     MerryDay
#     MountainKing333
#     BenTen
#     DeusMecanicus
#     Zardoz0609
#     NPTrading
#

declare once_per_bar;
declare hide_on_intraday;
declare lower;
def version = 0.5;

#-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------#
# Settings

# Colors
DefineGlobalColor("Call", Color.GREEN);
DefineGlobalColor("Put", Color.RED);
DefineGlobalColor("CallCloud",Color.DARK_GREEN);
DefineGlobalColor("PutCloud",Color.DARK_RED);
DefineGlobalColor("CallAverage",Color.LIGHT_GREEN);
DefineGlobalColor("PutAverage",Color.ORANGE);
DefineGlobalColor("GEX",Color.CYAN);
DefineGlobalColor("NOPE",Color.PINK);


#hint Series: The option expiration series to search. \n This value is used to determine the option symbol.
input Series = {
    default Weekly,
    Opex,
    Month1,
    Month2,
    Month3,
    Month4,
    Month5,
    Month6,
    Month7,
    Month8,
    Month9
};

# Todo: Maybe can add something like this to get more than just fridays ...
#hint OpexDay:
#input OpexDay = {
#    Monday,
#    Tuesday,
#    Wednesday,
#    Thursday,
#    default Friday
#};

#hint DataType: The type of option data to show.
input DataType = {default OpenInterest, Volume, GammaExposure, NetOptionPricingEffect};

#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 GEXCalculationMethod: The method to use for calculating gamma exposure. \n The total gamma exposure is then the sum of all call gex + put gex. \n <li>ContributionShares: \n gamma * OI * 100 (* -1 for puts)</li><li>Contribution: \n gamma * OI * 100 * Spot Price (* -1 for puts)</li><li>ContributionPercent: \n gamma * OI * 100 * Spot Price ^2 * 0.01 (* -1 for puts)</li>
input GEXCalculationMethod = {default ContributionShares, Contribution, ContributionPercent};

#hint ShowStrikeInfo: Show the strike info labels.
input ShowStrikeInfo = yes;

#hint ShowLabels: Show labels for the current data type.
input ShowLabels = yes;

#hint ShowClouds: Show clouds for volume and/or open interest.
input ShowClouds = yes;

#hint ShowLines: Show lines for values.
input ShowLines = yes;

#hint ShowAverages: Show the moving average lines.
input ShowAverages = yes;

#hint ShowGreeks: Show the estimated Greek calculation labels for the latest bar.
input ShowGreeks = yes;

#hint FlipPuts: Flip the puts underneath the axis
input FlipPuts = yes;

#hint StrikeMode: 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 allows an override of the AUTO behavior by using the ManualCenterStrike and ManualStrikeSpacing inputs to determine the option symbol.
input StrikeMode = {default AUTO, MANUAL};

#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 IVMethod: The method to use for calculating implied volatility. This is mainly used in the GEX calculations. \n ClosedFormIV uses an approximation for implied volatility based on methods from mobius. \n SeriesIV uses the built in SeriesVolatility() function.
input IVMethod = {default ClosedFormIV, SeriesIV};

#hint Debug: Shows plots and data for debugging purposes.
input Debug = no;


#-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------#
# Date, Symbol, and Strike
#

# OptionSeries is the expiry starting at 1 and raising by one for each next expiry. \n This is only used for the SeriesIV method calculation.
def OptionSeries;
switch (Series) {
    case Weekly:
        OptionSeries = 1;
    case Opex:
        OptionSeries = 2;
    case Month1:
        OptionSeries = 3;
    case Month2:
        OptionSeries = 4;
    case Month3:
        OptionSeries = 5;
    case Month4:
        OptionSeries = 6;
    case Month5:
        OptionSeries = 7;
    case Month6:
        OptionSeries = 8;
    case Month7:
        OptionSeries = 9;
    case Month8:
        OptionSeries = 10;
    case Month9:
        OptionSeries = 11;
};

# 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;

# Get the month of expiration for the option, accounting for end of month rollover
def ExpMonth1 =
    if Series == Series.Opex and ThirdUpcomingFriday > CurrentDayOfMonth then CurrentMonth
    else if Series == Series.Opex and ThirdUpcomingFriday < CurrentDayOfMonth then CurrentMonth + 1
    else if FourthUpcomingFriday > CurrentDayOfMonth then CurrentMonth
    else CurrentMonth + 1
;

# Get the month of expiration for the option, accounting for end of year rollover
def ExpMonth = if ExpMonth1 > 12 then ExpMonth1 - 12 else ExpMonth1;

# Get the year of expiration for the option
def ExpYear = if ExpMonth1 > 12 then CurrentYear + 1 else CurrentYear;

# Get the first day at the current expiration year and month
def ExpDay1DOW = GetDayOfWeek(ExpYear * 10000 + ExpMonth * 100 + 1);

# Get the first friday at the current expiration year and month
def ExpFirstFridayDOM =
    if ExpDay1DOW < 6 then 6 - ExpDay1DOW
    else if ExpDay1DOW == 6 then 7
    else 6
;

# Get the second, third, and fourth fridays at the current expiration year and month
def ExpSecondFridayDOM = ExpFirstFridayDOM + 7;
def ExpThirdFridayDOM = ExpFirstFridayDOM + 14;
def ExpFouthFridayDOM = ExpFirstFridayDOM + 21;

# Get the day of month of expiration for the option
def ExpDOM =
    if Series == Series.Opex then ExpThirdFridayDOM
    else 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
;

# Option Expiration Date - Depending on selected series
def OpexDate_NoHolidays = ExpYear * 10000 + ExpMonth * 100 + ExpDOM;

# Option Expiration Date - Accounting for holidays
def OpexDate =
    if ExpMonth == 4 and ExpDOM == 15 then OpexDate_NoHolidays - 1 else OpexDate_NoHolidays # Exchange holiday on April 15, 2022
;

# The OpexCode is the expiry date in the format of an option symbol.
# Example:
#     The date of expiry is April 15th, 2022.
#     The format of this date from the code we have above, the OptionExpiryDate, would be 20,220,415
#     Options symbols expect the format for this date as 220415 (we need to get rid of the leading 20)
def OpexCode = OpexDate - 20000000;

# Option Days to Expiration
#def DTE = CountTradingDays(CurrentDate, OptionExpiryDate);
def DTE = DaysTillDate(OpexDate);

# Find the center strike - the price closest to ATM option
def CenterStrike =
    if (StrikeMode == StrikeMode.AUTO and !IsNaN(close)) then Round(close / CenterStrikeOffset, 0) * CenterStrikeOffset
    else if (StrikeMode == StrikeMode.MANUAL and !IsNaN(close)) then ManualCenterStrike
    else CenterStrike[1]
;

# Strike Spacing
def StrikeSpacingC =
    fold i = 1 to MaxStrikeSpacing
    with spacing = 1
    do if !IsNaN(
        open_interest(("." + GetSymbolPart()) + AsPrice(OpexCode) + "P" + AsPrice(CenterStrike + (MaxStrikeSpacing - i)))
    )
    then MaxStrikeSpacing - i
    else if !IsNaN(
        volume(("." + GetSymbolPart()) + AsPrice(OpexCode) + "P" + AsPrice(CenterStrike + (MaxStrikeSpacing - i)))
    )
    then MaxStrikeSpacing - i
    else spacing
;
def StrikeSpacing =
    if (StrikeMode == StrikeMode.AUTO and !IsNaN(close)) then StrikeSpacingC
    else if (StrikeMode == StrikeMode.MANUAL and !IsNaN(close)) then ManualStrikeSpacing
    else StrikeSpacing[1]
;


# Date and Strike Debugging
plot DebugCurrentDate = CurrentDate - 20000000;
DebugCurrentDate.SetHiding(!Debug);
plot DebugOptionExpiryDate = OpexCode;
DebugOptionExpiryDate.SetHiding(!Debug);
plot DebugDTE = DTE;
DebugDTE.SetHiding(!Debug);
plot DebugCenterStrike = CenterStrike;
DebugCenterStrike.SetHiding(!Debug);
plot DebugStrikeSpacing = StrikeSpacing;
DebugStrikeSpacing.SetHiding(!Debug);


#-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------#
# Option Chain Data Gathering


# Script to get the total open interest at a spot
script TotalSpotOI {
    input StrikeDepth = 1;
    input OpexCode = 1;
    input CenterStrike = 1;
    input StrikeSpacing = 1;
    input IsCall = yes;
    def total =
        fold index = -(StrikeDepth) to (StrikeDepth + 1)
        with value = 0
        do
            if !IsNaN(
                open_interest(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            )
            then value + open_interest(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            else value + 0
    ;
    plot TotalSpotOI = total;
}

# Total Call Open Interest for selected chain depth and expiry series
def TotalCallOpenInterest = TotalSpotOI(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, yes);

# Total Put Open Interest for selected chain depth and expiry series
def TotalPutOpenInterest = TotalSpotOI(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, no);


# Script to get the total volume at a spot
script TotalSpotVolume {
    input StrikeDepth = 1;
    input OpexCode = 1;
    input CenterStrike = 1;
    input StrikeSpacing = 1;
    input IsCall = yes;
    def total =
        fold index = -(StrikeDepth) to (StrikeDepth + 1)
        with value = 0
        do
            if !IsNaN(
                volume(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            )
            then value + volume(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            else value + 0
    ;
    plot TotalSpotVolume = total;
}

# Total Call Open Interest for selected chain depth and expiry series
def TotalCallVolume = TotalSpotVolume(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, yes);

# Total Put Volume for selected chain depth and expiry series
def TotalPutVolume = TotalSpotVolume(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, no);

# Abramowiz Stegun Approximation for Cumulative Normal Distribution
script CND {
    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;
}

# Closed form IV approximation
# IV = sqrt(2*pi / t) * (C / S)
# S = Price of the underlying
# C = Price of the option
# t = time to maturity
script ClosedFormIV {
    input C = 1;
    input S = 1;
    input t = 1; # time to maturity 
    #plot IV = (C * Sqrt(2 * Double.Pi)) / (S * Sqrt(t));
    plot IV = (sqrt((2 * Double.Pi)/t)) * (C / S);
}

# 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)
def K = CenterStrike;
def S = close;
def r = GetInterestRate();
def t = (DTE / 365);
def y = GetYield();

# At the money call price used for ClosedFormIV calcuation
def ATMCallPrice = if isNaN(close(symbol = ("." + GetSymbolPart()) + AsPrice(OpexCode) + "C" + AsPrice(CenterStrike), period = aggregationPeriod.DAY))
    then ATMCallPrice[1]
    else close(symbol = ("." + GetSymbolPart()) + AsPrice(OpexCode) + "C" + AsPrice(CenterStrike), period = aggregationPeriod.DAY)
;

# ClosedFormIV is an approximation of implied volatility based on scripts/methods from mobius
def IV1 = ClosedFormIV(ATMCallPrice, RTHopen, t);
plot MobiusIV = if IV1 > 0 and !IsInfinite(IV1) then IV1 else IV1[1];
MobiusIV.SetHiding(!Debug);

# SeriesVolatility is the expiry starting at 1 and raising by 1 for each next expiry
def IV2 = SeriesVolatility(series = OptionSeries);
plot SeriesIV = if IV2 > 0 and !IsInfinite(IV2) then IV2 else IV2[1];
SeriesIV.SetHiding(!Debug);

# Implied Volatily method
def IV;
switch (IVMethod) {
    case ClosedFormIV:
        IV = IV1;
    case SeriesIV:
        IV = IV2;
}

# Start greek calculations
def d1 = (Log(S / K) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t));
def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi);

# Delta
def Delta = CND(d1);

# Gamma
def Gamma = d2 / (S * (IV * Sqrt(t)));

# Theta
def Theta = -(-(S*d2*IV*(.5000)/
             (2*sqrt(t)))-
             (r*(exp(-r*t)*K))*CND(d2)+(S*CND(d1)*(.5000)))/365;
# (.5000) variant less than .5 e(X/t)

# Vega
def Vega = (S*d2*sqrt(t))/100;

# script StrikeDelta {
#     input index = 1;
#     input close = 1;
#     input CenterStrike = 1;
#     input StrikeSpacing = 1;
#     input DTE = 1;
#     input IV = 1;
#     input r = 1;
#     input t = 1;
#     input y = 1;

#     def epsilon = 0.001 * close;
#     plot approxDelta = (OptionPrice(CenterStrike + StrikeSpacing * index, !isCall, DTE, close + epsilon, IV, no, y, r) - OptionPrice(CenterStrike + StrikeSpacing * index, !isCall, DTE, close, IV, no, y, r)) / epsilon;
# }


# GEX Calculation Methods:
#
# ContributionShares:
#     Call GEX = gamma * OI * 100
#     Put GEX  = gamma * OI * 100 * -1
#
# Contribution:
#     Call GEX = gamma * OI * 100 * Spot Price
#     Put GEX  = gamma * OI * 100 * Spot Price * -1
#
# ContributionPercent:
#     Call GEX = gamma * OI * 100 * Spot Price ^2 * 0.01
#     Put GEX  = gamma * OI * 100 * Spot Price ^2 * 0.01 * -1
#


# Script to get the total gamma exposure at a spot
script TotalSpotGEX {
    input StrikeDepth = 1;
    input OpexCode = 1;
    input CenterStrike = 1;
    input StrikeSpacing = 1;
    input IsCall = yes;
    input GEXCalculationMethod = {default ContributionShares, Contribution, ContributionPercent};
    input DTE = 1;
    input IV = 1;
    input r = 1;
    input t = 1;
    def total =
        fold index = -(StrikeDepth) to (StrikeDepth + 1)
        with value = 0
        do
            if !IsNaN(
                open_interest(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            )
            then value +
                (Exp(-(Sqr((Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))) / 2)) / Sqrt(2 * Double.Pi) / (close * (IV * Sqrt(t)))) *
                open_interest(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index))) *
                100 *
                if GEXCalculationMethod == GEXCalculationMethod.ContributionShares then
                    1 * if IsCall then 1 else -1
                else if GEXCalculationMethod == GEXCalculationMethod.Contribution then
                    close * if IsCall then 1 else -1
                else
                    Sqr(close) * 0.01 * if IsCall then 1 else -1
            else value + 0
    ;
    plot TotalSpotGEX = total;
}

# Total Call Gamma Exposure for selected chain depth and expiry series
def TotalCallGammaExposure = TotalSpotGEX(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, yes, GEXCalculationMethod, DTE, IV, r, t);

# Total Put Gamma Exposure for selected chain depth and expiry series
def TotalPutGammaExposure = TotalSpotGEX(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, no, GEXCalculationMethod, DTE, IV, r, t);

# Script to get the total volume*delta at a spot for Net Option Pricing Effect
script TotalNope {
    input StrikeDepth = 1;
    input OpexCode = 1;
    input CenterStrike = 1;
    input StrikeSpacing = 1;
    input IsCall = yes;
    input DTE = 1;
    input IV = 1;
    input r = 1;
    input t = 1;
    input y = 1;

    def epsilon = 0.001 * close;
    

    def total =
        fold index = -(StrikeDepth) to (StrikeDepth + 1)
        with value = 0
        do
            if !IsNaN(
                volume(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            )
            then value +
                volume(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index))) * 100 *
                (OptionPrice(CenterStrike + StrikeSpacing * index, !isCall, DTE, close + epsilon, IV, no, y, r) - OptionPrice(CenterStrike + StrikeSpacing * index, !isCall, DTE, close, IV, no, y, r)) / epsilon * 100
                # if (1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)))) < 0
                #     then 1 - (1 - 1 / Sqrt(2 * Double.Pi) * Exp(-Power(AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)), 2) / 2) *
                #         (.31938153 * 1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))) +
                #         -.356563782  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 2) +
                #         1.781477937  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 3) +
                #         -1.821255978 * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 4) +
                #         1.330274429  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 5)))
                #     else (1 - 1 / Sqrt(2 * Double.Pi) * Exp(-Power(AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)), 2) / 2) *
                #         (.31938153 * 1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))) +
                #         -.356563782  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 2) +
                #         1.781477937  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 3) +
                #         -1.821255978 * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 4) +
                #         1.330274429  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 5)))
            else value + 0
    ;
    plot TotalNopeValue = total;
}
def TotalCallNope = TotalNope(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, yes, DTE, IV, r, t, y);
def TotalPutNope = TotalNope(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, no, DTE, IV, r, t, y);


#-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------#
# Visuals

# Version Label
AddLabel(yes, "OptionsHacker v" + version, Color.LIGHT_GRAY);

# 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), if StrikeMode == StrikeMode.AUTO then Color.LIGHT_GRAY else Color.RED);

# Strike Spacing Label
AddLabel(ShowStrikeInfo, "Strike Spacing: " + AsDollars(StrikeSpacing), if StrikeMode == StrikeMode.AUTO then Color.LIGHT_GRAY else Color.RED);

# Chain Depth Label
AddLabel(ShowStrikeInfo, "Strike Depth: +/-" + StrikeDepth, Color.LIGHT_GRAY);

# Current ATM Options Labels
Addlabel(ShowStrikeInfo, "ATM Put: " + ("." + GetSymbol()) + AsPrice(OpexCode) + "P" + AsPrice(CenterStrike), GlobalColor("Call"));
Addlabel(ShowStrikeInfo, "ATM Call: " + ("." + GetSymbol()) + AsPrice(OpexCode) + "C" + AsPrice(CenterStrike), GlobalColor("Put"));

# Create a center line
plot ZeroLine = 0;
ZeroLine.SetDefaultColor(Color.WHITE);

# Call Open Interest
plot CallOpenInterest = TotalCallOpenInterest;
CallOpenInterest.SetHiding(!ShowLines or 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 = if FlipPuts then -(TotalPutOpenInterest) else TotalPutOpenInterest;
PutOpenInterest.SetHiding(!ShowLines or DataType != DataType.OpenInterest);
PutOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE);
PutOpenInterest.SetDefaultColor(GlobalColor("Put"));
AddLabel(ShowLabels and DataType == DataType.OpenInterest, "PutOI: " + AbsValue(PutOpenInterest), 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("PutCloud")
);

# Hull Moving Average of Put Open Interest
plot PutOpenInterestAverage = hullmovingavg(PutOpenInterest);
PutOpenInterestAverage.SetHiding(!ShowAverages or DataType != DataType.OpenInterest);
PutOpenInterestAverage.SetDefaultColor(GlobalColor("PutAverage"));
PutOpenInterestAverage.SetStyle(Curve.MEDIUM_DASH);

# Hull Moving Average of Call Open Interest
plot CallOpenInterestAverage = hullmovingavg(CallOpenInterest);
CallOpenInterestAverage.SetHiding(!ShowAverages or DataType != DataType.OpenInterest);
CallOpenInterestAverage.SetDefaultColor(GlobalColor("CallAverage"));
CallOpenInterestAverage.SetStyle(Curve.MEDIUM_DASH);

# Call Volume
plot CallVolume = TotalCallVolume;
CallVolume.SetHiding(!ShowLines or 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 = if FlipPuts then -(TotalPutVolume) else TotalPutVolume;
PutVolume.SetHiding(!ShowLines or DataType != DataType.Volume);
PutVolume.SetPaintingStrategy(PaintingStrategy.LINE);
PutVolume.SetDefaultColor(GlobalColor("Put"));
AddLabel(ShowLabels and DataType == DataType.Volume, "PutVol: " + AbsValue(PutVolume), 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("CallCloud")
);
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(!ShowAverages or DataType != DataType.Volume);
PutVolumeAverage.SetDefaultColor(GlobalColor("PutAverage"));
PutVolumeAverage.SetStyle(Curve.MEDIUM_DASH);

# Hull Moving Average of Call Volume
plot CallVolumeAverage = hullmovingavg(CallVolume);
CallVolumeAverage.SetHiding(!ShowAverages or DataType != DataType.Volume);
CallVolumeAverage.SetDefaultColor(GlobalColor("CallAverage"));
CallVolumeAverage.SetStyle(Curve.MEDIUM_DASH);

# Gamma Exposure
plot GammaExposure = (TotalCallGammaExposure + TotalPutGammaExposure);
GammaExposure.SetHiding(DataType != DataType.GammaExposure);
GammaExposure.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
GammaExposure.SetDefaultColor(GlobalColor("GEX"));
AddLabel(
    DataType == DataType.GammaExposure,
    if GEXCalculationMethod == GEXCalculationMethod.ContributionShares then
        "GEX: " + GammaExposure + " Shares"
    else if GEXCalculationMethod == GEXCalculationMethod.Contribution then
        "GEX: " + GammaExposure + "$ Per 1$ Move"
    else
        "GEX: " + GammaExposure + "$ Per 1% Move"
    ,
    GlobalColor("GEX")
);


# NOPE
plot NOPE = (TotalCallNope + TotalPutNope)/volume();
NOPE.SetHiding(DataType != DataType.NetOptionPricingEffect);
NOPE.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
NOPE.SetDefaultColor(GlobalColor("NOPE"));
AddLabel(ShowLabels and DataType == DataType.NetOptionPricingEffect, "NOPE: " + AbsValue(NOPE), GlobalColor("Nope"));

# 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 found this issue as well, the script had a bug where only the last gex calculation method has put multiplied by -1, that's why you see all positive
Ahhhh good catch, and great work on the NOPE indicator!

I had tried to get delta's before but couldn't figure out how to solve CND(d1) inside the fold. Am I understanding correct that instead of doing that, the epsilon is just an estimate of the new underlying stock price? So the equation becomes:
Delta = (Of - Oi) / (Sf - Si) where
  • Of: the new value of the option
  • Oi: the initial value of the option
  • Sf: the new value of the underlying stock
  • Si: the initial value of the underlying stock
Based on that and what @iom1 posted I think it works out well.
 
Ahhhh good catch, and great work on the NOPE indicator!

I had tried to get delta's before but couldn't figure out how to solve CND(d1) inside the fold. Am I understanding correct that instead of doing that, the epsilon is just an estimate of the new underlying stock price? So the equation becomes:

Based on that and what @iom1 posted I think it works out well.
you're right, I originally tried to calculate the Delta in the fold, and it was crazy I can't invoke another function call in it, hence you see the crazy expanded expression that was commented out, I tried the epsilon to approximate Delta, and it look good enough to me.

the only problem now is that this script run very slow, I really hope there's a better way to do this than thinkscript, they should update it to something modern and run faster, e.g. python.
 
There is an issue with indexes and futures on this script as the symbol is different in the options than the search bar in thinkorswim. For SPX you would normally type that into the search bar 'SPX' but the options chain looks like '.SPXW220920C1800' Notice the the 'W' is thrown in there.

I think this may be an easy fix but I'd like to try and find a way to accommodate others as well, might just need to do a giant if else statement ...
I will put this on the list of todo's
Hello Sir, Has there been any break through to the scrip for SPX?
 
Can you update the original post to include the dates of various version updates ... like when was v0.4 updated?

Thanks!
 
Has there been any word on splitting the indicator up into 3 or 4 different indicators? For volume, open interest, gamma, etc?

Here's my current setup, you can see how increased put buying + diminishing call buying is affecting the gamma environment.
 
Last edited by a moderator:
I found this issue as well, the script had a bug where only the last gex calculation method has put multiplied by -1, that's why you see all positive

I took the liberty to fix this and try to add a NOPE calculation, see if anyone has an extra eye to check if I done it correctly.

Code:
#hint: Options Hacker \n This study lets you scan and perform calculations on 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>
#
# sudoshu - I would not have been able to make this work without the help, and kindness to share code from those listed below. Thank you!
#
# Credits:
#     mobius
#     ziongotoptions
#     Angrybear
#     halcyonguy
#     MerryDay
#     MountainKing333
#     BenTen
#     DeusMecanicus
#     Zardoz0609
#     NPTrading
#

declare once_per_bar;
declare hide_on_intraday;
declare lower;
def version = 0.5;

#-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------#
# Settings

# Colors
DefineGlobalColor("Call", Color.GREEN);
DefineGlobalColor("Put", Color.RED);
DefineGlobalColor("CallCloud",Color.DARK_GREEN);
DefineGlobalColor("PutCloud",Color.DARK_RED);
DefineGlobalColor("CallAverage",Color.LIGHT_GREEN);
DefineGlobalColor("PutAverage",Color.ORANGE);
DefineGlobalColor("GEX",Color.CYAN);
DefineGlobalColor("NOPE",Color.PINK);


#hint Series: The option expiration series to search. \n This value is used to determine the option symbol.
input Series = {
    default Weekly,
    Opex,
    Month1,
    Month2,
    Month3,
    Month4,
    Month5,
    Month6,
    Month7,
    Month8,
    Month9
};

# Todo: Maybe can add something like this to get more than just fridays ...
#hint OpexDay:
#input OpexDay = {
#    Monday,
#    Tuesday,
#    Wednesday,
#    Thursday,
#    default Friday
#};

#hint DataType: The type of option data to show.
input DataType = {default OpenInterest, Volume, GammaExposure, NetOptionPricingEffect};

#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 GEXCalculationMethod: The method to use for calculating gamma exposure. \n The total gamma exposure is then the sum of all call gex + put gex. \n <li>ContributionShares: \n gamma * OI * 100 (* -1 for puts)</li><li>Contribution: \n gamma * OI * 100 * Spot Price (* -1 for puts)</li><li>ContributionPercent: \n gamma * OI * 100 * Spot Price ^2 * 0.01 (* -1 for puts)</li>
input GEXCalculationMethod = {default ContributionShares, Contribution, ContributionPercent};

#hint ShowStrikeInfo: Show the strike info labels.
input ShowStrikeInfo = yes;

#hint ShowLabels: Show labels for the current data type.
input ShowLabels = yes;

#hint ShowClouds: Show clouds for volume and/or open interest.
input ShowClouds = yes;

#hint ShowLines: Show lines for values.
input ShowLines = yes;

#hint ShowAverages: Show the moving average lines.
input ShowAverages = yes;

#hint ShowGreeks: Show the estimated Greek calculation labels for the latest bar.
input ShowGreeks = yes;

#hint FlipPuts: Flip the puts underneath the axis
input FlipPuts = yes;

#hint StrikeMode: 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 allows an override of the AUTO behavior by using the ManualCenterStrike and ManualStrikeSpacing inputs to determine the option symbol.
input StrikeMode = {default AUTO, MANUAL};

#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 IVMethod: The method to use for calculating implied volatility. This is mainly used in the GEX calculations. \n ClosedFormIV uses an approximation for implied volatility based on methods from mobius. \n SeriesIV uses the built in SeriesVolatility() function.
input IVMethod = {default ClosedFormIV, SeriesIV};

#hint Debug: Shows plots and data for debugging purposes.
input Debug = no;


#-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------#
# Date, Symbol, and Strike
#

# OptionSeries is the expiry starting at 1 and raising by one for each next expiry. \n This is only used for the SeriesIV method calculation.
def OptionSeries;
switch (Series) {
    case Weekly:
        OptionSeries = 1;
    case Opex:
        OptionSeries = 2;
    case Month1:
        OptionSeries = 3;
    case Month2:
        OptionSeries = 4;
    case Month3:
        OptionSeries = 5;
    case Month4:
        OptionSeries = 6;
    case Month5:
        OptionSeries = 7;
    case Month6:
        OptionSeries = 8;
    case Month7:
        OptionSeries = 9;
    case Month8:
        OptionSeries = 10;
    case Month9:
        OptionSeries = 11;
};

# 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;

# Get the month of expiration for the option, accounting for end of month rollover
def ExpMonth1 =
    if Series == Series.Opex and ThirdUpcomingFriday > CurrentDayOfMonth then CurrentMonth
    else if Series == Series.Opex and ThirdUpcomingFriday < CurrentDayOfMonth then CurrentMonth + 1
    else if FourthUpcomingFriday > CurrentDayOfMonth then CurrentMonth
    else CurrentMonth + 1
;

# Get the month of expiration for the option, accounting for end of year rollover
def ExpMonth = if ExpMonth1 > 12 then ExpMonth1 - 12 else ExpMonth1;

# Get the year of expiration for the option
def ExpYear = if ExpMonth1 > 12 then CurrentYear + 1 else CurrentYear;

# Get the first day at the current expiration year and month
def ExpDay1DOW = GetDayOfWeek(ExpYear * 10000 + ExpMonth * 100 + 1);

# Get the first friday at the current expiration year and month
def ExpFirstFridayDOM =
    if ExpDay1DOW < 6 then 6 - ExpDay1DOW
    else if ExpDay1DOW == 6 then 7
    else 6
;

# Get the second, third, and fourth fridays at the current expiration year and month
def ExpSecondFridayDOM = ExpFirstFridayDOM + 7;
def ExpThirdFridayDOM = ExpFirstFridayDOM + 14;
def ExpFouthFridayDOM = ExpFirstFridayDOM + 21;

# Get the day of month of expiration for the option
def ExpDOM =
    if Series == Series.Opex then ExpThirdFridayDOM
    else 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
;

# Option Expiration Date - Depending on selected series
def OpexDate_NoHolidays = ExpYear * 10000 + ExpMonth * 100 + ExpDOM;

# Option Expiration Date - Accounting for holidays
def OpexDate =
    if ExpMonth == 4 and ExpDOM == 15 then OpexDate_NoHolidays - 1 else OpexDate_NoHolidays # Exchange holiday on April 15, 2022
;

# The OpexCode is the expiry date in the format of an option symbol.
# Example:
#     The date of expiry is April 15th, 2022.
#     The format of this date from the code we have above, the OptionExpiryDate, would be 20,220,415
#     Options symbols expect the format for this date as 220415 (we need to get rid of the leading 20)
def OpexCode = OpexDate - 20000000;

# Option Days to Expiration
#def DTE = CountTradingDays(CurrentDate, OptionExpiryDate);
def DTE = DaysTillDate(OpexDate);

# Find the center strike - the price closest to ATM option
def CenterStrike =
    if (StrikeMode == StrikeMode.AUTO and !IsNaN(close)) then Round(close / CenterStrikeOffset, 0) * CenterStrikeOffset
    else if (StrikeMode == StrikeMode.MANUAL and !IsNaN(close)) then ManualCenterStrike
    else CenterStrike[1]
;

# Strike Spacing
def StrikeSpacingC =
    fold i = 1 to MaxStrikeSpacing
    with spacing = 1
    do if !IsNaN(
        open_interest(("." + GetSymbolPart()) + AsPrice(OpexCode) + "P" + AsPrice(CenterStrike + (MaxStrikeSpacing - i)))
    )
    then MaxStrikeSpacing - i
    else if !IsNaN(
        volume(("." + GetSymbolPart()) + AsPrice(OpexCode) + "P" + AsPrice(CenterStrike + (MaxStrikeSpacing - i)))
    )
    then MaxStrikeSpacing - i
    else spacing
;
def StrikeSpacing =
    if (StrikeMode == StrikeMode.AUTO and !IsNaN(close)) then StrikeSpacingC
    else if (StrikeMode == StrikeMode.MANUAL and !IsNaN(close)) then ManualStrikeSpacing
    else StrikeSpacing[1]
;


# Date and Strike Debugging
plot DebugCurrentDate = CurrentDate - 20000000;
DebugCurrentDate.SetHiding(!Debug);
plot DebugOptionExpiryDate = OpexCode;
DebugOptionExpiryDate.SetHiding(!Debug);
plot DebugDTE = DTE;
DebugDTE.SetHiding(!Debug);
plot DebugCenterStrike = CenterStrike;
DebugCenterStrike.SetHiding(!Debug);
plot DebugStrikeSpacing = StrikeSpacing;
DebugStrikeSpacing.SetHiding(!Debug);


#-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------#
# Option Chain Data Gathering


# Script to get the total open interest at a spot
script TotalSpotOI {
    input StrikeDepth = 1;
    input OpexCode = 1;
    input CenterStrike = 1;
    input StrikeSpacing = 1;
    input IsCall = yes;
    def total =
        fold index = -(StrikeDepth) to (StrikeDepth + 1)
        with value = 0
        do
            if !IsNaN(
                open_interest(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            )
            then value + open_interest(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            else value + 0
    ;
    plot TotalSpotOI = total;
}

# Total Call Open Interest for selected chain depth and expiry series
def TotalCallOpenInterest = TotalSpotOI(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, yes);

# Total Put Open Interest for selected chain depth and expiry series
def TotalPutOpenInterest = TotalSpotOI(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, no);


# Script to get the total volume at a spot
script TotalSpotVolume {
    input StrikeDepth = 1;
    input OpexCode = 1;
    input CenterStrike = 1;
    input StrikeSpacing = 1;
    input IsCall = yes;
    def total =
        fold index = -(StrikeDepth) to (StrikeDepth + 1)
        with value = 0
        do
            if !IsNaN(
                volume(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            )
            then value + volume(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            else value + 0
    ;
    plot TotalSpotVolume = total;
}

# Total Call Open Interest for selected chain depth and expiry series
def TotalCallVolume = TotalSpotVolume(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, yes);

# Total Put Volume for selected chain depth and expiry series
def TotalPutVolume = TotalSpotVolume(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, no);

# Abramowiz Stegun Approximation for Cumulative Normal Distribution
script CND {
    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;
}

# Closed form IV approximation
# IV = sqrt(2*pi / t) * (C / S)
# S = Price of the underlying
# C = Price of the option
# t = time to maturity
script ClosedFormIV {
    input C = 1;
    input S = 1;
    input t = 1; # time to maturity
    #plot IV = (C * Sqrt(2 * Double.Pi)) / (S * Sqrt(t));
    plot IV = (sqrt((2 * Double.Pi)/t)) * (C / S);
}

# 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)
def K = CenterStrike;
def S = close;
def r = GetInterestRate();
def t = (DTE / 365);
def y = GetYield();

# At the money call price used for ClosedFormIV calcuation
def ATMCallPrice = if isNaN(close(symbol = ("." + GetSymbolPart()) + AsPrice(OpexCode) + "C" + AsPrice(CenterStrike), period = aggregationPeriod.DAY))
    then ATMCallPrice[1]
    else close(symbol = ("." + GetSymbolPart()) + AsPrice(OpexCode) + "C" + AsPrice(CenterStrike), period = aggregationPeriod.DAY)
;

# ClosedFormIV is an approximation of implied volatility based on scripts/methods from mobius
def IV1 = ClosedFormIV(ATMCallPrice, RTHopen, t);
plot MobiusIV = if IV1 > 0 and !IsInfinite(IV1) then IV1 else IV1[1];
MobiusIV.SetHiding(!Debug);

# SeriesVolatility is the expiry starting at 1 and raising by 1 for each next expiry
def IV2 = SeriesVolatility(series = OptionSeries);
plot SeriesIV = if IV2 > 0 and !IsInfinite(IV2) then IV2 else IV2[1];
SeriesIV.SetHiding(!Debug);

# Implied Volatily method
def IV;
switch (IVMethod) {
    case ClosedFormIV:
        IV = IV1;
    case SeriesIV:
        IV = IV2;
}

# Start greek calculations
def d1 = (Log(S / K) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t));
def d2 = Exp(-(Sqr(d1) / 2)) / Sqrt(2 * Double.Pi);

# Delta
def Delta = CND(d1);

# Gamma
def Gamma = d2 / (S * (IV * Sqrt(t)));

# Theta
def Theta = -(-(S*d2*IV*(.5000)/
             (2*sqrt(t)))-
             (r*(exp(-r*t)*K))*CND(d2)+(S*CND(d1)*(.5000)))/365;
# (.5000) variant less than .5 e(X/t)

# Vega
def Vega = (S*d2*sqrt(t))/100;

# script StrikeDelta {
#     input index = 1;
#     input close = 1;
#     input CenterStrike = 1;
#     input StrikeSpacing = 1;
#     input DTE = 1;
#     input IV = 1;
#     input r = 1;
#     input t = 1;
#     input y = 1;

#     def epsilon = 0.001 * close;
#     plot approxDelta = (OptionPrice(CenterStrike + StrikeSpacing * index, !isCall, DTE, close + epsilon, IV, no, y, r) - OptionPrice(CenterStrike + StrikeSpacing * index, !isCall, DTE, close, IV, no, y, r)) / epsilon;
# }


# GEX Calculation Methods:
#
# ContributionShares:
#     Call GEX = gamma * OI * 100
#     Put GEX  = gamma * OI * 100 * -1
#
# Contribution:
#     Call GEX = gamma * OI * 100 * Spot Price
#     Put GEX  = gamma * OI * 100 * Spot Price * -1
#
# ContributionPercent:
#     Call GEX = gamma * OI * 100 * Spot Price ^2 * 0.01
#     Put GEX  = gamma * OI * 100 * Spot Price ^2 * 0.01 * -1
#


# Script to get the total gamma exposure at a spot
script TotalSpotGEX {
    input StrikeDepth = 1;
    input OpexCode = 1;
    input CenterStrike = 1;
    input StrikeSpacing = 1;
    input IsCall = yes;
    input GEXCalculationMethod = {default ContributionShares, Contribution, ContributionPercent};
    input DTE = 1;
    input IV = 1;
    input r = 1;
    input t = 1;
    def total =
        fold index = -(StrikeDepth) to (StrikeDepth + 1)
        with value = 0
        do
            if !IsNaN(
                open_interest(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            )
            then value +
                (Exp(-(Sqr((Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))) / 2)) / Sqrt(2 * Double.Pi) / (close * (IV * Sqrt(t)))) *
                open_interest(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index))) *
                100 *
                if GEXCalculationMethod == GEXCalculationMethod.ContributionShares then
                    1 * if IsCall then 1 else -1
                else if GEXCalculationMethod == GEXCalculationMethod.Contribution then
                    close * if IsCall then 1 else -1
                else
                    Sqr(close) * 0.01 * if IsCall then 1 else -1
            else value + 0
    ;
    plot TotalSpotGEX = total;
}

# Total Call Gamma Exposure for selected chain depth and expiry series
def TotalCallGammaExposure = TotalSpotGEX(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, yes, GEXCalculationMethod, DTE, IV, r, t);

# Total Put Gamma Exposure for selected chain depth and expiry series
def TotalPutGammaExposure = TotalSpotGEX(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, no, GEXCalculationMethod, DTE, IV, r, t);

# Script to get the total volume*delta at a spot for Net Option Pricing Effect
script TotalNope {
    input StrikeDepth = 1;
    input OpexCode = 1;
    input CenterStrike = 1;
    input StrikeSpacing = 1;
    input IsCall = yes;
    input DTE = 1;
    input IV = 1;
    input r = 1;
    input t = 1;
    input y = 1;

    def epsilon = 0.001 * close;
   

    def total =
        fold index = -(StrikeDepth) to (StrikeDepth + 1)
        with value = 0
        do
            if !IsNaN(
                volume(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index)))
            )
            then value +
                volume(("." + GetSymbolPart()) + AsPrice(OpexCode) + (if IsCall then "C" else "P") + AsPrice(CenterStrike + (StrikeSpacing * index))) * 100 *
                (OptionPrice(CenterStrike + StrikeSpacing * index, !isCall, DTE, close + epsilon, IV, no, y, r) - OptionPrice(CenterStrike + StrikeSpacing * index, !isCall, DTE, close, IV, no, y, r)) / epsilon * 100
                # if (1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)))) < 0
                #     then 1 - (1 - 1 / Sqrt(2 * Double.Pi) * Exp(-Power(AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)), 2) / 2) *
                #         (.31938153 * 1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))) +
                #         -.356563782  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 2) +
                #         1.781477937  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 3) +
                #         -1.821255978 * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 4) +
                #         1.330274429  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 5)))
                #     else (1 - 1 / Sqrt(2 * Double.Pi) * Exp(-Power(AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t)), 2) / 2) *
                #         (.31938153 * 1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))) +
                #         -.356563782  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 2) +
                #         1.781477937  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 3) +
                #         -1.821255978 * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 4) +
                #         1.330274429  * Power(1 / (1 + .2316419 * AbsValue(Log(close / (CenterStrike + (StrikeSpacing * index))) + ((r + (Sqr(IV) / 2)) * t)) / (IV * Sqrt(t))), 5)))
            else value + 0
    ;
    plot TotalNopeValue = total;
}
def TotalCallNope = TotalNope(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, yes, DTE, IV, r, t, y);
def TotalPutNope = TotalNope(StrikeDepth, OpexCode, CenterStrike, StrikeSpacing, no, DTE, IV, r, t, y);


#-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------#
# Visuals

# Version Label
AddLabel(yes, "OptionsHacker v" + version, Color.LIGHT_GRAY);

# 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), if StrikeMode == StrikeMode.AUTO then Color.LIGHT_GRAY else Color.RED);

# Strike Spacing Label
AddLabel(ShowStrikeInfo, "Strike Spacing: " + AsDollars(StrikeSpacing), if StrikeMode == StrikeMode.AUTO then Color.LIGHT_GRAY else Color.RED);

# Chain Depth Label
AddLabel(ShowStrikeInfo, "Strike Depth: +/-" + StrikeDepth, Color.LIGHT_GRAY);

# Current ATM Options Labels
Addlabel(ShowStrikeInfo, "ATM Put: " + ("." + GetSymbol()) + AsPrice(OpexCode) + "P" + AsPrice(CenterStrike), GlobalColor("Call"));
Addlabel(ShowStrikeInfo, "ATM Call: " + ("." + GetSymbol()) + AsPrice(OpexCode) + "C" + AsPrice(CenterStrike), GlobalColor("Put"));

# Create a center line
plot ZeroLine = 0;
ZeroLine.SetDefaultColor(Color.WHITE);

# Call Open Interest
plot CallOpenInterest = TotalCallOpenInterest;
CallOpenInterest.SetHiding(!ShowLines or 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 = if FlipPuts then -(TotalPutOpenInterest) else TotalPutOpenInterest;
PutOpenInterest.SetHiding(!ShowLines or DataType != DataType.OpenInterest);
PutOpenInterest.SetPaintingStrategy(PaintingStrategy.LINE);
PutOpenInterest.SetDefaultColor(GlobalColor("Put"));
AddLabel(ShowLabels and DataType == DataType.OpenInterest, "PutOI: " + AbsValue(PutOpenInterest), 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("PutCloud")
);

# Hull Moving Average of Put Open Interest
plot PutOpenInterestAverage = hullmovingavg(PutOpenInterest);
PutOpenInterestAverage.SetHiding(!ShowAverages or DataType != DataType.OpenInterest);
PutOpenInterestAverage.SetDefaultColor(GlobalColor("PutAverage"));
PutOpenInterestAverage.SetStyle(Curve.MEDIUM_DASH);

# Hull Moving Average of Call Open Interest
plot CallOpenInterestAverage = hullmovingavg(CallOpenInterest);
CallOpenInterestAverage.SetHiding(!ShowAverages or DataType != DataType.OpenInterest);
CallOpenInterestAverage.SetDefaultColor(GlobalColor("CallAverage"));
CallOpenInterestAverage.SetStyle(Curve.MEDIUM_DASH);

# Call Volume
plot CallVolume = TotalCallVolume;
CallVolume.SetHiding(!ShowLines or 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 = if FlipPuts then -(TotalPutVolume) else TotalPutVolume;
PutVolume.SetHiding(!ShowLines or DataType != DataType.Volume);
PutVolume.SetPaintingStrategy(PaintingStrategy.LINE);
PutVolume.SetDefaultColor(GlobalColor("Put"));
AddLabel(ShowLabels and DataType == DataType.Volume, "PutVol: " + AbsValue(PutVolume), 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("CallCloud")
);
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(!ShowAverages or DataType != DataType.Volume);
PutVolumeAverage.SetDefaultColor(GlobalColor("PutAverage"));
PutVolumeAverage.SetStyle(Curve.MEDIUM_DASH);

# Hull Moving Average of Call Volume
plot CallVolumeAverage = hullmovingavg(CallVolume);
CallVolumeAverage.SetHiding(!ShowAverages or DataType != DataType.Volume);
CallVolumeAverage.SetDefaultColor(GlobalColor("CallAverage"));
CallVolumeAverage.SetStyle(Curve.MEDIUM_DASH);

# Gamma Exposure
plot GammaExposure = (TotalCallGammaExposure + TotalPutGammaExposure);
GammaExposure.SetHiding(DataType != DataType.GammaExposure);
GammaExposure.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
GammaExposure.SetDefaultColor(GlobalColor("GEX"));
AddLabel(
    DataType == DataType.GammaExposure,
    if GEXCalculationMethod == GEXCalculationMethod.ContributionShares then
        "GEX: " + GammaExposure + " Shares"
    else if GEXCalculationMethod == GEXCalculationMethod.Contribution then
        "GEX: " + GammaExposure + "$ Per 1$ Move"
    else
        "GEX: " + GammaExposure + "$ Per 1% Move"
    ,
    GlobalColor("GEX")
);


# NOPE
plot NOPE = (TotalCallNope + TotalPutNope)/volume();
NOPE.SetHiding(DataType != DataType.NetOptionPricingEffect);
NOPE.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
NOPE.SetDefaultColor(GlobalColor("NOPE"));
AddLabel(ShowLabels and DataType == DataType.NetOptionPricingEffect, "NOPE: " + AbsValue(NOPE), GlobalColor("Nope"));

# 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);
Shouldnt the GEX label be
else if GEXCalculationMethod == GEXCalculationMethod.Contribution then
"GEX: $" + GammaExposure + " Per $1 Move"
else
"GEX: $" + GammaExposure + " Per 1% Move"

That way the dollar sign comes before the Gamma Exposure number i.e. GEX: $110,972,448.1813 per $1 move
 
TB3tPVn.png

PJCNCPW.png

Here's my current setup, you can see how increased put buying + diminishing call buying is affecting the gamma environment.
Is there a way to code the OptionsHacker so you can look just at 0DTE each day for SPY? One thing I have noticed is when a strike has a large negative gamma it seems to be the strike that gets hit. Example was 410 puts on Friday 5-12. Large OI but based on Tradytics data it was negative gamma or short puts. So if negative gamma is short calls and puts and positive gamma is long puts and calls its most important to know when that strike has a change in gamma. Tradytics data is good but sometimes there just to much delay in seeing the gamma change. So if I could have a study and say only want to see + or - 5 strikes of ATM on just one strike then maybe I could know when to take the trade. When we hit right at 409 Friday the gamma flipped and the futures went back up. They got squeeze out. I knew premarket that the short puts were there but short calls at 416 too. And the 416 calls was a little larger amount so I figured they would push it up but I was wrong. The market only cares about the 0DTE. So having a way to view changes in specific strikes is key to knowing when the direction is going to change. SPX gamma plays a big role to but just having SPY is enough but having it for SPX would be great if possible. And view it on a lower time chart 1 min, 5 min or tick?
 
Let me rephrase what I'm asking. The code on page 1 of this thread has lower code for options. I want to know is there a code for watching just one strike on 0DTE? A lower study as a visualization to see the change in gamma of that strike?
 
Let me rephrase what I'm asking. The code on page 1 of this thread has lower code for options. I want to know is there a code for watching just one strike on 0DTE? A lower study as a visualization to see the change in gamma of that strike?
None commercially available that I know of. Dr Harlin might have something, he's around on Twitter.
 
new to options trading, can some give a high level overview of how to interpret this signal..thank you so much
Two things that come to mind - Gex is real, and Option Volume flow matters.

Static Open Interest Gex may or may not act as gravity towards the strikes that are heaviest.
MM's (market makers) tend to use options to structure the daily market with 0DTE calls/puts sold throughout the day.

Map the highest volume and you will know the pivots of the SPX movement.
Hint: Use volume on SPY, price action on SPX.

Track gamma(volume) and gex(OI) throught the day.


Puts fuel rallies and Calls pull price upwards (in positive Gex) and the oppositie occurs (in negative Gex).
 

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
315 Online
Create Post

Similar threads

Similar threads

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

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.

How do I get started?

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.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top