383 lines
No EOL
13 KiB
Markdown
383 lines
No EOL
13 KiB
Markdown
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).
|
|
|
|
### 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
|
|
bool reboundUp = (macdHist[^2] > macdHist && macdHist < macdHist[^0] && macdHist[^0] > 0.0);
|
|
bool reboundDown = (macdHist[^2] < macdHist && macdHist > macdHist[^0] && macdHist[^0] < 0.0);
|
|
|
|
// Stochastic cross
|
|
bool stochCrossUp = (k < d && k[^0] > d[^0]);
|
|
bool stochCrossDown = (k > d && k[^0] < d[^0]);
|
|
|
|
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:
|
|
|
|
- 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.
|
|
|
|
Catatan implementasi:
|
|
|
|
- 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). |