Skip to content

[Indicator] Volume-Gated Trend Ribbon #10

@MDUYN

Description

@MDUYN

Indicator Name

Volume-Gated Trend Ribbon

Category

Trend

Description

The Volume-Gated Trend Ribbon employs a selective price-updating mechanism that filters market noise through volume validation, creating a trend-following system that responds exclusively to significant price movements. The indicator gates price updates to moving average calculations based on volume threshold crossovers, ensuring that only bars with significant participation influence the trend direction. By interpolating between fast and slow moving averages to create a multi-layered visual ribbon, the indicator provides traders and investors with an adaptive trend identification framework that distinguishes between volume-backed directional shifts and low-conviction price fluctuations across multiple timeframes and asset classes.

Reference Chart(s)

Image

Chart Description

  • See reference chart

Parameters

  • period (int, default=14):
  • source_column (str, default='Close'):

Source / Reference

// 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(title = 'Volume-Gated Trend Ribbon [QuantAlgo]', overlay = true)

// ╔════════════════════════════════╗ //
// ║ USER-DEFINED SETTINGS ║ //
// ╚════════════════════════════════╝ //

var string vol_settings = '════════ Volume Configuration ════════'
var string ma_settings = '════════ Moving Average Settings ════════'
var string visual_settings = '════════ Visualization Settings ════════'

tooltip_vol_mult = 'Multiplier applied to average volume to identify significant volume bars. Lower values (0.5-0.8) include more bars as "significant," making the indicator more reactive but potentially noisier. Higher values (1.2-2.0) filter for only high-volume bars, creating smoother trends but slower adaptation.'
tooltip_vol_period = 'Lookback period for calculating average volume baseline. Shorter periods (20-30) adapt quickly to recent volume patterns, useful for volatile markets. Longer periods (50-100) provide a more stable baseline, better for consistent markets. Choose based on your typical holding period.'
tooltip_ma_type = 'Type of moving average used for trend calculation. EMA/DEMA/TEMA: Exponential variants with increasing responsiveness. HMA: Hull MA minimizes lag while maintaining smoothness. KAMA: Adapts speed based on market efficiency. VWMA: Incorporates volume weighting. T3/VIDYA: Advanced adaptive algorithms for reduced whipsaws.'
tooltip_fast_len = 'Period for the fast (inner) moving average. Controls sensitivity to recent price changes. Lower values (8-12) create a more responsive ribbon that catches trends early but may generate false signals. Higher values (15-25) provide more confirmation but slower entries.'
tooltip_slow_len = 'Period for the slow (outer) moving average. Defines the trend baseline. Should be 1.5-3x the fast length. Lower values create a tighter ribbon with more frequent crossovers. Higher values create wider ribbons with stronger trend confirmation.'
tooltip_preset = 'Select a predefined configuration optimized for different trading styles and market conditions.'
tooltip_preset_details = 'Default (1.0, 50, 15/30): Balanced configuration suitable for most timeframes and general trading applications. Provides moderate responsiveness with good noise filtering. Optimal for swing trading and daily analysis.\n\nFast Response (0.8, 20, 10/20): Aggressive setup designed for active intraday trading and scalping. Lower volume threshold and shorter periods capture rapid price changes. Best for 1-15min charts where quick entries are critical. Expect more signals but also more false positives in ranging markets.\n\nSmooth Trend (1.2, 75, 20/40): Conservative configuration for position trading and swing trading. Higher volume filter and extended periods provide robust trend identification. Ideal for 4h-daily charts where trend persistence matters more than timing precision. Significantly reduces whipsaws but may lag major reversals.'
tooltip_bar_coloring = 'Enable/disable coloring of price bars on the main chart based on the current trend direction. When enabled, bars are colored using the bullish color during uptrends and bearish color during downtrends. This provides an immediate visual reference of the trend direction directly on the price chart without needing to analyze the trend line position.'
tooltip_color_preset = 'Pre-configured color schemes optimized for different chart themes and visual preferences. Each preset maintains strong contrast for instant trend identification.'
tooltip_bull_color = 'Color for bullish/upward trend signals. Represents market conditions where price is above the adaptive trend line and momentum is positive. Choose colors with high contrast against your chart background for optimal visibility. This color is used for the trend band during uptrends and bar coloring when enabled.'
tooltip_bear_color = 'Color for bearish/downward trend signals. Represents market conditions where price is below the adaptive trend line and momentum is negative. Ensure strong contrast with both chart background and bullish color. This color is used for the trend band during downtrends and bar coloring when enabled.'
tooltip_fill_opacity = 'Transparency level for the trend band fill. Lower values (20-40) create a more solid, prominent band that clearly highlights trend areas. Higher values (70-95) create a subtle background highlight that doesn't obscure price action.'

