Virtualizarea sistemelor de trading pentru ajustarea liniei de equity – un mit ?

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

Te-ai gandit vreodata cum ar fi sa ai o medie mobila pe linia de equity a unui sistem de trading, ca sa-l poti porni si opri ? Asta s-ar numi ajustarea liniei de equity.

Aspectul frustrant este acela ca odata ce incepi sa faci asta din sistemul de trading la care vrei sa ajustezi linia de equity, nu mai poti controla procesul, pentru ca indicatorii liniei de equity vor raporta diferit de fiecare data cand apesi pe buton.

Deci, ceea ce ai de facut e este sa lasi sistemul pe demo cumva in paralel cu sistemul de trading principal, sa copiezi tranzactiile in contul principal cand switchul e activat si sa le ignori cand switchul e dezactivat.

Aceasta se poate realiza prin virtualizare. Adica, trebuie sa scrii un sistem de management al pozitiilor , demo, ca un cont virtual, a carui iesire este “equity” catre sistemul de trading principal, care va optimiza aceasta curba si va decide daca va replica sau nu tranzactiile sistemului demo.

Astfel, incepem sa scriem un astfel de sistem, pe care-l vom numi TradeVirtualization.mqh .

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
//+------------------------------------------------------------------+
//|                                          TradeVirtualization.mqh |
//|                                       Copyright Bogdan Caramalac |
//|                                           http://mqlmagazine.com |
//+------------------------------------------------------------------+
#property copyright "Copyright Bogdan Caramalac"
#property link      "http://mqlmagazine.com"
#property version "1.0"
 
#define FLAT        0
#define LONG        1
#define SHORT       2
 
struct VirtualSymbolData
  {
    string Symbol;    
    double position;
    double price;
    double sl;
    double tp;
    double marked_price;    
  };
 
struct VirtualEquityData
  {
   datetime opentime,closetime;
   double open,high,low,close;  
  };

Incepem cu definitiile obisnuite pentru managementul pozitiilor, urmate de doua structuri. Prima este proiectata pentru stocarea datelor refritoare la simboluri, oferind simbolul, pozitia (pozitiva – lunga, negativa – scurta), pretul (pretul la initializarea pozitiei), pretul marcat (ultimul pret la care pozitia a fost marcata la piata). A doua este proiectata pentru a stoca datele de equity virtuale : timpii de deschidere si inchidere pentru bara de equity; deschidere, maxim, minim, inchidere pentru fiecare equity din interval. Acum e timpul pentru clasa VirtualAccount.

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
class VirtualAccount
  {   
   public:
   int VirtualSymbolsCount;    
   VirtualSymbolData VirtualSymbolsTable[30]; 
   VirtualEquityData VirtualEquity[500];
   double VirtualLastAccountEquity;  
   int VirtualLastEquityPos;
   uint VirtualEquityBarSpan;
 
   void VirtualAccountSet(double Equity,uint TimeSpan)   
       {
        datetime tc=TimeCurrent();
        VirtualEquity[0].opentime=tc;
        VirtualEquity[0].open=Equity;
        VirtualEquity[0].high=Equity;
        VirtualEquity[0].low=Equity;
        VirtualEquity[0].close=Equity;
        VirtualEquity[0].closetime=tc;
        VirtualLastAccountEquity=VirtualEquity[0].close;
        VirtualEquityBarSpan=TimeSpan;
        VirtualLastEquityPos=0;
        return;
       }  
 
    int VirtualGetPositionType(int asset_index); 
    double VirtualGetPositionSize(int asset_index);
    void VirtualPositionSetSL(double asset_index,double sl);
    void VirtualPositionSetTP(double asset_index,double tp);
    void VirtualMarkToMarket(int asset_index);
    void VirtualChangePosition(int asset_index,int direction,double price,double lotsize);
    private:
    void VirtualAddNewEquity(double equity);
 
    public:
    VirtualAccount() 
       {
        VirtualAccountSet(AccountInfoDouble(ACCOUNT_EQUITY),0);
       };      
  };

Clasa contine un tablou de VirtualSymbolsData si numarul sau de elemente, si un tablou cu VirtualEquityData. Alti parametri cheie sunt VirtualLastEquityPos si VirtualEquityBarSpan (timpul pe care se intinde o bara, in secunde). VirtualLastAccountEquity este doar un proxy pentru ultima valoare inregistrata a equity.

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
int VirtualAccount::VirtualGetPositionType(int asset_index)     
   {
    if (DoubleToString(NormalizeDouble(VirtualSymbolsTable[asset_index].position,2),2)=="0.00")
      return(FLAT);
    if (VirtualSymbolsTable[asset_index].position>0.00)
      return(LONG);
    if (VirtualSymbolsTable[asset_index].position<0.00)
      return(SHORT); 
    return(0);       
   }
 
double VirtualAccount::VirtualGetPositionSize(int asset_index)     
   {
    if (DoubleToString(NormalizeDouble(VirtualSymbolsTable[asset_index].position,2),2)=="0.00")
      return(0.00);
    return(NormalizeDouble(MathAbs(VirtualSymbolsTable[asset_index].position),2));       
   }       
 
 void VirtualAccount::VirtualPositionSetSL(double asset_index,double sl)
     {
      VirtualSymbolsTable[asset_index].sl=sl;
     }
 
 void VirtualAccount::VirtualPositionSetTP(double asset_index,double tp)
     {
      VirtualSymbolsTable[asset_index].tp=tp;
     }

