MQL5 Ордера истории

 

Убил 2 дня на поиск правильного решения по перебору ордеров истории. Без "костылей" осуществить по-прежнему не удаётся.

Задача была извлечь цену открытия и цену закрытия каждой транзакции в истории сделок. Нашёл решение (но это "костыль"):

#include <MT4Orders.mqh>

Как извелчь PriceOpen и PriceClose каждой транзакции, которая уже в истории? Извлечь без костылей - просто средствами языка.

Рецепты MQL5 - История сделок и библиотека функций для получения свойств позиции
Рецепты MQL5 - История сделок и библиотека функций для получения свойств позиции
  • www.mql5.com
Пришло время подвести краткий итог по материалам предыдущих статей о свойствах позиции. В этой статье мы создадим несколько дополнительных функций для получения тех свойств, которые можно получить только после обращения к истории сделок. Мы также познакомимся со структурами данных, что сделает доступ к свойствам позиции и символа еще удобнее.
 
Vitaly Murlenko:

Убил 2 дня на поиск правильного решения по перебору ордеров истории. Без "костылей" осуществить по-прежнему не удаётся.

Задача была извлечь цену открытия и цену закрытия каждой транзакции в истории сделок. Нашёл решение (но это "костыль"):

Как извелчь PriceOpen и PriceClose каждой транзакции, которая уже в истории? Извлечь без костылей - просто средствами языка.

Лучше не ордера перебирать, а сделки. Если позиция открыта по рынку, то в ордере цены нет. При закрытии, точно не знаю есть-ли…

зы: Вот в документации есть такой пример

void OnStart() 
  { 
   color BuyColor =clrBlue; 
   color SellColor=clrRed; 
//--- request trade history 
   HistorySelect(0,TimeCurrent()); 
//--- create objects 
   string   name; 
   uint     total=HistoryDealsTotal(); 
   ulong    ticket=0; 
   double   price; 
   double   profit; 
   datetime time; 
   string   symbol; 
   long     type; 
   long     entry; 
//--- for all deals 
   for(uint i=0;i<total;i++) 
     { 
      //--- try to get deals ticket 
      if((ticket=HistoryDealGetTicket(i))>0) 
        { 
         //--- get deals properties 
         price =HistoryDealGetDouble(ticket,DEAL_PRICE); 
         time  =(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); 
         symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); 
         type  =HistoryDealGetInteger(ticket,DEAL_TYPE); 
         entry =HistoryDealGetInteger(ticket,DEAL_ENTRY); 
         profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); 
         //--- only for current symbol 
         if(price && time && symbol==Symbol()) 
           { 
            //--- create price object 
            name="TradeHistory_Deal_"+string(ticket); 
            if(entry) ObjectCreate(0,name,OBJ_ARROW_RIGHT_PRICE,0,time,price,0,0); 
            else      ObjectCreate(0,name,OBJ_ARROW_LEFT_PRICE,0,time,price,0,0); 
            //--- set object properties 
            ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0); 
            ObjectSetInteger(0,name,OBJPROP_BACK,0); 
            ObjectSetInteger(0,name,OBJPROP_COLOR,type?BuyColor:SellColor); 
            if(profit!=0) ObjectSetString(0,name,OBJPROP_TEXT,"Profit: "+string(profit)); 
           } 
        } 
     } 
//--- apply on chart 
   ChartRedraw(); 
  }

его достаточно просто переделать под ваши нужды.

Только надо добавить 

DEAL_POSITION_ID

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

long

POSITION_IDENTIFIER

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

Идентификатор позиции указывается в каждом ордере (ORDER_POSITION_ID) и сделке (DEAL_POSITION_ID), которая ее открыла, изменила или закрыла. Используйте это свойство для поиска ордеров и сделок, связанных с позицией.

При развороте позиции в режиме неттинга (единой сделкой in/out) идентификатор позиции POSITION_IDENTIFIER не изменяется. Однако при этом POSITION_TICKET изменяется на тикет ордера, в результате которого произошел разворот. В режиме хеджинга разворот позиции не предусмотрен.

long


для того, чтобы не получить открытие одной позиции и закрытие другой.

 

Некоторые костыли с открытым исходным кодом, поэтому можно посмотреть...


Ticket.

Сначала нужно определиться с идентификацией закрытой позиции - тикет. Это можно сделать через тикеты соответствующих ордеров, сделок или позиций. Для каждого варианта MT5 предалагает по ПКМ нужное представление истории торговли. Поэтому, наверное, надо делать такой код.

#property script_show_inputs

input long inDealTicket = 0; // Deal Ticket
input long inOrderTicket = 0; // Order Ticket
input long inPositionTicket = 0; // Position Ticket

#define TOSTRING(A) #A + " = " + (string)(A) + " "

