Русский

Учебник по MQL4  Торговые операции  Закрытие и удаление ордеров

Закрытие и удаление ордеров


Закрытие рыночных ордеров


Торговые приказы для закрытия рыночных ордеров формируются с помощью функции OrderClose().

Функция OrderClose()

bool OrderClose (int ticket, double lots, double price, int slippage, color Color=CLR_NONE)

Функция закрытия рыночного ордера. Функция возвращает TRUE при успешном исполнении торговой операции. Возвращает FALSE при неудачном завершении торговой операции.

Параметры:

ticket - уникальный порядковый номер ордера.

lots - количество лотов для закрытия. Допускается указать значение меньшее, чем имеющееся количество лотов ордера. В этом случае (при успешном исполнении торгового приказа) ордер будет закрыт частично.

price - цена закрытия. Устанавливается в соответствии с требованиями и ограничениями, принятыми для проведения торговых операций (см. Характеристики ордеров и Приложение 3). Если заявленной цены для закрытия рыночного ордера не было в ценовом потоке или она сильно устарела, то такой торговый приказ отклоняется; если же цена устарела, но присутствует в ценовом потоке и при этом отклонение от текущей цены находится в пределах значения slippage, то такой торговый приказ будет принят клиентским терминалом и отправлен на торговый сервер.

slippage - максимально допустимое отклонение заявленной цены закрытия ордера от рыночной цены (пунктов).

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


Если в программе имеются сведения о типе ордера, который необходимо закрыть, о его уникальном номере, а также известно количество лотов, предназначенных для закрытия, то закрыть рыночный ордер очень просто. Для этого в коде программы необходимо использовать обращение к функции OrderClose() с заданными параметрами. Например, если уникальный номер ордера Buy 12345 и необходимо закрыть 0.5 лотов, то обращение к функции закрытия ордера может выглядеть так:

OrderClose( 12345, 0.5, Bid, 2 );

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

Функция OrderSelect()


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

bool OrderSelect(int index, int select, int pool=MODE_TRADES)

OrderSelect - функция выбирает ордер для дальнейшей работы с ним. Возвращает TRUE при успешном завершении функции. Возвращает FALSE при неудачном завершении функции.

Параметры:

index - позиция ордера или номер ордера в зависимости от второго параметра.

select - флаг способа выбора. Параметр select может принимать одно из двух значений:

SELECT_BY_POS - в параметре index передается порядковый номер ордера в списке (нумерация начинается с 0),

SELECT_BY_TICKET - в параметре index передается номер тикета (уникальный номер ордера).

pool - источник данных для выбора. Параметр pool используется, когда параметр select равен SELECT_BY_POS. Параметр pool игнорируется, если ордер выбирается по номеру тикета (SELECT_BY_TICKET). Параметр pool может принимать одно из двух значений:

MODE_TRADES (по умолчанию) - ордер выбирается среди открытых и отложенных ордеров, т.е. среди ордеров, отображаемых в Терминале на закладке Торговля;

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


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

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

Предположим, что в терминале имеется три рыночных ордера, открытых по финансовому инструменту Eur/Usd и один отложенный ордер, открытый по Usd/Chf:


Рис. 90. Отражение в окне Терминала нескольких ордеров, открытых по разным финансовым инструментам.

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


Рис. 91. Использование скрипта closeorder.mq4 для закрытия выбранного ордера.

Для решения задачи из всех ордеров необходимо выбрать ордера, открытые по финансовому инструменту (используя функцию OrderSymbol()), в окно которого брошен скрипт. Затем нужно определить цены открытия всех выбранных рыночных ордеров (т.е. необходимо исполнить функцию OrderOpenPrice() последовательно для каждого ордера). Зная цены открытия ордеров, легко выбрать из них один, соответствующий условию задачи. Чтобы указать правильные значения параметров в функции OrderClose(), понадобятся ещё и другие сведения о выбранном ордере: количество лотов (определяется функцией OrderLots()) и уникальный номер ордера (определяется функцией OrderTicket()). Кроме того, чтобы правильно определить ту или иную цену двухсторонней котировки, понадобится знать тип ордера (определяется с помощью функции OrderType()).

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

Прежде всего, необходимо определиться со способом выбора ордеров. В данной задаче выбор способа диктуется самой постановкой задачи: полагается, что в момент запуска скрипта на исполнение в программе нет сведений о номерах ордеров, т.е. программа должна содержать блок, в котором номера ордеров определяются. Это значит, что необходимо последовательно по порядку опросить все ордера, отражённые в Терминале (рис. 64.1), поэтому требуется использование параметра SELECT_BY_POS.

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

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

   for (int i=1; i<=OrdersTotal(); i++)       //Цикл по всем ордерам,..
{ //отражённым в терминале
if(OrderSelect(i-1,SELECT_BY_POS)==true)//Если есть следующий
{
// Здесь должен выполняться ..
// ..анализ характеристик ордеров
}
} //Конец тела цикла

