Moneyness Options [Loxx]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © loxx

//@version=5
indicator("Moneyness Options [Loxx]", 
     shorttitle ="MO [Loxx]",
     overlay = true, 
     max_lines_count = 500)

if not timeframe.isdaily
    runtime.error("Error: Invald timeframe. Indicator only works on daily timeframe.") 

import loxx/cnd/1
import loxx/cbnd/1

string Continuous = "Continuous"
string PeriodRate = "Period Rate"
string Annual = "Annual"
string SemiAnnual = "Semi-Annual"
string Quaterly = "Quaterly"
string Monthly = "Monthly"

color darkGreenColor = #1B7E02 
string callString = "Call"
string putString = "Put"

string rogersatch = "Roger-Satchell"
string parkinson = "Parkinson"
string c2c = "Close-to-Close"
string gkvol = "Garman-Klass"
string gkzhvol = "Garman-Klass-Yang-Zhang"
string ewmavolstr = "Exponential Weighted Moving Average"

string timtoolbar= "Time Now = Current time in UNIX format. It is the number of milliseconds that have elapsed since 00:00:00 UTC, 1 January 1970."
string timtoolnow = "Time Bar = The time function returns the UNIX time of the current bar for the specified timeframe and session or NaN if the time point is out of session."
string timetooltrade = "Trading Day = The beginning time of the trading day the current bar belongs to, in UNIX format (the number of milliseconds that have elapsed since 00:00:00 UTC, 1 January 1970)."

ewmavol(float src, int per) => 
    float lambda = (per - 1) / (per + 1)
    float temp = na
    temp := lambda * nz(temp[1], math.pow(src, 2)) + (1.0 - lambda) * math.pow(src, 2)
    out = math.sqrt(temp)
    out
    
rogerssatchel(int per) =>
    float sum = math.sum(math.log(high/ close) * math.log(high / open)
         + math.log(low / close) * math.log(low / open), per) / per
    float out = math.sqrt(sum) 
    out

closetoclose(float src, int per) => 
    float avg = ta.sma(src, per)
    array<float> sarr = array.new<float>(per, 0)
    for i = 0 to per - 1 
        array.set(sarr, i,  math.pow(nz(src[i]) - avg, 2))
    float out = math.sqrt(array.sum(sarr) / (per - 1))
    out 

parkinsonvol(int per)=>
    float volConst = 1.0 / (4.0 * per * math.log(2))
    float sum = volConst * math.sum(math.pow(math.log(high / low), 2), per)
    float out = math.sqrt(sum) 
    out

garmanKlass(int per)=>
    float hllog = math.log(high / low)
    float oplog = math.log(close / open)
    float garmult = (2 * math.log(2) - 1)
    float parkinsonsum = 1 / (2 * per) * math.sum(math.pow(hllog, 2), per)
    float garmansum = garmult / per * math.sum(math.pow(oplog, 2), per)
    float sum = parkinsonsum - garmansum
    float devpercent = math.sqrt(sum)  
    devpercent

gkyzvol(int per)=>
    float gzkylog = math.log(open / nz(close[1]))
    float pklog = math.log(high / low)
    float gklog = math.log(close / open)
    float garmult = (2 * math.log(2) - 1)
    float gkyzsum = 1 / per * math.sum(math.pow(gzkylog, 2), per)
    float parkinsonsum = 1 / (2 * per) * math.sum(math.pow(pklog, 2), per)
    float garmansum = garmult / per * math.sum(math.pow(gklog, 2), per)
    float sum = gkyzsum + parkinsonsum - garmansum
    float devpercent = math.sqrt(sum)  
    devpercent

ND(float x)=> 
    float out = math.exp(-x * x * 0.5) / math.sqrt(2 * math.pi)
    out 

// via Espen Gaarder Haug; The Complete Guide to Option Pricing Formulas
convertingToCCRate(float r, float Compoundings)=>
    float ConvertingToCCRate = 0
    if Compoundings == 0 
        ConvertingToCCRate := r
    else
        ConvertingToCCRate := Compoundings * math.log(1 + r / Compoundings)
    ConvertingToCCRate

GBlackScholes(string CallPutFlag, float S, float x, float T, float r, float b, float v)=>
    float d1 = 0
    float d2 = 0
    float gBlackScholes = 0

    d1 := (math.log(S / x) + (b + math.pow(v, 2) / 2) * T) / (v * math.sqrt(T))
    d2 := d1 - v * math.sqrt(T)

    if CallPutFlag == callString
        gBlackScholes := S * math.exp((b - r) * T) * cnd.CND1(d1) - x * math.exp(-r * T) * cnd.CND1(d2)
    else
        gBlackScholes := x * math.exp(-r * T) * cnd.CND1(-d2) - S * math.exp((b - r) * T) * cnd.CND1(-d1)
    gBlackScholes

