SPX Fair Value Bands V2
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TraderLeibniz, an updated version of the script dharmatech and based on the liquidity concept by MaxJAnderson
//      described here:  https://archive.ph/gwoBq   https://archive.ph/AXuMn   

// Also check out the my fixed version of dharmatech's powershell script that grabs the same data: 
//        https://pastebin.com/NK7D89kL

//@version=5
indicator("SPX Fair Value Bands V2", overlay=true, timeframe="", timeframe_gaps=true)

var float  _SCALE = 0.000001/1000 // depends on the units that TradingView decides to set from FRED
var string _NET_LIQUIDITY_EXPR = "(FRED:WALCL - FRED:WTREGEN - FRED:RRPONTSYD)"
var string _TIMEFRAME = "1D"  // only RRPONTSYD data is updated daily (TGA data from treasury updates daily, but FRED:WTREGEN is weekly)

// Max J Anderson's regression of SPX vs. Net Liquidity (94% r-squared)
var float _SLOPE = 1/1.1
var float _FAIR_VALUE_OFFSET = -1625.0
var float _UPPER_BAND_OFFSET = 350.0
var float _LOWER_BAND_OFFSET = 150.0

//------------------

fred_scale = input.float(defval=_SCALE, title='FRED Unit Scaling', group="Core Settings")
netliq_expr = input.string(defval=_NET_LIQUIDITY_EXPR, title='Net Liquidity Expression', group="Core Settings")

reg_slope = input.float(defval=_SLOPE, title='Regression Slope', group="Core Settings")
reg_offset = input.float(defval=_FAIR_VALUE_OFFSET, title='Regression Offset', group="Core Settings")

//------------------

hi_offset = input.float(defval=_UPPER_BAND_OFFSET, title='Offset for Sell Band', group="Std Band Settings")
lo_offset = input.float(defval=_LOWER_BAND_OFFSET, title='Offset for Buy Band', group="Std Band Settings")

bar_offset = input.int(defval=0, title='Bar (Temporal) Offset', group="Std Band Settings")

//------------------

// Multi timeframe settings

band_tf = input.timeframe(defval=_TIMEFRAME, title="Band Timeframe", 
   options=["1D","2D","3D","4D","1W","2W","3W","1M","5D","6D","7D",
   "8D","9D","10D","11D","12D","13D","14D","15D","16D","17D","18D",
   "19D","20D","21D","22D","23D","24D"], group="Band Timeframe Settings")
bb_tf = input.timeframe(defval=_TIMEFRAME, title="Bollinger Timeframe", 
   options=["1D","2D","3D","4D","1W","2W","3W","1M","5D","6D","7D",
   "8D","9D","10D","11D","12D","13D","14D","15D","16D","17D","18D",
   "19D","20D","21D","22D","23D","24D"], group="Band Timeframe Settings")
kc_tf = input.timeframe(defval=_TIMEFRAME, title="Keltner Timeframe", 
   options=["1D","2D","3D","4D","1W","2W","3W","1M","5D","6D","7D","8D",
   "9D","10D","11D","12D","13D","14D","15D","16D","17D","18D",
   "19D","20D","21D","22D","23D","24D"], group="Band Timeframe Settings")

//------------------

scaled_expr = close*fred_scale*reg_slope + reg_offset
scaled_upper_expr = scaled_expr + hi_offset
scaled_lower_expr = scaled_expr - lo_offset

fv_band = request.security(symbol=netliq_expr, timeframe=band_tf, expression=scaled_expr)
sell_band = request.security(symbol=netliq_expr, timeframe=band_tf, expression=scaled_upper_expr)
buy_band = request.security(symbol=netliq_expr, timeframe=band_tf, expression=scaled_lower_expr)

draw_sell_band = plot(series=sell_band, color=color.new(color.fuchsia,100), offset=bar_offset, title='Sell Band')
draw_buy_band = plot(series=buy_band, color=color.new(color.teal,100), offset=bar_offset, title='Buy Band')
draw_fv_band = plot(series=fv_band, color=color.new(color.gray,0), offset=bar_offset, title='Fair Value')

//=====================================

// bollinger bands
bb_len = input.int(defval=14, title='BB (SMA) Period', group="Bollinger Band Settings")
bb_hi_stdev = input.float(defval=2.5, title='BB Sell Mult', group="Bollinger Band Settings")
bb_lo_stdev = input.float(defval=0.6, title='BB Buy Mult', group="Bollinger Band Settings")

