MQL5 : Implementarea unui motor CEP simplu

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

Aceasta editie a fost mai dificil de facut decat editiile precedente. Lansarea tarzie a Strategy Tester si numarul mare de defecte iesite la iveala in timpul realizarii motorului CEP au consumat foarte mult timp. Din acest moment, editiile revistei nu vor mai fi lunare, pentru ca vor contine foarte multa munca si imbunatatiri ale unor lucrari mai vechi. De aceea, de aceasta data adaugam Forumul.

Acum inapoi la motorul CEP.

Motorul CEP a fost unul din cele mai dificile lucruri posibile de scris in MQL5. A fost dificil nu numai pentru mine ca programator, ci a avut implicatii si asupra MT5 de asemenea, intrucat structura intortocheata de date pe care o foloseste a pus MetaTrader la incercare ; a necesitat munca de remediere a defectelor, in special la obiecte si alocari de memorie. Codul a reusit sa functioneze cu probleme minore abia la build-ul 270 (da, deja in mai) si complet curat pe build 271. Este un cod lung si dificil, pe care il voi sparge in bucati pentru a-l comenta. Am pastrat comentariile de linie si liniile de Print pentru a fi mai bine inteles de cititori. Algoritmul este usor modicat, nu e exact cel prezentat in Anatomia unui motor CEP simplu .

Urmatorul fisier va fi denumit CEPEngine.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
29
30
31
32
33
34
//+------------------------------------------------------------------+
//|                                                    CEPEngine.mqh |
//|                                       Copyright Bogdan Caramalac |
//|                                           http://mqlmagazine.com |
//+------------------------------------------------------------------+
#property copyright "Bogdan Caramalac"
#property link      "http://mqlmagazine.com"
 
#property version "1.0";
 
//*******************************************
 
string Replicate(string s,int n)
   {
   string res="";
   int k=1;
   if (n<1)
      return(res);                    
   for (k=1;k<=n;k++) 
       {
        StringConcatenate(res,res,s);
       }
    return(res);
   }          
 
string BoolToString(bool b)
  {
   if (b==true)
     return(".T.");
   else
     return(".F.");   
  }
 
//*******************************************

Acestea sunt functii string foarte simple, necesare pentru functiile mai mari care traduc structuri in mesaje string, descrieri ale structurilor CEP. Prima functie va replica un string, iar a doua va returna un string pentru o variabila logica, intr-un stil gen FoxPro.

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
struct SimpleEvent
  {
   public:
   uint EventID;
   uint EventTime;
   uint Reserved;
   ushort BindCount;
   uint Binds[100];
  };
 
   bool BindEvent(uint eventid,SimpleEvent& s)
     {
      bool fd;
      if (s.BindCount==0)
        {
         s.Binds[0]=eventid;
         s.BindCount++;
         return(true);
        }
      else
        {
         fd=false;
         //checking previous bind
         for (int i=0;i<s.BindCount;i++)
            {
             if (s.Binds[i]==eventid)
               {
                fd=true;
                break;
               }
            }//for (int i=0;i<s.BindCount;i++)
         if (fd==false)
           {
            if (s.BindCount<ArrayRange(s.Binds,0)-1)
              {
               s.Binds[s.BindCount]=eventid;
               s.BindCount++;
               return(true);
              }
            else
              return(false);
           }
         else
           return(false);
        }//else if (s.BindCount!=0)
     }
 
   int FindBind(uint eventid,SimpleEvent& s)
     {
      bool fd;
      if (s.BindCount==0)
        {
         return(-1);
        }
      else
        {
         fd=false;
         //checking previous bind
         for (int i=0;i<s.BindCount;i++)
            {
             if (s.Binds[i]==eventid)
               {
                return(i);
                break;
               }
            }//for (int i=0;i<s.BindCount;i++)
         return(-1);
        }
     }     
 
   bool UnbindEvent(uint eventid,SimpleEvent& s)
     {
      if (s.BindCount==0)
        return(false);
      int fd;
      fd=-1;
      for (int i=0;i<s.BindCount;i++)
         {
          if (s.Binds[i]==eventid)
            {
             fd=i;
             break;
            }
         }//for (int i=0;i<s.BindCount;i++)
      if (fd==0)
        return(false);
      else
        {
         for (int j=fd+1;j<s.BindCount;j++)
            {
             s.Binds[j-1]=s.Binds[j];
            }
         s.Binds[s.BindCount-1]=0;
         s.BindCount--;
         return(true);
        }//if (fd==0)
     }

Structura SimpleEvent este la miezul motorului. Coada de evenimente pe care am descris-o in articolul precedent este facuta din micute structuri SimpleEvent. EventID joaca rol de ID, EventTime este timpul (in ticksi – milisecunde de la pornirea Windows – nu formatul datetime), BindCount spune cator evenimente complexe este asociat evenimentul simplu, iar Binds[] contine aceste evenimente. Functiile BindEvent(), FindBind(), si UnbindEvent() sunt cele care lucreaza cu acesta structura. Normal, ar fi trebuit sa optez pentru o clasa in loc de structura, dar ce castigi in functionarea clasei pierzi in manevrabilitatea datelor : nu poti copia obiecte, decat camp cu camp, altfel primesti Structura are obiecte si nu poate fi copiata (Structure have objects and cannot be copied).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct SimpleEventArrangement
  {
   int EventCount;
   uint EventArray[100];
   int Prebinds[100];
   int NegationsCount[100]; //how many negation events before each event from the array
   uint NegationsArray[10][100]; //store negation events before each event from the array
   bool NegationsChecked[100];//checks of the first negations that match
  };
 
