Perpetual American 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("Perpetual American Options [Loxx]", 
     shorttitle ="PMO [Loxx]",
     overlay = true, 
     max_lines_count = 500)

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

import loxx/loxxexpandedsourcetypes/4
import loxx/cnd/1
import loxx/cbnd/1

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

string Continuous = "Continuous"
string Annual = "Annual"
string SemiAnnual = "Semi-Annual"
string Quaterly = "Quaterly"
string Monthly = "Monthly"

// 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

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

PerpetualOption(string CallPutFlag, float S, float X, float r, float b, float v)=>
    float PerpetualOption = 0
    float y1 = 1 / 2 - b / math.pow(v, 2) + math.sqrt(math.pow((b / math.pow(v, 2) - 1 / 2), 2) + 2 * r / math.pow(v, 2))
    float y2 = 1 / 2 - b / math.pow(v, 2) - math.sqrt(math.pow((b / math.pow(v, 2) - 1 / 2), 2) + 2 * r / math.pow(v, 2))
 
    if CallPutFlag == callString 
        PerpetualOption := X / (y1 - 1) * math.pow((y1 - 1) / y1 * S / X, y1)
    else
        PerpetualOption := X / (1 - y2) * math.pow((y2 - 1) / y2 * S / X, y2)
    PerpetualOption

