Open interest flow / 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("Open interest flow / quantifytools")

//                      Inputs

//OI inputs

groupSource = "Open interest source"

i_oiSymbol = input.symbol("BTCUSDT.P_OI", "Open interest source", group=groupSource, inline="src")
i_autoOiCrypto = input.bool(false, "Auto-select source (crypto)", tooltip="Automatically chooses OI source for CRYPTO/USDT pairs when enabled. OI is fetched from Binance.")

groupSettings = "Open interest settings"

i_xtrSensitivity = input.int(3, "Flow burst sensitivity", minval=1, maxval=5, tooltip="Controls criteria set for bursts. 1 = OI value at 0.80 + 10% increase = OI burst. 5 = OI value at 1.2 + 50% increase = OI burst. 1 step in sensitivity = 0.10 increase in OI value/10% increase in required change.", group=groupSettings)
i_rBasisLength = input.int(10, "Length for relative OI/price calculations", tooltip="Controls length of moving average used in relative OI/price calculations. Greater length = more lag, less noise. Shorter length = less lag, more noise. Recommended to keep at default value (10).", group=groupSettings)

//Visual inputs

groupCol = "Visuals"

i_candleColor = input.string("None", "Colorize candles", options=["None", "Flows", "Extremes"], group=groupCol, tooltip="Colorizes candles based on either all flow states occuring or flow states during bursts")
i_flowInfoBool = input.bool(false, "Flow guidelines", group=groupCol, tooltip="Enables guidelines/reminders for OI flow interpretation")
i_staticColor = input.bool(false, "Static color", group=groupCol, tooltip="Enables static color instead of gradient. Better visibility for chart color.")
i_showPriceLine = input.bool(false, "Show price line", group=groupCol, tooltip="Enables relative price line alongside relative OI.")

//Color inputs
i_buyInflowCol = input.color(color.green, "Buy inflows", group=groupCol, inline="bin")
i_sellInflowCol = input.color(color.red, "Sell inflows", group=groupCol, inline="sin")
i_buyOutflowCol = input.color(color.teal, "Buy outflows", group=groupCol, inline="bout")
i_sellOutflowCol = input.color(color.maroon, "Sell outflows", group=groupCol, inline="sout")

i_buyInflowColExt = input.color(color.rgb(7, 255, 230) , "Extreme", group=groupCol, inline="bin")
i_sellInflowColExt = input.color(color.rgb(255, 125, 12), "Extreme", group=groupCol, inline="sin")
i_buyOutflowColExt = input.color(color.green, "Extreme", group=groupCol, inline="bout")
i_sellOutflowColExt = input.color(color.red, "Extreme", group=groupCol, inline="sout")

i_upperLine = input.color(color.new(color.gray, 90), "Upper band", group=groupCol, inline="bandu")
i_upperLine2 = input.color(color.new(color.red, 90), "Extreme", group=groupCol, inline="bandu")
i_lowerLine2 = input.color(color.new(color.red, 90), "Lower band", group=groupCol, inline="bandd")
i_lowerLine = input.color(color.new(color.gray, 90), "Extreme", group=groupCol, inline="bandd")


//                      Relative OI

//Relative price (OI/price relative to its moving average)
sma = ta.sma(close, i_rBasisLength)
smaSpread = ((close / sma) - 1) * 100

//Requested OI symbol
reqOiSymbol = i_autoOiCrypto and str.contains(syminfo.ticker, "USDT.P") ? "BINANCE:" + syminfo.ticker + "_OI" : 
 i_autoOiCrypto and str.contains(syminfo.ticker, "USDT.P") == false and str.contains(syminfo.ticker, "USDT") ? "BINANCE:" + syminfo.ticker + ".P_OI": i_oiSymbol

//Fetching relative OI
oiSpread = request.security(reqOiSymbol, timeframe.period, smaSpread)

//Function for forming relative values (used for price and OI)
relativeValue(source) =>

    //Initializing arrays
    var pivotHighs = array.new_float()
    var pivotLows = array.new_float()

    //Array size limit
    sizeLimit = 20

    //Keeping array sizes fixed
    if array.size(pivotHighs) > sizeLimit
        array.remove(pivotHighs, 0)

    if array.size(pivotLows) > sizeLimit
        array.remove(pivotLows, 0)

    //Defining pivot. Consider pivot valid only when spread is above/below 0.
    ph = source > 0 ? ta.pivothigh(source, 5, 5) : na
    pl = source < 0 ? ta.pivotlow(source, 5, 5) : na

    //Populating arrays
    if ph
        array.push(pivotHighs, ph)

    if pl
        array.push(pivotLows, pl)

    //Forming average values from arrays
    avgPh = array.avg(pivotHighs)
    avgPl = array.avg(pivotLows)

    [avgPh, avgPl]

//Average OI spread turning points
[smaPhOi, smaPlOi] = relativeValue(oiSpread)

//Calculating relative OI
relativeOi = oiSpread > 0 ? oiSpread / smaPhOi : 0 - (oiSpread / smaPlOi)


//                      Relative price

//Average price spread turning points
[smaPhPrice, smaPlPrice] = relativeValue(smaSpread)

//Calculating relative price
relativePrice = smaSpread > 0 ? smaSpread / smaPhPrice : 0 - (smaSpread / smaPlPrice)

//OI flow burst criteria, gain and position
oiXtremeGain = i_xtrSensitivity == 1 ? 1.1 : i_xtrSensitivity == 2 ? 1.2 : i_xtrSensitivity == 3 ? 1.3 : 
 i_xtrSensitivity == 4 ? 1.4 : 1.5

oiXtremePos = i_xtrSensitivity == 1 ? 0.80 : i_xtrSensitivity == 2 ? 0.9 : i_xtrSensitivity == 3 ? 1 : 
 i_xtrSensitivity == 4 ? 1.1 : 1.2

//OI burst up scenarios
oiBurstUp = relativeOi > relativeOi[1] * oiXtremeGain and relativeOi > oiXtremePos
oiBurstDown = relativeOi < relativeOi[1] * oiXtremeGain and relativeOi < 0 - oiXtremePos


//                      Plots

//Inflow/outflow colors. Gradient by default, static if selected
bullInflowCol = i_staticColor ? i_buyInflowCol : color.from_gradient(relativeOi, 0, 1, color.new(i_buyInflowCol, 70), color.new(i_buyInflowCol, 1))
bullOutflowCol = i_staticColor ? i_buyOutflowCol : color.from_gradient(math.abs(relativeOi), 0, 1, color.new(i_buyOutflowCol, 70), color.new(i_buyOutflowCol, 1))

bearInflowCol = i_staticColor ? i_sellInflowCol : color.from_gradient(math.abs(relativeOi), 0, 1, color.new(i_sellInflowCol, 70), color.new(i_sellInflowCol, 1))
bearOutflowCol = i_staticColor ? i_sellOutflowCol : color.from_gradient(math.abs(relativeOi), 0, 1, color.new(i_sellOutflowCol, 70), color.new(i_sellOutflowCol, 1))

//Choose appropriate color corresponding to a OI/price scenario
oiFlowCol = relativePrice > 0 and relativeOi < 0 ? bearOutflowCol : relativePrice > 0 and relativeOi > 0 ? bullInflowCol : 
 relativePrice < 0 and relativeOi < 0 ? bullOutflowCol : bearInflowCol //relativePrice < 0 and relativeOi > 0 ?  : color.white

//Plot extremes
pUpper1 = plot(1, "+1", color=color.new(color.gray, 100), display=display.data_window)
pLower1 = plot(-1, "-1", color=color.new(color.gray, 100), display=display.data_window)

pUpper2 = plot(2, "+2", color=color.new(color.gray, 100), display=display.data_window)
pLower2 = plot(-2, "-2", color=color.new(color.gray, 100), display=display.data_window)

pUpper3 = plot(3, "+3", color=color.new(color.gray, 100), display=display.data_window)
pLower3 = plot(-3, "-3", color=color.new(color.gray, 100), display=display.data_window)

//Fill extremes
fill(pUpper1, pUpper2, title = "+1 to +2 fill", color=i_upperLine)
fill(pLower1, pLower2, title = "-1 to -2 fill", color=i_lowerLine)

fill(pUpper2, pUpper3, title = "+2 to +3 fill", color=i_upperLine2)
fill(pLower2, pLower3, title = "-3 to -3 fill", color=i_lowerLine2)

//OI burst color
oiBurstCol = oiFlowCol == bearOutflowCol ? i_sellOutflowColExt : oiFlowCol == bearInflowCol ? i_sellInflowColExt :
 oiFlowCol == bullInflowCol ? i_buyInflowColExt : i_buyOutflowColExt

//Plot shapes for OI bursts
plotshape(oiBurstUp ? relativeOi : na, style=shape.circle, text="", color=oiBurstCol, location=location.absolute, size=size.tiny)
plotshape(oiBurstDown ? relativeOi : na, style=shape.circle, text="", color=oiBurstCol, location=location.absolute, size=size.tiny)

//Candle color for either all flow states or only during extremes
barcolor(i_candleColor == "Flows" ? oiFlowCol : i_candleColor == "Extremes" and (oiBurstUp or oiBurstDown) ? oiFlowCol : na)

//Plot relative OI and relative price if enabled
plot(relativeOi, "Relative OI", color=oiFlowCol, style=plot.style_columns)
plot(i_showPriceLine ? relativePrice : na, "Relative price", color=color.white)


//                      Alerts

//Conditions for abnormal flows
abnormalBuyInflow = relativeOi > 1 and oiFlowCol == bullInflowCol 
abnormalSellInflow = relativeOi > 1 and oiFlowCol == bearInflowCol 

abnormalBuyOutflow = relativeOi < -1 and oiFlowCol == bullOutflowCol 
abnormalSellOutflow = relativeOi < -1 and oiFlowCol == bearOutflowCol

//Conditions for bursts
abnormalBuyBurstUp = oiBurstUp and oiBurstCol == i_buyInflowColExt
abnormalSellBurstUp = oiBurstUp and oiBurstCol == i_sellInflowColExt 

abnormalBuyBurstDown = oiBurstDown and oiBurstCol == i_buyOutflowColExt
abnormalSellBurstDown = oiBurstDown and oiBurstCol == i_sellOutflowColExt

//Grouped conditions
anyAbnormalInflows = abnormalBuyInflow or abnormalSellInflow
anyAbnormalOutflows = abnormalBuyOutflow or abnormalSellOutflow

anyAbnormalBurstUp = abnormalBuyBurstUp or abnormalSellBurstUp
anyAbnormalBurstDown = abnormalBuyBurstDown or abnormalSellBurstDown

//Alert conditions
alertcondition(abnormalBuyInflow and barstate.isconfirmed, "Abnormal long inflows", "Abnormal long inflows detected.")
alertcondition(abnormalSellInflow and barstate.isconfirmed, "Abnormal short inflows", "Abnormal short inflows detected.")

alertcondition(abnormalBuyOutflow and barstate.isconfirmed, "Abnormal long outflows", "Abnormal long outflows detected.")
alertcondition(abnormalSellOutflow and barstate.isconfirmed, "Abnormal short outflows", "Abnormal short outflows detected.")

alertcondition(abnormalBuyBurstUp and barstate.isconfirmed, "Aggressive longs", "Aggressive long inflows detected.")
alertcondition(abnormalSellBurstUp and barstate.isconfirmed, "Aggressive shorts", "Aggressive short inflows detected.")

alertcondition(abnormalBuyBurstDown and barstate.isconfirmed, "Liquidated longs", "Liquidated long outflows detected.")
alertcondition(abnormalSellBurstDown and barstate.isconfirmed, "Liquidated shorts", "Liquidated short outflows detected.")

alertcondition(anyAbnormalInflows and barstate.isconfirmed, "Abnormal long/short inflows", "Abnormal long/short inflows detected.")
alertcondition(anyAbnormalOutflows and barstate.isconfirmed, "Abnormal long/short outflows", "Abnormal long/short outflows detected.")

alertcondition(anyAbnormalBurstUp and barstate.isconfirmed, "Aggressive longs/shorts", "Aggressive long/short inflows detected.")
alertcondition(anyAbnormalBurstDown and barstate.isconfirmed, "Liquidated longs/shorts", "Liquidated long/short outflows detected.")


//                      Table


//Initializing table
var flowTable = table.new(position = position.top_right, columns = 50, rows = 50, bgcolor = color.new(color.black, 100), border_width = 3, border_color=color.new(color.white, 100))

//Populate flow guidelines if user has enabled setting
if i_flowInfoBool
    table.cell(table_id = flowTable, column = 0, row = 0, text = "● Buy OI inflow", text_color=i_buyInflowCol, text_halign = text.align_left, text_size=size.small)
    table.cell(table_id = flowTable, column = 1, row = 0, text = "● Buy OI outflow", text_color=i_buyOutflowCol, text_halign = text.align_left, text_size=size.small)

    table.cell(table_id = flowTable, column = 2, row = 0, text = "● Sell OI inflow", text_color=i_sellInflowCol, text_halign = text.align_left, text_size=size.small)
    table.cell(table_id = flowTable, column = 3, row = 0, text = "● Sell OI outflow", text_color=i_sellOutflowCol, text_halign = text.align_left, text_size=size.small)

//Populate OI source
table.cell(table_id = flowTable, column = 4, row = 0, text = "OI: " + str.tostring(reqOiSymbol), text_color=color.gray, text_halign = text.align_left, text_size=size.small)