struct ComplexEvent
  {
   uint ComplexID;
   uint TimeConstraint;
   ushort ArrangementsCount;
   SimpleEventArrangement ArrangementsArray[100];
  };

Structura ComplexEvent este unitatea de baza a tabelei evenimentelor complexe. Va aduceti aminte imaginea din articolul precedent? Cam cuprinde ceea ce poate fi gasit in structura ComplexEvent. Astfel, avem ID-ul, constrangerea temporala, numarul de aranjamente (numarul de linii) sau versiuni ale cozilor de evenimente care constituie acelasi eveniment complex, si in final un tablou de aranjamente, constituit de mai multe SimpleEventArrangement.

Structura SimpleEventArrangement descrie ceea ce poate fi numita ca linie a matricii. Astfel, avem un contor si linia (adica EventArray[]) care contine evenimentele simple cerute. Tabloul Prebinds[] va nota, desigur, preasocierile acestor evenimente, adica pozitia in coada de evenimente simple care este gasita. NegationsCount[] va contine cate evenimente de negatie sunt cerute a nu aparea inainte de fiecare eveniment simplu care trebuie sa apara. Deci, un contor per fiecare eveniment simplu cerut. NegationsArray[] va contine evenimentele solicitate a nu aparea, pe coloane in loc de linii. NegationsChecked[] e un fel de tablou prescurtat de preasocieri. In loc sa stocheze locatiile negatiilor, un simplu steag este ridicat in acest tablou, pentru ca tabloul este unidimensional.

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
void MakeSimpleEventArrangement(string s,SimpleEventArrangement& arrg)
  {
   int len=StringLen(s+" ");
   string rdnow="";
   string c;
   uint number,swap;
   int normcount;
   arrg.EventCount=0;
   string first;
   int intg,d,crt;
   double dbl;
   s=s+" ";//to make sure it registers last word
   ArrayInitialize(arrg.EventArray,0);
   ArrayInitialize(arrg.NegationsCount,0);
   ArrayInitialize(arrg.NegationsArray,0);
   ArrayInitialize(arrg.Prebinds,-1);
   ArrayInitialize(arrg.NegationsChecked,false);
   normcount=-1;
   if (len!=0)
     {
      for (int i=0;i<len;i++)
         {
          c=StringSubstr(s,i,1);
          if (c==" ")
            {
             if (rdnow!="")
               {
                first=StringSubstr(rdnow,0,1);
                if (first=="-"||first=="!"||first=="n"||first=="N")
                  {
                   number=StringToInteger(StringSubstr(rdnow,1,StringLen(rdnow)-1));
                   if (normcount!=-1)   
                     {
                      arrg.NegationsArray[normcount][arrg.NegationsCount[normcount]]=number;
                      arrg.NegationsCount[normcount]++;
                     }                  
                  }
                else
                  {
                   normcount++;
                   number=StringToInteger(StringSubstr(rdnow,0,StringLen(rdnow) ));
                   arrg.EventArray[normcount]=number;
                   arrg.EventCount=normcount+1;
                  }
                rdnow="";
               }//if (rdnow!="")                
            }//if (c==" ")
          else
            {
             StringConcatenate(rdnow,rdnow,c);
            }
         }//for (int i=0;i<len;i++)
       if (rdnow!="")
         {
          first=StringSubstr(rdnow,0,1);
          if (first=="-"||first=="!"||first=="n"||first=="N")
            {
             number=StringToInteger(StringSubstr(rdnow,1,StringLen(rdnow)-1));
             if (normcount!=-1)   
               {
                arrg.NegationsArray[normcount][arrg.NegationsCount[normcount]]=number;
                arrg.NegationsCount[normcount]++;
               }                  
            }
          else
            {
             normcount++;
             number=StringToInteger(StringSubstr(rdnow,0,StringLen(rdnow) ));
             arrg.EventArray[normcount]=number;
             arrg.EventCount=normcount+1;
            }
          rdnow="";
         }//if (rdnow!="")
 
      //reversing event order
      if (arrg.EventCount>0)
        {
         dbl=NormalizeDouble(arrg.EventCount/2,0);
         intg=NormalizeDouble(MathRound(NormalizeDouble(arrg.EventCount,0)/2),0);
         if (dbl==intg)
           d=0;
         else
           d=1;           
         crt=0;         
         for (int i=0;i<=intg-1-d;i++)
            {             
             swap=arrg.EventArray[i];
             arrg.EventArray[i]=arrg.EventArray[arrg.EventCount-1-crt];
             arrg.EventArray[arrg.EventCount-1-crt]=swap;             
             for (int l=0;l<ArrayRange(arrg.NegationsArray,0);l++)
                {                 
                 swap=arrg.NegationsArray[l][i];
                 arrg.NegationsArray[l][i]=arrg.NegationsArray[l][arrg.EventCount-1-crt];
                 arrg.NegationsArray[l][arrg.EventCount-1-crt]=swap;
                }
             crt++;
            }
        }
 
     }//if (len!=0)
   return;
  }

