Функции

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

заголовок_функции
{
инструкции
}

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

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

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

Определение функции должно точно соответствовать ее объявлению. Каждая объявленная функция должна быть определена.

Пример:

double // тип возвращаемого значения
my_func (double a, double b) // имя функции и список параметров
{
   // составной оператор
   return (a + b); // возвращаемое значение
}

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

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

Пример:

void errmesg(string s)
{
   Print("error: "+s);
}

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

Пример:

int my_func(double a, double d=0.0001, int n=5, bool b=true, string s="passed string")
{
   Print("Обязательный параметр a= ",a);
   Print("Переданы следующие параметры: d = ",d," n = ",n," b = ",b," s = ",s);
   return(0);
}

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

Пример неправильного объявления:

int somefunc(double a,
             double d=0.0001, // объявлено значение по умолчанию 0.0001
             int n, // значение по умолчанию не указано !
             bool b, // значение по умолчанию не указано !
             string s="passed string")
{ 
   ...
}

 

Вызов функции

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

имя_функции (x1, x2,…, xn)

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

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

Примеры:

void OnTick()
{
   double my_array[4]={0.3, 1.4, 2.5, 3.6};
   double a = my_func(my_array, 10.5, 8);
   //...
}
double my_func(double x[], double a, double b)
{
   return (a*x[0] + b);
}

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

Примеры:

void my_func(double init,
              double sec=0.0001, //определены значения по умолчанию
              int level=10); 
//...
my_func(); // неправильный вызов. первый обязательный параметр должен быть.
my_func(3.14); // правильный вызов
my_func(3.14,0.0002); // правильный вызов
my_func(3.14,0.0002,10); // правильный вызов

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

my_func(3.14, , 10); // неправильный вызов -> второй параметр пропущен.

 

Передача параметров

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

//+------------------------------------------------------------------+
//| передача параметров по значению |
//+------------------------------------------------------------------+
double FirstMethod(int i,int j)
{
   double res;
   //---
   i*=2;
   j/=2;
   res=i+j;
   //---
   return(res);
}

//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
   //---
   int a=14,b=8;
   Print("a и b перед вызовом:",a," ",b);
   double d=FirstMethod(a,b);
   Print("a и b после вызова:",a," ",b);
}

//--- результат выполнения скрипта
// a и b перед вызовом: 14 8
// a и b после вызова: 14 8

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

//+------------------------------------------------------------------+
//| передача параметров по ссылке |
//+------------------------------------------------------------------+
double SecondMethod(int &i,int &j)
{
   double res;
   //---
   i*=2;
   j/=2;
   res=i+j;
   //---
   return(res);
}

//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
   //---
   int a=14,b=8;
   Print("a и b перед вызовом:",a," ",b);
   double d=SecondMethod(a,b);
   Print("a и b после вызова:",a," ",b);
}

//+------------------------------------------------------------------+
//--- результат выполнения скрипта
// a и b перед вызовом: 14 8
// a и b после вызова: 28 4

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

Примечание

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

Пример:

void OnStart()
{
   //---
   int a[]={0,1,2};
   int i=0;
 
   func(a[i],a[i++],"Первый вызов (i = "+string(i)+")");
   func(a[i++],a[i],"Второй вызов (i = "+string(i)+")");

// Результат:
// Первый вызов(i=0) : par1 = 1 par2 = 0
// Второй вызов(i=1) : par1 = 1 par2 = 1
 
}

//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void func(int par1,int par2,string comment)
{
   Print(comment,": par1 = ",par1," par2 = ",par2);
}

При первом вызове в приведенном примере сначала переменная i участвует в конкатенации строк:

«Первый вызов (i = «+string(i)+»)»

При этом ее значение не меняется. Затем переменная i участвует в вычислении элемента массива a[i++], то есть после взятия i-го элемента массива переменная i инкрементируется. И только после этого вычисляется первый параметр с измененным значением переменной i.

Во втором вызове при вычислении всех трех параметров используется одно и тоже значение i, измененное на первом вызове функции, и только после вычисления первого параметра переменная i опять изменяется.

 

Описание внешних функций

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

Примеры:

#import "user32.dll"
 int MessageBoxW(int hWnd ,string szText,string szCaption,int nType);
 int SendMessageW(int hWnd,int Msg,int wParam,int lParam);
#import "lib.ex5"
 double round(double value);
#import

