Mql4 как найти ордер

В свете последних изменений в свежих билдах, ордера расположены строго по возрастающей (проверено). Поэтому если исходить из условий вопроса, то Николай прав, и последний ордер будет иметь номер OrdersTotal()-1 или OrdersHistoryTotal()-1;

Ессно если нужен фильтр по магику и символу то все немного усложняется, но не на много :

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int GetLastTicket(int typ=-1,int magic=-1,string sy=""){int i;
   for(i=OrdersHistoryTotal()-1;i>=0;i--){
      if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)){
         if(sy  !=""){if(OrderSymbol()!=sy){continue;}}
         if(magic>=0){if(OrderMagicNumber()!=magic){continue;}}
         if(typ  >=0){if(OrderType()!=typ){continue;}}
         return(OrderTicket());
      }
   }
   return(0);
}
//+------------------------------------------------------------------+

2
Roll : постарайтесь не уплотнять так код, потом самому же легче будет разобраться.

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

Предположим, что вы программируете советника, который размещает ордер на покупку, если цена находится выше скользящей средней с периодом 25. Если цена будет выше МА 25, советник разместит ордер. Затем он будет перезапущен на следующем тике, и если цена опять будет выше МА 25, он разместит еще один ордер, и еще один на следующем тике и т. д. Это будет происходить пока маржа на вашем торговом счете не исчерпает себя. Это очень распространенная и глупая ошибка, с которой сталкиваются многие начинающие программисты, когда они начинают создавать свой первый советник. Решение этой проблемы очень простое: мы можем проверить, есть ли уже открытые ордера для данного торгового инструмента. Далее создать условие не открывать новые ордера, если результаты проверки положительные.

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

Вот синтаксис функции OrderSelect():

bool  OrderSelect(
   int     index,            // индекс или тикет ордера
   int     select,           // флаг способа выбора
   int     pool,             // источник данных для выбора
   );
  • index — порядковый номер или тикет ордера.
  • select — константа, которая указывает, является ли параметр index порядковым номером или тикетом ордера. Соответственно может принимать два значения: SELECT_BY_POS — значением параметра index является порядковый номер ордера, SELECT_BY_TICKET — Значением параметра index является тикет ордера.
  • pool — необязательная константа, которая обозначает тип ордеров: отложенные, открытые или закрытые. Может принимать два значения: MODE_TRADES — значение по умолчанию для текущих открытых ордеров, MODE_HISTORY — для закрытых ордеров.

Если функция OrderSelect() успешно выберет ордер, возвращаемое значение будет true, в противном случае возвращаемое значение будет false.

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

  • OrderSymbol() — торговый инструмент выбранного ордера.
  • OrdersTotal() — количество открытых и отложенных ордеров.
  • OrderType() — тип ордера: ордер на покупку или продажу, рыночный, стоп или лимитный ордер.
  • OrderOpenPrice() — цена открытия.
  • OrderLots() — размер лота.
  • OrderStopLoss() — цена стоп-лосс.
  • OrderTakeProfit() — цена тейк-профита.
  • OrderTicket() — номер тикета.
  • OrderMagicNumber() — магический номер.
  • OrderComment() — комментарий, который был размещен вместе с ордером.
  • OrderOpenTime() — время открытия.
  • OrderProfit() — возвращает значение чистой прибыли.

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

//Мы объявляем функцию CheckOpenOrders типа boolean и возвращаем true, если для данного торгового инструмента есть открытые ордера, и false, если их нет.
bool CheckOpenOrders(){
   //Далее нам нужно проверить, есть ли открытые или отложенные ордера. Нам возвращается общее количество рыночных и отложенных ордеров. Далее мы перебираем все ордера и проверяем, совпадают ли они с текущим символом, на котором работает советник.
   for(int i=0; i<OrdersTotal(); i++) {
      //Выбираем порядок выбора индекса ордера.
      OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
      //Если пара ордеров OrderSymbol() равна символу, на котором работает советник, возвращаем true.
      if(OrderSymbol() == Symbol()) return(true);
   }
   //Если цикл завершается, это означает, что для данной пары не было открытых ордеров.
   return(false);
}
 
 
int OnInit()
{
   return(INIT_SUCCEEDED);
}
 
void OnDeinit(const int reason)
{
 
}
 
void OnTick()
{
   Alert("Есть ли открытые ордера для данного символа? ",CheckOpenOrders());
}

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

  • Проверять магический номер ордера.
  • Проверять тип ордера (ордер на покупку или на продажу).
  • Проверять, убыточный ордер или прибыльный.

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

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

Торговые приказы для закрытия рыночных ордеров формируются с помощью функции 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).





int start()
{
string Symb=Symbol();
double Dist=1000000.0;
int Real_Order=-1;
double Win_Price=WindowPriceOnDropped();

for(int i=1; i<=OrdersTotal(); i++)
{
if (OrderSelect(i-1,SELECT_BY_POS)==true)
{

if (OrderSymbol()!= Symb) continue;
int Tip=OrderType();
if (Tip>1) continue;

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();
}

}
}

