TradingView indicator guides

Volume Trend Tracker (VTT): Vortex Formula and Color Rules

Pineify labels this component Volume Trend Tracker, but its calculation uses high, low and close—not trading volume. It smooths two Vortex ratios and plots their absolute difference. Four named conditions choose the color, with a white fallback when none matches. A “strong” or “weak” label describes those conditions, not a measured trading advantage.

Inspect the Pine Script

Resolve the name and the calculation

The product configuration links to Vortex Trend Tracker by ProfitProgrammers, published July 3, 2019. That publication describes EMA smoothing of Vortex values and a difference histogram. This page documents the exact Pineify helper below; it does not assert that every later script with the same abbreviation behaves identically.

For each chart bar, positive movement is abs(high − previous low), and negative movement is abs(low − previous high). Sum each over N bars and divide by the same N-bar sum of true range. Here ta.atr(1) supplies the one-bar true range. It includes gaps through the previous close; neither movement expression reads volume.

Then P = EMA(positive ratio, M), Q = EMA(negative ratio, M), and histogram height = abs(P − Q). The fixed call uses N = 30 and M = 10. The height is nonnegative whenever both values are available. A red histogram is not a negative plotted value, and these ratios are not restricted to the interval 0 to 1.

Verify the ratios before interpreting colors

Consider four hypothetical high/low/close bars: (10, 8, 9), (11, 9, 10), (12, 10, 11), and (10, 7, 8). For bars 2 through 4, the positive movements are 3, 3, 0; negative movements are 1, 1, 5; and true ranges are 2, 2, 4.

With N = 2, bar 3 has ratios 6/4 = 1.5 and 2/4 = 0.5. On bar 4 they become 3/6 = 0.5 and 6/6 = 1. This directly disproves a 0–1 upper bound. With M = 2 and the first valid ratios used as EMA seeds, bar 4 gives P = (2/3 × 0.5) + (1/3 × 1.5) = 5/6 and Q = (2/3 × 1) + (1/3 × 0.5) = 5/6.

At that tie, Q has risen, so the literal strong-down condition is true although the histogram height is zero. This calculation illustrates the equality rule; it is not a forecast. The retained script has no explicit complete-window or zero-denominator guard, so inspect startup and missing-data behavior separately instead of assuming every dataset follows the complete-data fixture.

The exact color classification

ColorHelper conditionWhat it checks
Dark greenP > Q and P ≥ previous PP leads and is rising or exactly unchanged.
Light greenP > Q and P < previous PP leads but has declined.
Dark redQ ≥ P and Q > previous QQ rises and leads or ties.
Light redQ > P and Q < previous QQ leads but has declined.
White fallbackNone of the aboveIncludes some flat or equal-value cases.

The inequalities are asymmetric. If Q exceeds P but Q is exactly unchanged, neither red condition is true: the bar is white. If P exceeds Q and P is unchanged, it is dark green. If P equals Q and Q is not rising, no named condition is true. Unavailable comparisons also do not establish a named state. White is the code’s fallback color, not a separately validated “neutral market” signal, and may be difficult to see against a white chart.

The helper returns both EMA series, the difference and four Boolean outputs, but this pasted script plots only the histogram. Its settings do not contain switches for plotting the two underlying lines.

Color and height answer different questions

A label checks the dominant line’s slope, not the slope of the difference. In a hypothetical state transition, P falls from 1.5 to 1.4 while Q falls from 1.2 to 1.0. The color is light green, but the height grows from 0.3 to 0.4. Conversely, P rising from 1.2 to 1.3 while Q rises from 0.5 to 1.2 produces dark green with a shrinking height of 0.7 to 0.1.

These classifier examples are assumed EMA values, not claims that a particular market generated them. They show why “strong” cannot be substituted for “histogram expanding,” and “weak” cannot be substituted for “reversal next.” A short height means the two smoothed ratios are close; it does not by itself establish low volatility, low volume or a directionless price path.

Two datasets with the same OHLC and different volumes produce the same VTT. Under valid finite data, multiplying all prices by a positive constant scales both directional distances and true range equally, leaving the ratios unchanged. Adding a constant to all prices also leaves those differences unchanged. The output is not a price target or a percentage return.

