Page 1 of 1

Building a Mechanical Scalping Signal in Pine Script v5

Posted: Thu Sep 03, 2026 8:46 pm
by Fairman
If you want consistency in scalping, your entries can’t depend on your mood or how fast you can click. Below is a clean, no-gimmick Pine Script v5 indicator that flags mechanical scalping setups based on a simple, testable rule: price sweeping a short-term liquidity level and reclaiming it with momentum confirmation via a fast EMA cross. No repainting, no arbitrary smoothing — just clean triggers you can backtest and trust.

Code: Select all

 //@version=5
indicator("Mechanical Liquidity Sweep Scalper", overlay=true, max_bars_back=500)

// === Inputs ===
swingLen   = input.int(5, "Swing Lookback (bars)", minval=2)
emaFastLen = input.int(9, "Fast EMA Length")
emaSlowLen = input.int(21, "Slow EMA Length")
volMult    = input.float(1.5, "Volume Spike Multiplier")
atrLen     = input.int(14, "ATR Length for Stop")
atrMult    = input.float(1.0, "ATR Stop Multiplier")

// === Core Calculations ===
emaFast = ta.ema(close, emaFastLen)
emaSlow = ta.ema(close, emaSlowLen)
atrVal  = ta.atr(atrLen)
avgVol  = ta.sma(volume, 20)

swingHigh = ta.highest(high, swingLen)[1]
swingLow  = ta.lowest(low, swingLen)[1]

// === Liquidity Sweep Detection ===
bullSweep = low < swingLow and close > swingLow and volume > avgVol * volMult
bearSweep = high > swingHigh and close < swingHigh and volume > avgVol * volMult

// === Momentum Confirmation ===
bullMomentum = ta.crossover(emaFast, emaSlow)
bearMomentum = ta.crossunder(emaFast, emaSlow)

// === Signal Logic (sweep must precede or align with momentum shift, within 3 bars) ===
var int lastBullSweepBar = na
var int lastBearSweepBar = na

if bullSweep
    lastBullSweepBar := bar_index
if bearSweep
    lastBearSweepBar := bar_index

longSignal  = bullMomentum and not na(lastBullSweepBar) and (bar_index - lastBullSweepBar) <= 3
shortSignal = bearMomentum and not na(lastBearSweepBar) and (bar_index - lastBearSweepBar) <= 3

// === Stop Loss Levels (ATR-based, fixed at signal bar) ===
longStop  = close - (atrVal * atrMult)
shortStop = close + (atrVal * atrMult)

// === Plotting ===
plot(emaFast, "Fast EMA", color=color.blue)
plot(emaSlow, "Slow EMA", color=color.orange)

plotshape(longSignal, title="Long Signal", location=location.belowbar, style=shape.triangleup, color=color.green, size=size.small)
plotshape(shortSignal, title="Short Signal", location=location.abovebar, style=shape.triangledown, color=color.red, size=size.small)

plot(longSignal ? longStop : na, title="Long Stop", style=plot.style_circles, color=color.green)
plot(shortSignal ? shortStop : na, title="Short Stop", style=plot.style_circles, color=color.red)

// === Alerts ===
alertcondition(longSignal, title="Long Sweep Signal", message="Bullish liquidity sweep + momentum confirmation")
alertcondition(shortSignal, title="Short Sweep Signal", message="Bearish liquidity sweep + momentum confirmation")
Why it’s built this way: the sweep detection requires both a wick through a prior swing level AND a volume spike, so you’re filtering out low-conviction fakeouts. The momentum confirmation (EMA cross) has to happen within 3 bars of the sweep — this stops the script from firing signals on stale setups. The stop is ATR-based and fixed at the signal bar, not trailing arbitrarily, so your risk is defined and testable.

Before you trade this live: backtest it across at least 200+ signals on your instrument of choice, check the real win rate and payoff ratio, and only then consider forward-testing on a demo account. Mechanical means the rules don’t bend — not that the first version is automatically profitable.