Как изменять открытые и отложенные ордера в MQL4?

В предыдущих статьях мы узнали, как открывать ордера, закрывать ордера и перебирать существующие ордера. Как вы уже знаете, ордера также могут быть изменены. Например, для отложенных ордеров вы можете изменить цену открытия. Для рыночных ордеров вы можете изменить стоп-лосс или тейк-профит. Для изменения ордеров в MQL4 есть функция OrderModify().

Как вы знаете ECN/STP-брокеры, которые используют MetaTrader, не поддерживают установку стоп-лосса и тейк-профита при открытии сделки. В этом случае нам нужно будет разместить стоп-лосс и тейк-профит после размещения ордера с помощью функции OrderModify(). Это относится только к рыночным ордерам — для отложенных ордеров вы все равно можете разместить стоп-лосс и тейк-профит с помощью функции OrderSend().

Рассмотрим синтаксис функции:

bool  OrderModify(
   int        ticket,      // номер ордера
   double     price,       // цена открытия
   double     stoploss,    // stop loss
   double     takeprofit,  // take profit
   datetime   expiration,  // время истечения ордера
   color      arrow_color  // цвет
   );

Ее параметры:

  • ticket — для идентификации тикета ордера.
  • price — используется для изменения цены открытия отложенного ордера.
  • stoploss — новая цена для стоп-лосса.
  • takeprofit — новая цена для тейк-профита.
  • expiration — изменить время истечения отложенных ордеров.
  • arrow_color — изменить цвет стрелки на графике.

Если изменение ордера выполнено успешно, OrderModify() вернет значение true. Если произошла ошибка, возвращаемое значение будет ложным.

При изменении ордеров мы должны быть уверены, что значения, которые мы передаем функции, действительны. Например, ордер по-прежнему должен быть открытым — мы не можем изменить закрытый ордер. При изменении отложенных ордеров с помощью параметра Price, ордер не должен быть уже исполнен.

Как и при открытии нового ордера, не забывайте учитывать, что стоп-лосс и тейк-профит не могут быть размещены слишком близки к текущей цене, иначе операция изменения ордера может не удастся.

Почему нам может быть необходимо изменить ордер? Вот несколько примеров:

  • Трейлинг-стоп, пожалуй, одна из самых популярных причин. Мы перемещаем цену стоп-лосса, когда цена движется в направлении нашего ордера.
  • Изменить цену тейк-профита. Обычно это делается вместе с трейлинг-стопом.
  • Изменить срок действия отложенного ордера.
  • Изменить цену открытия отложенного ордера.

Как добавить стоп-лосс и тейк-профит в существующий ордер?

Во-первых, нам нужно убедиться, что ордер был открыт правильно. Мы делаем это, проверяя значение функции OrderSend(), которая возвращает номером тикета открытого ордера. Если ордер не был открыт из-за ошибки, номер тикета будет равен -1.

Затем мы используем функцию OrderSelect(), чтобы получить информацию о только что размещенном ордере. Мы будем использовать функции OrderOpenPrice(), OrderTakeProfit(), OrderStopLoss() и опционально функции OrderExpiration() при передаче значений в функцию OrderModify(). Наконец, мы будем использовать функцию OrderModify(), чтобы добавить стоп-лосс и тейк-профит.

Вот пример, где мы устанавливаем стоп-лосс и тейк-профит для ордера на покупку, используя функцию OrderModify(). Мы переместили расчет стоп-лосса и тейк-профита после функции OrderSend (), чтобы она вычислялась до того, как мы изменим ордер:

int BuyTicket = OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage,0,0, "Ордер на покупку",MagicNumber,0,Green);
 
    if(BuyTicket > 0)
      {
        OrderSelect(BuyTicket,SELECT_BY_TICKET);
        double OpenPrice = OrderOpenPrice();
        if(StopLoss > 0) double BuyStopLoss = OpenPrice – (StopLoss * UsePoint);
        if(TakeProfit > 0) double BuyTakeProfit = OpenPrice + (TakeProfit * UsePoint);
 
        if(BuyStopLoss > 0 || BuyTakeProfit > 0)
          {
             bool TicketMod = OrderModify(BuyTicket,OrderOpenPrice(),BuyStopLoss,
               BuyTakeProfit,0);
} }

Функция OrderSend() идентична нашему более раннему примеру, за исключением того, что мы используем значение 0 для параметров стоп-лосса и тейк-профита. Нулевое значение означает, что стоп-лосс или тейк-профит не размещаются вместе с ордером. В переменной BuyTicket будет хранится номер ордера.

Мы используем оператор if, чтобы убедиться, что тикет BuyTicket действителен, то есть больше нуля. Если это так, мы вызываем функцию OrderSelect(), используя тикет BuyTicket. Мы получаем цену открытия для ордера, используя OrderOpenPrice(), и назначаем ее переменной OpenPrice.