MoneynessOption(float Moneyness, float T, float r, float v)=>
    float d1 = (-math.log(Moneyness) + v * v / 2 * T) / (v * math.sqrt(T))
    float d2 = d1 - v * math.sqrt(T)
    float MoneynessOption = math.exp(-r * T) * (cnd.CND1(d1) - Moneyness * cnd.CND1(d2))
    MoneynessOption

EMoneynessOption(string OutPutFlag, float S, float T, float r, float v, float dSin)=>
    float dS = 0
    if na(dSin) 
        dS := 0.01
    float EMoneynessOption = 0

    if OutPutFlag == "p"  // Value
        EMoneynessOption := MoneynessOption(S, T, r, v)

    else if OutPutFlag == "d"  //Delta
        EMoneynessOption := (MoneynessOption(S + dS, T, r, v) 
             - MoneynessOption(S - dS, T, r, v)) / (2 * dS)

    else if OutPutFlag == "e"  //Elasticity
        EMoneynessOption := (MoneynessOption(S + dS, T, r, v) 
             - MoneynessOption(S - dS, T, r, v)) / (2 * dS) * S / MoneynessOption(S, T, r, v)

    else if OutPutFlag == "g"  //Gamma
        EMoneynessOption := (MoneynessOption(S + dS, T, r, v) 
             - 2 * MoneynessOption(S, T, r, v) + MoneynessOption(S - dS, T, r, v)) / math.pow(dS, 2)

    else if OutPutFlag == "gv"  //DGammaDVol
        EMoneynessOption := (MoneynessOption(S + dS, T, r, v + 0.01) 
             - 2 * MoneynessOption(S, T, r, v + 0.01) 
             + MoneynessOption(S - dS, T, r, v + 0.01) 
             - MoneynessOption(S + dS, T, r, v - 0.01) 
             + 2 * MoneynessOption(S, T, r, v - 0.01) 
             - MoneynessOption(S - dS, T, r, v - 0.01)) / (2 * 0.01 * math.pow(dS, 2)) / 100

    else if OutPutFlag == "gp"  //GammaP
        EMoneynessOption := S / 100 * (MoneynessOption(S + dS, T, r, v) 
             - 2 * MoneynessOption(S, T, r, v) 
             + MoneynessOption(S - dS, T, r, v)) / math.pow(dS, 2)

    else if OutPutFlag == "tg"  //time Gamma
        EMoneynessOption := (MoneynessOption(S, T + 1 / 365, r, v) 
             - 2 * MoneynessOption(S, T, r, v) 
             + MoneynessOption(S, T - 1 / 365, r, v)) / math.pow(1 / 365, 2)

    else if OutPutFlag == "dddv"  //DDeltaDvol
        EMoneynessOption := 1 / (4 * dS * 0.01) 
             * (MoneynessOption(S + dS, T, r, v + 0.01) 
             - MoneynessOption(S + dS, T, r, v - 0.01) 
             - MoneynessOption(S - dS, T, r, v + 0.01) 
             + MoneynessOption(S - dS, T, r, v - 0.01)) / 100

    else if OutPutFlag == "v"  //Vega
        EMoneynessOption := (MoneynessOption(S, T, r, v + 0.01) 
             - MoneynessOption(S, T, r, v - 0.01)) / 2

    else if OutPutFlag == "vv"  //DvegaDvol/vomma
        EMoneynessOption := (MoneynessOption(S, T, r, v + 0.01) 
             - 2 * MoneynessOption(S, T, r, v) 
             + MoneynessOption(S, T, r, v - 0.01)) / math.pow(0.01, 2) / 10000

    else if OutPutFlag == "vp"  //VegaP
        EMoneynessOption := v / 0.1 
             * (MoneynessOption(S, T, r, v + 0.01) 
             - MoneynessOption(S, T, r, v - 0.01)) / 2

    else if OutPutFlag == "dvdv"  //DvegaDvol
        EMoneynessOption := (MoneynessOption(S, T, r, v + 0.01) 
             - 2 * MoneynessOption(S, T, r, v) + MoneynessOption(S, T, r, v - 0.01))

    else if OutPutFlag == "t"  //Theta
        if T <= 1 / 365 
            EMoneynessOption := MoneynessOption(S, 1E-05, r, v) 
                 - MoneynessOption(S, T, r, v)
        else
            EMoneynessOption := MoneynessOption(S, T - 1 / 365, r, v) 
                 - MoneynessOption(S, T, r, v)

    else if OutPutFlag == "r"  //Rho
        EMoneynessOption := (MoneynessOption(S, T, r + 0.01, v) 
             - MoneynessOption(S, T, r - 0.01, v)) / (2)
        
    else if OutPutFlag == "fr"  //Futures options rho
        EMoneynessOption := (MoneynessOption(S, T, r + 0.01, v) 
             - MoneynessOption(S, T, r - 0.01, v)) / (2)

    else if OutPutFlag == "s"  //Speed
        EMoneynessOption := 1 / math.pow(dS, 3) 
             * (MoneynessOption(S + 2 * dS, T, r, v) 
             - 3 * MoneynessOption(S + dS, T, r, v) 
             + 3 * MoneynessOption(S, T, r, v) 
             - MoneynessOption(S - dS, T, r, v))

    EMoneynessOption

