Торговые функции
Как правило, обычный эксперт содержит несколько торговых функций. Их можно разделить
на две категории - управляющие и исполнительные. В большинстве случаев в эксперте
используется всего одна управляющая функция и несколько исполнительных.
Торговая стратегия в обычном эксперте реализуется на основе двух функций - функции
определения торговых критериев и управляющей торговой функции. Нигде в других местах
программы не должно быть каких-либо указаний на торговую стратегию. Управляющая
торговая функция и функция определения критериев должны быть согласованы между
собой по значениям передаваемых параметров.
Каждая из исполнительных торговых функций имеет свой обособленный круг задач. В
зависимости от требований торговой стратегии в эксперте могут применяться исполнительные
торговые функции, имеющие следующее назначение:
- открытие рыночного ордера заданного типа;
- закрытие одного рыночного ордера заданного типа;
- частичное закрытие одного рыночного ордера заданного типа;
- закрытие всех рыночных ордеров заданного типа;
- встречное закрытие двух рыночных ордеров в заданном объёме;
- закрытие всех рыночных ордеров;
- модификация стоп-приказов рыночного ордера заданного типа;
- установка отложенного ордера заданного типа;
- удаление одного отложенного ордера заданного типа;
- удаление всех отложенных ордеров заданного типа;
- удаление всех отложенных ордеров;
- модификация отложенного ордера заданного типа.
Общий порядок осуществления торговли в обычном эксперте заключается в следующем:
на основе вычисленных (в соответствии со стратегией) торговых критериев управляющая
торговая функция (также реализуя стратегию) вызывает те или иные исполнительные
торговые функции, которые, в свою очередь, формируют необходимые торговые приказы.
Пользовательская управляющая торговая функция Trade()
int Trade( int Trad_Oper )
Основная функция, реализующая стратегию.
Параметр Trad_Oper может принимать следующие значения, соответствующие торговым критериям:
10 - сработал торговый критерий для открытия рыночного ордера Buy;
20 - сработал торговый критерий для открытия рыночного ордера Sell;
11 - сработал торговый критерий для закрытия рыночного ордера Buy;
21 - сработал торговый критерий для закрытия рыночного ордера Sell;
0 - значимых критериев нет;
-1 - используемый финансовый инструмент не является EURUSD.
Для исполнения функции требуются следующие торговые функции:
- Close_All() - функция закрытия всех рыночных ордеров заданного типа;
- Open_Ord() - функция открытия одного рыночного ордера заданного типа;
- Tral_Stop() - функция модификации StopLoss рыночного ордера заданного типа;
- Lot() - функция определения количества лотов для новых ордеров.
Управляющая торговая функция Trade() оформлена в виде включаемого файла Trade.mqh:
int Trade(int Trad_Oper)
{
switch(Trad_Oper)
{
case 10:
Close_All(1);
if (Lot()==false)
return;
Open_Ord(0);
return;
case 11:
Close_All(0);
return;
case 20:
Close_All(0);
if (Lot()==false)
return;
Open_Ord(1);
return;
case 21:
Close_All(1);
return;
case 0:
Tral_Stop(0);
Tral_Stop(1);
return;
}
}
Вызов управляющей торговой функции Trade() осуществляется из специальной функции
start() эксперта usualexpert.mq4. В качестве передаваемого параметра в функции Trade() указано значение, возвращаемое
функцией определения торговых критериев Criterion().
В блоке 1-2 функции Trade() описаны торговые критерии, принимаемые во внимание реализованной
торговой стратегией. В функции используется оператор switch() (блоки 2-7), позволяющий
активизировать необходимую группу функций для осуществления торговли согласно торговому
критерию. В соответствии с принятой в эксперте торговой стратегией эксперт открывает
и закрывает только рыночные ордера, использование отложенных ордеров в этой стратегии
не предусмотрено.
В разделе Функция определения торговых критериев указывалось, что по некоторым торговым критериям может быть сформировано несколько
различных торговых приказов. Так, в случае, если значимым является торговый критерий
на покупку (значение переменной Trad_Oper равно 10), то при исполнении оператора
switch() управление передаётся на метку case 10 (блок 2-3). В этом случае сначала
вызывается торговая функция Close_All(1). Исполнение этой функции приводит к закрытию
всех рыночных ордеров Sell, открытых по финансовому инструменту EURUSD. После того
как все ордера Sell закрыты, выполняется проверка достаточности средств для совершения
следующей торговой операции. Для этого вызывается пользовательская функция Lot()
(см. раздел
Функция определения количества лотов). Если эта функция возвращает false, значит имеющихся средств недостаточно даже
для открытия ордера Buy на минимально допустимое количество лотов. В этом случае
функция Trade() заканчивает свою работу. Если же средств достаточно, то вызывается
торговая функция Open_Ord(0) для открытия одного рыночного ордера Buy на такое
количество лотов, которое вычислено при исполнении функции Lot(). Указанная совокупность
действий являет реакцию эксперта на сложившуюся ситуацию на рынке (в соответствии
с данным торговым критерием).
Если значимым является критерий, указывающий на необходимость закрытия рыночных
ордеров Buy, то управление передаётся на метку case 11, в блок 3-4. В этом случае
вызывается для исполнения всего одна функция Close_All(0) для закрытия всех имеющихся
рыночных ордеров типа Buy. Блоки 4-6 составлены аналогично блокам 2-4, управление
вариантам case 20 и case 21 передаётся в случаях, если значимыми являются критерии
на продажу или закрытие рыночных ордеров Sell.
Обратите внимание, все исполнительные торговые функции, формирующие торговые приказы,
вызываются из функции Trade(), которая в свою очередь вызывается при исполнении
специальной функции start() эксперта, запускаемой клиентским терминалом в результате
прихода очередного тика. Код функции Trade() составлен таким образом, что управление
не возвращается в функцию start() (и в конечном счёте клиентскому терминалу) до
тех пор, пока не будут исполнены все необходимые исполнительные торговые функции.
Поэтому все торговые операции, предусмотренные для каждого из торговых критериев,
осуществляются экспертом подряд, без пауз. Исключение составляют случаи, когда
при исполнении торговых операций возникают критические ошибки (см. раздел Функция обработки ошибок).
Если при исполнении функции Criterion() определено, что ни один из торговых критериев
не является значимым (переменная Trad_Oper равна 0), то управление передаётся на
метку case 0, в результате чего дважды вызывается для исполнения функция Tral_Stop()
для модификации заявленных значений рыночных ордеров разных типов. Реализованная
в эксперте торговая стратегия допускает наличие только одного рыночного ордера,
поэтому порядок следования вызовов функций Tral_Stop(0) и Tral_Stop(1) не имеет
значения. В данном случае сделан случайный выбор.
В случае, если функция Criterion() вернула значение -1, то это означает, что эксперт
прикреплён в окно финансового инструмента, не являющегося EURUSD. В этом случае
функция Trade() не осуществляет вызов каких-либо исполнительных торговых функций
и возвращает управление вызвавшей её специальной функции start().
Пользовательская исполнительная торговая функция Close_All()
int Close_All( int Tip)
Функция закрывает все рыночные ордера указанного типа.
Параметр Tip может принимать следующие значения, соответствующие типу закрываемых ордеров:
0 - тип закрываемых ордеров Buy;
1 - тип закрываемых ордеров Sell.
Для исполнения функции требуется применение в программе функции учёта ордеров Terminal(),
функции слежения за событиями Events() и функции обработки ошибок Errors(). Для
вывода сообщений функция предполагает использование информационной функции Inform().
В случае, если функция Inform() не включена в эксперт, сообщения не выводятся.
Используются значения глобальных массивов:
- Mas_Ord_New - массив характеристик ордеров на момент последнего исполнения функции
Terminal();
- Mas_Tip - массив количества ордеров всех типов на момент последнего исполнения функции
Terminal().
Исполнительная торговая функция Close_All() оформлена в виде включаемого файла Close_All.mqh:
int Close_All(int Tip)
{
int Ticket=0;
double Lot=0;
double Price_Cls;
while(Mas_Tip[Tip]>0)
{
for(int i=1; i<=Mas_Ord_New[0][0]; i++)
{
if(Mas_Ord_New[i][6]==Tip &&
Mas_Ord_New[i][5]>Lot)
{
Lot=Mas_Ord_New[i][5];
Ticket=Mas_Ord_New[i][4];
}
}
if (Tip==0) Price_Cls=Bid;
if (Tip==1) Price_Cls=Ask;
Inform(12,Ticket);
bool Ans=OrderClose(Ticket,Lot,Price_Cls,2);
if (Ans==false)
{
if(Errors(GetLastError())==false)
return;
}
Terminal();
Events();
}
return;
}
В блоке 1-2 описаны используемые глобальные переменные, в блоке 2-3 открыты и описаны
локальные переменные. Условие Mas_Tip[Tip]>0 в заголовке оператора цикла while
(блоки 3-6) предполагает, что функция будет удерживать управление до тех пор, пока
не выполнит своё предназначение, а именно, пока не будут закрыты все ордера заданного
типа. Элемент глобального массива Mas_Tip[Tip] содержит значение, равное количеству
ордеров заданного типа Tip. Например, если функция Close_All() вызвана с передаваемым
параметром, равным 1, то это означает, что ей предписывается закрыть все рыночные
ордера Sell (см. Типы торговых операций). В этом случае значение элемента массива Mas_Tip[1] будет равно количеству имеющихся
ордеров Sell (последнему известному на момент исполнения функции Terminal()). Таким
образом, оператор цикла while будет исполнен столько раз, сколько имеется ордеров
Sell.
Если трейдер не вмешивается в работу эксперта (т.е. не выставляет ордера вручную),
то в торговле может присутствовать только один рыночный ордер того или иного типа.
Если же трейдер по своей инициативе установил дополнительно один или несколько
рыночных ордеров, то при исполнении функции Close_All() должен быть соблюдён некоторый
порядок закрытия ордеров. Предпочтительным является порядок закрытия ордеров от
большего к меньшему. Например, если на момент начала исполнения функции Close_All()
имеется три ордера Sell, причём один из них открыт на 5 лотов, другой на 1 лот,
а третий - на 4 лота, то в соответствии с указанным порядком ордера будут закрыты
в такой последовательности: сначала ордер в объёме 5 лотов, потом 4 лота и последним
- ордер объёмом 1 лот.
Обратите внимание, количество лотов - единственный критерий, принятый для определения
порядка закрытия ордеров. Прибыль/убыток по ордеру, курс открытия ордера, а также
другие параметры, характеризующие ордер (заявленные цены стоп-приказов, время и
причина открытия и пр.), не рассматриваются.
|
Все рыночные ордера определённого типа должны быть закрыты в случае, если критерий
закрытия ордеров этого типа является значимым, в порядке от большего к меньшему.
|
Для соблюдения указанного порядка закрытия ордеров в блоке 3-4 используется цикл
for, в котором среди всех ордеров заданного типа выбирается один ордер, имеющий
наибольшее количество лотов. Поиск этого ордера выполняется на основе анализа значений
элементов глобального массива Mas_Ord_New, содержащего информацию обо всех ордерах,
присутствующих в торговле. После того как номер этого ордера определён, в зависимости
от типа ордера вычисляется заявленная цена закрытия, равная соответствующему значению
последней известной двухсторонней котировки. Если тип закрываемых ордеров Buy,
то следует заявлять цену закрытия в соответствии со значением Bid, а если Sell,
- то Ask.
Непосредственно перед формированием торгового приказа выводится информация о попытке
закрыть ордер, для этого используется вызов функции Inform(). Торговый приказ на
закрытие ордера формируется в строке:
bool Ans=OrderClose(Ticket,Lot,Price_Cls,2);
В качестве параметров используются вычисленные значения: Ticket - номер ордера,
Lot - количество лотов, Price_Cls - заявленная цена закрытия, 2 - проскальзывание.
В блоке 4-5 выполняется анализ результатов выполнения торговой операции. Если функция
OrderClose() вернула значение true, то это означает, что торговая операция успешно
завершена, т.е. ордер закрыт. В этом случае управление передаётся в блок 5-6, где
обновляется информация об ордерах, имеющихся на текущий момент. После исполнения
функций Terminal() и Events() текущая итерация цикла while заканчивается (за время,
пока исполняется функция, в том числе, выполняются торговые операции, количество
ордеров может измениться, поэтому исполнение функции учёта ордеров на каждой итерации
цикла while обязательно). В случае если в торговле ещё присутствуют ордера заданного
типа, то на следующей итерации цикла while они будут закрыты, при этом для определения
параметров очередного закрываемого ордера используются новые значения элементов
массивов Mas_Ord_New и Mas_Tip, полученные при исполнении функции Terminal().
Если в результате исполнения торгового приказа функция OrderClose() вернула значение
false, то это значит, что ордер не закрыт. Чтобы выяснить причину неудачи выполняется
анализ последней ошибки, возникшей при попытке осуществления торговой операции.
Для этого вызывается функция Errors() (см. раздел Функция обработки ошибок). Если при исполнении этой функции обнаруживается, что ошибка является непреодолимой
(например, торговля запрещена), то функция Close_All() заканчивает работу и возвращает
управление в управляющую торговую функцию Trade(), что в конечном счёте приводит
к завершению исполнения специальной функции start() эксперта. На следующем тике
торговый терминал снова запустит на исполнение функцию start(), и если критерий
закрытия на тот момент будет ещё актуален, то это снова приведёт к вызову на исполнение
функции закрытия всех ордеров Close_All().
Пользовательская исполнительная торговая функция Open_Ord()
int Open_Ord ( int Tip)
Функция открывает один рыночный ордер указанного типа.
Параметр Tip может принимать следующие значения, соответствующие типу закрываемых ордеров:
0 - тип закрываемых ордеров Buy;
1 - тип закрываемых ордеров Sell.
Для исполнения функции требуется применение в программе функции учёта ордеров Terminal(),
функции слежения за событиями Events() и функции обработки ошибок Errors(). Для
вывода сообщений функция предполагает использование информационной функции Inform().
В случае, если функция Inform() не включена в эксперт, сообщения не выводятся.
Используются значения глобальных переменных:
- Mas_Tip - массив количества ордеров всех типов на момент последнего исполнения функции
Terminal();
- StopLoss - значение StopLoss (количество пунктов);
- TakeProfit - значение TakeProfit (количество пунктов).
Исполнительная торговая функция Open_Ord() оформлена в виде включаемого файла Open_Ord.mqh:
int Open_Ord(int Tip)
{
int Ticket,
MN;
double SL,
TP;
while(Mas_Tip[Tip]==0)
{
if (StopLoss<Level_new)
StopLoss=Level_new;
if (TakeProfit<Level_new)
TakeProfit=Level_new;
MN=TimeCurrent();
Inform(13,Tip);
if (Tip==0)
{
SL=Bid - StopLoss* Point;
TP=Bid + TakeProfit*Point;
Ticket=OrderSend(Symbol(),0,Lots_New,Ask,2,SL,TP,"",MN);
}
if (Tip==1)
{
SL=Ask + StopLoss* Point;
TP=Ask - TakeProfit*Point;
Ticket=OrderSend(Symbol(),1,Lots_New,Bid,2,SL,TP,"",MN);
}
if (Ticket<0)
{
if(Errors(GetLastError())==false)
return;
}
Terminal();
Events();
}
return;
}
В блоках 1-3 функции Open_Ord() описаны глобальные переменные, значения которых
используются при исполнении функции, а также открыты и описаны локальные переменные.
Основной код функции сосредоточен в операторе цикла while (блоки 3-5), который
исполняется до тех пор, пока в торговле нет ни одного ордера заданного типа Tip.
Торговая стратегия предполагает открытие ордеров, имеющих ненулевые стоп-приказы.
В общем случае трейдер может установить такие значения стоп-приказов, которые не
удовлетворяют требованиям дилингового центра, а именно меньше ограничивающей минимальной
дистанции. Поэтому перед открытием ордера выполняется необходимая проверка: если
последняя известная минимальная дистанция (Level_new) превышает значение внешней
переменной StopLoss или TakeProfit, то значение этой переменной увеличивается и
устанавливается равным Level_new.
Каждый открываемый ордер имеет свой уникальный MagicNumber, равный текущему серверному
времени. В результате исполнения одного эксперта по финансовому инструменту может
быть одновременно открыт только один рыночный ордер (или установлен отложенный).
Таким образом гарантируется, что все рыночные ордера будут иметь уникальный MagicNumber.
Перед открытием ордера исполняется функция Inform(), в результате чего выводится
сообщение о попытке совершить торговую операцию.
В зависимости от типа ордера выполняется тело одного из операторов if. Например,
если значение передаваемого параметра Tip равно 0, то это значит, что необходимо
открыть ордер Buy. В этом случае вычисляются значения StopLoss и TakeProfit, соответствующие
типу ордера Buy, и после этого управление передаётся в строку
Ticket=OrderSend(Symbol(),0,Lots_New,Ask,2,SL,TP,"",MN);
для формирования торгового приказа на открытие рыночного ордера Buy. Аналогичные
вычисления производятся в случае, если значение параметра Tip равно 1, т.е необходимо
открыть ордер Sell.
Обработка ошибок во всех пользовательских исполнительных торговых функциях выполняется
подобным образом. Если торговая операция завершилась успехом, то функция заканчивает
работу (потому, что не будет выполняться очередная итерация цикла while, т.к.
после исполнения функции Terminal() значение элемента массива Mas_Tip[Tip] будет
равно 1). Если же торговый приказ не был исполнен, то выполняется анализ ошибок
(блок 4-5). В этом случае вызывается для исполнения функция обработки ошибок Errors().
Если она возвращает false (ошибка является критической), то исполнение функции
Open_Ord() заканчивается, управление последовательно возвращается в управляющую
торговую функцию Trade(), специальную функцию start() и далее клиентскому терминалу.
Если же ошибка является преодолимой, то после обновления массивов ордеров в функции
Terminal() управление передаётся на очередную итерацию цикла while, в результате
чего выполняется ещё одна попытка открыть ордер.
Таким образом, функция Open_Ord() удерживает управление до тех пор, пока не будет
открыт ордер, либо при исполнении торгового приказа будет получена критическая
ошибка.
Пользовательская исполнительная торговая функция Tral_Stop()
int Tral_Stop ( int Tip)
Функция модификации всех рыночных ордеров указанного типа.
Параметр Tip может принимать следующие значения, соответствующие типу закрываемых ордеров:
0 - тип закрываемых ордеров Buy;
1 - тип закрываемых ордеров Sell.
Для исполнения функции требуется применение в программе функции учёта ордеров Terminal(),
функции слежения за событиями Events() и функции обработки ошибок Errors(). Для
вывода сообщений функция предполагает использование информационной функции Inform().
В случае, если функция Inform() не включена в эксперт, сообщения не выводятся.
Используются значения глобальных переменных:
- Mas_Ord_New - массив характеристик ордеров на момент последнего исполнения функции
Terminal();
- TralingStop - дистанция между рыночной ценой и желаемым значением заявленной цены
StopLoss (количество пунктов).
Исполнительная торговая функция Tral_Stop() оформлена в виде включаемого файла Tral_Stop.mqh:
int Tral_Stop(int Tip)
{
int Ticket;
double
Price,
TS,
SL,
TP;
bool Modify;
for(int i=1;i<=Mas_Ord_New[0][0];i++)
{
if (Mas_Ord_New[i][6]!=Tip)
continue;
Modify=false;
Price =Mas_Ord_New[i][1];
SL =Mas_Ord_New[i][2];
TP =Mas_Ord_New[i][3];
Ticket=Mas_Ord_New[i][4];
if (TralingStop<Level_new)
TralingStop=Level_new;
TS=TralingStop*Point;
switch(Tip)
{
case 0 :
if (NormalizeDouble(SL,Digits)<
NormalizeDouble(Bid-TS,Digits))
{
SL=Bid-TS;
Modify=true;
}
break;
case 1 :
if (NormalizeDouble(SL,Digits)>
NormalizeDouble(Ask+TS,Digits)||
NormalizeDouble(SL,Digits)==0)
{
SL=Ask+TS;
Modify=true;
}
}
if (Modify==false)
continue;
bool Ans=OrderModify(Ticket,Price,SL,TP,0);
if (Ans==false)
{
if(Errors(GetLastError())==false)
return;
i--;
}
Terminal();
Events();
}
return;
}
В блоках 1-3 описаны глобальные переменные, используемые в функции, а также открыты
и описаны локальные переменные. В цикле for (блоки 3-6) выполняется отбор ордеров
заданного типа, и если StopLoss какого-либо из этих ордеров находится дальше от
текущей цены, чем задано пользователем, этот ордер модифицируется.
Для облегчения восприятия кода значения некоторых элементов массива ордеров Mas_Ord_New
присваиваются простым переменным (блок 3-4). Далее для переменной TralingStop выполняется
необходимая проверка: если значение этой переменной меньше минимально допустимой
дистанции, установленной дилинговым центром, то оно увеличивается до минимально
допустимой величины.
В блоке 4-5, в зависимости от типа ордера, выполняются необходимые вычисления. Например,
если значение передаваемого параметра Tip равно 1 (необходимо модифицировать ордер
Sell), то управление передаётся на метку case 1 оператора switch. Здесь выполняется
проверка необходимости модификации StopLoss ордера (в соответствии с правилами,
применяемыми для ордера этого типа, см. Требования и ограничения торговых операций). Если StopLoss вообще не установлен или находится дальше от текущего курса, чем
на величину дистанции TralingStop, то вычисляется желаемое новое значение StopLoss.
Торговый приказ для модификации ордера формируется в строке:
bool Ans = OrderModify(Ticket,Price,SL,TP,0);
Ранее отмечалось, что рассматриваемая здесь торговая стратегия предполагает наличие
только одного рыночного ордера. Тем не менее, функция Tral_Stop() предусматривает
возможность модификации нескольких рыночных ордеров одного типа. Если трейдер не
вмешивается в торговлю во время работы эксперта, то необходимость модифицировать
несколько ордеров не возникает. Однако для случая, когда трейдер допускает открытие
рыночного ордера вручную (в дополнение к уже имеющемуся), возникает вопрос о порядке
модификации ордеров, а именно, какой из них необходимо модифицировать раньше других
и почему.
При рассмотрении порядка закрытия нескольких ордеров было указано, что критерием,
определяющим приоритет закрытия ордеров, является количество лотов. Такое решение
очевидно - чем больше лотов (из суммарного количества) будет закрыто, тем быстрее
будет реакция эксперта на срабатывание признака закрытия. Вопрос о порядке модификации
ордеров не имеет однозначного решения. Во всех случаях критерий для определения
порядка модификации ордеров диктуется сущностью торговой стратегии. Таким критерием
может быть не только количество лотов, но и факт отсутствия StopLoss на каком-либо
из ордеров, размер дистанции от StopLoss до текущей цены. В ряде случаев критерием
может выступать обобщённый показатель - размер убытка, который может быть получен
при резком изменении цены, т.е. при условии одномоментного автоматического закрытия
всех рыночных ордеров по StopLoss.
В рассматриваемом примере функции Tral_Stop() реализован случайный порядок модификации
ордеров - ордера модифицируются в той последовательности, в которой они встречаются
в списке открытых рыночных и установленных отложенных ордеров. В каждом конкретном
случае функцию необходимо доработать - запрограммировать порядок модификации ордеров
в соответствии с правилами конкретной стратегии.
Особое внимание нужно обратить на тот факт, что все торговые операции совершаются
в режиме реального времени. Если ордеров слишком много, то эксперт будет генерировать
множество торговых приказов. За время, пока эти приказы будут исполнены, рынок
может развернуться. Однако функция не возвращает управление в вызывающую её функцию
Trade() до тех пор, пока не будут модифицированы все ордера, подлежащие модификации.
Это значит, что возникает опасность пропустить торговый приказ на закрытие или
открытие ордеров. По этой причине любая стратегия должна составляться таким образом,
чтобы по возможности не допускать значительного количества рыночных ордеров.
В блоке 5-6 выполняется анализ ошибок, полученных при исполнении торговых приказов.
Если ошибка является критической, то функция заканчивает работу. Если же получена
преодолимая ошибка, то значение счётчика i понижается на 1. Это делается для того,
чтобы на очередной итерации цикла for была предпринята ещё одна попытка модифицировать
тот же ордер.
В большинстве случаев представленный код будет удовлетворять потребности модификации
нескольких ордеров. Вместе с тем, если в период нескольких неудачных попыток модификации
в составе ордеров произойдут изменения (например, какой-то из ордеров будет закрыт
при достижения рыночной ценой одного из стоп-приказов), то порядок ордеров в массиве
Mas_Ord_New может измениться. Это приведёт к тому, что какой-то ордер может быть
пропущен и не модифицирован в период последнего запуска специальной функции start().
Ситуация может быть исправлена на следующем тике, при очередном запуске функции
start().