В заголовке оператора цикла указано начальное значение i=1, а условием окончания цикла - выражение i<=OrdersTotal(). Функция OrdersTotal() возвращает общее количество рыночных и отложенных ордеров, т.е. тех ордеров, которые отражаются в Терминале на закладке Торговля. Поэтому в цикле будет столько итераций, сколько ордеров присутствует в торговле.

На каждой итерации, при вычислении условия в операторе if, будет исполнена функция OrderSelect(i-1,SELECT_BY_POS). Здесь необходимо обратить внимание на следующий важный момент:

Исчисление позиций ордеров в списке рыночных и отложенных ордеров начинается с нуля.

Это значит, что первый по порядку ордер (рис. 90) располагается в нулевой позиции, позиция второго ордера имеет значение 1, третьего ордера - значение 2 и т.д. Поэтому в вызове функции OrderSelect() указано значение индекса i-1. Таким образом, для всех выбираемых ордеров этот индекс будет на 1 меньше, чем значение переменной i (совпадающее с номером очередной итерации).

Функция OrderSelect() возвращает true в случае, если выбор ордера выполнен удачно. Под этим понимается, что возможен вариант, при котором выбор ордера может закончиться неудачей. Это может произойти в случае, если за время обработки ордеров количество ордеров изменилось. При программировании на MQL4 нужно хорошо помнить, что прикладная программа будет работать в режиме реального времени, и пока в ней выполняется обработка каких-то параметров, значение этих параметров может измениться. Например, может измениться количество рыночных ордеров, причём это может произойти как в результате открытия/закрытия ордеров, так и в результате преобразования отложенных ордеров в рыночные. Поэтому при программировании обработки ордеров необходимо придерживаться правила: обработка ордеров должна быть выполнена как можно быстрее, а программный блок, ответственный за эту обработку, по возможности не должен содержать лишних программных строк.

Согласно коду, представленному на рис. 64.3, в заголовке оператора if анализируется факт наличия следующего ордера в списке ордеров на момент его выбора. Если следующий ордер есть, то управление передаётся внутрь тела оператора if для обработки параметров ордера. Нужно заметить, что такая конструкция тоже не спасает от возможного конфликта, т.к. ордер может пропасть (быть закрытым) в процессе обработки его параметров. Однако такое решение оказывается наиболее эффективным в случае, если на момент выбора ордера его уже нет. В теле оператора if выполняется анализ параметров выбранного ордера. При исполнении, например, функций OrderOpenPrice(), OrderTicket(), OrderType() и других подобных каждая из них будет возвращать значение некоторой характеристики ордера, выбранного в результате исполнения функции OrderSelect().

Все предыдущие рассуждения использовались при составлении программы для решения Задачи 28.

Пример простого скрипта, предназначенного для закрытия рыночного ордера, цена открытия которого находится ближе к месту прикрепления скрипта, чем цены открытия других ордеров (closeorder.mq4).

