MQL Order Abstraction

About

This article is aimed at algorithmic traders using Metaquote's MQL programming language for Metatrader.
The following code is just a small collection of classes that extend MQL's capabilities in a way to allow for easy filtering and ordering of Metatrader4 orders.
An advanced version is being used in our Expert Advisors. This code has been stripped-down for educational purpose without losing its core functionality.

The Code

Our Order Abstraction consists of three interacting classes (Order, OrderSelection, OrderWorker) supported by one additional Helper class.

Order

The Order Object contains a single MT4 order with all necessary information (ticket #, open price, comment, etc.)

   class Order
   {
      public:

      int ticket;
      int type;
      int magic;
      double lots;
      double openPrice;
      double closePrice;
      datetime openTime;
      datetime closeTime;
      double profit;
      double swap;
      double commission;
      double stopLoss;
      double takeProfit;
      datetime expiration;
      string comment;
      string symbol;
  };

Order Selection

This class provides a selection of filtered and/or sorted MT4 orders. It acts as a “container” for multiple order objects. The class also includes methods for navigating through the orders. Navigation functionality includes the following abilities:

  • Count() returns the total amount of orders in the selection
  • End() returns true if the counter index has reached the end of the selection
  • Rewind() resets the counter index. If you are using Next() after doing this you will get the very first item of the selection
  • Get(_index) returns the order object at position _index
  • Current() returns the order object at the current index position
  • Prev() returns the order object at the previous index position
  • Next() returns the order object at the next index position

Additionaly you have the possibility to sort the order selection with configurable parameters in either ascending or descending order

  • Sort(_what, _direction) you can sort the order collection by:
    ORDER_TICKET, ORDER_TYPE, ORDER_MAGIC_NUMBER, ORDER_LOTS, ORDER_OPEN_PRICE, ORDER_CLOSE_PRICE, ORDER_OPEN_TIME, ORDER_CLOSE_TIME, ORDER_NET_PROFIT, ORDER_STOP_LOSS and ORDER_TAKE_PROFIT
    The _direction parameter specifies whether the selection is sorted in ascending or descending order.

Code:

   class OrderSelection
   {
      private:
      
      Order* orders[];  // array of order objects
      int index;        // index of current order
      int size;         // number of all orders in container
   
      public:
   
      void OrderSelection() // constructor for container
      {
         index = -1;
         size = 0;
      }

      void ~OrderSelection()  // destructor frees the memory
      {
         for(int i = 0; i < ArraySize(orders); i ++)
            delete(orders[i]);
      }
      
      int Count() // returns count of all orders in container
      {
         return(size);
      }
      
      bool End()  // returns true if index is at the end of container           
      {
         return(index >= (size - 1));
      }      
      
      void Rewind() // resets the container index
      {
         index = -1;
      }  
      
      Order* Get(int _index)  // returns order object with index
      {
         return(orders[_index]);
      }            

      Order* Current()  // returns the current order object of container (used by next and prev)
      {
         return(orders[index]);
      }       

      Order* Prev() // returns the previous order object of container
      {
         index --;
   
         if(index < 0)
            return(NULL);
   
         return(Current());
      }      

      Order* Next()  // returns the next order object of container
      {
         index ++;
   
         if(index >= size)
         {
            Rewind();
            return(NULL);
         }
   
         return(Current());
      }   
      
      void Insert(Order* _order) // adds an order to container
      {
         size ++;
         ArrayResize(orders, size);
         orders[(size - 1)] = _order;
      }          
   
      void Sort(int _what = ORDER_TICKET, int _direction = ASCENDING) // insertion sort
      {   
         int i,j;
         for(i = 1; i < size; i ++)
         {
            j = i;
            while(j > 0 && Compare(orders[j], orders[j - 1], _what, _direction))
            {
               Order* temp = orders[j];
               orders[j]= orders[j - 1];
               orders[j - 1] = temp;
               j--;               
            }
         }
      }

      private:
      
      bool Compare(Order* _o1, Order* _o2, int _what, int _direction) // comparision function for sort
      {
         switch(_what)
         {
            case ORDER_TICKET:         return (_o1.ticket - _o2.ticket) * _direction >= 0;            
            case ORDER_MAGIC_NUMBER:   return (_o1.magic - _o2.magic) * _direction >= 0;
            case ORDER_TYPE:           return (_o1.type - _o2.type) * _direction >= 0;            
            case ORDER_CLOSE_PRICE:    return (_o1.closePrice - _o2.closePrice) * _direction >= 0;
            case ORDER_CLOSE_TIME:     return (_o1.closeTime - _o2.closeTime) * _direction >= 0;
            case ORDER_OPEN_PRICE:     return (_o1.openPrice - _o2.openPrice) * _direction >= 0;
            case ORDER_OPEN_TIME:      return (_o1.openTime - _o2.openTime) * _direction >= 0;
            case ORDER_LOTS:           return (_o1.lots - _o2.lots) * _direction >= 0;
            case ORDER_NET_PROFIT:     return ( (_o1.profit + _o1.swap + _o1.commission) - 
                                                (_o2.profit + _o2.swap + _o2.commission)) * _direction >= 0;
            case ORDER_STOP_LOSS:      return (_o1.stopLoss - _o2.stopLoss) * _direction >= 0;
            case ORDER_TAKE_PROFIT:    return (_o1.takeProfit - _o2.takeProfit) * _direction >= 0;
            default:                   return true;
         }
      }
      
   };

OrderWorker

This class is operating on MT4 orders and pushes them into the requested order selection. In a more advanced version OrderWorker also does the opening, closing and modification of orders. This behaviour can be included in a second part of this article, granted there is enough interest 🙂

OrderWorker currently has two main functions. It can fill a selection of current or already closed orders.

  • GetOpen(_magic, _symbol, _type, _filter) you can filter the containing orders by Magic Number, Symbol, Order Type and a custom Filter. If you set any of those parameters to NULL it is not getting filtered. More information on the usage of the custom Filter follows in the next paragraph.
  • GetHistory(_magic, _symbol, _type, _filter) see description above

Code:

   class OrderWorker
   {
      public:
   
      static OrderSelection* GetOpen(int _magic = -1, string _symbol = "", int _type = -1, int _filter = 0)
      {
         OrderSelection* orders = new OrderSelection();  // new container for orders

         for(int i = OrdersTotal() - 1; i >= 0; i --)      
         {     
            bool selected = OrderSelect(i, SELECT_BY_POS);

            if(selected)
            {      
               Order* order = new Order();   // create new order object
               Fill(order);     // fill order object
               if (true
                  && (_magic == -1 || _magic == order.magic)
                  && (_type == -1 || _type == order.type)
                  && (_symbol == "" || _symbol == order.symbol)
                  && (_filter == 0 || Helper::Filter(order, _filter))   // do advanced selections                  
                  ) {
                  orders.Insert(order); // add object to container          
               }
               else
                  delete(order); // remove from memory
            }
         }
         return orders;
      }
        
      static OrderSelection* GetHistory(int _magic = -1, string _symbol = "", int _type = -1, int _filter = 0)
      {
         OrderSelection *orders = new OrderSelection();  // new container for orders

         for(int i = OrdersHistoryTotal() - 1; i >= 0; i --)      
         {     
            bool selected = OrderSelect(i, SELECT_BY_POS, MODE_HISTORY);

            if(selected)
            {      
               Order* order = new Order();   // create new order object
               Fill(order);     // fill order object

               if (true
                  && (_magic == -1 || _magic == order.magic)
                  && (_type == -1 || _type == order.type)
                  && (_symbol == "" || _symbol == order.symbol)
                  && (_filter == 0 || Helper::Filter(order, _filter))   // do advanced selections                  
                  )
               
                  orders.Insert(order); // add object to container          
               
               else
                  delete(order); // remove from memory

            }
         }
         return orders;
      }
      
      private:

      static void Fill(Order &order)
      {
         order.ticket = OrderTicket();
         order.type = OrderType();
         order.magic = OrderMagicNumber();
         order.lots = OrderLots();
         order.openPrice = OrderOpenPrice();
         order.closePrice = OrderClosePrice();
         order.openTime = OrderOpenTime();
         order.closeTime = OrderCloseTime();
         order.profit = OrderProfit();
         order.swap = OrderSwap();
         order.commission = OrderCommission();
         order.stopLoss = OrderStopLoss();
         order.takeProfit = OrderTakeProfit();
         order.expiration = OrderExpiration();
         order.comment = OrderComment();
         order.symbol = OrderSymbol();
      }
   }; 

Helper

As you have noticed OrderWorker makes use of a Helper class that is providing the filter functionality. This has to be adapted to your individual needs. In this version four basic filters are included to select for long, short, market and pending orders. It is pretty easy to extend this functionality to filter for e.g. a special date range as all order attributes are passed to the function. If an order should be included into the selection the filter function has to return true, otherwise false.

   class Helper
   {
      public: 
            
      static datetime MyDate(datetime _date = NULL)
      {
         static datetime date_saved;
         if(_date != NULL)
            date_saved = _date;
         return(date_saved);
      }
      
      static bool Filter(Order* _order, int _filter)  // additional filter function for order selection
      {
         int type = _order.type;
         switch(_filter)
         {
            case FILTER_LONG:    return(Direction(type) > 0);
            case FILTER_SHORT:   return(Direction(type) < 0);
            case FILTER_MARKET:  return(type >= OP_BUY && type <= OP_SELL);
            case FILTER_PENDING: return(type > OP_SELL && type <= OP_SELLSTOP);
            case MYFILTER:       return(_order.openTime < MyDate());
         }
         return true;      
      }
      
      static int Direction(int _type) // returns 1 for long and -1 for short
      {
         if(_type < OP_BUY || _type > OP_SELLSTOP) // no valid order type
            return(0);
         if(_type % 2 > 0)
            return(-1);
         return(1);
      }         
   };

Example

To make things a bit clearer here is how you could filter for all orders older than a specific date. To accomplish this we introduce a little extra method to the Helper class called MyDate. This Method holds the date that we are using later in the Filter. If we would like to save/update the date we pass a datetime as parameter to this function. If we just call MyDate without a parameter it returns the last saved datetime.

      static datetime MyDate(datetime _date = NULL)
      {
         static datetime date_saved;
         if(_date != NULL)
            date_saved = _date;
         return(date_saved);
      }

      static bool Filter(Order* _order, int _filter)  // additional filter function for order selection
      {
         int type = _order.type;

         switch(_filter)
         {
            case FILTER_LONG:    return(Direction(type) &gt; 0);
            case FILTER_SHORT:   return(Direction(type) &lt; 0);
            case FILTER_MARKET:  return(type &gt;= OP_BUY &amp;&amp; type &lt;= OP_SELL);
            case FILTER_PENDING: return(type &gt; OP_SELL &amp;&amp; type &lt;= OP_SELLSTOP);
            case MYFILTER:       return(_order.openTime &lt; MyDate());
         }
         return true;
      }

Usage

Having all necessary classes in place, let’s see how we can put them to work in order to receive an ordered and/or filtered selection of MT4 orders. First we include the class collection OrderKFX.mqh.
Afterwards we can e.g. select all opened buy orders with the magic number “123” on the current symbol. We also use our custom built date filter. Finally we sort the selection by order opentime with the oldest order being in first place.

#include <orderkfx.mqh>

int magic = 123;
int type = OP_BUY;
string symbol = Symbol();

Helper::MyDate(StringToTime("2016.09.01")); 

OrderSelection* myorders = OrderWorker::GetOpen(magic, symbol, type, MYFILTER); 

myorders.Sort(ORDER_OPEN_TIME, OLDEST);

Having our sorted order selection, we can easily loop through the single orders and do further manipulations (closing orders, managing a trailing stop, etc.).
There are different ways to loop through the selection. In the example below we are using the End() method.

while(!myorders.End()) // loop through selected orders
{
   Order* order = myorders.Next(); // select single order from collection
   Print(order.ticket); // print ticket of order
}

You can alternatively do a for-loop that would look like this:

for(int i = 0; i &lt; myorders.Count(); i ++)
{
   Order* order = myorders.Get(i);
   Print(order.ticket);
}

When you are finished and no longer need your order selection(s) please don’t forget to delete them. Otherwise you will end up having memory leakage.

delete(myorders); // free memory

For your convenience you can download the complete Order class and the usage example from our website. If you have any remarks, questions or suggestions please leave a comment!


OrderKFX.mqh (10.5 KB)


example.mq4 (2.0 KB)


Disclaimer: Metatrader is the registered trademark of MetaQuotes Software Corp. KlondikeFX is not associated with or sponsored by MetaQuotes Software Corp.