
Jangan lagi melakukan perdagangan dengan satu garis rata. Strategi ini menggunakan 25/50/100 tiga EMA untuk membangun sistem identifikasi tren yang lengkap, yang mengharuskan EMA harus disusun secara berurutan dan bersandar pada arah yang sama, ditambah dengan persyaratan jarak minimum 0.10 kali ATR. Data menunjukkan bahwa mekanisme triple filter ini efektif untuk menghindari terobosan palsu di pasar yang bergoyang, dan hanya bekerja dalam kondisi tren yang sebenarnya.
Kuncinya adalah “arrangement EMA yang bersih”: 25>50>100 dan semua uptrend pada multihead, 25<50<100 dan semua downtrend pada head kosong. Filter interval memastikan tren cukup kuat untuk menghindari sinyal tidak valid dalam kondisi perlekatan seragam.
Inti dari strategi ini adalah mekanisme deteksi penarikan balik. Penarikan balik multi-headed membutuhkan harga untuk mencapai 25 atau 50 EMA tetapi tetap di atas 100 EMA, penarikan balik kosong membutuhkan harga untuk mencapai 25 atau 50 EMA tetapi tetap di bawah 100 EMA.
Pengaturan jendela mundur 15 siklus adalah wajar. Data retrospektif menunjukkan bahwa mundur tren yang sebenarnya biasanya selesai dalam 10-15 siklus, dan mundur lebih dari jendela waktu ini sering berarti bahwa tren mungkin berubah.
Kondisi pencetakan sangat ketat: setelah mengkonfirmasi penutupan K-line, seluruh K-line (open, top, bottom, close) harus benar-benar berada di sisi yang benar dari 25EMA. Desain ini menghindari terobosan palsu dan kebisingan dalam disk, memastikan hanya setelah pengkonfirmasi pembalikan yang benar.
Persyaratan masuk multi head: open <25EMA, minimum >25EMA, close >25EMA ≠. Persyaratan masuk head kosong: open <25EMA, maximum <25EMA, close <25EMA ≠. Metode “pengesahan seluruh K-line” ini secara signifikan meningkatkan kualitas masuk dan mengurangi transaksi yang tidak valid.
Strategi default 10% posisi setelan sedang, baik untuk mendapatkan keuntungan yang cukup dan mengendalikan risiko tunggal. . Setelan biaya 0.05% dekat dengan biaya transaksi nyata, hasil pengamatan kembali lebih berharga referensi. Mendukung perdagangan dua arah yang banyak, juga dapat memilih operasi satu arah untuk beradaptasi dengan lingkungan pasar yang berbeda.
Peringatan penting: Strategi hanya mencakup logika entry, tanpa pengaturan stop loss. Penggunaan di tempat kerja harus didukung dengan manajemen risiko yang ketat, disarankan untuk mengatur stop loss 2-3 kali ATR dan stop loss 1,5-2 kali RR.
Strategi ini bekerja dengan baik di pasar dengan tren yang jelas dan sangat cocok untuk pembelian mundur dalam situasi unilateral. Namun, dalam pasar yang bergolak, kondisi EMA yang tersusun sulit dipenuhi dan peluang perdagangan relatif sedikit. Ini sebenarnya adalah keuntungan dari strategi ini, yang mencegah over-trading dalam situasi yang tidak menguntungkan.
Petunjuk risiko: Retrospeksi historis tidak mewakili keuntungan masa depan, strategi memiliki risiko kerugian berturut-turut. Pasar bergolak dapat terjadi dalam keadaan tanpa sinyal jangka panjang, perlu bersabar menunggu kondisi pasar yang tepat.
/*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))