EA MT5: Mudahkan Pengaturan Posisi Layering

EA ini dibuat untuk memudahkan pengaturan beberapa posisi yang dibuka secara layering.

Contoh Sederhana

Misalnya kita membuka 4 posisi BUY dengan harga berbeda:

BUY 1 → 4423
BUY 2 → 4425
BUY 3 → 4426
BUY 4 → 4429

Biasanya, jika ingin mengubah SL, kita harus mengaturnya satu per satu. Dengan EA ini, cukup geser SL pada satu posisi.

Misalnya SL digeser ke 4400, maka otomatis:

BUY 1 → SL 4400
BUY 2 → SL 4400
BUY 3 → SL 4400
BUY 4 → SL 4400

Jadi, cukup geser satu SL, semua SL posisi lainnya otomatis mengikuti.

TP Juga Bisa Mengikuti

Hal yang sama berlaku untuk TP. Jika TP pada satu posisi digeser, TP posisi lainnya juga otomatis mengikuti.

Jadi pengaturannya sederhana:

  • Geser SL → semua SL mengikuti.
  • Geser TP → semua TP mengikuti.
  • BUY → hanya mengikuti BUY.
  • SELL → hanya mengikuti SELL.

Risk/Reward 1:1

EA ini juga dapat digunakan dengan konsep Risk/Reward 1:1. Contohnya:

Risk   = 20 poin
Reward = 20 poin

RR = 1:1

Dengan begitu, jarak risiko dan target dibuat sama.

Kesimpulan

EA ini cocok untuk trader yang menggunakan posisi layering dan ingin mengatur SL serta TP beberapa posisi dengan lebih cepat.

Tidak perlu mengubah satu per satu. Cukup ubah satu posisi, posisi lainnya akan otomatis mengikuti.

//+------------------------------------------------------------------+
//|                 AutoSyncSLTP_NoTrade.mq5                         |
//|                                                                  |
//|  SL berubah -> hanya SL yang mengikuti                           |
//|  TP berubah -> hanya TP yang mengikuti                           |
//|                                                                  |
//|  BUY  -> BUY                                                      |
//|  SELL -> SELL                                                     |
//|  Symbol sama                                                      |
//|                                                                  |
//|  Tidak menggunakan Trade.mqh                                      |
//+------------------------------------------------------------------+
#property strict
#property version "5.00"

input bool SyncSameSymbolOnly = true;
input bool SyncSameTypeOnly   = true;
input int  CheckIntervalMS    = 200;
input int  DeviationPoints    = 20;

//--- data posisi sebelumnya
struct PositionData
{
   ulong  ticket;
   string symbol;
   long   type;
   double sl;
   double tp;
};

PositionData PreviousPositions[];

//+------------------------------------------------------------------+
//| INIT                                                             |
//+------------------------------------------------------------------+
int OnInit()
{
   EventSetMillisecondTimer(CheckIntervalMS);

   SaveCurrentPositions();

   Print("==========================================");
   Print("AUTO SYNC SL + TP AKTIF");
   Print("==========================================");
   Print("SL  -> hanya SL yang mengikuti");
   Print("TP  -> hanya TP yang mengikuti");
   Print("BUY -> BUY");
   Print("SELL -> SELL");
   Print("==========================================");

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| DEINIT                                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();

   Print("Auto Sync SL + TP berhenti.");
}

//+------------------------------------------------------------------+
//| TIMER                                                            |
//+------------------------------------------------------------------+
void OnTimer()
{
   CheckPositions();
}

