Дополнительные возможности MQL4 в разработке советников

Рассмотрим дополнительные функции и возможности MQL4, которые могут быть полезны в программировании советников.

Escape символы

Если вы хотите добавить кавычки или символ обратной черты в строку, вам нужно экранировать символ, используя обратную косую черту (\). Например, если вам нужно вставить двойную кавычку, escape-символ будет \». Для одиночной кавычки escape-символ — \’. Для обратной косой черты в качестве escape-символа используйте две обратные косые черты: \\

string EscQuotes = "Эта строка содержит \"двойные кавычки\""; // Эта строка содержит "двойные кавычки"
string EscQuote = "Эта строка содержит \'одинарные кавчки\'"; // Эта строка содержит 'одинарные кавычки'
string EscSlash = "Эта строка содержит обратную черту \\"; // Эта строка содержит обратную черту \

Если вам нужно, чтобы строка занимала несколько строк, используйте escape-символ \n для добавления новой строки:

string NewLine = "Эта строка \n новая строка"; // Output: Эта строка
новая строка

Использование комментариев на графике

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

Один из способов отображения комментариев к графику — объявить несколько строковых переменных и объединить их вместе с символами новой строки. Одна строка может использоваться для отображения настроек, другая — для отображения информационных сообщений или статуса ордера и т. д. Строка будет передана в функцию Comment().

Поместите функцию Comment() в конец функции start(), чтобы обновить комментарий на графике:

string SettingsComment = "FastMAPeriod: "+FastMAPeriod+" SlowMAPeriod: "+SlowMAPeriod; string StatusComment = "Размещен ордер на покупку";
Comment(SettingsComment+"\n"+StatusComment);

Мы объявляем и устанавливаем значения строк SettingsComment и StatusComment внутри функции start(). В конце функции start мы вызываем функцию Comment() и используем ее для вывода наших комментариев на графике. Мы используем символ новой строки (\n), чтобы разделить комментарий на две строки.

Проверка настроек

Существует несколько настроек советника, которые должны быть проверены, прежде чем советнику будет разрешено торговать. Эти настройки находятся на вкладке «Общие» в диалоговом окне «Свойства эксперта».

Параметр Allow live trading должен быть включен до начала торговли. Если он не включен, в правом верхнем углу графика рядом с именем советника появится эмоция недовольство. Вы можете проверить это условие в своем советнике, используя функцию IsTradeAllowed(). Если она возвращает false, настройка Allow live trading отключена.

Если вы хотите отобразить пользователю сообщение о том, что этот параметр должен быть активирован, вы можете сделать следующее:

if(IsTradeAllowed() == false) Alert("Активируйте настройку \'Allow live trading\' в свойствах советника!");

Если ваш эксперт использует внешнюю библиотеку .ex4, в свойствах эксперта должен быть включен параметр Разрешить импорт внешних экспертов. Вы можете проверить это с помощью функции IsLibrariesAllowed():

if(IsLibrariesAllowed() == false) Alert("Активруйте настройку \'Allow import of external experts\' в свойствах советника!");

То же самое можно сделать для DLL, используя функцию IsDllsAllowed():

if(IsDllsAllowed() == false) Alert("Активруйте настройку \'Allow DLL imports\' в свойствах советника!");

настройки советника

Ограчения аккаунтов

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

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

if(IsDemo() == false) {
Alert("Этот советник работает только на демо-аккаунте!");
return(0); }

Вы можете использовать функции учетной записи AccountName(), AccountNumber() и AccountBroker(), чтобы проверить имя учетной записи, номер аккаунта и брокера соответственно. Ограничение использования по номеру счета является распространенным и простым в реализации способом защиты:

int CustomerAccount = 123456;
if(AccountNumber() != CustomerAccount) {
Alert("Номер аккаунта не совпадает!");
return(0); 
}

Вы можете использовать AccountName() или AccountBroker() аналогичным образом. Для AccountBroker() вам сначала нужно использовать Print(), чтобы получить правильное возвращаемое имя брокера. Это значение будет напечатано в журнале экспертов.

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

MessageBox()

До сих пор в этой книге мы использовали встроенную функцию Alert() для отображения сообщений об ошибках. Но что, если вы хотите настроить диалоги оповещений или запросить ввод данных у пользователя? Функция MessageBox() позволит вам создать собственный всплывающий диалог с помощью Windows API.