VirtualGetPositionType() si VirtualGetPositionSize() sunt pur informationale. Marimea pozitiei e intotdeauna pozitiva, tot asa cum PositionInfoDouble() intoarce valorile pentru cererile POSITION_VOLUME. Celelalte doua functii fac exact ce spune numele lor ca face – seteaza stop loss si take profit.

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
void VirtualAccount::VirtualAddNewEquity(double equity)
    {
     datetime tc=TimeCurrent();
     uint diff;
     diff=tc-VirtualEquity[VirtualLastEquityPos].opentime;
     if (diff<=VirtualEquityBarSpan)
       {
        if (VirtualEquity[VirtualLastEquityPos].high<equity)
          VirtualEquity[VirtualLastEquityPos].high=equity;
        if (VirtualEquity[VirtualLastEquityPos].low>equity)
          VirtualEquity[VirtualLastEquityPos].low=equity;
        VirtualEquity[VirtualLastEquityPos].close=equity;
        diff=tc-VirtualEquity[VirtualLastEquityPos].closetime;           
        VirtualEquity[VirtualLastEquityPos].closetime=tc;           
        VirtualLastAccountEquity=equity;
        return;
       }
     if (VirtualLastEquityPos!=ArrayRange(VirtualEquity,0)-1)
       {           
        VirtualLastEquityPos++;
        VirtualEquity[VirtualLastEquityPos].opentime=tc;
        VirtualEquity[VirtualLastEquityPos].open=equity;
        VirtualEquity[VirtualLastEquityPos].high=equity;
        VirtualEquity[VirtualLastEquityPos].low=equity;
        VirtualEquity[VirtualLastEquityPos].close=equity;           
        VirtualEquity[VirtualLastEquityPos].closetime=tc;
        VirtualLastAccountEquity=equity;
        return;
       }
     else
       {
        for (int i=0;i<ArrayRange(VirtualEquity,0)-1;i++)
           VirtualEquity[i]=VirtualEquity[i+1];                  
        VirtualEquity[VirtualLastEquityPos].opentime=tc;
        VirtualEquity[VirtualLastEquityPos].open=equity;
        VirtualEquity[VirtualLastEquityPos].high=equity;
        VirtualEquity[VirtualLastEquityPos].low=equity;
        VirtualEquity[VirtualLastEquityPos].close=equity;           
        VirtualEquity[VirtualLastEquityPos].closetime=tc;
        VirtualLastAccountEquity=equity;
        return;
       }        
    }