EPerpetualOption(string OutPutFlag, string CallPutFlag, float S, float X, float r, float b, float v, float dSin)=>
    float dS = dSin
    if na(dS) 
        dS := 0.01

    float EPerpetualOption = 0

    if OutPutFlag == "p" // Value
        EPerpetualOption := PerpetualOption(CallPutFlag, S, X, r, b, v)

    else if OutPutFlag == "d" // 'Delta
        EPerpetualOption := (PerpetualOption(CallPutFlag, S + dS, X, r, b, v) 
             - PerpetualOption(CallPutFlag, S - dS, X, r, b, v)) / (2 * dS)

    else if OutPutFlag == "e" // 'Elasticity
        EPerpetualOption := (PerpetualOption(CallPutFlag, S + dS, X, r, b, v) 
             - PerpetualOption(CallPutFlag, S - dS, X, r, b, v)) 
             / (2 * dS) * S / PerpetualOption(CallPutFlag, S, X, r, b, v)

    else if OutPutFlag =="g" // 'Gamma
        EPerpetualOption := (PerpetualOption(CallPutFlag, S + dS, X, r, b, v) 
             - 2 * PerpetualOption(CallPutFlag, S, X, r, b, v) 
             + PerpetualOption(CallPutFlag, S - dS, X, r, b, v)) / math.pow(dS, 2)

    else if OutPutFlag =="gv" // 'DGammaDVol
        EPerpetualOption := (PerpetualOption(CallPutFlag, S + dS, X, r, b, v + 0.01) 
             - 2 * PerpetualOption(CallPutFlag, S, X, r, b, v + 0.01) + PerpetualOption(CallPutFlag, S - dS, X, r, b, v + 0.01) 
             - PerpetualOption(CallPutFlag, S + dS, X, r, b, v - 0.01) + 2 * PerpetualOption(CallPutFlag, S, X, r, b, v - 0.01) 
             - PerpetualOption(CallPutFlag, S - dS, X, r, b, v - 0.01)) / (2 * 0.01 * math.pow(dS, 2)) / 100

    else if OutPutFlag =="gp" // 'GammaP
        EPerpetualOption := S / 100 * (PerpetualOption(CallPutFlag, S + dS, X, r, b, v) 
             - 2 * PerpetualOption(CallPutFlag, S, X, r, b, v) 
             + PerpetualOption(CallPutFlag, S - dS, X, r, b, v)) / math.pow(dS, 2)

    else if OutPutFlag =="dddv" // 'DDeltaDvol
        EPerpetualOption := 1 / (4 * dS * 0.01) 
             * (PerpetualOption(CallPutFlag, S + dS, X, r, b, v + 0.01) 
             - PerpetualOption(CallPutFlag, S + dS, X, r, b, v - 0.01) 
             - PerpetualOption(CallPutFlag, S - dS, X, r, b, v + 0.01) 
             + PerpetualOption(CallPutFlag, S - dS, X, r, b, v - 0.01)) / 100

    else if OutPutFlag =="v" // 'Vega
        EPerpetualOption := (PerpetualOption(CallPutFlag, S, X, r, b, v + 0.01) 
             - PerpetualOption(CallPutFlag, S, X, r, b, v - 0.01)) / 2

    else if OutPutFlag =="vv" // 'DvegaDvol/vomma
        EPerpetualOption := (PerpetualOption(CallPutFlag, S, X, r, b, v + 0.01) 
             - 2 * PerpetualOption(CallPutFlag, S, X, r, b, v) 
             + PerpetualOption(CallPutFlag, S, X, r, b, v - 0.01)) / math.pow(0.01, 2) / 10000

    else if OutPutFlag =="vp" // 'VegaP
        EPerpetualOption := v / 0.1 * (PerpetualOption(CallPutFlag, S, X, r, b, v + 0.01) 
             - PerpetualOption(CallPutFlag, S, X, r, b, v - 0.01)) / 2

    else if OutPutFlag =="dvdv" // 'DvegaDvol
        EPerpetualOption := (PerpetualOption(CallPutFlag, S, X, r, b, v + 0.01) 
             - 2 * PerpetualOption(CallPutFlag, S, X, r, b, v) 
             + PerpetualOption(CallPutFlag, S, X, r, b, v - 0.01))

    else if OutPutFlag =="r"  // 'Rho
        EPerpetualOption := (PerpetualOption(CallPutFlag, S, X, r + 0.01, b + 0.01, v) 
             - PerpetualOption(CallPutFlag, S, X, r - 0.01, b - 0.01, v)) / 2

    else if OutPutFlag =="fr"  // 'Futures options rho
        EPerpetualOption := (PerpetualOption(CallPutFlag, S, X, r + 0.01, b, v) 
             - PerpetualOption(CallPutFlag, S, X, r - 0.01, b, v)) / 2

    else if OutPutFlag =="f"   // 'Rho2
        EPerpetualOption := (PerpetualOption(CallPutFlag, S, X, r, b - 0.01, v) 
             - PerpetualOption(CallPutFlag, S, X, r, b + 0.01, v)) / 2

    else if OutPutFlag =="b"   // 'Carry
        EPerpetualOption := (PerpetualOption(CallPutFlag, S, X, r, b + 0.01, v) 
             - PerpetualOption(CallPutFlag, S, X, r, b - 0.01, v)) / 2

    else if OutPutFlag =="s"   // 'Speed
        EPerpetualOption := 1 / math.pow(dS, 3) * (PerpetualOption(CallPutFlag, S + 2 * dS, X, r, b, v) 
             - 3 * PerpetualOption(CallPutFlag, S + dS, X, r, b, v)
             + 3 * PerpetualOption(CallPutFlag, S, X, r, b, v) - PerpetualOption(CallPutFlag, S - dS, X, r, b, v))

    else if OutPutFlag =="dx"  // 'Strike Delta
        EPerpetualOption := (PerpetualOption(CallPutFlag, S, X + dS, r, b, v) 
             - PerpetualOption(CallPutFlag, S, X - dS, r, b, v)) / (2 * dS)

    else if OutPutFlag =="dxdx"   // 'Strike Gamma
        EPerpetualOption := (PerpetualOption(CallPutFlag, S, X + dS, r, b, v) 
             - 2 * PerpetualOption(CallPutFlag, S, X, r, b, v) 
             + PerpetualOption(CallPutFlag, S, X - dS, r, b, v)) / math.pow(dS, 2)

    EPerpetualOption



smthtype = input.string("Kaufman", "Heikin-Ashi Better Caculation Type", options = ["AMA", "T3", "Kaufman"], group = "Spot Price Settings")
srcin = input.string("Close", "Spot Price", group= "Spot Price Settings", 
     options = 
     ["Close", "Open", "High", "Low", "Median", "Typical", "Weighted", "Average", "Average Median Body", "Trend Biased", "Trend Biased (Extreme)", 
     "HA Close", "HA Open", "HA High", "HA Low", "HA Median", "HA Typical", "HA Weighted", "HA Average", "HA Average Median Body", "HA Trend Biased", "HA Trend Biased (Extreme)",
     "HAB Close", "HAB Open", "HAB High", "HAB Low", "HAB Median", "HAB Typical", "HAB Weighted", "HAB Average", "HAB Average Median Body", "HAB Trend Biased", "HAB Trend Biased (Extreme)"])