Чтобы использовать функцию MessageBox(), мы должны сначала выполнить #include файл WinUser32.mqh, который устанавливается вместе с MetaTrader. Этот файл импортирует функции из файла Windows user32.dll и определяет константы, необходимые для работы функции MessageBox(). Вот синтаксис функции MessageBox():

int MessageBox(string Text, string Title, int Flags);

Чтобы использовать функцию MessageBox(), мы должны определить текст, который будет отображаться во всплывающем диалоговом окне, а также заголовок, отображаемый в строке заголовка. Нам также нужно будет указать флаги, которые указывают, какие кнопки и значки должны появляться в нашем всплывающем окне. Если флаги не указаны, кнопка ОК будет использоваться по умолчанию. Флаги должны быть разделены символом (|).

Вот пример окна сообщения с кнопками Да / Нет и значком вопросительного знака:

// #include <WinUser32.mqh>
// start() function
int YesNoBox = MessageBox("Открыть сделку?","Подтверждение торговли",
      MB_YESNO|MB_ICONQUESTION);
if(YesNoBox == IDYES) {
        // Разместить ордер
      }

Флаг MB_YESNO указывает, что мы будем использовать кнопки «Да / Нет» в нашем окне сообщений, в то время как флаг MB_ICONQUESTION помещает значок вопросительного знака в диалоговом окне. Переменная YesNoBox содержит возвращаемое значение функций MessageBox(), которое будет указывать, какая кнопка была нажата.

Если кнопка «Да» была нажата, значением YesNoBox будет IDYES, и будет размещен ордер. Если кнопка «Нет» была нажата, флаг возврата будет IDNO. Вы можете использовать возвращаемое значение MessageBox() в качестве входных данных, чтобы определить порядок действий, таких как размещение ордера.

Ниже приведен неполный список флагов, которые можно использовать в окнах сообщений.

Флаги для кнопок

Эти флаги указывают, какие кнопки появляются в вашем окне сообщения:

  • MB_OKCANCEL — кнопки ОК и Отмена.
  • MB_YESNO — кнопки Да и Нет.
  • MB_YESNOCANCEL — кнопки Да, Нет и Отмена.

Флаги для иконок

Эти флаги указывают на значки, которые появляются рядом с текстом в окне сообщения.

  • MB_ICONSTOP — значок знака остановки.
  • MB_ICONQUESTION — значок знака вопроса.
  • MB_ICONEXCLAMATION — значок с восклицательным знаком.
  • MB_ICONINFORMATION — информационный значок.

Возвращаемые флаги

Эти флаги являются возвращаемым значением функции MessageBox() и указывают, какая кнопка была нажата.

  • IDOK — нажата кнопка «ОК».
  • IDCANCEL — была нажата кнопка «Отмена».
  • IDYES — была нажата кнопка «Да».
  • IDNO — была нажата кнопка «Нет».

Уведомления по E-mail

Ваш эксперт может уведомить вас по электронной почте о размещенных сделках, возможных настройках торговли и многом другом. Функция SendMail() отправит электронное письмо с выбранной темой и текстом на адрес электронной почты, указанный в диалоговом окне «Сервис — Параметры» на вкладке «Электронная почта».

На вкладке «Электронная почта» необходимо сначала указать почтовый сервер SMTP с номером порта, например, mail.yourdomain.com:25, а также имя пользователя и пароль, если это необходимо.

Вы можете использовать любой адрес электронной почты в поле От. Поле «Кому» — это адрес электронной почты, на который отправляются сообщения.

Функция SendMail() имеет два аргумента: первый — это строка темы письма, а второй — содержимое самого письма. Вы можете использовать новые строки, экранированные символы, переменные и константы в теле вашего письма.

настройки e-mail

Вот пример использования функции SendMail():

string EmailSubject = "Ордер на покупку открыт";
string EmailBody = Ордер на покупку "+Ticket+" открыт на "+Symbol()+" по цене "+Ask; // "Ордер на покупку 12584 открыт на EURDUSD по цене 1.4544"
SendMail(EmailSubject,EmailBody);

Повтор после ошибки

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

Чтобы повторить ордер, мы поместим функцию OrderSend() в цикл while. Если OrderSend() не возвращает номер тикета, мы попробуем открыть ордер еще раз:

int Ticket = 0; while(Ticket <= 0) {
Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit); 
}

Сначала мы объявляем переменную для номера тикета, в данном случае Ticket. Пока Ticket не больше 0, цикл while с функцией OrderSend() будет выполняться снова и снова.

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

int Retries = 0;
int MaxRetries = 5;
int Ticket = 0; while(Ticket <= 0) {
Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit);
        if(Retries <= MaxRetries) Retries++;
else break;
}

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

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

    bool ErrorCheck(int ErrorCode)
      {
switch(ErrorCode)
  {
    case 128: // Trade timeout
    return(true);
 
    case 136: // Off quotes
    return(true);
 
 
  case 138: // Requotes
  return(true);
 
  case 146: // Trade context busy
  return(true);
 
default:
  return(false);
}
}

Эта функция использует оператор switch. Мы ищем метку, значение которой соответствует выражению, назначенному оператору switch (в данном примере ErrorCode).

Когда совпадение наблюдений найдено, блок switch должен быть завершен с оператором прерывания или возврата. В этом примере мы используем оператор return для возврата значения true / false вызывающей функции.

Вот как мы используем ErrorCheck(), чтобы повторить попытку размещения ордера:

int Retries;
int MaxRetries = 5;
int Ticket; while(Ticket <= 0) {
Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit);
if(Ticket == -1) int ErrCode = GetLastError();
if(Retries <= MaxRetries && ErrorCheck(ErrCode) == true) Retries++; else break;
}

Если Ticket возвращает -1, указывая на то, что произошла ошибка, мы получаем код ошибки с помощью GetLastError(). Мы передаем код ошибки в нашу функцию ErrorCheck(). Если код ошибки соответствует какой-либо из ошибок в функции проверки ошибок, ErrorCheck() вернет true, а функция OrderSend() будет повторена до 5 раз.

Использование комментариев к ордеру в качестве идентификатора

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

Допустим, ваш эксперт разместит два типа ордеров. Вы хотите иметь возможность изменить или закрыть эти ордера по отдельности. Вы можете использовать две функции OrderSend() и разместить разные комментарии к каждому из них. Затем, при выборе ордеров с использованием цикла, вы можете использовать OrderComment() в качестве одного из условий для нахождения ордеров для их изменения или закрытия.

string OrderComment1 = "Первый ордер"; 
string OrderComment2 = "Второй ордер";
 
// Размещение ордеров
int Ticket1 = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit,OrderComment1,MagicNumber,0,Green);
int Ticket2 = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit,OrderComment2,MagicNumber,0,Green);
 
// Изменение ордера
for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++) {
OrderSelect(Counter,SELECT_BY_POS);
if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() && OrderComment() == OrderComment1)
{
             // Изменение первого ордера
          }
else if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() && OrderComment() == OrderComment2)
{
             // Изменение второго ордера
          }
}

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

Вы можете использовать проверку OrderComment(), чтобы закрывать ордера независимо от других ордеров, использовать другие настройки трейлинг-стопа или что-либо другое, что требует ваша торговая система.

Проверка маржинальных требований

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

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

Мы сравним MinimumEquity с нашим текущим счетом. Если текущий эквити меньше нашего минимального эквити, ордер не будет размещен, и предупреждающее сообщение проинформирует пользователя о состоянии. Предположим, у нас есть баланс на счете 10000 долларов. Если мы потеряем более 20% этого капитала, мы не будет размещать ордера. Вот код для проверки минимального эквити:

// Внешние переменные
extern int MinimumEquity = 8000;
 
    // Размещение ордера
    if(AccountEquity() > MinimumEquity)
      {
        // Открываем ордер
}
else if(AccountEquity() <= MinimumEquity)
{
Alert("Размер эквити слишком низкий!");
}

Внешняя переменная MinimumEquity помещается в начало файла. Если текущий капитал, как указано AccountEquity(), больше MinimumEquity, ордер будет размещен. В противном случае заказ не будет размещен, и будет отображено предупреждающее сообщение.

Проверка спреда

Возможно, вы захотите избегать размещения ордеров в периоды, когда спред значительно расширяется. Мы можем установить максимальный спред и проверить текущий спред перед торговлей. Мы объявим внешнюю переменную MaximumSpread и используем MarketInfo() для проверки текущего спреда.

    // внешние переменные