Functia MakeSimpleArrangement() va construi un aranjament de evenimente simple, dintr-un sir, asa cum e instruita de user. De exemplu, “10 !6 11″ inseamna “10, urmat de 11, dar 6 trebuie sa nu apara inainte de 11″. Bara de spatiu este separator, iar steagul de negatie poate fi “-”,”!”,”n” sau “N”. De notat ca inainte de sfarsit, datele sunt inversate, de la prima la ultima, dupa cum am spus in evenimentul precedent, iar aranjamentele de evenimente complexe vor incepe de la cele mai recente catre cele mai vechi, Aceasta functie nu trebuie sa fie apelata de utilizator, ci va fi apelata de MakeComplexEvent().

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
void MakeComplexEvent(string s,uint eventid,uint time,ComplexEvent &cplx)
  {
   int len=StringLen(s+" ");
   string rdnow="";
   string c;
   string arrg;
   ushort cnt;
   s=s+" ";
   cplx.ComplexID=eventid;
   cplx.TimeConstraint=time;
   cplx.ArrangementsCount=0;
   if (len!=0)
     {
      for (int i=0;i<len;i++)
         {
          c=StringSubstr(s,i,1);
          if (c==";")
            {
             if (rdnow!="")
               {
                MakeSimpleEventArrangement(rdnow,cplx.ArrangementsArray[cplx.ArrangementsCount]);
                cplx.ArrangementsCount++;
                rdnow="";
               }//if (rdnow!="")
            }//if (c==" ")
          else
            StringConcatenate(rdnow,rdnow,c);
         }//for (int i=0;i<len;i++)
      //for last word
      if (rdnow!="")
        {
         MakeSimpleEventArrangement(rdnow,cplx.ArrangementsArray[cplx.ArrangementsCount]);
         cplx.ArrangementsCount++;
         rdnow="";
        }//if (rdnow!="")
     }//if (len!=0)
   return;
  }

Functia MakeComplexEvent() trebuie sa fie apelata de utilizator cand spune motorului cum trebuie sa arate evenimentele complexe. Definitia este un sir mare cu mai multe evenimente simple, separate de punct si virgula. Alti parametri mai sunt ID-ul, constrangerea temporala si o variabila eveniment complex in care sa fie asezat rezultatul.

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
string ComplexEventToString(ComplexEvent& c)
  {
   string res="";
   res=res+"ComplexID "+DoubleToString(c.ComplexID,0)+"n";
   res=res+"Arrangements "+DoubleToString(c.ArrangementsCount,0)+"n";
   for (int i=0;i<c.ArrangementsCount;i++)
      {
       res=res+"Events: "+DoubleToString(c.ArrangementsArray[i].EventCount,0)+"n";
       for (int j=0;j<c.ArrangementsArray[i].EventCount;j++)
          {
           res=res+" "+DoubleToString(c.ArrangementsArray[i].EventArray[j],0);
          }
       res=res+"n";       
       res=res+"Negations:n";
       for (int j=0;j<10;j++)
          {
           for (int k=0;k<c.ArrangementsArray[i].EventCount;k++)
              {
               res=res+" "+DoubleToString(c.ArrangementsArray[i].NegationsArray[j][k],0);
              }
           res=res+"n";
          }
       res=res+"n";
      }
   return(res);
  }

Aceasta functie a fost facuta numai din ratiuni de comentare. Functia descrie un eveniment complex intr-o forma sir. Poti scoate rezultatul pe ecran cu Comment().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CEPEngine
  {
   //setup elements
   private:
   uint QueueTimeConstraint;
   ComplexEvent ComplexEventsTable[30];
   ushort ComplexEventsCount;
   //data
   SimpleEvent EventsQueue[1000];    
   int OldestIndex;
   uint OldestTime;
 
   public:
   void CEPEngine();//constructor
   void ClearEvents();//cleans event table
   void AddComplexEvent(ComplexEvent &c);   
   virtual void EventsCallback(uint EventID);//here you treat raised events from the CEP Engine
   void SetupEngineMilliseconds(uint ms);
   void RaiseEvent(uint EventID);
   void EngineRun();
   string StatusString();
   };

In final, am ajuns si la clasa motorului CEP.
QueueTimeConstraint este o variabila importanta, care spune lungimea in timp pentru care motorul se va uita in coada. Evenimentele simple care sosesc, gasite in EventsQueue[] si care trec dincolo de “orizontul de timp” sunt suprascrise. OldestIndex este capatul tabloului ; este un capat dinamic, calculat la fiecare noua apelare RaiseEvent() din exterior, respectand QueueTimeConstraint, si are timpul, in ticksi, in OldestTime.

