Управление ордерами в MQL4: как модифицировать ордера?

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

Оператор for

Оператор for используется для циклического прохождения через блок кода заданное количество раз. Мы объявляем целочисленную переменную для использования в качестве счетчика и присваиваем ей начальное значение. Далее мы указываем условие, которое заставит цикл работать. Мы также указываем выражение, с помощью которого увеличивается значение счетчика.

Вот пример цикла for:

for(int Counter = 1; Counter <= 3; Counter++) {
        // Код для исполнения
      }

Первое выражение, int Counter = 1, инициализирует нашу переменную Counter значением 1. Второе выражение Counter Оператор while

Оператор while – это более простой метод цикла в MQL. Цикл for лучше всего подходит, если вы точно знаете, сколько раз вы планируете выполнить цикл. Если вы не уверены в количестве итераций, тогда цикл while будет для вас более подходящим.

Вот пример цикла while:

while (Something == true) {
// Код цикла
}

В этом примере используется логическая переменная Something. Если Something равно true, цикл будет выполнен. Конечно, если значение Something никогда не изменится, цикл будет выполняться бесконечно. Таким образом, необходимо, чтобы в какой-то момент цикла возникло условие изменить значение Something. Как только это условие выполнено, цикл прекращает свое выполнение.

Вы также можете увеличить переменную, как если бы вы использовали оператор for:

int Counter = 1; 
while(Counter <= 3) {
Counter++;
}

Цикл ордеров

Вот код, который мы будем использовать для перебора открытых ордеров:

for(Counter = 0; Counter <= OrdersTotal()-1; Counter++) {
OrderSelect(Counter,SELECT_BY_POS);
        // Условие
      }

Мы установим значение Counter равным 0 и будем повторять цикл до тех пор, пока значение Counter не станет меньше или равно значению OrdersTotal() минус один. Счетчик будет увеличиваться на 1 после каждой итерации цикла.

OrdersTotal() – это функция, которая возвращает количество открытых в данный момент ордеров. Почему мы вычитаем 1 из значения OrdersTotal()?

Пул ордеров содержит все ордера, которые в данный момент открыты в нашем терминале, включая ордера, размещенные вручную, а также ордера, размещенные экспертами. Порядковые индексы нумеруются начиная с нуля. Если открыт один ордер, его индекс равен 0. Когда открывается второй ордер, его индекс равен 1. Если третий ордер открыт, его индекс будет равен 2 и т. д.

OrdersTotal() вернет количество открытых ордеров. В приведенном выше примере у нас открыто три ордера. Но так как наш индекс ордера начинается с 0, мы хотим, чтобы наша переменная counter считала только до 2. Значение Counter должно соответствовать нашим номерам индекса ордера, поэтому мы должны вычесть 1 из OrdersTotal().

Когда ордер в пуле открытых ордеров закрыт, у всех новых ордеров в пуле индексы ордеров будут уменьшены. Например, если ордер с индексом 0 закрыт, то ордер с индексом 1 становится индексом 0, а индекс ордера 2 становится индексом 1. Это важно, когда мы закрываем ордера, и мы вскоре рассмотрим это более подробно.

Возвращаясь к нашему циклу ордера: оператор OrderSelect() использует нашу переменную Counter в качестве индекса позиции ордера. Параметр SELECT_BY_POS указывает, что мы выбираем заказ по его позиции в пуле заказов, а не по номеру тикета.

Для первой итерации этого цикла Counter будет равен 0, и мы выберем самый старый ордер из пула ордеров, используя OrderSelect(). Затем мы можем просмотреть информацию о заказе, используя такие функции, как OrderTicket() или OrderStopLoss(), а также изменить или закрыть ордер по мере необходимости.

Подсчет ордеров

Часто очень полезно узнать, сколько ордеров открыл наш советник и какого они типа. Мы создадим несколько функций для подсчета текущего количества открытых ордеров в зависимости от типа ордера. Следующая функция подсчитает общее количество открытых ордеров:

int TotalOrderCount(string argSymbol, int argMagicNumber) {
int OrderCount;
for(Counter = 0; Counter <= OrdersTotal()-1; Counter++) {
OrderSelect(Counter,SELECT_BY_POS);
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol) {
                 OrderCount++;
               }
          }
        return(OrderCount);
}