extern int MaximumSpread = 5;
extern int MinimumEquity = 8000;
 
if(AccountEquity() > MinimumEquity && MarketInfo(Symbol(),MODE_SPREAD) < MaximumSpread) {
        // Place order
      }
else {
}
if(AccountEquity() <= MinimumEquity) Alert("Размер эквити ниже минимального!");
if(MarketInfo(Symbol(),MODE_SPREAD) > MaximumSpread) Alert("Размер спреда слишком большой!");
}

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

Размещение нескольких ордеров

Вы можете разместить несколько ордеров с разными уровнями стоп-лосс и тейк-профит, а также размерами лота. Есть несколько способов сделать это. Один из способов — просто использовать разные операторы OrderSend() для каждого ордера. Это предполагает, что вы планируете размещать одинаковое количество ордеров каждый раз.

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

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

extern int StopLoss1 = 20; 
extern int StopLoss2 = 40; 
extern int StopLoss3 = 60;
 
extern int TakeProfit1 = 40; 
extern int TakeProfit2 = 80; 
extern int TakeProfit3 = 120;
 
extern int MaxOrders = 3;

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

double BuyTakeProfit[3];
double BuyStopLoss[3];
 
BuyTakeProfit[0] = CalcBuyTakeProfit(Symbol(),TakeProfit1,Ask); 
BuyTakeProfit[1] = CalcBuyTakeProfit(Symbol(),TakeProfit2,Ask); 
BuyTakeProfit[2] = CalcBuyTakeProfit(Symbol(),TakeProfit3,Ask);
 
BuyStopLoss[0] = CalcBuyStopLoss(Symbol(),StopLoss1,Ask); 
BuyStopLoss[1] = CalcBuyStopLoss(Symbol(),StopLoss2,Ask); 
BuyStopLoss[2] = CalcBuyStopLoss(Symbol(),StopLoss3,Ask);

Мы начнем с объявления массивов для хранения стоп-лосса и тейк-профита, BuyTakeProfit и BuyStopLoss. Количество элементов массива должно быть указано при объявлении массива. Индексы массива начинаются с нуля, поэтому, объявив размер измерения массива 3, наш начальный индекс равен 0, а наш самый большой индекс равен 2.

Далее мы рассчитываем цены стоп-лосс и тейк-профит, используя функции CalcBuyStopLoss() и CalcBuyTakeProfit(). Мы присваиваем рассчитанный стоп-лосс или тейк-профит соответствующему элементу массива. Обратите внимание, что первый индекс массива равен 0, а третий индекс массива равен 2.

Вот цикл for для размещения ордеров:

for(int Count = 0; Count <= MaxOrders - 1; Count++) {
int OrdInt = Count + 1;
OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage,BuyStopLoss[Count], BuyTakeProfit[Count],"Buy Order "+OrdInt,MagicNumber,0,Green);
}

Переменная Count начинается с 0 и соответствует первому элементу массива. Количество циклов (т.е. количество размещаемых ордеров) определяется MaxOrders — 1. Для каждой итерации цикла мы увеличиваем стоп-лосс и тейк-профит на единицу.

Мы используем переменную OrdInt для увеличения количества ордеров в комментарии к ордеру. Первым комментарием к ордеру будет «Ордер на покупку 1», следующим будет «Ордер на покупку 2» и так далее. Функция OrderSend() размещает ордер с соответствующим стоп-лоссом и значением тейк-профита, используя переменную Count для выбора соответствующего элемента массива.

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

extern int StopLossStart = 20; 
extern int StopLossIncr = 20;
extern int TakeProfitStart = 40; 
extern int TakeProfitIncr = 40;
extern int MaxOrders = 5;

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

for(int Count = 0; Count <= MaxOrders - 1; Count++) {
int OrdInt = Count + 1;
 
int UseStopLoss =  StopLossStart + (StopLossIncr * Count);
 int UseTakeProfit =  TakeProfitStart + (TakeProfitIncr * Count);
double BuyStopLoss = CalcBuyStopLoss(Symbol(),UseStopLoss,Ask); double BuyTakeProfit = CalcBuyTakeProfit(Symbol(),UseTakeProfit,Ask);
OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage,BuyStopLoss, BuyTakeProfit,"Buy Order "+OrdInt,MagicNumber,0,Green);
}

