Ripple (XRP) Model Price
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © BobRivera990

//@version=5
indicator("Ripple(XRP) Price Model", overlay = true)

// ——————————[Constants and inputs]———————————————————————————————————————————————————————————————————————————————————————————— {

// ————— Constants used in calculations
// Quandl's Total Bitcoins symbol
var string  TOTBC                   = "QUANDL:BCHAIN/TOTBC"

// Residual Standard Deviation
var float   RESIDUAL_STDEV          = 0.82188

// Bitcoins mined by Satoshi Nakamoto in 2009
var int     SATOSHI_MINED_COINS     = 1000000 

// Preston Pysh estimates that each cycle of bitcoin is about 463 time span.
var int PYSH_LEN = 463 

// Colors used in plots
var color   C_CUR_MODEL         = color.rgb(0,      0,      0,      0)
var color   C_CUR_BAND          = color.rgb(0,      0,      0,      60)
var color   C_CUR_OVER_BOUGHT   = color.rgb(255,    0,      0,      95)
var color   C_CUR_OVER_SOLD     = color.rgb(0,      255,    0,      95)

var color   C_FUT_MODEL         = color.rgb(0,      0,      0,      75)
var color   C_FUT_BAND          = color.rgb(0,      0,      0,      80)
var color   C_FUT_OVER_BOUGHT   = color.rgb(200,    120,    120,    95)
var color   C_FUT_OVER_SOLD     = color.rgb(120,    200,    120,    95)

// } ————————[Constants and inputs]

// ——————————[functions]——————————————————————————————————————————————————————————————————————————————————————————————————————— {

// Calculate the duration of each frame in seconds
f_resInSeconds() => 
      timeframe.multiplier  *   (
      timeframe.isseconds   ?       1                           :
      timeframe.isminutes   ?       60                          :
      timeframe.isdaily     ?       60 * 60 * 24                :
      timeframe.isweekly    ?       60 * 60 * 24 * 7            :
      timeframe.ismonthly   ?   int(60 * 60 * 24 * 30.4375)     : na)

// Calculate the average number of coins mined per day
f_getDailyFlow() =>
    float _dailyFlow = ta.sma(close - close[1], PYSH_LEN)
    _dailyFlow

// Calculate the bitcoin's stock-to-flow ratio 
f_getBsf() =>
    float _flow   = f_getDailyFlow()  * 365
    float _bsf    = (close - SATOSHI_MINED_COINS) / _flow
    _bsf

// Calculate price bands
f_getBands(_bsf) =>
    // The basic Equation
    float _base         = 3.3297 * math.log(_bsf) - 12.214
    // The basic final Equation
    float _modelPrc     = math.exp(_base)
    // The Price band Equation
    float _upper1st     = math.exp(_base + RESIDUAL_STDEV)
    float _lower1st     = math.exp(_base - RESIDUAL_STDEV)
    float _upper2nd     = math.exp(_base + RESIDUAL_STDEV * 2)
    float _lower2nd     = math.exp(_base - RESIDUAL_STDEV * 2)
    
    [_modelPrc, _upper1st, _lower1st, _upper2nd, _lower2nd]
    
// Calculate number of bitcoin halvings based on block-height
f_blockToHalvNum(_blockHgt) =>
    int _halvNum = math.floor(_blockHgt / 210000)
    _halvNum
    
// Calculate the circulating supply of bitcoin based on the block-height
f_blockToCirSuply(_blockHgt, _halvNum) =>
    int _cirSuply = math.floor(21000000 + (50 * _blockHgt - 10500000 * _halvNum - 21000000) * math.pow(0.5, _halvNum))
    _cirSuply

// Calculate number of bitcoin halvings based on the circulating supply
f_cirSuplyToHalvNum(_cirSuply) =>
    int _halvNum = math.floor(-math.log((21000000 - _cirSuply) / 21000000) / math.log(2))
    _halvNum
    
// Calculate the block-height of bitcoin based on the circulating supply
f_cirSuplyToBlock(_cirSuply, _halvNum) =>
    int _blockHgt = math.floor(210000 * _halvNum + (math.pow(2, _halvNum) / 50) * (_cirSuply - 21000000 * (1 - math.pow(0.5, _halvNum))))
    _blockHgt