Apply timing and data checks before a strategy

The script calls the helper with fixed lengths 30 and 10 and contains no input functions, alerts, orders, pivot-divergence rules or bar-close gate. Current high, low and close can change during a live bar, so both color and height can change. Unused time-range helpers do not create session filtering or a multi-timeframe calculation.

The shared denominator can be zero in flat data, and the example does not explicitly reject missing OHLC windows. More smoothing changes the recursive response but does not establish a fixed two-bar delay or a guaranteed reduction in false signals. No universal minimum chart timeframe, win rate or optimal settings are demonstrated here.

If you test a color transition as a filter, decide whether white interrupts the sequence, whether equality counts as a reversal, and whether an entry requires a newly changed state or may recur on every qualifying bar. Require the intended confirmation time, then specify price rules, exits, sizing and costs separately. The indicator supplies none of those execution decisions.

Compare the unsmoothed construction in the Vortex Indicator guide and the platform’s live-bar behavior. An extra EMA or price filter still depends on related market data; it is not independent evidence of future profit.

Pine Script v6: implementation and limits

The full calculation and its original color rules are retained for inspection. The helper returns seven values; the single plot displays only the absolute difference with conditional coloring. Startup, zero-range behavior, equality and live-bar limitations are part of this implementation rather than silently corrected assumptions.

Pine Script v6
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify

//======================================================================//
//                    ____  _            _  __                          //
//                   |  _ (_)_ __   ___(_)/ _|_   _                    //
//                   | |_) | | '_ \ / _ \ | |_| | | |                   //
//                   |  __/| | | | |  __/ |  _| |_| |                   //
//                   |_|   |_|_| |_|\___|_|_|  \__, |                   //
//                                             |___/                    //
//======================================================================//

//@version=6
indicator(title="Volume Trend Tracker", overlay=false, max_labels_count=500)

//#region —————————————————————————————————————————————————— Custom Code

//#endregion ————————————————————————————————————————————————————————————————————

//#region —————————————————————————————————————————————————— Common Dependence

p_comm_time_range_to_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int start_unix_time = na
    int end_unix_time = na
    int start_time_hour = na
    int start_time_minute = na
    int end_time_hour = na
    int end_time_minute = na
    if str.length(time_range) == 11
        // Format: hh:mm-hh:mm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 3, 5)))
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 6, 8)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 9, 11)))
    else if str.length(time_range) == 9
        // Format: hhmm-hhmm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 2, 4)))
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 5, 7)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 7, 9)))
    start_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), start_time_hour, start_time_minute, 0)
    end_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), end_time_hour, end_time_minute, 0)
    [start_unix_time, end_unix_time]

p_comm_time_range_to_start_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int start_time_hour = na
    int start_time_minute = na
    if str.length(time_range) == 11
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 3, 5)))
    else if str.length(time_range) == 9
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 2, 4)))
    timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), start_time_hour, start_time_minute, 0)

p_comm_time_range_to_end_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int end_time_hour = na
    int end_time_minute = na
    if str.length(time_range) == 11
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 6, 8)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 9, 11)))
    else if str.length(time_range) == 9
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 5, 7)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 7, 9)))
    timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), end_time_hour, end_time_minute, 0)

p_comm_timeframe_to_seconds(simple string tf) =>
    float seconds = 0
    tf_lower = str.lower(tf)
    value = str.tonumber(str.substring(tf_lower, 0, str.length(tf_lower) - 1))
    if str.endswith(tf_lower, 's')
        seconds := value
    else if str.endswith(tf_lower, 'd')
        seconds := value * 86400
    else if str.endswith(tf_lower, 'w')
        seconds := value * 604800
    else if str.endswith(tf_lower, 'm')
        seconds := value * 2592000
    else
        seconds := str.tonumber(tf_lower) * 60
    seconds

p_custom_sources() =>
    [open, high, low, close, volume]

//#endregion ———————————————————————————————————————————————————————————————————

//#region —————————————————————————————————————————————————— Ta Dependence

p_ta_vortex(posSum, negSum, vLen) =>
    tRange = math.sum(ta.atr(1), vLen)
    vmPlus = posSum / tRange
    vmNeg = negSum / tRange
    [vmPlus, vmNeg]