while(true)
{
if (Real_Order==-1)
{
Alert("По ",Symb," рыночных ордеров нет");
break;
}

switch(Real_Order)
{
case 0: double Price_Cls=Bid;
string Text="Buy ";
break;
case 1: Price_Cls=Ask;
Text="Sell ";
}
Alert("Попытка закрыть ",Text," ",Ticket,". Ожидание ответа..");
bool Ans=OrderClose(Ticket,Lot,Price_Cls,2);

if (Ans==true)
{
Alert ("Закрыт ордер ",Text," ",Ticket);
break;
}

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;
case 5 : Alert("Старая версия клиентского терминала.");
break;
case 64: Alert("Счет заблокирован.");
break;
case 133:Alert("Торговля запрещена");
break;
default: Alert("Возникла ошибка ",Error);
}
break;
}

Alert ("Скрипт закончил работу -----------------------------");
return;
}

Весь код программы 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).





int start()
{
string Symb=Symbol();
double Dist=1000000.0;
int Limit_Stop=-1;
double Win_Price=WindowPriceOnDropped();

for(int i=1; i<=OrdersTotal(); i++)
{
if (OrderSelect(i-1,SELECT_BY_POS)==true)
{

if (OrderSymbol()!= Symb) continue;
int Tip=OrderType();
if (Tip<2) continue;

double Price=OrderOpenPrice();
if (NormalizeDouble(MathAbs(Price-Win_Price),Digits)<
NormalizeDouble(Dist,Digits))
{
Dist=MathAbs(Price-Win_Price);
Limit_Stop=Tip;
int Ticket=OrderTicket();
}
}
}

switch(Limit_Stop)
{
case 2: string Text= "BuyLimit ";
break;
case 3: Text= "SellLimit ";
break;
case 4: Text= "BuyStopt ";
break;
case 5: Text= "SellStop ";
break;
}

while(true)
{
if (Limit_Stop==-1)
{
Alert("По ",Symb," отложенных ордеров нет");
break;
}

Alert("Попытка удалить ",Text," ",Ticket,". Ожидание ответа..");
bool Ans=OrderDelete(Ticket);

if (Ans==true)
{
Alert ("Удалён ордер ",Text," ",Ticket);
break;
}

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;
case 64: Alert("Счет заблокирован.");
break;
case 133:Alert("Торговля запрещена");
break;
case 139:Alert("Ордер заблокирован и уже обрабатывается");
break;
case 145:Alert("Модификация запрещена. ",
"Ордер слишком близок к рынку");
break;
default: Alert("Возникла ошибка ",Error);
}
break;
}

Alert ("Скрипт закончил работу -----------------------------");
return;
}

Блок обработки ошибок также претерпел некоторые изменения. Если при закрытии рыночных
ордеров необходимо учитывать возможность возникновения ошибок, связанных с изменением
цен (ошибки 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).




int start()
{
string Symb=Symbol();
double Dist=1000000.0;

while(true)
{
double Hedg_Buy = -1.0;
double Hedg_Sell= -1.0;
for(int i=1; i<=OrdersTotal(); i++)
{
if(OrderSelect(i-1,SELECT_BY_POS)==true)
{

if (OrderSymbol()!= Symb) continue;
int Tip=OrderType();
if (Tip>1) continue;

switch(Tip)
{
case 0:
if (OrderLots()>Hedg_Buy)
{
Hedg_Buy=OrderLots();
int Ticket_Buy=OrderTicket();
}
break;
case 1:
if (OrderLots()>Hedg_Sell)
{
Hedg_Sell=OrderLots();
int Ticket_Sell=OrderTicket();
}
}
}
}

if (Hedg_Buy<0 || Hedg_Sell<0)
{
Alert("Все встречные ордера закрыты :)");
return;
}

while(true)
{

Alert("Попытка встречного закрытия. Ожидание ответа..");
bool Ans=OrderCloseBy(Ticket_Buy,Ticket_Sell);

if (Ans==true)
{
Alert ("Выполнено встречное закрытие.");
break;
}

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;
case 64: Alert("Счет заблокирован.");
break;
case 133:Alert("Торговля запрещена");
break;
case 139:Alert("Ордер заблокирован и уже обрабатывается");
break;
case 145:Alert("Модификация запрещена. ",
"Ордер слишком близок к рынку");
break;
default: Alert("Возникла ошибка ",Error);
}
Alert ("Скрипт закончил работу --------------------------");
return;
}
}

}

Алгоритм представленного скрипта несколько отличается от последних рассмотренных.
Это отличие состоит в том, что для успешного закрытия нескольких ордеров (количество
закрываемых ордеров не ограничено) необходимо многократно исполнить один и тот
же код. Скрипт опробовался на случайном наборе рыночных ордеров. На рис. 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 удобно использовать при ручной торговле, особенно в случаях, когда в окне финансового
инструмента имеется множество разнонаправленных рыночных ордеров.

Рассмотрим пользовательскую функцию для определения факта существования установленного ордера. Прототип функции я когда-то нашел в интернете и с годами произвел удобные модификации для пользования.
Данная функция должна возвращать флаг существования ордера, т.е. есть он или нет. Больше нас в данном случае не интересует. Т.е. функция по смыслу должна быть логической. Назовем её f_ExistOrders.