La inceput, clasa este setata automat cu un equity egal cu cel al contului pe care lucreaza si o largime a barei in timp de 0. Userul poate seta de la inceput contul sa aiba un equity de inceput diferit, si o alta largime in timp a barei. La fiecare tick, VirtualMarkToMarket(-1) trebuie sa fie apelat pentru a ne asigura ca toate pozitiile sunt marcate la piata cu preturile noi. Inregistrarile in VirtualEquity[] incep de la 0. Daca distanta in timp nu este 0, si noul tick soseste in intervalul de la deschiderea barei, noul tick este inregistrat pe acea bara, campul de close, cu stampilarea campului closetime. Maximul si minimul equity per bara sunt recalculate.
At the beginning, the class is automatically set up with an equity equal to the real account equity and a timespan of 0. Daca intervalul e 0, sau daca noul equity soseste prea tarziu, se va inregistra pe bara urmatoare, pana la ultimul subscript (499). Cand trebuie sa intre pe 500, tabloul este impins spre stanga cu o pozitie si bara 499 incepe sa fie inregistrata de la inceput. VirtualAddNewEquity() este apelata numai din clasa. De aceea, a fost desemnata ca metoda private.

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
void VirtualAccount::VirtualMarkToMarket(int asset_index) //marks a new equity value
    {
     double fluctuation=0.00;
     double rprice,new_equity;
     int p;
     if (asset_index<0)//mark all
       {
        for (int i=0;i<VirtualSymbolsCount;i++)
           {
            if (DoubleToString(VirtualSymbolsTable[i].position,2)=="0.00")
              continue;
            if (NormalizeDouble(VirtualSymbolsTable[i].position,2)>0.00)
              rprice=NormalizeDouble(SymbolInfoDouble(VirtualSymbolsTable[i].Symbol,SYMBOL_BID),SymbolInfoInteger(VirtualSymbolsTable[i].Symbol,SYMBOL_DIGITS));
            if (NormalizeDouble(VirtualSymbolsTable[i].position,2)<0.00)
              rprice=NormalizeDouble(SymbolInfoDouble(VirtualSymbolsTable[i].Symbol,SYMBOL_ASK),SymbolInfoInteger(VirtualSymbolsTable[i].Symbol,SYMBOL_DIGITS));                
            fluctuation=fluctuation+MathAbs(VirtualSymbolsTable[i].position)*SymbolInfoDouble(VirtualSymbolsTable[i].Symbol,SYMBOL_TRADE_CONTRACT_SIZE)*(rprice-VirtualSymbolsTable[i].marked_price);               
            VirtualSymbolsTable[i].marked_price=rprice;
            if (DoubleToString(VirtualSymbolsTable[i].sl,5)!="0.00000")
              {
               if (VirtualSymbolsTable[i].position>0.00&&rprice<=VirtualSymbolsTable[i].sl)
                 {
                  VirtualSymbolsTable[i].position=0.00;                     
                  VirtualSymbolsTable[i].tp=0.00;
                  VirtualSymbolsTable[i].sl=0.00;
                 }
               if (VirtualSymbolsTable[i].position<0.00&&rprice>=VirtualSymbolsTable[i].sl)
                 {
                  VirtualSymbolsTable[i].position=0.00;                     
                  VirtualSymbolsTable[i].tp=0.00;
                  VirtualSymbolsTable[i].sl=0.00;
                 }                    
              }
            if (DoubleToString(VirtualSymbolsTable[i].tp,5)!="0.00000")
              {
               if (VirtualSymbolsTable[i].position>0.00&&rprice>=VirtualSymbolsTable[i].tp)
                 {
                  VirtualSymbolsTable[i].position=0.00;                     
                  VirtualSymbolsTable[i].tp=0.00;
                  VirtualSymbolsTable[i].sl=0.00;
                 }
               if (VirtualSymbolsTable[i].position<0.00&&rprice<=VirtualSymbolsTable[i].tp)
                 {
                  VirtualSymbolsTable[i].position=0.00;                     
                  VirtualSymbolsTable[i].tp=0.00;
                  VirtualSymbolsTable[i].sl=0.00;
                 }                    
              }                         
           }
        new_equity=VirtualEquity[VirtualLastEquityPos].close+fluctuation;
        VirtualAddNewEquity(new_equity);  
       }                      
     else
       {
        if (DoubleToString(VirtualSymbolsTable[asset_index].position,2)!="0.00")
          {
           if (NormalizeDouble(VirtualSymbolsTable[asset_index].position,2)>0.00)
             {
              p=LONG;
              rprice=NormalizeDouble(SymbolInfoDouble(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_BID),SymbolInfoInteger(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));
             }
           if (NormalizeDouble(VirtualSymbolsTable[asset_index].position,2)<0.00)
             {
              p=SHORT;
              rprice=NormalizeDouble(SymbolInfoDouble(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_ASK),SymbolInfoInteger(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));                
             }
           //if (p==LONG)
           //  Print("Market marking @ ",DoubleToString(rprice,SymbolInfoInteger(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_DIGITS))," for ",VirtualSymbolsTable[asset_index].Symbol," ",DoubleToString(VirtualSymbolsTable[asset_index].position,2)," LONG @ ",DoubleToString(VirtualSymbolsTable[asset_index].price,SymbolInfoInteger(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_DIGITS)),"last mark @ ",DoubleToString(VirtualSymbolsTable[asset_index].marked_price,SymbolInfoInteger(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_DIGITS))," -> ",DoubleToString(new_equity,2)," by fl. ",DoubleToString(fluctuation,2));
           //if (p==SHORT)
           //  Print("Market marking @ ",DoubleToString(rprice,SymbolInfoInteger(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_DIGITS))," for ",VirtualSymbolsTable[asset_index].Symbol," ",DoubleToString(VirtualSymbolsTable[asset_index].position,2)," SHORT @ ",DoubleToString(VirtualSymbolsTable[asset_index].price,SymbolInfoInteger(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_DIGITS)),"last mark @ ",DoubleToString(VirtualSymbolsTable[asset_index].marked_price,SymbolInfoInteger(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_DIGITS))," -> ",DoubleToString(new_equity,2)," by fl. ",DoubleToString(fluctuation,2));                
           fluctuation=fluctuation+MathAbs(VirtualSymbolsTable[asset_index].position)*SymbolInfoDouble(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_TRADE_CONTRACT_SIZE)*(rprice-VirtualSymbolsTable[asset_index].marked_price);               
           VirtualSymbolsTable[asset_index].marked_price=rprice;
           if (DoubleToString(VirtualSymbolsTable[asset_index].sl,5)!="0.00000")
             {
              if (VirtualSymbolsTable[asset_index].position>0.00&&rprice<=VirtualSymbolsTable[asset_index].sl)
                {
                 VirtualSymbolsTable[asset_index].position=0.00;                     
                 VirtualSymbolsTable[asset_index].tp=0.00;
                 VirtualSymbolsTable[asset_index].sl=0.00;
                }
              if (VirtualSymbolsTable[asset_index].position<0.00&&rprice>=VirtualSymbolsTable[asset_index].sl)
                {
                 VirtualSymbolsTable[asset_index].position=0.00;                     
                 VirtualSymbolsTable[asset_index].tp=0.00;
                 VirtualSymbolsTable[asset_index].sl=0.00;
                }                    
             }
           if (DoubleToString(VirtualSymbolsTable[asset_index].tp,5)!="0.00000")
             {
              if (VirtualSymbolsTable[asset_index].position>0.00&&rprice>=VirtualSymbolsTable[asset_index].tp)
                {
                 VirtualSymbolsTable[asset_index].position=0.00;                     
                 VirtualSymbolsTable[asset_index].tp=0.00;
                 VirtualSymbolsTable[asset_index].sl=0.00;
                }
              if (VirtualSymbolsTable[asset_index].position<0.00&&rprice<=VirtualSymbolsTable[asset_index].tp)
                {
                 VirtualSymbolsTable[asset_index].position=0.00;                     
                 VirtualSymbolsTable[asset_index].tp=0.00;
                 VirtualSymbolsTable[asset_index].sl=0.00;
                }                    
             }                                       
          }
        new_equity=VirtualEquity[VirtualLastEquityPos].close+fluctuation;
        VirtualAddNewEquity(new_equity);             
       }
     return;
    }

