TradingView indicator guides

LWMA in Pine Script: Custom Signed Weights and WMA Differences

This page examines Pineify’s custom p_ta_lwma helper. Its default period 10 and weight 6 produce some negative coefficients, so it is not the conventional positive-weight WMA. Inspect the coefficients before interpreting its line as an ordinary average.

Inspect the Pine Script

The custom formula changes the coefficient offset

Let N be period, W be weight and i the bar age, with i = 0 for the current bar. The source computes sub = W/N − 1 and raw coefficient a(i) = W − i − sub = W(1 − 1/N) + 1 − i. It divides the weighted price sum by D = sum(a(i)).

Adjacent raw coefficients always differ by one. Changing W shifts every raw coefficient equally; it does not change that raw slope. Normalization changes their relative influence. For N ≥ 1, D = (N − 1)W + N(3 − N)/2. A zero denominator is undefined, and a small denominator can amplify price differences substantially.

For the displayed N = 10 and W = 6, sub = −0.4 and the coefficients, newest first, are [6.4, 5.4, 4.4, 3.4, 2.4, 1.4, 0.4, −0.6, −1.6, −2.6]. Their sum is 19. The newest observation has normalized weight 6.4/19 = 33.6842%; the oldest has −2.6/19 = −13.6842%.

All coefficients still sum to one after normalization, so a constant valid input remains constant. Negative coefficients mean the output need not stay within the input range. This is a signed linear filter, not a convex average.

Compare it with conventional WMA

Conventional WMA uses positive raw weights N, N−1, …, 1, divided by N(N+1)/2. With the custom helper, setting W = N gives those same weights on a complete valid window. At N = W = 10, the newest and oldest normalized weights are 18.1818% and 1.8182%. Those values do not describe the default 10/6 case.

TradingView’s ta.wma reference provides the conventional built-in. Compare the actual source, missing-data handling and available history before assuming two implementations agree.

For N = 10, increasing W from 6 to 10 to 20 changes the newest normalized weight from 33.6842% to 18.1818% to 13.1034%. On this positive-denominator range, larger W reduces the newest observation’s relative influence. In the limit of very large positive W, the normalized weights approach equal 1/N weights.

At N = 10 and W = 4, D is only 1: the newest raw coefficient is 4.6 and the oldest −4.4. That is a very different filter, not a moderate variation of a 10-bar average. The current helper does not constrain these parameter combinations.

Two hypothetical inputs expose the difference

Start with a complete window of ten prices at 100. Change only the newest price to 110. The custom 10/6 output becomes 100 + 10 × 6.4/19 = 103.368421. Conventional WMA(10) becomes 101.818182, and SMA(10) becomes 101. These are calculated examples, not market observations.

Now put the 110 at the oldest position and leave the other nine prices at 100. The custom output becomes 100 + 10 × (−2.6)/19 = 98.631579, below every price in the window. Conventional WMA is 100.181818 and SMA remains 101. Negative historical weight explains the undershoot.

With the 10/4 combination, those same two fixtures produce 146 and 56. A small normalization sum magnifies the change. No claim about reduced noise, earlier profitable signals or an optimal timeframe follows from these calculations.

For a further check, prices [110, 109, …, 101], newest first, give custom 10/6 = 109.842105, standard WMA(10) = 107 and SMA(10) = 105.5. Record the chosen weighting contract when comparing charts.

Direction flags and colors have different edge cases

The helper returns five values: the line, rise, fall, buySignal and sellSignal. Rise is current line > previous line; fall is current line < previous line. A tie makes both false. The plotted line is green only for rise and red otherwise, so red also includes flat and unavailable comparisons. It does not necessarily mean a falling value.

buySignal is rise and not previous rise. A rise following a flat bar qualifies; a previous fall is not required. sellSignal is fall and not previous fall, so a fall after a flat bar also qualifies. A sequence 100, 100, 101 starts a rising flag on its third reading; 101, 100, 100 ends with red but no new falling flag.

