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

//@version=5
indicator("Bar metrics", overlay=true)


//                      Inputs


//Lookback inputs
groupLook = "General"

i_smoothingType = input.string("SMA", "Moving average", options=["SMA", "EMA", "HMA", "RMA", "WMA", "Median"], inline="source", group=groupLook, tooltip="Define average for relative volume/volatility calculations")
i_length = input.int(20, "", inline="source", group=groupLook, minval=1)
i_lookback = input.int(0, "Lookback", minval=0, group=groupLook, tooltip="Shows bar metric data for a bar that is X amount of bars back")

//Basket inputs
groupSym = "Basket average"

i_basketSyms = input.int(0, "Symbols included", minval=0, maxval=10, group=groupSym, tooltip="Select symbols that are included in average bar metric calculations. Useful for comparing broader market/comparable symbol metrics to current chart metrics. Keep at 0 to disable.")

sym1 = input.symbol("BTCUSDT", "#1", group=groupSym, inline="12")
sym2 = input.symbol("ETHUSDT", "#2", group=groupSym, inline="12")
sym3 = input.symbol("BNBUSDT", "#3", group=groupSym, inline="34")
sym4 = input.symbol("XRPUSDT", "#4", group=groupSym, inline="34")
sym5 = input.symbol("ADAUSDT", "#5", group=groupSym, inline="56")

sym6 = input.symbol("SOLUSDT", "#6", group=groupSym, inline="56")
sym7 = input.symbol("DOTUSDT", "#7", group=groupSym, inline="78")
sym8 = input.symbol("DOGEUSDT", "#8", group=groupSym, inline="78")
sym9 = input.symbol("XMRUSDT", "#9", group=groupSym, inline="910")
sym10 = input.symbol("MATICUSDT", "#10", group=groupSym,inline="910")


//Bar highlighter inputs
groupBar = "Bar highlights"

i_barCol = input.color(color.fuchsia, "", inline="hlight", group=groupBar)
i_highlightBool = input.bool(false, "Enable highlights & alerts", inline="hlight", group=groupBar, tooltip="Enable to highlight bars that are within specified bar metric bracket values. Keep this setting on when setting an alert.")

i_rVolume1 = input.float(0, "Relative volume", minval=0, group=groupBar, inline="rvolume", step=0.1)
i_rVolume2 = input.float(2, " - ", minval=0, group=groupBar, inline="rvolume", step=0.1)
i_rVolumeBool = input.bool(true, "", inline="rvolume", group=groupBar)

i_rVol1 = input.float(0, "Relative volatility", minval=0, group=groupBar, inline="rvol", step=0.1)
i_rVol2 = input.float(2, " - ", minval=0, group=groupBar, inline="rvol", step=0.1)
i_rVolBool = input.bool(true, "", inline="rvol", group=groupBar)

i_close1 = input.float(0, "Relative close", minval=0, maxval=100, group=groupBar, inline="rclose", step=5)
i_close2 = input.float(100, " - ", minval=0, maxval=100, group=groupBar, inline="rclose", step=5)
i_closeBool = input.bool(true, "", inline="rclose", group=groupBar)


//Table/label inputs
groupLT = "Metrics"

i_labelBool = input.bool(true, "Enable label", group=groupLT)

i_labelSize = input.int(2, "Label size", maxval=5, minval=1, group=groupLT)
i_offset = input.int(1, "Label offset", maxval=50, minval=1, group=groupLT)

i_tableBool = input.bool(false, "Enable table", group=groupLT)
tableSize = input.int(3, "Table size", maxval=5, minval=1, group=groupLT)
tablePos = input.string("Top right", "Table position", options=["Top right", "Bottom right", "Bottom left", "Top left"], group=groupLT)


//Plot related inputs
groupVol = "Relative volume/volatility"