float S = input.float(120, "% Moneyness", group = "Rates Settings") / 100
string OpType = input.string(callString, "Option type", options = [callString, putString], group = "Basic Settings")   
string side = input.string("Long", "Side", options = ["Long", "Short"], group = "Basic Settings")   

float r = input.float(8., "% Risk-free Rate", group = "Rates Settings") / 100
string rcmp = input.string(Continuous, "% Risk-free Rate Compounding Type", options = [Continuous, PeriodRate, Annual, SemiAnnual, Quaterly, Monthly], group = "Rates Settings")

float v = input.float(30., "% Volatility", group = "Rates Settings") / 100

int histvolper = input.int(22, "Historical Volatility Period", group = "Historical Volatility Settings", tooltip =  "Not used in calculation. This is here for comparison to implied volatility")
string hvoltype = input.string(c2c, "Historical Volatility Type", options = [c2c, gkvol, gkzhvol, rogersatch, ewmavolstr, parkinson], group = "Historical Volatility Settings")

string timein = input.string("Time Now", title = "Time Now Type", options = ["Time Now", "Time Bar", "Trading Day"], group = "Time Intrevals", tooltip = timtoolnow + "; " + timtoolbar + "; " + timetooltrade)
int daysinyear = input.int(252, title = "Days in Year", minval = 1, maxval = 365, group = "Time Intrevals", tooltip = "Typically 252 or 365")
float hoursinday = input.float(24, title = "Hours Per Day", minval = 1, maxval = 24, group = "Time Intrevals", tooltip = "Typically 6.5, 8, or 24")

int thruMonth = input.int(3, title = "Expiry Month", minval = 1, maxval = 12, group = "Expiry Date/Time")
int thruDay = input.int(31, title = "Expiry Day", minval = 1, maxval = 31, group = "Expiry Date/Time")
int thruYear = input.int(2023, title = "Expiry Year", minval = 1970, group = "Expiry Date/Time")
int mins = input.int(0, title = "Expiry Minute", minval = 0, maxval = 60, group = "Expiry Date/Time")
int hours = input.int(9, title = "Expiry Hour", minval = 0, maxval = 24, group = "Expiry Date/Time")
int secs = input.int(0, title = "Expiry Second", minval = 0, maxval = 60, group = "Expiry Date/Time")

string txtsize = input.string("Auto", title = "Text Size", options = ["Small", "Normal", "Tiny", "Auto", "Large"], group = "UI Options")

string outsize = switch txtsize
    "Small"=> size.small
    "Normal"=> size.normal
    "Tiny"=> size.tiny
    "Auto"=> size.auto
    "Large"=> size.large

// seconds per year given inputs above
int spyr = math.round(daysinyear * hoursinday * 60 * 60)

// precision calculation miliseconds in time intreval from time equals now
start = timein == "Time Now" ? timenow : timein == "Time Bar" ? time : time_tradingday
finish = timestamp(thruYear, thruMonth, thruDay, hours, mins, secs) 
temp = (finish - start) 
float T = (finish - start) / spyr / 1000

float rcmpval = switch rcmp
    Continuous=> 0
    PeriodRate=> math.max(1 / T, 1)
    Annual=> 1
    SemiAnnual=> 2
    Quaterly=> 4
    Monthly=> 12
    => 0

