Размещение ордеров в MQL4: как открывать и закрывать ордера?

Как трейдер, вы, вероятно, уже знакомы с ценами Bid и Ask. Но вы можете не знать об их роли в размещении ордеров. Очень важно использовать правильную цену при открытии или закрытии ордеров.

Цена Bid — это то, что вы видите на графиках MetaTrader. Цена Ask, как правило, всего на несколько пунктов выше цены Bid. Разница между Bid и Ask заключается в спреде, который представляет собой комиссию брокера за размещение ордера.

Цена Ask — это цена, по которой мы открываем ордера на покупку и закрываем ордера на продажу. Цена Bid — это цена, по которой мы открываем ордера на продажу и закрываем ордера на покупку.

Типы ордеров

В MetaTrader можно разместить три типа ордеров: рыночные, стоповые и лимитные ордера. Рыночные ордера являются наиболее распространенными. Рыночный ордер немедленно открывает позицию по текущей цене Bid или Ask.

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

Если вы разместите рыночный ордер с помощью диалогового окна «Новый ордер» в MetaTrader, в нижней части вы увидите настройку «Включить максимальное отклонение от котировочной цены». Когда этот флажок установлен, вы можете указать максимальное отклонение в пунктах. Это максимально допустимое проскальзывание.

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

Отложенные ордера — это запрос на открытие рыночного ордера по определенной цене. Стоп-ордер на покупку размещается выше текущей цены, а стоп-ордер на продажу размещается ниже текущей цены. Ожидается, что цена в конечном итоге вырастет или упадет до данного уровня и продолжит движение в этом направлении, что приведет к получению прибыли.

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

Также для отложенных ордеров может быть установлено время истечения. Если ордер не исполнен до определенного времени, он автоматически удаляется.

Процесс размещения ордеров

Процесс размещения ордера на MQL состоит из нескольких этапов. Для начала мы должны определить:

  • Тип размещаемого ордера — покупка или продажа. Будет ли это стоп, рыночный или лимитный ордер.
  • Валютная пара для торговли — график, к которому прикреплен советник.
  • Размер лота. Это может быть фиксированный размер лота или тот, который рассчитывается с помощью процедуры управления капиталом.
  • Цена открытия ордера. Для рыночных ордеров это будет текущая цена Bid или Ask. Для отложенных ордеров цена открытия должна быть на минимальном расстоянии от текущей цены и должна быть выше или ниже текущей цены, как того требует тип ордера.
  • Цена стоп-лосса. Стоп-лосс может быть предопределенной ценой, значением индикатора, фиксированным числом пунктов от цены открытия ордера или может быть динамически рассчитан с использованием процедуры управления рисками. Стоп-лосс можно разместить вместе с ордером или добавить к ордеру позже.
  • Цена тейк-профита. Обычно это фиксированное количество пунктов от цены открытия ордера, хотя его можно рассчитать и другими методами. Тейк-профит можно разместить в самом ордере или добавить позже.
  • Идентификаторы ордера — комментарий к ордеру или «магическое число», которое идентифицирует ордер как размещенный конкретным советником.
  • Дополнительная цена истечения для отложенных ордеров, если брокер поддерживает эту возможность.

Функция OrderSend()

Функция OrderSend() используется для размещения ордеров на MQL. Ее синтаксис выглядит следующим образом:

int OrderSend(string Symbol, int Type, double Lots, double Price, int Slippage, double StopLoss, double TakeProfit, string Comment = NULL, int MagicNumber = 0, datetime Expiration = 0, color Arrow = CLR_NONE);
  • Symbol — строка, представляющая валютную пару для торговли, например, GBPUSD. Функция Symbol() используется для определения валютной пары текущего графика.
  • Type — тип размещаемого ордера: покупка или продажа, рынок, стоп или лимит. Это целочисленное значение, представленное следующими константами:
    — OP_BUY — рыночный ордер на покупку (целое значение 0).
    — OP_SELL — Продать рыночный ордер (целое значение 1).
    — OP_BUYSTOP — стоп-ордер на покупку (целое значение 2).
    — OP_SELLSTOP — стоп ордер на продажу (целое значение 3).
    — OP_BUYLIMIT — лимитный ордер на покупку (целочисленное значение 4).
    — OP_SELLLIMIT — лимитный ордер на продажу (целое значение 5).
  • Lots — количество лотов для торговли. Вы можете указать мини-лоты (0.1) или микро-лоты (0.01).
  • Price — цена, по которой открывается ордер. Для ордера на покупку по рынку это будет цена Ask. Для ордера на продажу, это будет цена Bid. Для отложенных ордеров это будет любая действительная цена, которая выше или ниже текущей цены.
  • Slippage — максимальное проскальзывание в пунктах. Брокеры, которые не используют проскальзывание, будут игнорировать этот параметр.
  • StopLoss — цена стоп-лосса. Для ордера на покупку цена стоп-лосс будет ниже цены открытия ордера, а для ордера на продажу выше. Если установлено значение 0, стоп-лосс не будет использоваться.
  • TakeProfit — цена тейк-профита. Для ордера на покупку тейк-профит будет выше цены открытия ордера, а для ордера на продажу — ниже. Если установлено значение 0, тейк-профит не будет использоваться.
  • Comment — необязательная строка, которая будет служить комментарием к ордеру. Комментарии отображаются на вкладке Торговля в окне терминала. Комментарии к ордеру также можно использовать в качестве идентификатора.
  • MagicNumber — необязательное целочисленное значение, которое будет определять порядок размещения конкретного советника. Настоятельно рекомендуется его использовать.
  • Expiration — необязательный срок действия отложенных ордеров.
  • color Arrow — необязательный цвет для стрелки, которая будет отображаться на графике с указанием цены и времени открытия.

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

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