i_vol1 = input.color(color.white, "Neutral", inline="vol", group=groupVol)
i_vol2 = input.color(color.yellow, "Light", inline="vol", group=groupVol)
i_vol3 = input.color(color.orange, "Medium", inline="vol", group=groupVol)
i_vol4 = input.color(color.red, "Heavy", inline="vol", group=groupVol)

groupRange = "Relative close"

i_neutralRange = input.color(color.white, "Neutral", inline="range", group=groupRange)
i_lightBullRange = input.color(color.teal, "Light ▲", inline="range", group=groupRange)
i_heavyBullRange = input.color(color.lime, "Heavy ▲", inline="range", group=groupRange)
i_lightBearRange = input.color(color.maroon, "Light ▼", inline="range", group=groupRange)
i_heavyBearRange = input.color(color.red, "Heavy ▼", inline="range", group=groupRange)

groupText = "Text"

i_text = input.color(color.white, "Text color", group=groupText, inline="text")
i_textBool = input.bool(false, "Stealth", group=groupText, inline="text")


//                      rVolume, rVolatility and rClose metrics

//User defined source and smoothing
smoothedValue(source, length) =>
    i_smoothingType == "SMA" ? ta.sma(source, length) : i_smoothingType == "EMA" ? ta.ema(source, length) :
     i_smoothingType == "HMA" ? ta.hma(source, length) : i_smoothingType == "RMA" ? ta.rma(source, length) : 
      i_smoothingType == "Median" ? ta.median(source, length) : ta.wma(source, length)


//Volume multiplier
volumeMa = smoothedValue(volume, i_length) 
volumeMult = volume / volumeMa
volumeMultRounded = math.round(volume / volumeMa, 2)

//Volatility multiplier
volatility = high - low
volMa = smoothedValue(volatility, i_length) 
volMult = volatility / volMa
volMultRounded = math.round(volatility / volMa, 2)

//Relative close
rangePos = (close - low) / (high - low)
rangePerc = math.round(rangePos * 100, 0)


//                      Bar highlighter

//Volatility within bracket values condition
volatilityCond = i_rVolBool ? volMult >= i_rVol1 and volMult <= i_rVol2 : na(close) == false

//Volume within bracket values condition
volumeCond = i_rVolumeBool ? volumeMult >= i_rVolume1 and volumeMult <= i_rVolume2 : na(close) == false

//Close within bracket values condition
closeCond = i_closeBool ? rangePerc >= i_close1 and rangePerc <= i_close2 : na(close) == false

//All conditions combined
allConds = volatilityCond and volumeCond and closeCond


//                      Basket average metrics

//Function for fetching relative volume/volatility/close
basketVal() =>

    [volumeMultRounded, volMultRounded, rangePerc]

//Fetching bar metrics from chosen symbols
[volumeMult1, volMult1, rangePerc1] = request.security(sym1, timeframe.period, basketVal())
[volumeMult2, volMult2, rangePerc2] = request.security(sym2, timeframe.period, basketVal())
[volumeMult3, volMult3, rangePerc3] = request.security(sym3, timeframe.period, basketVal())
[volumeMult4, volMult4, rangePerc4] = request.security(sym4, timeframe.period, basketVal())
[volumeMult5, volMult5, rangePerc5] = request.security(sym5, timeframe.period, basketVal())
[volumeMult6, volMult6, rangePerc6] = request.security(sym6, timeframe.period, basketVal())
[volumeMult7, volMult7, rangePerc7] = request.security(sym7, timeframe.period, basketVal())
[volumeMult8, volMult8, rangePerc8] = request.security(sym8, timeframe.period, basketVal())
[volumeMult9, volMult9, rangePerc9] = request.security(sym9, timeframe.period, basketVal())
[volumeMult10, volMult10, rangePerc10] = request.security(sym10, timeframe.period, basketVal())

//Forming average relative volume
basketVolumeMult = i_basketSyms > 0 ? i_basketSyms == 1 ? volumeMult1 : i_basketSyms == 2 ? (volumeMult1 + volumeMult2) / i_basketSyms : 
 i_basketSyms == 3 ? (volumeMult1 + volumeMult2 + volumeMult3) / i_basketSyms : i_basketSyms == 4 ? (volumeMult1 + volumeMult2 + volumeMult3 + volumeMult4) / i_basketSyms : 
  i_basketSyms == 5 ? (volumeMult1 + volumeMult2 + volumeMult3 + volumeMult4 + volumeMult5) / i_basketSyms : i_basketSyms == 6 ? (volumeMult1 + volumeMult2 + volumeMult3 + volumeMult4 + volumeMult5 + volumeMult6) / i_basketSyms :
   i_basketSyms == 7 ? (volumeMult1 + volumeMult2 + volumeMult3 + volumeMult4 + volumeMult5 + volumeMult6 + volumeMult7) / i_basketSyms : i_basketSyms == 8 ? (volumeMult1 + volumeMult2 + volumeMult3 + volumeMult4 + volumeMult5 + volumeMult6 + volumeMult7 + volumeMult8) / i_basketSyms :
  i_basketSyms == 9 ? (volumeMult1 + volumeMult2 + volumeMult3 + volumeMult4 + volumeMult5 + volumeMult6 + volumeMult7 + volumeMult8 + volumeMult9) / i_basketSyms : i_basketSyms == 10 ? (volumeMult1 + volumeMult2 + volumeMult3 + volumeMult4 + volumeMult5 + volumeMult6 + volumeMult7 + volumeMult8 + volumeMult9 + volumeMult10) / i_basketSyms : na : na

//Forming average relative volatility
basketVolMult = i_basketSyms > 0 ? i_basketSyms == 1 ? volMult1 : i_basketSyms == 2 ? (volMult1 + volMult2) / i_basketSyms : 
 i_basketSyms == 3 ? (volMult1 + volMult2 + volMult3) / i_basketSyms : i_basketSyms == 4 ? (volMult1 + volMult2 + volMult3 + volMult4) / i_basketSyms : 
  i_basketSyms == 5 ? (volMult1 + volMult2 + volMult3 + volMult4 + volMult5) / i_basketSyms : i_basketSyms == 6 ? (volMult1 + volMult2 + volMult3 + volMult4 + volMult5 + volMult6) / i_basketSyms :
   i_basketSyms == 7 ? (volMult1 + volMult2 + volMult3 + volMult4 + volMult5 + volMult6 + volMult7) / i_basketSyms : i_basketSyms == 8 ? (volMult1 + volMult2 + volMult3 + volMult4 + volMult5 + volMult6 + volMult7 + volMult8) / i_basketSyms :
  i_basketSyms == 9 ? (volMult1 + volMult2 + volMult3 + volMult4 + volMult5 + volMult6 + volMult7 + volMult8 + volMult9) / i_basketSyms : i_basketSyms == 10 ? (volMult1 + volMult2 + volMult3 + volMult4 + volMult5 + volMult6 + volMult7 + volMult8 + volMult9 + volMult10) / i_basketSyms : na : na

//Forming average relative close
basketRange = i_basketSyms > 0 ? i_basketSyms == 1 ? rangePerc1 : i_basketSyms == 2 ? (rangePerc1 + rangePerc2) / i_basketSyms : 
 i_basketSyms == 3 ? (rangePerc1 + rangePerc2 + rangePerc3) / i_basketSyms : i_basketSyms == 4 ? (rangePerc1 + rangePerc2 + rangePerc3 + rangePerc4) / i_basketSyms : 
  i_basketSyms == 5 ? (rangePerc1 + rangePerc2 + rangePerc3 + rangePerc4 + rangePerc5) / i_basketSyms : i_basketSyms == 6 ? (rangePerc1 + rangePerc2 + rangePerc3 + rangePerc4 + rangePerc5 + rangePerc6) / i_basketSyms :
   i_basketSyms == 7 ? (rangePerc1 + rangePerc2 + rangePerc3 + rangePerc4 + rangePerc5 + rangePerc6 + rangePerc7) / i_basketSyms : i_basketSyms == 8 ? (rangePerc1 + rangePerc2 + rangePerc3 + rangePerc4 + rangePerc5 + rangePerc6 + rangePerc7 + rangePerc8) / i_basketSyms :
  i_basketSyms == 9 ? (rangePerc1 + rangePerc2 + rangePerc3 + rangePerc4 + rangePerc5 + rangePerc6 + rangePerc7 + rangePerc8 + rangePerc9) / i_basketSyms : i_basketSyms == 10 ? (rangePerc1 + rangePerc2 + rangePerc3 + rangePerc4 + rangePerc5 + rangePerc6 + rangePerc7 + rangePerc8 + rangePerc9 + rangePerc10) / i_basketSyms : na : na


