Trading Orientat pe Obiecte : o abordare POO a tradingului

[English version] [MQLmagazine.com in english] [Editia romaneasca]

Cititorii vor fi dezamagiti sa vada ca am dedicat acest articol lui MQL4. Adevarul e ca MQL5 are inca probleme cu managementul ordinelor, asa ca articolul despre TOO in MQL5 va fi intarziat. De fapt , codul MQL5 nici macar nu este solid. De aceea este probabil ca ceea ce este scris azi sa nu mai fie valabil curand, caci MetaQuotes se grabeste cu MT5. Sunt si zile cu mai mult decat un update!

Trebuie sa mentionez ca in acest articol voi folosi termenii “tranzactie” si “ordin” cu acelasi inteles, de ordin.

Cu ceva timp in urma, am scris despre TOO pe forumul MQL4.COM . Ideea TOO mi-a venit in timp ce programam strategii grid trading, pe care le vom discuta in articole viitoare. La acea vreme, am observat ca era nevoie de a crea comportamente fixe care afectau grupuri largi de tranzactii. De exemplu “inchide toate tranzactiile din grupul x cand profitul total al grupului x atinge y pipsi”. Sau “daca piata urca mai sus de [acest nivel] , mai genereaza tranzactii pentru 3 nivele”. Programarea Orientata pe Obiecte (POO) a facut posibila aplicarea unor comportamente definite obiectelor, ca de exemplu “deschide asta daca userul face clic pe butonul Ok”. In trading, poate sa fie nevoie de a aplica acelasi comportament pe grupuri mari de tranzactii (sigur, pe MT4, care are hedgingul permis) intr-o maniera similara tehnicilor POO. Desigur ca sunt obiecte de ecran, cum ar fi ferestre, butoane, meniuri, care sunt implementate ca si clase si obiecte. Dar nu exista obiecte de piata. Evenimentele de piata, cum ar fi executiile, sunt recunoscute, de multe API-uri, precum si ordinele ca si obiecte, dar aici nu vorbim de POO cu ordine. Ci, vorbim de POO cu grupuri de ordine.

Dar un grup de ordine nu este ceva ce poate fi instantiat dintr-o clasa, si nu poate avea el insusi un cod de clasa. Ceva trebuie facut pentru a trece peste aceasta limita, si anume a face ca grupuri de ordine sa raspunda la intrebari si comenzi. Acesta este TOO.

In MT4, ordinele au un numar de caracteristici care le face distincte. Primul este tichetul. Sistemul asigneaza un tichet, de fiecare data cand un ordin este lansat. De aceea, tichetele nu sunt un mod simplu de a recunoaste care ordine apartin unui grup, dar sunt folositoare in a stoca intr-un tablou un ordin pentru a nu repeta procedura de recunoastere de fiecare data inainte de aplicarea unei metode. Cea mai buna cale de a crea o stampila de ordin folosibila pentru recunoasterea ordinelor, este numarul magic.

Multi traderi folosesc numarul magic pentru a distinge intre ordinele plasate de diferiti experti. Dar o buna cale de a face asta e folosirea comentariului. Asa incat, comentariul va include numele expertului, iar numarul magic va avea rolul special de a disting intre obiectele de ordine care apartin aceluiasi expert.

Sa presupunem ca un expert poate avea un numar nelimitat de ordine (in practica nu e posibil oricum). Aceste ordine pot fi grupate, de exemplu, in obiecte care cuprind cate 6 ordine. Ordinele sunt deschise la un anumit pas unul de altul, iar grupurile de ordine sunt asezate unele peste altele. Vom face asta cu ajutorul criteriilor.

Primul criteriu este numarul obiectului. Al doilea criteriu este nivelul ordinului in interiorul obiectului. Alte criterii care urmeaza, dar nu sunt legate in numarul magic: tipul ordinului, simbolul, sau chiar comentariul. Constructia numarului magic va urmari un o baza de numeratie variabila.

Sa ne amintim cum un numar din baza 2 este transformat in baza 10: incepem de la prima cifra, inmultim cu 2, adaugam a doua cifra, s.a.m.d. Astfel, numarul din baza 10 va impacheta n criterii, fiecare cu 2 stari posibile (0 si 1). Conversia inapoi la baza 2 este facuta impartind constant la 2 , retinand restul si rezultatul final, urmata de o reconstructie plecand de la rezultatul final si seria de resturi, in ordine inversa.

Dar noi avem, cand construim numerele magice, N criterii, cu m valori (de la 0 la m(i)-1)

Numarul magic va fi format cu formula

