
Tidak lagi berdagang dengan satu garis rata tunggal. Strategi ini menggunakan 25/50/100 tiga EMA untuk membina sistem pengenalan trend yang lengkap, yang memerlukan EMA harus disusun secara berurutan dan bersandar pada arah yang sama, ditambah dengan keperluan jarak minimum 0.10 kali ATR. Data menunjukkan bahawa mekanisme penapisan tiga ini berkesan untuk mengelakkan terobosan palsu di pasaran yang bergolak, dan hanya berfungsi dalam keadaan trend sebenar.
Kuncinya adalah “array EMA yang bersih”: 25> 50> 100 apabila bermulut dan semua ke atas, 25 < 50 < 100 apabila kosong. Penapis selang memastikan trend cukup kuat untuk mengelakkan isyarat tidak sah dalam keadaan melekat serata.
Pusat strategi adalah mekanisme pengesanan penarikan balik. Penarikan balik bertopeng memerlukan harga mencapai 25 atau 50 EMA tetapi kekal di atas 100 EMA, penarikan balik kosong memerlukan harga mencapai 25 atau 50 EMA tetapi kekal di bawah 100 EMA. Reka bentuk ini lebih tepat daripada “menjatuhkan sokongan dan membeli semula” tradisional.
Tetapan tetingkap penarikan balik 15 kitaran adalah munasabah. Data retrospektif menunjukkan bahawa penarikan balik trend yang sebenar biasanya selesai dalam 10-15 kitaran, penarikan balik melebihi tetingkap masa ini sering bermaksud bahawa trend mungkin berubah.
Syarat-syarat pencetakan masuk sangat ketat: selepas pengesahan penutupan K, keseluruhan K ((bukaan, tertinggi, terendah, penutupan) mesti berada di sisi yang betul dari 25EMA. Reka bentuk ini mengelakkan pecah palsu dan bunyi dalam cakera, memastikan hanya selepas pengesahan pembalikan sebenar.
Keperluan kemasukan berbilang kepala: bukaan> 25EMA, minimum> 25EMA, penutupan> 25EMA. Keperluan kemasukan kepala kosong: bukaan <25EMA, maksimum <25EMA, penutupan <25EMA.
Tetapan kedudukan 10% secara lalai dalam strategi, kedua-dua mendapat keuntungan yang mencukupi dan mengawal risiko tunggal. Tetapan yuran 0.05% adalah lebih dekat dengan kos perdagangan sebenar, hasil pengesanan semula lebih bernilai rujukan. Mendukung perdagangan dua hala yang banyak, juga boleh memilih operasi satu arah untuk menyesuaikan diri dengan keadaan pasaran yang berbeza.
Amaran penting: Strategi ini hanya mengandungi logik masuk, tanpa menetapkan hentian hentian. Pengurusan risiko yang ketat mesti disertakan apabila digunakan secara fizikal, dan disyorkan untuk menetapkan hentian 2-3 kali ATR dan 1.5-2 kali rasio pengembalian risiko.
Strategi ini berfungsi dengan baik dalam pasaran yang jelas dan sangat sesuai untuk pembelian balik dalam keadaan unilateral. Tetapi dalam pasaran yang bergolak, syarat EMA yang diletakkan sukar dipenuhi dan peluang perdagangan agak sedikit. Ini sebenarnya adalah kelebihan strategi, mengelakkan perdagangan berlebihan dalam keadaan yang tidak menguntungkan.
Petua risiko: Pemantauan semula sejarah tidak mewakili keuntungan masa depan, strategi mempunyai risiko kerugian berturut-turut. Pasaran goyah mungkin berlaku dalam keadaan tanpa isyarat yang berpanjangan, perlu bersabar menunggu keadaan pasaran yang sesuai.
/*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))