Мы назвали нашу функцию подсчета ордеров TotalOrderCount(). Она вернет целочисленное значение, указывающее, сколько ордеров было открыто в данный момент на указанном символе графика, совпадающем с магическим числом, которое мы передали в качестве аргумента функции.

Начнем с объявления переменной OrderCount. Поскольку мы не указали начальное значение, OrderCount будет инициализирован как 0.

Поскольку пул ордеров содержит все открытые ордера, в том числе размещенные другими советниками, нам необходимо определить, какие ордера были выставлены нашим советником. Сначала мы проверяем OrderSymbol() выбранного порядка и проверяем, соответствует ли он аргументу argSymbol. Мы проверяем магический номер на ордер.

Если OrderMagicNumber() совпадает с аргументом argMagicNumber, мы можем быть достаточно уверены, что этот ордер был размещен этим советником. При запуске нескольких советников на одном инструменте убедитесь, что вы используете уникальный магический номер в каждом советнике.

Если порядок соответствует как нашему магическому номеру, так и нашему символу графика, значение OrderCount будет увеличено на единицу. После прохождения всех ордеров мы возвращаем значение OrderCount вызывающей функции.

Вот пример того, как мы будем использовать это в коде:

if (TotalOrderCount (Symbol(), MagicNumber)> 0 && CloseOrders == true) {
         // Закрыть все ордера
       }

Если этот советник открыл ордера, и значение CloseOrders равно true, тогда будет запущен код внутри фигурных скобок, который закроет все открытые ордера.

Давайте изменим нашу процедуру подсчета ордеров, чтобы она учитывала только рыночные ордера на покупку:

int BuyMarketCount(string argSymbol, int argMagicNumber) {
int OrderCount;
for(Counter = 0; Counter <= OrdersTotal()-1; Counter++)
{
OrderSelect(Counter,SELECT_BY_POS);
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol && OrderType() == OP_BUY) {
                 OrderCount++;
               }
          }
        return(OrderCount);
}

Код идентичен предыдущему, за исключением того, что мы добавили функцию OrderType() для проверки типа ордера. OP_BUY – это константа, которая указывает на рыночный ордер на покупку. Для подсчета других типов ордеров просто замените OP_BUY соответствующей константой типа ордера.

Закрытие нескольких ордеров

Чаще всего нам нужно закрыть несколько ордеров одного типа. Мы объединим наш цикл ордеров с нашими процедурами закрытия ордеров, чтобы закрыть несколько ордеров одновременно. Эта функция закроет все ордера на покупку, выставленные нашим советником:

void CloseAllBuyOrders(string argSymbol, int argMagicNumber, int argSlippage) {
for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++) {
OrderSelect(Counter,SELECT_BY_POS);
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol && OrderType() == OP_BUY) {
 
// Закрытие ордера
int CloseTicket = OrderTicket(); 
double CloseLots = OrderLots();
while(IsTradeContextBusy()) Sleep(10);
double ClosePrice = MarketInfo(argSymbol,MODE_BID);
bool Closed = OrderClose(CloseTicket,CloseLots,ClosePrice,argSlippage,Red);
 
// Обработка ошибок 
if(Closed == false) {
ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Close All Buy Orders - Error ", ErrorCode,": ",ErrDesc);
       Alert(ErrAlert);
string ErrLog = StringConcatenate("Bid: ", MarketInfo(argSymbol,MODE_BID)," Ticket: ",CloseTicket, " Price: ",ClosePrice);
       Print(ErrLog);
    }
else Counter--; }
}
}
}

Обратите внимание, что мы используем void в качестве типа данных функции.

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

Мы вызываем функцию OrderTicket(), чтобы получить номер тикета для текущего ордера. Отсюда наш код идентичен коду закрытия рынка покупки в предыдущих главах. Обратите внимание на самое последнее утверждение: Counter–. Если ордер был закрыт должным образом, переменная Counter будет уменьшена на единицу.

Ранее мы объясняли, что при закрытии ордера у всех ордеров за ним их индексы уменьшаются на единицу. Если мы не уменьшаем переменную counter после закрытия ордера, последующие ордера будут пропущены.

