ema_pvsr_master.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/
// © midnightpickle
//
// MASTER script — merges "EMA & PVSR Indicators" (v2) + "PVSR Volume Panel" (v2).
//
// How the single-indicator split works:
// The indicator is declared overlay=false so the volume columns get their OWN pane.
// Every price-chart element (EMAs, SMAs, VWAP, candle colouring, cross labels, zones)
// is tagged force_overlay=true to push it onto the main price chart. The volume plots
// are left un-forced, so they stay in the indicator's pane.
//
// Why combine:
// PVSR classification is now computed ONCE and feeds both the chart candle colours
// and the volume-bar colours, so they can never drift out of sync. The old
// "keep the two scripts' multipliers identical" caveat is gone.
//@version=6
indicator("EMA & PVSR Master MP", overlay=false, max_boxes_count=500)
// ==========================================
// 🔹 MASTER TOGGLE - QUICK ACCESS
// ==========================================
master_show_ema = input.bool(true, title="🔘 MASTER: Show EMAs", group="⚡ MASTER TOGGLES", tooltip="Master toggle for all EMAs")
master_show_ema_cross = input.bool(true, title="🔘 MASTER: Show EMA Cross Labels", group="⚡ MASTER TOGGLES", tooltip="Master toggle for EMA crossover labels")
master_show_sma = input.bool(true, title="🔘 MASTER: Show SMAs", group="⚡ MASTER TOGGLES", tooltip="Master toggle for all SMAs")
master_show_sma_cross = input.bool(true, title="🔘 MASTER: Show SMA Cross Labels", group="⚡ MASTER TOGGLES", tooltip="Master toggle for SMA crossover labels")
master_show_vwap = input.bool(true, title="🔘 MASTER: Show VWAP", group="⚡ MASTER TOGGLES", tooltip="Master toggle for VWAP")
master_show_pvsr = input.bool(true, title="🔘 MASTER: Show PVSR Candles", group="⚡ MASTER TOGGLES", tooltip="Master toggle for PVSR candle colouring on the price chart")
master_show_volume = input.bool(true, title="🔘 MASTER: Show Volume Panel", group="⚡ MASTER TOGGLES", tooltip="Master toggle for the volume columns / MA / threshold lines in this pane")
master_show_zones = input.bool(false, title="🔘 MASTER: Show Vector Zones", group="⚡ MASTER TOGGLES", tooltip="Master toggle for vector candle zones")
// ==========================================
// 🔹 INPUT MENU - EMA/SMA/VWAP SECTION
// ==========================================
// --- EMA Individual Settings ---
show_ema1 = input.bool(true, title="Show EMA 1", inline="e1", group="📈 EMA Settings")
len_ema1 = input.int(10, title="", inline="e1", group="📈 EMA Settings")
col_ema1 = input.color(color.orange, title="", inline="e1", group="📈 EMA Settings")
show_ema2 = input.bool(true, title="Show EMA 2", inline="e2", group="📈 EMA Settings")
len_ema2 = input.int(21, title="", inline="e2", group="📈 EMA Settings")
col_ema2 = input.color(color.green, title="", inline="e2", group="📈 EMA Settings")
show_ema3 = input.bool(true, title="Show EMA 3", inline="e3", group="📈 EMA Settings")
len_ema3 = input.int(50, title="", inline="e3", group="📈 EMA Settings")
col_ema3 = input.color(color.blue, title="", inline="e3", group="📈 EMA Settings")
show_ema4 = input.bool(true, title="Show EMA 4", inline="e4", group="📈 EMA Settings")
len_ema4 = input.int(100, title="", inline="e4", group="📈 EMA Settings")
col_ema4 = input.color(color.gray, title="", inline="e4", group="📈 EMA Settings")
show_ema5 = input.bool(true, title="Show EMA 5", inline="e5", group="📈 EMA Settings")
len_ema5 = input.int(200, title="", inline="e5", group="📈 EMA Settings")
col_ema5 = input.color(color.purple, title="", inline="e5", group="📈 EMA Settings")
show_ema6 = input.bool(true, title="Show EMA 6", inline="e6", group="📈 EMA Settings")
len_ema6 = input.int(800, title="", inline="e6", group="📈 EMA Settings")
col_ema6 = input.color(color.red, title="", inline="e6", group="📈 EMA Settings")
// --- EMA Crossover Settings ---
show_ema_cross_labels = input.bool(true, title="Show EMA Cross Labels", group="🔄 EMA Crossover Settings")
len_ema_cross_fast = input.int(9, title="Fast Length", group="🔄 EMA Crossover Settings")
len_ema_cross_slow = input.int(21, title="Slow Length", group="🔄 EMA Crossover Settings")
// --- SMA Individual Settings ---
show_sma1 = input.bool(true, title="Show SMA 1", inline="s1", group="📊 SMA Settings")
len_sma1 = input.int(50, title="", inline="s1", group="📊 SMA Settings")
col_sma1 = input.color(color.purple, title="", inline="s1", group="📊 SMA Settings")
show_sma2 = input.bool(true, title="Show SMA 2", inline="s2", group="📊 SMA Settings")
len_sma2 = input.int(100, title="", inline="s2", group="📊 SMA Settings")
col_sma2 = input.color(color.fuchsia, title="", inline="s2", group="📊 SMA Settings")
show_sma3 = input.bool(true, title="Show SMA 3", inline="s3", group="📊 SMA Settings")
len_sma3 = input.int(200, title="", inline="s3", group="📊 SMA Settings")
col_sma3 = input.color(color.navy, title="", inline="s3", group="📊 SMA Settings")
// --- SMA Crossover Settings ---
show_sma_cross_labels = input.bool(true, title="Show SMA Cross Labels", group="🔄 SMA Crossover Settings")
len_sma_cross_fast = input.int(50, title="Fast Length", group="🔄 SMA Crossover Settings")
len_sma_cross_slow = input.int(200, title="Slow Length", group="🔄 SMA Crossover Settings")
// --- VWAP Settings ---
show_vwap = input.bool(true, title="Show VWAP", group="📉 VWAP Settings")
col_vwap = input.color(color.orange, title="VWAP Color", group="📉 VWAP Settings")
wid_vwap = input.int(2, title="VWAP Width", minval=1, group="📉 VWAP Settings")
// ==========================================
// 🔹 INPUT MENU - PVSR SECTION (shared by candles + volume panel)
// ==========================================
// --- PVSR Candle Settings ---
show_pvsr_candles = input.bool(true, title="Show PVSR Candles", group="🕯️ PVSR Candle Settings")
// Classification thresholds — used for BOTH candle colours and volume-bar colours
climax_mult = input.float(2.0, title="Climax Volume ×", minval=1.0, step=0.1, group="🕯️ PVSR Candle Settings", tooltip="Volume ≥ this × average, or a volume-spread high, marks a climax (green/red) candle.")
aboveavg_mult = input.float(1.5, title="Above-Average Volume ×", minval=1.0, step=0.1, group="🕯️ PVSR Candle Settings", tooltip="Volume ≥ this × average marks an above-average (blue/violet) candle.")
vol_lookback = input.int(10, title="Volume Lookback (bars)", minval=1, group="🕯️ PVSR Candle Settings", tooltip="Bars used for the average volume and volume-spread high.")
// How the lookback window is measured, and which climax test is active.
pvsr_exclude_current = input.bool(true, title="Exclude current bar from lookback", group="🕯️ PVSR Candle Settings", tooltip="ON (canonical): the average volume and the volume-spread high are measured over the PREVIOUS N bars, matching the original PVSRA definition. OFF: the current bar sits inside its own comparison window, so an outsized bar raises the very threshold it is being tested against and can suppress its own signal. Turn OFF to reproduce charts drawn before this option existed.")
pvsr_use_spread_leg = input.bool(true, title="Use volume × spread climax leg", group="🕯️ PVSR Candle Settings", tooltip="A bar is climax if volume ≥ climax × average OR volume × range is the highest of the lookback. That second leg fires whenever a bar sets a new lookback high in volume × range, which happens roughly once every N bars regardless of conditions, so it triggers far more often than the volume leg. Turn OFF to see classification driven by volume alone.")
// PVSR Color Settings (shared)
red_vector_color = input.color(color.rgb(255, 82, 82), title="Red Vector Candle", group="🎨 PVSR Color Settings", inline="pvsrc")
green_vector_color = input.color(color.rgb(76, 175, 80), title="Green Vector Candle", group="🎨 PVSR Color Settings", inline="pvsrc")
violet_vector_color = input.color(color.rgb(156, 39, 176), title="Violet Vector Candle", group="🎨 PVSR Color Settings", inline="pvsrc")
blue_vector_color = input.color(color.rgb(33, 150, 243), title="Blue Vector Candle", group="🎨 PVSR Color Settings", inline="pvsrc")
dark_grey_candle_color = input.color(color.rgb(100, 100, 100), title="Regular Down Candle", group="🎨 PVSR Color Settings", inline="pvsrc")
light_gray_candle_color = input.color(color.rgb(169, 169, 169), title="Regular Up Candle", group="🎨 PVSR Color Settings", inline="pvsrc")
// --- Vector Zone Settings ---
show_vector_zones = input.bool(false, title="Show Vector Candle Zones", group="📦 Vector Zone Settings")
max_zones = input.int(20, title="Maximum Zones per Side", minval=1, maxval=50, group="📦 Vector Zone Settings")
zone_type = input.string("Body with wicks", title="Zone Extent", options=["Body with wicks", "Body only"], group="📦 Vector Zone Settings", tooltip="Whether a zone spans the full high/low (wicks) or just the candle body.")
zone_mitigation = input.string("Close", title="Zone Mitigation", options=["Close", "Wick"], group="📦 Vector Zone Settings", tooltip="Close: zone is removed when a bar closes through it. Wick: removed as soon as any wick pierces the far edge.")
zone_transparency = input.int(80, title="Zone Transparency", minval=0, maxval=100, group="📦 Vector Zone Settings")
zone_border_width = input.int(1, title="Zone Border Width", minval=0, maxval=3, group="📦 Vector Zone Settings")
override_zone_color = input.bool(false, title="Override Zone Color", group="📦 Vector Zone Settings")
zone_override_color = input.color(color.yellow, title="Override Zone Color", group="📦 Vector Zone Settings")
// --- Volume Panel Settings ---
show_volume_ma = input.bool(true, title="Show Volume Moving Average", group="📊 Volume Panel")
volume_ma_length = input.int(20, title="Volume MA Length", minval=5, maxval=200, group="📊 Volume Panel")
col_volume_ma = input.color(color.blue, title="Volume MA Color", group="📊 Volume Panel")
show_thresholds = input.bool(true, title="Show Threshold Lines", group="📊 Volume Panel")
col_aboveavg = input.color(color.new(color.yellow, 50), title="Above-Average Threshold", group="📊 Volume Panel")
col_climax = input.color(color.new(color.red, 50), title="Climax Threshold", group="📊 Volume Panel")
// --- Alert Settings ---
show_vector_alerts = input.bool(true, title="Show Vector Candle Alerts", group="🔔 Alert Settings")
// ==========================================
// 🔹 CALCULATIONS - EMA/SMA/VWAP
// ==========================================
ema1 = ta.ema(close, len_ema1)
ema2 = ta.ema(close, len_ema2)
ema3 = ta.ema(close, len_ema3)
ema4 = ta.ema(close, len_ema4)
ema5 = ta.ema(close, len_ema5)
ema6 = ta.ema(close, len_ema6)
ema_c_fast = ta.ema(close, len_ema_cross_fast)
ema_c_slow = ta.ema(close, len_ema_cross_slow)
ema_golden = ta.crossover(ema_c_fast, ema_c_slow)
ema_death = ta.crossunder(ema_c_fast, ema_c_slow)
sma1 = ta.sma(close, len_sma1)
sma2 = ta.sma(close, len_sma2)
sma3 = ta.sma(close, len_sma3)
sma_c_fast = ta.sma(close, len_sma_cross_fast)
sma_c_slow = ta.sma(close, len_sma_cross_slow)
golden_cross = ta.crossover(sma_c_fast, sma_c_slow)
death_cross = ta.crossunder(sma_c_fast, sma_c_slow)
vwap_value = ta.vwap(close)
// ==========================================
// 🔹 CALCULATIONS - PVSR (single source of truth)
// ==========================================
bool hasVolume = not na(volume)
float volumeSpread = volume * (high - low)
// Canonical PVSRA measures both comparisons over the PREVIOUS vol_lookback
// bars. With the current bar inside its own window, a large bar drags its own
// average up and can fail the very test it should pass, and the spread test
// degenerates into "is this bar the highest of the last N including itself".
// The [1] offset excludes the current bar from both.
float averageVolume = pvsr_exclude_current ? ta.sma(volume, vol_lookback)[1] : ta.sma(volume, vol_lookback)
float highestVolSpread = pvsr_exclude_current ? ta.highest(volumeSpread, vol_lookback)[1] : ta.highest(volumeSpread, vol_lookback)
// Split out so each climax leg can be toggled and read independently. The
// spread leg fires roughly once every vol_lookback bars by construction, so it
// dominates the volume leg unless it is switched off.
bool climaxByVolume = hasVolume and not na(averageVolume) and volume >= climax_mult * averageVolume
bool climaxBySpread = hasVolume and not na(highestVolSpread) and pvsr_use_spread_leg and volumeSpread >= highestVolSpread
bool aboveAverage = hasVolume and not na(averageVolume) and volume >= aboveavg_mult * averageVolume
color pvsraColor = na
int pvsraCode = na
bool vectorAlert = false
if climaxByVolume or climaxBySpread
// Climax
pvsraColor := close > open ? green_vector_color : red_vector_color
pvsraCode := close > open ? 3 : -3
vectorAlert := true
else if aboveAverage
// Above average
pvsraColor := close > open ? blue_vector_color : violet_vector_color
pvsraCode := close > open ? 2 : -2
vectorAlert := true
else
// Regular (also the fallback when volume is unavailable, and on the first
// vol_lookback bars where the shifted average is not yet defined)
pvsraColor := close > open ? light_gray_candle_color : dark_grey_candle_color
pvsraCode := close > open ? 1 : -1
vectorAlert := false
// Volume panel derived series
float volume_ma = ta.sma(volume, volume_ma_length)
float avgAbove = averageVolume * aboveavg_mult
float avgClimax = averageVolume * climax_mult
// ==========================================
// 🔹 VECTOR ZONE MANAGEMENT
// Bullish vector -> demand zone (belowZones, support)
// Bearish vector -> supply zone (aboveZones, resistance)
// ==========================================
var box[] aboveZones = array.new_box() // supply
var box[] belowZones = array.new_box() // demand
int trans = zone_transparency
color cuColorBG = color.new(green_vector_color, trans) // climax up (demand)
color cdColorBG = color.new(red_vector_color, trans) // climax down (supply)
color auColorBG = color.new(blue_vector_color, trans) // above-avg up (demand)
color adColorBG = color.new(violet_vector_color, trans) // above-avg down (supply)
zoneBgColor(int code) =>
override_zone_color ? zone_override_color :
code == 3 ? cuColorBG :
code == 2 ? auColorBG :
code == -3 ? cdColorBG :
code == -2 ? adColorBG : na
zonesActive = master_show_zones and show_vector_zones
useWickExtent = zone_type == "Body with wicks"
useWickMitigate = zone_mitigation == "Wick"
// --- 1) Mitigate/clean existing zones FIRST (before any new box exists) ---
if zonesActive and array.size(aboveZones) > 0
for i = array.size(aboveZones) - 1 to 0
box b = array.get(aboveZones, i)
float bTop = box.get_top(b)
bool mitigated = useWickMitigate ? high > bTop : close > bTop
if mitigated
box.delete(b)
array.remove(aboveZones, i)
if zonesActive and array.size(belowZones) > 0
for i = array.size(belowZones) - 1 to 0
box b = array.get(belowZones, i)
float bBottom = box.get_bottom(b)
bool mitigated = useWickMitigate ? low < bBottom : close < bBottom
if mitigated
box.delete(b)
array.remove(belowZones, i)
// --- 2) Create a new zone for this bar's vector candle (if any) ---
if zonesActive and (pvsraCode <= -2 or pvsraCode >= 2)
float top = useWickExtent ? high : math.max(open, close)
float bottom = useWickExtent ? low : math.min(open, close)
color bg = zoneBgColor(pvsraCode)
box nb = box.new(left=bar_index, top=top, right=bar_index, bottom=bottom,
extend=extend.right, bgcolor=bg, border_width=zone_border_width,
border_color=color.new(color.white, 80), force_overlay=true)
if pvsraCode >= 2
array.unshift(belowZones, nb) // bullish -> demand
else
array.unshift(aboveZones, nb) // bearish -> supply
// --- 3) Enforce the per-side cap (drop the oldest = last element) ---
if array.size(aboveZones) > max_zones
box old = array.pop(aboveZones)
box.delete(old)
if array.size(belowZones) > max_zones
box old = array.pop(belowZones)
box.delete(old)
// ==========================================
// 🔹 PLOTTING - PRICE CHART (forced overlay)
// ==========================================
plot(master_show_ema and show_ema1 ? ema1 : na, title='EMA 1 (10)', color=col_ema1, linewidth=1, force_overlay=true)
plot(master_show_ema and show_ema2 ? ema2 : na, title='EMA 2 (21)', color=col_ema2, linewidth=1, force_overlay=true)
plot(master_show_ema and show_ema3 ? ema3 : na, title='EMA 3 (50)', color=col_ema3, linewidth=1, force_overlay=true)
plot(master_show_ema and show_ema4 ? ema4 : na, title='EMA 4 (100)', color=col_ema4, linewidth=1, force_overlay=true)
plot(master_show_ema and show_ema5 ? ema5 : na, title='EMA 5 (200)', color=col_ema5, linewidth=1, force_overlay=true)
plot(master_show_ema and show_ema6 ? ema6 : na, title='EMA 6 (800)', color=col_ema6, linewidth=1, force_overlay=true)
plot(master_show_sma and show_sma1 ? sma1 : na, title='SMA 1 (50)', color=col_sma1, linewidth=2, force_overlay=true)
plot(master_show_sma and show_sma2 ? sma2 : na, title='SMA 2 (100)', color=col_sma2, linewidth=2, force_overlay=true)
plot(master_show_sma and show_sma3 ? sma3 : na, title='SMA 3 (200)', color=col_sma3, linewidth=2, force_overlay=true)
plot(master_show_vwap and show_vwap ? vwap_value : na, title="VWAP", color=col_vwap, linewidth=wid_vwap, force_overlay=true)
candleColorToUse = master_show_pvsr and show_pvsr_candles ? pvsraColor : na
// No force_overlay here: barcolor() does not accept it, and does not need it.
// Unlike plot(), barcolor() always paints the main chart's bars regardless of
// which pane the indicator lives in.
barcolor(candleColorToUse)
plotshape(master_show_sma_cross and show_sma_cross_labels and golden_cross, title='SMA Golden Cross', style=shape.triangleup,
location=location.belowbar, color=color.green, text='SMA GOLD', textcolor=color.rgb(255, 255, 255), size=size.small, force_overlay=true)
plotshape(master_show_sma_cross and show_sma_cross_labels and death_cross, title='SMA Death Cross', style=shape.triangledown,
location=location.abovebar, color=color.red, text='SMA DEATH', textcolor=color.white, size=size.small, force_overlay=true)
plotshape(master_show_ema_cross and show_ema_cross_labels and ema_golden, title='EMA Golden Cross', style=shape.triangleup,
location=location.belowbar, color=color.green, text='EMA GOLD', textcolor=color.rgb(255, 255, 255), size=size.small, force_overlay=true)
plotshape(master_show_ema_cross and show_ema_cross_labels and ema_death, title='EMA Death Cross', style=shape.triangledown,
location=location.abovebar, color=color.red, text='EMA DEATH', textcolor=color.white, size=size.small, force_overlay=true)
// ==========================================
// 🔹 PLOTTING - VOLUME PANE (this indicator's own pane, NOT forced)
// ==========================================
plot(master_show_volume ? volume : na, title="PVSR Volume", color=pvsraColor, style=plot.style_columns, linewidth=2)
plot(master_show_volume and show_volume_ma ? volume_ma : na, title="Volume MA", color=col_volume_ma, linewidth=2)
plot(master_show_volume and show_thresholds ? avgAbove : na, title="Above-Avg Threshold", color=col_aboveavg, linewidth=1)
plot(master_show_volume and show_thresholds ? avgClimax : na, title="Climax Threshold", color=col_climax, linewidth=1)
// ==========================================
// 🔹 ALERTS
// ==========================================
alertcondition(ema_golden, title="EMA Golden Cross", message="EMA Bullish Cross")
alertcondition(ema_death, title="EMA Death Cross", message="EMA Bearish Cross")
alertcondition(golden_cross, title="SMA Golden Cross", message="SMA Bullish Cross")
alertcondition(death_cross, title="SMA Death Cross", message="SMA Bearish Cross")
alertcondition(ta.cross(close, vwap_value), title="Price Cross VWAP", message="Price crossed VWAP")
alertcondition(show_vector_alerts and vectorAlert and pvsraCode >= 2, title="Bullish Vector Candle", message="Bullish Vector Candle")
alertcondition(show_vector_alerts and vectorAlert and pvsraCode <= -2, title="Bearish Vector Candle", message="Bearish Vector Candle")
Show all 327 lines