//                      Plots

//Volatility, volume and relative close colors
volatilityCol = volMultRounded <= 1.00 ? color.from_gradient(volMultRounded, 0, 1.00, i_vol1, i_vol2) : volMultRounded >= 1.00 and volMultRounded <= 1.70 ? color.from_gradient(volMultRounded, 1.00, 1.70, i_vol2, i_vol3) : color.from_gradient(volMultRounded, 1.70, 2.4, i_vol3, i_vol4)

volumeCol = volumeMultRounded <= 1.00 ? color.from_gradient(volumeMultRounded, 0, 1.00, i_vol1, i_vol2) : volumeMultRounded >= 1.00 and volumeMultRounded <= 1.70 ? color.from_gradient(volumeMultRounded, 1.00, 1.70, i_vol2, i_vol3) : color.from_gradient(volumeMultRounded, 1.70, 2.4, i_vol3, i_vol4)

rangeCol = rangePos <= 0.25 ? color.from_gradient(rangePos, 0, 0.25, i_heavyBearRange, i_lightBearRange) : 
 rangePos >= 0.25 and rangePos <= 0.45 ? color.from_gradient(rangePos, 0.25, 0.45, i_lightBearRange, i_neutralRange) : 
  rangePos >= 0.55 and rangePos <= 0.75 ? color.from_gradient(rangePos, 0.55, 0.75, i_neutralRange, i_lightBullRange) :
   rangePos >= 0.75 and rangePos <= 1.00 ? color.from_gradient(rangePos, 0.75, 1.00, i_lightBullRange, i_heavyBullRange) : i_neutralRange

//Label elements
n = "\n"
dot1 = "    ●" + n + n
dot2 = "    ●"
dot3 = n + n + "    ●"
lookbackSymbol = i_lookback > 0 ? "↩︎ " : ""
vl = "｜"

//Basket texts
avgText = i_textBool ? "" : "Avg. "

basketRangeText = i_basketSyms > 0 ? vl + avgText + str.tostring(math.round(basketRange, 0)) + "%" : ""
basketVolumeText = i_basketSyms > 0 ? vl + avgText + str.tostring(math.round(basketVolumeMult, 2)) + "x" : ""
basketVolText = i_basketSyms > 0 ? vl + avgText + str.tostring(math.round(basketVolMult, 2)) + "x" : ""

//Metric texts
rangeText = lookbackSymbol + (i_textBool ? " C: " : "Close: ") + str.tostring(rangePerc) + "%" +  basketRangeText
volumeText = lookbackSymbol + (i_textBool ? " vE: " : "Volume: ") + str.tostring(volumeMultRounded) + "x" + basketVolumeText
volatilityText = lookbackSymbol + (i_textBool ? " vY: " : "Volatility: ") + str.tostring(volMultRounded) + "x" + basketVolText

//Metric text aggregated
labelText = "         " + volumeText + n + "—      " + rangeText + n + "         " + volatilityText

//Initializing labels
var label posLabel = na
var label posLabel2 = na
var label posLabel3 = na
var label posLabel4 = na