// Calculate the stock-to-flow ratio of bitcoin in the future
f_getFutureBsf(_offset, _curCirSuply) =>
    int     _offsetInDay        = _offset * f_resInSeconds() / 86400
    int     _curHalvNum         = f_cirSuplyToHalvNum(_curCirSuply)
    int     _curBlockHeight     = f_cirSuplyToBlock(_curCirSuply, _curHalvNum)
    float   _avgDailyBlock      = 144.4
    int     _futBlockHeight     = math.floor(_curBlockHeight + _avgDailyBlock * _offsetInDay)
    int     _futHalvNum         = f_blockToHalvNum(_futBlockHeight)
    int     _futCirSuply        = f_blockToCirSuply(_futBlockHeight, _futHalvNum)
    int     _dayFromHalv        = math.floor((_futBlockHeight % 210000) / _avgDailyBlock)
    float   _futDailyCoin       = _avgDailyBlock * 50 * math.pow(0.5, _futHalvNum)
    float   _futDailyFlow       = (math.min(_dayFromHalv, PYSH_LEN) * _futDailyCoin + math.max(PYSH_LEN - _dayFromHalv, 0) * 2 * _futDailyCoin) / PYSH_LEN
    float   _futFlow            = 365 * _futDailyFlow
    float   _futBsf             = (_futCirSuply - SATOSHI_MINED_COINS) / _futFlow 

    _futBsf
    
// } ————————[functions]

// ——————————[calculations]———————————————————————————————————————————————————————————————————————————————————————————————————— {

// Calculate the bitcoin's stock-to-flow ratio 
float   curBsf      = request.security(TOTBC,   "D",    f_getBsf())
// Calculate XRP price bands
[modelPrc, upper1st, lower1st, upper2nd, lower2nd] = f_getBands(curBsf)

// Calculate the circulating supply of bitcoin
int     btcCirSuply = request.security(TOTBC,   "D",    math.floor(close))

// Estimate the total number of bars
var int TOTAL_BAR   = (timenow - time) / (f_resInSeconds() * 1000)

var int OFSSET      = math.min(TOTAL_BAR, 1500)

// Estimate the bitcoin stock-to-flow ratio in the future
float   futureBsf   = f_getFutureBsf(OFSSET, btcCirSuply)
// Estimate XRP price bands in the future
[futureModelPrc, futureUpper1st, futureLower1st, futureUpper2nd, futureLower2nd] = f_getBands(futureBsf)

bool cndPlotFuturePrc = timenow < time + (OFSSET - 20) * f_resInSeconds() * 1000

// } ————————[calculations]

// ——————————[Plots]——————————————————————————————————————————————————————————————————————————————————————————————————————————— {

// plot XRP price model
plot(modelPrc, title = "Model Price", color = C_CUR_MODEL, linewidth = 2)

// plot XRP price bands
p_curUpper1st  = plot(upper1st, title ='First Upper Band',     color = C_CUR_BAND, linewidth = 1)
p_curLower1st  = plot(lower1st, title ='First Lower Band',     color = C_CUR_BAND, linewidth = 1)
p_curUpper2nd  = plot(upper2nd, title ='Second Upper Band',    color = C_CUR_BAND, linewidth = 1)
p_curLower2nd  = plot(lower2nd, title ='Second Lower Band',    color = C_CUR_BAND, linewidth = 1)

fill(p_curLower2nd,    p_curLower1st,     color = C_CUR_OVER_SOLD  ,  title = "OverSold Zone")
fill(p_curUpper1st,    p_curUpper2nd,     color = C_CUR_OVER_BOUGHT,  title = "OverBought Zone")

// plot estimated XRP price pattern in the future
plot(cndPlotFuturePrc ? futureModelPrc : na, title = "Model Price (Future)", color = C_FUT_MODEL, linewidth = 2, offset = OFSSET)

// plot estimated XRP price bands in the future
p_futUpper1st  = plot(cndPlotFuturePrc ? futureUpper1st : na, title = 'First Upper Band (Future)',   color = C_FUT_BAND, linewidth = 1, offset = OFSSET)
p_futLower1st  = plot(cndPlotFuturePrc ? futureLower1st : na, title = 'First Lower Band (Future)',   color = C_FUT_BAND, linewidth = 1, offset = OFSSET)
p_futUpper2nd  = plot(cndPlotFuturePrc ? futureUpper2nd : na, title = 'Second Upper Band (Future)',  color = C_FUT_BAND, linewidth = 1, offset = OFSSET)
p_futLower2nd  = plot(cndPlotFuturePrc ? futureLower2nd : na, title = 'Second Lower Band (Future)',  color = C_FUT_BAND, linewidth = 1, offset = OFSSET)

fill(p_futLower2nd,    p_futLower1st,     color = C_FUT_OVER_SOLD  ,  title = "OverSold Zone (Future)")
fill(p_futUpper1st,    p_futUpper2nd,     color = C_FUT_OVER_BOUGHT,  title = "OverBought Zone (Future)")

// } ————————[Plots]