Acum lucrurile mici:

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
   void CEPEngine::ClearEvents()  //cleans event table
     {
      for (int i=0;i<ArrayRange(ComplexEventsTable,0);i++)
         {
          ComplexEventsTable[i].ArrangementsCount=0;
         }
      for (int i=0;i<ArrayRange(EventsQueue,0);i++)
          {
           EventsQueue[i].EventID=0;
           EventsQueue[i].EventTime=0;
           EventsQueue[i].BindCount=0;
           ArrayInitialize(EventsQueue[i].Binds,0);
          }
      OldestIndex=-1;
      OldestTime=0;
      ComplexEventsCount=0;
     }  
 
   void CEPEngine::CEPEngine()
     {
      ClearEvents();
     }
 
   void CEPEngine::AddComplexEvent(ComplexEvent &c)
     {
      ComplexEventsTable[ComplexEventsCount]=c;
      ComplexEventsCount=ComplexEventsCount+1;
     }
 
 
   void CEPEngine::SetupEngineMilliseconds(uint ms)
     {
      QueueTimeConstraint=ms;
      return;
     }
 
 
   void CEPEngine::EventsCallback(uint EventID)//here you treat raised events from the CEP Engine
     {
     }   
 
    string CEPEngine::StatusString()
       {
        string res="";
        string eventtext;
        int etl;
        if (ComplexEventsCount!=0)
          {
           for (int c=0;c<ComplexEventsCount;c++)
              {
               eventtext="["+DoubleToString(ComplexEventsTable[c].ComplexID,0)+"]::";
               etl=StringLen(eventtext);
               res=res+eventtext;
               if (ComplexEventsTable[c].ArrangementsCount!=0)
                 {
                  for (int a=0;a<ComplexEventsTable[c].ArrangementsCount;a++)
                     {                     
                      if (ComplexEventsTable[c].ArrangementsArray[a].EventCount!=0)
                        {                         
                         for (int e=0;e<ComplexEventsTable[c].ArrangementsArray[a].EventCount;e++)
                            {
                             res=res+DoubleToString(ComplexEventsTable[c].ArrangementsArray[a].Prebinds[e],0)+"  ";
                            }
                         res=res+"n"+Replicate(" ",etl);
                         for (int e=0;e<ComplexEventsTable[c].ArrangementsArray[a].EventCount;e++)
                            {
                             res=res+BoolToString(ComplexEventsTable[c].ArrangementsArray[a].NegationsChecked[e])+"  ";
                            }
                         res=res+"n";
                         if (a!=ComplexEventsTable[c].ArrangementsCount-1)
                           {
                            res=res+Replicate(" ",etl);
                           }                      
                        }//if (ComplexEventsTable[c].ArrangementsArray[a].EventCount!=0)                   
                     }//for (int a=0;a<ComplexEventsTable[c].ArrangementsCount;a++)
                 }//if (ComplexEventsTable[c].ArrangementsCount!=0)
              }//for (int c=0;c<ComplexEventsCount;c++)           
          }//if (ComplexEventsCount!=0) 
        return(res);
       }

Deci, ClearEvents() goleste coada de evenimente simple. Aceasta este si ratiunea pentru care este apelata din constructorul CEPEngine(). AddComplexEvent() adauga un nou eveniment complex la tabela. Trebuie sa fie apelat de utilizator, dupa construirea evenimentelor cu MakeComplexEvent(). Motorul e gata de lucru numai dupa ce constrangerea temporala e setata de utilizator cu SetupEngineMilliseconds(). Si, in final, EventsCallback() este metoda virtuala care trebuie sa fie redefinita de utilizator, fiind apelata cand motorul CEP ridica un eveniment complex. Functia StatusString() produce o versiune comentariu a starii motorului.

Acum ceva mai complicat : RaiseEvent() : cand utilizatorul ridica un eveniment simplu

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
    void CEPEngine::RaiseEvent(uint EventID)
      {
       long time=GetTickCount();
       bool isforbidden=true;
       uint opposite=0;
       uint difft;
       if (OldestIndex>0)
         {
          for (int i=0;i<ArrayRange(EventsQueue,0);i++)
             {
              if (EventsQueue[i].EventTime==0)
                {
                 OldestIndex=i-1;
                 OldestTime=EventsQueue[i-1].EventTime;
                 break;
                }
              if (time-EventsQueue[i].EventTime>QueueTimeConstraint)
                {
                 OldestIndex=i-1;
                 OldestTime=EventsQueue[i-1].EventTime;
                 break;
                }//if (time-EventsQueue[i].EventTime>QueueTimeConstraint)  
             }//for (int i=0;i<OldestIndex+1;i++)
         }//if (OldestIndex!=0)
       else
         {
          if (OldestIndex<=0)
            {
             OldestIndex=0;
             OldestTime=time;
            }
         }
       for (int j=OldestIndex+1;j>0;j--)
          {
           EventsQueue[j].EventID=EventsQueue[j-1].EventID;
           EventsQueue[j].EventTime=EventsQueue[j-1].EventTime;
           EventsQueue[j].BindCount=EventsQueue[j-1].BindCount;
          }
       ArrayInitialize(EventsQueue[0].Binds,0);
       EventsQueue[0].EventID=EventID;
       EventsQueue[0].EventTime=time;
       EventsQueue[0].BindCount=0;
       OldestIndex=OldestIndex+1;
 
       if (ComplexEventsCount!=0)
         {
          EngineRun();
         }         
       return;
      }//raise event ends