These internal booleans are returned but never connected to alertcondition, alert, plotted arrows or orders in the displayed indicator. They are slope-state transitions, not a line crossing itself. The code has no bar-close gate, so live values and flags can change before the chart bar closes.

If you add alerts, define the event and confirmation policy explicitly. Waiting for a second bar is a different rule from confirming the first bar’s close. Neither establishes a measured reliability improvement. See TradingView’s bar-state documentation.

What this reference and the editor actually expose

The displayed source calls p_ta_lwma(close, 10, 6) with fixed literals. It has no settings inputs. The Visual Pine Script Editor’s LWMA block separately exposes Source, Lookback Period and Weight and makes the five outputs available to other blocks. Configuring a block does not mean this copied reference contains editable inputs or running alerts.

The manual sum propagates an unavailable price in its window instead of explicitly skipping it. On continuous valid data, ten observations are needed for the first 10/6 result and another result for an ordinary slope comparison. Changing period requires a positive integer and checking the coefficient sum; the helper itself contains no parameter validation.

Use standard WMA when positive linear weights are intended. Use SMA for equal finite-window weights. Preserve this custom formula only when its signed weighting is part of the intended specification.

Pine Script v6: implementation and limits

Paste the full source into Pine Editor. It plots the custom 10/6 line and returns internal direction flags, but has no alert conditions, order logic or settings inputs. Compare known windows numerically before using it in another script.

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="Linear Weighted Moving Average", overlay=true, 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
        // 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)))
    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)))
    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_lwma(series float source, simple int period, simple int weight) =>
    price = source
    sub = weight / period - 1
    float p = na
    float d = na
    float sum = 0
    float divider = 0
    for i = 0 to period - 1 by 1
        p := price[i] * (weight - i - sub)
        d := weight - i - sub
        sum := sum + p
        divider := divider + d
        divider
    lwma = sum / divider
    lwmaRise = lwma[1] < lwma
    lwmaFall = lwma[1] > lwma
    buySignal = lwmaRise and not lwmaRise[1]
    sellSignal = lwmaFall and not lwmaFall[1]
    [lwma, lwmaRise, lwmaFall, buySignal, sellSignal]

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


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

// Input Groups
string P_GP_1      =      ""

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


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

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


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



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


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

[p_ind_1_lwma, p_ind_1_rise, p_ind_1_fall, p_ind_1_buySignal, p_ind_1_sellSignal]      =      p_ta_lwma(close, 10, 6) // LWMA


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


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

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


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

// LWMA
p_ind_1_lwma_color = p_ind_1_rise ? color.rgb(76, 175, 80, 0) : color.rgb(242, 54, 69, 0)
plot(p_ind_1_lwma, "LWMA", p_ind_1_lwma_color, 2)

//#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

Specify whether you want conventional positive WMA weights or this custom signed formula. Request explicit period constraints, a zero-denominator policy and the intended source/weight inputs. Review the coefficients, validate syntax and compare numerical fixtures before adding any strategy conditions.

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. The custom default contains negative historical weights and a normalization sum of 19. Standard WMA uses positive weights 10 through 1 with sum 55. Setting the custom weight equal to period matches those weights for a complete valid window.

Yes. Negative coefficients remove the range bound of a convex average. With the oldest price at 110 and nine newer prices at 100, the default output is about 98.631579.

No. At period 10, increasing weight from 6 to 10 to 20 reduces the newest normalized coefficient. Check the entire coefficient vector and denominator rather than relying on the parameter name.

No. Red is the fallback whenever rise is false, including flat values. The sellSignal boolean specifically requires a newly falling state and is not connected to an alert or order here.

Not in this fixed reference. Pineify’s visual block has configuration fields, but a copied script needs input declarations to expose editable Settings controls.

None is established here. The examples audit arithmetic and behavior; evaluating a strategy requires separate entries, exits, sizing, costs and reserved test data.