Weighted Moving Average (WMA) Explained + Custom Pine Script for ScalpersHey everyone, When trading lower timeframes, e

Description: Share, develop, and backtest custom indicators and automated trading bots. Discuss code implementations across cTrader, MetaTrader 4, MetaTrader 5, and TradingView Pine Script.
PTStockScalper
Site Admin
Posts: 36
Joined: Thu Sep 03, 2026 6:04 pm

Re: Weighted Moving Average (WMA) Explained + Custom Pine Script for ScalpersHey everyone, When trading lower timeframe

Post by PTStockScalper »

Pine Script (v5) Implementation

Code: Select all

//@version=5
indicator("WMA Scalping System: Signals, Vol + ATR Stop", overlay=true, timeframe="")

// --- Moving Average Parameters ---
fast_length = input.int(7, title="Fast WMA", minval=1, group="Moving Averages")
slow_length = input.int(21, title="Slow WMA", minval=1, group="Moving Averages")
src = input.source(close, title="Source", group="Moving Averages")

// --- Volume Filter Parameters ---
use_vol_filter = input.bool(true, title="Enable Volume Filter", group="Volume Filter")
vol_length = input.int(20, title="Volume MA Length", minval=1, group="Volume Filter")

// --- ATR Stop Parameters ---
atr_length = input.int(14, title="ATR Length", minval=1, group="ATR Trailing Stop")
atr_mult = input.float(1.5, title="ATR Multiplier", step=0.1, group="ATR Trailing Stop")

// --- Core Calculations ---
fast_wma = ta.wma(src, fast_length)
slow_wma = ta.wma(src, slow_length)
vol_ma = ta.sma(volume, vol_length)
atr_val = ta.atr(atr_length)

// --- Signal Logic ---
bull_cross = ta.crossover(fast_wma, slow_wma)
bear_cross = ta.crossunder(fast_wma, slow_wma)
vol_valid = not use_vol_filter or (volume > vol_ma)

long_signal = bull_cross and vol_valid
short_signal = bear_cross and vol_valid

// --- Stateful ATR Trailing Stop Logic ---
// Initializes memory states across historical bars
var float trail_stop = na
var int pos_dir = 0 // 1 for Long, -1 for Short

if long_signal
    pos_dir := 1
    trail_stop := close - (atr_val * atr_mult)
else if short_signal
    pos_dir := -1
    trail_stop := close + (atr_val * atr_mult)
else if pos_dir == 1
    // Long position: Stop level can only increase
    prev_stop = na(trail_stop[1]) ? close - (atr_val * atr_mult) : trail_stop[1]
    trail_stop := math.max(prev_stop, close - (atr_val * atr_mult))
else if pos_dir == -1
    // Short position: Stop level can only decrease
    prev_stop = na(trail_stop[1]) ? close + (atr_val * atr_mult) : trail_stop[1]
    trail_stop := math.min(prev_stop, close + (atr_val * atr_mult))