get_bol_bands(_hi_line, _lo_line, _sma_len, _std_dev_hi, _std_dev_lo) => 
    [_bb_a_sma, _bb_a_hi, _bb_a_lo] = ta.bb(_hi_line, _sma_len, _std_dev_hi)
    [_bb_b_sma, _bb_b_hi, _bb_b_lo] = ta.bb(_lo_line, _sma_len, _std_dev_lo)
    [_bb_a_lo, _bb_b_hi]

//=====================================

// keltner channel bands
kelt_len = input.int(defval=9, title='Keltner (EMA) Period', group="Keltner Channel Band Settings")
kelt_hi_atr = input.float(defval=1.5, title='Keltner Sell Mult', group="Keltner Channel Band Settings")
kelt_lo_atr = input.float(defval=0.5, title='Keltner Buy Mult', group="Keltner Channel Band Settings")

get_kelt_bands(_hi_line, _lo_line, _ema_len, _atr_hi, _atr_lo) => 
    [_kc_a_ema, _kc_a_hi, _kc_a_lo] = ta.kc(_hi_line, _ema_len, _atr_hi)
    [_kc_b_ema, _kc_b_hi, _kc_b_lo] = ta.kc(_lo_line, _ema_len, _atr_lo)
    [_kc_a_ema, _kc_b_ema]

//=====================================

sell_band_bb_tf = request.security(symbol=netliq_expr, timeframe=bb_tf, expression=scaled_upper_expr)
buy_band_bb_tf = request.security(symbol=netliq_expr, timeframe=bb_tf, expression=scaled_lower_expr)
sell_band_kc_tf = request.security(symbol=netliq_expr, timeframe=kc_tf, expression=scaled_upper_expr)
buy_band_kc_tf = request.security(symbol=netliq_expr, timeframe=kc_tf, expression=scaled_lower_expr)

// ---------

[ bb_sell_band, bb_buy_band ] = get_bol_bands(sell_band_bb_tf,buy_band_bb_tf,bb_len,bb_hi_stdev,bb_lo_stdev)

draw_bb_sell_band = plot(series=bb_sell_band, color=color.new(color.fuchsia,0), offset=bar_offset, title='BB Sell Band')
draw_bb_buy_band = plot(series=bb_buy_band, color=color.new(color.teal,0), offset=bar_offset, title='BB Buy Band')

// ---------

[ kc_sell_band, kc_buy_band ] = get_kelt_bands(sell_band_kc_tf,buy_band_kc_tf,kelt_len,kelt_hi_atr,kelt_lo_atr)

draw_kc_sell_band = plot(series=kc_sell_band, color=color.new(color.fuchsia,100), offset=bar_offset, title='KC Sell Band')
draw_kc_buy_band = plot(series=kc_buy_band, color=color.new(color.teal,100), offset=bar_offset, title='KC Buy Band')

//=====================================

fill_color_src = input.source(defval=close, title='Band Fill Color Decision Source', group="Band Fill Settings")