Размещение рыночного ордера

Рассмотрим пример рыночного ордера на покупку. Предположим, что переменные LotSize, Slippage, BuyStopLoss, BuyTakeProfit и MagicNumber уже рассчитаны и являются действительными.

OrderSend(Symbol(),OP_BUY,LotSize,Ask,Slippage,BuyStopLoss,BuyTakeProfit,"Ордер на покупку",MagicNumber,0,Green);

Функция Symbol() возвращает текущий символ графика.

OP_BUY указывает, что это рыночный ордер на покупку.

Ask — это предопределенная переменная в MQL, в которой хранится самая последняя цитата Ask.

Slippage устанавливается с помощью внешней переменной. Если ваш брокер использует 4-значные котировки (2 значные для пар с йеной), 1 пункт будет равен 1 пункту. Однако, если ваш брокер предлагает 3-х и 5-ти значные котировки, то 1 пункт будет равен 0,1 пункта. В этом случае вам необходимо добавить дополнительный ноль в конец Slippage.

Мы добавили комментарий «Ордер на покупку» к этому ордеру.

Поскольку для рыночных ордеров нет срока действия, параметр Expiration равен 0.

Наконец, мы указываем цветовую константу Green, чтобы нарисовать зеленую стрелку на графике.

Вот пример ордера на продажу, используя те же параметры, что и выше:

OrderSend(Symbol(),OP_SELL,LotSize,Bid,Slippage,SellStopLoss,SellTakeProfit,"Ордер на продажу",MagicNumber,0,Red);

Мы используем OP_SELL в качестве типа ордера, чтобы указать ордер на продажу.

Мы используем Bid в качестве цены открытия ордера, чтобы отразить тот факт, что ордера на продажу открываются по цене Bid.

«Ордер на продажу» — это наш комментарий к ордеру.

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

Размещение отложенного стоп-ордера

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

В этих примерах мы будем использовать переменную PendingPrice для цены отложенного ордера. Ее можно рассчитать на основании нашего торгового алгоритма или в качестве внешнего параметра.

Для стоп-ордера на покупку PendingPrice должен быть больше текущей цены Ask. Предположим, что BuyStopLoss и BuyTakeProfit были правильно рассчитаны относительно PendingPrice.

OrderSend(Symbol(),OP_BUYSTOP,LotSize,PendingPrice,Slippage,BuyStopLoss, BuyTakeProfit,"Ордер buy-stop",MagicNumber,0,Green);

Обратите внимание, что мы используем OP_BUYSTOP для указания стоп-ордера на покупку и PendingPrice для цены открытия нашего ордера.

Для стоп-ордера на продажу PendingPrice должен быть меньше текущей цены Bid. В этом примере мы добавим время истечения ордера, используя переменную Expiration. Время истечения должно быть больше текущего времени сервера. Вот пример размещения ордера Sell Stop:

OrderSend(Symbol(),OP_SELLSTOP,LotSize,PendingPrice,Slippage,SellStopLoss, SellTakeProfit,"Sell Stop ордер",MagicNumber,Expiration,Red);

Размещение отложенного лимитного ордера

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

OrderSend(Symbol(),OP_BUYLIMIT,LotSize,PendingPrice,Slippage,BuyStopLoss, BuyTakeProfit,"Buy Limit Order",MagicNumber,0,Green);

Обратите внимание, что мы использовали OP_BUYLIMIT, чтобы указать лимитный ордер на покупку. В остальном наши параметры идентичны параметрам для стоп-ордеров.

Для лимитного ордера на продажу цена отложенного ордера должна быть больше текущей цены Ask. Вот пример ордера на продажу:

OrderSend(Symbol(),OP_SELLLIMIT,LotSize,PendingPrice,Slippage,SellStopLoss, SellTakeProfit,"Sell Limit Order",MagicNumber,Expiration,Red);

Рассчет стоп-лосса и тейк-профита

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

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

Рассчет в пунктах

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

Для ордеров на покупку, цена открытия будет Ask, а для ордеров на продажу, цена открытия будет Bid. Для отложенных стоп-ордеров и лимитных ордеров мы назначаем действительную цену открытия, которая будет отличается от текущей рыночной цены. Мы назначим соответствующую цену переменной OpenPrice.

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

extern int StopLoss = 50;
extern int TakeProfit = 100;

В этом примере мы установили стоп-лосс в 50 пунктов и тейк-профит в 100 пунктов. Вы, вероятно, видели настройки, подобные этим, в советниках, которые уже использовали.