p_ta_vtt(simple int length, simple int ema_length) =>
    [vPlus, vNeg] = p_ta_vortex(math.sum(math.abs(high - low[1]), length), math.sum(math.abs(low - high[1]), length), length)
    plusEMA = ta.ema(vPlus, ema_length)
    negEMA = ta.ema(vNeg, ema_length)
    vDiff = math.abs(plusEMA - negEMA)
    is_strong_uptrend = plusEMA > negEMA and plusEMA >= plusEMA[1]
    is_weak_uptrend = plusEMA > negEMA and plusEMA < plusEMA[1]
    is_strong_downtrend = negEMA >= plusEMA and negEMA > negEMA[1]
    is_weak_downtrend = negEMA > plusEMA and negEMA < negEMA[1]
    [plusEMA, negEMA, vDiff, is_strong_uptrend, is_weak_uptrend, is_strong_downtrend, is_weak_downtrend]

//#endregion —————————————————————————————————————————————————————————————

//#region —————————————————————————————————————————————————— Constants

// Input Groups
string P_GP_1      =      ""

//#endregion —————————————————————————————————————————————————————————

//#region —————————————————————————————————————————————————— Inputs

//#endregion ————————————————————————————————————————————————————————

//#region —————————————————————————————————————————————————— Price Data



//#endregion ——————————————————————————————————————————————————————————————

//#region —————————————————————————————————————————————————— Indicators

[p_ind_1_plusEMA, p_ind_1_negEMA, p_ind_1_diff, p_ind_1_isStrongUptrend, p_ind_1_isWeakUptrend, p_ind_1_isStrongDowntrend, p_ind_1_isWeakDowntrend]      =      p_ta_vtt(30, 10) // VTT


//#endregion ————————————————————————————————————————————————————————————

//#region —————————————————————————————————————————————————— Conditions

//#endregion ————————————————————————————————————————————————————————————

//#region —————————————————————————————————————————————————— Indicator Plots

// VTT
plot(p_ind_1_diff, "VTT", style = plot.style_histogram, color = p_ind_1_isStrongUptrend ?  color.rgb(27, 94, 32, 0) : p_ind_1_isWeakUptrend ? color.rgb(165, 214, 167, 0) : p_ind_1_isStrongDowntrend ?  color.rgb(183, 28, 28, 0) : p_ind_1_isWeakDowntrend ? color.rgb(199, 106, 114, 0) : #FFFFFF, linewidth = 3)

//#endregion ————————————————————————————————————————————————————————————————

//#region —————————————————————————————————————————————————— Custom Plots

//#endregion ————————————————————————————————————————————————————————————

//#region —————————————————————————————————————————————————— Alert

//#endregion ————————————————————————————————————————————————————————
Existing indicator chart preview
Existing visual preview. Verify exact values against the source; the image is not a performance result.

Specify a checkable adaptation in Pineify

Ask the Pine Script AI Coding Agent to expose N and M as inputs, show P and Q in the Data Window, and make the equality, missing-data and close-confirmation policies explicit. Verify both the numerical fixture and the flat-line classifier cases before evaluating signals on market data. The Visual Editor has a VTT configuration, but that does not make its controls available inside this fixed standalone copy or establish free access to every product workflow.

Open Pine Script AI Coding Agent

Educational indicator analysis. No measured trading performance is established here. Syntax validation does not prove formula equivalence, execution assumptions or profitability.

Implementation questions

No. This Pineify helper uses high, low, previous extremes and true range, followed by two EMAs. Its linked community reference is named Vortex Trend Tracker.

Yes. For example a two-bar positive movement sum of 6 divided by a true-range sum of 4 is 1.5. The ratios are not bounded by one.

White is the fallback when none of the four Boolean conditions is true. For example Q above P with Q unchanged matches neither red condition. Some equality or unavailable-data cases also use the fallback.

No. Dark green checks P above Q and P not falling. Q can rise faster, making their separation shrink even while that condition is true.

No. The helper accepts two length arguments but receives fixed values 30 and 10. Add explicit input functions or edit the call to change this standalone example.

They describe a declining dominant EMA, not a proven reversal. The example contains no orders, execution rules or measured trading results.