Параметрами функции назначим:
sy — наименование торгового инструмента и предусмотрим случай «любого» инструмента;
op — тип ордера;
mn — мэджик ордера (т.е. для возможности работать с конретными экспертами);
ot — время открытия ордера.

Общее количество ордеров, как известно, опделяется функцией OrdersTotal(). Наша функция должна сразу же произвести анализ на наличие ордеров и если его нет, то сразу же вернуть «false».
Далее, мы должны в функции организовать цикл перебора имеющихся ордеров, т.е. пробежать от нуля до количества ордеров минус один.

Код: выделить все
int i,
k = OrdersTotal(),
ty;
if(k == 0) return(false); // нет ордеров вообще
if(sy == "0") sy = Symbol();
for(i = 0; i < k; i++)
{
...
}

Далее выбираем только ордера с идентификатором от 2 до 5, т.е. исключить открытые позиции, т.к. мы работаем именно с ордерами, а не с позициями.

Код: выделить все
ty = OrderType();
if(ty > 1 && ty < 6)

Далее мы должны выбрать только ордер в соответствие с заданными условиями (тип ордера, мэджик, время установки и, зачастую, конкретный торговый инструмент):

Код: выделить все
if((OrderSymbol() == sy || sy == "") && (op < 0 || ty == op))
{
   if(mn < 0 || OrderMagicNumber() == mn)
   {
       ...
   }
}

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

Код: выделить все
if(ot <= OrderOpenTime()) return(true);

Полный код функции f_ExistOrders расположен ниже:

Код: выделить все
bool f_ExistOrders(string sy = "", int op = -1, int mn = -1, datetime ot = 0)
{
/*
   Описание : Возвращает флаг существования ордеров.                         
   28/10/2015
   Параметры:                                                               
   sy - наименование инструмента   (""   - любой символ,                   
                                    "0" - текущий символ)                 
   op - операция                   (-1   - любой ордер)                   
   mn - MagicNumber                (-1   - любой магик)                   
   ot - время открытия             ( 0   - любое время установки)         
*/
  int i,
  k = OrdersTotal(),
  ty;

    if(k == 0) return(false); // нет ордеров вообще
  if(sy == "0") sy = Symbol();
  for(i = 0; i < k; i++)
  {
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    {
      ty = OrderType();
      if(ty > 1 && ty < 6)
      {
        if((OrderSymbol() == sy || sy == "") && (op < 0 || ty == op))
        {
          if(mn < 0 || OrderMagicNumber() == mn)
          {
            if(ot <= OrderOpenTime()) return(true);
          }
        }
      }
    }
  }
return(false);
}

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

Доброго времени суток. 

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

Ну что ж, продолжим изучение языка программирования MQL4.

Два прошлых урока были посвящены анализу открытых пользователем ордеров. Данная статья поможет вам в анализе уже закрытых ордеров и их характеристик. Урок будет разбит на два раздела: анализ закрытых ордеров и построение графических элементов на экране.

Анализ и сохранение данных по ордерам

Начнем работу с создания нового индикатора. Внешних параметров у него не будет, вы сможете их добавить сами после прохождения урока при необходимости. Свойство программы #property должно отображать индикатор на графике (indicator_chart_window), а не в отдельном окне. Функции обработки событий OnInit и OnDeinit пока оставляем пустыми. Переходим к функции обработки событий OnCalculate.

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

if(OrdersHistoryTotal() <= 1) { Print(«No closed orders»); return(false); }

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

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

//+——————————————————————+

//| 2.4 Indicator of analysis closed orders |

//| Copyright (c) DaVinci FX Group |

//| https://www.davinci-fx.com/ |

//+——————————————————————+

#property copyright «Copyright (c) DaVinci FX Group»

#property link «https://www.davinci-fx.com/»

#property version «1.00»

#property strict

#property indicator_chart_window

int TotalHistoryOrders = 0;

Далее прописываем условие в OnCalculate

if(OrdersHistoryTotal() != TotalHistoryOrders) {

TotalHistoryOrders = OrdersHistoryTotal();

//…

}

Идет сравнение сохраненного значения ордеров в память с фактическим количеством ордеров в истории. Если оно изменяется — мы перезаписываем значение переменной TotalHistoryOrders и выполняем все дальнейшие действия этого урока в фигурных скобках данного условия.

Для построения таблицы по всем валютным парам истории мы будем собирать данные по: символу, количеству ордеров отдельно для покупок и продаж, суммарную прибыль и торговый лот, а также количество ордеров, которые были отдельно прибыльными или убыточными.

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

Со структурой даты и времени мы познакомились в этом уроке. Сейчас давайте создадим свою пользовательскую структуру. Она объявляется вне функций, как правило, в самом начале программного кода. Разместим ее выше переменной TotalHistoryOrders.

struct TradeEnv {

string symbol;

int countB, countS;

double profitB, profitS;

double LotB, LotS;

int win, loss;

};

TradeEnv tradeenv[];

