Dual MA Crossover: Events, Confirmation and Pine Script
A dual moving-average crossover is a change in the ordering of two calculated lines. This reference uses EMA9 and EMA21 by default, shows their difference and marks events only when the chart bar closes. A confirmed event establishes that ordering change; it does not establish a profitable trade.
Inspect the Pine ScriptDistinguish a state from a crossover
Let F be the fast MA, S the slow MA and G = F−S. An upward cross requires current G > 0 and previous G ≤ 0. A downward cross requires current G < 0 and previous G ≥ 0. Remaining above or below is a state, not a new event on every bar.
Equality matters. G moving from0 to1 is an upward cross; moving from−1 to0 is not. Moving from1 to0 is not a downward cross, but0 to−1 is. The reference also requires both current and previous pairs to be available. A first valid pair does not create a crossover by itself.
Fast and slow describe configured lengths. The example rejects fast length greater than or equal to slow length. Changing source, averaging method or seed changes the values being compared, even if the two lengths stay the same.
A two-bar fixture can produce both directions
Assume a sufficiently long constant history at100 so both EMA9 and EMA21 equal100. Their update weights are2/10 =0.2 and2/22 =1/11. At the next close of110, fast becomes102 and slow becomes100.909091. The gap is1.090909: it crossed upward from zero.
If the following close is90, fast becomes99.6 and slow becomes99.917355. The gap is−0.317355, a downward cross immediately after the upward one. Both events can be fully closed-bar events. Waiting for close removes provisional intrabar changes; it does not remove successive losing trades.
This is a synthetic calculation, not a measured market result. No entry, exit, position size or cost was defined, so it does not calculate a strategy return.
Confirmation timing is a separate choice
During an open bar, the current source and both averages can change. The script computes raw cross conditions every update, then gates markers and alert conditions with barstate.isconfirmed. The lines continue to show live values. Create a TradingView alert using the desired condition after adding the indicator.
Waiting for the next bar to remain above the slow line is an additional persistence rule, not the definition of confirming the crossover bar. It adds a different delay and may discard events that already occurred. Referring to the previous bar’s crossover also changes when the event is acted on; it does not eliminate every form of repainting.
The example makes no higher-timeframe request. A chart-bar close does not confirm an unfinished higher-timeframe candle. A multi-timeframe extension must specify which completed values are requested and whether it requires simultaneous cross events or agreement between persistent above/below states. See TradingView’s repainting documentation and bar states.
Compare the right MACD event
For the same source, EMA fast/slow lengths and initialization, MACD’s main line is F−S. A dual EMA upward crossover therefore corresponds to that main line crossing zero. It is not the same as MACD crossing its signal average or the histogram crossing zero.
For example, F102/S101 gives gap1, then F103/S102.5 givesgap0.5. Both averages rose while their separation narrowed. The gap alone does not prove price acceleration or predict a reversal. A gap can be analyzed for divergence, but that requires separately defined swing points and confirmation timing.
Compare MACD for the difference-series task and the MA router for branch-specific calculations. There is no demonstrated universal ranking of their usefulness.
All eleven branches are included, with explicit limits
The script includes every helper needed for SMA, EMA, RMA, HMA, WMA, TMA, VAR, WWMA, ZLEMA, TSF and VWMA. The selector chooses one type for both lines; the default is EMA. Length inputs range from2 to500, with fast strictly shorter than slow.
These names do not make the branches numerically interchangeable. WWMA is zero-seeded and differs from SMA-seeded RMA. VAR uses a fixed nine-bar change ratio and can remain zero on a constant series loaded from the start. ZLEMA uses floor(N/2) for its adjusted-source lag. TSF extrapolates the current regression line. VWMA requires suitable volume data. The linked MA router explains these contracts.
Readiness checks available current and previous outputs. It is not a guarantee that recursive initialization has converged or that every branch handles missing source values identically. Compare the selected branch’s closed-bar values with a known baseline and enough history before interpreting crossings.
Turn a crossover into a test specification
For a hypothetical long-only baseline, define entry at the next open after a confirmed upward cross and exit at the next open after a confirmed downward cross, with one position at a time. Specify size, dates, commission and slippage. This is a research specification; the displayed indicator does not submit those orders.
A filter creates a different strategy. If a cross is rejected while a filter is false, decide whether a later filter change may enter without a new cross. Keep that rule fixed in the baseline comparison. If adding ADX, ta.dmi returns +DI, −DI and then ADX; its second output is not ADX. Neither a threshold nor price above SMA200 guarantees a profitable trend.
Freeze parameters before evaluating a later untouched period. Report trade count and drawdown with return, and inspect sensitivity to costs and nearby parameter values. A win rate without complete trade rules cannot support a comparison. TradingView’s strategy documentation describes order and fill assumptions.
Pine Script v6: implementation and limits
Paste the complete indicator into Pine Editor. Compare Fast minus slow in the Data Window with each confirmed triangle. Its alerts require chart-bar close; plots remain live. The script does not place trades or provide a performance report.
// 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("Dual MA: confirmed crossover reference", overlay=true)
p_ta_Var_Func(src, length) =>
valpha = 2 / (length + 1)
vud1 = src > src[1] ? src - src[1] : 0
vdd1 = src < src[1] ? src[1] - src : 0
vUD = math.sum(vud1, 9)
vDD = math.sum(vdd1, 9)
vCMO = nz((vUD - vDD) / (vUD + vDD))
VAR = 0.0
VAR := nz(valpha * math.abs(vCMO) * src) + (1 - valpha * math.abs(vCMO)) * nz(VAR[1])
VAR
p_ta_Wwma_Func(src, length) =>
wwalpha = 1 / length
WWMA = 0.0
WWMA := wwalpha * src + (1 - wwalpha) * nz(WWMA[1])
WWMA
p_ta_Zlema_Func(src, length) =>
zxLag = int(math.floor(length / 2))
zxEMAData = src + src - src[zxLag]
ZLEMA = ta.ema(zxEMAData, length)
ZLEMA
p_ta_Tsf_Func(src, length) =>
lrc = ta.linreg(src, length, 0)
lrc1 = ta.linreg(src, length, 1)
lrs = lrc - lrc1
TSF = ta.linreg(src, length, 0) + lrs
TSF
// Main MA function — supports 11 moving average types
p_ta_ma(series float source, simple int length, simple string ma_type) =>
switch ma_type
"SMA" => ta.sma(source, length)
"EMA" => ta.ema(source, length)
"RMA" => ta.rma(source, length)
"HMA" => ta.hma(source, length)
"WMA" => ta.wma(source, length)
"TMA" => ta.sma(ta.sma(source, math.ceil(length / 2)), math.floor(length / 2) + 1)
"VAR" => p_ta_Var_Func(source, length)
"WWMA" => p_ta_Wwma_Func(source, length)
"ZLEMA" => p_ta_Zlema_Func(source, length)
"TSF" => p_ta_Tsf_Func(source, length)
"VWMA" => ta.vwma(source, length)
source = input.source(close, "Source")
fastLength = input.int(9, "Fast length", minval=2, maxval=500)
slowLength = input.int(21, "Slow length", minval=2, maxval=500)
maType = input.string("EMA", "MA type", options=["SMA", "EMA", "RMA", "HMA", "WMA", "TMA", "VAR", "WWMA", "ZLEMA", "TSF", "VWMA"])
if fastLength >= slowLength
runtime.error("Fast length must be smaller than slow length.")
fastMa = p_ta_ma(source, fastLength, maType)
slowMa = p_ta_ma(source, slowLength, maType)
crossUp = ta.crossover(fastMa, slowMa)
crossDown = ta.crossunder(fastMa, slowMa)
ready = not na(fastMa) and not na(slowMa) and not na(fastMa[1]) and not na(slowMa[1])
confirmedUp = ready and barstate.isconfirmed and crossUp
confirmedDown = ready and barstate.isconfirmed and crossDown
plot(fastMa, "Fast MA", color.green, 1)
plot(slowMa, "Slow MA", color.red, 1)
plot(fastMa - slowMa, "Fast minus slow", display=display.data_window)
plotshape(confirmedUp, title="Confirmed upward cross", style=shape.triangleup, location=location.belowbar, color=color.green, size=size.small)
plotshape(confirmedDown, title="Confirmed downward cross", style=shape.triangledown, location=location.abovebar, color=color.red, size=size.small)
alertcondition(confirmedUp, "Upward MA cross at close", "Fast MA crossed above slow MA on a closed chart bar.")
alertcondition(confirmedDown, "Downward MA cross at close", "Fast MA crossed below slow MA on a closed chart bar.")

Specify a checkable adaptation in Pineify
Specify the MA formulas, two lengths, source, equality treatment and confirmation timing. Ask for separate event and state diagnostics. If requesting a strategy, define what happens when a filter changes after a rejected cross, as well as entry timing, exits and transaction costs.
Open Pine Script AI Coding AgentEducational indicator analysis. No measured trading performance is established here. Syntax validation does not prove formula equivalence, execution assumptions or profitability.