indicator_volume_keylevels_template.pine
Copy
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © {{SHORTTITLE}}
//@version=6
indicator(
"{{TITLE}}", shorttitle="{{SHORTTITLE}}", overlay=true,
max_boxes_count=500, max_lines_count=500, max_labels_count=500,
max_bars_back=1000
)
// Template for high-precision Volume & Key Levels indicators.
// Combines Auction Market Theory (Volume Profile & vPOCs), Multi-Timeframe Key
// Levels with Liquidity Sweeps, Anchored VWAP, and Order Flow Delta/Absorption.
// ————— Constants
string THEME_DARK = "Dark"
string THEME_LIGHT = "Light"
string THEME_AUTO = "Auto"
string THEME_CUSTOM = "Custom"
color BULL_DARK = #089981
color BEAR_DARK = #f23645
color ACCENT_DARK = #d98b1f
color NEUTRAL_DARK = #b2b5be
color BULL_LIGHT = #00796b
color BEAR_LIGHT = #c62828
color ACCENT_LIGHT = #b26a00
// ————— Inputs: Multi-Timeframe Key Levels
string G_HTF = "Key Levels & MTF"
bool showPdhPdlInput = input.bool(
true, "Show Prev Day High/Low (PDH/PDL)", group=G_HTF,
tooltip="Plots the Previous Day's High and Low levels without lookahead bias.")
bool showPwhPwlInput = input.bool(
true, "Show Prev Week High/Low (PWH/PWL)", group=G_HTF,
tooltip="Plots the Previous Week's High and Low macro liquidity extremes.")
bool showSweepsInput = input.bool(
true, "Detect Liquidity Sweeps", group=G_HTF,
tooltip="Highlights false breakouts where price pierces a key level with volume surge and closes back inside.")
float sweepVolMultInput = input.float(
1.8, "Sweep Volume Multiplier", minval=1.0, maxval=10.0, step=0.1, group=G_HTF,
tooltip="Volume must exceed this multiple of the 20-bar average to qualify as an institutional sweep.")
// ————— Inputs: Volume Profile
string G_VP = "Volume Profile"
bool showVpInput = input.bool(
true, "Enable Volume Profile", group=G_VP,
tooltip="Computes horizontal volume distribution, POC, and Value Area (VAH/VAL).")
int vpLookbackInput = input.int(
150, "Profile Lookback (Bars)", minval=20, maxval=800, group=G_VP,
tooltip="Number of chart bars included in the rolling volume profile.")
int vpRowsInput = input.int(
24, "Price Rows", minval=10, maxval=50, group=G_VP,
tooltip="Number of horizontal price bins used for volume profile resolution.")
float vaPercentInput = input.float(
70.0, "Value Area %", minval=50.0, maxval=90.0, step=1.0, group=G_VP,
tooltip="Percentage of volume encompassed by Value Area (Standard Auction Theory uses 70%).")
// ————— Inputs: Anchored VWAP & Order Flow
string G_OF = "Order Flow & Anchored VWAP"
bool showAvwapInput = input.bool(
true, "Show Anchored VWAP", group=G_OF,
tooltip="Plots Volume-Weighted Average Price anchored to the chosen period.")
string avwapAnchorInput = input.string(
"Session", "VWAP Anchor Period", options=["Session", "Week", "Month"], group=G_OF,
tooltip="Reset frequency for the Anchored VWAP accumulation.")
bool showAvwapBands = input.bool(
true, "Show VWAP ±1σ Bands", group=G_OF,
tooltip="Plots first standard deviation volume dispersion bands around VWAP.")
bool detectAbsorbInput = input.bool(
true, "Highlight Volume Absorption", group=G_OF,
tooltip="Identifies bars with unusually high volume and narrow spread (Effort vs Result divergence).")
// ————— Inputs: Appearance & Dashboard
string G_APP = "Appearance & Dashboard"
string themeInput = input.string(
THEME_DARK, "Theme", options=[THEME_DARK, THEME_LIGHT, THEME_AUTO, THEME_CUSTOM], group=G_APP,
tooltip="Color theme palette adaptation.")
color customBullInput = input.color(
#089981, "Custom Bull", group=G_APP, inline="cust",
tooltip="Bullish highlight color for Custom theme.")
color customBearInput = input.color(
#f23645, "Custom Bear", group=G_APP, inline="cust",
tooltip="Bearish highlight color for Custom theme.")
bool showDashInput = input.bool(
true, "Show HUD Dashboard", group=G_APP,
tooltip="Displays live on-chart heads-up summary table.")
string tablePosInput = input.string(
"Top Right", "Dashboard Position",
options=["Top Right", "Top Left", "Bottom Right", "Bottom Left"], group=G_APP,
tooltip="Corner placement for the HUD table.")
int panelTranspInput = input.int(
15, "Dashboard Transparency", minval=0, maxval=90, group=G_APP,
tooltip="Background opacity for the HUD table.")
bool testModeInput = input.bool(
false, "Test Mode", group="Debug/Test",
tooltip="Runs internal test assertions and displays validation counters.")
// ————— Theme Resolution Helpers
_isLightChart() =>
r = color.r(chart.bg_color)
g = color.g(chart.bg_color)
b = color.b(chart.bg_color)
r * 0.299 + g * 0.587 + b * 0.114 > 128.0
getBullColor() =>
switch themeInput
THEME_LIGHT => BULL_LIGHT
THEME_AUTO => _isLightChart() ? BULL_LIGHT : BULL_DARK
THEME_CUSTOM => customBullInput
=> BULL_DARK
getBearColor() =>
switch themeInput
THEME_LIGHT => BEAR_LIGHT
THEME_AUTO => _isLightChart() ? BEAR_LIGHT : BEAR_DARK
THEME_CUSTOM => customBearInput
=> BEAR_DARK
getAccentColor() =>
switch themeInput
THEME_LIGHT => ACCENT_LIGHT
=> ACCENT_DARK
// library-sync-exempt: this variant supports a third theme, THEME_AUTO, which
// follows the chart's own light/dark setting. The library has only two, because
// a published library cannot read the chart it is imported into.
getTextColor() =>
themeInput == THEME_LIGHT or (themeInput == THEME_AUTO and _isLightChart()) ? #131722 : #d1d4dc
// library-sync-exempt: this variant supports a third theme, THEME_AUTO, which
// follows the chart's own light/dark setting. The library has only two, because
// a published library cannot read the chart it is imported into.
getMutedColor() =>
themeInput == THEME_LIGHT or (themeInput == THEME_AUTO and _isLightChart()) ? #4a4e58 : #b2b5be
// library-sync-exempt: this variant supports a third theme, THEME_AUTO, which
// follows the chart's own light/dark setting. The library has only two, because
// a published library cannot read the chart it is imported into.
getPanelColor() =>
color.new(
themeInput == THEME_LIGHT or (themeInput == THEME_AUTO and _isLightChart()) ? #ffffff : #131722,
panelTranspInput)
getTablePosition() =>
switch tablePosInput
"Top Left" => position.top_left
"Bottom Right" => position.bottom_right
"Bottom Left" => position.bottom_left
=> position.top_right
// ————— Safe Zero-Repaint HTF Data
[pdh, pdl] = request.security(
syminfo.tickerid, "D", [high[1], low[1]],
lookahead=barmerge.lookahead_off, gaps=barmerge.gaps_off)
[pwh, pwl] = request.security(
syminfo.tickerid, "W", [high[1], low[1]],
lookahead=barmerge.lookahead_off, gaps=barmerge.gaps_off)
// ————— Order Flow & Cumulative Volume Delta (CVD)
float barRange = high - low
float buyShare = barRange > 0 ? (close - low) / barRange : 0.5
float barBuyVol = nz(volume) * buyShare
float barSellVol = nz(volume) * (1.0 - buyShare)
float barDelta = barBuyVol - barSellVol
var float cvd = 0.0
bool isNewDay = timeframe.change("D")
if isNewDay
cvd := barDelta
else
cvd += barDelta
// ————— Effort vs Result / Absorption Detection
float atr14 = ta.atr(14)
float avgVol20 = ta.sma(nz(volume), 20)
float spreadRatio = atr14 > 0 ? math.max(0.1, barRange / atr14) : 1.0
float volRatio = avgVol20 > 0 ? nz(volume) / avgVol20 : 1.0
float evr = volRatio / spreadRatio
bool isAbsorption = detectAbsorbInput and evr > 2.2 and nz(volume) >= (avgVol20 * 1.5)
// ————— Liquidity Sweeps
bool pdhSweep = showSweepsInput and not na(pdh) and high > pdh and close < pdh and
nz(volume) >= (avgVol20 * sweepVolMultInput)
bool pdlSweep = showSweepsInput and not na(pdl) and low < pdl and close > pdl and
nz(volume) >= (avgVol20 * sweepVolMultInput)
// ————— Anchored VWAP Calculation
string anchorTf = avwapAnchorInput == "Week" ? "W" : (avwapAnchorInput == "Month" ? "M" : "D")
bool isAnchorReset = timeframe.change(anchorTf)
var float vwapSumSrcVol = 0.0
var float vwapSumVol = 0.0
var float vwapSumSqVol = 0.0
if isAnchorReset
vwapSumSrcVol := 0.0
vwapSumVol := 0.0
vwapSumSqVol := 0.0
float curTypPrice = hlc3
float curVol = nz(volume)
vwapSumSrcVol += curTypPrice * curVol
vwapSumVol += curVol
vwapSumSqVol += math.pow(curTypPrice, 2) * curVol
float avwap = vwapSumVol > 0 ? vwapSumSrcVol / vwapSumVol : curTypPrice
float vwapVar = vwapSumVol > 0 ? math.max(0.0, (vwapSumSqVol / vwapSumVol) - math.pow(avwap, 2)) : 0.0
float vwapDev = math.sqrt(vwapVar)
float avwapBand1Up = avwap + vwapDev
float avwapBand1Dn = avwap - vwapDev
// ————— Volume Profile & vPOC Arrays (Pre-allocated Pools)
var float[] vpPricesPool = array.new_float(50, 0.0)
var float[] vpVolsPool = array.new_float(50, 0.0)
var float curPoc = na
var float curVah = na
var float curVal = na
if showVpInput and barstate.islast
int lb = math.min(bar_index, vpLookbackInput)
int nRows = math.min(50, vpRowsInput)
float hiB = -1.0e10
float loB = 1.0e10
for i = 0 to lb - 1
hiB := math.max(hiB, high[i])
loB := math.min(loB, low[i])
float rowStep = (hiB > loB and nRows > 0) ? (hiB - loB) / nRows : syminfo.mintick
array.fill(vpVolsPool, 0.0)
for r = 0 to nRows - 1
array.set(vpPricesPool, r, loB + (r + 0.5) * rowStep)
float totalVPVol = 0.0
for i = 0 to lb - 1
float v = nz(volume[i])
int rLo = math.max(0, math.min(nRows - 1, int(math.floor((low[i] - loB) / rowStep))))
int rHi = math.max(0, math.min(nRows - 1, int(math.floor((high[i] - loB) / rowStep))))
int span = math.max(1, rHi - rLo + 1)
float vSplit = v / span
for r = rLo to rHi
array.set(vpVolsPool, r, array.get(vpVolsPool, r) + vSplit)
totalVPVol += v
float maxV = 0.0
int pIdx = 0
for r = 0 to nRows - 1
float v = array.get(vpVolsPool, r)
if v > maxV
maxV := v
pIdx := r
curPoc := array.get(vpPricesPool, pIdx)
float targetVa = totalVPVol * (vaPercentInput / 100.0)
float vaAccum = maxV
int uP = pIdx + 1
int dP = pIdx - 1
while vaAccum < targetVa and (uP < nRows or dP >= 0)
float uV = uP < nRows ? array.get(vpVolsPool, uP) : 0.0
float dV = dP >= 0 ? array.get(vpVolsPool, dP) : 0.0
if uV >= dV and uP < nRows
vaAccum += uV
uP += 1
else if dP >= 0
vaAccum += dV
dP -= 1
else if uP < nRows
vaAccum += uV
uP += 1
curVah := array.get(vpPricesPool, math.min(nRows - 1, uP - 1))
curVal := array.get(vpPricesPool, math.max(0, dP + 1))
// ————— Plots & Visuals
color bullC = getBullColor()
color bearC = getBearColor()
color accentC = getAccentColor()
// HTF Levels
plot(showPdhPdlInput ? pdh : na, "PDH", color=color.new(accentC, 20), linewidth=1, style=plot.style_linebr)
plot(showPdhPdlInput ? pdl : na, "PDL", color=color.new(accentC, 20), linewidth=1, style=plot.style_linebr)
plot(showPwhPwlInput ? pwh : na, "PWH", color=color.new(accentC, 0), linewidth=2, style=plot.style_linebr)
plot(showPwhPwlInput ? pwl : na, "PWL", color=color.new(accentC, 0), linewidth=2, style=plot.style_linebr)
// Anchored VWAP & Dispersion Bands
plot(showAvwapInput ? avwap : na, "AVWAP", color=color.new(#2962ff, 0), linewidth=2)
plot(showAvwapInput and showAvwapBands ? avwapBand1Up : na, "AVWAP +1σ",
color=color.new(#2962ff, 50), linewidth=1, style=plot.style_circles)
plot(showAvwapInput and showAvwapBands ? avwapBand1Dn : na, "AVWAP -1σ",
color=color.new(#2962ff, 50), linewidth=1, style=plot.style_circles)
// Signals & Markers
plotshape(pdhSweep, "PDH Sweep (Bearish)", shape.triangledown, location.abovebar,
bearC, size=size.small, text="SWEEP")
plotshape(pdlSweep, "PDL Sweep (Bullish)", shape.triangleup, location.belowbar,
bullC, size=size.small, text="SWEEP")
plotshape(isAbsorption, "Volume Absorption", shape.diamond, location.bottom, accentC, size=size.tiny)
// ————— HUD Dashboard Table
var table hudTable = table.new(getTablePosition(), 2, 7, border_width=1)
formatVolCompact(float v) =>
float av = math.abs(nz(v))
switch
av >= 1000000000 => str.tostring(v / 1000000000, "#.##") + "B"
av >= 1000000 => str.tostring(v / 1000000, "#.##") + "M"
av >= 1000 => str.tostring(v / 1000, "#.#") + "K"
=> str.tostring(nz(v), "#.#")
if showDashInput and barstate.islast
color bgP = getPanelColor()
color txtP = getTextColor()
color mutP = getMutedColor()
table.cell(hudTable, 0, 0, "VOL & KEY LEVELS", bgcolor=bgP, text_color=txtP, text_size=size.small)
table.cell(hudTable, 1, 0, "HUD", bgcolor=bgP, text_color=accentC, text_size=size.small)
string deltaStr = (barDelta >= 0 ? "+" : "") + formatVolCompact(barDelta)
table.cell(hudTable, 0, 1, "Price / Delta", bgcolor=bgP, text_color=mutP, text_size=size.small)
table.cell(hudTable, 1, 1, str.tostring(close, "#.##") + " (" + deltaStr + ")",
bgcolor=bgP, text_color=barDelta >= 0 ? bullC : bearC, text_size=size.small)
table.cell(hudTable, 0, 2, "Cumulative Delta (CVD)", bgcolor=bgP, text_color=mutP, text_size=size.small)
table.cell(hudTable, 1, 2, formatVolCompact(cvd), bgcolor=bgP,
text_color=cvd >= 0 ? bullC : bearC, text_size=size.small)
table.cell(hudTable, 0, 3, "Volume Profile POC", bgcolor=bgP, text_color=mutP, text_size=size.small)
table.cell(hudTable, 1, 3, na(curPoc) ? "--" : str.tostring(curPoc, "#.##"),
bgcolor=bgP, text_color=accentC, text_size=size.small)
string vaStr = (na(curVah) ? "--" : str.tostring(curVah, "#.##")) + " / " +
(na(curVal) ? "--" : str.tostring(curVal, "#.##"))
table.cell(hudTable, 0, 4, "Value Area (VAH/VAL)", bgcolor=bgP, text_color=mutP, text_size=size.small)
table.cell(hudTable, 1, 4, vaStr, bgcolor=bgP, text_color=txtP, text_size=size.small)
table.cell(hudTable, 0, 5, "Anchored VWAP", bgcolor=bgP, text_color=mutP, text_size=size.small)
table.cell(hudTable, 1, 5, str.tostring(avwap, "#.##"), bgcolor=bgP, text_color=#2962ff, text_size=size.small)
string evrSuffix = isAbsorption ? " (ABSORB)" : ""
table.cell(hudTable, 0, 6, "Effort vs Result", bgcolor=bgP, text_color=mutP, text_size=size.small)
table.cell(hudTable, 1, 6, str.tostring(evr, "#.##") + evrSuffix,
bgcolor=bgP, text_color=isAbsorption ? accentC : txtP, text_size=size.small)
// ————— Alerts
alertcondition(pdhSweep, "PDH Liquidity Sweep", "Bearish liquidity sweep detected above Previous Day High.")
alertcondition(pdlSweep, "PDL Liquidity Sweep", "Bullish liquidity sweep detected below Previous Day Low.")
alertcondition(isAbsorption, "Volume Absorption Alert", "High volume absorption detected at current bar.")
// ————— Debug / Test Block
int TEST_COUNT = 3
var int passCount = 0
var int failCount = 0
assertFailed(bool condition, string description) =>
if not condition
label.new(
bar_index, high, "FAIL: " + description, color=#f23645,
textcolor=color.white, style=label.style_label_down, size=size.tiny)
condition ? 0 : 1
if testModeInput
int fails = 0
fails += assertFailed(buyShare >= 0.0 and buyShare <= 1.0, "buyShare out of range")
fails += assertFailed(not na(avwap), "avwap is na")
fails += assertFailed(evr >= 0.0, "evr is negative")
failCount += fails
passCount += TEST_COUNT - fails
var table testTable = table.new(position.bottom_left, 2, 2, border_width=1)
if testModeInput and barstate.islast
table.cell(testTable, 0, 0, "Passed", bgcolor=BULL_DARK, text_color=color.white)
table.cell(testTable, 1, 0, str.tostring(passCount), bgcolor=BULL_DARK, text_color=color.white)
table.cell(testTable, 0, 1, "Failed",
bgcolor=failCount == 0 ? NEUTRAL_DARK : BEAR_DARK, text_color=color.white)
table.cell(testTable, 1, 1, str.tostring(failCount),
bgcolor=failCount == 0 ? NEUTRAL_DARK : BEAR_DARK, text_color=color.white)
Show all 382 lines