Слово struct обозначает объявление самой структуры, TradeEnv — ее имя. В фигурных скобках идет объявление переменных разного типа, которые нам пригодятся:

  • symbol — наименование валютной пары, которые мы найдем в истории.
  • countB, countS — переменные целого типа для подсчета количества ордеров отдельно для покупок и продаж.
  • profitB, profitS — суммарный прибыль в валюте депозита отдельно для покупок и продаж.
  • LotB, LotS — торговый лот всех ордеров отдельно для покупок и продаж.
  • win, loss — количество прибыльных и убыточных сделок.

Всю эту информацию мы будем хранить в массиве, потому что у нас, скорее всего в торговле участвует не одна, а несколько валютных пар. Поэтому назначаем динамический массив для этой структуры — tradeenv[], количество элементов которого будет изменяться по мере нахождения в истории новых символов. Вывести отдельно элемент структуры можно через точку, например: tradeenv[0].LotB будет содержать в себе информацию для первого элемента массива под номером ноль о торговом лоте для покупок.

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

if(OrdersHistoryTotal() != TotalHistoryOrders) {

TotalHistoryOrders = OrdersHistoryTotal();

ArrayResize(tradeenv,1);

ZeroMemory(tradeenv);

}

Мы добавили две строчки. Функция ArrayResize задает размер массиву tradeenv, это обязательное правило для динамических массивов. Так как мы не знаем, сколько в нем будет элементов, то размер задаем равный единице.

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

Помимо подсчета значений для разных валютных пар мы будем также сохранять общее значение прибыли, ордеров и лота. Поэтому ниже вручную задаем самому первому элементу массива (нулевому) наименование символа «TOTAL».

tradeenv[0].symbol = «TOTAL»;

То есть самый первый элемент массива у нас всегда будет хранить суммарное значение всех ордеров. Точка и приписка symbol символизирует о том, что мы использовали string элемент структуры для сохранения наименования валютной пары. Другие наименования символов будут сохраняться в последующих элементах массива: tradeenv[1].symbol, tradeenv[2].symbol и т.д.

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

Теперь приступаем непосредственно к анализу. Нам нужно запустить цикл for для всех ордеров, которые хранятся в истории.

for(int i=0;i<=TotalHistoryOrders1;i++) {

if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) continue;

if(OrderType() > 1) continue;

}

Останавливаться подробно на цикле for не будет, его работы мы подробно разбирали в соответствующем уроке. Анализ идет с 0 позиции по значение количества ордеров в памяти, минус один, т.к. отсчет цикла идет с нуля, а ордера в истории учитываются с единицы.

Как и при анализе открытых ордеров, закрытый ордер вначале нужно выделить (OrderSelect). Выделение происходит по номеру позиции SELECT_BY_POS, источником данных тут является история (MODE_HISTORY). Если выделить не получилось — все дальнейшие действия бессмысленны, поэтому активируется оператор продолжения continue.

Окей, ордер был выделен, теперь нужно отсеять лишние типы ордеров. Нас интересуют только рыночные ордера, которые были закрыты с прибылью или убытком. Соответственно, если тип ордера OrderType больше единицы, значит это либо удаленный отложенный ордер, либо операция с балансом, что нас никак не интересует. Если же это так, активируем оператор continue.

Так как мы несколько раз будет использовать значение прибыли/убытка анализируемого ордера, то логичнее его сохранить в отдельную переменную

double OrdProfit = OrderProfit() + OrderSwap() + OrderCommission();

Как я уже упоминал, помимо торговой функции OrderProfit(), которая хранит в себе непосредственно информацию о прибыли или убытке ордера в валюте депозита, на счете есть еще две функции, связанные с деньгами: OrderSwap() — хранит в себе уплоченный своп при переносе позиции в ролловер и OrderCommission() — комиссия, которую мы оплатили брокеру при открытии сделки. Если вас эти дополнительные комиссии не интересуют, можете их не прибавлять к значению прибыли OrdProfit.

Теперь приступим к самому сложному блоку данного урока в плане разбора его кода — анализу символа выбранного ордера и сравнению его с данными из памяти массива.

bool found=false;

for(int j=ArraySize(tradeenv)-1; j>=1; j—) {

if(tradeenv[j].symbol==OrderSymbol()) {

symbolnumber = j;

found=true;

break;

}

}

if(!found){

symbolnumber = ArraySize(tradeenv);

ArrayResize(tradeenv,symbolnumber+1);

tradeenv[symbolnumber].symbol = OrderSymbol();

}

Так как в истории хранится много ордеров по одному и тому же символу, мы должны сохранять все их данные в общей ячейке массива tradeenv. Т.е. если по EURUSD у нас 20 ордеров на покупку, то нас интересует только суммарный лот и профит этих ордеров. Для этой пары должна быть выделена отдельная ячейка, для NZDCAD другая и т.д. Для этого нам необходимо внутри цикла по ордерам создать еще один цикл для анализа всех элементов имеющегося массива. В нем проверяется условие, если символ массива tradeenv[j].symbol совпадает с символом текущего ордера, тогда мы нашли искомый номер массива и сохраняем его порядковый номер (j) в переменную symbolnumber, которую необходимо предварительно объявить в самом начале функции OnCalculate до начала анализа.