С помощью импорта можно очень легко описывать функции, вызываемые из внешних DLL или скомпилированных EX5 библиотек. Библиотеками EX5 являются скомпилированные ex5-файлы, которые имеют свойство library. Импортировать из EX5 библиотек можно только функции, описанные с модификатором export.

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

Пример:

#import "kernel32.dll"
 int GetLastError();
#import "lib.ex5"
 int GetLastError();
#import
 
class CFoo
{
 public:
  int GetLastError() { return(12345); }

  void func()
  {
    Print(GetLastError()); // вызов метода класса
    Print(::GetLastError()); // вызов функции MQL5
    Print(kernel32::GetLastError()); // вызов функции kernel32.dll
    Print(lib::GetLastError()); // вызов функции lib.ex5
  }
};
 
void OnStart()
{
   CFoo foo;
   foo.func();
}

 

Экспортирование функций

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

int Function() export
{
}

Данный модификатор указывает компилятору внести функцию в таблицу EX5-функций, экспортируемых данным исполняемым ex5-файлом. Только функции с таким модификатором становятся доступными («видимыми») из других mql5-программ.

Свойство library указывает компилятору, что данный EX5-файл будет являться библиотекой, и компилятор проставит это в заголовке EX5.

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

 

Функции обработки событий

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

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

OnStart

Функция OnStart() является обработчиком события Start, которое автоматически генерируется только для запущенных на выполнение скриптов. Должна иметь тип void, параметров не имеет:

void OnStart();

Для функции OnStart() допустимо указывать тип возвращаемого значения int.
OnInit

Функция OnInit() является обработчиком события Init. Может иметь тип void или int, параметров не имеет:

void OnInit();

Событие Init генерируется сразу после загрузки эксперта или индикатора, для скриптов это событие не генерируется. Функция OnInit() используется для инициализации. Если OnInit() имеет возвращаемое значение типа int, то ненулевой код возврата означает неудачную инициализацию и генерирует событие Deinit с кодом причины деинициализации REASON_INITFAILED.

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

ENUM_INIT_RETCODE

Идентификатор

Описание

INIT_SUCCEEDED

Инициализация прошла успешно, тестирование эксперта можно продолжать.

Этот код означает то же самое, что и нулевое значение – инициализация эксперта в тестере прошла успешно.

INIT_FAILED

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

Возврат этого значения означает то же самое, что и возврат значения, отличного от нуля – инициализация эксперта в тестере прошла неудачно.

INIT_PARAMETERS_INCORRECT

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

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

При получении этого значения тестер стратегий гарантированно не будет передавать данное задание другим агентам для повторного выполнения.

INIT_AGENT_NOT_SUITABLE

Ошибок в работе программы при инициализации не возникло, но по каким-то причинам данный агент не подходит для проведения тестирования. Например, недостаточно оперативной памяти, нет поддержки OpenCL и так далее.

После возврата этого кода агент больше не будет получать заданий до самого конца данной оптимизации.

Функция OnInit() типа void всегда означает удачную инициализацию.

OnDeinit

Функция OnDeinit() вызывается при деинициализации и является обработчиком события Deinit. Должна быть объявлена с типом void и иметь один параметр типа const int , который содержит код причины деинициализации. Если объявлен иной тип, компилятор выдаст предупреждение, но функция вызываться не будет. Для скриптов событие Deinit не генерируется и поэтому использовать в скриптах функцию OnDeinit() нельзя.

void OnDeinit(const int reason);

Событие Deinit генерируется для экспертов и индикаторов в следующих случаях:

перед переинициализацией в связи со сменой символа или периода графика, к которому прикреплена mql5-программа;
перед переинициализацией в связи со сменой входных параметров;
перед выгрузкой mql5-программы.

OnTick

Событие NewTick генерируется только для экспертов при поступлении нового тика по символу, к графику которого прикреплен эксперт. Функцию OnTick() бесполезно определять в пользовательском индикаторе или скрипте, поскольку событие NewTick для них не генерируется.

Событие Tick генерируется только для экспертов, но это не означает, что эксперты обязаны иметь функцию OnTick(), так как для экспертов генерируются не только события NewTick, но и события Timer, BookEvent и ChartEvent. Должна быть объявлена с типом void, параметров не имеет:

void OnTick();

OnTimer

Функция OnTimer() вызывается при наступлении события Timer, которое генерируется системным таймером только для экспертов и индикаторов – использовать ее в скриптах нельзя. Периодичность наступления этого события устанавливается при подписке на получение функцией EventSetTimer() уведомлений о событии Timer.

