vol_bracket
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © voided

// This simple script shows an "N" standard deviation volatility bracket, 
// anchored at the opening price of the current month, week, or quarter. This
// anchor is meant to coincide roughly with the expiration of options issued at
// the same interval. You can choose between a manually-entered IV or the
// hv30 volatility model.
//
// The table shows the "efficiency" which is the ratio of periods where price
// closed within the bracket (both), below the top bound (up), or above the
// bottom bound (down).
//
// This script is meant for the daily chart. It is not accurate on intraday
// charts.


//@version=4
study("vol_bracket", overlay = true)

// inputs
model = input(title = "model", options = [ "hv20", "iv" ], defval = "hv20")
iv = input(title = "iv", type = input.float, defval = 20.0) / 100
period = input(title = "period", options = [ "daily", "weekly", "monthly", "quarterly" ], defval = "daily")
stdevs = input(title = "stdevs", type = input.float, defval = 1.0)

// time for scaling bands
t = if period == "weekly"
    sqrt(1.0 / 52)
else if period == "monthly" 
    sqrt(1.0 / 12)
else if period == "quarterly"
    sqrt(1.0 / 4) 
else if period == "daily"
    sqrt(1.0 / 252)

// set mark
var float mark = 0
var int periods = 0

quarter = int(month(time_close) / 4)

new_day = true
new_week = weekofyear != weekofyear[1]
new_month = month(time_close) != month(time_close)[1]
new_quarter = quarter != quarter[1]
new_period = false

if (period == "weekly" and new_week) or (period == "monthly" and new_month) or (period == "quarterly" and new_quarter) or (period == "daily" and new_day)
    mark := open
    periods := periods + 1
    new_period := true
else
    mark := mark[1]

// hv30
r = log(close / close[1])
variance = pow(r, 2)
hv20 = sqrt(ema(variance, 20) * 252)

// choose model
v = if model == "hv20"
    hv20
else if model == "iv"
    iv

// set bracket
up = mark * (1 + v * t * stdevs) 
down = mark * (1 - v * t * stdevs)

in_up = new_period and close[1] < up[1] ? 1 : 0
in_down = new_period and close[1] > down[1] ? 1 : 0
in_both = new_period and in_up == 1 and in_down == 1 ? 1 : 0

total_in_up = cum(in_up)
total_in_down = cum(in_down)
total_in_both = cum(in_both)

// table
if barstate.islast
    var ot = table.new(position.top_right, 4, 3, bgcolor = color.white)
    
    fmt = "#.##"
    lbl_clr = color.new(color.green, 70)
    
    table.cell(ot, 0, 0, "efficiency", bgcolor = lbl_clr)
    table.cell(ot, 1, 0, "both", bgcolor = lbl_clr)
    table.cell(ot, 2, 0, "up", bgcolor = lbl_clr)
    table.cell(ot, 3, 0, "down", bgcolor = lbl_clr)

    table.cell(ot, 0, 1, "percentage", bgcolor = lbl_clr)
    table.cell(ot, 1, 1, tostring(total_in_both / periods * 100, fmt))
    table.cell(ot, 2, 1, tostring(total_in_up / periods * 100, fmt))
    table.cell(ot, 3, 1, tostring(total_in_down / periods * 100, fmt))

    table.cell(ot, 0, 2, "count", bgcolor = lbl_clr)
    table.cell(ot, 1, 2, tostring(total_in_both) + " / " + tostring(periods))
    table.cell(ot, 2, 2, tostring(total_in_up) + " / " + tostring(periods))
    table.cell(ot, 3, 2, tostring(total_in_down) + " / " + tostring(periods))
    
    var ot2 = table.new(position.middle_right, 2, 1, bgcolor = color.white)
    
    table.cell(ot2, 0, 0, model, bgcolor = lbl_clr)
    table.cell(ot2, 1, 0, tostring(v, "#.###"))

// plot

bracket_style = period == "daily" ? plot.style_circles : plot.style_stepline
in_now = close < up and close > down
in_clr = color.blue
out_clr = color.fuchsia

plot(mark, color = color.new(color.gray, 60), style = bracket_style)
plot(up, color = in_now ? in_clr : out_clr, style = bracket_style)
plot(down, color = in_now ? in_clr : out_clr, style = bracket_style)