VirtualMarkToMarket() este inima intregii clase. Poate fi apelat pentru o pozitie sau pentru toate pozitiile. Marcheza pozitiile la piata, calculand equity. Pentru fiecare pozitie calculeaza fluctuatia in equity de la ultimul pret marcat pana la pretul curent, care devine pret marcat. Pentru aceeasi pozitie, procedura va sterge stop loss si take profit odata atinse. Userul trebuie doar sa apeleze VirtualMarkToMarket(-1) la fiecare tick pentru a marca la piata toate pozitiile.

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
void VirtualAccount::VirtualChangePosition(int asset_index,int direction,double price,double lotsize) //direction is ORDER_TYPE_BUY or ORDER_TYPE_SELL
    {
     int p=VirtualGetPositionType(asset_index);
     double rprice,fluctuation,new_equity;
     double oldposition;            
     if (p==FLAT)
       {
        VirtualSymbolsTable[asset_index].price=price;
        VirtualSymbolsTable[asset_index].marked_price=price;
        VirtualSymbolsTable[asset_index].sl=0.00000;
        VirtualSymbolsTable[asset_index].tp=0.00000;
        if (direction==ORDER_TYPE_BUY)             
          VirtualSymbolsTable[asset_index].position=NormalizeDouble(lotsize,2);
        if (direction==ORDER_TYPE_SELL)
          VirtualSymbolsTable[asset_index].position=NormalizeDouble(-lotsize,2);
        //Print("Position set to ",VirtualSymbolsTable[asset_index].position," @ price ",DoubleToString(VirtualSymbolsTable[asset_index].price,SymbolInfoInteger(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_DIGITS) ) );
       }
     else
       {
        oldposition=VirtualSymbolsTable[asset_index].position;
        //local mark to market
        VirtualMarkToMarket(asset_index);           
        //position update
        VirtualSymbolsTable[asset_index].price=price;           
        if (direction==ORDER_TYPE_BUY)
          {
           VirtualSymbolsTable[asset_index].position=NormalizeDouble(VirtualSymbolsTable[asset_index].position+lotsize,2);
           if (oldposition<0.00&&VirtualSymbolsTable[asset_index].position>0.00||DoubleToString(VirtualSymbolsTable[asset_index].position,5)=="0.00000")
             {
              VirtualSymbolsTable[asset_index].sl=0.00000;
              VirtualSymbolsTable[asset_index].tp=0.00000;
             }
          }
        if (direction==ORDER_TYPE_SELL)
          {
           VirtualSymbolsTable[asset_index].position=NormalizeDouble(VirtualSymbolsTable[asset_index].position-lotsize,2);
           if (oldposition>0.00&&VirtualSymbolsTable[asset_index].position<0.00||DoubleToString(VirtualSymbolsTable[asset_index].position,5)=="0.00000")
             {
              VirtualSymbolsTable[asset_index].sl=0.00000;
              VirtualSymbolsTable[asset_index].tp=0.00000;
             }
          }              
        //Print("Position set to ",VirtualSymbolsTable[asset_index].position," @ price ",DoubleToString(VirtualSymbolsTable[asset_index].price,SymbolInfoInteger(VirtualSymbolsTable[asset_index].Symbol,SYMBOL_DIGITS) ) );
       }
    return;
   }

Pentru a face o tranzactie, trebuie sa folosesti VirtualChangePosition(), care va deschide o pozitie in directia data, cu pretul si marimea lotului specificata, pe activul specificat prin index. Aceasta deschidere este similara deschiderii unei “afaceri” (deal). De aceea, numai ORDER_TYPE_BUY si ORDER_TYPE_SELL sunt suportate. Nu exista ordine pending sau StopLimit. Functia doar seteaza sau modifica pozitii. De asemenea elimina stop loss-ul sau take profit daca pozitia isi inverseaza semnul.

Acum ca am realizat clasa, e timpul sa virtualizam expertul pe medii mobile din articolul
MQL5 : Un expert demonstrativ de portofoliu folosind medii mobile, intr-un fisier nou, VirtualPortfolioMA.mq5 .

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
//+------------------------------------------------------------------+
//|                                           VirtualPortfolioMA.mq5 |
//|                                       Copyright Bogdan Caramalac |
//|                                           http://mqlmagazine.com |
//+------------------------------------------------------------------+
#property copyright "Copyright Bogdan Caramalac"
#property link      "http://mqlmagazine.com"
#property version   "1.00"
 