Мы определяем уровень тейк-профита и стоп-лосса в пунктах, умножая переменную StopLossIncr или TakeProfitIncr на значение Count и добавляя его к значению StopLossStart или TakeProfitStart. Для первого ордера уровень стоп-лосса или тейк-профита будет равен StopLossStart или TakeProfitStart.

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

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

Глобальные переменные

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

Текущий список глобальных переменных в терминале можно просмотреть, выбрав «Глобальные переменные» в меню «Инструменты» или нажав клавишу F3 на клавиатуре.

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

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

Чтобы объявить глобальную переменную, используйте функцию GlobalVariableSet(). Первый аргумент — это строка, обозначающая имя глобальной переменной, а второй аргумент — это значение типа double, присваиваемое ей.

GlobalVariableSet(GlobalVariableName,DoubleValue);

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

  // Глобальные переменные
    string GlobalVariablePrefix;
int init() {
GlobalVariablePrefix = Symbol()+Period()+"_"+"ProfitBuster"+"_"+MagicNumber+"_"; }

Мы используем текущий символ и период, а также идентификатор советника и внешнюю переменную MagicNumber. Теперь, когда мы устанавливаем глобальную переменную с помощью GlobalVariableSet(), мы используем префикс, который мы определили выше вместе с фактическим именем переменной:

GlobalVariableSet(GlobalVariablePrefix+Counter,Counter);

Поэтому, если мы торгуем на EURUSD на таймфрейме M15 с советником с именем «ProfitBuster», используя 11 в качестве магического числа и Counter в качестве имени переменной, именем нашей глобальной переменной будет EURUSD15_ProfitBuster_11_Counter.

Чтобы получить значение глобальной переменной, используйте функцию GlobalVariableGet() с именем переменной в качестве аргумента:

Counter = GlobalVariableGet(GlobalVariablePrefix+Counter);

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

    GlobalVariableDel(GlobalVariablePrefix+Counter);
    GlobalVariableDeleteAll(GlobalVariablePrefix);

Проверка прибыли ордера

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

OrderSelect(Ticket,SELECT_BY_TICKET); 
double GetProfit = OrderProfit(Ticket);

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

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

OrderSelect(Ticket,SELECT_BY_TICKET);
if(OrderType() == OP_BUY) double GetProfit = OrderClosePrice() - OrderOpenPrice(); else if(OrderType() == OP_SELL) GetProfit = OrderOpenPrice() - OrderClosePrice();
GetProfit /= PipPoint(Symbol());

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

Например, если цена открытия ордера на покупку составляет 1,4650, а цена закрытия — 1,4700, разница между OrderClosePrice() и OrderOpenPrice() составляет 0,0050. Когда мы делим это на нашу функцию PipPoint(), результат равен 50. Таким образом, для этого ордера мы получаем 50 пунктов прибыли. Если бы цена закрытия ордера была 1.4600, то мы бы потеряли 50 пунктов.

Мартингейл

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

Например, если размер вашего начального лота составляет 0,1 лота, то после 4 последовательных потерь ваш размер лота составит 1,6 лота — в 16 раз больше исходного размера лота. После 7 последовательных проигрышей размер вашего лота составит 12,8 лота — в 128 раз больше исходного размера лота! Долгая полоса неудач может легко уничтожить ваш аккаунт, прежде чем вы сможете вернуть его в безубыточность.

Тем не менее, вы можете включить систему увеличения размера лота при последовательных выигрышах или проигрышах, и это можно сделать, не уничтожая свой счет. Самый простой способ — установить ограничение на количество раз, чтобы увеличить размер лота. Надежная торговая система не должна иметь более 3 или 4 максимальных последовательных убытков. Вы можете определить это, изучив максимальное количество последовательных потерь на вкладке «Отчет» в окне «Тестер стратегий».

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

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

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

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

int WinCount;
int LossCount;
for(int Count = OrdersHistoryTotal()-1; ; Count--) {
OrderSelect(Count,SELECT_BY_POS,MODE_HISTORY);
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
if(OrderProfit() > 0 && LossCount == 0) WinCount++;
else if(OrderProfit() < 0 && WinCount == 0) LossCount++; else break;
} 
}