Чтобы рассчитать наш стоп-лосс, нам нужно добавить или вычесть 50 пунктов из цены открытия нашего ордера. Во-первых, нам нужно преобразовать целочисленное значение 50 в дробное значение, которое мы будем использовать для добавления или вычитания из цены открытия. Для пар с йеной 50 пунктов равны 0,50. Для всех остальных пар это будет 0,0050.

Чтобы преобразовать целое число в соответствующее дробное значение, нам нужно умножить нашу внешнюю переменную StopLoss на Point.

Point

Point — это предопределенная переменная в MQL, которая возвращает наименьшую цену валюты в зависимости от количества десятичных знаков. Для валютной пары с 4 десятичными знаками Point равен 0,0001. Для пар с йеной это значение равно 0,01.

Давайте рассчитаем стоп-лосс для ордера на покупку. Мы назначим текущую цену Ask для OpenPrice и используем ее в качестве цены открытия ордера. Далее проверим, что StopLoss больше нуля. Если это так, мы умножим стоп-лосс на Point. Затем мы вычтем полученное значение из OpenPrice. Результат будет сохранен в переменной BuyStopLoss.

double OpenPrice = Ask;
if(StopLoss > 0) double BuyStopLoss = OpenPrice – (StopLoss * Point); // 1.4600 - (50 * 0.0001) = 1.4550

Если StopLoss не больше нуля, то BuyStopLoss инициализируется со значением 0, и стоп-лосс не будет размещен вместе с ордером. Предполагая, что Point равен 0,0001, если цена открытия ордера равна 1,4600, а наш стоп-лосс равен 50 пунктам, тогда цена стоп-лосса для ордера на покупку составит 1,4600 — (0,0050) = 1,4550.

Если наш брокер использует пятизначные котировки, то в нашем примере выше Point будет равен 0,00001.

Если мы используем значение пункта 0,00001 в нашем примере расчета стоп-лосса, стоп-лосс будет рассчитываться как 5 пунктов от цены открытия, а не 50 пипсов. Чтобы получить правильное значение, мы должны добавить дополнительный ноль к нашей настройке стоп-лосса, то есть StopLoss = 500.

Вместо того чтобы требовать от пользователя добавлять дополнительный ноль к своим стоп-лоссам и настройкам тейк-профита, мы создадим функцию, которая всегда будет возвращать 0,01 или 0,0001 независимо от того, использует ли брокер пятизначные котировки. Мы назовем эту функцию PipPoint, потому что она всегда будет возвращать значение Point, равное одному пункту.

double PipPoint(string Currency) {
int CalcDigits = MarketInfo(Currency,MODE_DIGITS);
if(CalcDigits == 2 || CalcDigits == 3) double CalcPoint = 0.01;
else if(CalcDigits == 4 || CalcDigits == 5) CalcPoint = 0.0001;
return(CalcPoint);
}

Строковый аргумент Currency является символом валютной пары, для которой мы хотим получить Point. Функция MarketInfo() с параметром MODE_DIGITS возвращает количество десятичных разрядов для данной пары. Оператор if-else назначает соответствующее значение точки переменной CalcPoint в зависимости от количества цифр.

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

double UsePoint = PipPoint(Symbol());

Вот несколько примеров:

double UsePoint = PipPoint (EURUSD); // Результат 0.0001
double UsePoint = PipPoint (USDJPY); // Результат 0,01

Мы будем использовать эту функцию, чтобы найти значение единственного пункта в оставшейся части этой книги. Как мы показали, переменная Point не будет работать корректно на брокерах с пятизначными котировками при расчете стоимости одного пункта. Вы никогда не можете предполагать, что советник будет использоваться только на 2-х и 4-х значном брокере, поэтому необходимо автоматически определять значение пункта для одного пункта с помощью функции PipPoint().

Проскальзывание и Point

Давайте создадим функцию для определения размера проскальзывания. Как упоминалось ранее, для брокера с пятизначными котировками параметр проскальзывания для функции OrderSend() необходимо увеличить в 10 раз, чтобы получить правильное значение.

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

int GetSlippage(string Currency, int SlippagePips) {
int CalcDigits = MarketInfo(Currency,MODE_DIGITS);
if(CalcDigits == 2 || CalcDigits == 4) double CalcSlippage = SlippagePips; 
else if(CalcDigits == 3 || CalcDigits == 5) CalcSlippage = SlippagePips * 10; 
return(CalcSlippage);
}

Мы передаем символ валюты и параметр внешнего проскальзывания в качестве аргументов. Если валюта использует 2 или 4 цифры, мы используем аргумент SlippagePips в качестве параметра проскальзывания. Если валюта использует 3 или 5 цифр, мы умножаем SlippagePips на 10. Вот как мы используем эту функцию в OrderSend():

// Внешние параметры
extern int Slippage = 5;
 
// Размещение оредра
OrderSend(Symbol(),OP_BUY,LotSize,Ask,GetSlippage(Symbol(),Slippage),BuyStopLoss,BuyTakeProfit,"Ордер на покупку",MagicNumber,0,Green);