#define FLAT        0
#define LONG        1
#define SHORT       2
 
input int Slippage=30;
input double MarginUsagePerPosition=0.5;
input bool VolatilityFilter=false;
input int VirtualStopLoss=300;
input int VirtualTakeProfit=1000;
input int SlowMALength=14;
input int FastMALength=9;
 
input int EquitySlowMAPeriod=14;
input int EquityFastMAPeriod=9;
input int RealStopLoss=300;
input int RealTakeProfit=1000;

Variabilele sunt cam aceleasi, cu diferenta ca acum stop loss, take profit si noile perioade ale mediilor mobile se impart in doua : pentru contul virtual si contul pe care ruleaza sistemul efectiv.

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
#include <TradeVirtualization.mqh>
#include <MovingAverages.mqh>
 
int SymbolsCount;
 
struct IndicatorHandlersStruct
  {
   int SlowMovingAverageHandler;
   int FastMovingAverageHandler;
   int BBoverSTDHandler;
  };
 
struct SymbolData
  {
    string Symbol;
    IndicatorHandlersStruct IndicatorHandlers;
    datetime LastBarTime;
  };
 
SymbolData SymbolsTable[30];
 
VirtualAccount VirtualSystem;    
 
datetime VirtualLastBarTime;
string RealTrading="FORBIDDEN";

Structurile sunt destul de similare. Clasa este instantiata in obiectul VirtualSystem. Timpul de deschidere al ultimei bare de equity este inregistrat in VirtualLastBarTime. Variabila RealTrading retine modul in care tranzactiile se copiaza din contul virtual in cel efectiv, si anume “STRAIGHT” (direct), “REVERSED” (invers), “FORBIDDEN” (interzis) (numai “STRAIGHT” si “FORBIDDEN” sunt utilizate efectiv).

VirtualMakeIndicatorHanders() ia locul lui MakeIndicatorHandlers(). Codul este la fel, doar numele este schimbat.

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
void VirtualPositionSetSLTP(int asset_index,int sl,int tp)
  {
   VirtualSystem.VirtualPositionSetSL(asset_index,sl);
   VirtualSystem.VirtualPositionSetSL(asset_index,tp);
  }  
 
void VirtualManagePosition(int asset_index,int operation)
  {
   MqlTradeRequest request;
   MqlTradeResult result;
   int p;
   p=VirtualSystem.VirtualGetPositionType(asset_index);
   double now_volume;
   double current_volume,v0;
   current_volume=VirtualSystem.VirtualGetPositionSize(asset_index);
   v0=current_volume;   
   request.action=TRADE_ACTION_DEAL;
   request.symbol=SymbolsTable[asset_index].Symbol; 
   request.deviation=Slippage;
   request.type_filling=ORDER_FILLING_AON;
   request.type_time=ORDER_TIME_GTC;
   if (p==FLAT)
     {
      request.volume=UnitsToLots( (MarginUsagePerPosition/100)*VirtualSystem.VirtualLastAccountEquity*AccountInfoInteger(ACCOUNT_LEVERAGE),SymbolsTable[asset_index].Symbol );
      if (operation==LONG)
        {
         request.type=ORDER_TYPE_BUY;
         request.price=NormalizeDouble(SymbolInfoDouble(SymbolsTable[asset_index].Symbol,SYMBOL_ASK),SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));
        }
      if (operation==SHORT)
        {
         request.type=ORDER_TYPE_SELL;
         request.price=NormalizeDouble(SymbolInfoDouble(SymbolsTable[asset_index].Symbol,SYMBOL_BID),SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));
        }
     }
   else//if (p==FLAT)
     {      
      if (p==LONG&&operation==SHORT)
        {
         request.volume=current_volume+UnitsToLots( (MarginUsagePerPosition/100)*VirtualSystem.VirtualLastAccountEquity*AccountInfoInteger(ACCOUNT_LEVERAGE),SymbolsTable[asset_index].Symbol );
         request.type=ORDER_TYPE_SELL;         
         request.price=NormalizeDouble(SymbolInfoDouble(SymbolsTable[asset_index].Symbol,SYMBOL_BID),SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));         
        }
      if (p==SHORT&&operation==LONG)
        {
         request.volume=current_volume+UnitsToLots( (MarginUsagePerPosition/100)*VirtualSystem.VirtualLastAccountEquity*AccountInfoInteger(ACCOUNT_LEVERAGE),SymbolsTable[asset_index].Symbol );
         request.type=ORDER_TYPE_BUY;
         request.price=NormalizeDouble(SymbolInfoDouble(SymbolsTable[asset_index].Symbol,SYMBOL_ASK),SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));
        }
      if (p==LONG&&operation==FLAT)
        {
         request.volume=current_volume;
         request.type=ORDER_TYPE_SELL;         
         request.price=NormalizeDouble(SymbolInfoDouble(SymbolsTable[asset_index].Symbol,SYMBOL_BID),SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));         
        }
      if (p==SHORT&&operation==FLAT)
        {
         request.volume=current_volume;
         request.type=ORDER_TYPE_BUY;         
         request.price=NormalizeDouble(SymbolInfoDouble(SymbolsTable[asset_index].Symbol,SYMBOL_ASK),SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));         
        } 
      if (p==LONG&&operation==LONG)//adjustment of the present LONG position
        {
         request.volume=UnitsToLots( (MarginUsagePerPosition/100)*VirtualSystem.VirtualLastAccountEquity*AccountInfoInteger(ACCOUNT_LEVERAGE),SymbolsTable[asset_index].Symbol )-current_volume;
         if (NormalizeDouble(request.volume,2)>0.00)
           {         
            request.type=ORDER_TYPE_BUY;
            request.price=NormalizeDouble(SymbolInfoDouble(SymbolsTable[asset_index].Symbol,SYMBOL_ASK),SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));
           }
         else //we close the difference
           {
            request.volume=MathAbs(request.volume);
            request.type=ORDER_TYPE_SELL;         
            request.price=NormalizeDouble(SymbolInfoDouble(SymbolsTable[asset_index].Symbol,SYMBOL_BID),SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));         
           }
        }        
      if (p==SHORT&&operation==SHORT)//adjustment of the present SHORT position
        {
         request.volume=UnitsToLots( (MarginUsagePerPosition/100)*VirtualSystem.VirtualLastAccountEquity*AccountInfoInteger(ACCOUNT_LEVERAGE),SymbolsTable[asset_index].Symbol )-current_volume;
         if (NormalizeDouble(request.volume,2)>0.00)
           {
            request.type=ORDER_TYPE_SELL;         
            request.price=NormalizeDouble(SymbolInfoDouble(SymbolsTable[asset_index].Symbol,SYMBOL_BID),SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));                  
           }
         else
           {
            request.volume=MathAbs(request.volume);
            request.type=ORDER_TYPE_BUY;
            request.price=NormalizeDouble(SymbolInfoDouble(SymbolsTable[asset_index].Symbol,SYMBOL_ASK),SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));            
           }
        }        
     }//if (p==FLAT)
   if (request.volume>0.00)
     {       
      VirtualSystem.VirtualChangePosition(asset_index,request.type,request.price,request.volume);
      VirtualPositionSetSLTP(asset_index,VirtualStopLoss,VirtualTakeProfit);      
     }
   //deciding to replicate , stay out or reverse signals on real portfolio
   if (RealTrading!="FORBIDDEN")
     {
      if (RealTrading=="STRAIGHT")
        ManagePosition(asset_index,operation);
      else
        {
         if (operation==LONG)
           ManagePosition(asset_index,SHORT);
         if (operation==SHORT)
           ManagePosition(asset_index,LONG);
         if (operation==FLAT)
           ManagePosition(asset_index,FLAT);
        }
     }
   return;     
  }

