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

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

// USAGE
//
// This script compares trend trading with a volatility stop to "buy and hold".
// Trades are taken with the trend, except when price exceeds a volatility 
// forecast. The trend is defined by a moving average crossover. The forecast
// is based on projecting future volatility from historical volatility.
//
// The trend is defined by two parameters:
//
//      - long: the length of a long ("slow") moving average.
//      - short: the length of a short ("fast") moving average.
//
// The trend is up when the short moving average is above the long. Otherwise
// it is down.
//
// The volatility stop is defined by three parameters:
//
//      - volatility window: determines the number of periods in the historical
//        volatility calculation. More periods means a slower (smoother)
//        estimate of historical volatility.
//      - stop forecast periods: the number of periods in the volatility 
//        forecast. For example, "7" on a daily chart means that the volatility
//        will be forecasted with a one week lag.
//      - stop forecast stdev: the number of standard deviations in the stop
//        forecast. For example, "2" means two standard deviations.
//
// EXAMPLE
//
// The default parameters are:
//
//      - long: 50
//      - short: 20
//      - volatility window: 30
//      - stop forecast periods: 7
//      - stop forecast standard deviations: 1
//
// The trend will be up when the 20 period moving average is above the 50
// period moving average. On each bar, the historical volatility will be 
// calculated from the previous 30 bars. If the historical volatility is 0.65
// (65%), then a forecast will be drawn as a fuchsia line, subtracting
// 0.65 * sqrt(7 / 365) from the closing price. If price at any point falls
// below the forecast, the volatility stop is in place, and the trend is
// negated.
//
// OUTPUTS
// 
// Plots: 
//      - The trend is shown by painting the slow moving average green (up), red 
//        (down), or black (none; volatility stop).
//      - The fast moving average is shown in faint blue
//      - The previous volatility forecasts are shown in faint fuchsia
//      - The current volatility forecast is shown as a fuchsia line, projecting
//        into the future as far as it is valid.
//
// Tables:
//      - The current historical volatility is given in the top right corner, as a
//        whole number percentage.
//      - The performance table shows the mean, standard deviation, and sharpe
//        ratio of the volatility stop trend strategy, as well as buy and hold.
//        If the trend is up, each period's return is added to the sample (the 
//        strategy is long). If the trend is down, the inverse of each period's
//        return is added to the sample (the strategy is short). If there is no
//        trend (the volatility stop is active), the period's return is excluded
//        from the sample. Every period is added to the buy-and-hold strategy's
//        sample. The total number of periods in each sample is also shown.

// INPUTS
long = input(title = "long ma lag", type = input.integer, defval = 50)
short = input(title = "short ma lag", type = input.integer, defval = 20)
vol_window = input(title = "volatility window", type = input.integer, defval = 30)
stop_fcst_periods = input(title = "stop forecast periods", type = input.float, defval = 7)
stop_fcst_stdev = input(title = "stop forecast stdev", type = input.float, defval = 1)

// PROGRAM
var all_returns = array.new_float(0)
var strategy_returns = array.new_float(0)

slow = ema(close, long)
fast = ema(close, short)

r = log(close / close[1])
pd = time - time[1]
ppy = (365 * 24 * 60 * 60 * 1000) / pd
hv = sqrt(ema(pow(r, 2), vol_window) * ppy)

fcst = hv * stop_fcst_stdev * sqrt(stop_fcst_periods / ppy)
fcst_up = close[stop_fcst_periods] * (1 + fcst[stop_fcst_periods])
fcst_down = close[stop_fcst_periods] * (1 - fcst[stop_fcst_periods])

up = fast > slow and close > fcst_down
down = fast < slow and close < fcst_up

if up
    array.push(strategy_returns, r)
else if down
    array.push(strategy_returns, -r)
    
array.push(all_returns, r)

// OUTPUT
if barstate.islast
    fmt = "#.###"
    
    fcst_up_next = close * (1 + fcst)
    fcst_down_next = close * (1 - fcst)
    
    fcst_ln_up = line.new(time, fcst_up_next, int(time + stop_fcst_periods * pd), fcst_up_next, xloc = xloc.bar_time, color = down ? color.fuchsia : na)
    fcst_ln_down = line.new(time, fcst_down_next, int(time + stop_fcst_periods * pd), fcst_down_next, xloc = xloc.bar_time, color = up ? color.fuchsia : na)
    
    dt = table.new(position.top_right, 2, 3, bgcolor = color.white)
    
    table.cell(dt, 0, 0, "hv current")
    table.cell(dt, 1, 0, tostring(hv * 100, fmt))
    
    table.cell(dt, 0, 1, "stop level")
    table.cell(dt, 1, 1, tostring(down ? fcst_down : up ? fcst_up : na, fmt))
    
    table.cell(dt, 0, 2, "stop %")
    table.cell(dt, 1, 2, tostring(down ? abs((fcst_down - close) / close) * 100 : up ? abs((fcst_up - close) / close) * 100 : na, fmt))
    
    rt = table.new(position.middle_right, 5, 3, bgcolor = color.white)
    
    table.cell(rt, 0, 0, "", bgcolor = na)
    table.cell(rt, 1, 0, "mean")
    table.cell(rt, 2, 0, "stdev")
    table.cell(rt, 3, 0, "sharpe")
    table.cell(rt, 4, 0, "samples")
    
    bh_avg = array.avg(all_returns) * 100
    bh_std = array.stdev(all_returns) * 100
    bh_shr = bh_avg / bh_std
    bh_smp = array.size(all_returns)
    
    table.cell(rt, 0, 1, "buy and hold")
    table.cell(rt, 1, 1, tostring(bh_avg, fmt))
    table.cell(rt, 2, 1, tostring(bh_std, fmt))
    table.cell(rt, 3, 1, tostring(bh_shr, fmt))
    table.cell(rt, 4, 1, tostring(bh_smp))
    
    str_avg = array.avg(strategy_returns) * 100
    str_std = array.stdev(strategy_returns) * 100
    str_shr = str_avg / str_std
    str_smp = array.size(strategy_returns)
    
    table.cell(rt, 0, 2, "strategy")
    table.cell(rt, 1, 2, tostring(str_avg, fmt))
    table.cell(rt, 2, 2, tostring(str_std, fmt))
    table.cell(rt, 3, 2, tostring(str_shr, fmt))
    table.cell(rt, 4, 2, tostring(str_smp))
    
// plots and labels
plot(slow, color = up ? color.green : down ? color.red : color.black)
plot(fast, color = color.new(color.blue, 80))

fcst_clr = color.new(color.fuchsia, 80)

plot(fcst_up, color = down ? fcst_clr : na, style = plot.style_stepline)
plot(fcst_down, color = up ? fcst_clr : na, style = plot.style_stepline)

if up and low < fcst_down
    label.new(bar_index, low, "x", style = label.style_none, textcolor = color.fuchsia)
else if down and high > fcst_up
    label.new(bar_index, high, "x", style = label.style_none, textcolor = color.fuchsia)