Далее для переменной bool found выставляется значение true и цикл прерывается. Мы нашли номер ячейки массива с текущим символом и будем его использовать в дальнейшем для сохранения данных по текущему ордеру. Но что, если данного символа еще нет в памяти массива, а анализ закрытых ордеров только начался?

Тогда логическое значение переменной found остается равным false и в работу включается условие if(!found). В фигурных скобках условия прописывается порядковый номер symbolnumber для массива tradeenv. Он будет равен размеру текущего массива ArraySize(tradeenv). То есть если размер массива был равен 1 и в нем был задействована только нулевая ячейка, то теперь текущему символу передается первая ячейка, а размер динамического массива нужно увеличить на единицу ArrayResize(tradeenv,symbolnumber+1). Массив стал больше на один элемент — новый символ.

Осталось сохранить обнаруженный символ как элемент структуры tradeenv[symbolnumber].symbol. Соответственно при следующей итерации цикла for наш массив будет уже больше и поиск нового символа будет происходить в уже увеличенном массиве.

Если более простым языком, то при начале анализе ордеров в истории массив tradeenv пока что пустой. В момент анализа первого выделенного ордера мы узнаем, что его символ USDJPY. Так как в массиве еще нет данных, то мы сохраняем этот символ в 1 ячейку массива и увеличиваем его размер. На следующей итерации у нас появляется пара AUDNZD. Ее не было до этого в массиве, поэтому она сохраняется во 2 ячейку и размер массива уже становится равным трем (включая нулевую ячейку). Следующая итерация — пара USDJPY, она уже есть в памяти массива, поэтому его размер не изменяется, мы просто запоминаем, что пара уже есть в 1 ячейке и с ней в дальнейшем продолжим работать во время данной итерации.

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

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

if(OrderType() == OP_BUY) {

tradeenv[0].countB++;

tradeenv[0].LotB += OrderLots();

tradeenv[0].profitB += OrdProfit;

tradeenv[symbolnumber].countB++;

tradeenv[symbolnumber].LotB += OrderLots();

tradeenv[symbolnumber].profitB += OrdProfit;

}

Ранее я говорил, что суммарную информацию для всех валютных пар мы будем сохранять в самой первой ячейке массива, т.е. нулевой и имя символа будет «TOTAL». Соответственно для tradeenv[0] массива мы отдельно произведем сохранение в:

  • countB — счетчик количества ордеров на покупку. Каждую итерацию к элементу будет прибавляться единица, когда ордер будет на покупку.
  • LotB — суммарный торговый лот для всех закрытых ордеров. К нему прибавляем размер лота текущего ордера OrderLots().
  • profitB — прибыль всех ордеров на покупку. Прибавляем к этой переменной ранее объявленную переменную OrdProfit.

Этими тремя строками мы произвели действия для нулевой ячейки массива tradeenv, сохранив данные для раздела ИТОГО. Для того, чтобы сделать такое вычисление только для текущей валютной пары, нужно использовать уже определенный нами номер ячейки symbolnumber. Те же три строчки кода повторяются и для нее.

С продажами все аналогично:

else if(OrderType() == OP_SELL) {

tradeenv[0].countS++;

tradeenv[0].LotS += OrderLots();

tradeenv[0].profitS += OrdProfit;

tradeenv[symbolnumber].countS++;

tradeenv[symbolnumber].LotS += OrderLots();

tradeenv[symbolnumber].profitS += OrdProfit;

}

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

if(OrdProfit >= 0) {

tradeenv[0].win++;

tradeenv[symbolnumber].win++;

}

else {

tradeenv[0].loss++;

tradeenv[symbolnumber].loss++;

}

Если переменная OrdProfit, содержащая в себе значение прибыли/убытка больше или равна нулю, то мы считаем, что данный ордер был «выигрышным» и прибавляем единицу к счетчику win как для total значения всех анализируемых ордеров, так и для текущего символа. Если же нет, то прибавление идет к счетчику «проигрышных» ордеров loss.

На этом первая часть кода закончена. Мы проанализировали все закрытые ордера в истории, которые не являются отложенными и операциями баланса. Выписали в структуру массива tradeenv количество ордеров, торговый лот, суммарную прибыль/убыток, а также отдельно «выигрышное» и «убыточное» количество ордеров как по каждому символу отдельно, так и суммарное значение для всего торгового счета в терминале МТ4.

Ваша задача сейчас правильно перенести полученный код в редактор Meta Editor и скомпилировать код без ошибок.

Построение графических элементов на экране

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

Для этого создадим пользовательскую функцию ObjectsDelete и разместим ее сразу после цикла for для ордеров истории, после закрытия фигурной скобки.

Само тело функции добавляем после функции обработки событий OnCalculate. Она будет иметь вид:

void ObjectsDelete(string search) { //Удаление объектов с графика, содержащих определенное название

for(int i = ObjectsTotal() 1; i >= 0; i—) {

if(StringFind(ObjectName(i), search) != 1) ObjectDelete(ObjectName(i));

}

}