Отписывание от приема посылки событий таймера для конкретного эксперта производится функцией EventKillTimer(). Функция должна быть определена с типом void, параметров не имеет:

void OnTimer();

Рекомендуется вызывать функцию EventSetTimer() однократно в функции OnInit(), а функцию EventKillTimer() вызывать однократно в OnDeinit().

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

OnTrade

Функция вызывается при наступлении события Trade, которое возникает при изменении списка выставленных ордеров и открытых позиций, истории ордеров и истории сделок. При любом торговом действии (выставлении отложенного ордера, открытии/закрытии позиции, установке стопов, срабатывании отложенных ордеров и т.п.) соответствующим образом изменяется история ордеров и сделок и/или список позиций и текущих ордеров.

void OnTrade();

Пользователь должен самостоятельно в коде реализовать проверку состояния торгового счета при получении этого события (если это необходимо по условиям торговой стратегии). Если вызов функции OrderSend() завершился успешно и вернул значение true – это означает, что торговый сервер поставил ордер в очередь на исполнение и присвоил ему номер тикета. Как только сервер обработает данный приказ, будет сгенерировано событие Trade. И если пользователь запомнил значение тикета, то при обработке события OnTrade() он может по этому тикету выяснить что именно случилось с ордером.

OnTradeTransaction

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

Отсылка торгового запроса любым MQL5-приложением в клиентском терминале при помощи функций OrderSend и OrderSendAsync и его последующее исполнение;
Отсылка торгового запроса через графический интерфейс терминала и его последующее исполнение;

Срабатывание отложенных ордеров и стоп-ордеров на сервере;
Выполнение операций на стороне торгового сервера.

В результате данных действий, для счета выполняются торговые транзакции:

  • обработка торгового запроса;
  • изменение открытых ордеров;
  • изменение истории ордеров;
  • изменение истории сделок;
  • изменение позиций.

Например, при отсылке рыночного ордера на покупку, он обрабатывается, для счета создается соответствующий ордер на покупку, происходит исполнение ордера, его удаление из списка открытых, добавление в историю ордеров, далее добавляется соответствующая сделка в историю и создается новая позиция. Все эти действия являются торговыми транзакциями. Приход каждой такой транзакции в терминал является событием TradeTransaction. Оно вызывает обработчик OnTradeTransaction():

void OnTradeTransaction(const MqlTradeTransaction& trans, // структура торговой транзакции
                        const MqlTradeRequest& request, // структура запроса
                        const MqlTradeResult& result // структура ответа
 );

Обработчик содержит три параметра:

  • trans — в данный параметр передается структура MqlTradeTransaction, описывающая торговую транзакцию, примененную к торговому счету;
  • request — в данный параметр передается структура MqlTradeRequest, описывающая торговый запрос;
  • result — в данный параметр передается структура MqlTradeResult, описывающая результат исполнения торгового запроса.

Два последних параметра request и result заполняются значениями только для транзакции типа TRADE_TRANSACTION_REQUEST, информацию о транзакции можно получить из параметра type переменной trans. Обратите внимание, что в этом случае поле request_id в переменной result содержит идентификатор торгового запроса request, в результате выполнения которого и была произведена торговая транзакция, описанная в переменной trans. Наличие идентификатора запроса позволяет связать выполненное действие (вызов функций OrderSend или OrderSendAsync) с результатом этого действия, передаваемым в OnTradeTransaction().

Один торговый запрос, отправленный из терминала вручную или через торговые функции OrderSend()/OrderSendAsync(), может порождать на торговом сервере несколько последовательных торговых транзакций. При этом очередность поступления этих транзакций в терминал не гарантирована, поэтому нельзя свой торговый алгоритм строить на ожидании поступления одних торговых транзакций после прихода других.

Все типы торговых транзакций описываются в перечислении ENUM_TRADE_TRANSACTION_TYPE.
Структура MqlTradeTransaction, описывающая торговую транзакцию, заполняется по-разному в зависимости от типа транзакции. Например для транзакций типа TRADE_TRANSACTION_REQUEST необходимо анализировать только одно поле — type (тип торговой транзакции). Для получения дополнительной информации необходимо анализировать второй и третий параметры функции OnTradeTransaction (request и result). Более подробная информация приведена в разделе «Структура торговой транзакции».
В описании торговой транзакции передается не вся доступная информация по ордерам, сделкам и позициям (например, комментарий). Для получения расширенной информации следует использовать функции OrderGet*, HistoryOrderGet*, HistoryDealGet* и PositionGet*.

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

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

