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 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).