Functia are un nivel moderat de complexitate.
Liniile 7 – 32 stabilesc orizontul de timp in interiorul cozii. Adica stabilesc care este cel mai vechi eveniment din coada care respecta constrangerea temporala a intregii cozi.
Liniile 33 – 38 misca coada cu un pas mai jos, facand loc pentru un nou eveniment simplu.
Line 74 – metoda EngineRun() este apelata pentru a procesa noile date, daca cel putin un eveniment complex este inregistrat in tabela.

Inima motorului CEP este metoda EngineRun().Este cea mai complicata piesa de cod din intreaga clasa. Dar, n-ar exista masina fara motor, nu ?

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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
   void CEPEngine::EngineRun()
     {
      long time=GetTickCount();
      bool any_binded;
      int CrtColumn[100][100];//address CrtColumn as CrtColumn(event,arrangement)
      bool bypass=false;
      int q,p;
      int j,lastj;
      int test;
      if (ComplexEventsCount==0)
        return;
      ArrayInitialize(CrtColumn,0);
      for (int c=0;c<ComplexEventsCount;c++)
         {
          Print("Arrangements in complex event ",c," are ",ComplexEventsTable[c].ArrangementsCount);
          if (ComplexEventsTable[c].ArrangementsCount!=0)
            {                          
             for (int a=0;a<ComplexEventsTable[c].ArrangementsCount;a++)
                {      
                 //Check if the current simple event within arrangement within current complex event is to be binded
                 //by checking with simple events from the queue
                 p=0;                                       
                 while (time-EventsQueue[p].EventTime<=ComplexEventsTable[c].TimeConstraint)
                   {
                    Print("Looking on entry ",p," where is simple event ",EventsQueue[p].EventID," comparing with ",ComplexEventsTable[c].ArrangementsArray[a].EventArray[CrtColumn[c][a]]," on complex event ",c,", arrg. ",a," (column ",CrtColumn[c][a],") vs count ",ComplexEventsTable[c].ArrangementsArray[a].EventCount);                       
                    if (EventsQueue[p].EventID==ComplexEventsTable[c].ArrangementsArray[a].EventArray[CrtColumn[c][a]])
                      {
                       Print("Found simple event ",EventsQueue[p].EventID," for column ",CrtColumn[c][a]," of arrangement ",a," of event ",c," meaningly id=",ComplexEventsTable[c].ComplexID); 
                       ComplexEventsTable[c].ArrangementsArray[a].Prebinds[CrtColumn[c][a]]=p;
                       CrtColumn[c][a]++;                                                                                        
                       bypass=false;
                       if (CrtColumn[c][a]>1)
                         {
                          q=ComplexEventsTable[c].ArrangementsArray[a].Prebinds[CrtColumn[c][a]-2];
                          //-2 instead of -1 because we already incremented the CrtColumn by ++
                          Print("Checking negations from previous bind, ",q," to current element ",p);
                          q=q+1;
                          //Checking negations; if test passes, simple event is prebinded;                                                          
                          //q is an older event than the current one, p, so q>p
                          if (ComplexEventsTable[c].ArrangementsArray[a].NegationsCount[a]!=0)
                            {                                                
                             while (q<p)
                               {
                                for (int j=0;j<ComplexEventsTable[c].ArrangementsArray[a].NegationsCount[a];j++)
                                   {
                                    lastj=j;
                                    Print("Comparing event on ",q," meaningly ",EventsQueue[q].EventID," with negation ",ComplexEventsTable[c].ArrangementsArray[a].NegationsArray[j][CrtColumn[c][a]-2+1]);
                                    if (EventsQueue[q].EventID==ComplexEventsTable[c].ArrangementsArray[a].NegationsArray[j][CrtColumn[c][a]-2+1])
                                      {
                                       ComplexEventsTable[c].ArrangementsArray[a].NegationsChecked[j]=true;
                                       break;
                                      }
                                   }
                                 if (ComplexEventsTable[c].ArrangementsArray[a].NegationsChecked[lastj]==true)
                                   break;
                                 q++;
                                }
                            }//if (ComplexEventsTable[c].ArrangementsArray[a].NegationsCount!=0)
                          if (ComplexEventsTable[c].ArrangementsArray[a].NegationsChecked[lastj]==true)
                            {
                             Print("Erasing last bind on ",CrtColumn[c][a]-1,". Decreasing current column for complex ",c,", arrangement ",a);
                             ComplexEventsTable[c].ArrangementsArray[a].Prebinds[CrtColumn[c][a]-1]=-1;//erase prebind;                                  
                             CrtColumn[c][a]--;
                             bypass=true;
                             break;
                            } 
                         }//if (CrtColumn[c,a]>1)                            
                       if (bypass==false)//meaning,if no negations are checked                          
                         {
                          if (ComplexEventsTable[c].ArrangementsArray[a].EventCount==CrtColumn[c][a])//all prebinds are done
                            {
                             //checking time constraint again
                             if (GetTickCount()-EventsQueue[ComplexEventsTable[c].ArrangementsArray[a].Prebinds[ComplexEventsTable[c].ArrangementsArray[a].EventCount-1]].EventTime<=ComplexEventsTable[c].TimeConstraint)
                               {
                                //before binding, will check if any of these events is previously binded to the same event
                                any_binded=false;
                                for (int e=0;e<ComplexEventsTable[c].ArrangementsArray[a].EventCount;e++)
                                   {
                                    Print("Prebind",ComplexEventsTable[c].ArrangementsArray[a].Prebinds[e]," bind location of complex event id ",ComplexEventsTable[c].ComplexID," : ",FindBind(ComplexEventsTable[c].ComplexID,EventsQueue[ComplexEventsTable[c].ArrangementsArray[a].Prebinds[e]]));
                                    if (FindBind(ComplexEventsTable[c].ComplexID,EventsQueue[ComplexEventsTable[c].ArrangementsArray[a].Prebinds[e]])!=-1)
                                      {
                                       any_binded=true;
                                       Print("One event is already binded to complex event ",ComplexEventsTable[c].ComplexID);
                                       break;
                                      }
                                   }
                                if (any_binded==false)
                                  {
                                   //binding event
                                   for (int e=0;e<ComplexEventsTable[c].ArrangementsArray[a].EventCount;e++)
                                      {
                                       Print("Binding simple event on ",ComplexEventsTable[c].ArrangementsArray[a].Prebinds[e]," to ",ComplexEventsTable[c].ComplexID);
                                       BindEvent(ComplexEventsTable[c].ComplexID,EventsQueue[ComplexEventsTable[c].ArrangementsArray[a].Prebinds[e]]);
                                      }                       
                                   EventsCallback(ComplexEventsTable[c].ComplexID);//raise complex event for user to handle                                   
                                  }//if (any_binded==false)                                
                                break;//search exhausted for current arrangement of current complex event
                                      //it's either complete, so pointless to look forward
                                      //or one of the simple events is binded to the same complex event, again pointless
                               }//if (GetTickCount()-EventsQueue[ComplexEventsTable[c].ArrangementArray[a].Prebinds[...]].EventTime<=ComplexEventsTable[c].TimeConstraint)
                             else//it's a flop: completed too late; deleting binds
                               {                                      
                                for (int f=0;f<ComplexEventsTable[c].ArrangementsArray[a].EventCount;f++)
                                   {
                                    Print("Unbinding simple event from ",ComplexEventsTable[c].ArrangementsArray[a].Prebinds[f]," to ",ComplexEventsTable[c].ComplexID);
                                    UnbindEvent(ComplexEventsTable[c].ComplexID,EventsQueue[ComplexEventsTable[c].ArrangementsArray[a].Prebinds[f]]);
                                    ComplexEventsTable[c].ArrangementsArray[a].Prebinds[f]=-1;
                                    ComplexEventsTable[c].ArrangementsArray[a].NegationsChecked[f]=false;
                                   }//for (int f=0;f<ComplexEventsTable[c].ArrangementsArray[a].EventCount;f++)
                                 break;//no point to look forward , time`s up already
                               }//else if (GetTickCount()-EventsQueue[ComplexEventsTable[c].ArrangementArray[a].Prebinds[...]].EventTime<=ComplexEventsTable[c].TimeConstraint)
                            }//if (ComplexEventsTable[c].ArrangementsArray[a].EventCount==CrtColumn[c][a])
                         }//else if (bypass==true)                                                                                                 
                      }//if (EventsQueue[p].EventID==ComplexEventsTable[c].ArrangementsArray[a].EventArray[CrtColumn[c,a]])
                    p++;
                   }//while (time-EventsQueue[p].EventTime<=ComplexEventsTable[c].TimeConstraint)
                }//for (int a=0;a<ComplexEventsTable[c].ArrangementsCount;a++)
            }//if (ComplexEventsTable[c].ArrangementsCount!=0)
         }//for (int c=0;c<ComplexEventsCount;c++)       
 
      for (int c=0;c<ComplexEventsCount;c++)
         {          
          if (ComplexEventsTable[c].ArrangementsCount!=0)
            {
             for (int a=0;a<ComplexEventsTable[c].ArrangementsCount;a++)
                {                     
                 if (ComplexEventsTable[c].ArrangementsArray[a].EventCount!=0)
                   { 
                    if (CrtColumn[c][a]<ComplexEventsTable[c].ArrangementsArray[a].EventCount)
                      {
                       //removing prebinds
                       for (int f=0;f<ComplexEventsTable[c].ArrangementsArray[a].EventCount;f++)
                          {
                           ComplexEventsTable[c].ArrangementsArray[a].Prebinds[f]=-1;
                           ComplexEventsTable[c].ArrangementsArray[a].NegationsChecked[f]=false;
                          }//for (int f=0;f<ComplexEventsTable[c].ArrangementsArray[a].EventCount;f++)                         
                      }//if (CrtColumn[c][a]<ComplexEventsTable[c].ArrangementsArray[a].EventCount)
                   }//if (ComplexEventsTable[c].ArrangementsArray[a].EventCount!=0)                       
                }//for (int a=0;a<ComplexEventsTable[c].ArrangementsCount;a++)
            }//if (ComplexEventsTable[c].ArrangementsCount!=0)
         }//for (int c=0;c<ComplexEventsCount;c++)          
      return;
     }