//+------------------------------------------------------------------+
//| CEK SEMUA POSISI                                                 |
//+------------------------------------------------------------------+
void CheckPositions()
{
   int total = PositionsTotal();

   for(int i = 0; i < total; i++)
   {
      ulong ticket = PositionGetTicket(i);

      if(ticket == 0)
         continue;

      if(!PositionSelectByTicket(ticket))
         continue;

      string symbol = PositionGetString(POSITION_SYMBOL);
      long type     = PositionGetInteger(POSITION_TYPE);

      double currentSL = PositionGetDouble(POSITION_SL);
      double currentTP = PositionGetDouble(POSITION_TP);

      int index = FindPosition(ticket);

      // Posisi baru
      if(index == -1)
      {
         AddPosition(
            ticket,
            symbol,
            type,
            currentSL,
            currentTP
         );

         continue;
      }

      // Cek perubahan SL
      bool slChanged = PriceChanged(
         PreviousPositions[index].sl,
         currentSL,
         symbol
      );

      // Cek perubahan TP
      bool tpChanged = PriceChanged(
         PreviousPositions[index].tp,
         currentTP,
         symbol
      );

      //==============================================================
      // SL BERUBAH
      //==============================================================
      if(slChanged)
      {
         Print("------------------------------------------");
         Print("PERUBAHAN SL TERDETEKSI");
         Print("Ticket : ", ticket);
         Print("Symbol : ", symbol);

         Print(
            "SL Lama: ",
            DoubleToString(
               PreviousPositions[index].sl,
               GetDigits(symbol)
            )
         );

         Print(
            "SL Baru: ",
            DoubleToString(
               currentSL,
               GetDigits(symbol)
            )
         );

         Print("------------------------------------------");

         PreviousPositions[index].sl = currentSL;

         SyncSL(
            ticket,
            symbol,
            type,
            currentSL
         );
      }

      //==============================================================
      // TP BERUBAH
      //==============================================================
      if(tpChanged)
      {
         Print("------------------------------------------");
         Print("PERUBAHAN TP TERDETEKSI");
         Print("Ticket : ", ticket);
         Print("Symbol : ", symbol);

         Print(
            "TP Lama: ",
            DoubleToString(
               PreviousPositions[index].tp,
               GetDigits(symbol)
            )
         );

         Print(
            "TP Baru: ",
            DoubleToString(
               currentTP,
               GetDigits(symbol)
            )
         );

         Print("------------------------------------------");

         PreviousPositions[index].tp = currentTP;

         SyncTP(
            ticket,
            symbol,
            type,
            currentTP
         );
      }
   }

   RemoveClosedPositions();
}

//+------------------------------------------------------------------+
//| SYNC SL                                                          |
//+------------------------------------------------------------------+
void SyncSL(
   ulong masterTicket,
   string masterSymbol,
   long masterType,
   double masterSL
)
{
   int total = PositionsTotal();

   for(int i = 0; i < total; i++)
   {
      ulong ticket = PositionGetTicket(i);

      if(ticket == 0)
         continue;

      if(ticket == masterTicket)
         continue;

      if(!PositionSelectByTicket(ticket))
         continue;

      string symbol = PositionGetString(POSITION_SYMBOL);
      long type     = PositionGetInteger(POSITION_TYPE);

      double currentTP =
         PositionGetDouble(POSITION_TP);

      //--- filter symbol
      if(
         SyncSameSymbolOnly &&
         symbol != masterSymbol
      )
         continue;

      //--- filter BUY / SELL
      if(
         SyncSameTypeOnly &&
         type != masterType
      )
         continue;

      int digits = GetDigits(symbol);

      double newSL = masterSL;

      //--- jika SL bukan 0, cek validitas
      if(newSL > 0.0)
      {
         newSL = NormalizeDouble(
            newSL,
            digits
         );

         if(
            !IsValidSL(
               symbol,
               type,
               newSL
            )
         )
         {
            Print(
               "SL tidak valid untuk ticket ",
               ticket
            );

            continue;
         }
      }

      //--- TP TIDAK DIUBAH
      double newTP = currentTP;

      //--- kirim request
      bool result = ModifyPosition(
         ticket,
         symbol,
         newSL,
         newTP
      );

      if(result)
      {
         Print(
            "SL berhasil mengikuti | Ticket: ",
            ticket,
            " | SL: ",
            DoubleToString(
               newSL,
               digits
            )
         );

         UpdateStoredSL(
            ticket,
            newSL
         );
      }
      else
      {
         Print(
            "GAGAL SYNC SL | Ticket: ",
            ticket,
            " | Retcode: ",
            LastTradeRetcode,
            " | ",
            LastTradeComment
         );
      }
   }
}