Далее мы рассчитываем стоп-лосс и тейк-профит относительно цены открытия ордера, который мы только что разместили. Сначала мы проверяем, являются ли внешние переменные StopLoss и TakeProfit больше нуля. Если это так, мы рассчитываем новые цены для стоп-лосса и тейк-профита.

Наконец, мы вызываем функцию OrderModify(), чтобы добавить наш стоп-лосс и зафиксировать прибыль в ордере. Сначала мы проверяем, чтобы переменные BuyStopLoss или BuyTakeProfit отличались от нуля. Если мы попытаемся изменить порядок с неизменными значениями, мы получим код ошибки 1 из функции OrderModify().

Первым параметром для OrderModify() является наш номер BuyTicket. Мы также можем использовать OrderTicket(). Второй параметр — цена нового ордера. Поскольку мы не изменяем цену ордера, мы используем функцию OrderOpenPrice(), чтобы указать, что цена ордера не изменилась.

Помните, что мы можем изменять цены только для отложенных ордеров. Если мы модифицируем рыночный ордер, мы можем передать любое значение параметра Price, поскольку вы не можете изменить цену для рыночного ордера. Мы не можем предполагать, что мы всегда будем изменять рыночные ордера, поэтому мы всегда будем использовать функцию OrderOpenPrice().

Переменные BuyStopLoss и BuyTakeProfit передают измененный стоп-лосс и значения тейк-профита в функцию OrderModify(). Если вы планируете использовать время истечения ордера для отложенных ордеров, вы можете использовать OrderExpiration(). В противном случае просто используйте 0.

Хотя этот метод добавляет несколько дополнительных шагов, мы рекомендуем вам использовать этот метод размещения стоп-лоссов и фиксации прибыли для рыночных ордеров в ваших советниках, чтобы обеспечить их совместимость со всеми брокерами без влияния проскальзывания.

Как изменять отложенные ордера?

Функция OrderModify() также можно использовать для изменения цены отложенного ордера. Если цена отложенного ордера уже была достигнута и ордер был исполнен, он больше не является отложенным ордером и его цена открытия не может быть изменена.

Мы будем использовать переменную NewPendingPrice для представления новой цены ордера.

    OrderSelect(Ticket,SELECT_BY_TICKET);
    if(NewPendingPrice != OrderOpenPrice())
      {
bool TicketMod = OrderModify(Ticket,NewPendingPrice,OrderStopLoss(), OrderTakeProfit(),0);
}

Как всегда, мы получаем информацию о заказе, используя функцию OrderSelect(). Таким образом, мы можем передать стоп-лосс и зафиксировать цену. Перед изменением ордера мы проверяем, что наша новая цена отложенного ордера не совпадает с текущей ценой.

Для OrderModify() мы указываем наш тикет ордера, новую цену ордера, сохраненную в NewPendingPrice, и неизменные значения стоп-лосс и тейк-профит, представленные OrderStopLoss() и OrderTakeProfit(). Мы не используем время истечения для этого ордера, поэтому мы подставляем 0 в качестве параметра истечения срока действия.

Как разместить стоп-лосс и тейк-профит для каждого открытого ордера?

В примере, который вы увидите, мы установим стоп-лосс и тейк-профит для каждого открытого ордера.

// Допустимое проскальзывание.
extern double Slippage=3;  //Allowed Slippage
 
extern int TakeProfit=40;  //Размер тейк-профита в пунктах.
extern int StopLoss=20;    //Размер стоп-лосса в пунктах.
 
//В соответствии с рекомендациями нам необходимо нормализовать цену, чтобы правильно рассчитать тейк-профит и стоп-лосс.
double CalculateNormalizedDigits()
{
   if(Digits<=3) {
   return(0.01);
   } 
   else if(Digits>=4) {
      return(0.0001);
   }
   else return(0);
}
 