// --- UI / Rendering ---
fast_color = fast_wma > fast_wma[1] ? color.new(#26a69a, 0) : fast_wma < fast_wma[1] ? color.new(#ef5350, 0) : color.new(color.gray, 0)
plot(fast_wma, title="Fast WMA", color=fast_color, linewidth=2)
plot(slow_wma, title="Slow WMA", color=color.new(color.white, 50), linewidth=2)

stop_color = pos_dir == 1 ? color.new(#26a69a, 30) : pos_dir == -1 ? color.new(#ef5350, 30) : na
plot(trail_stop, title="ATR Trailing Stop", color=stop_color, style=plot.style_cross, linewidth=1)

plotshape(long_signal, title="Long Signal", style=shape.triangleup, location=location.belowbar, color=color.new(#26a69a, 0), size=size.small)
plotshape(short_signal, title="Short Signal", style=shape.triangledown, location=location.abovebar, color=color.new(#ef5350, 0), size=size.small)

// --- Webhook Alerts ---
alertcondition(long_signal, title="Long Entry Triggered", message="LONG ALERT: WMA Cross with Volume Confirmed")
alertcondition(short_signal, title="Short Entry Triggered", message="SHORT ALERT: WMA Cross with Volume Confirmed")
PTStockScalper
Site Admin
Posts: 36
Joined: Thu Sep 03, 2026 6:04 pm

Re: Weighted Moving Average (WMA) Explained + Custom Pine Script for ScalpersHey everyone, When trading lower timeframe

Post by PTStockScalper »

Implementation Notes

Multiplier Calibration: The default atr_mult is set to 1.5 to accommodate the tighter risk parameters typical of lower timeframe scalping. Swing traders operating on H1 or H4 intervals should scale this closer to 3.0.

Automation: The script is natively structured for automated execution. The alertcondition() outputs can be directly routed to third-party webhooks for execution on external platforms.

I welcome any feedback or optimization suggestions from the community. If there is sufficient interest, I can translate this logic into C# (cTrader/cBot) or MQL5 for those deploying automated strategies outside of TradingView.
PTStockScalper
Site Admin
Posts: 36
Joined: Thu Sep 03, 2026 6:04 pm

Re: Weighted Moving Average (WMA) Explained + Custom Pine Script for ScalpersHey everyone, When trading lower timeframe

Post by PTStockScalper »

Plus if you will like, you can add session filter mechanics.

Session Filter Mechanics
The script now utilizes Pine Script's time() function to check the current bar's timestamp against a user-defined session string and timezone.

Default Timezone: Configured to Europe/London (GMT/BST), but this can be adjusted in the settings to match any preferred exchange timezone.

Visual Backtesting: A subtle background highlight is painted on the chart during the active trading window, allowing for immediate visual verification of signal timing.

Updated Pine Script (v5) Implementation

Code: Select all

//@version=5
indicator("WMA Scalping System: Signals, Vol, ATR Stop & Session", overlay=true, timeframe="")

// --- Moving Average Parameters ---
fast_length = input.int(7, title="Fast WMA", minval=1, group="Moving Averages")
slow_length = input.int(21, title="Slow WMA", minval=1, group="Moving Averages")
src = input.source(close, title="Source", group="Moving Averages")

// --- Volume Filter Parameters ---
use_vol_filter = input.bool(true, title="Enable Volume Filter", group="Volume Filter")
vol_length = input.int(20, title="Volume MA Length", minval=1, group="Volume Filter")

// --- Session Filter Parameters ---
use_session = input.bool(true, title="Enable Session Filter", group="Session Filter")
trade_session = input.session("0800-1700", title="Trading Session", group="Session Filter")
trade_tz = input.string("Europe/London", title="Timezone", group="Session Filter")

// --- ATR Stop Parameters ---
atr_length = input.int(14, title="ATR Length", minval=1, group="ATR Trailing Stop")
atr_mult = input.float(1.5, title="ATR Multiplier", step=0.1, group="ATR Trailing Stop")

// --- Core Calculations ---
fast_wma = ta.wma(src, fast_length)
slow_wma = ta.wma(src, slow_length)
vol_ma = ta.sma(volume, vol_length)
atr_val = ta.atr(atr_length)

// --- Session Logic ---
// Returns true if the current bar is within the defined session and timezone
in_session = not use_session or not na(time(timeframe.period, trade_session, trade_tz))

// --- Signal Logic ---
bull_cross = ta.crossover(fast_wma, slow_wma)
bear_cross = ta.crossunder(fast_wma, slow_wma)
vol_valid = not use_vol_filter or (volume > vol_ma)

// Signals now require the in_session boolean to be true
long_signal = bull_cross and vol_valid and in_session
short_signal = bear_cross and vol_valid and in_session

// --- Stateful ATR Trailing Stop Logic ---
var float trail_stop = na
var int pos_dir = 0 

if long_signal
    pos_dir := 1
    trail_stop := close - (atr_val * atr_mult)
else if short_signal
    pos_dir := -1
    trail_stop := close + (atr_val * atr_mult)
else if pos_dir == 1
    prev_stop = na(trail_stop[1]) ? close - (atr_val * atr_mult) : trail_stop[1]
    trail_stop := math.max(prev_stop, close - (atr_val * atr_mult))
else if pos_dir == -1
    prev_stop = na(trail_stop[1]) ? close + (atr_val * atr_mult) : trail_stop[1]
    trail_stop := math.min(prev_stop, close + (atr_val * atr_mult))

// --- UI / Rendering ---
// Session Background
bgcolor(use_session and in_session ? color.new(color.blue, 95) : na, title="Session Background")

fast_color = fast_wma > fast_wma[1] ? color.new(#26a69a, 0) : fast_wma < fast_wma[1] ? color.new(#ef5350, 0) : color.new(color.gray, 0)
plot(fast_wma, title="Fast WMA", color=fast_color, linewidth=2)
plot(slow_wma, title="Slow WMA", color=color.new(color.white, 50), linewidth=2)

stop_color = pos_dir == 1 ? color.new(#26a69a, 30) : pos_dir == -1 ? color.new(#ef5350, 30) : na
plot(trail_stop, title="ATR Trailing Stop", color=stop_color, style=plot.style_cross, linewidth=1)

plotshape(long_signal, title="Long Signal", style=shape.triangleup, location=location.belowbar, color=color.new(#26a69a, 0), size=size.small)
plotshape(short_signal, title="Short Signal", style=shape.triangledown, location=location.abovebar, color=color.new(#ef5350, 0), size=size.small)

// --- Webhook Alerts ---
alertcondition(long_signal, title="Long Entry Triggered", message="LONG ALERT: WMA Cross with Volume Confirmed (In Session)")
alertcondition(short_signal, title="Short Entry Triggered", message="SHORT ALERT: WMA Cross with Volume Confirmed (In Session)")
PTStockScalper
Site Admin
Posts: 36
Joined: Thu Sep 03, 2026 6:04 pm

Re: Weighted Moving Average (WMA) Explained + Custom Pine Script for ScalpersHey everyone, When trading lower timeframe

Post by PTStockScalper »

Implementation Notes

Continuous Trailing: While the entry signals are restricted by the session filter, the ATR trailing stop calculation persists globally. This ensures that if a position is opened near the session close, the logic will continue to ratchet the stop-loss appropriately through the off-hours until the trailing stop is breached or a new signal is generated the next day.

Timezone Formatting: For the New York session, users can adjust the string to "1300-2100" while leaving the timezone as Europe/London, or they can explicitly type "America/New_York" in the timezone input field and use local NY hours (e.g., "0800-1600").
PTStockScalper
Site Admin
Posts: 36
Joined: Thu Sep 03, 2026 6:04 pm

Re: Weighted Moving Average (WMA) Explained + Custom Pine Script for ScalpersHey everyone, When trading lower timeframe

Post by PTStockScalper »

Visualizing indicator signals is only the first phase of algorithmic development.

To validate the statistical edge of the Weighted Moving Average (WMA) system we have been building, we must subject it to the TradingView Strategy Tester.

I have converted our previous indicator into a complete Pine Script (v5) strategy(). This engine not only executes the Long/Short logic based on our session and volume filters but also strictly enforces the ratcheting ATR trailing stop, calculating hypothetical Net Profit, Win Rate, and Drawdown.

Architectural Shifts for Backtesting

State Synchronization: The trailing stop calculation now anchors directly to strategy.position_size. This ensures the mathematical stop-loss exactly matches the simulated broker execution, eliminating desynchronization between visual lines and actual trades.

Friction Simulation: The strategy() declaration at the top includes built-in commission parameters (set to a placeholder of $3 per trade). Scalpers must adjust this to match their specific broker's fee structure, as transaction costs will heavily degrade raw algorithmic yield on the 1-minute chart.

Pine Script (v5) Strategy Implementation

Code: Select all

//@version=5
strategy("WMA Scalping System [Backtest]", overlay=true, initial_capital=10000, default_qty_type=strategy.percent_of_equity, default_qty_value=10, commission_type=strategy.commission.cash_per_order, commission_value=3)

// --- Moving Average Parameters ---
fast_length = input.int(7, title="Fast WMA", minval=1, group="Moving Averages")
slow_length = input.int(21, title="Slow WMA", minval=1, group="Moving Averages")
src = input.source(close, title="Source", group="Moving Averages")

// --- Volume Filter Parameters ---
use_vol_filter = input.bool(true, title="Enable Volume Filter", group="Volume Filter")
vol_length = input.int(20, title="Volume MA Length", minval=1, group="Volume Filter")

// --- Session Filter Parameters ---
use_session = input.bool(true, title="Enable Session Filter", group="Session Filter")
trade_session = input.session("0800-1700", title="Trading Session", group="Session Filter")
trade_tz = input.string("Europe/London", title="Timezone", group="Session Filter")

// --- ATR Stop Parameters ---
atr_length = input.int(14, title="ATR Length", minval=1, group="ATR Trailing Stop")
atr_mult = input.float(1.5, title="ATR Multiplier", step=0.1, group="ATR Trailing Stop")

// --- Core Calculations ---
fast_wma = ta.wma(src, fast_length)
slow_wma = ta.wma(src, slow_length)
vol_ma = ta.sma(volume, vol_length)
atr_val = ta.atr(atr_length)

// --- Session Logic ---
in_session = not use_session or not na(time(timeframe.period, trade_session, trade_tz))

// --- Signal Logic ---
bull_cross = ta.crossover(fast_wma, slow_wma)
bear_cross = ta.crossunder(fast_wma, slow_wma)
vol_valid = not use_vol_filter or (volume > vol_ma)

long_signal = bull_cross and vol_valid and in_session
short_signal = bear_cross and vol_valid and in_session

// --- Strategy Execution Variables ---
is_long = strategy.position_size > 0
is_short = strategy.position_size < 0
var float trail_stop = na

// --- ATR Trailing Stop Logic (Tied to Position) ---
if long_signal and not is_long
    trail_stop := close - (atr_val * atr_mult)
else if short_signal and not is_short
    trail_stop := close + (atr_val * atr_mult)
else if is_long
    prev_stop = na(trail_stop[1]) ? close - (atr_val * atr_mult) : trail_stop[1]
    trail_stop := math.max(prev_stop, close - (atr_val * atr_mult))
else if is_short
    prev_stop = na(trail_stop[1]) ? close + (atr_val * atr_mult) : trail_stop[1]
    trail_stop := math.min(prev_stop, close + (atr_val * atr_mult))

// --- Order Execution ---
if long_signal
    strategy.entry("Long", strategy.long)

if short_signal
    strategy.entry("Short", strategy.short)

// --- Exit Execution ---
if is_long
    strategy.exit("Exit Long", "Long", stop=trail_stop)

if is_short
    strategy.exit("Exit Short", "Short", stop=trail_stop)

// --- UI / Rendering ---
bgcolor(use_session and in_session ? color.new(color.blue, 95) : na, title="Session Background")

fast_color = fast_wma > fast_wma[1] ? color.new(#26a69a, 0) : fast_wma < fast_wma[1] ? color.new(#ef5350, 0) : color.new(color.gray, 0)
plot(fast_wma, title="Fast WMA", color=fast_color, linewidth=2)
plot(slow_wma, title="Slow WMA", color=color.new(color.white, 50), linewidth=2)

stop_color = is_long ? color.new(#26a69a, 30) : is_short ? color.new(#ef5350, 30) : na
plot(trail_stop, title="ATR Trailing Stop", color=stop_color, style=plot.style_cross, linewidth=1)
PTStockScalper
Site Admin
Posts: 36
Joined: Thu Sep 03, 2026 6:04 pm

Re: Weighted Moving Average (WMA) Explained + Custom Pine Script for ScalpersHey everyone, When trading lower timeframe

Post by PTStockScalper »

How to Analyze Your Results

Once applied to the chart, navigate to the Strategy Tester tab at the bottom of your TradingView interface. Pay closest attention to:

Profit Factor: Gross Profit divided by Gross Loss. For a scalping strategy, aim for a baseline of $> 1.4$.

Maximum Drawdown: Ensure the sequence of consecutive losses falls within your risk tolerance limits.

Average Trade: If your average winning trade is smaller than your spread + commission, the strategy is mathematically unviable in live conditions regardless of the win rate.Run your backtests and post your Profit Factors below. Let me know which ticker and timeframe combination is yielding the cleanest distribution curve for you.
Post Reply