//+------------------------------------------------------------------+
//| SYNC TP                                                          |
//+------------------------------------------------------------------+
void SyncTP(
   ulong masterTicket,
   string masterSymbol,
   long masterType,
   double masterTP
)
{
   int total = PositionsTotal();

   for(int i = 0; i < total; i++)
   {
      ulong ticket = PositionGetTicket(i);

      if(ticket == 0)
         continue;

      if(ticket == masterTicket)
         continue;

      if(!PositionSelectByTicket(ticket))
         continue;

      string symbol = PositionGetString(POSITION_SYMBOL);
      long type     = PositionGetInteger(POSITION_TYPE);

      double currentSL =
         PositionGetDouble(POSITION_SL);

      //--- filter symbol
      if(
         SyncSameSymbolOnly &&
         symbol != masterSymbol
      )
         continue;

      //--- filter BUY / SELL
      if(
         SyncSameTypeOnly &&
         type != masterType
      )
         continue;

      int digits = GetDigits(symbol);

      double newTP = masterTP;

      //--- jika TP bukan 0, cek validitas
      if(newTP > 0.0)
      {
         newTP = NormalizeDouble(
            newTP,
            digits
         );

         if(
            !IsValidTP(
               symbol,
               type,
               newTP
            )
         )
         {
            Print(
               "TP tidak valid untuk ticket ",
               ticket
            );

            continue;
         }
      }

      //--- SL TIDAK DIUBAH
      double newSL = currentSL;

      //--- kirim request
      bool result = ModifyPosition(
         ticket,
         symbol,
         newSL,
         newTP
      );

      if(result)
      {
         Print(
            "TP berhasil mengikuti | Ticket: ",
            ticket,
            " | TP: ",
            DoubleToString(
               newTP,
               digits
            )
         );

         UpdateStoredTP(
            ticket,
            newTP
         );
      }
      else
      {
         Print(
            "GAGAL SYNC TP | Ticket: ",
            ticket,
            " | Retcode: ",
            LastTradeRetcode,
            " | ",
            LastTradeComment
         );
      }
   }
}

//+------------------------------------------------------------------+
//| GLOBAL HASIL REQUEST                                             |
//+------------------------------------------------------------------+
uint   LastTradeRetcode = 0;
string LastTradeComment = "";

//+------------------------------------------------------------------+
//| MODIFY POSITION                                                  |
//+------------------------------------------------------------------+
bool ModifyPosition(
   ulong ticket,
   string symbol,
   double sl,
   double tp
)
{
   MqlTradeRequest request;
   MqlTradeResult  result;

   ZeroMemory(request);
   ZeroMemory(result);

   request.action = TRADE_ACTION_SLTP;
   request.position = ticket;
   request.symbol = symbol;
   request.sl = sl;
   request.tp = tp;

   ResetLastError();

   bool sent = OrderSend(
      request,
      result
   );

   LastTradeRetcode = result.retcode;
   LastTradeComment = result.comment;

   if(!sent)
   {
      Print(
         "OrderSend gagal | Error: ",
         GetLastError()
      );

      return false;
   }

   if(
      result.retcode != TRADE_RETCODE_DONE &&
      result.retcode != TRADE_RETCODE_DONE_PARTIAL
   )
   {
      return false;
   }

   return true;
}

//+------------------------------------------------------------------+
//| VALIDASI SL                                                      |
//+------------------------------------------------------------------+
bool IsValidSL(
   string symbol,
   long type,
   double sl
)
{
   double bid =
      SymbolInfoDouble(
         symbol,
         SYMBOL_BID
      );

   double ask =
      SymbolInfoDouble(
         symbol,
         SYMBOL_ASK
      );

   double point =
      SymbolInfoDouble(
         symbol,
         SYMBOL_POINT
      );

   int stopsLevel =
      (int)SymbolInfoInteger(
         symbol,
         SYMBOL_TRADE_STOPS_LEVEL
      );

   int freezeLevel =
      (int)SymbolInfoInteger(
         symbol,
         SYMBOL_TRADE_FREEZE_LEVEL
      );

   double distance =
      MathMax(
         stopsLevel,
         freezeLevel
      ) * point;

   // BUY
   if(type == POSITION_TYPE_BUY)
   {
      if(sl >= bid)
         return false;

      if(
         distance > 0.0 &&
         (bid - sl) < distance
      )
         return false;
   }

   // SELL
   if(type == POSITION_TYPE_SELL)
   {
      if(sl <= ask)
         return false;

      if(
         distance > 0.0 &&
         (sl - ask) < distance
      )
         return false;
   }

   return true;
}

//+------------------------------------------------------------------+
//| VALIDASI TP                                                      |
//+------------------------------------------------------------------+
bool IsValidTP(
   string symbol,
   long type,
   double tp
)
{
   double bid =
      SymbolInfoDouble(
         symbol,
         SYMBOL_BID
      );

   double ask =
      SymbolInfoDouble(
         symbol,
         SYMBOL_ASK
      );

   double point =
      SymbolInfoDouble(
         symbol,
         SYMBOL_POINT
      );

   int stopsLevel =
      (int)SymbolInfoInteger(
         symbol,
         SYMBOL_TRADE_STOPS_LEVEL
      );

   int freezeLevel =
      (int)SymbolInfoInteger(
         symbol,
         SYMBOL_TRADE_FREEZE_LEVEL
      );

   double distance =
      MathMax(
         stopsLevel,
         freezeLevel
      ) * point;

   // BUY
   if(type == POSITION_TYPE_BUY)
   {
      if(tp <= bid)
         return false;

      if(
         distance > 0.0 &&
         (tp - bid) < distance
      )
         return false;
   }

   // SELL
   if(type == POSITION_TYPE_SELL)
   {
      if(tp >= ask)
         return false;

      if(
         distance > 0.0 &&
         (ask - tp) < distance
      )
         return false;
   }

   return true;
}