//Функцию UpdateOpenOrders возвращает количество ордеров, которые должны быть изменены.
int UpdateOpenOrders(){
 
   int TotalUpdated=0;  //Мы хотим посчитать, сколько ордеров было изменено.
 
   double nDigits=CalculateNormalizedDigits();
 
   //Нормализируем проскальзывание.
   if(Digits==3 || Digits==5){
      Slippage=Slippage*10;
   }
 
   //Перебираем все ордера в обратном порядке.
   for(int i=OrdersTotal()-1; i>=0; i--) {
 
      //Мы выбираем ордера по индексу. Если выбор прошел успешно, мы приступаем к их изменению.
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)){
         // Проверяем, относится ли ордер к той же валютной паре на графике, на котором выполняется скрипт.
         if(OrderSymbol()==Symbol()){
            double OpenPrice, StopLossPrice, TakeProfitPrice;
            // Получаем цену открытия.
            OpenPrice=OrderOpenPrice();
            // Мы рассчитываем стоп-лосс и тейк-профит в зависимости от типа ордера.
            if(OrderType()==OP_BUY){
               StopLossPrice=NormalizeDouble(OpenPrice-StopLoss*nDigits,Digits);
               TakeProfitPrice=NormalizeDouble(OpenPrice+TakeProfit*nDigits,Digits);
            }
            if(OrderType()==OP_SELL){
               StopLossPrice=NormalizeDouble(OpenPrice+StopLoss*nDigits,Digits);
               TakeProfitPrice=NormalizeDouble(OpenPrice-TakeProfit*nDigits,Digits);
            }         
            if(OrderModify(OrderTicket(),OpenPrice,StopLossPrice,TakeProfitPrice,CLR_NONE)){
               TotalUpdated++;
            }
            else{
               Print("Не удалось обновить заказ с ошибкой - ",GetLastError());
            }
 
         }
 
         // Если ордер изменяется корректно, мы увеличиваем счетчик измененных ордеров. Если ордер не изменяется, мы выводим ошибку.
     }
      else{
         Print("Не удалось изменить ордер - ",GetLastError());
      }  
 
      // Устанавливаем небольшую паузу.
      Sleep(300);
   }
   //Если цикл завершается, это означает, что для данного инструмента больше нет открытых ордеров.
   return(TotalUpdated);
}
 
 
void OnStart()
{
   Print("Сколько ордеров было изменено? ",UpdateOpenOrders());
}

Рассмотрим пример скрипта, который позволит нам устанавливать стоп-лосс и тейк-профит для всех открытых ордеров. Когда вы запускаете скрипт на графике, вы можете установить некоторые параметры, чтобы ограничить, какие ордера будут изменены.

 
#property copyright     "Александр Паркер"
#property link          "https://traderblog.net/"
#property version       "1.00"
#property strict
 
#property show_inputs
 
//Внешние переменные
extern int TakeProfit=40;              //Размер тейк-профита в пунктах.
extern int StopLoss=20;                //Размер стоп-лосса в пунктах.
extern bool OnlyMagicNumber=false;     //Изменять только ордера с magic number.
extern int MagicNumber=0;              //Magic number.
extern bool OnlyWithComment=false;     //Изменять ордера с определенным комментарием.
extern string MatchingComment="";      //Комментарий.
extern double Slippage=2;              //Проскальзывание.
extern int Delay=0;                    //Задержка между модификацией ордеров.
 
//Нормализуем значение цены для рассчета стоп-лосса и тейк-профита.
double CalculateNormalizedDigits()
{
   if(Digits<=3){
      return(0.01);
   }
   else if(Digits>=4){
      return(0.0001);
   }
   else return(0);
}
 
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //Счетчик ордеров
   int TotalModified=0;
 
   //Нормализуем проскальзывание.
   if(Digits==3 || Digits==5){
      Slippage=Slippage*10;
   }
   double nDigits=CalculateNormalizedDigits();
 
   //Перебираем ордера.
   for(int i=OrdersTotal()-1; i>=0; i--){
 
      //Выбераем ордер. Если орден не выбран, выводим ошибку и переходим к следующему индексу.
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) {
         Print("Ошибка. Нельзя выбрать ордер - ",GetLastError());
         continue;
      } 
 
      //Проверяем, совпадает ли ордер в соответствии с заданными критериями. Если критерии не совпадают, переходим к следующим.
      if(OrderSymbol()!=Symbol()) continue;
      if(OnlyMagicNumber && OrderMagicNumber()!=MagicNumber) continue;
      if(OnlyWithComment && StringCompare(OrderComment(),MatchingComment)!=0) continue;
 
      //Цены.
      double TakeProfitPrice=0;
      double StopLossPrice=0;
      double OpenPrice=OrderOpenPrice();
      RefreshRates();
      if(OrderType()==OP_BUY){
         TakeProfitPrice=NormalizeDouble(OpenPrice+TakeProfit*nDigits,Digits);
         StopLossPrice=NormalizeDouble(OpenPrice-StopLoss*nDigits,Digits);
      } 
      if(OrderType()==OP_SELL){
         TakeProfitPrice=NormalizeDouble(OpenPrice-TakeProfit*nDigits,Digits);
         StopLossPrice=NormalizeDouble(OpenPrice+StopLoss*nDigits,Digits);      
      }
 
      //Модифицируем ордер.
      if(OrderModify(OrderTicket(),OpenPrice,StopLossPrice,TakeProfitPrice,0,clrNONE)){
         TotalModified++;
      }
      else{
         Print("Ошибка - ",GetLastError());
      }      
 
      //Задержка.
      Sleep(Delay);
 
   }
 
   //Выводим количество модифицированных ордеров.
   Print("Всего ордеров изменено = ",TotalModified);
 
  }
//+------------------------------------------------------------------+