Object Oriented Trading : an OOP approach to trading

[Versiunea romaneasca] [MQLmagazine.com in romana] [English edition]

Readers will be disappointed to see that I dedicated this article to MQL4. Truth is, MQL5 still has issues on order management, and the article about OOT in MQL5 will be delayed. In fact, MQL5 code here is not rock solid. Rather, it’s very likely that code that works today not to work any longer soon, as MetaQuotes is in a rush with MT5. There are even days with more than one update!

I have to mention that in this article I will use the terms “trade” and “order” with the same meaning, of “order”

Some time ago, I wrote about Object Oriented Trading on the MQL4.COM forum . OOT came to me while I was programming grid trading strategies, that will be discussed in other articles. At that time, I observed that there was the need to specify fixed behaviours to affect large groups of trades. For instance “close all trades on group x when total profit on group x touches y pips” . Or “if market goes above [this level] , generate trades for 3 more levels”. Object Oriented Programming (OOP) made possible the ability to apply defined behaviours to objects, such as “open this if user clicks the Ok button”. In trading, there may be the need to apply same behaviour to large groups of trades (sure, on MT4, that is hedging enabled) in a quite similar fashion to OOP techniques. There are screen objects, like windows, buttons, menus, that are implemented as classes and objects. But there are no market objects. Market events, such as getting a fill, are recognized, by many APIs, as well as orders as objects, but we’re not talking here about OOP with orders. Rather, we talk OOP with groups of orders.

But a group of orders is not something that will be instantiated out of a class, and can’t have itself a class code. Something has to be done to bridge this gap, making groups of orders answer inquiries and commands. This is what OOT is.

In MT4, orders have a number of characteristics that makes them distinct. The first is the ticket. The system assigns a ticket, every time an order is placed. Because of this, tickets are not an easy way to recognize which orders are part of a group, but they are helpful in storing an order in an array in order not to repeat the recognizing technique every time before applying a method. One of the best ways to create an order stamp that is usable for recognizing groups, is the magic number.

Many traders use the magic number to distinguish between orders that are placed by different expert advisors. But a good way to do this is to use the comment. So that, comment will contain the EA name , and the magic number will have the special role to distinguish between order objects that belong to the same EA.

Suppose that an EA can have an unlimited (but in practice is not possible anyway) number of orders. These orders can be grouped, for instance, in objects that are 6 orders each. Orders are opened at a given step one from another, and order objects are one atop the other. We will do this with the use of criterias.

First criteria is the object number. Second criteria is the order layer inside the object. Other criterias may follow, but they are not bound in the magic number: order type, symbol, or even comment. The construction of the magic number will follow a variable numeration base criteria.

Let’s remember how a base 2 number is transformed in base 10 : we start from the first digit, multiply by 2, add the second digit, and so on. Thus, the base 10 number will pack n criterias, each one with 2 possible states (0 and 1). The conversion back to base 2 is done by continuous diving by 2 and retaining and modulo and final result, followed by a reconstruction from the final result and the series of modulos, in reverse order.

But we have, when we construct our magic numbers, N criterias, each with m values (0 to m(i)-1)

So our magic number will be formed with the formula:

Magic number =(…( ( ((Value criteria one-1)*M[2])+(Value criteria two -1) )*M[3] + (Value criteria three -1) )*M[4]+…)+(Value criteria Nth -1)

We have basically two criterias : object number and order layer. So our magic number will be:

Magic number = Object Number * Layers + layer;

However, if for some reasons we want to have negative order layers, or even with negative object numbers, we have to sever the sign and make it a separate criteria:

Magic number = ( (Absolute Object Number * 2 + Object Sign) * Layers + Absolute Layer * 2 + Layer Sign

As it follows in the next MQL4 code. (It also contains parameters that are needed in the next code excerpts, that all make up an EA):

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
55
56
57
58
59
60
61
62
extern int LAYERS=4;
extern int HALFCHANNEL=10;
 
// a MathDiv function ; MathMod exists, but MathDiv is missing
int MathDiv(int a,int b)
    {
     int res;
     res=a-MathRound(MathMod(a,b));
     res=MathRound(res/b);
     return(res);
    }
 
int MakeMagic(int ObjectNumber,int ObjectLayer)
   {
    int ObjectSign,LayerSign;
    int AbsObjectNumber,AbsObjectLayer;
    int res;
    if (ObjectNumber<0)
      {
       AbsObjectNumber=-ObjectNumber;
       ObjectSign=1;
      }
    else
      {
       AbsObjectNumber=0;
       ObjectSign=0;
      }
    if (ObjectLayer<0)
      {
       AbsObjectLayer=-ObjectLayer;
       LayerSign=1;
      }
    else
      {
       AbsObjectLayer=0;
       LayerSign=0;
      }
    res=AbsObjectNumber;
    res=res*2;
    res=res+ObjectSign;
    res=res*LAYERS;
    res=res+ObjectLayer;
    res=res*2;
    res=res+LayerSign;
    return(res);
   }
 
void DisassembleMagic(int magic,int &ObjectNumber,int &TradeLayer)
   {
    int LayerSign,AbsTradeLayer,ObjectSign,AbsObjectNumber;
    int crtmagic=magic;
    LayerSign=MathMod(crtmagic,2); crtmagic=MathDiv(crtmagic,2);
    AbsTradeLayer=MathMod(crtmagic, LAYERS); crtmagic=MathDiv(crtmagic,LAYERS);
    ObjectSign=MathMod(crtmagic,2); AbsObjectNumber=MathDiv(crtmagic,2);
    if (ObjectSign==1)
      AbsObjectNumber=-AbsObjectNumber;
    if (LayerSign==1)
      AbsTradeLayer=-AbsTradeLayer;
    ObjectNumber=AbsObjectNumber;
    TradeLayer=AbsTradeLayer;
    return;
   }

These two functions are about magic numbers – to create or dissasemble them. But what about truly object management in MT4?
Well, MT4 is not an OOP language so it doesn’t have what it takes. However, arrays, procedures, public and global variables might do the trick. Our objects will be made out of pending orders. For pending orders, we’ll write a few modules to quickly work with pending orders trade types:

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
int SellOperation(double ToCompare)
  {
   int op;
   if (Bid>ToCompare)
     op=OP_SELLSTOP;
   if (Bid==ToCompare)
     op=OP_SELL;
   if (Bid<ToCompare)
     op=OP_SELLLIMIT;        
   return(op);   
  }
 
 
int BuyOperation(double ToCompare)
  { 
   int op;
   if (Ask>ToCompare)
     op=OP_BUYLIMIT;
   if (Ask==ToCompare)
     op=OP_BUY;
   if (Ask<ToCompare)
     op=OP_BUYSTOP;
   return(op);
  }
 
int WhatOperation(int operation, double ToCompare)
  { 
   if (operation==OP_BUY)
     return(BuyOperation(ToCompare));
   if (operation==OP_SELL)
     return(SellOperation(ToCompare));     
  }

Et voila… No need to have problems about selecting LIMIT or STOP. These functions will select for you ; orders will be filled once that market gets to them. Will work in a MIT (market if touched) manner.

Now, going back to our object implementation modules. ObjectCreate() will create an object with a group of 6 orders around a middle price; ObjectDestroy() will play the destructor.

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
extern double LotSize=0.1;
extern int Slippage=3;
 
void CreateObject(int ObjectNumber, double midprice)
   {
    double price;
    int i,ticket;
    for (i=1;i<=3;i++)
       {
        price=midprice+(HALFCHANNEL+(i-1)*2)*MarketInfo(Symbol(),MODE_POINT);
        ticket=OrderSend(Symbol(),SellOperation(price),LotSize, price, Slippage,0,0,"",MakeMagic(ObjectNumber,i) );
       }
    for (i=1;i<=3;i++)
       {
        price=midprice-(HALFCHANNEL-(i-1)*2)*MarketInfo(Symbol(),MODE_POINT);
        ticket=OrderSend(Symbol(),BuyOperation(price),LotSize, price, Slippage,0,0,"",MakeMagic(ObjectNumber,-i) );
       }
    return;
   }
 
void DestroyObject(int ObjectNumber)
    {
     int ot,ObjNumber,Layer;
     double price;
     int ntrades=OrdersTotal();
     if (ntrades!=0)
     for (int i=ntrades-1;i>=0;i--)
        {
         if (OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==True)
           {
            DisassembleMagic(OrderMagicNumber(),ObjNumber,Layer);
            if (ObjNumber==ObjectNumber)
              {
               ot=OrderType();
               if (ot!=OP_BUY&&ot!=OP_SELL)
                 OrderDelete(ot);
               else
                 {
                  if (ot==OP_BUY)
                    price=MarketInfo(Symbol(),MODE_BID);
                  if (ot==OP_SELL)
                    price=MarketInfo(Symbol(),MODE_ASK);
                  OrderClose(OrderTicket(), OrderLots(), price, Slippage); 
                 }
              }//if (ObjNumber==ObjectNumber)
           }//if (OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==True)
        }//for (int i=ntrades-1;i>=0;i--)
    return;
    }

Note that the procedure does not “register” the object by writing any global or public variables.
However there will be some distinction between them. Just make two objects and run the next function to see:

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
int CountObjects()
   {
    int res=0;
    int ObjectNumbers[500];    
    int ntrades=OrdersTotal();
    int i,j,ObjNumber,TradeLayer;
    bool reject;
    if (ntrades!=0)
      {
       for (i=ntrades-1;i>=0;i--)
          {
           if (OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==True)
             {
              reject=false; 
              DisassembleMagic(OrderMagicNumber(),ObjNumber,TradeLayer);
              if (res!=0) 
                {
                 for (j=0;j<res;j++)
                    {
                     if (ObjectNumbers[j]==ObjNumber)
                       {
                        reject=true; 
                        break;
                       }//if (ObjectNumbers(j)==ObjNumber)
                    }//for (j=0;j<res;j++)
                }//if (res!=0) 
              if (reject==false)
                {
                 res=res+1;
                 ObjectNumbers[res-1]=ObjNumber;
                } 
             }//if (OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==True)
          }//for (i=ntrades-1;i>=0;i--)
      }//if (ntrades!=0)
    return(res);
   }

Now provided that you just played with CreateObject(), without creating any other trades, CountObjects() will return the number of objects you created. And now the things are looking interesting, because objects start having a distinct look and operation mode, although there is no object-like coding , like regular classes with methods. By simply checking the magic number of every trade and finding out the object and trade you can manipulate the object in diverse ways: calculations, closing trades, opening new trades, setting stop losses and take profits. However , MQL4 will not see what happens , because MT4 is not event based. You will have to call routines in start() , so they are run at every tick. Thing which will be very time consuming. You will need bidimensional arrays to store ticket numbers, so that recognitions will not be necessary, rather checks on specific trades.

In order to calculate the profit in pips per each object, we have first to calculate it on the trade level, because MQL4 can calculate only in account currency, not in pips:

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
double OrderPipsResult(int ticket)
  {
  double res=0.0;
  int op,cm;
  bool sel;
  sel=OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES);
  if (sel==false)
     return(res);
  op=OrderType();
  double oop,pnw,pts;
  if (op!=-1)
    {
    oop=OrderOpenPrice();
    if (op==OP_BUY||op==OP_BUYLIMIT||op==OP_BUYSTOP)
       cm=MODE_BID;
    if (op==OP_SELL||op==OP_SELLLIMIT||op==OP_SELLSTOP)
      cm=MODE_ASK;
    pnw=MarketInfo(OrderSymbol(),cm);
    pts=MarketInfo(OrderSymbol(),MODE_POINT);
    if (op==OP_BUY)
       res=(pnw-oop)/pts;
    if (op==OP_SELL)
       res=(oop-pnw)/pts;   
    if (op!=OP_BUY&&op!=OP_SELL) //order is pending
       res=0.0;       
    }
  return(res);   
  }