void OnStart()
{
  Print(TOSTRING(inDealTicket) + TOSTRING(PriceOpenByDeal(inDealTicket)));
  Print(TOSTRING(inOrderTicket) + TOSTRING(PriceOpenByOrder(inOrderTicket)));
  Print(TOSTRING(inPositionTicket) + TOSTRING(PriceOpenByPosition(inPositionTicket)));

  Print(TOSTRING(inDealTicket) + TOSTRING(PriceCloseByDeal(inDealTicket)));
  Print(TOSTRING(inOrderTicket) + TOSTRING(PriceCloseByOrder(inOrderTicket)));
  Print(TOSTRING(inPositionTicket) + TOSTRING(PriceCloseByPosition(inPositionTicket)));
}


Дальше возникает проблема с определением цены открытия и закрытия.


PriceOpen.

Хорошо бы понимать, что такое цена открытия в следующих ситуациях.

  1. Позиция открылась одной сделкой и закрылась.
  2. Позиция открылась несколькими сделками и закрылась.
  3. Отложенный ордер частично исполнился и открыл позицию. Затем модицифировался и долил позицию. Затем позиция была закрыта, а остаток отложки был модифицирован. Затем остаток исполнился, открыв позицию, и она закрылась.
Для п.1. такой код.

double PriceOpenByDeal( const long Deal )
{
  return((HistoryDealSelect(Deal) && HistorySelectByPosition(HistoryDealGetInteger(Deal, DEAL_POSITION_ID)))
          ? HistoryDealGetDouble(HistoryDealGetTicket(0), DEAL_PRICE) : 0);
}

double PriceOpenByOrder( const long Order )
{
  return((HistoryOrderSelect(Order) && HistorySelectByPosition(HistoryOrderGetInteger(Order, ORDER_POSITION_ID)))
          ? HistoryDealGetDouble(HistoryDealGetTicket(0), DEAL_PRICE) : 0);
}

double PriceOpenByPosition( const long Position )
{
  return(HistorySelectByPosition(Position)
          ? HistoryDealGetDouble(HistoryDealGetTicket(0), DEAL_PRICE) : 0);
}

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


PriceClose.

  1. Позиция закрылась одной сделкой.
  2. Позиция частично закрывалась несколькими сделками.
  3. п.3 выше.

Для п.1. такой код.

double PriceCloseByDeal( const long Deal )
{
  return((HistoryDealSelect(Deal) && HistorySelectByPosition(HistoryDealGetInteger(Deal, DEAL_POSITION_ID))
          ? HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal() - 1), DEAL_PRICE) : 0));
}

double PriceCloseByOrder( const long Order )
{
  return((HistoryOrderSelect(Order) && HistorySelectByPosition(HistoryOrderGetInteger(Order, ORDER_POSITION_ID)))
          ? HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal() - 1), DEAL_PRICE) : 0);
}

double PriceCloseByPosition( const long Position )
{
  return(HistorySelectByPosition(Position)
          ? HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal() - 1), DEAL_PRICE) : 0);
}

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


Производительность.

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


ЗЫ Код не проверял - не запускал. Знаю архитектуру, поэтому почти уверен, что ошибок нет.

 