//--------------------------------------------------------------------
// closeorder.mq4
// Предназначен для использования в качестве примера в учебнике MQL4.
//--------------------------------------------------------------- 1 --
int start() // Спец.функция start
{
string Symb=Symbol(); // Финанс. инструмент
double Dist=1000000.0; // Предустановка
int Real_Order=-1; // Пока рыночных нет
double Win_Price=WindowPriceOnDropped(); // Здесь брошен скрипт
//--------------------------------------------------------------- 2 --
for(int i=1; i<=OrdersTotal(); i++) // Цикл перебора ордер
{
if (OrderSelect(i-1,SELECT_BY_POS)==true) // Если есть следующий
{ // Анализ ордеров:
//------------------------------------------------------ 3 --
if (OrderSymbol()!= Symb) continue; // Не наш фин.инструм.
int Tip=OrderType(); // Тип ордера
if (Tip>1) continue; // Отложенный ордер
//------------------------------------------------------ 4 --
double Price=OrderOpenPrice(); // Цена ордера
if (NormalizeDouble(MathAbs(Price-Win_Price),Digits)< //Выбор
NormalizeDouble(Dist,Digits)) // самого близкого орд
{
Dist=MathAbs(Price-Win_Price); // Новое значение
Real_Order=Tip; // Есть рыночный ордер
int Ticket=OrderTicket(); // Номер ордера
double Lot=OrderLots(); // Количество лотов
}
//------------------------------------------------------ 5 --
} //Конец анализа ордера
} //Конец перебора орд.
//--------------------------------------------------------------- 6 --
while(true) // Цикл закрытия орд.
{
if (Real_Order==-1) // Если рыночных нет
{
Alert("По ",Symb," рыночных ордеров нет");
break; // Выход из цикла закр
}
//--------------------------------------------------------- 7 --
switch(Real_Order) // По типу ордера
{
case 0: double Price_Cls=Bid; // Ордер Buy
string Text="Buy "; // Текст для Buy
break; // Из switch
case 1: Price_Cls=Ask; // Ордер Sell
Text="Sell "; // Текст для Sell
}
Alert("Попытка закрыть ",Text," ",Ticket,". Ожидание ответа..");
bool Ans=OrderClose(Ticket,Lot,Price_Cls,2);// Закрытие ордера
//--------------------------------------------------------- 8 --
if (Ans==true) // Получилось :)
{
Alert ("Закрыт ордер ",Text," ",Ticket);
break; // Выход из цикла закр
}
//--------------------------------------------------------- 9 --
int Error=GetLastError(); // Не получилось :(
switch(Error) // Преодолимые ошибки
{
case 135:Alert("Цена изменилась. Пробуем ещё раз..");
RefreshRates(); // Обновим данные
continue; // На след. итерацию
case 136:Alert("Нет цен. Ждём новый тик..");
while(RefreshRates()==false) // До нового тика
Sleep(1); // Задержка в цикле
continue; // На след. итерацию
case 146:Alert("Подсистема торговли занята. Пробуем ещё..");
Sleep(500); // Простое решение
RefreshRates(); // Обновим данные
continue; // На след. итерацию
}
switch(Error) // Критические ошибки
{
case 2 : Alert("Общая ошибка.");
break; // Выход из switch
case 5 : Alert("Старая версия клиентского терминала.");
break; // Выход из switch
case 64: Alert("Счет заблокирован.");
break; // Выход из switch
case 133:Alert("Торговля запрещена");
break; // Выход из switch
default: Alert("Возникла ошибка ",Error);//Другие варианты
}
break; // Выход из цикла закр
}
//-------------------------------------------------------------- 10 --
Alert ("Скрипт закончил работу -----------------------------");
return; // Выход из start()
}
//-------------------------------------------------------------- 11 --

Весь код программы closeorder.mq4 сосредоточен в специальной функции start(). В блоке 1-2 инициализируются некоторые переменные. Переменная Dist - дистанция от места, где был брошен скрипт, до ближайшего ордера. Переменная Real_Order - флаг, отражающий факт наличия в терминале хотя бы одного рыночного ордера (неотрицательное значение). Переменная Win_Price - цена, на которой пользователь прикрепил скрипт в окно финансового инструмента. В блоке 2-6 выполняется анализ ордеров: один из имеющихся ордеров назначается к закрытию. Блок 6-10 представляет блок закрытия ордера и обработку ошибок, которые могут возникнуть при исполнении торговой операции.

С момента, когда пользователь присоединил скрипт в окно финансового инструмента, вычисляются значения переменных в блоке 1-2, в том числе переменная Win_Price получает значение цены, на уровне которой пользователь прикрепил скрипт. Теперь необходимо найти ордер (и определить его характеристики), который расположен ближе других к этому месту.

В цикле for (блок 2-6) выполняется перебор ордеров. В блоке 2-3 программа определяет, есть ли в очередной строке Терминала ордер, и если он есть, то управление передаётся в тело оператора if для получения и анализа характеристик этого ордера. В блоке 3-4 отсортировываются ордера, открытые не по тому финансовому инструменту, где исполняется программа. В нашем случае - это ордер 4372930, открытый по Usd/Chf. Функция OrderSymbol() возвращает название финансового инструмента выбранного ордера. Если это название не совпадает с названием финансового инструмента, в котором исполняется программа, то текущая итерация прерывается, тем самым предотвращая обработку ордера, открытого по другому финансовому инструменту. Если же исследуемый ордер оказался открытым по "нашему" финансовому инструменту, то выполняется ещё одна проверка. С помощью функции OrderType() определяется тип ордера (см. Типы торговых операций). Если тип ордера оказывается больше 1, это означает, что исследуемый ордер является отложенным. В этом случае также прерывается текущая итерация - отложенные ордера в данном случае нас не интересуют. В нашем примере такой ордер есть, но он к тому же открыт по другому финансовому инструменту, поэтому ранее уже отсортирован. Все ордера, которые благополучно проходят проверку в блоке 3-4, являются рыночными.

Блок 4-5 предназначен для того, чтобы из всех (ранее прошедших проверку) рыночных ордеров выбрать один, а именно тот, который находится ближе всего к ранее определённой цене (значению переменной Win_Price). От пользователя не требуется точного попадания курсором мыши в линию ордера. Выбор производится в пользу того ордера, который оказался ближе других к курсору в момент запуска скрипта на исполнение. С помощью функции OrderOpenPrice() определяется цена открытия обрабатываемого ордера. Если абсолютное значение дистанции между ценой ордера и ценой курсора меньше, чем эта же дистанция для предыдущего ордера, то выбор производится в пользу текущего (абсолютная величина дистанции нужна для того, чтобы исключить влияние положения курсора - выше или ниже линии ордера). В этом случае на текущей итерации цикла for этот ордер запоминается как первый претендент на закрытие. Для такого ордера в конце блока 4-5 вычисляется номер тикета (индивидуальный номер ордера) и количество лотов. В данном примере (рис. 90) общее количество ордеров - четыре (три рыночных и один отложенный), поэтому в цикле for будет выполнено четыре итерации, в результате чего будет определена вся необходимая информация для закрытия одного выбранного ордера.

Далее управление в исполняемой программе будет передано оператору цикла while (блок 6-10). В блоке 6-7 выполняется проверка наличия найденных рыночных ордеров. Если в блоке 2-4 не было обнаружено ни одного рыночного ордера (а это в общем случае вполне возможно), то значение флага Real_Order остаётся равным -1, что означает отсутствие рыночных ордеров. Если при проверке в блоке 6-7 выявлено отсутствие рыночных ордеров, то выполнение цикла while прерывается и программа заканчивает работу. Если же значение переменной Real_Order оказывается равным 0 или 1, то это значит, что рыночный ордер к закрытию ранее определён и его надо закрыть.

В блоке 7-8, в зависимости от типа ордера вычисляется цена закрытия - для ордеров Buy это значение Bid, а для ордеров Sell - Ask (см. Требования и ограничения торговых операций).

В блоке 7-8 также вычисляется значения вспомогательной переменной Text. Собственно торговый приказ на закрытие ордера формируется в функции OrderClose () в следующей строке:

      bool Ans=OrderClose(Ticket,Lot,Price_Cls,2);// Закрытие ордера

Торговая функция OrderClose() возвращает true при успешном исполнении торговой операции и false при неудачном. Если торговый приказ успешно исполнен на сервере, то переменной Ans (ответ) будет присвоено значение true. В этом случае, исполняя блок 8-9, программа сообщит пользователю об успешном закрытии ордера, после чего исполнение оператора цикла while будет прекращено, и программа закончит работу. В противном случае управление будет передано в блок 9-10 для анализа ошибки, возвращённой в программу клиентским терминалом.

В начале блока 9-10 вычисляется код ошибки. В дальнейшем, в зависимости от кода ошибки, выполняется либо выход из программы либо повторное исполнение торговой операции. В первом операторе switch обрабатываются ошибки, которые по смыслу являются преодолимыми, т.е, если можно считать, что при выполнении торговой операции возникли временные затруднения. Для каждой из таких ошибок выполняются необходимые действия, после чего текущая итерация прерывается, и исполнение цикла while начинается снова. (Обратите внимание, в данном примере при обработке преодолимых ошибок используется оператор switch, выход из которого осуществляется в результате исполнения оператора continue, который сам по себе не предназначен для передачи управления за пределы switch. Такая конструкция оказывается рабочей только благодаря тому, что оператор switch является содержимым внешнего оператора цикла while, а оператор continue прерывает текущую итерацию, передавая управление в заголовок while).

Если код ошибки не обрабатывается в первом операторе switch, то эта ошибка считается непреодолимой. В этом случае управление передаётся второму оператору switch, смысл исполнения которого сводится к информированию пользователя о возникновении той или иной критической ошибки. В дальнейшем исполняется оператор break, прерывающий исполнение цикла while. Выход из цикла while по любой причине приводит к передаче управления в блок 9-10, в котором выдаётся сообщение о завершении работы программы. Оператор return прекращает исполнение специальной функции start() и программа завершает работу.

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


Рис. 92. Сообщения, полученные в результате успешного исполнения скрипта closeorder.mq4.

В результате закрытия одного из ордеров в окне финансового инструмента Eur/Usd осталось два ордера.


Рис. 93. Исполнение скрипта closeorder.mq4 привело к закрытию одного из ордеров

Закрытие ордера также нашло своё отражение и в окне Терминала:


Рис. 94. После исполнение скрипта closeorder.mq4 в Терминале отражаются два рыночных ордера.

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


Удаление отложенных ордеров


Торговые приказы для удаления отложенных ордеров формируются с помощью функции OrderDelete( ).

Функция OrderDelete()

bool OrderDelete(int ticket, color arrow_color=CLR_NONE)

Функция удаляет ранее установленный отложенный ордер. Возвращает TRUE при успешном завершении функции. Возвращает FALSE при неудачном завершении функции.

Параметры:

ticket - уникальный порядковый номер ордера.

arrow_color - цвет стрелки на графике. Если параметр отсутствует или его значение равно CLR_NONE, то стрелка на графике не отображаются.

Легко заметить, что в функции OrderDelete( ) нет указания на количество лотов в удаляемом ордере и цену закрытия.

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

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

Пример простого скрипта, предназначенного для удаления одного отложенного ордера, заявленная цена которого находится ближе к месту прикрепления скрипта, чем цены других отложенных ордеров (deleteorder.mq4).

//--------------------------------------------------------------------
// deleteorder.mq4
// Предназначен для использования в качестве примера в учебнике MQL4.
//--------------------------------------------------------------- 1 --
int start() // Спец.функция start
{
string Symb=Symbol(); // Финанс. инструмент
double Dist=1000000.0; // Предустановка
int Limit_Stop=-1; // Пока отложенных нет
double Win_Price=WindowPriceOnDropped(); // Здесь брошен скрипт
//--------------------------------------------------------------- 2 --
for(int i=1; i<=OrdersTotal(); i++) // Цикл перебора ордер
{
if (OrderSelect(i-1,SELECT_BY_POS)==true) // Если есть следующий
{ // Анализ ордеров:
//------------------------------------------------------ 3 --
if (OrderSymbol()!= Symb) continue; // Не наш фин.инструм.
int Tip=OrderType(); // Тип ордера
if (Tip<2) continue; // Рыночный ордер
//------------------------------------------------------ 4 --
double Price=OrderOpenPrice(); // Цена ордера
if (NormalizeDouble(MathAbs(Price-Win_Price),Digits)< //Выбор
NormalizeDouble(Dist,Digits)) // самого близкого орд
{
Dist=MathAbs(Price-Win_Price); // Новое значение
Limit_Stop=Tip; // Есть отложен. ордер
int Ticket=OrderTicket(); // Номер ордера
} // Конец if
} //Конец анализа ордера
} // Конец перебора орд.
//--------------------------------------------------------------- 5 --
switch(Limit_Stop) // По типу ордера
{
case 2: string Text= "BuyLimit "; // Текст для BuyLimit
break; // Выход из switch
case 3: Text= "SellLimit "; // Текст для SellLimit
break; // Выход из switch
case 4: Text= "BuyStopt "; // Текст для BuyStopt
break; // Выход из switch
case 5: Text= "SellStop "; // Текст для SellStop
break; // Выход из switch
}
//--------------------------------------------------------------- 6 --
while(true) // Цикл закрытия орд.
{
if (Limit_Stop==-1) // Если отложенных нет
{
Alert("По ",Symb," отложенных ордеров нет");
break; // Выход из цикла закр
}
//--------------------------------------------------------- 7 --
Alert("Попытка удалить ",Text," ",Ticket,". Ожидание ответа..");
bool Ans=OrderDelete(Ticket); // Удаление ордера
//--------------------------------------------------------- 8 --
if (Ans==true) // Получилось :)
{
Alert ("Удалён ордер ",Text," ",Ticket);
break; // Выход из цикла закр
}
//--------------------------------------------------------- 9 --
int Error=GetLastError(); // Не получилось :(
switch(Error) // Преодолимые ошибки
{
case 4: Alert("Торговый сервер занят. Пробуем ещё раз..");
Sleep(3000); // Простое решение
continue; // На след. итерацию
case 137:Alert("Брокер занят. Пробуем ещё раз..");
Sleep(3000); // Простое решение
continue; // На след. итерацию
case 146:Alert("Подсистема торговли занята. Пробуем ещё..");
Sleep(500); // Простое решение
continue; // На след. итерацию
}
switch(Error) // Критические ошибки
{
case 2 : Alert("Общая ошибка.");
break; // Выход из switch
case 64: Alert("Счет заблокирован.");
break; // Выход из switch
case 133:Alert("Торговля запрещена");
break; // Выход из switch
case 139:Alert("Ордер заблокирован и уже обрабатывается");
break; // Выход из switch
case 145:Alert("Модификация запрещена. ",
"Ордер слишком близок к рынку");
break; // Выход из switch
default: Alert("Возникла ошибка ",Error);//Другие варианты
}
break; // Выход из цикла закр
}
//-------------------------------------------------------------- 10 --
Alert ("Скрипт закончил работу -----------------------------");
return; // Выход из start()
}
//-------------------------------------------------------------- 11 --

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