Now I’m going to suppose that you will assign only positive numbers to objects. A rule is needed. Because, otherwise, profit calculations will need to pass N times thru the mass of orders, where all calculations can be done in two passes (first to calculate maximum object number, second to perform calculations). The following module will put in an array current profit per each object.

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
double ProfitArray[200];    
 
void CalculateProfits(int &minobject,int &maxobject)
   {
    minobject=99999999;
    maxobject=-999999999;
    int ntrades=OrdersTotal();
    int ot,om,ObjNumber,TradeLayer;
    ArrayInitialize(ProfitArray,0.0);
    if (ntrades>0)
      {
       for (int i=ntrades-1;i>=0;i--)
          {
           if (OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==True)
             {
              ot=OrderType();
              if (ot==OP_BUY||ot==OP_SELL)
                {
                 om=OrderMagicNumber();
                 DisassembleMagic(om,ObjNumber,TradeLayer);
                 if (ObjNumber>maxobject)
                   maxobject=ObjNumber;
                 if (ObjNumber<minobject)
                   minobject=ObjNumber;
                 ProfitArray[ObjNumber]=ProfitArray[ObjNumber]+OrderPipsResult(OrderTicket());
                }//if (ot==OP_BUY||ot==OP_SELL)
             }//if (OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==True)
          }//for (int i=ntrades-1;i>=0;i--)
     }//if (ntrades>0)   
   return;
  }