В этом примере проскальзывание будет составлять 5 пунктов, а параметр проскальзывания будет автоматически корректироваться в зависимости от количества цифр в котировке валюты.

Проскальзывание и Point как глобальные переменные

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

Поскольку эти значения никогда не изменяются во время выполнения программы, мы вычислим эти значения в функции init(). Предположим, что внешняя переменная Slippage уже присутствует:

// Глобальные переменные
double UsePoint;
int UseSlippage;
 
int init() {
UsePoint = PipPoint(Symbol());
UseSlippage = GetSlippage(Symbol(),Slippage); 
}

Отныне мы будем использовать UsePoint и UseSlippage для ссылки на эти значения. Код выше предполагает, что ваш советник выставляет ордера только на один торговый инструмент.

MarketInfo()

Мы использовали функцию MarketInfo(), чтобы получить значение Point и количество цифр в котировке валюты. Функция MarketInfo() имеет множество применений, и вы будете использовать ее для получения необходимой информации о ценах. Вот синтаксис для функции MarketInfo():

double MarketInfo(string Symbol, int RequestType);

Аргумент Symbol — это просто символ валюты, для которого вы хотите получить информацию. Для текущего символа графика можно использовать функцию Symbol(). Для других символов вам нужно указать символ валюты, например, EURJPY.

RequestType — это целочисленная константа, представляющая информацию, которую вы запрашиваете у функции. Вот список наиболее полезных констант MarketInfo(). Полный список можно найти в справочнике по MQL.

  • MODE_POINT — значение Point. Например, 0,01 или 0,00001.
  • MODE_DIGITS — количество десятичных знаков в цене. Будет 2 или 3 для пар иен и 4 или 5 для всех остальных пар.
  • MODE_SPREAD — текущий спред. Например, 3 пункта (или 30 для брокера с дробным числом пунктов).
  • MODE_STOPLEVEL — уровень стоп-лосса. Например, 3 пункта (или 30 для брокера с дробным числом пунктов).

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

  • MODE_BID — текущая цена Bid.
  • MODE_ASK — текущая цена Ask.
  • MODE_LOW — минимум текущего бара выбранного символа.
  • MODE_HIGH — максимум текущего бара выбранного символа.

Расчет стоп-лосса

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

Вот наш предыдущий расчет стоп-лосса ордера на покупку с добавленной переменной UsePoint. Обратите внимание, что мы присвоили цену Ask переменной OpenPrice:

double OpenPrice = Ask;
if(StopLoss > 0) double BuyStopLoss = OpenPrice – (StopLoss * UsePoint);

А вот расчет для ордера на продажу. Обратите внимание, что мы назначили цену Bid для OpenPrice, и мы просто добавляем вместо вычитания:

double OpenPrice = Bid;
if (StopLoss> 0) double SellStopLoss = OpenPrice + (StopLoss * UsePoint);

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

Расчет тейк-профита

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

if(TakeProfit > 0) double BuyTakeProfit = OpenPrice + (TakeProfit * UsePoint); 
if(TakeProfit > 0) double SellTakeProfit = OpenPrice - (TakeProfit * UsePoint);

Альтернативный метод расчета стоп-лосса

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

Допустим, мы используем торговую систему, которая размещает стоп-лосс на 2 пункта ниже минимума текущего бара. Мы используем предопределенный ценовой массив Low[], чтобы получить минимум бара. Low[0] — это минимум текущего бара, Low[1] — это минимум предыдущего бара и так далее.

Как только мы определили минимум текущего бара, мы умножим 2 на UsePoint, чтобы получить десятичное значение, и вычтем это из нашего минимума:

double BuyStopLoss = Low[0] - (2 * UsePoint);

Таким образом, если минимум бара составляет 1,4760, стоп-лосс будет размещен на 1,4758.

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

Вот пример того, как мы будем использовать iLowest ), чтобы найти самый низкий минимум из последних 10 баров:

int CountBars = 10;
int LowestShift = iLowest(NULL,0,MODE_LOW,CountBars,0);
double BuyStopLoss = Low[LowestShift];

Первым параметром iLowest() является символ валюты — NULL означает, что мы используем текущий символ. Многие функции в MQL используют строковую константу NULL для ссылки на текущий символ. Вторым параметром является период графика — 0 относится к текущему графика.

MODE_LOW — это целочисленная константа, которая задает массив низких цен. Другими словами, мы ищем самый низкий минимум из последних CountBars. Например, если бы мы хотели найти самое низкое закрытие, мы бы использовали MODE_CLOSE.

CountBars — это количество баров, которые мы хотим найти, в данном случае 10. Наконец, последний параметр — это наше начальное местоположение. 0 — текущий бар Чтобы начать с предыдущего бара, посчитайте назад от текущего бара — предыдущий бар равен 1, до этого — 2 и т. д.

Выходные данные функции iLowest() представляют собой целое число, указывающее обратное смещение бара с самым низким значением в ценовом ряду. В приведенном выше примере, если iLowest() возвращает 6, это означает, что самый низкий минимум равен 6 барам назад. Мы храним это значение в переменной LowestShift. Чтобы найти фактическую цену, мы просто получаем значение цены Low [LowestShift] или, другими словами, Low [6].