Длина очереди транзакций составляет 1024 элемента. В случае, если OnTradeTransaction() будет обрабатывать очередную транзакцию слишком долго, старые транзакции в очереди могут быть вытеснены более новыми.

В общем случае нет точного соотношения по количеству вызовов OnTrade и OnTradeTransaction. Один вызов OnTrade соответствует одному или нескольким вызовам OnTradeTransaction.
OnTrade вызывается после соответствующих вызовов OnTradeTransaction.

OnTester

Функция OnTester() является обработчиком события Tester, которое автоматически генерируется по окончании исторического тестирования эксперта на заданном интервале дат. Функция должна быть определена с типом double, параметров не имеет:

double OnTester();

Функция вызывается непосредственно перед вызовом функции OnDeinit() и имеет тип возвращаемого значения double. Функция OnTester() может быть использована только в экспертах при тестировании и предназначена в первую очередь для расчета некоторого значения, используемого в качестве критерия Custom max при генетической оптимизации входных параметров.

При генетической оптимизации сортировка результатов в пределах одного поколения производится по убыванию. То есть, лучшими с точки зрения критерия оптимизации считаются результаты с наибольшим значением (для критерия оптимизации Custom max в расчет принимаются значения, возвращенные функцией OnTester). Худшие значения при такой сортировке помещаются в конец и впоследствии отбрасываются и не принимают участия в формировании следующего поколения.

OnTesterInit

Функция OnTesterInit() является обработчиком события TesterInit, которое автоматически генерируется перед началом оптимизации эксперта в тестере стратегий. Функция должна быть определена с типом void, параметров не имеет:

void OnTesterInit();

Эксперт, имеющий обработчик OnTesterDeinit() или OnTesterPass(), при запуске оптимизации автоматически загружается на отдельном графике терминала с указанными в тестере символом и периодом, и получает событие TesterInit. Функция предназначена для инициализации эксперта перед началом оптимизации для последующей обработки результатов оптимизации.

OnTesterPass

Функция OnTesterPass() является обработчиком события TesterPass, которое автоматически генерируется при поступлении фрейма во время оптимизации эксперта в тестере стратегий. Функция должна быть определена с типом void, параметров не имеет:

void OnTesterPass();

Эксперт с обработчиком OnTesterPass() автоматически загружается на отдельном графике терминала с указанными для тестирования символом/периодом и получает во время оптимизации события TesterPass при получении фрейма. Функция предназначена для динамической обработки результатов оптимизации прямо «на лету», не дожидаясь её окончания. Добавление фреймов производится функцией FrameAdd(), которую можно вызывать по окончании одиночного прохода в обработчике OnTester().

OnTesterDeinit

Функция OnTesterDeinit() является обработчиком события TesterDeinit, которое автоматически генерируется по окончании оптимизации эксперта в тестере стратегий. Функция должна быть определена с типом void, параметров не имеет:

void OnTesterDeinit();

Эксперт с обработчиком TesterDeinit() автоматически загружается на график при запуске оптимизации и получает событие TesterDeinit после её завершения. Функция предназначена для финальной обработки всех результатов оптимизации.

OnBookEvent

Функция OnBookEvent() является обработчиком события BookEvent. Событие BookEvent генерируется для экспертов и индикаторов при изменении состояния стакана цен (Depth of Market). Должна иметь тип void и один параметр типа string:

void OnBookEvent (const string& symbol);

Чтобы получать события BookEvent по любому символу, достаточно предварительно подписаться на получение этих событий для этого символа с помощью функции MarketBookAdd(). Для того чтобы отписаться от получения события BookEvent по конкретному символу, необходимо вызывать функцию MarketBookRelease().

В отличие от других событий, событие BookEvent является широковещательным. Это означает, что достаточно одному эксперту подписаться на получение события BookEvent с помощью функции MarketBookAdd, все остальные эксперты, имеющие обработчик OnBookEvent(), будут получать это событие. Поэтому необходимо анализировать имя символа, которое передается в обработчик в качестве параметра const string& symbol.

OnChartEvent

