
Cette stratégie consiste à construire un système de détection de tendance complet avec trois EMA de 25/50/100, exigeant que les EMA soient alignées dans l’ordre et inclinées dans la même direction, ainsi qu’une exigence d’intervalle minimum de 0,10 fois l’ATR. Les données montrent que ce triple filtre est efficace pour éviter les fausses ruptures dans les marchés volatiles et ne fonctionne que lorsque la tendance est réelle.
La clé réside dans un “arrangement EMA propre”: 25>50>100 avec une inclinaison totale vers le haut en cas de multiples, 25<50<100 avec une inclinaison totale vers le bas en cas d’une inclinaison nulle. Le filtrage d’intervalle assure une tendance suffisamment forte pour éviter des signaux inefficaces dans un état de collision uniforme.
Le cœur de la stratégie est le mécanisme de détection des retraits. Les retraits à plusieurs têtes exigent que les prix touchent 25 ou 50 EMA mais restent au-dessus de 100 EMA, et les retraits à tête vide exigent que les prix touchent 25 ou 50 EMA mais restent en dessous de 100 EMA. Cette conception est plus précise que le traditionnel “ retrait de support et achat “.
Le réglage de la fenêtre de rétractation de 15 cycles est raisonnable. Les données de retracement montrent que la véritable rétractation de tendance est généralement terminée dans un délai de 10 à 15 cycles, et une rétractation au-delà de cette fenêtre de temps signifie souvent qu’une modification de tendance est possible.
Les conditions de déclenchement de l’entrée sont extrêmement strictes: après la confirmation de la fermeture de la ligne K, l’ensemble de la ligne K (ouverture, haut, bas, fermeture) doit être complètement situé du bon côté de la 25EMA. Cette conception évite les fausses percées et le bruit dans la disque, assurant l’entrée en jeu uniquement après la confirmation d’un véritable renversement.
Les conditions d’entrée à plusieurs têtes sont les suivantes: ouverture <25EMA, minimum >25EMA, clôture >25EMA. Conditions d’entrée à vide: ouverture <25EMA, maximum <25EMA, clôture <25EMA. Cette méthode de “confirmation de ligne K entière” améliore considérablement la qualité de l’entrée et réduit les transactions invalides.
La stratégie de mise en position par défaut de 10% est modérée, permettant à la fois de générer des gains suffisants et de contrôler les risques individuels. La mise en place de frais de traitement de 0,05% est proche du coût réel de la transaction, les résultats de retracement sont plus utiles.
Remarque importante: la stratégie ne contient que la logique d’entrée et ne contient pas de stop loss. L’utilisation en ligne doit être accompagnée d’une gestion des risques stricte, il est recommandé de définir un stop loss de 2 à 3 fois l’ATR et un stop loss de 1,5 à 2 fois le rapport de risque / rendement.
Les stratégies fonctionnent bien dans les marchés clairement tendanciels et sont particulièrement adaptées aux achats et retours unilatéraux. Cependant, dans les marchés horizontalement volatiles, les conditions de classement des EMA sont difficiles à satisfaire et les opportunités de négociation sont relativement rares.
Remarque de risque: la rétroaction historique ne représente pas les gains futurs, la stratégie présente un risque de pertes continues. Les marchés en crise peuvent être en état de non-signal pendant une longue période, il faut attendre patiemment l’environnement de marché approprié.
/*backtest
start: 2025-01-01 00:00:00
end: 2025-09-27 08:00:00
period: 1d
basePeriod: 1d
exchanges: [{"eid":"Futures_Bybit","currency":"ETH_USDT","balance":500000}]
*/
//@version=6
strategy("Clean 25/50/100 EMA Pullback Scalper — Entries Only (Side Select)",
overlay=true, calc_on_every_tick=true, calc_on_order_fills=true,
initial_capital=10000, commission_type=strategy.commission.percent, commission_value=0.05,
pyramiding=0, default_qty_type=strategy.percent_of_equity, default_qty_value=10)
// === Side selector ===
side = input.string("Both", "Trade Side", options=["Both", "Long Only", "Short Only"])
longsEnabled = side == "Both" or side == "Long Only"
shortsEnabled = side == "Both" or side == "Short Only"
// === Inputs ===
lenFast = input.int(25, "Fast EMA (pullback)", minval=1)
lenMid = input.int(50, "Mid EMA (filter)", minval=1)
lenSlow = input.int(100, "Slow EMA (safety)", minval=1)
useSlope = input.bool(true, "Require EMAs sloping same way?")
useSpread = input.bool(true, "Require clean spacing (min spread)?")
spreadPct = input.float(0.10, "Min spread vs ATR (0.10 = 0.10×ATR)", step=0.01, minval=0.0)
pullLookback = input.int(15, "Max bars after pullback", minval=1, maxval=100)
showSignals = input.bool(true, "Show entry markers?")
// === Series ===
ema25 = ta.ema(close, lenFast)
ema50 = ta.ema(close, lenMid)
ema100 = ta.ema(close, lenSlow)
atr = ta.atr(14)
// === Trend & spacing ===
isUpStack = ema25 > ema50 and ema50 > ema100
isDownStack = ema25 < ema50 and ema50 < ema100
slopeUp = ema25 > ema25[1] and ema50 > ema50[1] and ema100 > ema100[1]
slopeDown = ema25 < ema25[1] and ema50 < ema50[1] and ema100 < ema100[1]
minGap = atr * spreadPct
spreadUpOK = (ema25 - ema50) > minGap and (ema50 - ema100) > minGap
spreadDownOK = (ema100 - ema50) > minGap and (ema50 - ema25) > minGap
trendLongOK = isUpStack and (useSlope ? slopeUp : true) and (useSpread ? spreadUpOK : true)
trendShortOK = isDownStack and (useSlope ? slopeDown : true) and (useSpread ? spreadDownOK : true)
// === Pullback detection state ===
var bool pullArmedLong = false
var bool pullArmedShort = false
var int pullBarIdxLong = na
var int pullBarIdxShort = na
var float pullMinLong = na
var float pullMaxShort = na
// Long pullback state
if trendLongOK
touched25 = low <= ema25
touched50 = low <= ema50
stayedAbove100 = low > ema100
if (touched25 or touched50) and stayedAbove100
pullArmedLong := true
pullBarIdxLong := bar_index
pullMinLong := na(pullMinLong) ? low : math.min(pullMinLong, low)
else if pullArmedLong
pullMinLong := na(pullMinLong) ? low : math.min(pullMinLong, low)
if low <= ema100 or (bar_index - pullBarIdxLong > pullLookback)
pullArmedLong := false
pullMinLong := na
else
pullArmedLong := false
pullMinLong := na
// Short pullback state
if trendShortOK
touched25s = high >= ema25
touched50s = high >= ema50
stayedBelow100 = high < ema100
if (touched25s or touched50s) and stayedBelow100
pullArmedShort := true
pullBarIdxShort := bar_index
pullMaxShort := na(pullMaxShort) ? high : math.max(pullMaxShort, high)
else if pullArmedShort
pullMaxShort := na(pullMaxShort) ? high : math.max(pullMaxShort, high)
if high >= ema100 or (bar_index - pullBarIdxShort > pullLookback)
pullArmedShort := false
pullMaxShort := na
else
pullArmedShort := false
pullMaxShort := na
// === Entry triggers (confirmed bar & whole candle outside 25 EMA) ===
longEntryRaw = pullArmedLong and barstate.isconfirmed and (open > ema25 and low > ema25 and close > ema25) and (na(pullMinLong) or pullMinLong > ema100)
shortEntryRaw = pullArmedShort and barstate.isconfirmed and (open < ema25 and high < ema25 and close < ema25) and (na(pullMaxShort) or pullMaxShort < ema100)
longEntry = longsEnabled and longEntryRaw
shortEntry = shortsEnabled and shortEntryRaw
// Disarm after trigger
if longEntry
pullArmedLong := false
pullMinLong := na
if shortEntry
pullArmedShort := false
pullMaxShort := na
// === Orders (entries only; no TP/SL) ===
if longEntry and strategy.position_size <= 0
strategy.entry("Long", strategy.long)
if shortEntry and strategy.position_size >= 0
strategy.entry("Short", strategy.short)
// === Plots & visuals ===
plot(ema25, "EMA 25", color=color.new(color.teal, 0))
plot(ema50, "EMA 50", color=color.new(color.orange, 0))
plot(ema100, "EMA 100", color=color.new(color.purple, 0))
bgcolor(trendLongOK ? color.new(color.green, 92) : na)
bgcolor(trendShortOK ? color.new(color.red, 92) : na)
if showSignals and longEntry
label.new(bar_index, low, "▲ BUY\nFull candle above 25 EMA", style=label.style_label_up, textcolor=color.white, color=color.new(color.green, 0))
if showSignals and shortEntry
label.new(bar_index, high, "▼ SELL\nFull candle below 25 EMA", style=label.style_label_down, textcolor=color.white, color=color.new(color.red, 0))