Aceasta este VirtualManagePosition. Opereaza ca ManagePosition() dar cererile ei sunt directionate catre contul virtual. La sfarsit, dupa ce analizeaza variabila RealTrading, decide sa nu faca nimic, sa replice identic sau inversat operatiunile din contul virtual in cel efectiv.

Functiile MASignal(), PositionSetSLTP() si ManagePosition() raman la fel ca in expertul original.

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
63
64
65
66
67
68
69
70
71
void VirtualTradeLogics(int asset_index)
  {
   string s;
   double stddev[1];
   double bbhi[1];
   double bblo[1];
   int copycount;
   s=MASignal(asset_index);    
   if (VolatilityFilter==true)
     {
      copycount=CopyBuffer(SymbolsTable[asset_index].IndicatorHandlers.BBoverSTDHandler,0,0,1,stddev);
      copycount=CopyBuffer(SymbolsTable[asset_index].IndicatorHandlers.BBoverSTDHandler,2,0,1,bbhi);
     }
   if (VirtualSystem.VirtualGetPositionType(asset_index)==FLAT)
     {      
      if (s=="L")
        {
         if (VolatilityFilter==true)
           {
            if (stddev[0]>bbhi[0])
              VirtualManagePosition(asset_index,LONG);
           }
         else
           VirtualManagePosition(asset_index,LONG);
        }
      if (s=="S")
        {
         if (VolatilityFilter==true)
           {
            if (stddev[0]>bbhi[0])
              VirtualManagePosition(asset_index,SHORT);
           }
         else
           VirtualManagePosition(asset_index,SHORT);
        }
     }//else if (GetPositionType(asset_index)==FLAT)
   else//position is not flat
     {
      if (VirtualSystem.VirtualGetPositionType(asset_index)==LONG)
        {        
         if (VolatilityFilter==true)
           {
            if (s=="S"||stddev[0]<bbhi[0])
              VirtualManagePosition(asset_index,FLAT);
           }           
         else
           {            
            if (s=="S")
              {               
               VirtualManagePosition(asset_index,FLAT);           
              }
           }//if (VolatilityFilter==true)
        }
      if (VirtualSystem.VirtualGetPositionType(asset_index)==SHORT)
        {         
         if (VolatilityFilter==true)
           {
            if (s=="L"||stddev[0]<bbhi[0])
              VirtualManagePosition(asset_index,FLAT);
           }
         else
           {            
            if (s=="L")
              {               
               VirtualManagePosition(asset_index,FLAT);           
              }
           }//if (VolatilityFilter==true)        
        }//if (VirtualSystem.VirtualGetPositionType(asset_index)==SHORT)
     }           
   return;
  }

