//+------------------------------------------------------------------+ //| AutoSL_Manager.mq5 | //| Auto SL 70 pip, SL+ after 50 pip, lock 10 pip, multi-entry sync | //+------------------------------------------------------------------+ #property copyright "Dwi Yulianto" #property version "1.21" #property strict #include CTrade trade; bool processing = false; enum ENUM_PIP_MODE { PIP_MODE_AUTO = 0, // Otomatis (1 Pip = 10 Point / $0.10 di Gold 2-digit) PIP_MODE_10_POINTS = 1, // Paksa 1 Pip = 10 Point (Standar Forex 5-digit & Gold 2/3-digit) PIP_MODE_1_POINT = 2, // Paksa 1 Pip = 1 Point PIP_MODE_CUSTOM = 3 // Nilai custom (isi CustomPipValue di bawah) }; input group "=== SETTING JARAK (PIPS) ===" input double InitialSLPips = 70.0; // SL awal dari entry master (70 pips = 700 points) input double SLPlusTriggerPips = 50.0; // Running profit pemicu SL+ (50 pips = 500 points) input double SLPlusLockPips = 10.0; // Profit yang dikunci di atas/bawah entry (10 pips = 100 points) input group "=== METODE PERHITUNGAN PIP ===" input ENUM_PIP_MODE PipCalculationMode = PIP_MODE_AUTO; // Mode kalkulasi pip input double CustomPipValue = 0.10; // Nilai per 1 pip jika mode CUSTOM (contoh Gold: 0.10) input group "=== MULTI ENTRY ===" input bool SyncToFirstActive = true; // Entry ke-2, ke-3 dst ikut level SL entry ke-1 aktif input bool NeverLoosenSL = true; // Jangan mundurkan SL jika posisi sudah lebih aman input group "=== FILTER POSISI ===" input bool CurrentSymbolOnly = true; // True = Chart ini saja | False = Semua Pair input ulong MagicNumber = 0; // 0 = Semua posisi (termasuk entry manual) //+------------------------------------------------------------------+ //| Menghitung nilai harga 1 Pip sebenarnya | //+------------------------------------------------------------------+ double PipSize(const string symbol) { if(PipCalculationMode == PIP_MODE_CUSTOM && CustomPipValue > 0.0) return CustomPipValue; const int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); const double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if(PipCalculationMode == PIP_MODE_1_POINT) return point; if(PipCalculationMode == PIP_MODE_10_POINTS) return point * 10.0; // AUTO DETECTION: // 1. Simbol Emas / Gold / XAU: // - 1 Pip standar Gold selalu = $0.10 (10 cents) // - Broker 2-digit (point=0.01) -> 0.10 = 10 point // - Broker 3-digit (point=0.001) -> 0.10 = 100 point string symUpper = symbol; StringToUpper(symUpper); if(StringFind(symUpper, "XAU") >= 0 || StringFind(symUpper, "GOLD") >= 0) { return 0.10; } // 2. Forex Pairs: // - Forex 5-digit (EURUSD 1.08500, point 0.00001) -> 1 pip = 0.00010 (10 points) // - Forex 3-digit JPY (USDJPY 155.200, point 0.001) -> 1 pip = 0.010 (10 points) if(digits == 5 || digits == 3 || digits == 1) return point * 10.0; if(digits == 2) return point * 10.0; if(digits == 4) return point; return point * 10.0; } //+------------------------------------------------------------------+ double NormalizeStopPrice(const string symbol, const ENUM_POSITION_TYPE type, const double price) { const int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); const double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); if(tickSize <= 0.0) return NormalizeDouble(price, digits); double ticks = price / tickSize; ticks = (type == POSITION_TYPE_BUY) ? MathFloor(ticks) : MathCeil(ticks); return NormalizeDouble(ticks * tickSize, digits); } //+------------------------------------------------------------------+ bool IsManagedSelectedPosition() { const string symbol = PositionGetString(POSITION_SYMBOL); if(CurrentSymbolOnly && symbol != _Symbol) return false; const ulong magic = (ulong)PositionGetInteger(POSITION_MAGIC); return MagicNumber == 0 || magic == MagicNumber; } //+------------------------------------------------------------------+ bool IsBetterStop(const ENUM_POSITION_TYPE type, const double candidate, const double current, const double point) { if(current <= 0.0) return true; if(type == POSITION_TYPE_BUY) return candidate > current + point * 0.5; return candidate < current - point * 0.5; } //+------------------------------------------------------------------+ bool IsValidStopNow(const string symbol, const ENUM_POSITION_TYPE type, const double stopLoss) { const double point = SymbolInfoDouble(symbol, SYMBOL_POINT); const int stopsLevel = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL); const int freezeLevel = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_FREEZE_LEVEL); const double minimumDistance = MathMax(stopsLevel, freezeLevel) * point; const double bid = SymbolInfoDouble(symbol, SYMBOL_BID); const double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); if(type == POSITION_TYPE_BUY) return stopLoss < bid - minimumDistance; return stopLoss > ask + minimumDistance; } //+------------------------------------------------------------------+ bool ModifyStopLoss(const ulong ticket, const double targetStopLoss, const bool allowLoosening) { if(!PositionSelectByTicket(ticket)) return false; const string symbol = PositionGetString(POSITION_SYMBOL); const ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); const double currentStopLoss = PositionGetDouble(POSITION_SL); const double takeProfit = PositionGetDouble(POSITION_TP); const double point = SymbolInfoDouble(symbol, SYMBOL_POINT); const double normalizedStop = NormalizeStopPrice(symbol, type, targetStopLoss); if(MathAbs(normalizedStop - currentStopLoss) < point * 0.5) return true; if(!allowLoosening && !IsBetterStop(type, normalizedStop, currentStopLoss, point)) return true; if(!IsValidStopNow(symbol, type, normalizedStop)) return false; ResetLastError(); const bool requestSent = trade.PositionModify(ticket, normalizedStop, takeProfit); const uint retcode = trade.ResultRetcode(); const bool completed = requestSent && (retcode == TRADE_RETCODE_DONE || retcode == TRADE_RETCODE_NO_CHANGES); if(completed) { PrintFormat("SL Diubah: Ticket=%I64u Symbol=%s SL=%.*f", ticket, symbol, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS), normalizedStop); return true; } return false; } //+------------------------------------------------------------------+ ulong FindFirstActiveTicket(const string symbol, const ENUM_POSITION_TYPE type) { ulong masterTicket = 0; long oldestTimeMsc = 0; for(int index = 0; index < PositionsTotal(); index++) { const ulong ticket = PositionGetTicket(index); if(ticket == 0 || !PositionSelectByTicket(ticket)) continue; if(!IsManagedSelectedPosition()) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != type) continue; const long timeMsc = PositionGetInteger(POSITION_TIME_MSC); if(masterTicket == 0 || timeMsc < oldestTimeMsc || (timeMsc == oldestTimeMsc && ticket < masterTicket)) { masterTicket = ticket; oldestTimeMsc = timeMsc; } } return masterTicket; } //+------------------------------------------------------------------+ double CalculateDesiredMasterStop(const ulong masterTicket) { if(!PositionSelectByTicket(masterTicket)) return 0.0; const string symbol = PositionGetString(POSITION_SYMBOL); const ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); const double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); const double currentStop = PositionGetDouble(POSITION_SL); const double pip = PipSize(symbol); const double bid = SymbolInfoDouble(symbol, SYMBOL_BID); const double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); if(pip <= 0.0) return currentStop; double desiredStop = currentStop; // 1. Initial SL 70 pips (70 * pip) if(desiredStop <= 0.0 && InitialSLPips > 0.0) { desiredStop = (type == POSITION_TYPE_BUY) ? openPrice - (InitialSLPips * pip) : openPrice + (InitialSLPips * pip); } // 2. Cek running profit dalam Pips const double runningPips = (type == POSITION_TYPE_BUY) ? (bid - openPrice) / pip : (openPrice - ask) / pip; // 3. Pasang SL+ jika profit sudah >= 50 pips (SL digeser ke +10 pips) if(SLPlusTriggerPips > 0.0 && runningPips >= SLPlusTriggerPips) { const double slPlusPrice = (type == POSITION_TYPE_BUY) ? openPrice + (SLPlusLockPips * pip) : openPrice - (SLPlusLockPips * pip); const double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if(IsBetterStop(type, slPlusPrice, desiredStop, point)) desiredStop = slPlusPrice; } return NormalizeStopPrice(symbol, type, desiredStop); } //+------------------------------------------------------------------+ void ManageSinglePosition(const ulong ticket) { const double desiredStop = CalculateDesiredMasterStop(ticket); if(desiredStop > 0.0) ModifyStopLoss(ticket, desiredStop, !NeverLoosenSL); } //+------------------------------------------------------------------+ void ManagePositionGroup(const string symbol, const ENUM_POSITION_TYPE type, const ulong masterTicket) { double desiredMasterStop = CalculateDesiredMasterStop(masterTicket); if(desiredMasterStop > 0.0) { ModifyStopLoss(masterTicket, desiredMasterStop, false); } if(!PositionSelectByTicket(masterTicket)) return; const double activeMasterStop = PositionGetDouble(POSITION_SL); if(activeMasterStop <= 0.0) return; // Sinkronkan semua entry berikutnya ke SL master aktif for(int index = 0; index < PositionsTotal(); index++) { const ulong ticket = PositionGetTicket(index); if(ticket == 0 || ticket == masterTicket || !PositionSelectByTicket(ticket)) continue; if(!IsManagedSelectedPosition()) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != type) continue; ModifyStopLoss(ticket, activeMasterStop, !NeverLoosenSL); } } //+------------------------------------------------------------------+ void ProcessPositions() { if(processing) return; processing = true; for(int index = 0; index < PositionsTotal(); index++) { const ulong ticket = PositionGetTicket(index); if(ticket == 0 || !PositionSelectByTicket(ticket)) continue; if(!IsManagedSelectedPosition()) continue; if(!SyncToFirstActive) { ManageSinglePosition(ticket); continue; } const string symbol = PositionGetString(POSITION_SYMBOL); const ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); const ulong masterTicket = FindFirstActiveTicket(symbol, type); if(masterTicket == ticket) ManagePositionGroup(symbol, type, masterTicket); } processing = false; } //+------------------------------------------------------------------+ int OnInit() { if(InitialSLPips < 0.0 || SLPlusTriggerPips < 0.0 || SLPlusLockPips < 0.0) { Print("Parameter error: Jarak pips tidak boleh negatif."); return INIT_PARAMETERS_INCORRECT; } trade.SetAsyncMode(false); EventSetTimer(1); double pip = PipSize(_Symbol); PrintFormat("AutoSL_Manager v1.21 Aktif di %s | 1 Pip = %.*f price units", _Symbol, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS), pip); PrintFormat("Settings: SL awal %.1f pips = jarak %.*f | Trigger %.1f pips = jarak %.*f | SL+ %.1f pips = jarak %.*f", InitialSLPips, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS), InitialSLPips * pip, SLPlusTriggerPips, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS), SLPlusTriggerPips * pip, SLPlusLockPips, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS), SLPlusLockPips * pip); ProcessPositions(); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { ProcessPositions(); } //+------------------------------------------------------------------+ void OnTimer() { ProcessPositions(); } //+------------------------------------------------------------------+ void OnTrade() { ProcessPositions(); } //+------------------------------------------------------------------+