Мы начнем с объявления переменных для счетчиков выигрышей и проигрышей. Обратите внимание, что в операторе for мы используем OrdersHistoryTotal() для определения начальной позиции. OrdersHistoryTotal() возвращает количество ордеров в пуле истории. Вычитаем 1, чтобы определить позицию индекса для самого последнего ордера, который хранится в переменной Count.

Обратите внимание, что мы пропустили второе выражение в цикле for — то, которое определяет условие прекращения цикла. Мы будем уменьшать переменную Count на каждой итерации цикла.

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

Мы проверяем, что текущий выбранный ордер соответствует нашему символу графика и нашему магическому номеру. Затем мы проверяем прибыль по ордеру с помощью функции OrderProfit(). Если возвращаемое значение указывает на прибыль (то есть больше нуля), то мы увеличиваем переменную WinCount. Если это потеря, мы увеличиваем LossCount.

Поскольку мы ищем последовательные выигрыши или проигрыши, нам нужно завершить цикл, как только будет найдено альтернативное условие. Для этого мы проверяем переменную WinCount или LossCount при проверке прибыли ордера.

Например, если у нас есть 2 последовательных убытка — это означает, что LossCount = 2 — и наш следующий ордер будет выигрышным, тогда оба наших оператора if будут ложными, и управление перейдет к оператору break, который завершит цикл.

Преимущество этого метода в том, что он не подведет, если советник случайно отключится. Советник продолжит свою работу прямо там, где остановился. Конечно, это означает, что при первом запуске советника он будет использовать любую предыдущую серию выигрышей / проигрышей при определении размера лота. Но, как вы можете видеть, преимущества перевешивают недостатки.

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

Мы будем использовать внешнюю целочисленную переменную с именем MartingaleType. Если MartingaleType установлен в 0, мы будем использовать стратегию Martingale. Если установлено значение 1, мы будем использовать стратегию анти-мартингейла.

// Внешние переменные
extern int MartingaleType = 0; // 0: Martingale, 1: Anti-Martingale
extern int LotMultiplier = 2; 
extern int MaxMartingale = 4; 
extern double BaseLotSize = 0.1;
 
// Расчет размера лота
if(MartingaleType == 0) int ConsecutiveCount = LossCount; else if(MartingaleType = 1) ConsecutiveCount = WinCount;
if(ConsecutiveCount > MaxMartingale) ConsecutiveCount = MaxMartingale; double LotSize = BaseLotSize * MathPow(LotMultiplier,ConsecutiveCount);

Мы устанавливаем значение ConsecutiveCount либо в WinCount, либо в LossCount, в зависимости от настройки MartingaleType. Мы сравним это с нашей настройкой MaxMartingale. Если количество наших последовательных ордеров больше, чем MaxMartingale, мы изменим его размер, чтобы он был равен MaxMartingale. (Вы также можете изменить его размер до размера лота по умолчанию, если хотите). Размер лота будет оставаться на этом размере до тех пор, пока выигрыш или проигрыш не нарушат нашу последовательную серию ордеров.

Размер лота определяется умножением нашего BaseLotSize на LotMultiplier, который экспоненциально увеличивается на ConsecutiveCount. Функция MathPow() увеличивает число до указанной степени. Например, если размер нашего начального лота равен 0,1, множитель лота равен 2, и у нас есть четыре последовательных ордера, уравнение равно 0,1 * 24 = 1,6.

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

Отладка работы вашего советника

В отличие от большинства программных средств разработки, MetaEditor не поддерживает современные методы отладки. Поэтому вам придется использовать операторы Print() и журнал для отладки ваших экспертов.

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

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

Журналы тестера стратегий хранятся в папке \tester\logs. Щелкните правой кнопкой мыши в любом месте окна журнала и выберите «Открыть» во всплывающем меню. Откроется окно проводника Windows, отображающее содержимое папки журнала. Имена файлов имеют формат yyyymmdd.log, где yyyy — год из четырех цифр, mm — месяц из двух цифр, а dd — дата из двух цифр. Вы можете просматривать журналы в блокноте или любом текстовом редакторе.

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

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

Далее мы перейдем на вкладку «Журнал» и проверим, какая информация нам нужна. Если нам нужно просмотреть весь журнал или если на вкладке «Журнал» не отображаются сделки, мы можем щелкнуть правой кнопкой мыши и выбрать «Открыть» во всплывающем меню, а затем напрямую открыть файл журнала.