volMult = input.float(1.0, 'Volume Multiplier', minval = 0.5, maxval = 3.0, step = 0.1, group = vol_settings, tooltip = tooltip_vol_mult)
volPeriod = input.int(50, 'Volume Period', minval = 1, group = vol_settings, tooltip = tooltip_vol_period)
maType = input.string('EMA', 'MA Type', options = ['SMA', 'EMA', 'WMA', 'RMA', 'HMA', 'VWMA', 'DEMA', 'TEMA', 'ALMA', 'LSMA', 'SMMA', 'KAMA', 'ZLEMA', 'T3', 'VIDYA'], group = ma_settings, tooltip = tooltip_ma_type)
fastLen = input.int(15, 'Fast Length', minval = 1, group = ma_settings, tooltip = tooltip_fast_len)
slowLen = input.int(30, 'Slow Length', minval = 1, group = ma_settings, tooltip = tooltip_slow_len)

presetConfig = input.string('Default', 'Preset Configuration', options = ['Default', 'Fast Response', 'Smooth Trend'], group = ma_settings, tooltip = tooltip_preset + '\n\n' + tooltip_preset_details)

if presetConfig == 'Fast Response'
volMult := 0.8
volPeriod := 20
fastLen := 10
slowLen := 20
slowLen
else if presetConfig == 'Smooth Trend'
volMult := 1.2
volPeriod := 75
fastLen := 20
slowLen := 40
slowLen

