Automated Option Price - Black-Scholes model
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TonioVolcano

//@version=5
indicator("Automated Option Price - Black-Scholes model", shorttitle = 'Op.Pr',  overlay = true)

// User Input for Strike Price
auto_strike = input.bool(defval = true, title = "Automatically Set Strike Price")
manual_strike_price = input.float(defval = 100.0, title = "Manual Strike Price")
// User input for Time to Expiration
auto_expiration = input.bool(defval = true, title = "Automatically Set Time to Expiration")
expiration_date = input.time(defval = timestamp("21 Jul 2023 00:00 +0300"), title = "Expiration Date")
// User input for Volatility
volatility_periods = input.int(defval = 14, title = "Volatility Periods")
// User Input for Interest Rate
risk_free_rate = input.float(defval = 0.0427, title = "Risk-Free Interest Rate (e.g. 0.02 for 2%)")
//Other Inputs
color1 = input.color(color.new(#d1d4dc, 10), title='Color1 Table')
color2 = input.color(color.new(color.gray, 10), title='Color2 Table')

// Strike Price Calculation
var float strike_price_calls = na
var float strike_price_puts = na

if auto_strike
    if close >= 1 and close <= 60
        strike_price_calls := math.ceil(close)
        strike_price_puts := math.floor(close)
    else if close > 60 and close <= 90
        strike_price_calls := math.ceil(close) + 1
        strike_price_puts := math.floor(close) - 1  
    else if close > 90 and close <= 120
        strike_price_calls := math.ceil(close) + 2
        strike_price_puts := math.floor(close) - 2
    else if close > 120
        strike_price_calls := math.ceil(close / 5) * 5
        strike_price_puts := math.floor(close / 5) * 5
else
    strike_price_calls := manual_strike_price
    strike_price_puts := manual_strike_price

// Time to Expiration
// Calculate the weekday of the current bar (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
weekday_today = dayofweek(time)

// If it's Monday (2), set expiration date to this week's Friday // If it's Tuesday (3) or later, set expiration date to next week's Friday
days_to_next_friday = (weekday_today == 2) ? 4 : (13 - weekday_today)

// Calculate the automatic expiration date
auto_expiration_date = time + (days_to_next_friday * 24 * 60 * 60 * 1000)

// Use the automatic expiration date if auto_expiration is true, otherwise use the manual expiration date input
selected_expiration_date = auto_expiration ? auto_expiration_date : expiration_date

// Calculate the time to expiration in years
time_to_expiration = (selected_expiration_date - time) / 365.0 / (24 * 60 * 60 * 1000)

// Calculate the expiration date
expiration_date_str = str.format_time(auto_expiration_date, "dd-MMM")

//Volatility
// Calculate daily returns
daily_returns = (close - close[1]) / close[1]
daily_std_dev = ta.stdev(daily_returns, volatility_periods)
annualized_volatility = daily_std_dev * math.sqrt(252)

// Calculate the ATR
atr = ta.atr(volatility_periods)
annualized_volatility_atr = atr / close * math.sqrt(252)

// Black S. Model 
// Cumulative distribution function of the standard normal distribution
phi(input_z) =>
    a1 = 0.254829592
    a2 = -0.284496736
    a3 = 1.421413741
    a4 = -1.453152027
    a5 = 1.061405429
    p = 0.3275911
    sign = input_z < 0.0 ? -1.0 : 1.0
    z_abs = math.abs(input_z) / math.sqrt(2.0)
    t = 1.0 / (1.0 + p * z_abs)
    y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * math.exp(-z_abs * z_abs)
    0.5 * (1.0 + sign * y)

// Black-Scholes model for call option price
black_scholes_call(S, K, T, r, sigma) =>
    d1 = (math.log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * math.sqrt(T))
    d2 = d1 - sigma * math.sqrt(T)
    S * phi(d1) - K * math.exp(-r * T) * phi(d2)

// Black-Scholes model for put option price
black_scholes_put(S, K, T, r, sigma) =>
    d1 = (math.log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * math.sqrt(T))
    d2 = d1 - sigma * math.sqrt(T)
    K * math.exp(-r * T) * phi(-d2) - S * phi(-d1)

// Calculate the call and put option price
call_option_price = black_scholes_call(close, strike_price_calls, time_to_expiration, risk_free_rate, annualized_volatility)
put_option_price = black_scholes_put(close, strike_price_puts, time_to_expiration, risk_free_rate, annualized_volatility)
// Calculate the call and put option price for ATR volatility
call_option_price1 = black_scholes_call(close, strike_price_calls, time_to_expiration, risk_free_rate, annualized_volatility_atr)
put_option_price1 = black_scholes_put(close, strike_price_puts, time_to_expiration, risk_free_rate, annualized_volatility_atr)

// Calculate the call and put option price at alternative price
Hcall_option_price = black_scholes_call(hl2, strike_price_calls, time_to_expiration, risk_free_rate, annualized_volatility)
Hput_option_price = black_scholes_put(hl2, strike_price_puts, time_to_expiration, risk_free_rate, annualized_volatility)
// Calculate the call and put option price for ATR volatility at alternative price
Hcall_option_price1 = black_scholes_call(hl2, strike_price_calls, time_to_expiration, risk_free_rate, annualized_volatility_atr)
Hput_option_price1 = black_scholes_put(hl2, strike_price_puts, time_to_expiration, risk_free_rate, annualized_volatility_atr)

//Blending mid points
call_option_priceF =  ((2.0 / (1.0 / call_option_price + 1.0 / call_option_price1)) + ((call_option_price + call_option_price1) / 2) ) / 2
put_option_priceF = ((2.0 / (1.0 / put_option_price + 1.0 / put_option_price1)) + ((put_option_price + put_option_price1) / 2)) / 2

Hcall_option_priceF =  ((2.0 / (1.0 / Hcall_option_price + 1.0 / Hcall_option_price1)) + ((Hcall_option_price + Hcall_option_price1) / 2) ) / 2
Hput_option_priceF = ((2.0 / (1.0 / Hput_option_price + 1.0 / Hput_option_price1)) + ((Hput_option_price + Hput_option_price1) / 2)) / 2

// Draw the table
rows = 5
cols = 3
tbl = table.new(position="top_right", rows=rows, columns=cols, bgcolor=color2, border_width=1, border_color=color2)

table.cell(tbl, row=0, column=0, text="S/Exp:", bgcolor=color2, text_color=color.new(#000000, 0), text_size = size.small)
table.cell(tbl, row=0, column=1, text=expiration_date_str, bgcolor=color2, text_color=color.new(#000000, 0), text_size = size.small)
table.cell(tbl, row=0, column=2, text="Price", bgcolor=color2, text_color=color.new(#000000, 0), text_size = size.small)

table.cell(tbl, row=1, column=0, text=str.tostring(strike_price_calls, "###"), bgcolor=color1, text_color=color.new(#000000, 0), text_size = size.small)
table.cell(tbl, row=1, column=1, text="C @ " + str.tostring(close, "##.00"), bgcolor=color1, text_color=color.new(#000000, 0), text_size = size.small)
table.cell(tbl, row=1, column=2, text=str.tostring(call_option_priceF, "##.00"), bgcolor=color1, text_color=color.new(#000000, 0), text_size = size.small)

table.cell(tbl, row=2, column=0, text=str.tostring(strike_price_calls,"###"), bgcolor=color2, text_color=color.new(#000000, 0), text_size = size.small)
table.cell(tbl, row=2, column=1, text="C @ " + str.tostring(hl2, "##.00"), bgcolor=color2, text_color=color.new(#000000, 0), text_size = size.small)
table.cell(tbl, row=2, column=2, text=str.tostring(Hcall_option_priceF, "##.00"), bgcolor=color2, text_color=color.new(#000000, 0), text_size = size.small)

table.cell(tbl, row=3, column=0, text=str.tostring(strike_price_puts,"###"), bgcolor=color1, text_color=color.new(#000000, 0), text_size = size.small)
table.cell(tbl, row=3, column=1, text="P @ " + str.tostring(close, "##.00"), bgcolor=color1, text_color=color.new(#000000, 0), text_size = size.small)
table.cell(tbl, row=3, column=2, text=str.tostring(put_option_priceF, "##.00"), bgcolor=color1, text_color=color.new(#000000, 0), text_size = size.small)

table.cell(tbl, row=4, column=0, text=str.tostring(strike_price_puts,"###"), bgcolor=color2, text_color=color.new(#000000, 0), text_size = size.small)
table.cell(tbl, row=4, column=1, text="P @ " + str.tostring(hl2, "##.00"), bgcolor=color2, text_color=color.new(#000000, 0), text_size = size.small)
table.cell(tbl, row=4, column=2, text=str.tostring(Hput_option_priceF, "##.00"), bgcolor=color2, text_color=color.new(#000000, 0), text_size = size.small)

//End