From b90358be65f84d5acd2941b0de08525a24e16872 Mon Sep 17 00:00:00 2001 From: anggol23 Date: Mon, 25 May 2026 08:29:56 +0700 Subject: [PATCH] Update v5.10: Fix startup bugs, add Pin Bar, dynamic volatility filter, and trailing step safeguards --- README.md | 21 +++++++++++ hedge.mq5 | 106 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 105 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 3518755..5c728fb 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ - [Arsitektur & Penjelasan Kode](#arsitektur--penjelasan-kode) - [Sistem Proteksi Anti-MC](#sistem-proteksi-anti-mc) - [Cara Backtest](#cara-backtest) +- [Pembaruan Terakhir (Changelog)](#pembaruan-terakhir-changelog) - [Disclaimer](#disclaimer) --- @@ -290,6 +291,26 @@ Ditambah proteksi entry: --- +## Pembaruan Terakhir (Changelog v5.10) + +EA ini telah diperbarui untuk meningkatkan stabilitas sistem (*system safeguard*), profitabilitas strategi, serta keamanan transaksi saat digunakan langsung (*live trading*). Berikut detail pembaruan: + +### 1. Perbaikan Bug Stabilitas Sistem +* **Fix Double-Counting OnTrade:** Variabel penanda transaksi `lastDealsTotal` dipindahkan dari variabel lokal `static` menjadi variabel global dan diinisialisasi secara tepat pada `OnInit()`. Hal ini menghentikan bug pelacakan kekalahan beruntun (*consecLosses*) yang terhitung ganda secara keliru saat EA pertama kali berjalan. +* **Filter Entry Tengah Bar:** Nilai awal `lastSwingBar` diinisialisasi dengan open time bar berjalan saat EA dipasang. EA kini akan menunggu pembentukan bar baru selesai sebelum mengevaluasi sinyal entry, menghindari masuk posisi secara acak di tengah bar. +* **Lookback Kekalahan Beruntun:** Fungsi `CountRecentLosses()` sekarang memindai history transaksi 30 hari ke belakang (sebelumnya hanya awal hari ini). Membuat fitur pemotongan risiko otomatis saat beruntun rugi (*dynamic risk reduction*) bekerja akurat lintas hari. + +### 2. Peningkatan Profitabilitas (Swing & Scalp) +* **Integrasi Sinyal Pin Bar (Swing):** Menambahkan deteksi otomatis pola rejection candle **Pin Bar** (Hammer/Shooting Star) sebagai pemicu alternatif untuk entry swing. EA kini dapat mengambil peluang pembalikan arah harga (*pullback*) yang kuat pada level support/resistance utama. +* **Optimasi RSI untuk Pullback Swing:** Melonggarkan filter RSI ke batas bawah **35.0** (Buy) dan batas atas **65.0** (Sell) saat tren dikonfirmasi kuat. Memberikan lebih banyak peluang bagi EA untuk melakukan entry pada koreksi harga yang sehat. +* **Filter Volatilitas Dinamis (Adaptif):** Mengganti filter statis `30 points` yang kaku dengan pemeriksaan dinamis `IsVolatilityOk()`. EA membandingkan volatilitas saat ini dengan rata-rata 50 bar terakhir. EA akan memblokir entry jika pasar mati/sepi (di bawah 60% rata-rata) demi menghindari biaya spread broker yang lebar. + +### 3. Pengamanan Transaksi Live (Anti-Spam Broker) +* **Trailing Step Dinamis:** Menghindari pengiriman request modifikasi Stop Loss (SL) secara terus-menerus ke server broker (*broker spamming*). Trailing stop kini diatur dengan langkah dinamis minimal **10% ATR** untuk Swing dan **5% ATR** untuk Scalping. +* **Koreksi Validasi SL/TP:** Meningkatkan fungsi `ValidateSLTP()` agar tidak terjadi penolakan order oleh broker saat EA memproses transaksi tanpa SL/TP statis (SL/TP nol). + +--- + ## Disclaimer > **PERINGATAN**: Trading forex/CFD memiliki risiko tinggi. EA ini **TIDAK menjamin profit**. Past performance (backtest) TIDAK menjamin hasil di masa depan. Selalu test di **akun demo** terlebih dahulu. Gunakan modal yang siap Anda risikokan. Pembuat tidak bertanggung jawab atas kerugian yang terjadi. diff --git a/hedge.mq5 b/hedge.mq5 index 1ae30fd..7aa0964 100644 --- a/hedge.mq5 +++ b/hedge.mq5 @@ -107,6 +107,7 @@ datetime lastSwingBar = 0; datetime lastLossTime = 0; int consecLosses = 0; datetime lastTradeDay = 0; // Track hari terakhir untuk OnTrade reset +int lastDealsTotal = 0; // Track total deal terakhir untuk OnTrade string gvPeakEquity; @@ -218,6 +219,12 @@ int OnInit() CountRecentLosses(); ParseNewsHours(); + // Inisialisasi tracking bar dan trade history untuk mencegah double-counting & entry tengah bar saat startup + lastSwingBar = iTime(_Symbol, _Period, 0); + lastTradeDay = iTime(_Symbol, PERIOD_D1, 0); + HistorySelect(lastTradeDay, TimeCurrent()); + lastDealsTotal = HistoryDealsTotal(); + Print("HUP v5.0 DUAL ENGINE | Filling=", EnumToString(DetectFilling()), " | Scalp=", EnableScalp ? "ON" : "OFF", " | ConsecLoss=", consecLosses); @@ -242,8 +249,8 @@ void OnDeinit(const int reason) void CountRecentLosses() { consecLosses = 0; - datetime dayStart = iTime(_Symbol, PERIOD_D1, 0); - HistorySelect(dayStart, TimeCurrent()); + // Scan history 30 hari ke belakang untuk consec losses lintas hari + HistorySelect(TimeCurrent() - 30 * 24 * 3600, TimeCurrent()); int total = HistoryDealsTotal(); for(int i = total - 1; i >= 0; i--) @@ -513,7 +520,7 @@ bool LoadIndicators() if(CopyBuffer(adx_handle, 1, 0, 3, plus_di) < 3) return false; if(CopyBuffer(adx_handle, 2, 0, 3, minus_di) < 3) return false; if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_val) < 3) return false; - if(CopyBuffer(atr_handle, 0, 0, 3, atr_val) < 3) return false; + if(CopyBuffer(atr_handle, 0, 0, 50, atr_val) < 50) return false; if(CopyBuffer(bb_handle, 1, 0, 3, bb_upper) < 3) return false; // Upper if(CopyBuffer(bb_handle, 2, 0, 3, bb_lower) < 3) return false; // Lower if(CopyBuffer(bb_handle, 0, 0, 3, bb_mid) < 3) return false; // Middle @@ -521,6 +528,20 @@ bool LoadIndicators() return true; } +//============================================================ +// VOLATILITY FILTER (DYNAMIC ATR) +//============================================================ +bool IsVolatilityOk() +{ + int totalATR = ArraySize(atr_val); + if(totalATR < 10) return true; // fallback jika data kurang + double sum = 0; + for(int i = 0; i < totalATR; i++) sum += atr_val[i]; + double avgATR = sum / totalATR; + // Volatilitas OK jika ATR saat ini >= 60% dari rata-rata 50 bar terakhir + return (atr_val[1] >= avgATR * 0.6); +} + //============================================================ // LOT CALCULATION //============================================================ @@ -560,11 +581,11 @@ bool ValidateSLTP(double price, double sl, double tp, int direction) { if(direction == +1) { - if((price - sl) < minDist || (tp - price) < minDist) return false; + if((sl > 0 && (price - sl) < minDist) || (tp > 0 && (tp - price) < minDist)) return false; } else { - if((sl - price) < minDist || (price - tp) < minDist) return false; + if((sl > 0 && (sl - price) < minDist) || (tp > 0 && (price - tp) < minDist)) return false; } } @@ -575,8 +596,8 @@ bool ValidateSLTP(double price, double sl, double tp, int direction) { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); - if(direction == +1 && (ask - sl) < freezeDist) return false; - if(direction == -1 && (sl - bid) < freezeDist) return false; + if(direction == +1 && sl > 0 && (ask - sl) < freezeDist) return false; + if(direction == -1 && sl > 0 && (sl - bid) < freezeDist) return false; } return true; } @@ -600,6 +621,36 @@ int GetTrend() bool TrendStrong() { return adx_val[0] >= ADX_Min; } +//============================================================ +// DETECT PIN BAR PATTERN +//============================================================ +bool IsPinBar(int barIndex, int direction) +{ + double open = iOpen(_Symbol, _Period, barIndex); + double close = iClose(_Symbol, _Period, barIndex); + double high = iHigh(_Symbol, _Period, barIndex); + double low = iLow(_Symbol, _Period, barIndex); + + double range = high - low; + if(range <= 0) return false; + + double body = MathAbs(close - open); + + if(direction == +1) // Bullish Pin Bar (Hammer) + { + double lowerShadow = MathMin(open, close) - low; + double upperShadow = high - MathMax(open, close); + return (lowerShadow >= range * 0.6 && body <= range * 0.3 && upperShadow <= range * 0.2); + } + else if(direction == -1) // Bearish Pin Bar (Shooting Star) + { + double lowerShadow = MathMin(open, close) - low; + double upperShadow = high - MathMax(open, close); + return (upperShadow >= range * 0.6 && body <= range * 0.3 && lowerShadow <= range * 0.2); + } + return false; +} + //============================================================ // SWING BUY SIGNAL //============================================================ @@ -612,7 +663,8 @@ bool SwingBuySignal() if(!TrendStrong()) return false; if(plus_di[0] <= minus_di[0]) return false; - if(rsi_val[0] >= RSI_OB || rsi_val[0] < 40.0) return false; + // Dioptimalkan menjadi 35.0 (sebelumnya 40.0) agar bisa menangkap pullback sehat yang lebih dalam + if(rsi_val[0] >= RSI_OB || rsi_val[0] < 35.0) return false; double open1 = iOpen(_Symbol, _Period, 1); double close1 = iClose(_Symbol, _Period, 1); @@ -631,7 +683,8 @@ bool SwingBuySignal() bool breakout = close1 > iHigh(_Symbol, _Period, 2); bool engulfing = (body2 < 0) && (close1 > open2) && (open1 < close2); - return (breakout || engulfing); + bool pinbar = IsPinBar(1, +1); + return (breakout || engulfing || pinbar); } //============================================================ @@ -646,7 +699,8 @@ bool SwingSellSignal() if(!TrendStrong()) return false; if(minus_di[0] <= plus_di[0]) return false; - if(rsi_val[0] <= RSI_OS || rsi_val[0] > 60.0) return false; + // Dioptimalkan menjadi 65.0 (sebelumnya 60.0) agar bisa menangkap pullback sehat yang lebih tinggi + if(rsi_val[0] <= RSI_OS || rsi_val[0] > 65.0) return false; double open1 = iOpen(_Symbol, _Period, 1); double close1 = iClose(_Symbol, _Period, 1); @@ -665,7 +719,8 @@ bool SwingSellSignal() bool breakout = close1 < iLow(_Symbol, _Period, 2); bool engulfing = (body2 < 0) && (close1 < open2) && (open1 > close2); - return (breakout || engulfing); + bool pinbar = IsPinBar(1, -1); + return (breakout || engulfing || pinbar); } //============================================================ @@ -831,7 +886,8 @@ void ManageSwing() if(type==POSITION_TYPE_BUY) { double nSL = NormalizePrice(price - trailDist); - if(nSL > sl && nSL > openPrice) + // Trailing step minimal 10% dari ATR untuk mencegah spamming order modifikasi ke broker + if((nSL - sl >= atr * 0.1 || sl == 0) && nSL > openPrice) { trade.SetExpertMagicNumber(MagicSwing); trade.PositionModify(ticket, nSL, newTP); @@ -845,7 +901,8 @@ void ManageSwing() else { double nSL = NormalizePrice(price + trailDist); - if((nSL < sl || sl == 0) && nSL < openPrice) + // Trailing step minimal 10% dari ATR + if((sl - nSL >= atr * 0.1 || sl == 0) && nSL < openPrice) { trade.SetExpertMagicNumber(MagicSwing); trade.PositionModify(ticket, nSL, newTP); @@ -926,22 +983,26 @@ void ManageScalp() // Adaptive Trailing + Dynamic TP toward BB mid if(profitDist >= trailStart) { - // Dynamic TP: geser ke BB mid jika lebih jauh dari TP awal + // Dynamic TP: geser ke BB mid secara dinamis mengikuti pergerakan band double newTP = tp; - if(type==POSITION_TYPE_BUY && bbMid[0] > tp && bbMid[0] > price) + if(type==POSITION_TYPE_BUY && bbMid[0] > price) newTP = NormalizePrice(bbMid[0]); - else if(type==POSITION_TYPE_SELL && bbMid[0] < tp && bbMid[0] < price) + else if(type==POSITION_TYPE_SELL && bbMid[0] < price) newTP = NormalizePrice(bbMid[0]); + // Hanya modifikasi jika perbedaan TP cukup signifikan untuk mencegah spamming order ke broker + bool updateTP = (newTP != tp && MathAbs(tp - newTP) >= atr * 0.1); + if(type==POSITION_TYPE_BUY) { double nSL = NormalizePrice(price - trailDist); - if(nSL > sl && nSL > openPrice) + // Trailing step minimal 5% dari ATR untuk scalp + if((nSL - sl >= atr * 0.05 || sl == 0) && nSL > openPrice) { trade.SetExpertMagicNumber(MagicScalp); trade.PositionModify(ticket, nSL, newTP); } - else if(newTP != tp && newTP > tp) + else if(updateTP) { trade.SetExpertMagicNumber(MagicScalp); trade.PositionModify(ticket, sl, newTP); @@ -950,12 +1011,13 @@ void ManageScalp() else { double nSL = NormalizePrice(price + trailDist); - if((nSL < sl || sl == 0) && nSL < openPrice) + // Trailing step minimal 5% dari ATR untuk scalp + if((sl - nSL >= atr * 0.05 || sl == 0) && nSL < openPrice) { trade.SetExpertMagicNumber(MagicScalp); trade.PositionModify(ticket, nSL, newTP); } - else if(newTP != tp && newTP < tp) + else if(updateTP) { trade.SetExpertMagicNumber(MagicScalp); trade.PositionModify(ticket, sl, newTP); @@ -970,7 +1032,6 @@ void ManageScalp() //============================================================ void OnTrade() { - static int lastDealsTotal = 0; datetime dayStart = iTime(_Symbol, PERIOD_D1, 0); // Reset counter saat ganti hari (fix day-boundary bug) @@ -1052,7 +1113,8 @@ void OnTick() if(!LoadIndicators()) return; double atr = atr_val[1]; - if(atr < _Point * 30) return; // ATR terlalu kecil + if(atr < _Point * 10) return; // Proteksi batas spread dasar (sangat kecil) + if(!IsVolatilityOk()) return; // Filter volatilitas adaptif double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);