Functia consta in doua cicluri. Primul este ciclul principal, liniile 13 – 117, iar al doilea ciclu este 120 – 140. Al doilea ciclu are un rol de curatare care va fi explicat mai jos.

Primul ciclu va trece prin ComplexEventsTable, ca sa vada care evenimente complexe apar. Pentru fiecare eveniment complex din tabela, aranjamentele vor fi verificate, si pentru fiecare aranjament, o serie de proceduri vor fi indeplinite.
In interiorul acestui ciclu, alt ciclu va trece prin EventsQueue[], (liniile 23 – 115) de la primul la ultimul, uitandu-se dupa evenimente ce respecta constrangerea temporala a evenimentului complex curent. Una din cele mai importante variabile in acesta metoda este tabloul CrtColumn[]. Acesta stocheza pointerul curent (coloana curenta in tabloul unidimensional EventArray[]). Astfel, procedura verifica evenimentul simplu curent cerut de tabela cu cel gasit in coada (linia 26) , il preasociaza si incrementeaza valoarea CrtColumn[] pentru aranjamentul curent al evenimentului complex (liniile 29 – 31). Deoarece CrtColumn[] este intializat cu zerouri, daca gaseste primul eveniment, ajunge la 1. Acum, daca CrtColumn[] e mai mare decat 1 (insemnand ca al doilea eveniment a fost gasit, si se pregateste pentru al treilea) este necesar sa verifice daca intre evenimentele precedente (adica primul si al doilea, in acest caz) exista evenimente de negatie (acelea care sunt interzise de a aparea), numai daca NegationsCount[] pentru acel eveniment simplu specific este nonzero (liniile 40 – 59). Daca un eveniment de negatie apare, se va intrerupe cautarea – nu mai are sens, caci aranjamentul curent nu mai poate aparea. De asemenea seteaza variabila bypass la true, pentru ca partea de cod ce urmeaza, care e totusi doar o tehnica de programare pentru a face listingul sa arate mai bine – acest cod va fi executat daca nu apar negatii intre timp. Din moment ce tabloul de evenimente este bazat pe zero, evenimentul maxim care poate fi asociat este EventCount-1, si, daca CrtColumn[] pentru aranjamentul curent este egal cu EventCount, atunci toate evenimentele au fost preasociate. Evenimentul complex este aproape gata. Ceea ce urmeaza se intampla numai daca cel mai vechi eveniment inca verifica constrangerea temporala. Va verifica daca cel putin unul din evenimentele pre-asociate in aceasta trecere a fost deja asociat definitiv evenimentului complex analizat. In acest caz, va ridica true pentru steguletul any_binded, si va intrerupe. Daca steguletul nu e ridicat, atunci evenimentul complex este completat in acest aranjament, evenimentele simple sunt asociate evenimentului complex, EventsCallback() este apelata pentru a-l notifica pe utilizator, si cautarea completa este intrerupta (liniile 90-96). Altfel pre-asocierea este anulata – s-a intamplat prea tarziu. Evenimentele sunt dezasociate, negatiile si pre-asocierile sterse, cautarea este intrerupta – nu mai are sens caci e prea tarziu deja.