Некоторую сложность вызывает обработка таких ошибок, как 4 и 137 (см. Коды ошибок). Например, при получении ошибки 137 программа может принять к сведению информацию о том, что "брокер занят". Но возникает естественный вопрос: когда он освободится, чтобы пользователь мог продолжить торговлю? Ошибка 137 не содержит такой информации. Поэтому вопрос о том, как правильно составить программу обработки подобных ошибок, программист должен решить сам. В простом случае можно повторить торговый приказ после некоторой паузы (в данном примере - 3 секунды). С другой стороны, в результате серии неудачных попыток удаления (или в общем случае закрытия, открытия или модификации) ордера сервер может вернуть ошибку 141 - слишком много запросов. В результате этой ошибки скрипт deleteorder.mq4 прекратит работу. В целом, подобные конфликты не являются вопросами программирования. В подобных случаях следует связаться со службой поддержки дилингового центра и выяснить причины отказа в исполнении торгового приказа.

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

В общем случае, для предотвращения ошибки 145 необходимо учитывать уровень заморозки, установленный дилинговым центром. Уровень заморозки - это значение, определяющее коридор цен, внутри которого ордер считается замороженным, т.е. его удаление может быть запрещено. Например, если отложенный ордер установлен по цене 1.2500, а уровень заморозки равен 10 пунктов, то это значит, что если цена находится в коридоре цен от 1.2490 до 1.2510 включительно, то удаление отложенного ордера запрещено. Уровень заморозки можно получить, исполнив функцию MarketInfo() с идентификатором запроса MODE_FREEZELEVEL.

Встречное закрытие рыночных ордеров


Встречный ордер - это рыночный ордер, открытый в противоположном направлении по отношению к другому рыночному ордеру по тому же финансовому инструменту.

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

Функция OrderCloseBy()

bool OrderCloseBy(int ticket, int opposite, color Color=CLR_NONE)

Функция закрывает один рыночный ордер другим рыночным ордером, открытым по тому же финансовому инструменту, но в противоположном направлении. Функция возвращает TRUE при успешном завершении функции и FALSE при неудачном завершении функции.

Параметры:

ticket - Уникальный порядковый номер закрываемого ордера.

opposite - Уникальный порядковый номер противоположного ордера.

Color - Цвет стрелки закрытия на графике. Если параметр отсутствует или его значение равно CLR_NONE, то стрелка на графике не отображается.

Размеры встречных ордеров не обязательно должны совпадать. В случае несовпадения размеров ордеров торговая операция выполняется в размере меньшего объема одного из ордеров.

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


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

Если же в этой ситуации использовать функцию встречного закрытия ордеров OrderCloseBy(), то экономический результат окажется лучше (в сравнении с предыдущим вариантом) на сумму, пропорциональную стоимости спреда одного из ордеров:


Рис. 96. Результат встречного закрытия ордеров с помощью функции OrderCloseBy().

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

В связи с экономией одного спреда при встречном закрытии ордеров необходимо дать замечания более общего характера. В сущности, торговая операция открытия ордера (например, на покупку) по смыслу является противоположной торговой операцией по отношению к операции открытия ордера в противоположном направлении (на продажу) в той же мере, как и операция закрытия ордера (на покупку). Иначе говоря, с экономической точки зрения безразлично какой из двух вариантов использовать: то ли просто закрыть рыночный ордер, то ли открыть встречный на такое же количество лотов (а впоследствии оба ордера закрыть встречно). Разница между этими вариантами может состоять лишь в различных методах расчёта денежных средств, отвлекаемых на поддержание рыночных ордеров в различных дилинговых центрах (см. рис. 85 и рис. 88).

Легко заметить также, что в функции OrderCloseBy() для встречного закрытия ордеров не предусмотрено указание цены закрытия. В этом нет необходимости, потому что прибыль и убыток от двух встречных ордеров взаимно гасят друг друга, и общий экономический результат от рыночной цены не зависит. Это правило справедливо, конечно же, только в отношении ордеров одинакового количества лотов. Например, если по одному финансовому инструменту имеются два ордера - Buy размером 1 лот и Sell размером 0.7 лота, то зависимость от цены этой торговой ситуации касается только ордера Buy в размере 0.3 лота. Совпадающая же часть ордеров 0.7 лота от цены финансового инструмента не зависит.

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