@Alexey Viktorov спасибо. Пример этот видел, по нему и пытался разобраться. Не понял зачем было так усложнять язык? Было же хорошо OrderClosePrice() и всё. Который раз убеждаюсь: лучшее - враг хорошего. Чем лучше стало - не понимаю. А чем хуже - понятно: хрен разберёшься. Это в духе метаквотесов - дать крупицу инфы и ни какой ясности :(

@fxsaber спасибо, что откликнулись. Тип счёта - хеджинговый и только хеджинговый. Преимущества неттингового не вижу совсем, поэтому не использую. Отложенные ордера в цикле перебора не участвуют. Честно говря, Ваш код не добавил ясности. Хорошо, попробуем иначе.

P.S.

Меня тут знакомый спросил, зачем я создал новую тему, а не написал в "Ошибки, баги, вопросы"? Сначала я так и хотел сделать. Но покопав конкретон информацию, понял что ясности в этом вопросе в инете нет вообще. Поэтому решил вынести в отдельную тему. Во-первых, чтоб обсуждение не перемешивалось в кашу с ответами, темы этой не скасающимися. Во-вторых, чтоб другой ищущий в этом вопросе ясности (а он 100% будет) мог найти результат этого обсуждения.

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

 
Vitaly Murlenko #:

@fxsaber спасибо, что откликнулись. Тип счёта - хеджинговый и только хеджинговый. Преимущества неттингового не вижу совсем, поэтому не использую. Отложенные ордера в цикле перебора не участвуют. Честно говря, Ваш код не добавил ясности. Хорошо, попробуем иначе.

Только для хеджа и написал свой пост.
 

Вот такое нужно?

HistorySelect(0,TimeCurrent());
   int historyDealsTotal = HistoryDealsTotal();
   for(int i=0; i < historyDealsTotal && !IsStopped(); i++) {
      ulong ticketIn=HistoryDealGetTicket(i);
      if(ticketIn > 0 && HistoryDealGetInteger(ticketIn,DEAL_ENTRY) == DEAL_ENTRY_IN) {
      
         double dealPriceOpen = HistoryDealGetDouble(ticketIn,DEAL_PRICE); // цена открытия
         ulong positionId = HistoryDealGetInteger(ticketIn,DEAL_POSITION_ID);
         
         for(int q=0; q < historyDealsTotal && !IsStopped(); q++) {
            ulong ticketOut=HistoryDealGetTicket(q);
            if(ticketOut > 0 && HistoryDealGetInteger(ticketOut,DEAL_ENTRY) == DEAL_ENTRY_OUT) {
               if(HistoryDealGetInteger(ticketOut,DEAL_POSITION_ID) == positionId) {
               
                  double dealPriceClose = HistoryDealGetDouble(ticketOut,DEAL_PRICE); // цена закрытия
                  
                  break;
               }
            }
         }
      }
   }


upd. Если правильно понимаю, при программном извлечении данных по историческим сделкам, все они (сделки) сортированы от самой новой к самой старой. Тогда, наверное, лучше искать от закрытия к открытию. И открытие искать с индекса закрытия + 1.

 
fxsaber #:

Производительность.

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

Замер производительности.

#define TOSTRING(A) #A + " = " + (string)(A) + " "

#define BENCH(A)                                                \
{                                                               \
  const ulong StartTime = GetMicrosecondCount();                \
  Print(TOSTRING(A) + "- " +                                    \
        (string)(GetMicrosecondCount() - StartTime) + " mcs."); \
}

double CalcDeals1( const ulong &Deals[] )
{
  double Res = 0;
  
  for (uint i = ArraySize(Deals); (bool)i--;)
    Res += PriceOpenByDeal(Deals[i]); // https://www.mql5.com/ru/forum/457706#comment_50623924
    
  return(Res);    
}

void OnStart()
{
  HistorySelect(0, INT_MAX);
  Print(TOSTRING(HistoryDealsTotal()) + TOSTRING(HistoryOrdersTotal()));
    
  ulong Deals[];
  
  // Собрали все сделки в массив, чтобы хоть как-то оптимизировать.
  for (uint i = ArrayResize(Deals, HistoryDealsTotal()); (bool)i--;)  
    Deals[i] = HistoryDealGetTicket(i);
    
  BENCH(CalcDeals1(Deals))
//  BENCH(CalcDeals2(Deals))
}


Результат.

HistoryDealsTotal() = 31383 HistoryOrdersTotal() = 65969 

CalcDeals1(Deals) = 48559.13890000004 - 21997433 mcs.
CalcDeals2(Deals) = 48559.13890000004 - 81357 mcs.


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

 
Yevhenii Levchenko #:

Если правильно понимаю, при программном извлечении данных по историческим сделкам, все они (сделки) сортированы от самой новой к самой старой.

Да.

 

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

void OnStart(){
  //--- request trade history
  HistorySelect(0,TimeCurrent()); // Нахрена это вызывать, я так и не понял
        uint     TotalOrder=HistoryOrdersTotal();
        uint     TotalDeal=HistoryDealsTotal();
        Print("TotalOrder = ",TotalOrder);
        Print("HTotalDeal = ",TotalDeal);
  
  return;
}

Кидаем на график, получаем в журнале экспертов результат:

Опа, а сделок-то всего 4!


 
Возникает закономерный вопрос, что же это за числа мы вывели на экран?
 

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

MQL5 Ордера истории

fxsaber, 2023.11.20 22:28

HistoryDealsTotal() = 31383 HistoryOrdersTotal() = 65969 

CalcDeals1(Deals) = 48559.13890000004 - 21997433 mcs.
CalcDeals2(Deals) = 48559.13890000004 - 81357 mcs.


Почти в 300 раз медленнее не самого быстрого варианта.

Без каких-либо торговых библиотек (а-ля MT4Orders) получилось ускорить изначальный вариант в 1000+ раз.
CalcDeals1(Deals) = 48559.13890000004 - 19674 mcs.


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

Но небольшую стороннюю библиотеку (не торговую) для этого все же пришлось использовать.

#include <fxsaber\TradesID\TradesID.mqh> // https://www.mql5.com/ru/code/34173

double PriceOpenByDeal( const long Deal )
{
  static TRADESID TradesID;
  
  ulong Deals[];
  
  return(TradesID.GetDealsByID(HistoryDealGetInteger(Deal, DEAL_POSITION_ID), Deals)
          ? HistoryDealGetDouble(Deals[0], DEAL_PRICE) : 0);
}