Discussion of article "Library for easy and quick development of MetaTrader programs (part XXI): Trading classes - Base cross-platform trading object"

 

New article Library for easy and quick development of MetaTrader programs (part XXI): Trading classes - Base cross-platform trading object has been published:

In this article, we will start the development of the new library section - trading classes. Besides, we will consider the development of a unified base trading object for MetaTrader 5 and MetaTrader 4 platforms. When sending a request to the server, such a trading object implies that verified and correct trading request parameters are passed to it.

It is nice to have an easy access to various data at any time. However, that data is useless if we cannot apply it in trading. This means we need trading functionality along with the already existing one. 
This section will be relatively large, and we will do everything step by step.

  • We should be able to send any trading requests from any platform, be it MetaTrader 5 or MetaTrader 4, without even thinking about differences between them. Everything should be unified.
  • First, we need to verify trading requests in order not to load the server with deliberately erroneous requests.
  • We need to consider and correctly handle the return codes of the trade server. What does an EA do while sending a request to the server? It maintains the 'request-response' dialog with the server. Our task is to correctly arrange such a "communication channel", i.e. create the methods of handling trade server responses.
  • We need to create several options of handling server responses since sometimes we need to open a position "preferably at any cost". To do this, we need to arrange a repeated sending of a request to the server in case of a refusal to place an order — we can either adjust the trading request parameters or re-send it, or leave all the parameters intact but wait for the right moment when the request with these parameters is passed to send it immediately. Besides, we need to consider the price level in order not to re-send an order at a knowingly worse price.
    Sometimes, we need to send a trading request and continue work regardless of a request result.
  • Besides, we need to arrange the work with trading classes so that to avoid issues when placing a library-based program to MQL5 Market. The program should pass all the checks smoothly.
This is my current plan regarding trading classes.

Author: Artyom Trishkin

 
Hello Artyom - is there an easier way to get the ticket number after placing an order or opening a position? This seems overly complicated :-(
long CMySetup::PlaceOrder()
{
   bool order_ok;
   if (m_direction == POSITION_TYPE_BUY) {
      order_ok = m_engine.PlaceBuyLimit(m_lot_size, m_symbol, m_entry, m_hard_stop, m_ratio_target, m_magic, m_comment, m_expiry);
   }
   else {
      order_ok = m_engine.PlaceSellLimit(m_lot_size, m_symbol, m_entry, m_hard_stop, m_ratio_target, m_magic, m_comment, m_expiry);
   }

   if (order_ok) {
      CArrayObj* list = m_engine.GetListHistoryOrders();
      if (list != NULL) {
         list.Sort(SORT_BY_ORDER_TIME_OPEN);
         COrder* order = list.At(list.Total() - 1);
         if (order != NULL) {
            m_ticket = order.Ticket();
         }
      }
   }
   return m_ticket;
}
 
Dima Diall :
Hello Artyom - is there an easier way to get the ticket number after placing an order or opening a position? This seems overly complicated :-(

Test advisors display all the data of the last placed order or open position. This means that the library knows this data. It implements event functionality, and the library reports all set events. Accordingly, you need to control these events and get the object of the last order or position, and from it take a ticket (and all other parameters of the order or position). It is not yet possible to give any example. But I'll be back soon and I can help.
You can see for yourself where the test advisors take event data and print it to the log. Accordingly, you can access the same point from your program and take data from these events.

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

 
Artyom Trishkin:

Test advisors display all the data of the last placed order or open position. This means that the library knows this data. It implements event functionality, and the library reports all set events. Accordingly, you need to control these events and get the object of the last order or position, and from it take a ticket (and all other parameters of the order or position). It is not yet possible to give any example. But I'll be back soon and I can help.
You can see for yourself where the test advisors take event data and print it to the log. Accordingly, you can access the same point from your program and take data from these events.

Yes, I understand -- I am having a have closer look and wondering if I am guaranteed to to have my event handler be called immediately and, if multiple orders/positions have be requested, what sort of analysis I need to carry out to ensure I correctly match the events with each request... all this just to find out the ticket number which for e.g. pending orders the library receives in 'm_result' inside CTradeObj::SetOrder() 

#ifdef __MQL5__
   return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result));
#else 
   ::ResetLastError();
   int ticket=::OrderSend(this.m_request.symbol,
                          this.m_request.type,
                          this.m_request.volume,
                          this.m_request.price,
                          (int)this.m_request.deviation,
                          this.m_request.sl,
                          this.m_request.tp,
                          this.m_request.comment,
                          (int)this.m_request.magic,
                          this.m_request.expiration,
                          clrNONE);
// ...
   if(ticket!=WRONG_VALUE)
     {
      this.m_result.order=ticket;
      this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price);
      this.m_result.volume=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume);
      return true;
     }
// ...   
#endif

