Shared Chart Link: http://tos.mx/!YAXsXIxY MUST follow these instructions for loading shared links.
Case Study: I want you to notice the little yellow Bullish Exhaustion bubble. The volume there was decreasing 3 bars in a row. If you look up at the price chart the price was increasing over the same 3 bars. This divergence triggered the bubble, telling us the volume wasnt keeping up with the price
Okay guys, this is a work in progress but I felt there was enough here to post about it. This is my volume pattern recognizer. It uses the idea of volume pressure from this post here with a bit of a twist and some pattern seeking behaviors.
First of all note the different colors our histogram can take:
Next note the line. That line is actually a twist on the Chaikin Money Flow Line that works like this:
Next you have our bubbles for our context pattern scenario analysis:
I hope you enjoy. Happy Trading!

Case Study: I want you to notice the little yellow Bullish Exhaustion bubble. The volume there was decreasing 3 bars in a row. If you look up at the price chart the price was increasing over the same 3 bars. This divergence triggered the bubble, telling us the volume wasnt keeping up with the price
Okay guys, this is a work in progress but I felt there was enough here to post about it. This is my volume pattern recognizer. It uses the idea of volume pressure from this post here with a bit of a twist and some pattern seeking behaviors.
First of all note the different colors our histogram can take:
- Black : This means that the trading volume was not significant AND the candle body sized was less than half of the ATR
- This helps us ignore some data points and help us to focus on volume that matters. This is an adjustable paramater
- Cyan: This means that volume was significant but the body size was small, this is a could indicator of a possible pivot point. Notice the image above
- Green/Red: If the price action exceeded our body size requirements then you will see our best estimate of buyers vs sellers
Next note the line. That line is actually a twist on the Chaikin Money Flow Line that works like this:
- If CMF > .1 (indicating strong positive money flow) then the CMF line will be above the volume
- If the slope of CMF is positive then the line will be green, otherwise red
Next you have our bubbles for our context pattern scenario analysis:
- Bearish Exhaustion: If price is decreasing but volume is also decreasing
- Bullish Exhaustion: If Price is increasing but volume is decreasing
- Bear Rev + Vol Surge: Bearish reversal with volume increase-decrease-increase
- Bear Rev + Rising Vol: Bearish reversal with increasing volume
- (The same for the bullish side)
- ... I plan on adding more patterns later
I hope you enjoy. Happy Trading!
Code:
# === 3-Bar Price / Volume Pattern Detector ===
# Shows buying/selling pressure with separate volume histograms
# Patterns shown with bubbles only
########################################
# ========= USER INPUTS ===============
########################################
input showPlot = yes; # master on/off
input showBubbles = yes; # show pattern bubbles
input lineWeight = 3;
input minBarsNeeded = 4; # Minimum bars needed for patterns
input atr_length = 14; # ATR period length
input BodyToATR_Threshold = 0.5; # Body must be this % of ATR to color by control
input volZScoreThres = 1.2; # Z-score threshold for volume significance
input volLookback = 20; # Lookback period for volume z-score calculation
input volDeclineThreshold = 0.95; # Volume must decline to this % of bar[2] for exhaustion signals
input volIncreaseThreshold = 1.05; # Volume must increase by this % between bars for surge signals
########################################
# ======== PATTERN DETECTION ==========
########################################
# Check if we have enough bars for pattern detection
def hasEnoughBars = BarNumber() >= minBarsNeeded;
# --- Price Patterns ---
# Compare bar 0 vs bar 1, bar 1 vs bar 2, bar 2 vs bar 3:
def lh_ll_0 = if hasEnoughBars then high < high[1] and low < low[1] else no;
def hh_hl_0 = if hasEnoughBars then high > high[1] and low > low[1] else no;
def lh_ll_1 = if hasEnoughBars then high[1] < high[2] and low[1] < low[2] else no;
def hh_hl_1 = if hasEnoughBars then high[1] > high[2] and low[1] > low[2] else no;
def lh_ll_2 = if hasEnoughBars then high[2] < high[3] and low[2] < low[3] else no;
def hh_hl_2 = if hasEnoughBars then high[2] > high[3] and low[2] > low[3] else no;
# "2-down then 1-up" reversal
def priceRevUp = lh_ll_2 and lh_ll_1 and hh_hl_0;
# "2-up then 1-down" reversal
def priceRevDn = hh_hl_2 and hh_hl_1 and lh_ll_0;
# Price trend patterns (for divergence detection)
def priceUp3 = if hasEnoughBars then close > close[1] and close[1] > close[2] else no;
def priceDn3 = if hasEnoughBars then close < close[1] and close[1] < close[2] else no;
########################################
# ========== VOLUME PATTERNS ==========
########################################
# Three‐bar volume ↑↑↑ with percentage threshold
def volInc3 = if hasEnoughBars then
volume > volume[1] * volIncreaseThreshold and
volume[1] > volume[2] * volIncreaseThreshold and
(volume / volume[2]) > volIncreaseThreshold * volIncreaseThreshold # Ensure significant increase
else no;
# Three‐bar volume ↓↓↓ with percentage threshold
def volDec3 = if hasEnoughBars then
volume < volume[1] * volDeclineThreshold and
volume[1] < volume[2] * volDeclineThreshold and
(volume / volume[2]) < volDeclineThreshold * volDeclineThreshold # Ensure significant decline
else no;
# Three‐bar volume ↑↓↑
def volIncDecInc = if hasEnoughBars then
volume > volume[1] * volIncreaseThreshold and
volume[1] < volume[2] * volDeclineThreshold
else no;
########################################
# ========== VOLUME Z-SCORE ===========
########################################
# Calculate volume Z-score (how many standard deviations from mean)
def volAvg = Average(volume, volLookback);
def volSD = StDev(volume, volLookback);
def volZ = if volSD != 0 then (volume - volAvg) / volSD else 0;
# Define volume significance conditions
def highVolume = volZ > volZScoreThres;
def lowVolume = volZ <= volZScoreThres;
########################################
# ========== BUYER/SELLER CONTROL ====
########################################
# Calculate ATR
def atr = Average(TrueRange(high, close, low), atr_length);
# Calculate candle body and compare to ATR
def body = AbsValue(close - open);
def bodyToATR = body / atr;
def significantBody = bodyToATR > BodyToATR_Threshold;
def insignificantBody = !significantBody;
# Check for significant bodies in previous candles
def body_1 = AbsValue(close[1] - open[1]);
def bodyToATR_1 = body_1 / atr;
def significantBody_1 = bodyToATR_1 > BodyToATR_Threshold;
def body_2 = AbsValue(close[2] - open[2]);
def bodyToATR_2 = body_2 / atr;
def significantBody_2 = bodyToATR_2 > BodyToATR_Threshold;
# Calculate buyer/seller control percentages
def totalRange = high - low;
def buyerControl = if totalRange != 0 then (close - low) / totalRange else 0.5;
def sellerControl = 1 - buyerControl;
# Calculate buying and selling volume components
def buyingVolume = volume * buyerControl;
def sellingVolume = volume * sellerControl;
########################################
# ============ PATTERN LOGIC ==========
########################################
# Basic price + volume patterns
def condRevUp_IncVol = priceRevUp and volInc3 and significantBody_1 and significantBody_2;
def condRevUp_IncDecInc = priceRevUp and volIncDecInc and significantBody_1 and significantBody_2;
def condRevDn_IncVol = priceRevDn and volInc3 and significantBody_1 and significantBody_2;
def condRevDn_IncDecInc = priceRevDn and volIncDecInc and significantBody_1 and significantBody_2;
# Divergence patterns with significant body requirement
def bullishExhaustion = priceUp3 and volDec3 and significantBody_1 and significantBody_2;
def bearishExhaustion = priceDn3 and volDec3 and significantBody_1 and significantBody_2;
########################################
# ============= LABELS ===============
########################################
# Add informative context labels
AddLabel(yes, "Volume Z-Score: " + Round(volZ, 1),
if highVolume then Color.CYAN else if lowVolume then Color.DARK_GRAY else Color.WHITE);
AddLabel(yes, "Body/ATR: " + Round(bodyToATR, 2),
if significantBody then Color.WHITE else Color.GRAY);
AddLabel(yes, "Buyer Control: " + Round(buyerControl * 100, 0) + "%",
if buyerControl > 0.7 then Color.GREEN else if buyerControl < 0.3 then Color.RED else Color.WHITE);
# Volume trend labels
AddLabel(volInc3, "Volume Surging", Color.CYAN);
AddLabel(volDec3, "Volume Fading", Color.DARK_GRAY);
AddLabel(volIncDecInc, "Volume Spike", Color.MAGENTA);
########################################
# ============== BUBBLES ==============
########################################
# Add bubbles at the bottom/top of the chart for pattern detection
def bubbleY = if !showBubbles then Double.NaN else (low - (0.05 * (high - low)));
def bubbleYTop = if !showBubbles then Double.NaN else (high + (0.05 * (high - low)));
# Bullish reversal with increasing volume
AddChartBubble(
condRevUp_IncVol,
bubbleY,
"Bull Rev + Rising Vol",
Color.CYAN,
no
);
# Bullish reversal with volume increase-decrease-increase
AddChartBubble(
condRevUp_IncDecInc,
bubbleY,
"Bull Rev + Vol Surge",
Color.GREEN,
no
);
# Bearish reversal with increasing volume
AddChartBubble(
condRevDn_IncVol,
bubbleYTop,
"Bear Rev + Rising Vol",
Color.MAGENTA,
yes
);
# Bearish reversal with volume increase-decrease-increase
AddChartBubble(
condRevDn_IncDecInc,
bubbleYTop,
"Bear Rev + Vol Surge",
Color.ORANGE,
yes
);
# Bullish exhaustion (divergence - price up but volume declining)
AddChartBubble(
bullishExhaustion,
bubbleYTop,
"Bullish Exhaustion",
Color.YELLOW,
yes
);
# Bearish exhaustion (divergence - price down but volume declining)
AddChartBubble(
bearishExhaustion,
bubbleY,
"Bearish Exhaustion",
CreateColor(255, 165, 0), # Orange
no
);
########################################
# =========== TWISTED CMF =============
########################################
# Chaikin Money Flow calculation
def mfMultiplier = if high != low then ((close - low) - (high - close)) / (high - low) else 0;
def mfVolume = mfMultiplier * volume;
def cmf = Average(mfVolume, 20) / Average(volume, 20);
def cmfSlope = cmf - cmf[1];
# Add CMF value label
AddLabel(yes, "CMF: " + Round(cmf, 2),
if cmf > 0.1 then (if cmfSlope >= 0 then Color.GREEN else Color.RED)
else Color.DARK_GRAY);
# Twisted CMF line - shows at volume level when CMF > 0.1, otherwise at zero
plot CMFLine = if cmf > 0.1 then volume else 0;
CMFLine.AssignValueColor(if cmfSlope >= 0 then Color.GREEN else Color.RED);
CMFLine.SetPaintingStrategy(PaintingStrategy.LINE);
CMFLine.SetLineWeight(2);
########################################
# ========= VOLUME HISTOGRAMS =========
########################################
# Selling volume histogram
plot SV = if showPlot and hasEnoughBars then sellingVolume else Double.NaN;
SV.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
SV.SetLineWeight(lineWeight);
SV.AssignValueColor(
# First check body significance, then volume levels for all cases
if insignificantBody then
if highVolume then Color.CYAN else Color.DARK_GRAY
else # Significant body
if sellerControl >= 0.9 then Color.DARK_RED
else if sellerControl >= 0.8 then CreateColor(180, 0, 0)
else if sellerControl >= 0.7 then CreateColor(160, 0, 0)
else if sellerControl >= 0.6 then CreateColor(140, 0, 0)
else CreateColor(120, 0, 0)
);
# Buying volume histogram
plot BV = if showPlot and hasEnoughBars then buyingVolume else Double.NaN;
BV.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
BV.SetLineWeight(lineWeight);
BV.AssignValueColor(
# First check body significance, then volume levels for all cases
if insignificantBody then
if highVolume then Color.CYAN else Color.DARK_GRAY
else # Significant body
if buyerControl >= 0.9 then Color.DARK_GREEN
else if buyerControl >= 0.8 then CreateColor(0, 180, 0)
else if buyerControl >= 0.7 then CreateColor(0, 160, 0)
else if buyerControl >= 0.6 then CreateColor(0, 140, 0)
else CreateColor(0, 120, 0)
);
########################################
# =========== VWAP COMPARISON =========
########################################
# Calculate VWAP
def vwap = VWAP();
# Calculate distance from close to VWAP
def closeToVWAP = close - vwap;
def closeToVWAP_Normalized = AbsValue(closeToVWAP) / atr;
# Determine position relative to VWAP using same body threshold
def aboveVWAP_Significant = closeToVWAP > 0 and closeToVWAP_Normalized > BodyToATR_Threshold;
def belowVWAP_Significant = closeToVWAP < 0 and closeToVWAP_Normalized > BodyToATR_Threshold;
def nearVWAP = !aboveVWAP_Significant and !belowVWAP_Significant;
# Prepare label text
def vwapPct = (closeToVWAP / vwap) * 100;
AddLabel(yes,
"VWAP: " + if aboveVWAP_Significant then
"+$" + Round(AbsValue(closeToVWAP), 2) + " (" + Round(AbsValue(vwapPct), 1) + "%)" + " Above"
else if belowVWAP_Significant then
"-$" + Round(AbsValue(closeToVWAP), 2) + " (" + Round(AbsValue(vwapPct), 1) + "%)" + " Below"
else
"Near ±" + Round(BodyToATR_Threshold * atr, 2),
if aboveVWAP_Significant then Color.GREEN
else if belowVWAP_Significant then Color.RED
else Color.GRAY
);