float hvolout = switch hvoltype
    parkinson => parkinsonvol(histvolper)
    rogersatch => rogerssatchel(histvolper) 
    c2c => closetoclose(math.log(S / nz(S[1])), histvolper) 
    gkvol => garmanKlass(histvolper) 
    gkzhvol => gkyzvol(histvolper) 
    ewmavolstr => ewmavol(math.log(S / nz(S[1])), histvolper) 

if barstate.islast

    sideout = side == "Long" ? 1 : -1

    kouta = convertingToCCRate(r, rcmpval)

    price               = EMoneynessOption("p", S, T, kouta, v, na)
    Delta               = EMoneynessOption("d", S, T, kouta, v, na) * sideout
    Elasticity          = EMoneynessOption("e", S, T, kouta, v, na) * sideout
    Gamma               = EMoneynessOption("g", S, T, kouta, v, na) * sideout
    DGammaDvol          = EMoneynessOption("gv", S, T, kouta, v, na) * sideout
    GammaP              = EMoneynessOption("gp", S, T, kouta, v, na) * sideout
    Vega                = EMoneynessOption("v",  S, T, kouta, v, na) * sideout
    DvegaDvol           = EMoneynessOption("dvdv", S, T, kouta, v, na) * sideout
    VegaP               = EMoneynessOption("vp", S, T, kouta, v, na) * sideout
    Theta               = EMoneynessOption("t", S, T, kouta, v, na) * sideout
    Rho                 = EMoneynessOption("r", S, T, kouta, v, na) * sideout
    RhoFuturesOption    = EMoneynessOption("fr", S, T, kouta, v, na) * sideout
    DDeltaDvol          = EMoneynessOption("dddv", S, T, kouta, v, na) * sideout
    Speed               = EMoneynessOption("s", S, T, kouta, v, na) * sideout
 
    var testTable = table.new(position = position.middle_right, columns = 2, rows = 16, bgcolor = color.gray, border_width = 1)

    table.cell(table_id = testTable, column = 0, row = 0,  text = "Moneyness Options", bgcolor=color.yellow, text_color = color.black, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 1,  text = "Option Type: " + OpType, bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 2,  text = "Side: " + side , bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 3,  text = "% Moneyness: " + str.tostring(S * 100, "##.##") + "%" , bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 4,  text = "% Risk-free Rate: " + str.tostring(r * 100, "##.##") + "%\n" + "Compounding Type: " + rcmp + "\nCC Rate: " + str.tostring(kouta * 100, "##.##") + "%", bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 5,  text = "% Volatility (annual): " + str.tostring(v * 100, "##.##") + "%", bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 6,  text = "Time Now: " + str.format("{0,date,MMMM dd, yyyy - HH:mm:ss}", timenow), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 7, text = "Expiry Date: " + str.format("{0,date,MMMM dd, yyyy - HH:mm:ss}", finish), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 8, text = "Calculated Values", bgcolor=color.yellow, text_color = color.black, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 9, text = "Hist. Volatility Type: " + hvoltype, bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 10, text = "Hist. Daily Volatility: " + str.tostring(hvolout * 100, "##.##") + "%", bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 11, text = "Hist. Annualized Volatility: " + str.tostring(hvolout * math.sqrt(daysinyear) * 100, "##.##") + "%", bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)

    table.cell(table_id = testTable, column = 1, row = 0, text = "Option Ouputs", bgcolor=color.yellow, text_color = color.black, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 1, text = "Value: " + str.tostring(price, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 2, text = "Option Sensitivities", bgcolor=color.yellow, text_color = color.black, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 3, text = "Delta Δ: " + str.tostring(Delta, "##.#####"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 4, text = "Elasticity Λ: " + str.tostring(Elasticity, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 5, text = "Gamma Γ: " + str.tostring(Gamma, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 6, text = "DGammaDvol: " + str.tostring(DGammaDvol, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 7, text = "GammaP Γ: " + str.tostring(GammaP, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 8, text = "Vega: " + str.tostring(Vega, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 9, text = "DVegaDvol: " + str.tostring(DvegaDvol, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 10, text = "VegaP: " + str.tostring(VegaP, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 11, text = "Theta Θ (1 day): " + str.tostring(Theta, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 12, text = "Rho ρ: " + str.tostring(Rho, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 13, text = "Rho Futures 0ption ρ: " + str.tostring(RhoFuturesOption, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 14, text = "DDeltaDvol: " + str.tostring(DDeltaDvol, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 15, text = "Speed: " + str.tostring(Speed, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