float K = input.float(275, "Strike Price", group = "Basic Settings")   
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(6., "% Risk-free Rate", group = "Rates Settings") / 100
string rcmp = input.string(Continuous, "% Risk-free Rate Compounding Type", options = [Continuous, Annual, SemiAnnual, Quaterly, Monthly], group = "Rates Settings")

float b = input.float(6., "% Cost of Carry", group = "Rates Settings") / 100
string bcmp = input.string(Continuous, "% Cost of Carry Compounding Type", options = [Continuous, Annual, SemiAnnual, Quaterly, Monthly], group = "Rates Settings")

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

string txtsize = input.string("Auto", title = "Expiry Second", 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


kfl=input.float(0.666, title="* Kaufman's Adaptive MA (KAMA) Only - Fast End", group = "Moving Average Inputs")
ksl=input.float(0.0645, title="* Kaufman's Adaptive MA (KAMA) Only - Slow End", group = "Moving Average Inputs")
amafl = input.int(2, title="* Adaptive Moving Average (AMA) Only - Fast", group = "Moving Average Inputs")
amasl = input.int(30, title="* Adaptive Moving Average (AMA) Only - Slow", group = "Moving Average Inputs")

haclose = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, close)
haopen = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, open)
hahigh = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, high)
halow = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, low)
hamedian = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, hl2)
hatypical = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, hlc3)
haweighted = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, hlcc4)
haaverage = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, ohlc4)

float rcmpval = switch rcmp
    Continuous=> 0
    Annual=> 1
    SemiAnnual=> 2
    Quaterly=> 4
    Monthly=> 12
    => 0

float bcmpval = switch bcmp
    Continuous=> 0
    Annual=> 1
    SemiAnnual=> 2
    Quaterly=> 4
    Monthly=> 12
    => 0

float S = switch srcin
	"Close" => loxxexpandedsourcetypes.rclose()
	"Open" => loxxexpandedsourcetypes.ropen()
	"High" => loxxexpandedsourcetypes.rhigh()
	"Low" => loxxexpandedsourcetypes.rlow()
	"Median" => loxxexpandedsourcetypes.rmedian()
	"Typical" => loxxexpandedsourcetypes.rtypical()
	"Weighted" => loxxexpandedsourcetypes.rweighted()
	"Average" => loxxexpandedsourcetypes.raverage()
    "Average Median Body" => loxxexpandedsourcetypes.ravemedbody()
	"Trend Biased" => loxxexpandedsourcetypes.rtrendb()
	"Trend Biased (Extreme)" => loxxexpandedsourcetypes.rtrendbext()
	"HA Close" => loxxexpandedsourcetypes.haclose(haclose)
	"HA Open" => loxxexpandedsourcetypes.haopen(haopen)
	"HA High" => loxxexpandedsourcetypes.hahigh(hahigh)
	"HA Low" => loxxexpandedsourcetypes.halow(halow)
	"HA Median" => loxxexpandedsourcetypes.hamedian(hamedian)
	"HA Typical" => loxxexpandedsourcetypes.hatypical(hatypical)
	"HA Weighted" => loxxexpandedsourcetypes.haweighted(haweighted)
	"HA Average" => loxxexpandedsourcetypes.haaverage(haaverage)
    "HA Average Median Body" => loxxexpandedsourcetypes.haavemedbody(haclose, haopen)
	"HA Trend Biased" => loxxexpandedsourcetypes.hatrendb(haclose, haopen, hahigh, halow)
	"HA Trend Biased (Extreme)" => loxxexpandedsourcetypes.hatrendb(haclose, haopen, hahigh, halow)
	"HAB Close" => loxxexpandedsourcetypes.habclose(smthtype, amafl, amasl, kfl, ksl)
	"HAB Open" => loxxexpandedsourcetypes.habopen(smthtype, amafl, amasl, kfl, ksl)
	"HAB High" => loxxexpandedsourcetypes.habhigh(smthtype, amafl, amasl, kfl, ksl)
	"HAB Low" => loxxexpandedsourcetypes.hablow(smthtype, amafl, amasl, kfl, ksl)
	"HAB Median" => loxxexpandedsourcetypes.habmedian(smthtype, amafl, amasl, kfl, ksl)
    "HAB Typical" => loxxexpandedsourcetypes.habtypical(smthtype, amafl, amasl, kfl, ksl)
	"HAB Weighted" => loxxexpandedsourcetypes.habweighted(smthtype, amafl, amasl, kfl, ksl)
	"HAB Average" => loxxexpandedsourcetypes.habaverage(smthtype, amafl, amasl, kfl, ksl)
    "HAB Average Median Body" => loxxexpandedsourcetypes.habavemedbody(smthtype, amafl, amasl, kfl, ksl)
	"HAB Trend Biased" => loxxexpandedsourcetypes.habtrendb(smthtype, amafl, amasl, kfl, ksl)
	"HAB Trend Biased (Extreme)" => loxxexpandedsourcetypes.habtrendbext(smthtype, amafl, amasl, kfl, ksl)
	=> haclose