Существует веская причина, по которой мы перебираем ордера от самых старых до самых новых: правила NFA, вступившие в силу летом 2009 года для американских брокеров, требуют, чтобы несколько ордеров, размещенных на одном и том же символе валюты, были закрыты в том порядке, в котором они были размещены. Это называется правилом FIFO (первым пришел, первым вышел). Циклические ордера от старых к новым гарантируют, что мы соблюдаем правило FIFO при закрытии ордеров.

Чтобы закрыть рыночные ордера на продажу с использованием вышеуказанного кода, просто измените тип ордера на OP_SELL, а ClosePrice – на цену Ask.

Давайте рассмотрим код для закрытия нескольких отложенных ордеров. Этот пример закроет все стоп-ордера. Разница между этим кодом и кодом для закрытия рыночных ордеров на покупку выше заключается в том, что мы указываем OP_BUYSTOP в качестве нашего типа ордера и используем OrderDelete() для закрытия ордеров.

void CloseAllBuyStopOrders(string argSymbol, int argMagicNumber, int argSlippage) {
for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++) {
OrderSelect(Counter,SELECT_BY_POS);
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol && OrderType() == OP_BUYSTOP) {
 
// Удаление ордера
int CloseTicket = OrderTicket();
while(IsTradeContextBusy()) Sleep(10);
bool Closed = OrderDelete(CloseTicket,Red);
 
// Обработка ошибок 
if(Closed == false) {
ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Close All Buy Stop Orders", " - Error ",ErrorCode,": ",ErrDesc);
       Alert(ErrAlert);
string ErrLog = StringConcatenate("Bid: ", MarketInfo(argSymbol,MODE_BID)," Ask: ", MarketInfo(argSymbol,MODE_ASK)," Ticket: ",CloseTicket);
       Print(ErrLog);
    }
else Counter--;
}
}
}

Трейлинг стопы

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

Трейлинг-стоп выражается как максимальное количество пунктов. Например, если ваш трейлинг-стоп составляет 50 пунктов, стоп-лосс никогда не будет более чем на 50 пунктов от вашей цены. Если цена развернется и прибыль снизится, стоп-лосс останется на том же уровне. Стоп движется только в направлении прибыли, а не наоборот.

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

Трейлинг-стоп рассчитывается относительно цены закрытия, которая является ценой Bid для ордеров на покупку и Ask для ордеров на продажу.

Давайте рассмотрим код для модификации трейлинг-стопа. Сначала мы объявляем внешнюю переменную для нашего параметра трейлинг-стопа:

extern double TrailingStop = 50;

Этот код проверяет все ордера на покупку и изменяет стоп-лосс при необходимости:

for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++) {
OrderSelect(Counter,SELECT_BY_POS);
double MaxStopLoss = MarketInfo(Symbol(),MODE_BID) - (TrailingStop * PipPoint(Symbol()));
MaxStopLoss = NormalizeDouble(MaxStopLoss,MarketInfo(OrderSymbol(),MODE_DIGITS));
double CurrentStop = NormalizeDouble(OrderStopLoss(), MarketInfo(OrderSymbol(),MODE_DIGITS));
 
// Изменение оредра
if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol()
&& OrderType() == OP_BUY && CurrentStop < MaxStopLoss) {
bool Trailed = OrderModify(OrderTicket(),OrderOpenPrice(),MaxStopLoss, OrderTakeProfit(),0);
 
// Обработка ошибок
if(Trailed == false) {
ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Buy Trailing Stop - Error ", ErrorCode,": ",ErrDesc);
  Alert(ErrAlert);
string ErrLog = StringConcatenate("Bid: "MarketInfo(Symbol(),MODE_BID), " Ticket: ",CloseTicket," Stop: ",OrderStopLoss()," Trail: ", MaxStopLoss);
  Print(ErrLog);
}
}
}

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

Мы используем MQL-функцию NormalizeDouble() для округления переменной MaxStopLoss до правильного числа цифр после десятичной точки. Цены в MetaTrader могут быть указаны до восьми знаков после запятой. Используя NormalizeDouble(), мы округляем это до 4 или 5 цифр (2-3 цифры для пар JPY).

Затем мы извлекаем стоп-лосс текущего выбранного ордера и округляем его с помощью NormalizeDouble(). Мы присваиваем это значение переменной CurrentStop.