And now an example for init() and start(). Better to be tested in an EA, because it allows fast backtesting with Strategy Tester.

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
extern double SL=150.0;
extern double TP=70.0;
 
int init()
   {
     //place your objects here
     // like 
     CreateObject(1,Bid);
     CreateObject(2,Bid-100*Point);
     CreateObject(3,Bid+100*Point);
     //end
     return(0);
   }
 
int start()
   {     
     int min,max;
     CalculateProfits(min,max);
     for (int i=min;i<=max;i++)
        {
          if (ProfitArray[i]>=TP||ProfitArray[i]<=-SL) //emulate event raising
            DestroyObject(i);
        }
     return(0);
   }

Of course your normal init() should first recognize objects, but this example is made for backtesting purposes.
So don’t expect any trading strategy here: to make an analogy with management, OOT is somewhere between operational and tactical levels.

Note that you may also need to move all extern declarations to the begin of the file.

OOT as technique comes into use when you have a large number of trades that have to be managed (methods) according to rules, rules (events) that are separated for specific groups of trades (objects).
Surely, OOT is applicable to MT5 too, but with the differences implied by the positional system and the changes that will be brought by the OnTrade() event.

One Response to “ Object Oriented Trading : an OOP approach to trading ”

  1. gordon on February 4, 2010 at 7:59 am

    Good article, although I disagree with some of your comments regarding distinct characteristics of orders:

    1. Order ticket number - there are cases where ticket numbers change (partial close, at rollover, etc. -> these are all broker specific), hence it is not the most reliable order identifier.
    2. Order comment - many servers modify comments (in undocumented ways), hence that too is not a reliable order identifier.
    3. Order magic number - even in case of a change of order ticket number or a change of order comment, magic number is copied and retained, hence this is the only universal reliable method of identifying an order.

    In my own EA’s I use similar methods (although more simplified) as described in this article for using magic number to hold various info. To distinguish between EA’s what I do is have each EA use a RANGE of magic numbers.
    So for example each EA has it’s own extern param to set magic, but it uses the magic numbers in range magic to magic + x. All that the user needs to remember is to set each EA’s magic so as their used ranges have no intersections (so all EA’s magic numbers must have gaps of at least x between them).