Соответственно, если объект будет содержать в себе название DV (сокращенно от DaVinci), значит этот объект был создан нашим индикатором и его нужно удалить. Данная функция выполняет проход по всем графическим элементам на текущем графике через цикл for. Далее с помощью строковой функции StringFind в имени выделенного объекта ObjectName(i) ищется искомое слово search (в нашем случае это «DV«) и если оно найдено — объект удаляется благодаря функции ObjectDelete. и так для всех элементов цикла.

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

void OnDeinit(const int reason)

{

ObjectsDelete(«DV|»);

}

Идем дальше. Нам нужно создать текстовые лейблы типа OBJ_LABEL, а также задний фон для них. Так как объекты накладываются друг на друга, то вначале мы должны сделать фон, а уже поверх него расположить текст.

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

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

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

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

Так как у нас будет использоваться пять прямоугольников, то мы пять раз вызываем функцию RectLabelCreate, но предварительно мы зададим изначальные координаты X и Y на графике для построения объектов.

int Y0 = 30, Y_ = 12;

int X0 = 40, X_ = 40;

//Создаем прямоугольные метки.

RectLabelCreate(0,«DV|Rect1»,0,5,16,910,30,clrSlateGray,1,0);

RectLabelCreate(0,«DV|Rect2»,0,5,46,75,Y0*(ArraySize(tradeenv)+1),clrSlateGray,1,0);

RectLabelCreate(0,«DV|Rect3»,0,80,46,225,Y0*(ArraySize(tradeenv)+1),clrSlateGray,1,0);

RectLabelCreate(0,«DV|Rect4»,0,305,46,225,Y0*(ArraySize(tradeenv)+1),clrSlateGray,1,0);

RectLabelCreate(0,«DV|Rect5»,0,530,46,385,Y0*(ArraySize(tradeenv)+1),clrSlateGray,1,0);

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

Переменные Y0, Y_, X0 и X_ это начальные координаты, отступ от левой верхней границы графика. Они также подбираются вручную.

Вот что из себя представляет функция RectLabelCreate:

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

bool RectLabelCreate(const long chart_ID = 0, // ID графика

const string name = «RectLabel», // имя метки

const int sub_window = 0, // номер подокна

const int x = 0, // координата по оси X

const int y = 0, // координата по оси Y

const int width = 50, // ширина

const int height = 18, // высота

const color back_clr = C‘236,233,216’, // цвет фона

const ENUM_BORDER_TYPE border = BORDER_RAISED, // тип границы

const ENUM_BASE_CORNER _corner = CORNER_LEFT_UPPER, // угол графика для привязки

const color clr = clrRed, // цвет плоской границы (Flat)

const ENUM_LINE_STYLE style = STYLE_SOLID, // стиль плоской границы

const int line_width = 1, // толщина плоской границы

const bool back = false, // на заднем плане

const bool selection = false, // выделить для перемещений

const bool hidden = false, // скрыт в списке объектов

const long z_order = 0) // приоритет на нажатие мышью

{

ResetLastError(); //— сбросим значение ошибки

if(ObjectFind(chart_ID,name) == 1) {

if(!ObjectCreate(chart_ID,name,OBJ_RECTANGLE_LABEL,sub_window,0,0)) {

Print(__FUNCTION__,«: не удалось создать прямоугольную метку! « + IntegerToString(GetLastError()));

return(false);

}

}

//— установим координаты метки

ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x);

ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y);

//— установим размеры метки

ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,width);

ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,height);

//— установим цвет фона

ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,back_clr);

//— установим тип границы

ObjectSetInteger(chart_ID,name,OBJPROP_BORDER_TYPE,border);

//— установим угол графика, относительно которого будут определяться координаты точки

ObjectSetInteger(chart_ID,name,OBJPROP_CORNER,_corner);

//— установим цвет плоской рамки (в режиме Flat)

ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);

//— установим стиль линии плоской рамки

ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);

//— установим толщину плоской границы

ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,line_width);

//— отобразим на переднем (false) или заднем (true) плане

ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);

//— включим (true) или отключим (false) режим перемещения метки мышью

ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);

ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);

//— скроем (true) или отобразим (false) имя графического объекта в списке объектов

ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden);

//— установим приоритет на получение события нажатия мыши на графике

ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order);

//— успешное выполнение

return(true);

}

Тип данных функции bool, т.е. она возвращает true/false. Но нам это не важно для текущей задачи.

Весь этот код я описывать не буду, под каждым свойством объекта есть приписка из справочника, которая объясняет его действие. Если вкратце, то вначале создается объект (ObjectCreate). В нашем случае это OBJ_RECTANGLE_LABEL, т.е. прямоугольный лейбл. По условию, он создается только в том случае, если прямоугольника с таким именем еще нет на графике. Далее этому объекту задаются свойства: координаты X и Y, ширина и высота, цвет заливки, угол привязки к этому прямоугольнику, стиль рамки, ее толщина, выбор плана отображения (передний или задний), возможность выделять объект мышью, изначально выделять объект или нет, спрятать ли его имя в списке объектов и приоритет размещения на графике. Свойства для большинства других объектов аналогичны.