var color _USER_SELECT_STD_BAND_FILL_BELOW_BAND_COLOR = input.color(defval=color.new(color.white,100), title='Below Band', inline='Std Band Fill Color', group='Std Band Fill Settings')
var color _USER_SELECT_STD_BAND_FILL_BELOW_FV_COLOR   = input.color(defval=color.new(color.white,100), title='Below FV', inline='Std Band Fill Color', group='Std Band Fill Settings')
var color _USER_SELECT_STD_BAND_FILL_ABOVE_FV_COLOR   = input.color(defval=color.new(color.white,100), title='Above FV', inline='Std Band Fill Color', group='Std Band Fill Settings')
var color _USER_SELECT_STD_BAND_FILL_ABOVE_BAND_COLOR = input.color(defval=color.new(color.white,100), title='Above Band', inline='Std Band Fill Color', group='Std Band Fill Settings')
var color _USER_SELECT_BB_BAND_FILL_BELOW_BAND_COLOR  = input.color(defval=color.new(color.white,100), title='Below Band', inline='BB Fill Color', group='BB Fill Settings')
var color _USER_SELECT_BB_BAND_FILL_BELOW_FV_COLOR    = input.color(defval=color.new(color.white,100), title='Below FV', inline='BB Fill Color', group='BB Fill Settings')
var color _USER_SELECT_BB_BAND_FILL_ABOVE_FV_COLOR    = input.color(defval=color.new(color.white,100), title='Above FV', inline='BB Fill Color', group='BB Fill Settings')
var color _USER_SELECT_BB_BAND_FILL_ABOVE_BAND_COLOR  = input.color(defval=color.new(color.white,100), title='Above Band', inline='BB Fill Color', group='BB Fill Settings')
var color _USER_SELECT_KC_BAND_FILL_BELOW_BAND_COLOR  = input.color(defval=color.new(color.white,100), title='Below Band', inline='KC Fill Color', group='KC Fill Settings')
var color _USER_SELECT_KC_BAND_FILL_BELOW_FV_COLOR    = input.color(defval=color.new(color.white,100), title='Below FV', inline='KC Fill Color', group='KC Fill Settings')
var color _USER_SELECT_KC_BAND_FILL_ABOVE_FV_COLOR    = input.color(defval=color.new(color.white,100), title='Above FV', inline='KC Fill Color', group='KC Fill Settings')
var color _USER_SELECT_KC_BAND_FILL_ABOVE_BAND_COLOR  = input.color(defval=color.new(color.white,100), title='Above Band', inline='KC Fill Color', group='KC Fill Settings')

//----

var bool below_fv_cond = false
var bool std_above_band_cond = false
var bool std_below_band_cond = false
var bool bb_above_band_cond = false
var bool bb_below_band_cond = false
var bool kc_above_band_cond = false
var bool kc_below_band_cond = false

below_fv_cond       := na(fill_color_src) or na(fv_band)      ? below_fv_cond[1]       : fill_color_src < fv_band
std_above_band_cond := na(fill_color_src) or na(sell_band)    ? std_above_band_cond[1] : fill_color_src >= sell_band
std_below_band_cond := na(fill_color_src) or na(buy_band)     ? std_below_band_cond[1] : fill_color_src <= buy_band
bb_above_band_cond  := na(fill_color_src) or na(bb_sell_band) ? bb_above_band_cond[1]  : fill_color_src >= bb_sell_band
bb_below_band_cond  := na(fill_color_src) or na(bb_buy_band)  ? bb_below_band_cond[1]  : fill_color_src <= bb_buy_band
kc_above_band_cond  := na(fill_color_src) or na(kc_sell_band) ? kc_above_band_cond[1]  : fill_color_src >= kc_sell_band
kc_below_band_cond  := na(fill_color_src) or na(kc_buy_band)  ? kc_below_band_cond[1]  : fill_color_src <= kc_buy_band

fill(draw_sell_band, draw_buy_band,
 color= std_below_band_cond ? _USER_SELECT_STD_BAND_FILL_BELOW_BAND_COLOR : 
   std_above_band_cond ? _USER_SELECT_STD_BAND_FILL_ABOVE_BAND_COLOR : 
   below_fv_cond ? _USER_SELECT_STD_BAND_FILL_BELOW_FV_COLOR: _USER_SELECT_STD_BAND_FILL_ABOVE_FV_COLOR,
 title='Std Band Fill', editable=true)

fill(draw_bb_sell_band, draw_bb_buy_band,
 color= bb_below_band_cond ? _USER_SELECT_BB_BAND_FILL_BELOW_BAND_COLOR : 
   bb_above_band_cond ? _USER_SELECT_BB_BAND_FILL_ABOVE_BAND_COLOR : 
   below_fv_cond ? _USER_SELECT_BB_BAND_FILL_BELOW_FV_COLOR: _USER_SELECT_BB_BAND_FILL_ABOVE_FV_COLOR,
 title='Bollinger Band Fill', editable=true)

fill(draw_kc_sell_band, draw_kc_buy_band,
 color= kc_below_band_cond ? _USER_SELECT_KC_BAND_FILL_BELOW_BAND_COLOR : 
   kc_above_band_cond ? _USER_SELECT_KC_BAND_FILL_ABOVE_BAND_COLOR : 
   below_fv_cond ? _USER_SELECT_KC_BAND_FILL_BELOW_FV_COLOR: _USER_SELECT_KC_BAND_FILL_ABOVE_FV_COLOR,
 title='Keltner Channel Fill', editable=true)
