2025-11-14 10:21:06 +00:00
Berikut struktur/kerangka kode MQL5 untuk EA scalping MACD + Slow Stochastic sesuai deskripsi (entry setelah candle konfirmasi, SL/TP 1R/2R dengan partial 80/20, SL ke breakeven setelah TP1).
2025-11-14 10:19:05 +00:00
### Kerangka file EA (SimpleSkeleton.mq5)
```cpp
//+------------------------------------------------------------------+
//| MACD_Stoch_Scalp |
//| (kerangka MQL5) |
//+------------------------------------------------------------------+
#property copyright ""
#property version "1.0"
#property strict
#include <Trade/Trade.mqh>
CTrade trade;
//-------------------------------
// Input parameter
//-------------------------------
input ENUM_TIMEFRAMES InpTF = PERIOD_M5;
input int MACD_Fast = 12;
input int MACD_Slow = 26;
input int MACD_Signal = 9;
input int Stoch_K = 14;
input int Stoch_D = 3;
input int Stoch_Slowing = 3;
input double Stoch_OB = 80.0; // overbought
input double Stoch_OS = 20.0; // oversold
input double Risk_Percent = 1.0; // risiko per posisi (% ekuitas)
input double Min_SL_Pips = 5; // SL minimum
input bool Use_Partial = true;
input double Partial_Close_Percent = 80.0; // tutup 80% di TP1
input ulong Magic = 20251114;
// Filter biaya & sesi
input double Max_Spread_Points = 30;
input bool Use_Session_Filter = true;
input string Session_Start = "07:00:00";
input string Session_End = "23:00:00";
//-------------------------------
// Global handle & state
//-------------------------------
int macdHandle = INVALID_HANDLE;
int stochHandle = INVALID_HANDLE;
datetime lastBarTime = 0;
//+------------------------------------------------------------------+
//| OnInit |
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(Magic);
macdHandle = iMACD(_Symbol, InpTF, MACD_Fast, MACD_Slow, MACD_Signal, PRICE_CLOSE);
stochHandle = iStochastic(_Symbol, InpTF, Stoch_K, Stoch_D, Stoch_Slowing, MODE_SMA, STO_LOWHIGH);
if(macdHandle==INVALID_HANDLE || stochHandle==INVALID_HANDLE)
{
Print("Indicator handle creation failed");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| OnDeinit |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(macdHandle!=INVALID_HANDLE) IndicatorRelease(macdHandle);
if(stochHandle!=INVALID_HANDLE) IndicatorRelease(stochHandle);
}
//+------------------------------------------------------------------+
//| OnTick |
//+------------------------------------------------------------------+
void OnTick()
{
if(!IsNewBar(InpTF)) return;
// Perbarui data indikator
double macdMain[^3], macdSignal[^3], macdHist[^3];
if(!ReadMACD(3, macdMain, macdSignal, macdHist)) return;
double k[^3], d[^3];
if(!ReadStoch(3, k, d)) return;
// Filter spread & sesi
if(!CostFilterOK() || !SessionOK()) return;
// Evaluasi setup
bool buySetup=false, sellSetup=false;
EvaluateSetups(macdHist, k, d, buySetup, sellSetup);
// Eksekusi
if(buySetup) TryBuyEntry();
if(sellSetup) TrySellEntry();
// Manajemen posisi berjalan
ManageOpenPositions();
}
//-------------------------------
// Utilitas Bar Baru
//-------------------------------
bool IsNewBar(ENUM_TIMEFRAMES tf)
{
datetime t = iTime(_Symbol, tf, 0);
if(t==0) return false;
if(t!=lastBarTime)
{
lastBarTime = t;
return true;
}
return false;
}
//-------------------------------
// Baca MACD (Main/Signal/Hist)
//-------------------------------
bool ReadMACD(int count, double &main[], double &signal[], double &hist[])
{
ArraySetAsSeries(main,true);
ArraySetAsSeries(signal,true);
ArraySetAsSeries(hist,true);
int copied1 = CopyBuffer(macdHandle, 0, 0, count, main);
int copied2 = CopyBuffer(macdHandle, 1, 0, count, signal);
int copied3 = CopyBuffer(macdHandle, 2, 0, count, hist);
return (copied1==count && copied2==count && copied3==count);
}
//-------------------------------
// Baca Stochastic (K/D)
//-------------------------------
bool ReadStoch(int count, double &k[], double &d[])
{
ArraySetAsSeries(k,true);
ArraySetAsSeries(d,true);
int copiedK = CopyBuffer(stochHandle, 0, 0, count, k);
int copiedD = CopyBuffer(stochHandle, 1, 0, count, d);
return (copiedK==count && copiedD==count);
}
//-------------------------------
// Filter biaya & sesi
//-------------------------------
bool CostFilterOK()
{
double spr = 0.0;
if(!SymbolInfoDouble(_Symbol, SYMBOL_SPREAD, spr)) return false;
// SYMBOL_SPREAD sudah dalam points pada MT5
return spr <= Max_Spread_Points;
}
bool SessionOK()
{
if(!Use_Session_Filter) return true;
datetime now = TimeCurrent();
MqlDateTime mt; TimeToStruct(now, mt);
datetime s = StringToTime(Session_Start);
datetime e = StringToTime(Session_End);
// Normalisasi ke hari berjalan
MqlDateTime ms; TimeToStruct(s, ms);
ms.year = mt.year; ms.mon = mt.mon; ms.day = mt.day;
MqlDateTime me; TimeToStruct(e, me);
me.year = mt.year; me.mon = mt.mon; me.day = mt.day;
datetime start = StructToTime(ms);
datetime end = StructToTime(me);
if(start<=end) return (now>=start && now<=end);
// sesi lintas tengah malam
return (now>=start || now<=end);
}
//-------------------------------
// Evaluasi setup entry
// Aturan (ringkas) dari dokumen:
// Long: hist > 0, sempat melemah lalu rebound naik (sebelum 0),
// Stoch <=20 dan cross up; entry setelah candle konfirmasi tutup.
// Short kebalikan (hist < 0, rebound turun, Stoch >=80 cross down).
//-------------------------------
void EvaluateSetups(const double &macdHist[], const double &k[], const double &d[],
bool &buySetup, bool &sellSetup)
{
buySetup=false; sellSetup=false;
// Sinyal MACD histogram
bool macdUpBias = (macdHist[^0] > 0.0);
bool macdDownBias = (macdHist[^0] < 0.0);
// Deteksi "melemah lalu rebound" sederhana memakai tiga bar
2025-11-14 10:21:06 +00:00
bool reboundUp = (macdHist[^2] > macdHist && macdHist < macdHist[^0] && macdHist[^0] > 0.0);
bool reboundDown = (macdHist[^2] < macdHist && macdHist > macdHist[^0] && macdHist[^0] < 0.0);
2025-11-14 10:19:05 +00:00
// Stochastic cross
2025-11-14 10:21:06 +00:00
bool stochCrossUp = (k < d && k[^0] > d[^0]);
bool stochCrossDown = (k > d && k[^0] < d[^0]);
2025-11-14 10:19:05 +00:00
bool stochOS = (k[^0] <= Stoch_OS && d[^0] <= Stoch_OS);
bool stochOB = (k[^0] >= Stoch_OB && d[^0] >= Stoch_OB);
// Long
if(macdUpBias && reboundUp && stochOS && stochCrossUp)
buySetup = true;
// Short
if(macdDownBias && reboundDown && stochOB && stochCrossDown)
sellSetup = true;
}
//-------------------------------
// Position sizing dan SL/TP
//-------------------------------
double PipToPoints(double pips)
{
// Untuk simbol dengan fractional pip, pips ke points = pips * 10 (umum pada forex 5-digit)
// Sesuaikan jika perlu.
return pips * 10.0;
}
double CalcSLPoints(bool isBuy)
{
// SL berdasar base candle (bar konfirmasi, index 1)
double baseHigh = iHigh(_Symbol, InpTF, 1);
double baseLow = iLow(_Symbol, InpTF, 1);
double slPips = MathMax(Min_SL_Pips, 0.0);
if(isBuy)
return ( (NormalizeDouble((iClose(_Symbol, InpTF, 1) - (baseLow - (slPips*_Point*10.0))), _Digits)) / _Point ); // pendekatan
else
return ( (NormalizeDouble(((baseHigh + (slPips*_Point*10.0)) - iClose(_Symbol, InpTF, 1)), _Digits)) / _Point );
}
// Versi sederhana: hitung lot berdasar Risk_Percent dan SL points aproksimasi
double CalcLots(double sl_points)
{
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double riskMoney = equity * (Risk_Percent/100.0);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tickValue<=0 || tickSize<=0 || sl_points<=0) return 0.01;
// Perkiraan nilai per point per lot
double pointValuePerLot = tickValue * (_Point / tickSize);
double lots = riskMoney / (sl_points * pointValuePerLot);
// Sesuaikan ke step & min lot
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
lots = MathMax(minLot, MathMin(maxLot, MathFloor(lots/lotStep)*lotStep));
return lots;
}
//-------------------------------
// Eksekusi order
//-------------------------------
void TryBuyEntry()
{
// Hitung SL berbasis base candle: 1 pip di bawah low
double baseLow = iLow(_Symbol, InpTF, 1);
double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double slPrice = baseLow - (Min_SL_Pips * _Point * 10.0);
if(slPrice<=0 || entry<=0) return;
double sl_points = (entry - slPrice) / _Point;
if(sl_points < PipToPoints(Min_SL_Pips)) sl_points = PipToPoints(Min_SL_Pips);
double lots = CalcLots(sl_points);
if(lots <= 0) return;
double tp1Price = entry + sl_points * _Point; // 1R
double tp2Price = entry + (2.0 * sl_points) * _Point; // 2R
trade.Buy(lots, NULL, entry, slPrice, 0.0, "MACD_Stoch_LONG");
// Partial: pasang TP1 via pending take-profit manual atau kelola via ManageOpenPositions()
// Di sini, kita kelola via manajemen per-tick untuk fleksibilitas.
}
void TrySellEntry()
{
double baseHigh = iHigh(_Symbol, InpTF, 1);
double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double slPrice = baseHigh + ((Min_SL_Pips) * _Point * 10.0); // tambahkan spread bisa diperhitungkan di Min_SL_Pips
if(slPrice<=0 || entry<=0) return;
double sl_points = (slPrice - entry) / _Point;
if(sl_points < PipToPoints(Min_SL_Pips)) sl_points = PipToPoints(Min_SL_Pips);
double lots = CalcLots(sl_points);
if(lots <= 0) return;
double tp1Price = entry - sl_points * _Point; // 1R
double tp2Price = entry - (2.0 * sl_points) * _Point; // 2R
trade.Sell(lots, NULL, entry, slPrice, 0.0, "MACD_Stoch_SHORT");
}
//-------------------------------
// Manajemen posisi: partial 80/20, BE, TP2
// Catatan: contoh ini mengilustrasikan logika; implementasi penuh perlu tracking tiket/order.
//-------------------------------
void ManageOpenPositions()
{
int total = PositionsTotal();
for(int i=total-1; i>=0; --i)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetInteger(POSITION_MAGIC)!= (long)Magic) continue;
string sym = PositionGetString(POSITION_SYMBOL);
long type = PositionGetInteger(POSITION_TYPE);
double priceOpen = PositionGetDouble(POSITION_PRICE_OPEN);
double sl = PositionGetDouble(POSITION_SL);
double volume = PositionGetDouble(POSITION_VOLUME);
double bid = SymbolInfoDouble(sym, SYMBOL_BID);
double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
// Hitung 1R berdasarkan SL jarak awal
double r_points = 0.0;
if(type==POSITION_TYPE_BUY) r_points = (priceOpen - sl) / _Point;
if(type==POSITION_TYPE_SELL) r_points = (sl - priceOpen) / _Point;
if(r_points<=0) continue;
// TP1 & TP2 target
double tp1 = 0.0, tp2 = 0.0;
if(type==POSITION_TYPE_BUY)
{ tp1 = priceOpen + r_points*_Point; tp2 = priceOpen + 2.0*r_points*_Point; }
else
{ tp1 = priceOpen - r_points*_Point; tp2 = priceOpen - 2.0*r_points*_Point; }
// Deteksi TP1 tercapai dan belum partial
// Perlu mekanisme flag; demo sederhana pakai volume sebagai indikator partial.
bool tp1Hit=false, tp2Hit=false;
if(type==POSITION_TYPE_BUY) { tp1Hit = (bid>=tp1); tp2Hit = (bid>=tp2); }
if(type==POSITION_TYPE_SELL) { tp1Hit = (ask<=tp1); tp2Hit = (ask<=tp2); }
// TP2: tutup semua sisa
if(tp2Hit)
{
trade.PositionClose(ticket);
continue;
}
// TP1: partial close 80% dan SL -> BE
if(tp1Hit)
{
if(Use_Partial && volume > SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN))
{
double volClose = volume * (Partial_Close_Percent/100.0);
double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
volClose = MathMax(step, MathFloor(volClose/step)*step);
trade.PositionClosePartial(ticket, volClose);
}
// Pindahkan SL ke breakeven
if(type==POSITION_TYPE_BUY) trade.PositionModify(ticket, priceOpen, 0.0);
if(type==POSITION_TYPE_SELL) trade.PositionModify(ticket, priceOpen, 0.0);
}
}
}
```
Penjelasan singkat:
2025-11-14 10:21:06 +00:00
- Struktur mengikuti event MQL5: OnInit menyiapkan handle indikator, OnTick memproses sinyal hanya saat bar M5 baru, lalu mengevaluasi aturan MACD histogram + Stochastic dan mengeksekusi order setelah candle konfirmasi.
- Manajemen risiko menerapkan SL dari base candle; target 1R untuk partial 80% lalu SL ke breakeven dan target akhir 2R, sesuai aturan di dokumen.
- Kerangka menyertakan filter spread dan sesi trading, serta placeholder kalkulasi lot berbasis risiko. Silakan sesuaikan konversi pips→points sesuai simbol yang dipakai.
2025-11-14 10:19:05 +00:00
Catatan implementasi:
2025-11-14 10:21:06 +00:00
- Untuk produksi, tambahkan tracking state per posisi (custom comment/tag, map ticket→R, status partial) agar logika TP1/TP2 lebih andal.
- Sesuaikan deteksi “rebound sebelum nol” MACD histogram dengan aturan yang lebih presisi jika diperlukan (mis. deteksi local min/max).