Соответственно из функции OnCalculate мы передаем следующие значения в эту функцию:

RectLabelCreate(0,«DV|Rect1»,0,5,16,910,30,clrSlateGray,1,0);

Где по порядку через запятую идет:

  • ID графика для построения графической фигуры. Текущий график обозначается как нулевой.
  • Наименование элемента. Оно может быть любым, я использовал «DV|Rect» с цифрами от 1 до 5 для пяти прямоугольников
  • Подокно. Нужно только, если вы используете подвальный индикатор. В нашем случае пишется ноль.
  • Координаты X и Y для начала построения прямоугольника. X=5, Y=16. Все подбирается на глаз.
  • Ширина в пикселях (910), высота в пикселях (30).
  • Цвет заливки прямоугольника. Я выбрал темно серый.
  • Тип границы обводки. Их три вида — плоский, выпуклый и вогнутый. В справочнике находятся как ENUM_BORDER_TYPE.
  • Угол привязки к графику. Мы строим в верхнем левом углу, его номер привязки 0. Для верхнего правого угла номер 1 и т.д.

Чтобы узнать заранее высоту для 2-5 прямоугольников, нужно заданный отступ Y0 умножить на количество элементов массива ArraySize(tradeenv)+1, т.к. оно уже определено и будет неизменным.

Так как все переменные пользовательской функции RectLabelCreate являются константой, то не обязательно их все передавать в нее. Те переменные, которые я не прописал, будут браться из значений, которые указаны по умолчанию. Главное передавать все последовательно, без пропусков, т.е так как это прописано в функции. Пока мы смотрим полный пример из справочника. В последствии для себя вы можете подправить эту функцию отрисовки прямоугольника так, чтобы ничего лишнего в ней не было.

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

void out_Label(string name, string text, int X, int Y, color col) {

if(ObjectFind(name) < 0) {

ObjectCreate(0,name,OBJ_LABEL,0,0,0);

ObjectSetInteger(0,name,OBJPROP_CORNER,0);

ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);

ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_CENTER);

ObjectSetInteger(0,name,OBJPROP_XDISTANCE,X);

ObjectSetInteger(0,name,OBJPROP_YDISTANCE,Y);

}

ObjectSetText(name,text,10,«Arial»,col);

}

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

Тут создается (ObjectCreate) тип графического объекта OBJ_LABEL, если он не был найден на графике. Ему по порядку задается угол привязки к графику, возможность выделения мышью, якорь привязки к самому объекту (я сделал по центру), дистанция X и Y. С помощью свойства ObjectSetText созданному объекту передается текст, размер шрифта, тип шрифта и цвет.

Попробуем на наш фон нанести первые три надписи:

out_Label(«DV|Title1», «Long», 190, Y0, clrWheat);

out_Label(«DV|Title2», «Short», 415, Y0, clrWheat);

out_Label(«DV|Title3», «Total», 710, Y0, clrWheat);

Y0 += 30;

Три лейбла будут обозначать колонки для покупок, продаж и итого. В функцию мы передаем имя объекта («DV|Title»), тест для надписи, координаты для построения и цвет.

После создания этих объектов нужно сдвинуться по оси Y ниже к следующим прямоугольникам, потому мы изменяем значение переменной для координаты Y0 на 30 пикселей.

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

Теперь нам нужно создать наименования для каждой колонки будущей таблицы. У нас будет 12 колонок. Мы можем двенадцать раз вызывать вручную функцию создания текстового лейбла out_Label, либо же создать отдельный массив, который будет хранить в себе текст этих переменных и циклом пропустим их создание, что намного проще:

string titles[12] = { «Symbol»,«Trades»,«Lots»,«Profit($)»,«Trades»,«Lots»,«Profit($)»,

«Trades»,«Lots»,«Profit($)»,«Won(%)»,«Lost(%)»};

for(int i=0;i<12;i++) {

out_Label(«DV|Title_» + IntegerToString(i), titles[i], X_, Y0, clrAliceBlue);

X_+=75;

}

Y0 += 30; X_ = X0;

Объявили строковый массив titles, который хранит в себе слова Symbol, Trades, Lots и т.д. Зная, что у нас 12 элементов, запустили цикл for от 0 до 11 в котором создаем лейблы, задавая им имя равное DV|Title_ плюс номер итерации. Текстом является порядковый номер массива titles. Координата Y0 у нас остается неизменной, потому что перечень весь идет в одну строчку. В конце каждой итерации мы изменяем координату X_, чтобы объекты не наложились друг на друга и сдвигались вправо.

После завершения цикла мы должны будем опустить весь будущий текст ниже на 30 пикселей вниз и вернуть значение координаты X в первоначальное значение.

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

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