Apoi vine al doilea ciclu, liniile 121 – 141. Intrucat evenimentele sunt mapate de la cel mai apropiat la cel mai vehci, ele sunt raportate utilizatorului , creand evenimente complexe incomplete a caror “umbra” se manifesta in viitor oprind acelasi eveniment de la raportare, daca ar aparea din nou, dar fiind construit din alte evenimente simple. Toate pre-asocierile sunt eliminate, numai asocierile definitive ramanand valide. De notat, totusi, ca acelasi eveniment complex, conceptual, poate fi anuntat de mai mult decat o singura daca, de aranjamente diferite, daca acestea contin siruri de evenimente simple complet diferite (de exemplu 15 si 16 sau 11 si 10 , daca toate apar, insemnand acelasi eveniment, vor fi raportate amandoua aranjamentele ca fiind evenimente complexe cu acelasi ID, dar distincte), pentru ca motorul nu poate sti ca evenimentul a mai aparut si acestea inseamna acelasi lucru.

Poti intreba despre negatii, caci vezi ca negatiile sunt specifice evenimentelor, fiecare eveniment avand propriile sale evenimente de negatie intre el si evenimentul precedent. Motorul curent nu suporta negatii globale. Totusi, daca vrei ca anumite evenimente sa fie negatii globale, acestea trebuie sa fie setate intre fiecare doua evenimente simple consecutive. Primul eveniment din sir poate avea negatii asociate, dar acestea nu vor fi verificate, pentru ca nu exista un orizont temporal pentru a fi verificate. Negatiile sunt valide numai intre evenimente simple consecutive.