//+------------------------------------------------------------------+
//| CEK PERUBAHAN HARGA                                              |
//+------------------------------------------------------------------+
bool PriceChanged(
   double oldPrice,
   double newPrice,
   string symbol
)
{
   double point =
      SymbolInfoDouble(
         symbol,
         SYMBOL_POINT
      );

   return(
      MathAbs(
         oldPrice - newPrice
      ) > point * 0.5
   );
}

//+------------------------------------------------------------------+
//| SIMPAN POSISI                                                    |
//+------------------------------------------------------------------+
void SaveCurrentPositions()
{
   ArrayResize(
      PreviousPositions,
      0
   );

   int total = PositionsTotal();

   for(int i = 0; i < total; i++)
   {
      ulong ticket =
         PositionGetTicket(i);

      if(ticket == 0)
         continue;

      if(!PositionSelectByTicket(ticket))
         continue;

      string symbol =
         PositionGetString(
            POSITION_SYMBOL
         );

      long type =
         PositionGetInteger(
            POSITION_TYPE
         );

      double sl =
         PositionGetDouble(
            POSITION_SL
         );

      double tp =
         PositionGetDouble(
            POSITION_TP
         );

      AddPosition(
         ticket,
         symbol,
         type,
         sl,
         tp
      );
   }
}

//+------------------------------------------------------------------+
//| TAMBAH POSISI                                                     |
//+------------------------------------------------------------------+
void AddPosition(
   ulong ticket,
   string symbol,
   long type,
   double sl,
   double tp
)
{
   int size =
      ArraySize(
         PreviousPositions
      );

   ArrayResize(
      PreviousPositions,
      size + 1
   );

   PreviousPositions[size].ticket = ticket;
   PreviousPositions[size].symbol = symbol;
   PreviousPositions[size].type   = type;
   PreviousPositions[size].sl     = sl;
   PreviousPositions[size].tp     = tp;
}

//+------------------------------------------------------------------+
//| CARI POSISI                                                       |
//+------------------------------------------------------------------+
int FindPosition(
   ulong ticket
)
{
   int total =
      ArraySize(
         PreviousPositions
      );

   for(int i = 0; i < total; i++)
   {
      if(
         PreviousPositions[i].ticket ==
         ticket
      )
      {
         return i;
      }
   }

   return -1;
}

//+------------------------------------------------------------------+
//| HAPUS POSISI YANG SUDAH CLOSE                                    |
//+------------------------------------------------------------------+
void RemoveClosedPositions()
{
   for(
      int i =
         ArraySize(
            PreviousPositions
         ) - 1;

      i >= 0;

      i--
   )
   {
      if(
         !PositionSelectByTicket(
            PreviousPositions[i].ticket
         )
      )
      {
         int size =
            ArraySize(
               PreviousPositions
            );

         for(
            int j = i;
            j < size - 1;
            j++
         )
         {
            PreviousPositions[j] =
               PreviousPositions[j + 1];
         }

         ArrayResize(
            PreviousPositions,
            size - 1
         );
      }
   }
}

//+------------------------------------------------------------------+
//| UPDATE SL                                                        |
//+------------------------------------------------------------------+
void UpdateStoredSL(
   ulong ticket,
   double sl
)
{
   int index =
      FindPosition(ticket);

   if(index >= 0)
   {
      PreviousPositions[index].sl = sl;
   }
}

//+------------------------------------------------------------------+
//| UPDATE TP                                                        |
//+------------------------------------------------------------------+
void UpdateStoredTP(
   ulong ticket,
   double tp
)
{
   int index =
      FindPosition(ticket);

   if(index >= 0)
   {
      PreviousPositions[index].tp = tp;
   }
}

//+------------------------------------------------------------------+
//| DIGITS                                                           |
//+------------------------------------------------------------------+
int GetDigits(
   string symbol
)
{
   return(
      (int)SymbolInfoInteger(
         symbol,
         SYMBOL_DIGITS
      )
   );
}
//+------------------------------------------------------------------+