// This script is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
// https://creativecommons.org/licenses/by-nc-sa/4.0/
// © QuantAlgo
//@version=6
indicator(‘Volume-Weighted Price Z-Score [QuantAlgo]’, overlay = false)
// ╔════════════════════════════════╗ //
// ║ USER-DEFINED SETTINGS ║ //
// ╚════════════════════════════════╝ //
var string calculation_settings = ‘════════ Calculation Parameters ════════’
var string visual_settings = ‘════════ Visualization Settings ════════’
var string alert_settings = ‘════════ Alert Configuration ════════’
tooltip_price_source = ‘The price data used for calculations. Close is most common for end-of-period analysis. High/Low can be used for extremes, HL2 for midpoint, HLC3 for typical price, or OHLC4 for complete bar representation. Each source provides different sensitivity to price movements.’
tooltip_lookback = ‘Period for calculating the volume-weighted moving average and volatility baseline. Shorter periods (20-50) make the indicator more responsive to recent price changes, capturing short-term deviations quickly. Longer periods (100-200) provide more stable reference points, better for identifying major market dislocations. Match this to your trading timeframe.’
tooltip_smoothing = ‘EMA smoothing applied to the final z-score to reduce noise and create cleaner signals. Lower values (1-3) preserve more detail and react faster but may show more false signals. Higher values (5-10) create smoother curves with fewer whipsaws but slower response. Set to 1 for no smoothing.’
tooltip_preset = ‘Select a predefined configuration optimized for different analysis styles and market conditions.’
tooltip_preset_details = ‘Default (100, 5): Balanced configuration suitable for swing trading and daily/4H analysis. Provides good balance between responsiveness and stability. Works well for identifying medium-term overbought/oversold conditions.nnFast Response (50, 3): Faster configuration for intraday trading and scalping. Shorter lookback period and minimal smoothing catch price deviations quickly. Best for 15min-1H charts where rapid mean reversion opportunities are important. Expect more frequent signals.nnSmooth Trend (200, 8): Stable configuration for position trading and long-term analysis. Extended lookback and heavy smoothing filter out noise to identify only major market extremes. Ideal for daily-weekly charts where you want high-confidence deviation signals with minimal false positives.’
tooltip_extreme_threshold = ‘Z-score threshold for marking extreme deviation levels. Values above +threshold indicate extreme overvaluation, values below -threshold indicate extreme undervaluation. Standard values: 2.0 (95.4% confidence), 2.5 (98.8% confidence), 3.0 (99.7% confidence). Higher thresholds identify rarer, more extreme conditions.’
tooltip_color_preset = ‘Pre-configured color schemes optimized for different chart themes and visual preferences. Each preset maintains strong contrast for clear identification of positive vs negative deviations.’
tooltip_positive_color = ‘Color for positive z-score values (price above volume-weighted average). Represents conditions where the asset may be overvalued relative to its volume-weighted price. Typically shown in warm colors (red, orange) to indicate caution.’
tooltip_negative_color = ‘Color for negative z-score values (price below volume-weighted average). Represents conditions where the asset may be undervalued relative to its volume-weighted price. Typically shown in cool colors (cyan, blue, green) to indicate potential buying opportunities.’
tooltip_fill_transparency = ‘Transparency level for the gradient fill area between the z-score line and zero line. Higher values (70-95) create a more subtle, transparent background. Lower values (20-40) create a more solid, prominent fill that clearly highlights deviations. Set to 100 for completely transparent (no fill).’
tooltip_bar_coloring = ‘Enable/disable coloring of indicator bars based on the z-score gradient color. When enabled, the background bars of the indicator pane are colored to match the current z-score deviation, providing additional visual context for positive vs negative deviations.’
priceSource = input.source(close, ‘Price Source’, group = calculation_settings, tooltip = tooltip_price_source)
lookbackPeriod = input.int(100, ‘Lookback Period’, group = calculation_settings, tooltip = tooltip_lookback)
smoothingPeriod = input.int(5, ‘Smoothing Period’, minval = 1, group = calculation_settings, tooltip = tooltip_smoothing)
presetConfig = input.string(‘Default’, ‘Preset Configuration’, options = [‘Default’, ‘Fast Response’, ‘Smooth Trend’], group = calculation_settings, tooltip = tooltip_preset + ‘nn’ + tooltip_preset_details)
if presetConfig == ‘Fast Response’
lookbackPeriod := 50
smoothingPeriod := 3
smoothingPeriod
else if presetConfig == ‘Smooth Trend’
lookbackPeriod := 200
smoothingPeriod := 8
smoothingPeriod
extremeThreshold = input.float(2.5, ‘Extreme Threshold’, minval = 1.0, maxval = 4.0, step = 0.5, group = calculation_settings, tooltip = tooltip_extreme_threshold)
colorPreset = input.string(‘Custom’, ‘Color Preset’, options = [‘Classic’, ‘Aqua’, ‘Cosmic’, ‘Ember’, ‘Neon’, ‘Custom’], group = visual_settings, tooltip = tooltip_color_preset)
positiveColorInput = input.color(#ff0000, ‘Positive Deviation Color’, group = visual_settings, tooltip = tooltip_positive_color)
negativeColorInput = input.color(#00ffaa, ‘Negative Deviation Color’, group = visual_settings, tooltip = tooltip_negative_color)
fillTransparency = input.int(70, ‘Fill Transparency’, minval = 0, maxval = 100, group = visual_settings, tooltip = tooltip_fill_transparency)
enableBarColoring = input.bool(true, ‘Enable Bar Coloring’, group = visual_settings, tooltip = tooltip_bar_coloring)
[positiveColor, negativeColor] = switch colorPreset
‘Classic’ => [#ff0000, #00ff00]
‘Aqua’ => [#ff8c00, #00bfff]
‘Cosmic’ => [#9932cc, #49ffce]
‘Ember’ => [#00cccc, #ff6600]
‘Neon’ => [#ff00ff, #ffff00]
‘Custom’ => [positiveColorInput, negativeColorInput]
// ╔════════════════════════════════╗ //
// ║ Z-SCORE CALCULATION ║ //
// ╚════════════════════════════════╝ //
volumeWeightedAverage = ta.vwma(priceSource, lookbackPeriod)
logDeviation = math.log(priceSource / volumeWeightedAverage)
volatilityMeasure = ta.stdev(logDeviation, lookbackPeriod)
rawZScore = logDeviation / volatilityMeasure
zScore = ta.ema(rawZScore, smoothingPeriod)
// ╔════════════════════════════════╗ //
// ║ SIGNAL & LEVEL DETECTION ║ //
// ╚════════════════════════════════╝ //
extremePositive = zScore >= extremeThreshold
extremeNegative = zScore <= –extremeThreshold
crossAboveZero = ta.crossover(zScore, 0)
crossBelowZero = ta.crossunder(zScore, 0)
enterExtremePositive = ta.crossover(zScore, extremeThreshold)
enterExtremeNegative = ta.crossunder(zScore, –extremeThreshold)
exitExtremePositive = ta.crossunder(zScore, extremeThreshold)
exitExtremeNegative = ta.crossover(zScore, –extremeThreshold)
// ╔════════════════════════════════╗ //
// ║ VISUALIZATION ║ //
// ╚════════════════════════════════╝ //
var color neutralColor = #808080
gradientColor = zScore > 0 ?
color.from_gradient(zScore, 0, extremeThreshold, neutralColor, positiveColor) :
color.from_gradient(zScore, –extremeThreshold, 0, negativeColor, neutralColor)
zScorePlot = plot(zScore, color = gradientColor, linewidth = 3, title = ‘Z-Score’)
zeroLinePlot = plot(0, color = color.new(color.gray, 50), title = ‘Zero Line’, linestyle = plot.linestyle_dotted, editable = true)
fill(zScorePlot, zeroLinePlot, zScore, 0, color.new(gradientColor, fillTransparency), color.new(gradientColor, math.min(fillTransparency + 20, 100)), title = ‘Z-Score Fill’)
plot(extremeThreshold, ‘Extreme Positive Level’, color = positiveColor, linestyle = plot.linestyle_dashed, editable = true)
plot(–extremeThreshold, ‘Extreme Negative Level’, color = negativeColor, linestyle = plot.linestyle_dashed, editable = true)
plot(2, ‘+2σ’, color = color.new(color.gray, 50), linestyle = plot.linestyle_dotted, editable = true, display = display.none)
plot(-2, ‘-2σ’, color = color.new(color.gray, 50), linestyle = plot.linestyle_dotted, editable = true, display = display.none)
plot(1, ‘+1σ’, color = color.new(color.gray, 50), linestyle = plot.linestyle_dotted, editable = true)
plot(-1, ‘-1σ’, color = color.new(color.gray, 50), linestyle = plot.linestyle_dotted, editable = true)
barcolor(enableBarColoring ? gradientColor : na, title = ‘Z-Score Bar Color’)
// ╔════════════════════════════════╗ //
// ║ ALERTS ║ //
// ╚════════════════════════════════╝ //
alertcondition(enterExtremePositive, title = ‘Extreme Overbought’, message = ‘VWPZ: Extreme OVERBOUGHT condition detected! {{exchange}}:{{ticker}} – {{interval}}’)
alertcondition(enterExtremeNegative, title = ‘Extreme Oversold’, message = ‘VWPZ: Extreme OVERSOLD condition detected! {{exchange}}:{{ticker}} – {{interval}}’)
alertcondition(exitExtremePositive, title = ‘Exit Extreme Overbought’, message = ‘VWPZ: Exiting extreme overbought zone {{exchange}}:{{ticker}} – {{interval}}’)
alertcondition(exitExtremeNegative, title = ‘Exit Extreme Oversold’, message = ‘VWPZ: Exiting extreme oversold zone {{exchange}}:{{ticker}} – {{interval}}’)
alertcondition(crossAboveZero, title = ‘Bullish Mean Reversion’, message = ‘VWPZ: Crossed above zero – Bullish momentum {{exchange}}:{{ticker}} – {{interval}}’)
alertcondition(crossBelowZero, title = ‘Bearish Mean Reversion’, message = ‘VWPZ: Crossed below zero – Bearish momentum {{exchange}}:{{ticker}} – {{interval}}’)
alertcondition(enterExtremePositive or enterExtremeNegative, title = ‘Any Extreme Level’, message = ‘VWPZ: Extreme deviation detected! Check chart {{exchange}}:{{ticker}} – {{interval}}’)