Acesta este fostul TradeLogics(), dar modificat pentru a opera in contul virtual.

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
int PeriodToMinutes(ENUM_TIMEFRAMES p)
  {
   switch(p)
     {
      case PERIOD_M1:return(1);
      case PERIOD_M2:return(2);
      case PERIOD_M3:return(3);
      case PERIOD_M4:return(4);
      case PERIOD_M5:return(5);
      case PERIOD_M6:return(6);
      case PERIOD_M10:return(10);
      case PERIOD_M12:return(12);
      case PERIOD_M15:return(15);
      case PERIOD_M20:return(20);
      case PERIOD_M30:return(30);
      case PERIOD_H1:return(60);
      case PERIOD_H2:return(60*2);
      case PERIOD_H3:return(60*3);
      case PERIOD_H4:return(60*4);
      case PERIOD_H6:return(60*6);
      case PERIOD_H8:return(60*8);
      case PERIOD_H12:return(60*12);
      case PERIOD_D1:return(60*24);
      case PERIOD_W1:return(60*24*7);
      case PERIOD_MN1:return(60*24*30);
      default: return(0);
     }
  };

Aceasta
This function is used in OnInit pentru a seta distanta in timp a barelor contului virtual – ea converteste perioadele in minute.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   SymbolsTable[0].Symbol="EURUSD";
   SymbolsTable[1].Symbol="USDJPY";
   SymbolsTable[2].Symbol="GBPUSD";
   SymbolsTable[3].Symbol="USDCHF";
   SymbolsCount=1;
 
   VirtualSystem.VirtualAccountSet(AccountInfoDouble(ACCOUNT_EQUITY),PeriodToMinutes(Period())*60);  
   VirtualSystem.VirtualSymbolsCount=SymbolsCount;      
   for (int i=0;i<SymbolsCount;i++)
      VirtualSystem.VirtualSymbolsTable[i].Symbol=SymbolsTable[i].Symbol;
   VirtualMakeIndicatorHandlers();
   VirtualLastBarTime=VirtualSystem.VirtualEquity[VirtualSystem.VirtualLastEquityPos].opentime;
   RealTrading="FORBIDDEN";
   return(0);
  }

Acesta este OnInit(). Pregateste simbolurile la fel ca fostul OnInit() apoi copiaza simbolurile in contul virtual. Seteaza contul virtual si se asigura ca RealTrading e setat ca “FORBIDDEN”.

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
63
64
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {   
   double loweq;         
   double fast_ma_now,slow_ma_now,fast_ma_old,slow_ma_old;
   double EquityCloseBuffer[31];
   datetime datetime_array[1];   
   //marking to market virtual positions
   VirtualSystem.VirtualMarkToMarket(-1);
   //looping symbols to apply logics
   for (int i=0;i<SymbolsCount;i++)
      {
       CopyTime(SymbolsTable[i].Symbol,Period(),0,1,datetime_array);
       if (datetime_array[0]!=SymbolsTable[i].LastBarTime)
         {                  
          VirtualTradeLogics(i);
          SymbolsTable[i].LastBarTime=datetime_array[0];
         }//if (datetime_array[0]!=LastBarOccurred[i])
      }//for (int i=0;i<SymbolsCount;i++)   
 
   //now we check if we have enough equity bars ; if true, we analyse to see whether we activate or not live trading
   if (VirtualSystem.VirtualEquity[VirtualSystem.VirtualLastEquityPos].opentime!=VirtualLastBarTime)
     {
      if (VirtualSystem.VirtualLastEquityPos>ArrayRange(EquityCloseBuffer,0))  
        {
         for (int i=1;i<=ArrayRange(EquityCloseBuffer,0)-1;i++)
            {         
             EquityCloseBuffer[i]=StringToDouble( DoubleToString(VirtualSystem.VirtualEquity[VirtualSystem.VirtualLastEquityPos-ArrayRange(EquityCloseBuffer,0)+1+i].close,2) );             
             loweq=StringToDouble( DoubleToString(VirtualSystem.VirtualEquity[VirtualSystem.VirtualLastEquityPos-ArrayRange(EquityCloseBuffer,0)+1+i].low,2));                
            }                                    
        }
      fast_ma_now=SimpleMA(ArrayRange(EquityCloseBuffer,0)-1,EquityFastMAPeriod,EquityCloseBuffer);
      slow_ma_now=SimpleMA(ArrayRange(EquityCloseBuffer,0)-1,EquitySlowMAPeriod,EquityCloseBuffer);
      fast_ma_old=SimpleMA(ArrayRange(EquityCloseBuffer,0)-2,EquityFastMAPeriod,EquityCloseBuffer);
      slow_ma_old=SimpleMA(ArrayRange(EquityCloseBuffer,0)-2,EquitySlowMAPeriod,EquityCloseBuffer);
      //bullish ma cross on virtual system's equity line      
      if (fast_ma_now>slow_ma_now&&fast_ma_old<slow_ma_old)
        {          
         RealTrading="STRAIGHT";
         //replicating virtual portfolio into real
         //position sizes are calculated to be proportional with the real account equity
         for (int i=0;i<SymbolsCount;i++)
            ManagePosition(i,VirtualSystem.VirtualGetPositionType(i), 
                    UnitsToLots(
                     (VirtualSystem.VirtualGetPositionSize(i)*
                      AccountInfoDouble(ACCOUNT_EQUITY)/
                      VirtualSystem.VirtualLastAccountEquity)*
                      SymbolInfoDouble(SymbolsTable[i].Symbol,SYMBOL_TRADE_CONTRACT_SIZE)
                      ,SymbolsTable[i].Symbol) );
        }
      //bearish ma cross on virtual system's equity line        
      if (fast_ma_now<slow_ma_now&&fast_ma_old>slow_ma_old)
        {
         RealTrading="FORBIDDEN";
         //closing real positions
         for (int i=0;i<SymbolsCount;i++)
            ManagePosition(i,FLAT);         
        }             
      VirtualLastBarTime=VirtualSystem.VirtualEquity[VirtualSystem.VirtualLastEquityPos].opentime;
     }
   return;
  }