enableBarColor = input.bool(false, 'Enable Bar Coloring', group = visual_settings, tooltip = tooltip_bar_coloring)
colorPreset = input.string('Custom', 'Color Preset', options = ['Classic', 'Aqua', 'Cosmic', 'Ember', 'Neon', 'Custom'], group = visual_settings, tooltip = tooltip_color_preset)
bullColorInput = input.color(#00ffaa, 'Bullish Color', group = visual_settings, tooltip = tooltip_bull_color)
bearColorInput = input.color(#ff0000, 'Bearish Color', group = visual_settings, tooltip = tooltip_bear_color)
fillOpacity = input.int(100, 'Fill Opacity', minval = 0, maxval = 100, group = visual_settings, tooltip = tooltip_fill_opacity)

[bullColor, bearColor] = switch colorPreset
'Classic' => [#00ff00, #ff0000]
'Aqua' => [#00bfff, #ff8c00]
'Cosmic' => [#49ffce, #9932cc]
'Ember' => [#ff6600, #00cccc]
'Neon' => [#ffff00, #ff00ff]
'Custom' => [bullColorInput, bearColorInput]

fastPeriod = maType == 'HMA' ? math.max(fastLen, 4) : fastLen
slowPeriod = math.max(slowLen, fastPeriod + 1)

// ╔════════════════════════════════╗ //
// ║ MA CALCULATION FUNCTIONS ║ //
// ╚════════════════════════════════╝ //

dema(src, len) =>
e1 = ta.ema(src, len)
e2 = ta.ema(e1, len)
2 * e1 - e2

tema(src, len) =>
e1 = ta.ema(src, len)
e2 = ta.ema(e1, len)
e3 = ta.ema(e2, len)
3 * (e1 - e2) + e3

smma(src, len) =>
var float result = na
result := na(result[1]) ? ta.sma(src, len) : (result[1] * (len - 1) + src) / len
result

lsma(src, len) =>
ta.linreg(src, len, 0)

kama(src, len) =>
change = math.abs(src - src[len])
vol = math.sum(math.abs(src - src[1]), len)
er = vol != 0 ? change / vol : 0
fastSC = 2.0 / 3
slowSC = 2.0 / 31
sc = math.pow(er * (fastSC - slowSC) + slowSC, 2)
var float result = na
result := na(result[1]) ? src : result[1] + sc * (src - result[1])
result

zlema(src, len) =>
lag = (len - 1) / 2
ta.ema(src + (src - src[lag]), len)

gd(src, len, factor) =>
e1 = ta.ema(src, len)
e2 = ta.ema(e1, len)
e1 * (1 + factor) - e2 * factor

t3(src, len) =>
gd(gd(gd(src, len, 0.7), len, 0.7), len, 0.7)

vidya(src, len) =>
mom = ta.change(src)
upSum = math.sum(math.max(mom, 0), len)
dnSum = math.sum(math.max(-mom, 0), len)
cmo = (upSum + dnSum) != 0 ? math.abs((upSum - dnSum) / (upSum + dnSum)) : 0
alpha = 2.0 / (len + 1)
var float result = na
result := na(result[1]) ? src : src * alpha * cmo + result[1] * (1 - alpha * cmo)
result

alma(src, len) =>
ta.alma(src, len, 0.85, 6)

ma(src, len) =>
switch maType
'SMA' => ta.sma(src, len)
'EMA' => ta.ema(src, len)
'WMA' => ta.wma(src, len)
'RMA' => ta.rma(src, len)
'HMA' => ta.hma(src, len)
'VWMA' => ta.vwma(src, len)
'DEMA' => dema(src, len)
'TEMA' => tema(src, len)
'ALMA' => alma(src, len)
'LSMA' => lsma(src, len)
'SMMA' => smma(src, len)
'KAMA' => kama(src, len)
'ZLEMA' => zlema(src, len)
'T3' => t3(src, len)
'VIDYA' => vidya(src, len)
=> ta.ema(src, len)

volMA(gatedSrc, rawSrc, len) =>
maType == 'VWMA' ? ta.vwma(rawSrc, len) : ma(gatedSrc, len)

// ╔════════════════════════════════╗ //
// ║ VOLUME ANALYSIS ║ //
// ╚════════════════════════════════╝ //

avgVol = ta.sma(volume, volPeriod)
highVol = volume >= avgVol * volMult

var float gatedClose = close
if highVol
gatedClose := close

// ╔════════════════════════════════╗ //
// ║ TREND DETECTION ║ //
// ╚════════════════════════════════╝ //

fastMA = volMA(gatedClose, close, fastPeriod)
slowMA = volMA(gatedClose, close, slowPeriod)
midFastMA = fastMA * 0.67 + slowMA * 0.33
midSlowMA = fastMA * 0.33 + slowMA * 0.67

bullish = fastMA > slowMA

var int trendState = 0
if bullish
trendState := 1
if not bullish
trendState := -1

longSignal = trendState == 1 and trendState[1] != 1
shortSignal = trendState == -1 and trendState[1] != -1

trendCol = bullish ? bullColor : bearColor

// ╔════════════════════════════════╗ //
// ║ VISUALIZATION ║ //
// ╚════════════════════════════════╝ //

outerOp = 100 - math.round(fillOpacity * 0.8)
innerOp = 100 - math.round(fillOpacity * 0.6)
coreOp = 100 - math.round(fillOpacity * 0.4)

pFast = plot(fastMA, title = 'Fast MA', color = color.new(trendCol, 80), linewidth = 1)
pMidFast = plot(midFastMA, title = 'Mid-Fast MA', color = color.new(trendCol, 80), linewidth = 1)
pMidSlow = plot(midSlowMA, title = 'Mid-Slow MA', color = color.new(trendCol, 80), linewidth = 1)
pSlow = plot(slowMA, title = 'Slow MA', color = color.new(trendCol, 80), linewidth = 1)

fill(pMidSlow, pSlow, color = color.new(trendCol, coreOp), title = 'Core Fill')
fill(pMidFast, pMidSlow, color = color.new(trendCol, innerOp), title = 'Inner Fill')
fill(pFast, pMidFast, color = color.new(trendCol, outerOp), title = 'Outer Fill')

plot(fastMA, title = 'Fast Edge', color = color.new(trendCol, 0), linewidth = 2, style = plot.style_line)
plot(slowMA, title = 'Slow Edge', color = color.new(trendCol, 0), linewidth = 2, style = plot.style_line)

barcolor(enableBarColor ? trendCol : na, title = 'Trend Bar Color')

// ╔════════════════════════════════╗ //
// ║ ALERTS ║ //
// ╚════════════════════════════════╝ //

alertcondition(longSignal, title = 'Bullish Trend Signal', message = 'Volume-Gated Ribbon: Trend changed to BULLISH {{exchange}}:{{ticker}} - {{interval}}')
alertcondition(shortSignal, title = 'Bearish Trend Signal', message = 'Volume-Gated Ribbon: Trend changed to BEARISH {{exchange}}:{{ticker}} - {{interval}}')
alertcondition(longSignal or shortSignal, title = 'Trend Change', message = 'Volume-Gated Ribbon: Trend direction changed - Check chart {{exchange}}:{{ticker}} - {{interval}}')

// ╔════════════════════════════════╗ //
// ║ CREATED BY ║ //
// ╚════════════════════════════════╝ //

// ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗ █████╗ ██╗ ██████╗ ██████╗
//██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝ ██╔══██╗██║ ██╔════╝ ██╔═══██╗
//██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ███████║██║ ██║ ███╗██║ ██║
//██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ██╔══██║██║ ██║ ██║██║ ██║
//╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ██║ ██║███████╗╚██████╔╝╚██████╔╝
// ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝

Expected Output Columns

No response

Deliverables Checklist

  • Implementation in pyindicators/indicators/ (pandas + polars support)
  • Exports in __init__.py and __all__
  • Unit tests in tests/indicators/ (pandas, polars, edge cases)
  • Documentation page in docs/content/indicators/ with chart image
  • Sidebar registration in docs/sidebars.js
  • Entry in README.md features list
  • Analysis notebook in analysis/indicators/ with plotly chart

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    squadSquad triage inbox — Lead will assign to a member

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions