footprint-iq-pro-tradingiq.pine
Copy
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TradingIQ
//@version=6
indicator("Footprint IQ Pro [TradingIQ]", overlay = true, max_boxes_count = 500, max_lines_count = 500, max_labels_count = 500, max_polylines_count = 100)
enum granularity
m1 = "1-Minute"
s1 = "1-Second"
t1 = "1-Tick"
gran = input.enum(defval = granularity.m1, title = "Granularity", options = [granularity.m1, granularity.s1, granularity.t1])
// targetTF = input.timeframe(defval = "1D", title = "Tick Data Target TF")
ticks = input.float(0, title = "Tick Level Distance (0 = Auto)")
useCVD = input.bool(defval = false, title = "Use CVD Levels", group = "CVD")
cvdRes = input.timeframe("1D", title = "CVD Reset TF", group = "CVD")
showDeltaP = input.bool(defval = true, title = "Show Delta Percentage", group = "Delta Boxes")
showTotalVolume = input.bool(defval = true, title = "Show Total Volume Per Level", group = "Delta Boxes")
showBuyVol = input.bool(defval = true, title = "Show Buy Volume Per Level", group = "Delta Boxes")
showSellVol = input.bool(defval = true, title = "Show Sell Volume Per Level", group = "Delta Boxes")
useSymbols = input.bool(defval = true, title = "Footprint IQ Delta Symbols", group = "Delta Boxes")
vaCumu = input.float(defval = 70, title = "Value Area %", minval = 0, maxval = 100) / 100
highestBuying = input.int(defval = 1, minval = 0, title = "Highest +Delta Levels To Show", group = "Delta Lines")
highestSelling = input.int(defval = 1, minval = 0, title = "Highest -Delta To Show", group = "Delta Lines")
belowAbove = input.bool(defval = true, title = "Show +Delta Levels Below Price And -Delta Levels Above Price", group = "Delta Lines")
imbalanceP = input.float(defval = 70, minval = 0, maxval = 100, title = "Imbalance Percentage", group = "Imbalances")
stacked = input.int (defval = 3, title = "Stacked Imbalance Count", minval = 2, group = "Imbalances")
upCol = input.color(defval = color.rgb(128, 116, 255), title = "+ Color", group = "Colors")
dnCol = input.color(defval = color.rgb(255, 116, 116), title = "+ Color", group = "Colors")
showPOC = input.bool(defval = true, title = "", inline = "POC", group = "Colors")
pocCol = input.color(defval = color.rgb(255, 243, 116), title = "POC Color", inline = "POC", group = "Colors")
var tf = switch gran
granularity.m1 => "1"
granularity.s1 => "1S"
granularity.t1 => "1T"
[ltfV, ltfD, ltfC, ltfH, ltfL, ltfBid, ltfAsk, ltfC1] =
request.security_lower_tf(syminfo.tickerid, tf, [volume, math.sign(close - open), close, high, low, bid, ask, close[1]])
type footprintData
array<float> tickLevels
array<float> deltaArr
array<float> upVol
array<float> dnVol
array<float> totalVol
type heatmapData
array<float> gradientLevelsDelta
array<float> gradientLevelsPrice
type gradientDrawings
array<line> gradientLine
array<linefill> gradientLineFill
label deltaLabel
atr = ta.atr(14)
var avgPriceArr = array.new<float>()
avgPriceArr.push(math.abs(close - open))
if avgPriceArr.size() > 1000
avgPriceArr.shift()
method shiftPopAll(array<float> tickLevels, deltaArr, upVol, dnVol, totalVol, pop = false) =>
if tickLevels.size() > 90000
switch pop
false =>
tickLevels.shift(), deltaArr.shift(),
upVol .shift(), dnVol .shift(),
totalVol .shift()
=>
tickLevels.pop(), deltaArr.pop(),
upVol .pop(), dnVol .pop(),
totalVol .pop()
method pushUnshiftAll(array<float> tickLevels, deltaArr, upVol, dnVol, totalVol, tickAmount, unshift = false) =>
switch unshift
false => tickLevels.push(tickAmount),
deltaArr.push(0), upVol.push(0), dnVol.push(0), totalVol.push(0),
=> tickLevels.unshift(tickAmount),
deltaArr.unshift(0), upVol.unshift(0), dnVol.unshift(0), totalVol.unshift(0)
method fillAll(array<float> deltaArr, upVol, dnVol, totalVol) =>
if gran != granularity.t1
if timeframe.change("") and not useCVD
if deltaArr.size() > 0
deltaArr.fill(0), upVol.fill(0), dnVol.fill(0), totalVol.fill(0)
else
if not useCVD
if deltaArr.size() > 0
deltaArr.fill(0), upVol.fill(0), dnVol.fill(0), totalVol.fill(0)
method textGenerate(array<string> boxText, condition, useSymbols, textSymbol, textGeneric) =>
if condition
switch useSymbols
true => boxText.set(0, boxText.first() + textSymbol)
=> boxText.set(0, boxText.first() + textGeneric)
if ltfV.size() > 0
var FD = footprintData.new(array.new<float>(), array.new<float>(), array.new<float>(), array.new<float>())
var HD = heatmapData .new(array.new<float>(), array.new<float>())
var tickAmount = 0.
FD.deltaArr.fillAll(FD.upVol, FD.dnVol, FD.totalVol)
cond = switch gran == granularity.t1
false => timeframe.change("1D") and not useCVD or useCVD and timeframe.change(cvdRes)
=> ltfV.size() > 0 and na(ltfV.size()[1]) or timeframe.change("1D") and not useCVD or useCVD and timeframe.change(cvdRes)
if cond
tickAmount := switch ticks == 0
true => atr * syminfo.mintick
=> ticks * syminfo.mintick
FD.tickLevels := array.from(open - tickAmount, open, open + tickAmount)
FD.deltaArr := array.from(0, 0, 0), FD.upVol := array.from(0, 0, 0)
FD.dnVol := array.from(0, 0, 0), FD.totalVol := array.from(0, 0, 0)
if FD.tickLevels.size() > 0
if HD.gradientLevelsDelta.size() == 0
HD.gradientLevelsPrice := array.from(open - tickAmount, open, open + tickAmount)
HD.gradientLevelsDelta := array.from(0, 0, 0)
last = FD.tickLevels.last ()
first = FD.tickLevels.first()
while high >= last
last += tickAmount
FD.tickLevels.pushUnshiftAll(FD.deltaArr, FD.upVol, FD.dnVol, FD.totalVol, last)
FD.tickLevels.shiftPopAll (FD.deltaArr, FD.upVol, FD.dnVol, FD.totalVol)
while low <= first
first -= tickAmount
FD.tickLevels.pushUnshiftAll(FD.deltaArr, FD.upVol, FD.dnVol, FD.totalVol, first, true)
FD.tickLevels.shiftPopAll (FD.deltaArr, FD.upVol, FD.dnVol, FD.totalVol, true)
last := HD.gradientLevelsPrice.last()
first := HD.gradientLevelsPrice.first()
while high >= last
last += tickAmount
HD.gradientLevelsPrice.push(last)
HD.gradientLevelsDelta.push(0)
if HD.gradientLevelsPrice.size() > 90000
HD.gradientLevelsPrice.shift()
HD.gradientLevelsDelta.shift()
while low <= first
first -= tickAmount
HD.gradientLevelsPrice.unshift(first)
HD.gradientLevelsDelta.unshift(0)
if HD.gradientLevelsPrice.size() > 90000
HD.gradientLevelsPrice.pop()
HD.gradientLevelsDelta.pop()
if gran != granularity.t1
for i = 0 to ltfV.size() - 1
getVol = ltfV.get(i), getHigh = ltfH.get(i), getLow = ltfL.get(i)
getTop = FD.tickLevels.binary_search_leftmost(getHigh)
getBot = FD.tickLevels.binary_search_leftmost(getLow)
if ltfD.get(i) == -1
getVol *= -1
div = getVol / (getTop - getBot + 1)
for x = getBot to getTop
FD.deltaArr.set(x, FD.deltaArr.get(x) + div)
switch math.sign(getVol)
1 => FD.upVol.set(x, FD.upVol.get(x) + div)
-1 => FD.dnVol.set(x, FD.dnVol.get(x) + div)
FD.totalVol.set(x, FD.totalVol.get(x) + math.abs(div))
getTop := HD.gradientLevelsPrice.binary_search_leftmost(getHigh)
getBot := HD.gradientLevelsPrice.binary_search_leftmost(getLow)
for x = getBot to getTop
HD.gradientLevelsDelta.set(x, HD.gradientLevelsDelta.get(x) + div)
if gran == granularity.t1
for i = 0 to ltfV.size() - 1
getVol = ltfV.get(i) , getHigh = ltfH .get(i), getLow = ltfL.get(i)
getBid = ltfBid.get(i), getAsk = ltfAsk.get(i), getClose = ltfC.get(i)
getCloseP = ltfC1.get(i)
getTop = FD.tickLevels.binary_search_leftmost(getHigh)
getBot = FD.tickLevels.binary_search_leftmost(getLow)
getVol *= switch
getClose <= getBid => -1
getClose >= getAsk => 1
getClose > getCloseP => 1
getClose < getCloseP => -1
=> 0
div = getVol / (getTop - getBot + 1)
for x = getBot to getTop
FD.deltaArr.set(x, FD.deltaArr.get(x) + div)
switch math.sign(getVol)
1 => FD.upVol.set(x, FD.upVol.get(x) + div)
-1 => FD.dnVol.set(x, FD.dnVol.get(x) + div)
FD.totalVol.set(x, FD.totalVol.get(x) + math.abs(div))
getTop := HD.gradientLevelsPrice.binary_search_leftmost(getHigh)
getBot := HD.gradientLevelsPrice.binary_search_leftmost(getLow)
for x = getBot to getTop
HD.gradientLevelsDelta.set(x, HD.gradientLevelsDelta.get(x) + div)
getStart = FD.tickLevels.binary_search_leftmost(low), getEnd = FD.tickLevels.binary_search_leftmost(high)
getLevelsSize = FD.tickLevels.size()
posVals = array.new<float>(), negVals = array.new<float>()
for data in FD.deltaArr
switch
data > 0 => posVals.push(data)
data < 0 => negVals.push(data)
minGrad = math.min(nz(posVals.min()), nz(math.abs(negVals.max())))
maxGrad = math.max(nz(posVals.max()), nz(math.abs(negVals.min())))
stackCount = 0
imbalanceLab = array.new<label>()
var finalLevel = float(na)
for i = getStart to getEnd
getLevel = FD.tickLevels.get(i)
getLevelNext = switch i + 1 == getLevelsSize
false => FD.tickLevels.get(i + 1)
=> getLevel + tickAmount
getDelta = FD.deltaArr.get(i)
finalLevel := getLevelNext
gradient = switch math.sign(getDelta)
1 => color.from_gradient(getDelta, minGrad, maxGrad, color.new(upCol, 70), color.new(upCol, 10))
=> color.from_gradient(getDelta, -maxGrad, -minGrad, color.new(dnCol, 10), color.new(dnCol, 70))
getTotalVol = FD.totalVol.get(i)
getDeltaP = getDelta / getTotalVol * 100
if showPOC
if getDelta == maxGrad or getDelta == -maxGrad
gradient := color.new(pocCol, 40)
txtValues = ""
textArr = switch useSymbols
true => array.from("δ: ", "\nδ%: ", "\n⧎: ", "\n◭: ", "\n⧩: ")
=> array.from("Delta: ", "\nDelta Percentage: ", "\nTotal Vol.: ", "\nBuy Vol.: ", "\nSell Vol.: ")
addArr = array.from(true, showDeltaP, showTotalVolume, showBuyVol, showSellVol)
valuesArr = array.from(
str.tostring(getDelta, format.volume),
str.tostring(getDeltaP, format.percent),
str.tostring(FD.totalVol.get(i), format.volume),
str.tostring(FD.upVol.get(i), format.volume),
str.tostring(FD.dnVol.get(i), format.volume)
)
for [x, data] in valuesArr
if addArr.get(x)
txtValues += textArr.get(x) + data
box.new(bar_index, getLevel, bar_index + 1, getLevelNext, border_color = chart.bg_color, border_width = 4, bgcolor = gradient,
text = txtValues)
if math.abs(getDeltaP) >= imbalanceP
stackCount += 1
getSign = math.sign(getDeltaP)
[imbColor, txt] = switch getSign
1 => [upCol, "𝅉"]
=> [dnCol, "𝅏"]
imbalanceLab.push(label.new(bar_index + 1, math.avg(getLevel, getLevelNext), textcolor = chart.fg_color,
text = txt,
size = size.normal,
color = #00000000,
style = label.style_label_center,
tooltip = str.tostring(getDeltaP, format.percent) + " Imbalance"
))
if stackCount == stacked
alert("Stacked Imbalance", freq = alert.freq_once_per_bar_close)
for x = 0 to imbalanceLab.size() - 1
imbalanceLab.get(x).set_textcolor(color.rgb(255, 243, 116))
else if stackCount > stacked
imbalanceLab.last() .set_textcolor(color.rgb(255, 243, 116))
else
stackCount := 0
imbalanceLab.clear()
if not useCVD
sliceVol = FD.totalVol .slice(getStart, getEnd + 1)
sliceVolMax = sliceVol .max()
newPoc = sliceVol .indexof(sliceVolMax)
POC = newPoc, bottom = 0, top = 0
sliceLevels = FD.tickLevels.slice(getStart, getEnd + 1)
sumVol = sliceVol.sum(), arrSize = sliceVol.size()
for x = 0 to arrSize - 1
slice = sliceVol.slice(math.max(newPoc - x, 0), math.min(newPoc + x + 1, arrSize))
if slice.sum() / sumVol >= vaCumu
bottom := math.max(newPoc - x, 0)
top := math.min(newPoc + x + 1, arrSize - 1)
box.new(bar_index, sliceLevels.get(bottom), bar_index, sliceLevels.get(top) + tickAmount,
border_color = #00000000,
bgcolor = #74ffbc,
border_width = 1
)
break
if not useCVD
txtValues = ""
textArr = switch useSymbols
true => array.from("δ: ", "\nδ%: ", "\n⧎: ", "\n◭: ", "\n⧩: ")
=> array.from("Delta: ", "\nDelta Percentage: ", "\nTotal Vol.: ", "\nBuy Vol.: ", "\nSell Vol.: ")
addArr = array.from(true, showDeltaP, showTotalVolume, showBuyVol, showSellVol)
getDelta = FD.deltaArr.sum()
getDeltaP = getDelta / volume * 100
valuesArr = array.from(
str.tostring(getDelta, format.volume),
str.tostring(getDeltaP, format.percent),
str.tostring(volume, format.volume),
str.tostring(FD.upVol.sum(), format.volume),
str.tostring(FD.dnVol.sum(), format.volume)
)
for [x, data] in valuesArr
if addArr.get(x)
txtValues += textArr.get(x) + data
box.new(bar_index, finalLevel, bar_index + 1, finalLevel + tickAmount, border_color = #00000000, bgcolor = #00000000, text = txtValues)
if barstate.islast
var deltaPlus = array.new<gradientDrawings>(highestBuying)
var deltaNeg = array.new<gradientDrawings>(highestSelling)
if highestBuying > 0
if na(deltaPlus.first())
for i = 0 to deltaPlus.size() - 1
deltaPlus.set(i, gradientDrawings.new(array.new<line>(20), array.new<linefill>(20), label(na)))
if highestSelling > 0
if na(deltaNeg.first())
for i = 0 to deltaNeg.size() - 1
deltaNeg .set(i, gradientDrawings.new(array.new<line>(20), array.new<linefill>(20), label(na)))
maxPos = 0., minPos = 20e20
minNeg = 0., maxNeg = -20e20
copyGradient = HD.gradientLevelsDelta.copy()
copyGradient.sort(order.ascending)
upCount = 0, dnCount = 0
highestPlus = map.new<float, float>(), highestNeg = map.new<float, float>()
if highestBuying != 0
for i = copyGradient.size() - 1 to 0
getDelta = copyGradient.get(i)
if math.sign(getDelta) == 1
if upCount < highestBuying
getPrice = HD.gradientLevelsPrice.get(HD.gradientLevelsDelta.indexof(getDelta))
if not belowAbove or belowAbove and getPrice <= open
highestPlus.put(getPrice, getDelta)
upCount += 1
else
break
else
break
if highestSelling != 0
for i = 0 to copyGradient.size() - 1
getDelta = copyGradient.get(i)
if math.sign(getDelta) == -1
if dnCount < highestSelling
getPrice = HD.gradientLevelsPrice.get(HD.gradientLevelsDelta.indexof(getDelta))
if not belowAbove or belowAbove and getPrice >= open
highestNeg.put(getPrice, getDelta)
dnCount += 1
else
break
else
break
if highestPlus.size() > 0
keys = highestPlus.keys()
for i = 0 to highestPlus.size() - 1
getLevel = keys.get(i)
getIndex = deltaPlus.get(i)
top = getLevel * 1.0008
bot = getLevel * .9998
Range = (top - bot) / 20
getIndex.deltaLabel.delete()
getIndex.deltaLabel := label.new(bar_index + 20, math.avg(top, bot, top),
text = "+" + str.tostring(highestPlus.get(getLevel), format.volume),
color = #00000000,
style = label.style_label_left,
size = size.small,
textcolor = upCol
)
for x = 0 to getIndex.gradientLine.size() - 1
getIndex.gradientLine.get(x).delete()
getIndex.gradientLine.set(x, line.new(bar_index - 1, bot + Range * x, bar_index + 1, bot + Range * x, color = #00000000, extend = extend.both))
getLine = getIndex.gradientLine.get(x)
getLineP = getIndex.gradientLine.get(math.max(0, x - 1))
if x > 0
getIndex.gradientLineFill.get(x).delete()
col = switch
x < 10 => color.from_gradient(x, 0, 10, chart.bg_color, upCol)
=> color.from_gradient(x, 11, 20, upCol, chart.bg_color)
getIndex.gradientLineFill.set(x, linefill.new(getLineP, getLine, color.new(col, 80)))
getLevelNow = getIndex.gradientLine.get(math.floor(getIndex.gradientLine.size() / 2))
getLevelNow.set_color(upCol)
getLevelNow.set_width(2)
if highestNeg.size() > 0
keys = highestNeg.keys()
for i = 0 to highestNeg.size() - 1
getLevel = keys.get(i)
getIndex = deltaNeg.get(i)
top = getLevel * 1.0008
bot = getLevel * .9998
Range = (top - bot) / 20
for x = 0 to getIndex.gradientLine.size() - 1
getIndex.gradientLine.get(x).delete()
getIndex.gradientLine.set(x, line.new(bar_index - 1, bot + Range * x, bar_index + 1, bot + Range * x, color = #00000000, extend = extend.both))
getLine = getIndex.gradientLine.get(x)
getLineP = getIndex.gradientLine.get(math.max(0, x - 1))
getIndex.deltaLabel.delete()
getIndex.deltaLabel := label.new(bar_index + 20, math.avg(top, bot, bot),
text = str.tostring(highestNeg.get(getLevel), format.volume),
color = #00000000,
style = label.style_label_left,
size = size.small,
textcolor = dnCol
)
if x > 0
getIndex.gradientLineFill.get(x).delete()
col = switch
x < 10 => color.from_gradient(x, 0, 10, chart.bg_color, dnCol)
=> color.from_gradient(x, 11, 20, dnCol, chart.bg_color)
getIndex.gradientLineFill.set(x, linefill.new(getLineP, getLine, color.new(col, 80)))
getLevelNow = getIndex.gradientLine.get(math.floor(getIndex.gradientLine.size() / 2))
getLevelNow.set_color(dnCol)
getLevelNow.set_width(2)
Show all 654 lines