Затем мы проверяем, нужно ли изменить текущий ордер. Если магическое число, символ и тип ордера совпадают, а текущий стоп-лосс (CurrentStop) меньше, чем MaxStopLoss, мы модифицируем стоп-лосс ордера. Мы передаем переменную MaxStopLoss как наш новый стоп-лосс в функцию OrderModify().

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

Вот условие для изменения ордера на продажу:

// Изменение оредра
if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() && OrderType() == OP_SELL && (CurrentStop > MaxStopLoss || CurrentStop == 0))

Обратите внимание на условие (CurrentStop> MaxStopLoss || CurrentStop == 0). Если стоп-лосс не размещен в ордере, то условие CurrentStop> MaxStopLoss никогда не будет истинным, поскольку MaxStopLoss никогда не будет меньше нуля. Таким образом, мы добавляем условие ИЛИ, CurrentStop == 0.

Если стоп-лосс текущего ордера равен 0 (без стоп-лосса), то при условии выполнения остальных условий трейлинг-стоп будет размещен.

Минимальная прибыль

Давайте улучшим наш трейлинг-стоп, добавив минимальный уровень прибыли. В приведенном выше примере трейлинг-стоп сработает сразу. Если вы установили начальный стоп-лосс в 100 пунктов, а ваш трейлинг-стоп равен 50 пунктам, то стоп-лосс будет немедленно установлен на 50 пунктов, что сделает недействительным первоначальный стоп-лосс в 100 пунктов.

Добавление минимального уровня прибыли позволит вам установить начальный стоп-лосс, одновременно задерживая трейлинг-стоп, пока не будет достигнута указанная сумма прибыли. В этом примере давайте предположим, что при размещении ордера установлен начальный стоп-лосс в 100 пунктов. Мы используем трейлинг-стоп в 50 пунктов с минимальным уровнем прибыли в 50 пунктов. Когда прибыль по ордеру достигнет 50 пунктов, стоп-лосс будет скорректирован в безубыток.

Добавим внешнюю переменную для настройки минимальной прибыли:

extern int TrailingStop = 50;
extern int MinimumProfit = 50;

Следующая функция изменяет стоп-лосс для всех ордеров на покупку, проверяя минимальную прибыль:

void BuyTrailingStop(string argSymbol, int argTrailingStop, int argMinProfit, int argMagicNumber) {
for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++) {
OrderSelect(Counter,SELECT_BY_POS);
 
// Рассчитываем максимальный стоп-лосс и тейк-профит
double MaxStopLoss = MarketInfo(argSymbol,MODE_BID) - (TrailingStop * PipPoint(argSymbol));
MaxStopLoss = NormalizeDouble(MaxStopLoss, MarketInfo(OrderSymbol(),MODE_DIGITS));
double CurrentStop = NormalizeDouble(OrderStopLoss(), MarketInfo(OrderSymbol(),MODE_DIGITS));
double PipsProfit = MarketInfo(argSymbol,MODE_BID) - OrderOpenPrice(); double MinProfit = MinimumProfit * PipPoint(argSymbol));
 
// Изменяем стоп-лосс
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol && OrderType() == OP_BUY && CurrentStop < MaxStopLoss && PipsProfit >= MinProfit) {
bool Trailed = OrderModify(OrderTicket(),OrderOpenPrice(),MaxStopLoss, OrderTakeProfit(),0);
 
// Обработка ошибок
if(Trailed == false) {
ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Buy Trailing Stop - Error ", ErrorCode,": ",ErrDesc);
  Alert(ErrAlert);
string ErrLog = StringConcatenate("Bid: ", MarketInfo(argSymbol,MODE_BID)," Ticket: ",CloseTicket, " Stop: ",OrderStopLoss()," Trail: ",MaxStopLoss);
  Print(ErrLog);
}
}
}
}

Мы рассчитываем прибыль текущего ордера в пунктах, вычитая OrderOpenPrice() из текущей цены Bid и сохраняя ее в переменной PipsProfit. Мы сравниваем это значение с нашей минимальной прибылью, которая умножается на PipPoint() и сохраняется в переменной MinProfit.

Если текущая прибыль в пунктах PipsProfit больше или равна нашей минимальной прибыли MinProfit, и все остальные условия выполняются, стоп будет изменен.

Перенос стоп-лосса в безубыток

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

Вот внешняя переменная для настройки уровня безубытка. Минимальная прибыль указана в пунктах.