Если вы хотите рассчитать стоп-лосс для ордера на продажу, используя этот метод, функция iHighest() работает так же. Ссылаясь на пример выше, мы бы использовали MODE_HIGH для параметра массива рядов.

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

double BuyStopLoss = MA;

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

Получение информации об ордере

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

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

OrderSelect()

Вот синтаксис для функции OrderSelect():

bool OrderSelect(int Index, int Select, int Pool = MODE_TRADES)
  • Index — это либо номер заявки по ордеру, который мы хотим выбрать, либо позиция среди ордеров. Параметр Select укажет, какой из них мы используем.
  • Select — константа, указывающая, является ли параметр Index номером заявки или позицией среди ордеров:
    — ECT SELECT_BY_TICKET — значение параметра Index является номером ордера.
    — SELECT_BY_POS — значение параметра Index является позицией среди ордеров.
  • Pool — необязательная константа, обозначающая пул ордеров: отложенные/открытые ордера или закрытые ордера.
    — MODE_TRADES — по умолчанию мы перебираем пул открытых в данный момент ордеров.
    — MODE_HISTORY — перебираем пул закрытых заказов (историю).

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

Вот пример функции OrderSelect(), использующей номер ордера. Переменная Ticket должна содержать действительный тикет ордера:

OrderSelect(Ticket,SELECT_BY_TICKET);

После вызова функции OrderSelect() мы можем использовать любую из функций информации об ордере. Полный список функций, которые можно использовать с OrderSelect (), можно найти в Справочнике по MQL в разделе Торговые функции. Вот список наиболее часто используемых функций:

  • OrderSymbol() — символ инструмента, на котором был размещен выбранный ордер.
  • OrderType() — тип ордера выбранного ордера: покупка или продажа; рынок, стоп или лимит.
  • OrderOpenPrice() — цена открытия выбранного ордера.
  • OrderLots() — размер лота выбранного ордера.
  • OrderStopLoss() — цена стоп-лосса выбранного ордера.
  • OrderTakeProfit() — цена тейк-профита выбранного ордера.
  • OrderTicket() — номер выбранного ордера. Обычно используется при циклическом переборе ордеров с параметром SELECT_BY_POS.
  • OrderMagicNumber() — магический номер выбранного ордера. При циклическом переборе ордеров нам нужно использовать этот параметр для определения ордеров, выставленных нашим советником.
  • OrderComment() — комментарий, который был размещен вместе с ордером. Может быть использовано как вторичный идентификатор ордера.
  • OrderClosePrice() — цена закрытия выбранного ордера. Ордер должен быть уже закрыт (то есть представлен в пуле истории ордеров).
  • OrderOpenTime() — время открытия выбранного ордера.
  • OrderCloseTime() — время закрытия выбранного ордера.
  • OrderProfit() — возвращает прибыль (в валюте депозита) для выбранного ордера.

Нам нужно будет использовать OrderSelect() перед закрытием или изменением ордера.

Закрытие ордеров

Когда мы закрываем рыночный ордер, мы выходим из сделки по текущей рыночной цене. Для ордеров на покупку мы закрываем по цене Bid, а для ордеров на продажу мы закрываем по Ask. Для отложенных ордеров мы просто удаляем ордер.

OrderClose()

Мы закрываем рыночные ордера с помощью функции OrderClose().

bool OrderClose(int Ticket, double Lots, double Price, int Slippage, color Arrow);
  • Ticket — номер тикета рыночного ордера на закрытие.
  • Lots — количество лотов для закрытия. Большинство брокеров допускают частичное закрытие.
  • Price — предпочтительная цена для закрытия сделки. Для ордеров на покупку это будет текущая цена Bid, а для ордеров на продажу текущая цена Ask.
  • Slippage — допустимое проскальзывание от цены закрытия в пунктах.
  • color — цветовая константа для закрывающей стрелки. Если цвет не указан, стрелка не будет нарисована.

Вы можете закрыть часть сделки, указав частичный размер лота. Например, если у вас есть открытая сделка с размером лота 2,00 и вы хотите закрыть половину сделки, укажите 1 лот для аргумента Lots. Обратите внимание, что не все брокеры поддерживают частичное закрытие.

Следующий пример закрывает ордер на покупку на рынке:

OrderSelect(CloseTicket,SELECT_BY_TICKET);
if(OrderCloseTime() == 0 && OrderType() == OP_BUY) {
double CloseLots = OrderLots(); double ClosePrice = Bid;
bool Closed = OrderClose(CloseTicket,CloseLots,ClosePrice,UseSlippage,Red); 
}

Переменная CloseTicket — это номер ордера, который мы хотим закрыть. Функция OrderSelect() выбирает ордер и позволяет нам получить о нем информацию. Мы используем OrderCloseTime(), чтобы проверить время закрытия ордера, и чтобы увидеть, был ли ордер уже закрыт. Если OrderCloseTime() возвращает 0, то мы знаем, что ордер еще не закрыт.

Нам также необходимо проверить тип ордера, так как тип ордера определяет его цену закрытия. Функция OrderType() возвращает целое число, указывающее на тип ордера. Если это рыночный ордер на покупку, обозначенный OP_BUY, мы продолжим закрывать ордер.