for(int j=ArraySize(tradeenv)-1;j>=0;j—) {

out_Label(«DV|Symbol « +IntegerToString(j), tradeenv[j].symbol, X_, Y0, clrSkyBlue); X_ += 75;

out_Label(«DV|TradeB « +IntegerToString(j), IntegerToString(tradeenv[j].countB), X_, Y0, clrSkyBlue); X_ += 75;

out_Label(«DV|LotB « +IntegerToString(j), DoubleToString(tradeenv[j].LotB,2), X_, Y0, clrSkyBlue); X_ += 75;

out_Label(«DV|ProfitB « +IntegerToString(j), DoubleToString(tradeenv[j].profitB,2), X_, Y0, clrSkyBlue); X_ += 75;

out_Label(«DV|TradeS « +IntegerToString(j), IntegerToString(tradeenv[j].countS), X_, Y0, clrSkyBlue); X_ += 75;

out_Label(«DV|LotS « +IntegerToString(j), DoubleToString(tradeenv[j].LotS,2), X_, Y0, clrSkyBlue); X_ += 75;

out_Label(«DV|ProfitS « +IntegerToString(j), DoubleToString(tradeenv[j].profitS,2), X_, Y0, clrSkyBlue); X_ += 75;

out_Label(«DV|TradeT « +IntegerToString(j), IntegerToString(tradeenv[j].countB+tradeenv[j].countS), X_, Y0, clrSkyBlue); X_ += 75;

out_Label(«DV|LotT « +IntegerToString(j), DoubleToString(tradeenv[j].LotB+tradeenv[j].LotS,2), X_, Y0, clrSkyBlue); X_ += 75;

out_Label(«DV|ProfitT « +IntegerToString(j), DoubleToString(tradeenv[j].profitB+tradeenv[j].profitS,2), X_, Y0, clrSkyBlue); X_ += 75;

double win_prc = tradeenv[j].win * 100 / (tradeenv[j].win + tradeenv[j].loss);

double loss_prc = tradeenv[j].loss * 100 / (tradeenv[j].win + tradeenv[j].loss);

out_Label(«DV|WinT « +IntegerToString(j), IntegerToString(tradeenv[j].win) + » (« + DoubleToString(win_prc,0) + «%)», X_, Y0, clrLime); X_ += 75;

out_Label(«DV|LossT « +IntegerToString(j), IntegerToString(tradeenv[j].loss) + » (« + DoubleToString(loss_prc,0) + «%)», X_, Y0, clrOrange);

X_ = X0;

Y0 += 30;

}

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

  1. tradeenv[j].symbol — текущее наименование символа для текста.
  2. tradeenv[j].countB — количество ордеров на покупку из памяти массива. Чтобы перевести целое число в текст мы используем функцию IntegerToString.
  3. tradeenv[j].LotB — суммарный лот всех ордеров на покупку для выбранного символа. Чтобы перевести дробное число в текст мы используем функцию DoubleToString и указываем количество знаков для округления.
  4. tradeenv[j].profitB — суммарная прибыль/убыток всех ордеров по текущему символу на покупку.
  5. tradeenv[j].countS — количество ордеров на продажу.
  6. tradeenv[j].LotS — лот всех ордеров на продажу.
  7. tradeenv[j].profitS — прибыль/убыток всех ордеров на продажу.
  8. tradeenv[j].countB+tradeenv[j].countS — складываем количество ордеров на покупки и продажу для определения суммарного значения для последней колонки таблицы Total.
  9. tradeenv[j].LotB+tradeenv[j].LotS — суммируем лоты для покупки и продажи для последней колонки таблицы Total
  10. tradeenv[j].profitB+tradeenv[j].profitS — суммируем прибыль/убыток для покупки и продажи для последней колонки таблицы Total

Чтобы указать не только количество прибыльных и убыточных ордеров отдельно, но и их процент от общего числа, мы создадим переменные win_prc и loss_prc с простой формулой расчета, описанной выше в коде.

11. IntegerToString(tradeenv[j].win) + » (» + DoubleToString(win_prc,0) + «%)» — количество прибыльных ордеров и их процент от общего количества.

12. IntegerToString(tradeenv[j].loss) + » (» + DoubleToString(loss_prc,0) + «%)» — количество убыточных ордеров и их процент от общего количества.

После каждого созданного лейбла необходимо сдвигаться по оси X вправо (X_ += 75). В конце каждой итерации необходимо восстанавливать значение переменной X, а также смещаться на определенную строчку вниз по координате Y.

Итоговая таблица будет иметь вот такой вид:

В таблице отображаются результаты торговли нашего советника Night Hawk. Сравним ее результаты с данными сайта MyFxBook:

Сортировка немного отличается, но количество ордеров и прибыль сходятся. А значит работа выполнена корректно.

Заключение

Мы разобрали пример анализа закрытых ордеров и отображения части информации о них. Все координаты X и Y были подобраны на глаз по диагонали монитора. Они введены только для примера, начинающий программист сможет их поменять по своему вкусу. Также мною не были добавлены в таблицу отдельно такие колонки, как: размер свопа, комиссии, средний Тейк Профит или Стоп Лосс, удаленные отложки, прибыль в пунктах и т.д. Тут я уже оставляю простор для творчества вам.

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

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

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

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

0. Начало работы

Понравилась статья? Поделить с друзьями:
  • Как найти спонсора для благотворительного фонда
  • Как составить классический стиль
  • 2012 как найти радиус
  • Как найти своего беса
  • Как найти архитектора в корсарах гпк