Этот код дает нам ошибку 130: «недействительные стопы» каждый раз, когда мы размещаем заказ на покупку. Мы знаем, что ошибка 130 означает, что либо стоп-лосс, либо тейк-профит неверны.

if(Close[0] > MA && BuyTicket == 0) {
double OpenPrice = Ask;
double BuyStopLoss = OpenPrice + (StopLoss * UsePoint); double BuyTakeProfit = OpenPrice + (TakeProfit * UsePoint);
BuyTicket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage, BuyStopLoss,BuyTakeProfit,"Buy Order",MagicNumber,0,Green);
SellTicket = 0; }

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

Print("Price:"+OpenPrice+" Stop:"+BuyStopLoss+" Profit:"+BuyTakeProfit);

Вот вывод, когда мы запускаем советник в тестере стратегий. Стоп-лосс и тейк-профит в размере 50 пунктов:

11:52:12 2009.11.02 02:00 Example EURUSD,H1: OrderSend error 130
11:52:12 2009.11.02 02:00 Example EURUSD,H1: Price:1.47340000 Stop:1.47840000
Profit:1.47840000

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

double BuyStopLoss = OpenPrice - (StopLoss * UsePoint);

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

Ошибка 129: недействительная цена — недействительная цена открытия. Для рыночных ордеров убедитесь, что текущая цена Bid или Ask передается в соответствии с типом ордера. Для отложенных ордеров убедитесь, что цена выше или ниже текущей цены, как того требует тип ордера. Также убедитесь, что цена отложенного ордера не слишком близка к текущей цене.

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

Ошибка 131: неверный объем сделки — неверный размер лота. Убедитесь, что размер лота не превышает минимального или максимального значения брокера и что размер лота нормализован до правильного значения шага (0,1 или 0,01 для большинства брокеров).

Описания всех сообщений об ошибках можно найти в справочнике по MQL в разделе Стандартные константы — коды ошибок.

Устранение ошибок в случае прерывания торговли

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

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

// Внешние переменные 
extern bool Debug = true;
 
if(Debug == true) Print(StringConcatenate("Bid:",Bid," Ask:",Ask," MA:",MA," BuyTicket:",BuyTicket," SellTicket:",SellTicket));

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

Оператор отладки Print() должен быть помещен в конце функции start() после всех торговых функций. Если вы используете таймер, поместите оператор debug Print() в блок таймера, чтобы он работал только при необходимости. В противном случае строка отладки будет выводиться в журнал при каждом тике, что может привести к разрастанию файла журнала.

Обработка ошибок компиляции

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

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

Вот список распространенных ошибок компиляции и их решения:

Variable not defined — вы забыли объявить переменную с типом данных. Если это глобальная или внешняя переменная, объявите ее в верхней части файла. Если это локальная переменная, найдите первое вхождение и поместите перед ним объявление типа данных. В противном случае проверьте правильность написания или регистр (верхний / нижний) имени переменной.

Variable already defined — вы объявили одну и ту же переменную дважды. Удалите объявление типа данных из всех дублированных объявлений переменных.

Function is not defined — если рассматриваемая функция находится в файле включения или библиотеки, убедитесь, что директива #include или #import находится в верхней части файла и является правильной. В противном случае проверьте правильность написания или регистр имени функции и убедитесь, что оно существует либо в текущем файле, либо в соответствующих файлах включений или библиотек.

Illegal assignment used — обычно это относится к знаку равенства (=). Помните, что один знак равенства предназначен для присваивания переменной, а два знака равенства (==) — оператор сравнения. Исправьте оператор присваивания в соответствующем операторе сравнения.

Assignment expected — обычно это относится к оператору сравнения «равно» (==). Вы использовали два знака равенства вместо одного в присваивании переменной. Исправьте оператор в один знак равенства.

Unbalanced right parenthesis — обычно это происходит в операторе if при использовании вложенных скобок. Перейдите к строке, обозначенной первой ошибкой, и вставьте левую скобку в соответствующее место.

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

Wrong parameters count — у вас слишком мало или слишком много аргументов в функции. Дважды проверьте синтаксис функции в MQL Reference и исправьте аргументы.

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

Комментарии (1)