Затем мы получаем размер лота ордера с помощью OrderLots() и сохраняем это значение в CloseLots. Мы назначаем текущую цену Bid для ClosePrice. Затем мы вызываем функцию OrderClose(), чтобы закрыть наш ордер.

Мы задаем наш параметр Slippage с помощью UseSlippage и указываем красную стрелку. Возвращаемое логическое значение сохраняется в переменной Closed. Если ордер был успешно закрыт, значение Closed будет true, в противном случае — false.

Чтобы закрыть ордер на продажу, все, что вам нужно сделать, это изменить тип ордера на OP_SELL и назначить текущую цену Ask для ClosePrice:

if (OrderCloseTime () == 0 && OrderType () == OP_SELL) {
double CloseLots = OrderLots (); double ClosePrice = Ask;
bool Closed = OrderClose (CloseTicket, CloseLots, ClosePrice, UseSlippage, Red);

OrderDelete()

Существует отдельная функция для закрытия отложенных ордеров — OrderDelete(). У этой функции есть два аргумента: номер ордера и цвет стрелки.

OrderSelect(CloseTicket,SELECT_BY_TICKET);
if(OrderCloseTime() == 0 && OrderType() == OP_BUYSTOP) {
    bool Deleted = OrderDelete(CloseTicket,Red);
}

Нам нужно проверить тип ордера, чтобы убедиться, что это отложенный ордер. Константы типа отложенного ордера: OP_BUYSTOP, OP_SELLSTOP, OP_BUYLIMIT и OP_SELLLIMIT. Чтобы закрыть другие типы отложенных ордеров, просто измените тип ордера.

Пример кода простого советника

Давайте посмотрим, как код, который мы обсудили, будет работать в советнике. Наш советник будет основан на пересечении скользящих средних. Ордер на покупку открывается, когда скользящая средняя за 10 периодов будет больше скользящей средней за 20 периодов. Когда скользящая средняя за 10 периодов меньше скользящей средней за 20 периодов, открывается ордер на продажу.

Советник будет чередовать открытие ордеров на покупку и продажу. Ордера будут закрыты, когда ордер будет открыт в обратном направлении, либо по стоп-лоссу или тейк-профиту. Мы будем использовать глобальные переменные BuyTicket и SellTicket для хранения последнего ордера. При открытии нового ордера тикет последнего заказа очищается. Это предотвращает открытие нескольких последовательных ордеров.

#property copyright "Александр Паркер"
// Внешние переменные 
extern double LotSize = 0.1;
extern double StopLoss = 50;
extern double TakeProfit = 100;
 
extern int Slippage = 5; 
extern int MagicNumber = 123;
 
extern int FastMAPeriod = 10;
extern int SlowMAPeriod = 20;
 
// Глобальные переменные
int BuyTicket;
int SellTicket;
double UsePoint;
int UseSlippage;
 
// Функция init
int init() {
UsePoint = PipPoint(Symbol());
UseSlippage = GetSlippage(Symbol(),Slippage);
}
 
// Функция start
int start() {
 
// Скользящая средняя
double FastMA = iMA(NULL,0,FastMAPeriod,0,0,0,0); 
double SlowMA = iMA(NULL,0,SlowMAPeriod,0,0,0,0);
 
// Ордер на покупку
if(FastMA > SlowMA && BuyTicket == 0) {
OrderSelect(SellTicket,SELECT_BY_TICKET);
 
// Закрытие ордера
if(OrderCloseTime() == 0 && SellTicket > 0) {
double CloseLots = OrderLots(); double ClosePrice = Ask;
bool Closed = OrderClose(SellTicket,CloseLots,ClosePrice,UseSlippage,Red); 
}
 
double OpenPrice = Ask;
 
// Рассчет стоп-лосса и тейк-профита
 
if(StopLoss > 0) double BuyStopLoss = OpenPrice - (StopLoss * UsePoint);
if(TakeProfit > 0) double BuyTakeProfit = OpenPrice + (TakeProfit * UsePoint);
 
// Открытие ордера на покупку
BuyTicket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit,"Ордер на покупку",MagicNumber,0,Green);
SellTicket = 0; 
}
 
// Ордер на продажу
if(FastMA < SlowMA && SellTicket == 0) {
OrderSelect(BuyTicket,SELECT_BY_TICKET);
if(OrderCloseTime() == 0 && BuyTicket > 0) {
CloseLots = OrderLots(); ClosePrice = Bid;
Closed = OrderClose(BuyTicket,CloseLots,ClosePrice,UseSlippage,Red); 
}
OpenPrice = Bid;
 
if(StopLoss > 0) double SellStopLoss = OpenPrice + (StopLoss * UsePoint); 
if(TakeProfit > 0) double SellTakeProfit = OpenPrice - (TakeProfit * UsePoint);
SellTicket = OrderSend(Symbol(),OP_SELL,LotSize,OpenPrice,UseSlippage, SellStopLoss,SellTakeProfit,"Ордер на продажу",MagicNumber,0,Red);
BuyTicket = 0; 
}
return(0); 
}
 
 // Функция Pip Point
    double PipPoint(string Currency) {
int CalcDigits = MarketInfo(Currency,MODE_DIGITS);
if(CalcDigits == 2 || CalcDigits == 3) double CalcPoint = 0.01; 
else if(CalcDigits == 4 || CalcDigits == 5) CalcPoint = 0.0001; 
return(CalcPoint);
}
 