//Label size
labelSize = i_labelSize == 1 ? size.tiny : i_labelSize == 2 ? size.small : i_labelSize == 3 ? size.normal : i_labelSize == 4 ? size.large : size.huge

//Populating labels, keeping label only on real-time bar
if i_labelBool
    posLabel := label.new(x=bar_index+i_offset, y=close, xloc=xloc.bar_index, text=labelText[i_lookback], style=label.style_label_left, color=color.new(color.white, 100), textcolor=i_text, size=labelSize, textalign=text.align_left)
    label.delete(posLabel[1])

if i_labelBool
    posLabel2 := label.new(x=bar_index+i_offset, y=close, xloc=xloc.bar_index, text=dot1[i_lookback], style=label.style_label_left, color=color.new(color.white, 100), textcolor=volumeCol[i_lookback], size=labelSize, textalign=text.align_left)
    label.delete(posLabel2[1])

if i_labelBool
    posLabel3 := label.new(x=bar_index+i_offset, y=close, xloc=xloc.bar_index, text=dot2[i_lookback], style=label.style_label_left, color=color.new(color.white, 100), textcolor=rangeCol[i_lookback], size=labelSize, textalign=text.align_left)
    label.delete(posLabel3[1])

if i_labelBool
    posLabel4 := label.new(x=bar_index+i_offset, y=close, xloc=xloc.bar_index, text=dot3[i_lookback], style=label.style_label_left, color=color.new(color.white, 100), textcolor=volatilityCol[i_lookback], size=labelSize, textalign=text.align_left)
    label.delete(posLabel4[1])

//Bar highlighter color
barcolor(i_highlightBool and allConds ? i_barCol : na)

//Initializing box
var box highlightBox = na

//Highlighting lookback bar with a box.
if i_lookback > 0

    highlightBox := box.new(left=bar_index[i_lookback] - 1, top=high[i_lookback], right=bar_index[i_lookback] + 1, bottom=low[i_lookback], bgcolor=color.new(color.yellow, 80), border_color=color.new(color.white, 100))   
    box.delete(highlightBox[1])


//                      Table

//Table position and size
tablePosition = tablePos == "Top right" ? position.top_right : tablePos == "Bottom right" ? position.bottom_right : tablePos == "Bottom left" ? position.bottom_left : position.top_left
tableTextSize = tableSize == 1 ? size.tiny : tableSize == 2 ? size.small : tableSize == 3 ? size.normal : tableSize == 4 ? size.huge : size.large

//Initializing table
var metricTable = table.new(position = tablePosition, columns = 50, rows = 50, bgcolor = color.new(#191919, 100), border_width = 0, border_color=color.new(color.black, 100))

//Populating table
if i_tableBool

    table.cell(table_id = metricTable, column = 0, row = 0, text = "●", text_color=rangeCol, text_halign=text.align_right, text_size=tableTextSize)
    table.cell(table_id = metricTable, column = 0, row = 1, text = "●", text_color=volumeCol, text_halign=text.align_right, text_size=tableTextSize)
    table.cell(table_id = metricTable, column = 0, row = 2, text = "●", text_color=volatilityCol, text_halign=text.align_right, text_size=tableTextSize)

    table.cell(table_id = metricTable, column = 1, row = 0, text = rangeText[i_lookback], text_color=i_text, text_halign=text.align_left, text_size=tableTextSize)
    table.cell(table_id = metricTable, column = 1, row = 1, text = volumeText[i_lookback], text_color=i_text, text_halign=text.align_left, text_size=tableTextSize)
    table.cell(table_id = metricTable, column = 1, row = 2, text = volatilityText[i_lookback], text_color=i_text, text_halign=text.align_left, text_size=tableTextSize)


//                      Alerts

//Alert for specified bar metrics
alertcondition(allConds and (i_closeBool ? barstate.isconfirmed : na(close) == false), "Bar metric values within bracket range", "Bar metric values within bracket range detected.")