This is why I think it would be more useful that the ticket number (or -1 if unsuccessful) is returned from CEngine::PlaceBuyLimit() and all this family of methods, because when you place an order or open a position, most frequently you immediately want to record the ticket number so you can refer back to it later... Maybe it could even return a COrder* pointer or NULL if unsuccessful. In my humble opinion, forcing the DoEasy user to implement event-handling for this makes the library feel unnecessarily complicated.

Probably there is a good reason for your approach so not sure if you agree; if not, I'd be very interested to understand the reasoning behind your design decision ;-)

 
Dima Diall :

Yes, I understand -- I am having a have closer look and wondering if I am guaranteed to to have my event handler be called immediately and, if multiple orders/positions have be requested, what sort of analysis I need to carry out to ensure I correctly match the events with each request... all this just to find out the ticket number which for e.g. pending orders the library receives in 'm_result' inside CTradeObj::SetOrder()  

This is why I think it would be more useful that the ticket number (or -1 if unsuccessful) is returned from  CEngine::PlaceBuyLimit()  and all this family  of methods, because  when you place an order or open a position, most frequently you immediately want to record the ticket number so you can refer back to it later... Maybe it could even return a COrder* pointer  or NULL if unsuccessful.  In my humble opinion, forcing the DoEasy user to implement event-handling for this makes the library feel unnecessarily complicated.

Probably there is a good reason for your approach so not sure if you agree; if not, I'd be very interested to understand the reasoning behind your design decision ;-)

Everything is very simple. In MQL5, the success of sending a trade order to the server does not guarantee its execution. The success of sending is a millet check for the correctness of the parameters in a trade order. The execution of the order lies with the exchange. We can only use the fact of a change in the trading environment, and after detecting its change, move on. It is for this reason that I do not use the data received in the server response.

Всё очень просто. В MQL5 успешность отсылки торгового приказа на сервер не гарантирует его исполнения. Успешность отсылки - это просо проверка на корректность параметров в торговом приказе. Исполнение приказа лежит на стороне биржи. Мы можем лишь использовать факт изменения торгового окружения, и после обнаружения его изменения уже двигаться дальше. Именно по этой причине я не использую данные, полученные в ответе сервера.

 
Artyom Trishkin:

Everything is very simple. In MQL5, the success of sending a trade order to the server does not guarantee its execution. The success of sending is a millet check for the correctness of the parameters in a trade order. The execution of the order lies with the exchange. We can only use the fact of a change in the trading environment, and after detecting its change, move on. It is for this reason that I do not use the data received in the server response.

OK, I am trying to implement this approach in my EA framework to make it easier to manage using your TestDoEasyPart39.mq5 as a guide, but am having some issues with missed events -- still investigating/debugging (everything in tester mode for now)... I would appreciate other code examples of how you do this yourself in other EAs, if you can share some.

I am still using the library version from Part 39; is this the best version to use for the moment or you recommend upgrading to a newer version?

 
Dima Diall :

OK, I am trying to implement this approach in my EA framework to make it easier to manage using your TestDoEasyPart39.mq5 as a guide, but am having some issues with missed events -- still investigating/debugging (everything in tester mode for now)... I would appreciate other code examples of how you do this yourself in other EAs, if you can share some.

I am still using the library version from Part 39; is this the best version to use for the moment or you recommend upgrading to a newer version?

So far, all subsequent versions are devoted to the creation of indicators.

I'll be back to the advisors soon. There I will also check user messages that some trading events (occurring simultaneously) are lost.
And then I can show you how to use the event model of the library.

Пока все последующие версии посвящены созданию индикаторов.

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

 
Artyom Trishkin:

So far, all subsequent versions are devoted to the creation of indicators.

I'll be back to the advisors soon. There I will also check user messages that some trading events (occurring simultaneously) are lost.
And then I can show you how to use the event model of the library.

When do you expect to start working again on features for expert advisors? Depending on your answer I may need to choose writing my own multiplatform class/methods for simplified placing orders, opening positions, etc... at least for my current project.

When handling trade events such as TRADE_EVENT_PENDING_ORDER_PLASED or TRADE_EVENT_POSITION_OPENED, how can I ensure that a given event corresponds to a particular trade request I submitted earlier? Currently I only have one order/position per symbol, so it's easy to check that the symbol of the event matches my request... However, later my EA should be able to open multiple orders/positions on the same symbol (using a single magic number), hopefully it is not necessary to compare entry price, SL, TP, etc... I was thinking maybe use different magic numbers, or that feature that packs group #1 & #2 into the magic number.

I am a little confused and not quite sure what is the best approach to match trade events to requests -- can you please help me with this for now? If I can reliably associate a trade request with its ticket number, I believe I can continue using DoEasy for this project.

