Glassnode BTC SOPR Toolkit
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © JBroadway

// NOTE: Due to data source, data can only be displayed on a 1 day chart.

// SOPR stand for Spent Output Profit Ratio. It’s a Bitcoin on-chain metric that
// shows whether Bitcoin spent outputs (UTXO’s) are being realized in profit or 
// loss. It’s calculated by dividing realized value by the value at creation 
// (price sold / price paid).

// SOPR servers as a great short/mid-term indicator.

//@version=5
indicator("Glassnode BTC SOPR Toolkit", shorttitle="BTC SOPR Toolkit", timeframe="D")

////////////////////////////////////////////////////////////////////////////////
//FUNCTIONS
////////////////////////////////////////////////////////////////////////////////

//On-chain volume metric
btc_vol = request.security("GLASSNODE:BTC_TOTALVOLUME", "D", close)

vwma(src, length, vol_src) =>
    out = ta.sma(src * vol_src, length) / ta.sma(vol_src, length)
    out

vw_ema(src, length, vol_src) =>
    out = ta.ema(src * vol_src, length) / ta.ema(vol_src, length)
    out

////////////////////////////////////////////////////////////////////////////////
//INIT VARS
////////////////////////////////////////////////////////////////////////////////

//SOPR
//------------------------------------------------------------------------------
sopr = request.security("GLASSNODE:BTC_SOPR", timeframe="D", expression=close)
sopr_adjusted = (sopr - 1) * 60
show_sopr = input.bool(false, title="SOPR", group="Tools")

//SOPR Smoothing Vars
//------------------------------------------------------------------------------
show_sopr_smoothing = input.bool(true, title="SOPR Smoothed", group="Tools")
ma_length = input(10, "MA Length", tooltip="Moving Average Length", group="SOPR Smoothing")
ma_type = input.string("EMA", title="MA Type", tooltip="Moving Average Type", options=["SMA", "EMA", "VW-SMA", "VW-EMA"], group="SOPR Smoothing")

//SOPR Spikes Vars
//------------------------------------------------------------------------------
show_sopr_spikes = input.bool(false, title="SOPR Spikes", group="Tools")
bb_length = input(20, title="Length", group="SOPR Spikes")
bb_std_dev = input(2 , "StdDev", tooltip="Standard Deviation", group="SOPR Spikes")

//MISC Settings
//------------------------------------------------------------------------------
show_range = input.bool(false, title="Show Bollinger Bands", group="MISC Settings")


////////////////////////////////////////////////////////////////////////////////
//SOPR SMOOTHING
////////////////////////////////////////////////////////////////////////////////

//Arrays
//------------------------------------------------------------------------------
var pos_array = array.new_float(2)
var neg_array = array.new_float(2)

array.shift(pos_array)
array.shift(neg_array)

//Separate SOPR into distinced array based on if SOPR is > or < 1
     //Value of 1 will be added to opposing array if SOPR positive/negative
     //For example if SOPR is 1.10, 1.10 will be added to pos_array, 1 is added to neg_array
     //IF SOPR is 0.8, 0.8 is added to neg_array, 1 is added to pos_array
if sopr > 1
    array.push(pos_array, sopr)
    array.push(neg_array, 1)
else
    array.push(pos_array, 1)
    array.push(neg_array, sopr)

//Convert data into relative values (easier to manage)
positive = (array.get(pos_array, 1) - 1) * 100
negative = (1 - array.get(neg_array, 1)) * 100

//Moving average for positive array
sopr_pos_ma = switch ma_type
    "SMA"   => ta.sma(positive, ma_length)
    "EMA"   => ta.ema(positive, ma_length)
    "VW-SMA"  => vwma(positive, ma_length, btc_vol)
    "VW-EMA"  => vw_ema(positive, ma_length, btc_vol)

//Moving average for negative array
sopr_neg_ma = switch ma_type
    "SMA" => ta.sma(negative, ma_length)
    "EMA" => ta.ema(negative, ma_length)
    "VW-SMA"  => vwma(negative, ma_length, btc_vol)
    "VW-EMA"  => vw_ema(negative, ma_length, btc_vol)

//SOPR Smoothing Calculation
sopr_smoothing = sopr_pos_ma - sopr_neg_ma

////////////////////////////////////////////////////////////////////////////////
//SOPR SPIKES
////////////////////////////////////////////////////////////////////////////////

//Bollinger Bands
[middle, upper, lower] = ta.bb(sopr_adjusted, bb_length, bb_std_dev)

//SOPR spikes Calculation
sopr_spikes = if sopr_adjusted > upper or sopr_adjusted < lower
    sopr_adjusted

// plot(lower)
// plot(upper, color=color.red)

////////////////////////////////////////////////////////////////////////////////
//PLOTTING
////////////////////////////////////////////////////////////////////////////////

//SOPR
//------------------------------------------------------------------------------
plot(sopr_adjusted, color=show_sopr ? color.white : na, title="SOPR")

//SOPR Smoothing
//------------------------------------------------------------------------------
//Colors
sm_c = if show_sopr_smoothing == true
    sopr_smoothing > 0 ? color.new(color.green, 0) : color.red
else
    na

//Fill Colors
sm_f = if show_sopr_smoothing == true
    sopr_smoothing > 0 ? color.new(color.green, 90) : color.new(color.red, 90)
else
    na

//Smoothing Plot
sopr_p = plot(sopr_smoothing, title="SOPR Smoothing", color=sm_c)

//SOPR Spikes
//------------------------------------------------------------------------------
//Colors
spikes_c = if sopr_adjusted > 0
    color.green
else
    color.red

//Spikes Plot
plot(sopr_spikes, title="SOPR Spikes", color=show_sopr_spikes ? spikes_c : na, style=plot.style_histogram, linewidth=5)

//SOPR Range
//------------------------------------------------------------------------------
plot(upper, title="Upper Bollinger Band", color=show_range ? color.yellow : na, style=plot.style_cross)
plot(lower, title="Lower Bollinger Band", color=show_range ? color.yellow : na, style=plot.style_cross)

//Misc Plotting
//------------------------------------------------------------------------------
hline(0, editable=false)
zero = plot(0, color=na, display=display.none)

//Fill Colors
//------------------------------------------------------------------------------
fill(sopr_p, zero, color=sm_f)

////////////////////////////////////////////////////////////////////////////////
//DEBUG
////////////////////////////////////////////////////////////////////////////////

// plot(positive, color=color.green)
// plot(sopr_pos_ma, color=color.green)

// plot(neagtive, color=color.green)
// plot(sopr_neg_ma, color=color.red)
