Anatomia unui motor CEP simplu
[English version] [MQLmagazine.com in english] [Editia romaneasca]
Daca n-ar fi acea abreviere CEP probabil ati intreba cum ceva inerent complex ar putea deveni simplu. Chestia e ca totul incepe mai intai a fi simplu inainte de a evolua in ceva complicat.
Chiar si daca mi-ati citit articolul despre Progress Apama, va reamintesc definitia data CEP de Dan Hubscher : “abilitatea de a lega doua sau mai multe evenimente, chiar daca acestea apar intr-un interval de timp, si de a le determina semnificatia din acea relatie imediat, cum ar fi detectia unui semnal de trading, sau a unei oportunitati de genera alfa.”
Deci, dat fiind ca trebuie sa se formeze conexiuni intre evenimente, avand o constrangere temporala, trebuie sa existe o coada de evenimente, care stocheaza evenimente individuale pe masura ce acestea sunt receptionate. Desigur, stocarea nu are loc la infinit intr-o coada in continua crestere; marimea cozii trebuie sa fie dinamica, dar lungimea ei in timp trebuie sa nu depaseasca un interval de timp predeterminat, sa zicem 5 minute. De aceea un motor CEP, care este in principal pentru HFT si trading de Nivel II, nu trebuie sa fie amestecat cu evenimente de Nivel I, deoarece acestea se intind pe un interval de timp mult mai larg, ceea ce ar face nu numai ca stiva de evenimente sa creasca pana la o marime necontrolabila, ci ar si reduce viteza motorului.
Intervalul de timp acoperit este prima limitare a motorului.
A doua, este legatura dintre evenimentele simple. Evenimentele simple nu sunt complet independente, asa cum ar reiesi din webinarul Progress Apama. De exemplu, ai putea considera o intersectie a unei Benzi Bollinger superioare ca si eveniment, dar si intersectia Benzii Bollinger inferioare. Aceste evenimente sunt complet opuse. Daca s-ar intampla amandoua, cand al doilea are loc, primul ar trebui eliminat din coada de evenimente, dupa cum se vede in schema de mai jos, evenimentul 4 este sters. Se poate opta pentru doua cai : o varianta in care evenimentele opuse sunt cautate pe o perioada in coada de evenimente – perioada dificil de estimat – sau o alta in care inainte de validarea unui eveniment complex sunt cautate evenimente opuse aflate dupa evenimentele simple care constituie evenimentul complex.
In al treilea rand, este evitarea redundantei. Implementarile proaste pot cauza redundanta, adica acelasi eveniment sa fie raportat in continuu. De exemplu, “MSFT este sub media mobila de 5 minute”. O paranteza aici – nu ma refer la chartul de 5 minute, ci la media mobila a ticksilor care au aparut in ultimele 5 minute, de aceea am zis ca MetaTrader ar trebui sa aibe istoric la nivel de tick si perioada tick activata. Cand aceasta se intampla, e un eveniment. Dar in secunda urmatoare, la tickul urmator, va fi raportata din nou, iar motorul trebuie sa o respinga, pentru ca este deja in coada de evenimente, asa cum e cazul cu evenimentul colorat in portocaliu din schema, care nici nu ia un numar, pentru ca este respins de la inceput. Asemenea situatii ar trebui evitate din start – motorul nu ar trebui sa piarda timp cu verificarea redundantei, altfel ar trebui sa filtreze aceste evenimente, dar e mai bine ca fluxul de evenimente sa fie curatat dinainte.
Aceasta e schema de functionare a motorului. Evenimentele de tick, sau evenimente mai complexe provin din evenimentul OnChart() care este declansat de mai multi EA Tick Event, asa cum am descris in articolul Evenimente de tick in MQL5 : complicate deocamdata, dar functionale .
Prima coloana este numarul evenimentului din coada, de la cel mai recent la cel mai vechi. Fiecare eveniment simplu, care este legat la date, are un numar alocat. Acelasi eveniment, cand are loc pe un instrument diferit, are alocat un alt numar, asa ca aceeasi secventa de evenimente care formeaza un eveniment complex va fi construit din aceleasi evenimente simple dar cu alti identificatori. Motorul trebuie sa stie ce evenimente sunt opuse, de exemplu 1 si 3, de asemenea caror evenimente le este permis sa apara de mai multe ori in lant, pentru ca unora le va fi permis (de exemplu evenimentele de executie (cele care incep cu E in schema)). Tuturor celorlalte evenimente li se interzice sa apara de mai multe ori – dar toturi nu de catre motorul propriu-zis, care anima coada de evenimente – ci chiar din faza de preprocesare.
In acelasi timp, motorul CEP trebuie sa recunoasca fiecare eveniment complex cunoscand toate aranjamentele de evenimente simple.
De exemplu, daca un eveniment complex pentru Microsoft (MSFT) consta in “10 urmat de (11 sau (12 si 13))” in 3 minute, atunci motorul va cauta , in 3 minute : 10, 11 ; 10, 12, 13 ; 10, 13, 12 insemnand toate caile alternative. Daca acelasi eveniment ar fi mapat pentru SP500 (^GSPC) , dat fiind ca toate subevenimentele au numere diferite, conditia va arata gen “5 urmat de (15 sau (16 si 17))”, avand aranjamentele ; 5, 15 ; 5, 16, 17 ; 5, 17, 16. Asemenea aranjamente pot fi date direct motorului, scrise direct in interior, sau generate cu un algoritm, transformand expresia binara, cu diferenta notabila ca “URMAT DE” e un “SI” necomutativ.. Pentru ca regulile sunt construite ca serii de evenimente ce apar intr-o anumita ordine, negatia nu poate exista la fel ca intr-o expresie binara. Poate fi implementata doar ca si interdictie de aparitie a anumitor evenimente pe parcursul restrictiei temporale sau in anumite secvente , drept simple evenimente interzise intre evenimentele cerute.
Se poate vedea ca am inclus in schema doua coloane de asociere. Adica, un eveniment simplu poate fi asociat la mai mult decat un eveniment complex, pentru ca altfel anumite reguli nu s-ar declansa niciodata. Evenimentele complexe trebuie sa aiba un set diferit de ID-uri la evenimente, pentru a nu se intersecta cu cele simple. Motorul va trimite evenimentele complexe spre a fi procesate de utilizator in EventsCallback(), dar in acelasi timp, userul le poate trimite printr-o conducta la un alt motor CEP.
Algoritmul de potrivire incepe cu constructia evenimentelor simple per fiecare eveniment complex. Matricea evenimentelor simple va contine pe fiecare linie cate un aranjament. Pentru ca verificarea este facuta invers, de la evenimentele recente catre evenimentele mai vechi, aranjamentele trebuie sa fie scrise invers in matrice. Fiecare eveniment complex va necesita o trecere prin coada de evenimente numai cata vreme se intinde constrangerea temporala, intr-o maniera verticala: prima trecere va verifica evenimentele de pe prima coloana, si evenimentele simple gasite in acest fel vor fi pre-asociate. Urmatoarea trecere va fi pe a doua coloana numai pentru aranjamentele care au avut o pre-asociere pe prima. Cand nu mai au loc pre-asocieri noi, cautarea este anulata pentru evenimentul complex in cauza. Totusi, pentru a minimiza timpul necesar, fiecare trecere va privi toate evenimentele pana are loc inceperea urmatoarei treceri. Cand cautarile sunt anulate pentru toate evenimentele, algoritmul se termina.
Cand un aranajament are toate evenimentele simple pre-asociate, e timpul pentru verificarea negatiilor. Mai intai, se verifica toata perioada pentru negatiile globale – evenimentele interzise pe toata perioada evenimentului complex, apoi pentru evenimentele interzise intre evenimentele pre-asociate, si in final verificarea de timp pentru a ne asigura ca ele inca respecta constrangerea temporala, evenimentele simple sunt asociate evenimentului complex, exceptie atunci cand in tabelul de asocieri al evenimentului simplu nu se gaseste deja aceasta asociere – pentru a evita re-asocierea lui la acelasi eveniment complex.
Evenimentul complex rezultat este pus intr-o coada si transmis la EventsCallback(). Trimiterea evenimentelor la EventsCallback() trebuie sa aiba loc dupa terminarea algoritmului altfel ar putea aparea apelari recursive ale motorului care nu isi vor inceta executia.Pentru evenimentele simple care sunt asociate, pozitia in tabelul de asocieri va fi completata cu evenimentul complex, si pozitia va incrementata cu o unitate.
Aici e un exemplu cu doua evenimente complexe is stagii difererite de pre-asociere:
Pentru evenimentul de MSFT, motorul gaseste evenimentul 12 la prima trecere, si il pre-asociaza pe al treilea aranjament. La a doua trecere, gaseste evenimentul 13, si il pre-asociaza pe al doilea aranjament. Mai gaseste si 12 inca o data, dar nu il pre-asociaza pe al doilea aranjament pentru ca a aparut inainte de 13. Daca la urmatoarea trecere gaseste evenimentul mai vechi, 10, al treilea aranjament este complet, si, in lipsa negatiilor, daca restrictia temporala inca e aplicabila, in conditiile in care evenimentul complex 1 nu a fost asociat inca, atunci evenimentele simple de pe al treilea aranjament sunt asociate evenimentului complex 1, contorul de asocieri incrementat, evenimentul pus in coada de asocieri. Pentru evenimentul de ^GSPC (evenimentul complex 2), motorul a completat deja primul aranjament. Evenimentul 5 nu apare verificat pentru al doilea si al treilea aranjament intrucat evenimentele de pe coloanele precedente nu au fost verificate.
Cat despre evenimentele de executie, acestea sunt trimise de EnumCallbacks(), eveniment al clasei DealHandler din articolul a title=”Distingirea executiilor cvasisimultane – clasa de raportare a ultimelor tranzactii” href=”http://mqlmagazine.com/ro/programarea-in-mql/distingirea-executiilor-cvasisimultane-clasa-de-raportare-a-ultimelor-tranzactii/” target=”_top”>Distingirea executiilor cvasisimultane – clasa de raportare a ultimelor tranzactii. Evenimentele simple de executie pot fi mapate in evenimente mai complex de executie deoarece tranzactiile au numere magice si comentarii, si daca ar fi sa cumperi de doua ori aceleasi 1000 de actiuni, vei stie fiecare din ce eveniment complex face parte. De aceea asemenea evenimente pot fi tratate ori separat ori in combinatie cu evenimentele de piata.
Vom veni cu un exemplu de cod intr-un articol viitor.

