trend_vol_stop
// 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_stop", overlay = true, precision = 3)

// Usage:
//
// The inputs define the trend and the volatility stop. 
//
// Trend:
//      The trend is defined by a moving average crossover. When the short 
//      (or fast) moving average is above the long (slow) moving average, the 
//      trend is up. Otherwise, the trend is down. The inputs are:
//
//      long: the number of periods in the long/slow moving average.
//      short: the number of periods in the short/fast moving average.
//
//      The slow moving average is shown in various colors (see explanation 
//      below. The fast moving average is a faint blue.
//
// Volatility stop:
//      The volatility stop has two modes, percentage and rank. The percentage
//      stop is given in terms of annualized volatility. The rank stop is given
//      in terms of percentile.
//
//      stop_pct and stop_rank are initialized with "-1". You need to set one of
//      these to the values you want after adding the indicator to your chart.
//      This is the only setting that requires your input.
//
//      mode: choose "rank" for a rank stop, "percentage" for a percentage stop.
//      vol_window: the number of periods in the historical volatility 
//          calculation. e.g. "30" means the volatility will be a weighted 
//          average of the previous 30 periods. applies to both types of stop.
//      stop_pct: the volatility limit, annualized. for example, "50" means
//          that the trend will not be followed when historical volatility rises
//          above 50%.
//      stop_rank: the trend will not be followed when the volatility is in the
//          N-th percentile. for example, "75" means the trend will not be
//          followed when the current historical volatility is greater than 75%
//          of previous volatilities.
//      rank_window: the number of periods in the rank percentile calculation.
//          for example, if rank_window is "252" and "stop_rank" is "80", the
//          trend will not be followed when current historical volatility is
//          greater than 80% of the previous 252 historical volatilities.
//
// Outputs:
//
// The outputs include moving averages, to visually identify the trend,
// a volatility table, and a performance table.
//
// Moving averages:
//      The slow moving average is colored green in an uptrend, red in a
//      downtrend, and black when the volatility stop is in place.
//
// Volatility table:
//      The volatility table gives the current historical volatility, annualized
//      and expressed as a whole number percentage. E.g. "65" means the
//      instrument's one standard deviation annual move is 65% of its price.
//
//      The current rank is expressed, also as a whole number percentage. E.g.
//      "15" means the current volatility is greater than 15% of previous
//      volatilities. For convenience, the volatilities corresponding to the
//      0, 25, 50, 75, and 100th percentiles are also shown.
//
//  Performance table:
//      The performance table shows the current strategy's performance versus
//      buy-and-hold. If the trend is up, the instrument's return for that 
//      period is added to the strategy's return, because the strategy is long.
//      If the trend is down, the negative return is added, because the strategy
//      is short. If the volatility stop is in (the slow moving average is 
//      black), that period's return is excluded from the strategy returns.
//
//      Every period's return is added to the buy-and-hold returns.
//
//      The table shows the average return, the standard deviation of returns,
//      and the sharpe ratio (average return / standard deviation of returns).
//      All figures are expressed as per-period, whole number percentages.
//      For exmaple, "0.1" in the mean column on a daily chart means a 
//      0.1% daily return.
//
//      The number of periods (samples) for each strategy 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)
mode = input(title = "mode", options = [ "percentage", "rank" ], defval = "rank")
vol_window = input(title = "volatility window", type = input.integer, defval = 30)
stop_pct = input(title = "stop percentage", type = input.float, defval = -1)
stop_rank = input(title = "stop rank", type = input.float, defval = -1)
rank_window = input(title = "rank window", type = input.integer, defval = 252)

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

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

ppy = (365 * 24 * 60 * 60 * 1000) / (time - time[1])

r = log(close / close[1])
hv = sqrt(ema(pow(r, 2), vol_window) * ppy) * 100
rank = percentrank(hv, rank_window)

var bool up = na
var bool down = na

if mode == "percentage"
    up := fast > slow and hv < stop_pct and hv < stop_pct
    down := fast < slow and hv < stop_pct and hv < stop_pct
else if mode == "rank"
    up := fast > slow and rank < stop_rank and rank < stop_rank
    down := fast < slow and rank < stop_rank and rank < stop_rank

if up
    array.push(strategy_returns, r)
else if down
    array.push(strategy_returns, -r)
    
array.push(all_returns, r)
    
rank_0 = percentile_nearest_rank(hv, rank_window, 0)
rank_25 = percentile_nearest_rank(hv, rank_window, 25)
rank_50 = percentile_nearest_rank(hv, rank_window, 50)
rank_75 = percentile_nearest_rank(hv, rank_window, 75)
rank_100 = percentile_nearest_rank(hv, rank_window, 100)

if barstate.islast
    fmt = "#.##"
    
    dt = table.new(position.top_right, 2, 7, bgcolor = color.white)
    
    table.cell(dt, 0, 0, "hv current")
    table.cell(dt, 1, 0, tostring(hv, fmt))
    
    table.cell(dt, 0, 1, "rank current")
    table.cell(dt, 1, 1, tostring(rank, fmt))
    
    table.cell(dt, 0, 2, "0%")
    table.cell(dt, 1, 2, tostring(rank_0, fmt))
    
    table.cell(dt, 0, 3, "25%")
    table.cell(dt, 1, 3, tostring(rank_25, fmt))
    
    table.cell(dt, 0, 4, "50%")
    table.cell(dt, 1, 4, tostring(rank_50, fmt))
    
    table.cell(dt, 0, 5, "75%")
    table.cell(dt, 1, 5, tostring(rank_75, fmt))
    
    table.cell(dt, 0, 6, "100%")
    table.cell(dt, 1, 6, tostring(rank_100, 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))
    
plot(slow, color = up ? color.green : down ? color.red : color.black)
plot(fast, color = color.new(color.blue, 80))