Acesta este un exemplu de script care foloseste motorul CEP intr-un test. Testul consta in transmiterea de evenimente abstracte catre motorul CEP. Sa-l numim ceptest.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
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
//+------------------------------------------------------------------+
//|                                                      ceptest.mq5 |
//|                                        Copyright Bogdan Caramalac|
//|                                           http://mqlmagazine.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
 
#include <CEPEngine.mqh>
 
 
//our class: we needed it to override EventsCallback;
class LocalCEPEngine : public CEPEngine
  { 
    void EventsCallback(uint EventID)
     {
      Print("COMPLEX EVENT RECOGNIZED: ",EventID);
     }   
  };
 
//the CEP engine object
LocalCEPEngine myengine;
 
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   uint tickstart,tick;
   ulong AbstractEvent;
   ComplexEvent c;
   //setting up engine time span
   myengine.SetupEngineMilliseconds(10000);
   //creating complex events
   MakeComplexEvent("10 !6 11 ; 10 12 13 ; 10 13 12",1111,2000,c); //the event for MSFT;  
   myengine.AddComplexEvent(c);   
   //note the events with ! are negations
   MakeComplexEvent("5 !7 15 ; 5 !7 16 17 ; 5 !7 17 !7 !9 16",2222,3000,c); //the event for ^GSPC;
   myengine.AddComplexEvent(c); 
 
   tickstart=GetTickCount();
   tick=tickstart;
 
//alternate tests:
   myengine.RaiseEvent(10);   
   Print("*****************************************************************************************************************");
   myengine.RaiseEvent(6);   
   Print("*****************************************************************************************************************");   
   myengine.RaiseEvent(11);   
   Print("*****************************************************************************************************************");
   myengine.RaiseEvent(10);
   Print("*****************************************************************************************************************");
   myengine.RaiseEvent(11);   
   Print("*****************************************************************************************************************");
   return;
}

Aceasta de exemplu genereaza urmatoarea iesire:

Te invit sa verifici logurile, pentru a vedea cum functioneaza sistemul. Incearca alte teste:
Like this:

1
2
3
4
5
6
//alternate tests:
   myengine.RaiseEvent(10);   
   Print("*****************************************************************************************************************");
   myengine.RaiseEvent(11);
   Print("*****************************************************************************************************************");
   return;

Sau asa:

1
2
3
4
5
6
7
//alternate tests:
   myengine.RaiseEvent(10);   
   Print("*****************************************************************************************************************");
   myengine.RaiseEvent(6);   
   Print("*****************************************************************************************************************");   
   myengine.RaiseEvent(11);   
   Print("*****************************************************************************************************************");

Sau asa:

1
2
3
4
5
6
7
8
//alternate tests:
   myengine.RaiseEvent(10);   
   Print("*****************************************************************************************************************");
   myengine.RaiseEvent(11);   
   Print("*****************************************************************************************************************");
   myengine.RaiseEvent(10);
   Print("*****************************************************************************************************************");
   return;

Sau asa:

1
2
3
4
5
6
7
8
9
10
//alternate tests:
   myengine.RaiseEvent(10);   
   Print("*****************************************************************************************************************");
   myengine.RaiseEvent(11);   
   Print("*****************************************************************************************************************");
   myengine.RaiseEvent(10);
   Print("*****************************************************************************************************************");
   myengine.RaiseEvent(11);   
   Print("*****************************************************************************************************************");
   return;

Acum ma vei intreba cum poti afla care sunt combinatiile de evenimente simple care sa fie legate ca evenimente complexe. Care evenimente complexe stau la baza algoritmilor de frecventa medie? Cum se pot cauta?

Ei bine, nu am absolvit CQF-ul lui Paul Wilmott. Nu-ti pot spune asta. Sunt un simplu absolvent de finante din Romania, probabil urmand sa fiu prajit in mizeria ce sta sa vina in sectorul public in care din pacate lucrez. Am dat doar o sugestie minora despre mecanica acestei cautari in articolul despre sistemele cu reglare automata. Daca ai creierii si puterea de a merge mai departe, si de crezi in valoarea de fata a lucrurilor, corectitudinea educatiei si alte chestii blah blah – incearca sa-ti faci un master in Finante Computationale – sau in Finante Cantitative – ar trebui sa fie de-ajuns. Daca nu poti sa-ti finantezi unul, incearca Finlanda. Mi se pare ca Hanken e o solutie destul de cinstita – daca ai nervii de a trece prin hartii si intrebari. Altfel, studiaza singur si implementeaza acasa – cu toate limitele decurgand de aici!

Linkuri:
CEPEngine.mqh
ceptest.mq5

Editii