Acesta este OnTick(). Ca si OnTick() precednt, are un ciclu de simboluri si aplica logica pentru fiecare simbol, de aceasta data pe contul virtual. Declara EquityCloseBuffer[] unde ultimele valori ale barelor de equity din contul virtual sunt copiate, dupa ce fiecare ciclu este inchis. Cand aces buffer are destule valori, o intersectie de medii mobile pe linia de equity a contului virtual este calculata. Cand aceaste medii mobile dau un semnal de crestere, RealTrading trece pe “STRAIGHT”. Portofoliul este replicat din contul virtual pe contul real, portofoliul este replicat din contul virutal pe contul efectiv, proportional cu marimea contului efectiv. Desigur, VirtualManagePosition() va apela la randul lui ManagePosition() , iar din acest moment ambele conturi vor functiona simultan. Cand semnalul mediilor mobile este de scadere, RealTrading este setat la “FORBIDDEN” iar pozitiile contului efectiv sunt inchise.

OnDeinit() este exact la fel, dupa cum handlerele indicatorilor rezida in aceeasi SymbolsTable[].

Surprins? Executia pe portofoliu este mai instabila decat cea pe o singura pereche, asa cum a fost si pentru sistemul de trading principal. Volatilitatea suplimentara se traduce in miscari in range ale equity, care se transforma in semnale false date de mediile mobile pe equity, facand ca virtualizarea sa fie un fiasco. Exista cel putin trei puncte care trebuie luate in consideratie.

1. Frecventa samplingului
Care este frecventa optima de sampling pentru media mobila pe equity? La urma urmei, nu stim exact cum MetaTrader comprima barele de equity deci nu stim nici macar intervalul lui de sampling. Atunci, cum sa stim sa extragem mostrele pentru medie?

2. Sistemul de trading optim pentru linia de equity
Nimeni n-a zis ca mediile mobile peste equity sunt cea mai buna cale de ajustare. Alte criterii se pot aplica, la fel ca in tradingul obisnuit.

3. Ar trebui sa tranzactionam toate instrumentele in acelasi cont virtual, sau in conturi virtuale separate si sa ajustam linia de equity pentru fiecare?

Concluzia principala care se extrage e ca sistemele de trading nu trebuie sa fie bazate pe o aplicare rece, pura, a indicatorilor. Indicatorii, asa cum le spune si numele, indica unele actiuni care trebuie luate, dar precum consilierii care dau sfaturi, influenteaza pozitiv rezultatele sistemelor de trading, dar locul lor nu trebuie sa fie in miezul acestora. Mai degraba, statistici riguroase si relatii de arbitraj sunt la baza sistemelor de trading eficiente.

Linkuri:
TradeVirtualization.mqh
VirtualPortfolioMA.mq5

One Response to “ Virtualizarea sistemelor de trading pentru ajustarea liniei de equity - un mit ? ”

  1. Bogdan Caramalac, MQLmagazine sr.editor on July 10, 2010 at 4:15 pm

    Datorita unor erori conceptuale pe care nu le-am vazut la timp, la rularea virtualizarii pe portfolii, sistemul nu va tine cont de diferentele de valoare a pip-ului care exista intre diferitele perechi valutare; totodata, nici nu tine seama de diferenta dintre vanzare si cumparare (doar bid si ask sunt schimbate). Totusi, ajustarea programului pentru valoarea corecta a pip-ului nu a fost posibila intr-o maniera normala, ci ar fi trebuit folosite alte modalitati. Totusi, incepand cu build 292, de pe la 10 iulie, SYMBOL_TRADE_TICK_VALUE si SYMBOL_TRADE_TICK_SIZE intorc valori corecte. De aceea, Virtualization.mqh va fi adus la zi, dar numai ca fisier pus la dispozitie pentru download.
    De asemenea, un nou update va fi lansat curand, pentru a permite ordine pending.

Editii