extern double BreakEvenProfit = 25;

Этот код будет изменять стоп-лосс на всех ордерах на покупку, если прибыль в пунктах равна или превышает BreakEvenProfit.

for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++) {
OrderSelect(Counter,SELECT_BY_POS); RefreshRates();
double PipsProfit = Bid – OrderOpenPrice();
double MinProfit = BreakEvenProfit * PipPoint(OrderSymbol()));
if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() && OrderType() == OP_BUY && PipsProfit >= MinProfit
&& OrderOpenPrice() != OrderStopLoss()) {
bool BreakEven = OrderModify(OrderTicket(),OrderOpenPrice(), OrderOpenPrice(),OrderTakeProfit(),0);
if(BreakEven == false) {
ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Buy Break Even - Error ", ErrorCode,": ",ErrDesc);
  Alert(ErrAlert);
string ErrLog = StringConcatenate("Bid: ",Bid,", Ask: ",Ask,
", Ticket: ",CloseTicket,", Stop: ",OrderStopLoss(),", Break: ", MinProfit);
  Print(ErrLog);
}
}
}

Мы вычитаем цену открытия ордера из текущей цены Bid, чтобы рассчитать текущую прибыль в пунктах, далее сохраняем ее в PipsProfit. Рассчитываем минимальную прибыль в пунктах и сохраняем ее в MinProfit. Если PipsProfit больше или равен MinProfit, мы изменим стоп-лосс так, чтобы он равнялся цене открытия ордера.

Мы также проверяем, чтобы стоп-лосс еще не был установлен по цене безубытка. Если OrderOpenPrice () не равен OrderStopLoss(), то мы можем продолжить.

Обновление советника

Давайте изменим функцию start() нашего советника, торгующего по скользящей средней, чтобы отразить новые функции, которые мы создали. Сначала мы проверим, есть ли открытые ордера на покупку, прежде чем открывать новые. Вместо того, чтобы закрывать один ордер на продажу, мы просто используем функцию, чтобы закрыть все ордера на продажу. Этот метод не требует от нас использовать тикет ордера.

// Ордер на покупку
if(FastMA > SlowMA && BuyTicket == 0 && BuyMarketCount(Symbol(),MagicNumber) == 0) {
if(SellMarketCount(Symbol(),MagicNumber) > 0)
  {
    CloseAllSellOrders(Symbol(),MagicNumber,Slippage);
  }
SellTicket = 0;
BuyTicket = OpenBuyOrder(Symbol(),LotSize,UseSlippage,MagicNumber); }

Мы использовали функцию BuyMarketCount(), чтобы получить количество открытых ордеров на покупку. Cохраним регистрацию BuyTicket, чтобы открывались только чередующиеся ордера на покупку / продажу.

Функция CloseAllSellOrders() закрывает все открытые ордера на продажу. Сначала мы проверяем SellMarketCount(), чтобы увидеть, есть ли ордера на продажу для закрытия. Эта функция не требует тикета ордера, в отличие от функции CloseSellOrder().

Остальной код размещения ордера на покупку такой же, как и раньше. Соответствующий код размещения ордера на продажу ниже:

// Ордер на продажу
if(FastMA < SlowMA && SellTicket == 0 && SellMarketCount(Symbol(),MagicNumber) == 0)
{
if(BuyMarketCount(Symbol(),MagicNumber) > 0)
{
    CloseAllBuyOrders(Symbol(),MagicNumber,Slippage);
}
BuyTicket = 0;
SellTicket = OpenSellOrder(Symbol(),LotSize,UseSlippage,MagicNumber);
}

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

extern int TrailingStop = 50;
extern int MinimumProfit = 50;

Это код для проверки и изменения трейлинг-стопов. Обратите внимание, что мы проверяем, есть ли запись для настройки TrailingStop.

if(BuyMarketCount(Symbol(),MagicNumber) > 0 && TrailingStop > 0) {
BuyTrailingStop(Symbol(),TrailingStop,MinimumProfit,MagicNumber); }
if(SellMarketCount(Symbol(),MagicNumber) > 0 && TrailingStop > 0) {
SellTrailingStop(Symbol(),TrailingStop,MinimumProfit,MagicNumber); }
Добавить комментарий

Ваш адрес email не будет опубликован.