OnChartEvent() является обработчиком группы событий ChartEvent:

  • CHARTEVENT_KEYDOWN — событие нажатия клавиатуры, когда окно графика находится в фокусе;
  • CHARTEVENT_MOUSE_MOVE — события перемещения мыши и нажатия кнопок мыши (если для графика установлено свойство CHART_EVENT_MOUSE_MOVE=true);
  • CHARTEVENT_OBJECT_CREATE — событие создания графического объекта (если для графика установлено свойство CHART_EVENT_OBJECT_CREATE=true);
  • CHARTEVENT_OBJECT_CHANGE — событие изменения свойств объекта через диалог свойств;
  • CHARTEVENT_OBJECT_DELETE — событие удаления графического объекта (если для графика установлено свойство CHART_EVENT_OBJECT_DELETE=true);
  • CHARTEVENT_CLICK — cобытие щелчка мыши на графике;
  • CHARTEVENT_OBJECT_CLICK — событие щелчка мыши на графическом объекте, принадлежащем графику;
  • CHARTEVENT_OBJECT_DRAG — событие перемещения графического объекта при помощи мыши;
  • CHARTEVENT_OBJECT_ENDEDIT — событие окончания редактирования текста в поле ввода графического объекта LabelEdit;
  • CHARTEVENT_CHART_CHANGE — событие изменения графика;
  • CHARTEVENT_CUSTOM+n — идентификатор пользовательского события, где n находится в диапазоне от 0 до 65535.
  • CHARTEVENT_CUSTOM_LAST — последний допустимый идентификатор пользовательского события (CHARTEVENT_CUSTOM+65535).

Функция может вызываться в экспертах и индикаторах, должна иметь тип void и 4 параметра:

void OnChartEvent(const int id, // идентификатор события
                  const long& lparam, // параметр события типа long
                  const double& dparam, // параметр события типа double
                  const string& sparam // параметр события типа string
 );

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

 

Событие

Значение параметра id

Значение параметра lparam

Значение параметра dparam

Значение параметра sparam

Событие нажатия клавиатуры

CHARTEVENT_KEYDOWN

Код нажатой клавиши

Количество нажатий клавиши,сгенерированных за время её удержания в нажатом состоянии

Строковое значение битовой маски, описывающее статус кнопок клавиатуры

События мыши (если для графика установлено свойство CHART_EVENT_MOUSE_MOVE=true)

CHARTEVENT_MOUSE_MOVE

X координата

Y координата

Строковое значение битовой маски, описывающее статус кнопок мыши

Событие создания графического объекта (если для графика установлено свойство CHART_EVENT_OBJECT_CREATE=true)

CHARTEVENT_OBJECT_CREATE

Имя созданного графического объекта

Событие изменения свойств объекта через диалог свойств

CHARTEVENT_OBJECT_CHANGE

Имя измененного графического объекта

Событие удаления графического объекта (если для графика установлено свойство CHART_EVENT_OBJECT_DELETE=true)

CHARTEVENT_OBJECT_DELETE

Имя удаленного графического объекта

Событие щелчка мыши на графике

CHARTEVENT_CLICK

X координата

Y координата

Событие щелчка мыши на графическом объекте

CHARTEVENT_OBJECT_CLICK

X координата

Y координата

Имя графического объекта, на котором произошло событие

Событие перемещения графического объекта при помощи мыши

CHARTEVENT_OBJECT_DRAG

Имя перемещенного графического объекта

Событие окончания редактирования текста в поле ввода графического объекта «Поле ввода»

CHARTEVENT_OBJECT_ENDEDIT

Имя графического объекта «Поле ввода», в котором завершилось редактирование текста

Событие изменения графика

CHARTEVENT_CHART_CHANGE

Пользовательское событие с номером N

CHARTEVENT_CUSTOM+N

Значение, заданное функцией EventChartCustom()

Значение, заданное функцией EventChartCustom()

Значение, заданное функцией EventChartCustom()

OnCalculate

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

Функция OnCalculate() должна иметь тип возвращаемого значения int. Существует два варианта определения. В пределах одного индикатора нельзя использовать оба варианта функции.

Первая форма вызова предназначена для тех индикаторов, которые могут быть рассчитаны на одном буфере данных. Пример такого индикатора — Custom Moving Average.

int OnCalculate (const int rates_total, // размер массива price[]
                 const int prev_calculated, // обработано баров на предыдущем вызове
                 const int begin, // откуда начинаются значимые данные
                 const double& price[] // массив для расчета
 );

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

Выбор необходимой таймсерии или индикатора в качестве массива price[] осуществляется пользователем на вкладке «Parameters» при запуске индикатора. Для этого необходимо указать нужный элемент в выпадающем списке поля «Apply to».

Для получения значений пользовательского индикатора из других mql5-программ используется функция iCustom(), возвращающая хэндл индикатора для последующих операций. При этом также можно указать необходимый массив price[] или хэндл другого индикатора. Этот параметр должен передаваться последним в списке входных переменных пользовательского индикатора.

Пример:

void OnStart()
{
   //---
   string terminal_path=TerminalInfoString(STATUS_TERMINAL_PATH);
   int handle_customMA=iCustom(Symbol(),PERIOD_CURRENT, "Custom Moving Average",13,0, MODE_EMA,PRICE_TYPICAL);
   if(handle_customMA>0)
     Print("handle_customMA = ",handle_customMA);
   else
     Print("Cannot open or not EX5 file '"+terminal_path+"\MQL5\Indicators\"+"Custom Moving Average.ex5'");
}

В данном примере последним параметром передано значение PRICE_TYPICAL (из перечисления ENUM_APPLIED_PRICE ), которое указывает, что пользовательский индикатор будет построен по типическим ценам, полученным как (High+Low+Close)/3. Если параметр не указывается, то индикатор строится по значениям PRICE_CLOSE, то есть по ценам закрытия каждого бара.

Другой пример, демонстрирующий передачу хендла индикатора последним параметром для указания массива price[], приведен в описании функции iCustom().

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

int OnCalculate (const int rates_total, // размер входных таймсерий
                 const int prev_calculated, // обработано баров на предыдущем вызове
                 const datetime& time[], // Time
                 const double& open[], // Open
                 const double& high[], // High
                 const double& low[], // Low
                 const double& close[], // Close
                 const long& tick_volume[], // Tick Volume
                 const long& volume[], // Real Volume
                 const int& spread[] // Spread
 );

Параметры open[], high[], low[] и close[] содержит массивы с ценами открытия, максимальной, минимальной ценами и ценами закрытия текущего таймфрейма. Параметр time[] содержит массив со значениями времени открытия, параметр spread[] – массив, содержащий историю спредов (если спред предусмотрен для данного торгового инструмента). Параметры volume[] и tick_volume[] содержат соответственно историю торгового и тикового объема.

Чтобы определить направление индексации в массивах time[], open[], high[], low[], close[], tick_volume[], volume[] и spread[], необходимо вызывать функцию ArrayGetAsSeries(). Чтобы не зависеть от умолчаний, необходимо безусловно вызывать функцию ArraySetAsSeries() для тех массивов, с которыми предполагается работать.

Первый параметр rates_total содержит количество баров, доступных индикатору для расчета, и соответствует количеству баров, доступных на графике.

Необходимо отметить связь между значением, возвращаемым функцией OnCalculate() и вторым входным параметром prev_calculated. Параметр prev_calculated при вызове функции содержит значение, которое вернула функция OnCalculate() на предыдущем вызове. Это позволяет реализовать экономные алгоритмы расчета пользовательского индикатора с тем, чтобы избежать повторных расчетов для тех баров, которые не изменились с предыдущего запуска этой функции.

Для этого обычно достаточно вернуть значение параметра rates_total, которое содержит количество баров при текущем вызове функции. Если с момента последнего вызова функции OnCalculate() ценовые данные были изменены (подкачана более глубокая история или были заполнены пропуски истории), то значение входного параметра prev_calculated будет установлено в нулевое значение самим терминалом.

Примечание: если функция OnCalculate возвращает нулевое значение, то в окне DataWindow клиентского терминала значения индикатора не показываются.

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

Пример индикатора:

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots 1
//---- plot Line
#property indicator_label1 "Line"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDarkBlue
#property indicator_style1 STYLE_SOLID
#property indicator_width1 1
//--- indicator buffers

double LineBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- indicator buffers mapping
   SetIndexBuffer(0,LineBuffer,INDICATOR_DATA);
   //---
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
{
   //--- получим количество доступных баров для текущих символа и периода на графике
   int bars=Bars(Symbol(),0);
   Print("Bars = ",bars,", rates_total = ",rates_total,", prev_calculated = ",prev_calculated);
   Print("time[0] = ",time[0]," time[rates_total-1] = ",time[rates_total-1]);
   //--- return value of prev_calculated for next call
   return(rates_total);
}
//+------------------------------------------------------------------+