Numar magic =(…( ( ((Prima valoare-1)*M[2])+(A doua valoare -1) )*M[3] + (A treia valoare -1) )*M[4]+…)+(A N-a valoare -1)

Avem la baza doua criterii : numarul obiectului si nivelul ordinului. Deci numarul nostru magic va fi:

Numar magic = Numar de obiect * Nivele + nivel;

Totusi, daca pentru anumite motive dorim sa avem numere de nivel negative, precum si numere de obiect negative, pentru care trebuie sa separam semnele:

Magic number = ( (Numar absolut de obiect * 2 + Semn de obiect) * Nivele + Nivel absolut * 2 + Semn de nivel

Asa si urmeaza in urmatorul cod MQL4. (Acesta contine parametri care sunt necesare in urmatoarele extrase de cod, care impreuna formeaza un 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;
 
// o functie MathDiv ; MathMod exista, dar MathDiv lipseste
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;
   }

Cele doua functii sunt despre numere magice – sa le creeze si sa le dezasambleze. Dar ce putem sa facem cu managementul real al obiectelor in MT4?
Ei bine, MT4 nu este un limbaj POO si nu are ce-i trebuie. Totusi, tablouri, procedurile, variabilele publice si global pot fi de ajutor. Obiectele noastre vor fi facute din ordine pending. Pentru ordinele pending, vom scrie cateva module care lucreaza cu aceste tipuri de ordine:

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… Nu mai ai probleme selectand LIMIT sau STOP. Functiile o vor face pentru tine : ordinele vor fi executate odata ce piata le atinge. Vor lucra intr-o maniera MIT (market if touched).

Acum, inapoi la modulele de implementare. ObjectCreate() va crea un obiect cu 6 ordine in jurul unui pret de mijloc. ObjectDestroy() va juca rolul destructorului.

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;
    }

Observa ca procedura nu trebuie sa “inregistreze” obiectul scriind vreo variabila globala sau publica.
Totusi va exista distinctie intre obiecte. Creeaza doua obiecte si ruleaza functia urmatoare ca sa vezi:

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);
   }

Acum presupunand ca te-ai jucat cu CreateObject(), fara sa creeazi alte tranzactii, CountObjects() va intoarce numarul obiectelor create. Si acum lucrurile arata chiar interesant, pentru ca obiectele incep sa aiba un look si un mod de operare distinct, desi nu sunt scrise obiectural, cu clase si metode obisnuite. Prin simpla verificare a numarului magic al fiecarei tranzactii si gasind obiectul si tranzactia poti manipula obiectul in feluri diverse : calcule, inchideri de tranzactii, deschideri de noi ordine, setari de stop loss/take profit. Totusi, MQL4 nu va vedea nimic din ce se intampla, pentru ca MT4 nu este bazat pe evenimente. Va trebui sa chemi aceste rutine in start() astfel incat sunt executate la fiecare tick. Lucru care va consuma timp. Vei avea nevoie de tablouri bidimensionale pentru a stoca numere de tichete, astfel incat recunoasterile nu vor mai fi necesare, ci doar verificari ale unor tranzactii specifice.

Pentru a calcula profitul in pipsi pe fiecare obiect, avem nevoie sa-l calculam mai intai la nivel de tranzactie,
pentru ca MQL4 stie sa calculeze profitul doar in moneda contului:

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);   
  }

Acum o sa presupun ca vei asigna numei numere pozitive obiectelor. E nevoie de o regula. Pentru ca altfel, calculele de profit vor necesita treceri de N ori prin masa ordinelor, si toate calculele pot fi facute in doua treceri : una pentru a determina obiectul maxim, iar a doua pentru a face calculele. Urmatorul modul va pune profitul curent per obiect intr-un tablou.

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;
  }

Si acum un exemplu pentru init() si start(). Mai bine sa fie testate intr-un EA, pentru ca permite backtesting rapid cu 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);
   }

Desigur ca init() in mod normal ar trebui sa recunoasca obiectele mai intai, dar acest exemplu este facut numai pentru scopuri de backtesting. Asa ca nu te astepta la vreo strategie de trading aici : ca sa fac o analogie cu managementul, TOO este undeva intre nivelele operational si tactic.

E posibil sa fie necesar sa muti toate declaratiile extern la inceputul fisierului.

TOO este o tehnica ce intra in uz cand ai un numar mare de tranzactii care trebuie sa fie administrate (prin metode) dupa reguli, reguli (evenimente) care sunt separate dupa grupuri specifice de tranzactii (obiecte).
Desigur, TOO este aplicabil si in MT5, dar cu diferentele implicate de sistemul pozitional si de schimbarile aduse de evenimentul OnTrade().

Leave a Reply