По умолчанию метод размещения стоп-лосса и тейк-профита с помощью рыночного ордера заключается в размещении их с помощью функции OrderSend(). Однако это невозможно для ECN/STP-брокеров.
В этом случае нам нужно будет разместить стоп-лосс и тейк-профит после размещения ордера с помощью функции OrderModify(). Это относится только к рыночным ордерам — для отложенных ордеров вы все равно сможете разместить стоп-лосс и тейк-профит с помощью функции OrderSend().
Изменение ордеров
После размещения ордера вы можете изменить тейк-профит, стоп-лосс, цену отложенного ордера или время истечения, используя функцию OrderModify(). Чтобы использовать OrderModify(), нам понадобится тикет ордера, который мы хотим изменить. Вот синтаксис функции OrderModify():
bool OrderModify(int Ticket, double Price, double StopLoss, double TakeProfit, datetime Expiration, color Arrow = CLR_NONE)
- Ticket — номер тикета ордера, который нужно изменить.
- Price — цена нового отложенного ордера.
- StopLoss — новая цена стоп-лосса.
- TakeProfit — новая цена тейк-профита.
- Expiration — новое время истечения для отложенных ордеров.
- Arrow — необязательный параметр цвета стрелки.
Если изменение ордера выполнено успешно, OrderModify() вернет значение true. В противном случае будет возвращено false.
При изменении ордеров мы должны быть уверены, что значения, которые мы передаем функции, действительны. Например, ордер должен быть открытым — мы не можем изменить закрытый ордер. При изменении отложенных ордеров с помощью параметра Price, ордер не должен быть уже исполнен.
Измененная цена ордера также не должна быть слишком близкой к текущей цене Bid или Ask. Мы также должны проверить стоп-лосс и тейк-профит.
Если мы не изменяем определенный параметр, мы должны передать исходное значение в функцию OrderModify(). Например, если мы изменяем только стоп-лосс для отложенного ордера, мы должны извлечь текущую цену ордера и тейк-профит с помощью OrderSelect() и передать эти значения в функцию OrderModify().
Если вы попытаетесь изменить ордер без указания каких-либо измененных значений, вы получите ошибку 1: «нет результата».
Добавление стоп-лосса и тейк-профита в существующий ордер
Во-первых, нам нужно убедиться, что ордер был размещен правильно. Мы делаем это, проверяя возвращаемое значение функции OrderSend(), которая является номером тикета только что размещенного ордера. Если ордер не был создан из-за ошибки, номер тикета будет равен -1.
Затем мы используем функцию OrderSelect(), чтобы получить информацию о только что размещенном ордере. Мы будем использовать функции OrderOpenPrice(), OrderTakeProfit(), OrderStopLoss() и, опционально, функции OrderExpiration() при передаче неизменных значений в функцию OrderModify(). Наконец, мы будем использовать OrderModify(), чтобы добавить стоп-лосс и зафиксировать прибыль в ордере.
Разберем пример, где мы устанавливаем стоп-лосс и тейк-профит для ордера на покупку, используя функцию OrderModify().
int BuyTicket = OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage,0,0, "Buy Order",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, поскольку вы не можете изменить цену рыночного ордера.
Переменные 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(). Перед изменением ордера мы проверяем, что наша новая цена отложенного ордера не совпадает с текущей ценой отложенного ордера.
Для OrderModify() мы указываем тикет ордера, новую цену, сохраненную в NewPendingPrice, и неизменные значения стоп-лосса и тейк-профита, представленные OrderStopLoss() и OrderTakeProfit().
Проверка цен для ордеров
Стоп-лосс, тейк-профит и цены отложенного ордера должны находиться на минимальном расстоянии от цен Bid и Ask. Если цена стоп-ордера или отложенного ордера слишком близка к текущей цене, возникнет ошибка, и ордер не будет размещен. Это одна из самых распространенных ошибок, и ее легко можно предотвратить, если трейдер будет устанавливать свои стопы и отложенные ордера на достаточном расстоянии от цены.
Но в периоды быстрого движения цены действительные цены стоп-лосса могут быть признаны недействительными за счет расширения спредов. Разные брокеры имеют разные уровни стопов, поэтому стоп-лосс, действительный для одного брокера, может быть слишком близким для другого. Некоторые торговые системы устанавливают цены стопов и отложенных ордеров на основании значений индикатора, максимумов или минимумов цены или другого метода расчета, когда минимальное расстояние не может быть гарантировано.
По этим причинам всегда необходимо убедиться, что цена стоп-лосса, тейк-профита или отложенного ордера действительна и не находится слишком близко к текущей рыночной цене.
Уровни стопов
Уровень стопа — это количество пунктов от текущей цены Bid или Ask, на которое могут быть выставлены стопы и отложенные ордера. Для большинства брокеров уровень стопа составляет примерно 3-4 пункта. У ECN-брокеров обычно очень узкие уровни стопов, в то время как у других брокеров, таких как Alpari, более широкие уровни стопов (не менее 8 пунктов).
По обе стороны от этой ценовой линии находятся границы, обозначенные уровнями стопов. Стоп-лосс, тейк-профит и отложенные ордера должны быть размещены за пределами этих границ.
Функция MarketInfo() с параметром MODE_STOPLEVEL используется для получения уровня стопа. Уровень остановки выражается как целое число, и его нужно будет преобразовать в дробное значение с помощью Point.
Для 4-значных котировок со стоп-уровнем в 3 пункта функция MarketInfo() с MODE_STOPLEVEL вернет 3. Для 5-значных котировок со стоп-уровнем в 3 пункта MarketInfo() вернет 30 из-за дополнительного десятичного числа. Вот код для извлечения уровня стопов и преобразования его в десятичное значение:
double StopLevel = MarketInfo (Symbol (), MODE_STOPLEVEL) * Point;
Обратите внимание, что мы используем предопределенную переменную Point вместо функции PipPoint(), которую мы создали ранее. Это потому, что нам нужно умножить уровень стопа на фактическое значение Point. Для валюты из 4 цифр Point составит 0,0001, а для валюты из 5 цифр — 0,00001. Если уровень стопа составляет 3 пункта, как показано выше, то дробное значение будет 0,0003.
Теперь, когда мы выяснили, как найти уровень стопа, нам нужно рассчитать минимальное и максимальное значения для нашего стоп-лосса, тейк-профита и отложенного ордера. Мы делаем это, добавляя или вычитая уровень стопа из наших текущих цен Bid и Ask.
Следующий код рассчитывает минимально допустимую цену для тейк-профита на покупку, стоп-лосс на продажу, стоп-ордер на покупку или лимитный ордер на продажу. Мы будем использовать значение StopLevel, которое мы рассчитали выше.
double UpperStopLevel = Ask + StopLevel;
Если наша цена Ask составляет 1,4650, а StopLevel равен 0,0003 пункта, как рассчитано выше, минимальная цена уровня стопа будет 1,4653. Если мы размещаем тейк-профит на покупку с этим ордером, то он должен быть выше этой цены.
Следующий код рассчитывает максимально допустимую цену для тейк-профита на продажу, стоп-лосс на покупку, стоп-ордер на продажу или лимитного ордера на продажу. Обратите внимание, что мы просто используем Bid вместо Ask и вычитаем вместо добавления.
double LowerStopLevel = Bid - StopLevel;
Перед размещением ордера используйте приведенные выше значения UpperStopLevel и LowerStopLevel, чтобы проверить стоп-лосс, тейк-профит и цены отложенного ордера. Имейте в виду, что цены могут быстро меняться, и вам нужно, чтобы ваши фактические стопы, прибыль и отложенные ордера находились далеко за пределами данных уровней.
Проверка цен для стоп-лосса и тейк-профита
Минимальный тейк-профит в пунктах будет равен цене открытия ордера, плюс или минус уровень стопа. Если уровень стопа составляет 3 пункта, а цена открытия ордера — 1.4500, цена тейк-профита для ордера на покупку должна быть выше 1.4503.
Минимальный стоп-лосс в пунктах для рыночного ордера будет включать текущий спред, поэтому минимальный стоп-лосс будет больше минимального тейк-профита. Например, если уровень стопа составляет 3 пункта, спред составляет 2 пункта, а цена открытия ордера составляет 1,4500, стоп-лосс для ордера на покупку на рынке должен быть ниже 1,4495.
Это не относится к отложенным ордерам, поэтому при проверке стоп-лосса для отложенных ордеров указывать спред не обязательно. Таким образом, если вы выставляете отложенный ордер на 1.4500, а уровень стопа составляет 3 пункта, то стоп-лосс можно разместить в любом месте ниже 1.4497.
Вот пример, где мы проверяем стоп-лосс и тейк-профит для ордера на покупку, чтобы убедиться, что цены действительны. Если цена стоп-лосс или тейк-профит недействительна, мы автоматически настроим ее так, чтобы она находилась в нескольких пунктах за пределами уровня стопа.
double MinStop = 5 * UsePoint; if (BuyStopLoss> LowerStopLevel) BuyStopLoss = LowerStopLevel - MinStop; if (BuyTakeProfit <UpperStopLevel) BuyTakeProfit = UpperStopLevel + MinStop;
Переменная MinStop добавляет или вычитает 5 пунктов от уровня стопа, чтобы гарантировать, что наши проверенные цены не станут недействительными из-за проскальзывания. Вы можете отрегулировать это значение для обеспечения достаточного минимального уровня или даже использовать внешнюю переменную для корректировки этой суммы.
Вторая строка сравнивает наш стоп-лосс с нашим LowerStopLevel. Если стоп-лосс больше нашего нижнего стоп-уровня, мы знаем, что стоп-лосс недействителен. В этом случае мы корректируем стоп-лосс на несколько пунктов ниже нашего уровня. Третья строка делает то же самое для нашего тейк-профита.
Чтобы проверить стоп-лосс и тейк-профит для ордера на продажу, мы просто изменим расчеты:
if (SellTakeProfit> LowerStopLevel) SellTakeProfit = LowerStopLevel - MinStop; if (SellStopLoss <UpperStopLevel) SellStopLoss = UpperStopLevel + MinStop;
Вместо автоматической корректировки недопустимой цены вы также можете отобразить сообщение об ошибке и остановить выполнение советника. Таким образом, пользователь должен будет перенастроить свой стоп-лосс или тейк-профит перед продолжением. Вот пример того, как это можно сделать:
if (BuyStopLoss > LowerStopLevel) { Alert («Стоп-лосс слишком мал!»); return (0); }
Если рассчитанный стоп-лосс выше уровня стопа и, следовательно, находится слишком близок к цене, функция Alert() отобразит всплывающее сообщение для пользователя. Оператор возврата выходит из текущей функции и гарантирует, что ордер не будет размещен.
Также может быть полезно печатать сообщение в журнал:
if(BuyStopLoss > LowerStopLevel) { BuyStopLoss = LowerStopLevel - MinStop; Print("Стоп-лосс был автоматически настроен."); }
Проверка цены для отложенных ордеров
Вот как мы проверяем цену отложенного ордера для стоп-ордера на покупку или продажу. Переменная PendingPrice хранит цену отложенного ордера:
if (PendingPrice < UpperStopLevel) PendingPrice = UpperStopLevel + MinStop;
Обратите внимание на то, что логика здесь идентична приведенному выше коду, который проверяет цену на покупку и тейк-профит, а также цену на продажу. А вот код для проверки цены отложенного ордера для стоп-ордера на продажу или ордера на покупку:
if (PendingPrice > UpperStopLevel) PendingPrice = UpperStopLevel - MinStop;
Рассчет размера лота
Помимо выбора подходящего уровня стоп-лосса и тейк-профита, использование подходящего размера лота является одним из лучших инструментов управления рисками. Задать размер лота можно так же просто, как объявить внешнюю переменную или использовать фиксированный размера лота для каждого ордера. Мы рассмотрим более сложный метод, который вычисляет размер лота на основе максимальной суммы, которую вы готовы потерять за сделку.
Чрезмерное кредитное плечо — один из главных убийц депозитов трейдеров. Использование размеров лотов, которые слишком велики по отношению к вашему эквити, может легко уничтожить вашу учетную запись.
Рекомендуется рисковать не более 2-3% своего капитала за сделку. Максимальная сумма, которую вы можете потерять за сделку, должна составлять не более 2-3% от вашего счета.
Управление капиталом
Чтобы рассчитать размер лота с помощью этого метода, нам нужно указать процент используемых средств и стоп-лосс в пунктах. Мы будем использовать внешнюю переменную EquityPercent, чтобы установить процент использования капитала. Мы предполагаем, что используется стоп-лосс в 50 пипсов.
extern double EquityPercent = 2;
Во-первых, нам нужно рассчитать сумму капитала, указанную EquityPercent. Если у нас есть баланс в размере 10 000 долларов США, и мы используем 2% нашего капитала, тогда расчет будет следующим:
double RiskAmount = AccountEquity () * (EquityPercent / 100);
AccountEquity() — это MQL-функция, которая возвращает текущий баланс счета. Мы делим EquityPercent на 100, чтобы получить дробное значение (0,02). Затем мы умножаем его на AccountEquity(), чтобы вычислить сумму используемого капитала. 2% от 10000 долларов — это 200 долларов, и они будут храниться в переменной RiskAmount.
Далее мы должны найти значение тика. Это прибыль на пункт, если мы торговали одним лотом желаемой валюты. Например, если мы торгуем 1 лотом EURUSD на стандартном счете (лоты по 100 тыс.), прибыль за пункт составит 10 долларов. На мини-счете (10 000 лотов) прибыль за пункт будет 1 доллар.
Мы можем использовать функцию MarketInfo() с параметром MODE_TICKVALUE, чтобы получить прибыль за пункт для указанной валюты. Значение тика должно быть указано в пунктах, поэтому, если мы торгуем на брокере с дробным количеством пунктов (3 или 5 десятичных знаков), мы должны умножить значение тика на 10.
double TickValue = MarketInfo (Symbol (), MODE_TICKVALUE); if (Point == 0.001 || Point == 0.00001) TickValue * = 10;
Предполагая, что мы торгуем на стандартном счете, значение тика для EURUSD будет равно 10. Это будет сохранено в переменной TickValue. Если это брокер с пятизначными котировками, то TickValue будет равен 1. Нам нужно умножить это значение на 10, чтобы сделать его эквивалентным одному пункту. Если переменная Point указывает, что валюта имеет 3 или 5 десятичных знаков, то значение TickValue будет умножено на 10, чтобы сделать его равным значению 2 или 4 десятичных знаков.
Следующим шагом является расчет размера нашего лота. Сначала мы делим RiskAmount на StopLoss. Это даст нам нашу прибыль за тик для данного ордера. 200 долларов, разделенные на наш стоп-лосс в 50, дадут нам 4 доллара. Теперь все, что нам нужно сделать, это разделить это значение на TickValue, чтобы получить размер лота:
double CalcLots = (RiskAmount / StopLoss) / TickValue;
Наш расчетный размер лота на стандартном счете составит 0,4 лота. На мини-счете рассчитанный размер лота составит 4 лота. Это значение хранится в переменной CalcLots.
Если вы используете правильное управление капиталом, процент капитала, который вы используете, будет постоянным. (1-2% для консервативного риска, до 5% для более высокого риска). С другой стороны, ваш стоп-лосс будет меняться в зависимости от выбранного таймфрейма и вашей торговой системы. Размер лота будет также будет варьироваться в зависимости от размера вашего стоп-лосса.
Фиксированный стоп-лосс приведет к увеличению размера лота, что обеспечит большую выгоду, если ваш ордер достигнет тейк-профита. С другой стороны, если вы используете большой стоп-лосс, размер вашего лота будет достаточно маленьким. Этот метод лучше всего выиграет от использования довольно узких стопов или больших значений тейк-профита.
Нам нужно иметь возможность выбирать между расчетом размера лота или использованием фиксированного размера лота. Давайте используем внешнюю логическую переменную DynamicLotSize для включения и выключения расчета размера лота:
// Внешние переменные extern bool DynamicLotSize = true; extern double EquityPercent = 2; extern double FixedLotSize = 0,1; // Запуск функции if (DynamicLotSize == true) { double RiskAmount = AccountEquity () * (EquityPercent / 100); double TickValue = MarketInfo (Symbol (), MODE_TICKVALUE); if (Digits == 3 || Digits == 5) TickValue * = 10; double CalcLots = (RiskAmount / StopLoss) / TickValue; double LotSize = CalcLots; } else LotSize = FixedLotSize;
Если для DynamicLotSize установлено значение true, мы рассчитаем размер лота на основании стоп-лосса и присвоим это значение переменной LotSize. Если DynamicLotSize имеет значение false, мы просто назначаем значение FixedLotSize для LotSize. Переменная LotSize будет передана в функцию OrderSend() в качестве размера лота для выбранного ордера.
Проверка размера лота
Так же, как стоп-лосс, тейк-профит и цены отложенного ордера, размер лота также должен быть проверен, чтобы убедиться, что он приемлем для вашего брокера. Это означает, что размер вашего лота не должен быть слишком большим или слишком маленьким, и его не следует указывать в микролотах (0,01), если ваш брокер их не поддерживает. Вам также следует нормализовать размер вашего лота до соответствующего десятичного знака.
Давайте сначала проверим минимальный и максимальный размер лота. Функция MarketInfo(), использующая параметры MODE_MINLOT и MODE_MAXLOT, будет использоваться для сравнения размера текущего лота с минимальным и максимальным размером лота. Если размер лота недействителен, он будет автоматически изменен до минимального или максимального.
if (LotSize < MarketInfo (Symbol(), MODE_MINLOT)) { LotSize = MarketInfo (Symbol(), MODE_MINLOT); } else if (LotSize > MarketInfo (Symbol(), MODE_MAXLOT)) { LotSize = MarketInfo (Symbol(), MODE_MAXLOT); }
Мы просто сравниваем значение LotSize, наш расчетный или фиксированный размер лота с минимальным и максимальным размером лота. Если LotSize меньше минимального размера лота или превышает максимальный размер лота, ему будет назначено соответствующее минимальное или максимальное значение.
Если вы попытаетесь использовать размер микро-лота для брокера, который разрешает только мини-лоты, вы получите ошибку, и сделка не будет размещена.
if (MarketInfo (Symbol (), MODE_LOTSTEP) == 0,1) { LotSize = NormalizeDouble (LotSize, 1); } else LotSize = NormalizeDouble (LotSize, 2);
Функция NormalizeDouble() округляет значение LotSize до количества цифр, указанного во втором аргументе. В первой строке, если размер шага равен 0,1 и указывает, что брокер использует только мини-лоты, LotSize будет округлен до одного десятичного знака. В противном случае, LotSize будет округлен до 2 десятичных знаков.
Если в будущем вы столкнетесь с брокером, который допускает размеры лотов до трех знаков после запятой, то вы можете легко изменить приведенный выше код, чтобы проверить это. Но на данный момент практически каждый брокер MetaTrader использует одно или два десятичных знака для определения размера лота.
Контекст торговли
В MetaTrader есть единый поток исполнения сделок для советников. Это означает, что только один эксперт может торговать одновременно, независимо от того, сколько экспертов работает в терминале. Перед началом любых торговых операций мы должны проверить, используется ли поток исполнения сделки в настоящее время.
Функция IsTradeContextBusy() вернет true, если поток исполнения сделки занят. Мы будем вызывать эту функцию непосредственно перед вызовом любых торговых функций, включая OrderSend(), OrderClose(), OrderDelete() или OrderModify().
while(IsTradeContextBusy()) Sleep(10); int Ticket = OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage,0,0, "Buy Order",MagicNumber,0,Green);
Мы используем цикл while для оценки IsTradeContextBusy(). Если функция возвращает true, указывая, что поток исполнения сделки занят, советник будет остановлен в течение 10 миллисекунд. Цикл while будет выполняться до тех пор, пока IsTradeContextBus () вернет true. Как только поток торговли освобождается, торговля возобновляется.
Если советник пытается торговать, когда поток выполнения сделки занят, возникает ошибка 147: «торговый контекст занят».
Обновление заданных переменных
Значения предопределенных переменных, таких как Bid и Ask, устанавливаются, когда советник начинает свою работу. Время, необходимое для выполнения кода советника, очень мало и может измеряться в миллисекундах. Но когда вы учитываете задержки ответа торгового сервера и тот факт, что цены могут меняться очень быстро, важно, чтобы вы всегда использовали самые актуальные цены.
Функция RefreshRates() обновляет содержимое предопределенных переменных с последними ценами с сервера. Рекомендуется вызывать эту функцию каждый раз, когда вы используете переменные Bid или Ask, особенно после заключения сделки.
Обратите внимание, что если вы получаете цену с помощью функции MarketInfo(), нет необходимости использовать RefreshRates().
Обработка ошибок
При размещении, изменении или закрытии ордеров могут возникать ошибки из-за неверных торговых параметров, реквотов или проблем с сервером. Когда ошибки возникают, мы должны предупредить пользователя об ошибке и записать любую соответствующую информацию для устранения неполадок.
Мы проверяем возможные ошибки, проверяя вывод таких функций, как OrderSend(), OrderModify() и OrderClose(). Если функция не завершилась успешно, функция вернет -1 для OrderSend() или false для OrderModify() и OrderClose().
В этом разделе мы создадим процедуру обработки ошибок для функции OrderSend(). Если возвращаемое значение OrderSend() равно -1, мы запустим процедуру обработки ошибок, чтобы отобразить предупреждение для пользователя и распечатать соответствующий торговый параметр и информацию о цене в торговый журнал.
Сначала мы должны сначала получить код ошибки. Это делается с помощью функции GetLastError(). Нам нужно сохранить возвращаемое значение GetLastError() в переменной, потому что после вызова GetLastError() код ошибки будет очищен, а следующий вызов GetLastError() вернет 0. Мы объявим глобальную переменную с именем ErrorCode и используем ее для хранения значения GetLastError().
Далее нам нужно получить некоторую информацию об ошибке. Включаемый файл stdlib.mqh содержит функцию ErrorDescription(). Эта функция возвращает строку с описанием ошибки. Это на самом деле не очень наглядно, но лучше, чем ничего. Нам нужно добавить оператор #include для stdlib.mqh сверху нашего файла.
Затем мы распечатаем предупреждение на экране пользователя с помощью встроенной функции Alert(). Эта информация также будет напечатана в журнале. Предупреждение будет содержать код ошибки, описание ошибки и краткое описание операции, которую мы только что попытались выполнить. Таким образом, вы будете точно знать, в каком разделе вашей программы возникла ошибка.
Наконец, мы распечатаем соответствующую информацию о цене в журнале с помощью функции Print(). Наряду с текущими ценами Bid и Ask мы будем включать параметры торговли, такие как размер лота и цена ордера.
// Препроцессор #include <stdlib.mqh> // Глобальные переменные int ErrorCode; // Размещение ордеров int Ticket = OrderSend(Symbol(),OP_BUYSTOP,LotSize,PendingPrice,UseSlippage,0,0,"Buy Stop ордер",MagicNumber,0,Green); if(Ticket == -1) { ErrorCode = GetLastError(); string ErrDesc = ErrorDescription(ErrorCode); string ErrAlert = StringConcatenate("Open Buy Stop ордер - ошибка ", ErrorCode,": ",ErrDesc); Alert(ErrAlert); string ErrLog = StringConcatenate("Bid: ",Bid," Ask: ",Ask," Price: ", PendingPrice," Lots: ",LotSize); Print(ErrLog); }
Вверху мы включаем файл stdlib.mqh. Мы добавляем глобальную переменную ErrorCode для хранения нашего кода ошибки. OrderSend() размещает стоп-ордер на покупку. Если функция не работает, выполняется наш код обработки ошибок.
Сначала мы сохраняем значение GetLastError() в ErrorCode. Затем мы вызываем функцию ErrorDescription(), используя ErrorCode в качестве аргумента. Далее мы используем функцию StringConcatenate() для создания предупреждающего сообщения, которое хранится в строковой переменной ErrAlert.
StringConcatenate() — это MQL-функция, которая позволяет создавать сложные строки с использованием переменных и констант. Каждый строковый элемент, который должен быть соединен вместе, разделен запятой. Попробуйте ввести приведенные выше примеры в MetaEditor, чтобы просмотреть его с подсветкой синтаксиса.
Вы также можете объединять строки, комбинируя их со знаком плюс (+). Использование StringConcatenate() понятнее и эффективнее, но если вы хотите просто объединить короткую строку, используйте знак плюс для объединения строковых констант и переменных:
string PlusCat = "Текущая цена Ask "+Ask; // Пример вывода: текущая цена Ask 1.4320
Функция Alert() отображает всплывающее окно на рабочем столе пользователя, содержащее содержимое переменной ErrAlert.
Мы создаем еще одну строку с нашими ценовыми и торговыми параметрами и сохраняем ее в переменной ErrLog, которую мы передаем в функцию Print(). Print() печатает содержимое аргумента функции в журнал экспертов. Журнал экспертов можно просмотреть на вкладке «Эксперты» в окне «Терминал» или на вкладке «Журнал» в окне «Тестер», если вы используете тестер стратегий.
Вот содержимое журнала. Первая строка — это вывод функции Alert(). Вторая строка — это вывод функции Print(). Обратите внимание на ошибку, «неверный объем сделки» и тот факт, что размер лота, указанный в журнале, равен 0. В этом случае проблема заключается в том, что размер лота является недопустимым.
Вы можете создать аналогичные процедуры обработки ошибок и для других функций, особенно для функций OrderModify() и OrderClose(). Вы также можете создавать более сложные процедуры обработки ошибок, которые предоставляют настраиваемые сообщения об ошибках на основе кода ошибки, или выполнять другие действия.
Например, если вы получили ошибку 130: «Недопустимые стопы», вы можете отобразить сообщение, например «Недопустимый стоп-лосс или тейк-профит».
Соединяем все вместе
Добавим все функции, которые мы рассмотрели в этом разделе, к простому советнику, который мы создали ранее. Мы добавим модификацию ордера, проверку уровня стопа, проверку контекста сделки, обновление предопределенной переменной и размер лота.
#property copyright "Александр Паркер" #include <stdlib.mqh> // Внешние переменные extern bool DynamicLotSize = true; extern double EquityPercent = 2; extern double FixedLotSize = 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; int ErrorCode;
Мы добавили оператор #include для файла stdlib.mqh, который содержит функцию ErrorDescription() для наших процедур обработки ошибок. Мы добавили три внешние переменные для определения размера лота и глобальную переменную для кода ошибки.
Следующий код идет в начале функции start():
// Скользящие средние double FastMA = iMA(NULL,0,FastMAPeriod,0,0,0,0); double SlowMA = iMA(NULL,0,SlowMAPeriod,0,0,0,0); // Размер лота if(DynamicLotSize == true) { double RiskAmount = AccountEquity() * (EquityPercent / 100); double TickValue = MarketInfo(Symbol(),MODE_TICKVALUE); if(Point == 0.001 || Point == 0.00001) TickValue *= 10; double CalcLots = (RiskAmount / StopLoss) / TickValue; double LotSize = CalcLots; } else LotSize = FixedLotSize; // Проверка размера лота if(LotSize < MarketInfo(Symbol(),MODE_MINLOT)) { LotSize = MarketInfo(Symbol(),MODE_MINLOT); } else if(LotSize > MarketInfo(Symbol(),MODE_MAXLOT)) { LotSize = MarketInfo(Symbol(),MODE_MAXLOT); } if(MarketInfo(Symbol(),MODE_LOTSTEP) == 0.1) { LotSize = NormalizeDouble(LotSize,1); } else LotSize = NormalizeDouble(LotSize,2);
Расчет размера лота и код подтверждения со страницы 51 добавляются в начало нашей функции.
// Ордер на покупку if(FastMA > SlowMA && BuyTicket == 0) { // Закрытие ордера OrderSelect(SellTicket,SELECT_BY_TICKET); if(OrderCloseTime() == 0 && SellTicket > 0) { double CloseLots = OrderLots(); while(IsTradeContextBusy()) Sleep(10); RefreshRates(); double ClosePrice = Ask; bool Closed = OrderClose(SellTicket,CloseLots,ClosePrice,UseSlippage,Red); // Обработка ошибок if(Closed == false) { ErrorCode = GetLastError(); string ErrDesc = ErrorDescription(ErrorCode); string ErrAlert = StringConcatenate("Close Sell Order - Error ", ErrorCode,": ",ErrDesc); Alert(ErrAlert); string ErrLog = StringConcatenate("Ask: ",Ask," Lots: ",LotSize, " Ticket: ",SellTicket); Print(ErrLog); } } // Ордер на покупку while(IsTradeContextBusy()) Sleep(10); RefreshRates(); BuyTicket = OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage,0,0, "Buy Order",MagicNumber,0,Green); // Обработка ошибок if(BuyTicket == -1) { ErrorCode = GetLastError(); ErrDesc = ErrorDescription(ErrorCode); ErrAlert = StringConcatenate("Open Buy Order - Error ", ErrorCode,": ",ErrDesc); Alert(ErrAlert); ErrLog = StringConcatenate("Ask: ",Ask," Lots: ",LotSize); Print(ErrLog); } // Изменение ордера else { OrderSelect(BuyTicket,SELECT_BY_TICKET); double OpenPrice = OrderOpenPrice(); // Расчет уровня стопа double StopLevel = MarketInfo(Symbol(),MODE_STOPLEVEL) * Point; RefreshRates(); double UpperStopLevel = Ask + StopLevel; double LowerStopLevel = Bid - StopLevel; double MinStop = 5 * UsePoint; // Расчет стоп-лосса и тейк-профита if(StopLoss > 0) double BuyStopLoss = OpenPrice - (StopLoss * UsePoint); if(TakeProfit > 0) double BuyTakeProfit = OpenPrice + (TakeProfit * UsePoint); // Подтверждение стоп-лосса и тейк-профита if(BuyStopLoss > 0 && BuyStopLoss > LowerStopLevel) { BuyStopLoss = LowerStopLevel - MinStop; } if(BuyTakeProfit > 0 && BuyTakeProfit < UpperStopLevel) { BuyTakeProfit = UpperStopLevel + MinStop; } // Модификация ордера if(IsTradeContextBusy()) Sleep(10); if(BuyStopLoss > 0 || BuyTakeProfit > 0) { bool TicketMod = OrderModify(BuyTicket,OpenPrice,BuyStopLoss, BuyTakeProfit,0); // Обработка ошибок if(TicketMod == false) { ErrorCode = GetLastError(); ErrDesc = ErrorDescription(ErrorCode); ErrAlert = StringConcatenate("Modify Buy Order - Error ", ErrorCode,": ",ErrDesc); Alert(ErrAlert); ErrLog = StringConcatenate("Ask: ",Ask," Bid: ",Bid," Ticket: ", BuyTicket," Stop: ",BuyStopLoss," Profit: ",BuyTakeProfit); Print(ErrLog); } SellTicket = 0; }
Оставшаяся часть нашего кода содержит блок размещения ордеров на продажу, а также функции PipPoint() и GetSlippage().
Обратите внимание, что мы добавили функцию IsTradeContextBusy() перед каждой торговой операцией. Мы используем RefreshRates() перед каждым указанием переменных Bid или Ask, чтобы убедиться, что мы всегда используем самые последние цены.
Мы начнем с выбора предыдущего тикета на продажу и закрытия его с помощью OrderClose(). Если функция завершается ошибкой, запускается блок обработки ошибок. Далее мы открываем ордер на покупку с помощью OrderSend(). Если функция завершается ошибкой, запускается блок обработки ошибок. В противном случае мы продолжаем к блоку модификации ордера.
Мы выбираем ордер, который был только что размещен, используя OrderSelect(), и назначаем цену открытия ордера переменной OpenPrice. Затем мы рассчитываем уровень стопа и цены верхнего и нижнего уровня стопа. Затем мы рассчитываем наш стоп-лосс и тейк-профит, проверяем их и, наконец, модифицируем ордер с помощью OrderModify().
Вот как мы модифицируем код для отложенного ордера на покупку:
// Закрытие ордера OrderSelect(SellTicket,SELECT_BY_TICKET); if(OrderCloseTime() == 0 && SellTicket > 0 && OrderType() == OP_SELL) { double CloseLots = OrderLots(); while(IsTradeContextBusy()) Sleep(10); RefreshRates(); double ClosePrice = Ask; bool Closed = OrderClose(SellTicket,CloseLots,ClosePrice,UseSlippage,Red); // Обработка ошибок if(Closed == false) { ErrorCode = GetLastError(); string ErrDesc = ErrorDescription(ErrorCode); string ErrAlert = StringConcatenate("Close Sell Order - Error ",ErrorCode, ": ",ErrDesc); Alert(ErrAlert); string ErrLog = StringConcatenate("Ask: ",Ask," Lots: ",LotSize, " Ticket: ",SellTicket); Print(ErrLog); } } // Удаление ордера else if(OrderCloseTime() == 0 && SellTicket > 0 && OrderType() == OP_SELLSTOP) { bool Deleted = OrderDelete(SellTicket,Red); if(Deleted == true) SellTicket = 0; // Обработка ошибок if(Deleted == false) { ErrorCode = GetLastError(); ErrDesc = ErrorDescription(ErrorCode); ErrAlert = StringConcatenate("Delete Sell Stop Order - Error ",ErrorCode, ": ",ErrDesc); Alert(ErrAlert); ErrLog = StringConcatenate("Ask: ",Ask," Ticket: ",SellTicket); Print(ErrLog); } }
Мы добавили код для удаления отложенных ордеров с помощью OrderDelete() после функции OrderClose(). Тип ордера предыдущего ордера на продажу определяет, какая функция используется для закрытия ордера.
Основное различие между следующим кодом и кодом рыночного ордера заключается в том, что у нас нет блока модификации ордера. Нет необходимости размещать стоп-лосс и тейк-профит отдельно для отложенных ордеров. Поэтому мы рассчитаем стоп-лосс и тейк-профит перед размещением ордера с помощью OrderSend().
// Расчет уровня стопа double StopLevel = MarketInfo(Symbol(),MODE_STOPLEVEL) * Point; RefreshRates(); double UpperStopLevel = Ask + StopLevel; double MinStop = 5 * UsePoint; // Расчет цены отложенного ордера double PendingPrice = High[0] + (PendingPips * UsePoint); if(PendingPrice < UpperStopLevel) PendingPrice = UpperStopLevel + MinStop; // Расчет стоп-лосса и тейк-профита if(StopLoss > 0) double BuyStopLoss = PendingPrice - (StopLoss * UsePoint); if(TakeProfit > 0) double BuyTakeProfit = PendingPrice + (TakeProfit * UsePoint); // Проверка стоп-лосса и тейк-профита UpperStopLevel = PendingPrice + StopLevel; double LowerStopLevel = PendingPrice – StopLevel; if(BuyStopLoss > 0 && BuyStopLoss > LowerStopLevel) { BuyStopLoss = LowerStopLevel - MinStop; } if(BuyTakeProfit > 0 && BuyTakeProfit < UpperStopLevel) { BuyTakeProfit = UpperStopLevel + MinStop; } // Размещение отложенного ордера if(IsTradeContextBusy()) Sleep(10); BuyTicket = OrderSend(Symbol(),OP_BUYSTOP,LotSize,PendingPrice,UseSlippage, BuyStopLoss,BuyTakeProfit,"Buy Stop Order",MagicNumber,0,Green); // Обработка ошибок if(BuyTicket == -1) { ErrorCode = GetLastError(); ErrDesc = ErrorDescription(ErrorCode); ErrAlert = StringConcatenate("Open Buy Stop Order - Error ",ErrorCode, ": ",ErrDesc); Alert(ErrAlert); ErrLog = StringConcatenate("Ask: ",Ask," Lots: ",LotSize," Price: ",PendingPrice, " Stop: ",BuyStopLoss," Profit: ",BuyTakeProfit); Print(ErrLog); } SellTicket = 0;
Сначала рассчитаем верхний уровень стопа. Затем мы рассчитываем и проверяем цену нашего отложенного ордера, которая хранится в PendingPrice. Затем мы пересчитываем UpperStopLevel и вычисляем LowerStopLevel так, чтобы они были размещены относительно цены отложенного ордера. Обратите внимание, что нам не нужно использовать цены Ask или Bid или цифру в спреде при проверке стоп-лосса и цены тейк-профита.
Наконец, мы выставляем наш отложенный ордер с помощью OrderSend(), размещая стоп-лосс и тейк-профит. У нас есть стандартная функция обработки ошибок для обработки ошибок при размещении ордера.