// Проскальзывание
int GetSlippage(string Currency, int SlippagePips) {
int CalcDigits = MarketInfo(Currency,MODE_DIGITS);
if(CalcDigits == 2 || CalcDigits == 4) double CalcSlippage = SlippagePips; 
else if(CalcDigits == 3 || CalcDigits == 5) CalcSlippage = SlippagePips * 10; 
return(CalcSlippage);
}

Мы объявляем BuyTicket и SellTicket как глобальные переменные — таким образом тикет ордера сохраняется между выполнениями программы. Мы могли бы также объявить их как статические переменные в функции start().

Мы добавим UsePoint и UseSlippage в качестве глобальных переменных и подсчитаем их значения. Наша функция init() запускается первой. Мы вызываем функции PipPoint() и GetSlippage() (объявленные внизу файла) и присваиваем возвращаемые значения нашим глобальным переменным. Мы будем использовать их при ссылках на значения Point или проскальзывания в остальной части нашего советника.

Далее идет функция start(). Функция iMA() вычисляет скользящее среднее. Переменная FastMA содержит 10-периодное скользящее среднее, которое устанавливается с помощью переменной FastMAPeriod. Переменная SlowMA — это скользящее среднее за 20 периодов, установленное с помощью SlowMAPeriod. Все остальное установлено по умолчанию.

Мы используем оператор if для определения условий открытия ордеров. Если текущая скользящая средняя за 10 периодов (FastMA) больше, чем скользящая средняя за 20 периодов (SlowMA), и если BuyTicket равен 0, мы открываем ордер на покупку.

Прежде чем открыть ордер на покупку, закроем текущий ордер на продажу, если он существует. Мы используем OrderSelect(), чтобы получить текущий SellTicket. Если время закрытия ордера равно 0 (указывает на то, что ордер еще не был закрыт), а SellTicket больше 0 (указывает на то, что SellTicket, вероятно, действителен), мы продолжим и закроем ордер на продажу.

Использование отложенных ордеров

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

extern int PendingPips = 10;

Мы добавляем функцию OrderDelete() в наш блок ордеров на покупку и продажу, чтобы закрыть все незакрыте отложенные ордера. Нам нужно проверить тип ордера, указанного в SellTicket, чтобы убедиться, что мы используем правильную функцию для закрытия ордера.

OrderSelect(SellTicket,SELECT_BY_TICKET);
// Закрытие ордера
if(OrderCloseTime() == 0 && SellTicket > 0 && OrderType() == OP_SELL) {
double CloseLots = OrderLots(); 
double ClosePrice = Ask;
bool Closed = OrderClose(SellTicket,CloseLots,ClosePrice,UseSlippage,Red);
if(Closed == true) SellTicket = 0; 
}
 
// Удаление ордера
else if(OrderCloseTime() == 0 && SellTicket > 0 && OrderType() == OP_SELLSTOP) {
        bool Deleted = OrderDelete(SellTicket,Red);
        if(Deleted == true) SellTicket = 0;
}

Мы используем OrderType(), чтобы проверить, является ли выбранный ордер на продажу рыночным или стоп-ордером. Если это рыночный ордер, мы закрываем его с помощью OrderClose(). Если это отложенный ордер, мы закрываем его с помощью OrderDelete().

Вот наш расчет цены отложенного ордера. Мы просто конвертируем PendingPips в дробное значение с помощью UsePoint и добавляем его к текущей цене закрытия. Мы будем хранить это значение в переменной PendingPrice. Далее мы рассчитываем стоп-лосс и тейк-профит относительно цены нашего отложенного ордера. Наконец, мы выставляем наш отложенный ордер с помощью OrderSend(), сохраняя результат сделки в переменной BuyTicket:

double PendingPrice = Close[0] + (PendingPips * UsePoint);
if(StopLoss > 0) double BuyStopLoss = PendingPrice - (StopLoss * UsePoint);
if(TakeProfit > 0) double BuyTakeProfit = PendingPrice + (TakeProfit * UsePoint);
BuyTicket = OrderSend(Symbol(),OP_BUYSTOP,LotSize,PendingPrice,UseSlippage, BuyStopLoss,BuyTakeProfit,"Buy стоп ордер",MagicNumber,0,Green);

Код ниже показывает изменения для стоп-ордера на продажу:

OrderSelect(BuyTicket,SELECT_BY_TICKET);
 
// Закрытие ордера
if(OrderCloseTime() == 0 && BuyTicket > 0 && OrderType() == OP_BUY) {
CloseLots = OrderLots(); 
ClosePrice = Bid;
Closed = OrderClose(BuyTicket,CloseLots,ClosePrice,UseSlippage,Red);
if(Closed == true) BuyTicket = 0; 
}
 
// Удаление ордера
else if(OrderCloseTime() == 0 && BuyTicket > 0 && OrderType() == OP_BUYSTOP) {
Closed = OrderDelete(BuyTicket,Red); 
if(Closed == true) BuyTicket = 0;
}
 
PendingPrice = Close[0] - (PendingPips * UsePoint);
double SellStopLoss = PendingPrice + (StopLoss * UsePoint); 
double SellTakeProfit = PendingPrice - (TakeProfit * UsePoint);
SellTicket = OrderSend(Symbol(),OP_SELLSTOP,LotSize,PendingPrice,UseSlippage, SellStopLoss,SellTakeProfit,"Ордер на продажу",MagicNumber,0,Red);
BuyTicket = 0;

Полный код советника:

#property copyright "Александр Паркер"
 
// Внешние переменные
extern double LotSize = 0.1;
extern double StopLoss = 50;
extern double TakeProfit = 100;
extern int PendingPips = 10;
 
extern int Slippage = 5; 
extern int MagicNumber = 123;
 
extern int FastMAPeriod = 10;
extern int SlowMAPeriod = 20;
 
// Глобальные переменные
int BuyTicket;
int SellTicket;
double UsePoint;
int UseSlippage;
 
// Init функция
int init() {
UsePoint = PipPoint(Symbol());
UseSlippage = GetSlippage(Symbol(),Slippage);
}
 
// Start функция
int start() {
// Скользящие средние
double FastMA = iMA(NULL,0,FastMAPeriod,0,0,0,0); 
double SlowMA = iMA(NULL,0,SlowMAPeriod,0,0,0,0);
 
// Ордер на покупку
if(FastMA > SlowMA && BuyTicket == 0) {
OrderSelect(SellTicket,SELECT_BY_TICKET);
 
// Закрытие ордера
if(OrderCloseTime() == 0 && SellTicket > 0 && OrderType() == OP_SELL) {
double CloseLots = OrderLots(); 
double ClosePrice = Ask;
bool Closed = OrderClose(SellTicket,CloseLots,ClosePrice,UseSlippage,Red); 
}
 
// Удаление ордера
else if(OrderCloseTime() == 0 && SellTicket > 0 && OrderType() == OP_SELLSTOP) {
bool Deleted = OrderDelete(SellTicket,Red);
}
 
double PendingPrice = High[0] + (PendingPips * UsePoint);
 
// Расчет стоп-лосса и тейк-профита
if(StopLoss > 0) double BuyStopLoss = PendingPrice - (StopLoss * UsePoint); 
if(TakeProfit > 0) double BuyTakeProfit = PendingPrice + (TakeProfit * UsePoint);
 
// Ордер на покупку
BuyTicket = OrderSend(Symbol(),OP_BUYSTOP,LotSize,PendingPrice,UseSlippage,BuyStopLoss,BuyTakeProfit,"Buy Stop ордер",MagicNumber,0,Green);
SellTicket = 0; 
}
 
// Ордер на продажу
if(FastMA < SlowMA && SellTicket == 0) {
OrderSelect(BuyTicket,SELECT_BY_TICKET);
if(OrderCloseTime() == 0 && BuyTicket > 0 && OrderType() == OP_BUY) {
CloseLots = OrderLots(); 
ClosePrice = Bid;
Closed = OrderClose(BuyTicket,CloseLots,ClosePrice,UseSlippage,Red); 
}
else if(OrderCloseTime() == 0 && SellTicket > 0 && OrderType() == OP_BUYSTOP) {
Deleted = OrderDelete(SellTicket,Red); 
}
PendingPrice = Low[0] - (PendingPips * UsePoint);
if(StopLoss > 0) double SellStopLoss = PendingPrice + (StopLoss * UsePoint); 
if(TakeProfit > 0) double SellTakeProfit = PendingPrice - (TakeProfit * UsePoint);
SellTicket = OrderSend(Symbol(),OP_SELLSTOP,LotSize,PendingPrice,UseSlippage, SellStopLoss,SellTakeProfit,"Sell Stop ордер",MagicNumber,0,Red);
BuyTicket = 0; 
}
return(0); 
}
 
// Pip Point функция
double PipPoint(string Currency)
{
int CalcDigits = MarketInfo(Currency,MODE_DIGITS);
if(CalcDigits == 2 || CalcDigits == 3) double CalcPoint = 0.01;
else if(CalcDigits == 4 || CalcDigits == 5) CalcPoint = 0.0001; return(CalcPoint);
}
 
// GetSlippage функция
int GetSlippage(string Currency, int SlippagePips) {
int CalcDigits = MarketInfo(Currency,MODE_DIGITS);
if(CalcDigits == 2 || CalcDigits == 4) double CalcSlippage = SlippagePips; 
else if(CalcDigits == 3 || CalcDigits == 5) CalcSlippage = SlippagePips * 10; return(CalcSlippage);
}
Комментарии (1)
  1. Добрый вечер! Нашел небольшую неточность во втором скрипте. Там не происходит закрытие buy ордеров ввиду опечатки при открытии sell ордеров. Там используется глобальная переменная не BuyTicket, а SellTicket