Library for easy and quick development of MetaTrader programs (part XXVI): Working with pending trading requests - first implementation (opening positions)
Library for easy and quick development of MetaTrader programs (part XXVI): Working with pending trading requests - first implementation (opening positions)
  • www.mql5.com
I have already mentioned the concept of a pending request in a number of previous articles. In this article, we are going to figure out what it is and why we need it, as well as start implementing pending requests. When receiving and handling a trade server error, we sometimes need to wait and repeat the request. In the simplest case, waiting...
 
Dima Diall :

When do you expect to start working again on features for expert advisors? Depending on your answer I may need to choose writing my own multiplatform class/methods for simplified placing orders, opening positions, etc... at least for my current project.

When handling trade events such as  TRADE_EVENT_PENDING_ORDER_PLASED or  TRADE_EVENT_POSITION_OPENED , how can I ensure that a given event corresponds to a particular trade request I submitted earlier? Currently I only have one order/position per symbol, so it's easy to check that the symbol of the event matches my request... However, later my EA should be able to open multiple orders/positions on the same symbol (using a single magic number), hopefully it is not necessary to compare entry price, SL, TP, etc... I was thinking maybe use different magic numbers, or that  feature  that packs group #1 & #2 into the magic number .

I am a little confused and not quite sure what is the best approach to match trade events to requests -- can you please help me with this for now? If I can reliably associate a trade request with its ticket number, I believe I can continue using DoEasy for this project.

If you need to compare a trade request and a position, then the easiest way is to set the position identifier (not magic). Each request has its own identifier. The magic of the advisor can be left one for all positions. Then by the identifier (it is written in the magic of a position or order and will never be lost), you can accurately match the request and position/order at any time.
 
Artyom Trishkin:
If you need to compare a trade request and a position, then the easiest way is to set the position identifier (not magic). Each request has its own identifier. The magic of the advisor can be left one for all positions. Then by the identifier (it is written in the magic of a position or order and will never be lost), you can accurately match the request and position/order at any time.

Is it something like this?

Trade Request:

ulong order_magic = m_engine.SetCompositeMagicNumber(m_magic, 0, 0, m_order_id);
bool submit_ok    = m_engine.PlaceBuyLimit(m_lot_size, m_symbol, m_entry, m_hard_stop, m_ratio_target, order_magic, m_comment, m_expiry);

Event Handler:

void CMyRobot::EngineEventHandler(int event_id, const long &lparam, const double &dparam, const string &sparam, bool testing, long chart_id)
{
   if (sparam != m_symbol) return;                                                  //--- skip event-handling if mismatched symbols

   if (event_id > SERIES_EVENTS_NO_EVENT && event_id < SERIES_EVENTS_NEXT_CODE) {   //--- HANDLING OF TIMESERIES EVENTS
      if (event_id == SERIES_EVENTS_NEW_BAR) {                                      // "new bar" event: lparam = period | dparam = date/time
         this.RefreshAllIndicators(1);
         this.ExecuteFSM(1, chart_id);                                              // execute finite state machine (NB: new bar's index = 1)
      }
   }
   else
   if (event_id > TRADE_EVENT_NO_EVENT && event_id < TRADE_EVENTS_NEXT_CODE) {      //--- HANDLING OF TRADING EVENTS
      CArrayObj *event_list = m_engine.GetListAllOrdersEvents();                    // get the list of trading events
      if (event_list == NULL) return;
      
      //--- compute event index shift relative to the end of the list
      //--- when back-testing, the shift value is passed in the 'lparam' parameter to the event handler
      //--- in normal operation, trading events are sent one by one and handled in OnChartEvent()
      int shift = testing ? (int)lparam : 0;
      CEvent *event = event_list.At(event_list.Total() - 1 - shift);
      if (event == NULL || event.GetPendReqID() != m_order_id) return;
      
      switch (event.TypeEvent())
      {
         case TRADE_EVENT_PENDING_ORDER_PLASED:                                     //--- pending order placed
            if (m_state_curr == FSM_PENDING_ORDER && m_ticket == WRONG_VALUE)
                m_ticket = event.TicketOrderEvent();
            break;
      
         // ...
      }
   }
}
 

Problem: the CEvent::GetPendReqID() method I need above is protected!! Any better ideas without me having to changw the DoEasy source code? In my humble opinion, these methods should be public ;-)

class CEvent : public CObject
  {
//...
protected:
//--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value
   ushort            GetMagicID(void)                          const { return ushort(this.Magic() & 0xFFFF);                                 }
   uchar             GetGroupID1(void)                         const { return uchar(this.Magic()>>16) & 0x0F;                                }
   uchar             GetGroupID2(void)                         const { return uchar((this.Magic()>>16) & 0xF0)>>4;                           }
   uchar             GetPendReqID(void)                        const { return uchar(this.Magic()>>24) & 0xFF;                                }
//...
};
Reason: