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) > 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; }
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 < 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.
Thank you for sharing your code. It is like poetry!
Glad you like it!
I can’t hear anhynitg over the sound of how awesome this article is.
just to be clear: is this a subset of C++ for MT5? if so, will it work on MT4 as well or does one need to run it on the MT5 client?
That will work for MT4. Metaquotes has kind of expanded mql4 with some elements of mql5 since MT4 build 600+
please I want a code to multiply my next order lot with the previous openorder lots. I appreciate any contribution.
If I don’t misunderstand what you are trying to achieve this could be done like this:
Very nice working. Thanks for sharing.
Would you please make the files available for download again? I get a Forbidden error. I guess it is not public anymore.
Sorry, I moved the site to a different hoster and forgot to relink the downloads. It should be working again.
I think the code is wrong and cannot work as intented in that state, considering GetOpen and GetHistory logic and those basic rules:
0 equals NULL (for number types)
OP_BUY equals 0, so OP_BUY equals NULL, and vice versa.
Conclusion: your code as it is right now does not allow OP_BUY orders filtering.
I faced the same problem with order properties and choose to create my own global order types enumeration (same values as native + OP_ANY) to solve the problem.
Coming from the JAVA world i got tricked by the NULL type which is inconsistant in MQL4 and should be avoided as much as possible.
Hi,
thank you for pointing this out. You are perfectly right, of course. I didn’t notice earlier as I actually only use the custom filter method and didn’t filter by order type, yet. I have updated the code and filtering for order type should work as well now.
Cool and thank you, i learned quite a few things reading your script 😉
Hi Dear, this code is insane! Unfortunately I didn’t get any errors but in this way can’t work correctly, It doesn’t retrieve any ticket number. Can you help me? Thanks a lot
int CheckConsNotAtMultiRByMagic()
{
int c = 0;
int d = MaxMartingaleMultiplier * martingaleEvery;
OrderSelection* myorders = OrderWorker::GetHistory(1, NULL, -1, 0); //– Want to Get ALL Closed Order (Limit and Stop Expired Included)
myorders.Sort(ORDER_CLOSE_TIME, NEWEST); //– Want to Sort By Latest Close Time First
for(int i = 0; c < d && i = 2)
{ return NormalizeDouble(c / martingaleEvery, 0); }
}
}
}
return NormalizeDouble(c / martingaleEvery, 0);
}
Hi,
the code below will print out all orders in the trading history (latest first). You can try to modify it to your needs.
int magic = -1; //get with all magic numbers
OrderSelection* myorders = OrderWorker::GetHistory(-1,"");
myorders.Sort(ORDER_CLOSE_TIME, NEWEST);
for(int i = 0; i < myorders.Count(); i ++) { Order* order = myorders.Get(i); Print(order.ticket); } delete(myorders); // free memory
thank you! this is what I’m looking at. using built-in MQL4 function. when I use SELECT_BY_POS, sometimes the selection is not in an order and cause my take partial not properly execute. when using your function, the take partial working like charm!