Пример простого скрипта, закрывающего все встречные ордера по финансовому инструменту (closeby.mq4).

//--------------------------------------------------------------------
// closeby.mq4
// Предназначен для использования в качестве примера в учебнике MQL4.
//--------------------------------------------------------------- 1 --
int start() // Спец.функция start
{
string Symb=Symbol(); // Финанс. инструмент
double Dist=1000000.0; // Предустановка
//--------------------------------------------------------------- 2 --
while(true) // Цикл обработки..
{ // ..встречных ордеров
double Hedg_Buy = -1.0; // Макс. стоимость Buy
double Hedg_Sell= -1.0; // Макс.стоимость Sell
for(int i=1; i<=OrdersTotal(); i++) // Цикл перебора ордер
{
if(OrderSelect(i-1,SELECT_BY_POS)==true)// Если есть следующий
{ // Анализ ордеров:
//--------------------------------------------------- 3 --
if (OrderSymbol()!= Symb) continue; // Не наш фин.инструм.
int Tip=OrderType(); // Тип ордера
if (Tip>1) continue; // Отложенный ордер
//--------------------------------------------------- 4 --
switch(Tip) // По типу ордера
{
case 0: // Ордер Buy
if (OrderLots()>Hedg_Buy)
{
Hedg_Buy=OrderLots(); // Выбираем макс.стоим
int Ticket_Buy=OrderTicket();//Номер ордера
}
break; // Из switch
case 1: // Ордер Sell
if (OrderLots()>Hedg_Sell)
{
Hedg_Sell=OrderLots(); // Выбираем макс.стоим
int Ticket_Sell=OrderTicket();//Номер ордера
}
} //Конец switch
} //Конец анализа ордера
} //Конец перебора орд.
//--------------------------------------------------------- 5 --
if (Hedg_Buy<0 || Hedg_Sell<0) // Если нет ордера..
{ // ..какого-то типа
Alert("Все встречные ордера закрыты :)");// Сообщение
return; // Выход из start()
}
//--------------------------------------------------------- 6 --
while(true) // Цикл закрытия
{
//------------------------------------------------------ 7 --
Alert("Попытка встречного закрытия. Ожидание ответа..");
bool Ans=OrderCloseBy(Ticket_Buy,Ticket_Sell);// Закрытие
//------------------------------------------------------ 8 --
if (Ans==true) // Получилось :)
{
Alert ("Выполнено встречное закрытие.");
break; // Выход из цикла закр
}
//------------------------------------------------------ 9 --
int Error=GetLastError(); // Не получилось :(
switch(Error) // Преодолимые ошибки
{
case 4: Alert("Торговый сервер занят. Пробуем ещё раз..");
Sleep(3000); // Простое решение
continue; // На след. итерацию
case 137:Alert("Брокер занят. Пробуем ещё раз..");
Sleep(3000); // Простое решение
continue; // На след. итерацию
case 146:Alert("Подсистема торговли занята. Пробуем ещё..");
Sleep(500); // Простое решение
continue; // На след. итерацию
}
switch(Error) // Критические ошибки
{
case 2 : Alert("Общая ошибка.");
break; // Выход из switch
case 64: Alert("Счет заблокирован.");
break; // Выход из switch
case 133:Alert("Торговля запрещена");
break; // Выход из switch
case 139:Alert("Ордер заблокирован и уже обрабатывается");
break; // Выход из switch
case 145:Alert("Модификация запрещена. ",
"Ордер слишком близок к рынку");
break; // Выход из switch
default: Alert("Возникла ошибка ",Error);//Другие варианты
}
Alert ("Скрипт закончил работу --------------------------");
return; // Выход из start()
}
} // Конец цикла обработ
//-------------------------------------------------------------- 10 --
} // Конец start()
//--------------------------------------------------------------------

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


Рис. 97. Рыночные ордера, открытые по одному финансовому инструменту.

Для того чтобы встречно закрыть имеющиеся ордера, необходимо для начала задаться критериями отбора ордеров. Таким критерием в данном алгоритме является размер ордера - сначала закрываются ордера большего объема, а затем меньшего. После встречного закрытия ордеров различного объема остаются ордера на разницу разутых ордеров. Например, в результате встречного закрытия ордера Buy в размере 1 лот и ордера Sell в размере 0.8 лотов останется ордер Buy объемом 0.2 лота. Поэтому после каждого успешного закрытия необходимо снова обратиться к (теперь уже обновлённому) набору ордеров, с тем, чтобы в этом наборе ордеров выявить два встречных ордера максимального объема.

Указанные вычисления реализованы в (условно) бесконечном цикле while в блоках 2-10. В начале этого цикла на каждой итерации делается предположение, что ордеров какого-либо типа уже не осталось. Для этого переменным Hedg_Buy и Hedg_Sell присваивается значение -1. Алгоритм блока обработки ордеров в целом сохранён (см. код closeby.mq4). В цикле перебора ордеров for, а именно в блоке 3-4, так же, как и в предыдущих программах, производится отсев "не наших" ордеров, в данном случае - открытых по другому финансовому инструменту, а также отложенных ордеров.

В блоке 4-5 для каждого из ордеров, прошедших проверку в блоке 3-4, вычисляется его объем. Если в процессе вычислений оказывается, что текущий обрабатываемый ордер имеет больший размер из всех обрабатываемых, то запоминается его тикет. Это значит, что на данной стадии вычислений ордер с этим номером является претендентом на участие в операции встречного закрытия. К моменту окончания последней итерации цикла for известны номера ордеров с максимальным количеством лотов, открытых в разных направлениях. Эти ордера и являются выбранными. Если же к этому моменту ордеров какого-либо типа уже нет, то в блоке 5-6 осуществляется выход из программы.

Блок 6-10 представляет обработку ошибок, он полностью аналогичен рассмотренным ранее (в этом и предыдущем параграфах). Формирование торгового приказа для встречного закрытия ордеров осуществляется в блоке 7-8 с помощью функции OrderCloseBy(). В случае неудачи, в зависимости от кода ошибки, управление передаётся либо на повторение попытки исполнения торговой операции (для тех же тикетов) либо оператору return, в результате чего скрипт заканчивает работу.

Если торговая операция прошла успешно, то выполняется выход из блока обработки ошибок, и заканчивается текущая итерация самого внешнего цикла обработки while. На очередной итерации этого цикла все вычисления повторяются: выполняется опрос имеющихся ордеров, отбор рыночных ордеров, выбор по одному тикету для каждого из типов ордеров, формирование торгового приказа на встречное закрытие и последующий анализ ошибок. Указанный цикл выполняется до тех пор, пока в терминале не закончатся ордера какого-либо типа (или - в частном случае - обоих типов). Это событие будет вычислено в блоке 5-6, в результате чего программа закончит работу.

При исполнении скрипта closeby.mq4 для закрытия рыночных ордеров, показанных на рис. 97, были получены такие сообщения:


Рис. 98. Сообщения, полученные при исполнении скрипта closeby.mq4.

На закладке История Счёта в Терминале видно, что некоторые ордера закрыты с нулевой прибылью. Это и есть экономия, получаемая при встречном закрытии ордеров. Сравните экономические показатели на рис. 97 и рис. 99:


Рис. 99. История Счёта после исполнения скрипта closeby.mq4.

На закладке Журнал в Терминале можно проследить историю закрытия ордеров (последние события сверху):


Рис. 100. События, произошедшие при исполнении скрипта closeby.mq4.

При исполнении скрипта в соответствии с алгоритмом закрываются ордера максимального размера, имеющиеся на текущий момент. Несмотря на то, что ордера были открыты в случайной последовательности (рис. 97), первыми в закрытии участвовали ордера Buy 778594 и Sell 778595, соответственно размером 1 лот и 0.8 лота (нижние строки на рис. 100). Объемы этих ордеров разные, поэтому в результате встречного закрытия образовался новый ордер Buy 778597 остаточного размера 0.2 лота. В дальнейшем программа выбрала для закрытия ордера Buy 778592 и Sell 778593, по 0.5 лотов каждый. Эти ордера были закрыты без остатка.

К моменту начала третьей итерации во внешнем цикле обработки в окне финансового инструмента остались два ордера: исходный ордер Sell 778596 стоимостью 0.3 лота и тот, который образовался в процессе исполнения скрипта - Buy 778597 стоимостью 0.2 лота. В верхних строках рис. 100 видно, что эти ордера тоже были закрыты встречно. Объемы этих ордеров разные, поэтому в результате исполнения последней торговой операции в окне финансового инструмента остался один рыночный ордер размером 0.1 лота (обратите внимание на экономические показатели):


Рис. 101. Ордер Sell остаточной стоимостью 0.1 лота.

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