if barstate.islast

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

    kouta = convertingToCCRate(r, rcmpval)
    koutb = convertingToCCRate(b, bcmpval)

    amaproxprice        = EPerpetualOption("p", OpType, S, K, kouta, koutb, v, na) * sideout

    Delta               = EPerpetualOption("d", OpType, S, K, kouta, koutb, v, na) * sideout
    Elasticity          = EPerpetualOption("e", OpType, S, K, kouta, koutb, v, na) * sideout
    Gamma               = EPerpetualOption("g", OpType, S, K, kouta, koutb, v, na) * sideout
    DGammaDvol          = EPerpetualOption("gv", OpType, S, K, kouta, koutb, v, na) * sideout
    GammaP              = EPerpetualOption("gp", OpType, S, K, kouta, koutb, v, na) * sideout
    Vega                = EPerpetualOption("v", OpType, S, K, kouta, koutb, v, na) * sideout
    DvegaDvol           = EPerpetualOption("dvdv", OpType, S, K, kouta, koutb, v, na) * sideout
    VegaP               = EPerpetualOption("vp", OpType, S, K, kouta, koutb, v, na) * sideout
    Rho                 = EPerpetualOption("r", OpType, S, K, kouta, koutb, v, na) * sideout
    RhoFuturesOption    = EPerpetualOption("fr", OpType, S, K, kouta, koutb, v, na) * sideout
    PhiRho2             = EPerpetualOption("f", OpType, S, K, kouta, koutb, v, na) * sideout
    Carry               = EPerpetualOption("b", OpType, S, K, kouta, koutb, v, na) * sideout
    DDeltaDvol          = EPerpetualOption("dddv", OpType, S, K, kouta, koutb, v, na) * sideout
    Speed               = EPerpetualOption("s", OpType, S, K, kouta, koutb, v, na) * sideout
    StrikeDelta         = EPerpetualOption("dx", OpType, S, K, kouta, koutb, v, na) * sideout
    StrikeGamma         = EPerpetualOption("dxdx", OpType, S, K, kouta, koutb, v, na) * sideout

    var testTable = table.new(position = position.middle_right, columns = 2, rows = 21, bgcolor = color.gray, border_width = 1)

    table.cell(table_id = testTable, column = 0, row = 0,  text = "Perpetual American 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 = "Spot Price: " + str.tostring(S, format.mintick) , bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 4,  text = "Strike Price: " + str.tostring(K, format.mintick), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 5,  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 = 6,  text = "% Cost of Carry: " + str.tostring(b * 100, "##.##") + "%\n" + "Compounding Type: " + bcmp + "\nCC Carry: " + str.tostring(koutb * 100, "##.##") + "%", bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 0, row = 7,  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 = 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 = "Perpetual American Price: " + str.tostring(amaproxprice, format.mintick), 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 = "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 = 12, 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 = 13, text = "Phi/Rho2: " + str.tostring(PhiRho2, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 14, text = "Carry: " + str.tostring(Carry, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 15, 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 = 16, text = "Speed: " + str.tostring(Speed, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 17, text = "Strike Delta: " + str.tostring(StrikeDelta, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)
    table.cell(table_id = testTable, column = 1, row = 18, text = "Strike Gamma: " + str.tostring(StrikeGamma, "##.########"), bgcolor=darkGreenColor, text_color = color.white, text_size = outsize, text_halign = text.align_left)



