Update v5.10: Fix startup bugs, add Pin Bar, dynamic volatility filter, and trailing step safeguards
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user