Utolsó pozíciók lekérdezése

Nagyon rég volt már programozással kapcsolatos poszt, úgyhogy illik már ez ügyben is tennem valamit. Innentől kezdve kisebb-nagyobb posztokban fogok foglalkozni mindenki számára hasznos, gyakorta felmerülő funkciók megosztásával és részletesebb tárgyalásával.

Mai áldozatunk: hogyan kérdezhető le a nyitott pozíciók vagy a számlatörténet pozíciói közül a legutolsó pozíció ? Hogyan lehet lekérdezni a nyitott pozíciók darabszámát általunk megadott feltételek alapján? Sajnálatos tény, hogy az mql4 nyelvben nincsen egyetlen olyan beépített függvény sem, amely az életünket ilyen szempontból megkönnyítené. Hozzunk hát létre párat!

Mi számít legutolsó pozíciónak?

Természetesen ez is jelenthet mindenkinek mást, szóval definiáljuk: a legutolsó pozíció az időben legfiatalabb, azaz a legújabb pozíciót jelenti. 

Amennyiben a legelső (legkorábban nyitott) pozícióra vagyunk kíváncsiak, az megint más kérdés – abban az esetben ugyanis a legrégebben nyitott pozícióra gondolunk.

A legutolsó pozíciót egyébként nagyon könnyű azonosítani: mindig, minden körülmények között neki a legnagyobb a ticket száma. Ezt a tényt fogjuk használni a probléma megoldásához.

Ticket szám

A ticket szám a pozíció bróker által kiadott, egyértelmű és egyedi azonosító száma. Egy pozíció ticket száma soha nem változhat meg! A ticket számok a Számlatörténet és a Kereskedés panelen az első oszlopban láthatóak.

A függő megbízások (buy stop, buy limit, sell stop és sell limit) ticket számai nem változnak meg akkor, amikor a bróker teljesíti azt nekünk. Így a teljesült buy vagy sell pozíciók “öröklik” azon függő megbízás ticket számát, amelyből születtek.

Fontos továbbá, hogy amikor a függő pozícióból élő pozíció lesz, a pozícióhoz tartozó nyitóidőpont a kihelyezési dátumról a teljesülés dátumára fog változni. Ezért a nyitóidőpont alapján történő rendezés során úgy tűnhet, hogy a bróker felborította a ticket számok sorrendjét; ez azonban csak abból a tényből ered, hogy az adott pozíció függő típusként lett korábban létrehozva. Az alábbi képpel illusztrálom a helyzetet: jól látszik, hogy ticket szám ugyanaz, míg a nyitóidőpont és a típus természetesen eltérnek egymástól.

Függő pozícióból élő pozíció

Függő pozícióból élő pozíció

 

Szűrés egyéb feltételek alapján, pozíciók megkülönböztetése

Ömlesztett pozíciók közül történő horgászás esetén nyilván ezernyi szűrési feltételünk van. Ilyen lehet például a magic szám, pozíciótípus, nyitott-e még a pozíció, stb. Ezekre ki kell térnünk a programkódban is – én melegen ajánlom azt, hogy előre elkészített függvényeket használjunk és mindig, minden körülmények között szűrjünk magic számra. Ha most nem is futtattok több robotot, vagy kevertek kézi kötéseket a robot kötései mellé, a jövőben szükségetek lehet a pozíciók elkülönítésére. A magic szám használata mindenképpen fontos! A backtesztben ugyanis minden tökéletesen működik, hiszen ott alap esetben csak egy stratégia pozíciói léteznek. Nincsen kézi kötés, nincs másik futtatott robot. Ez azonban a valós idejű futtatásnál nincs mindig így – elég egyszer elfeledkeznünk arról, hogy nem voltunk elég körültekintőek! Elég egy kézi kötés, és az OrdersTotal() -ra épülő feltételeink azonnal elhasalnak.

Emiatt a beépített OrdersTotal() és OrdersHistoryTotal() függvényt is csak a pozíciók végignyálazása során használjuk majd – ebben a cikkben egy egyéni pozíciószámláló függvényt is létrehozunk majd.

Pozíciók listája

A pozíciók listájára kettő lista (tár, queue, “fiók”) áll rendelkezésünkre: az egyik az aktív pozíciók listája (MODE_TRADES), a másik pedig a már lezártaké (MODE_HISTORY).

Pozíciók kiválasztásához az OrderSelect() függvény használható, amelynek három paramétere van:

  1. egy szám, amely lehet az adott lista numerikus indexe, illetve konkrét ticket szám
  2. a kiválasztás módja (lásd lentebb)
  3. a lista, ahonnan dolgozni szeretnénk (MODE_TRADES vagy MODE_HISTORY; ha nem adunk meg semmit, az alapértelmezés a MODE_TRADES).

Hivatkozás egy konkrét pozícióra

Mindenképpen számmal lehet hivatkozni egy pozícióra, azonban erre két különböző mód létezik:

  1. hivatkozás a queue indexével (SELECT_BY_POS)
  2. hivatkozás konkrét ticket számmal (SELECT_BY_TICKET)

Amennyiben az utóbbit használjuk, nem szükséges megadni azt, hogy mely pozíciólistából guberálunk, hisz’ a ticket szám egyértelműen azonosítja a pozíciót. Nyissunk egy tetszőleges pozíciót (kézzel), és a ticket számát (1234567) helyettesítsük be az alábbi kódba:

OrderSelect (1234567, SELECT_BY_TICKET);
 
Print (OrderTicket(), " pozíció nyitóára: ", OrderOpenPrice());

Az OrderSelect() -nek megadtuk a konkrét pozíció ticket számát (1234567), valamint azt hogy ticket alapján szeretnénk azonosítani őt (SELECT_BY_TICKET). Abban az esetben (tehát majdnem mindig), ha nem tudjuk a konkrét ticket számot, a numerikus index alapján fogunk dolgozni.

OrderSelect (0, SELECT_BY_POS);
 
Print (OrderTicket(), " pozíció nyitóára: ", OrderOpenPrice());

A számlatörténet és az aktív pozíciók numerikus indexe mindig nullától indul, tehát az index legmagasabb értéke mindig OrdersTotal() – 1 vagy OrdersHistoryTotal() – 1. Amennyiben olyan indexű pozíciót akarunk kiválasztani amely nem létezik, hibaüzenetet fogunk kapni. Természetesen ez a nem létező ticket számokra is igaz!

Amennyiben sikeresen kiválasztottunk egy pozíciót, az adott pozíció minden adatához hozzáférünk mindaddig, amely más pozíciót nem választunk ki. Erre érdemes különös figyelmet fordítani: ha kiválasztottunk egy pozíciót, akkor a későbbiekben – pláne ha függvényeket használunk – előfordulhat, hogy egy függvényünk szintén OrderSelect() segítségével megváltoztatja az aktív kiválasztott pozíciót. Ekkor hirtelen olyan adatokkal fogunk dolgozni, amelyek nem passzolnak az adott helyzethez és káoszt okozhatnak. Mindig figyeljünk oda, hogy éppen mikor és melyik pozíciót választjuk ki!

A nem létező pozíciókkal kapcsolatban: az OrderSelect() függvény visszatérési értéke csak akkor true, ha sikerült kiválasztani az adott pozíciót.

if (OrderSelect(0, SELECT_BY_POS)) {
    Print ("Pozíció sikeresen kiválasztva!");
}
else {
    Print ("Pozíció kiválasztása sikertelen: ", GetLastError());
}

A GetLastError() segít a hiba megállapításában: egy nullánál nagyobb számot ad vissza, amennyiben valamilyen hiba történt. A hibakódokat itt olvashatjátok.

Pozíciók “átnyálazása”

Ahhoz, hogy biztonsággal megállapítsuk a számunkra fontos pozíciók ticket számait, mindig végig kell futni a számlatörténet/aktív pozíciólista pozícióit (vagylagosan!). Ez azt jelenti, hogy ha csak egy pozíció ticket számára van szükségünk, akkor is ki kell választanunk az összes pozíciót az adott listában. A kiválasztás mindenképpen szükséges, hiszen csak ezután tudjuk eldönteni, hogy az adott pozíció adatai alapján beleesik-e az általunk definiált kondíciókba.

Írassuk ki az összes BUY típusú EURUSD pozíciót!

(Megjegyzés: a továbbiakban nem fogom külön kérni, hogy nyissatok több pozíciót; legyen nyitva több pozíciótok vegyesen buy, sell, EURUSD, GBPUSD, tetszőleges instrumentumokon!)

for (int i = 0; i < OrdersTotal(); i++) {
    if (OrderSelect(i, SELECT_BY_POS)) {
        if (OrderSymbol() == "EURUSD" && OrderType() == OP_BUY) {
            Print (OrderTicket());
        }
    }
}

 A fenti kód egymás alá fogja kiírni az összes buy típusú EURUSD instrumentumon nyitott pozíciót. A ciklus nullától indul és addig fut, ameddig i < OrdersTotal() feltétel igaz. Ha tehát 5 pozíciónk van, akkor a pozíciók indexe 0-tól 4-ig terjed. A ciklus minden körben eggyel növeli i értékét. Ötször fut le tehát ciklusunk, ebből azonban csak annyiszor fogunk “bejutni” a második if feltételen belülre, ha EURUSD instrumentumú buy pozíció van éppen terítéken.

Mivel a pozícióindex folyamatosan változhat, javaslom hogy minden esetben ticket alapján dolgozzunk. Vagyis, ne reménykedjünk abban hogy az éppen nyitott pozíciónk 0-ás indexe sosem fog változni! Kérdezzük le a ticket számát, és későbbi hivatkozáskor is használjuk azt!

Nézzük meg, hogyan tudunk a fenti kódból egy egyszerű függvényt létrehozni!

void PozicioTipus (int Tipus, string Instrumentum = "") {
 
   // Ha nincs megadva az instrumentum,
   // az aktuális chart szimbólumát vesszük alapul
 
   if (Instrumentum == "") {
      Instrumentum = Symbol();
   }
 
   for (int i = 0; i &lt; OrdersTotal(); i++) {
      if (OrderSelect(i, SELECT_BY_POS)) {
         if (OrderSymbol() == Instrumentum &amp;&amp; OrderType() == Tipus) {
            Print (OrderTicket());
         }
      }
   }
}

A fenti függvényt a későbbiekben így használhatjuk:

PozicioTipus (OP_BUY);
PozicioTipus (OP_BUY, "EURGBP");

Természetesen a kód egy kis módosításával pillanatokon belül megszületik első pozíciószámláló függvényünk is, mintegy az utolsó pozíció keresése során folytatott út melléktermékeként:

int PozicioSzamlalo (int Tipus, int MagicNumber = 0, string Instrumentum = "") {
 
   int
      Darabszam = 0;
 
   // Ha nincs megadva az instrumentum,
   // az aktuális chart szimbólumát vesszük alapul
 
   if (Instrumentum == "") {
      Instrumentum = Symbol();
   }
 
   for (int i = 0; i &lt; OrdersTotal(); i++) {
      if (OrderSelect(i, SELECT_BY_POS)) {
         if (OrderSymbol() == Instrumentum &amp;&amp; OrderType() == Tipus &amp;&amp; OrderMagicNumber() == MagicNumber) {
            Darabszam++;
         }
      }
   }
 
   return (Darabszam);
}
 
// Használat:
 
// 5-ös magic számú buy pozíciók megszámlálása
Print (PozicioSzamlalo (OP_BUY, 5));
 
// Magic szám nélküli sell pozíciók megszámlása
Print (PozicioSzamlalo (OP_SELL));
 
// Magic szám nélküli, EURGBP -n nyitott buy limit pozíciók megszámlása
Print (PozicioSzamlalo (OP_BUYLIMIT, 0, "EURGBP"));

A függvény visszatérési értéke egy egész szám – az érintett pozíciók darabszáma. Természetesen a kódot érdemes kiegészíteni egy olyan feltétellel, hogy amennyiben Tipus értéke egy általunk definiált konstans (pl. OP_ALL), akkor az összes pozíciót számolja, ne csak egy konkrét típust. Ekkor már készen is vagyunk egy egyéni pozíciószámláló függvénnyel, de újabb konstansokat is létrehozhatunk, amely saját kényelmünket szolgálja: például OP_BUYSELL – buy és sell pozíciók “begyűjtésére”.

#define OP_ALL          6     // Minden pozíció
 
int PozicioSzamlalo (int Tipus = OP_ALL, int MagicNumber = 0, string Instrumentum = "") {
 
   int
      Darabszam = 0;
 
   // Ha nincs megadva az instrumentum,
   // az aktuális chart szimbólumát vesszük alapul
 
   if (Instrumentum == "") {
      Instrumentum = Symbol();
   }
 
   for (int i = 0; i &lt; OrdersTotal(); i++) {
      if (OrderSelect(i, SELECT_BY_POS)) {
         if (OrderSymbol() == Instrumentum &amp;&amp; (Tipus == OP_ALL || OrderType() == Tipus) &amp;&amp; OrderMagicNumber() == MagicNumber) {
            Darabszam++;
         }
      }
   }
 
   return (Darabszam);
}

Megjegyzés: idegen kódokban konstansok helyett gyakran találkozhatunk a konstansok értékeinek megfelelő számokkal (pl.: OP_BUY = 0, OP_SELL = 1, stb.). Habár ez is jó megoldás, véleményem szerint a későbbi kódolvasást megkönnyítendő érdemesebb inkább a konstansokat használni, hiszen így egy pillanat alatt egyértelmű hogy mely pozíciókra gondoltunk anno.

A legutolsó pozíció megkeresése

A megfelelő feltételek alapján már képesek vagyunk csak azon pozíciókkal foglalkozni, amelyek beleesnek az általunk fontosnak tartott halmazba. Innen már csak egy dolgunk van: a halmaz lehető legnagyobb ticket számának megállapítása!

#define OP_ALL          6     // Minden pozíció
 
int UtolsoPozicio (int Tipus = OP_ALL, int MagicNumber = 0, string Instrumentum = "") {
 
   int
      UtolsoTicket = 0;
 
   // Ha nincs megadva az instrumentum,
   // az aktuális chart szimbólumát vesszük alapul
 
   if (Instrumentum == "") {
      Instrumentum = Symbol();
   }
 
   for (int i = 0; i &lt; OrdersTotal(); i++) {
      if (OrderSelect(i, SELECT_BY_POS)) {
         if (OrderSymbol() == Instrumentum &amp;&amp; (Tipus == OP_ALL || OrderType() == Tipus) &amp;&amp; OrderMagicNumber() == MagicNumber) {
            UtolsoTicket = MathMax (UtolsoTicket, OrderTicket());
         }
      }
   }
 
   return (UtolsoTicket);
}

Lássuk, mi történik: a függvény kezdetekor létrehoztunk egy UtolsoTicket nevű változót. A cikluson belül a MathMax() függvénnyel megnézzük, hogy az aktuális pozíció ticket száma – OrderTicket() – nagyobb-e, mint az UtolsoTicket változó értéke. Amennyiben igen, átállítjuk az UtolsoTicket változó értékét a MathMax() által visszaadott, magasabb számértékre. A függvény visszatérési értéke így a halmazunkon belüli legmagasabb ticket számot fogja visszaadni: az időben legkésőbb nyílt, azaz a legutolsó pozíciót.

Ha a számlatörténetben is szeretnénk ugyanezt a keresést elvégezni, még egy kis bonyolítás szükséges: tegyük állíthatóvá azt, hogy for ciklusunk mely pozíciókon szaladjon végig!

int UtolsoPozicio (int Tipus = OP_ALL, int MagicNumber = 0, int Lista = MODE_TRADES, string Instrumentum = "") {
 
   int
      UtolsoTicket = 0,
      Darabszam = 0;
 
   // Ha nincs megadva az instrumentum,
   // az aktuális chart szimbólumát vesszük alapul
 
   if (Instrumentum == "") {
      Instrumentum = Symbol();
   }
 
   if (Lista == MODE_TRADES) {
      Darabszam = OrdersTotal();
   }
   else {
      Darabszam = OrdersHistoryTotal();
   }
 
   for (int i = 0; i &lt; Darabszam; i++) {
      if (OrderSelect(i, SELECT_BY_POS, Lista)) {
         if (OrderSymbol() == Instrumentum &amp;&amp; (Tipus == OP_ALL || OrderType() == Tipus) &amp;&amp; OrderMagicNumber() == MagicNumber) {
            UtolsoTicket = MathMax (UtolsoTicket, OrderTicket());
         }
      }
   }
 
   return (UtolsoTicket);
}

Függvényünk Lista nevű paramétere alapesetben a MODE_TRADES értéket kapja meg, azaz ha az aktív pozíciók közül kukázunk, akkor szükségtelen megadni. Ha a számlatörténet a kívánt cél, adjunk meg MODE_HISTORY -t! A Darabszam nevű változó a Lista alapján kapja meg értékét, és az OrderSelect() függvényt is kibővítettük a Lista nevű változóval. Így kerek a világ, most már tudunk az aktív és lezárt pozíciók között is keresgélni!

// Az utolsó BUY pozíció
// az aktív tradek közül
UtolsoPozicio (OP_BUY);
 
// Az utolsó 23-as magic számú
// BUY pozíció az aktív tradek közül
UtolsoPozicio (OP_BUY, 23);
 
// Az utolsó 23-as magic számú
// EURGBP BUY pozíció az aktív tradek közül
UtolsoPozicio (OP_BUY, 23, MODE_TRADES, "EURGBP");
 
// Az utolsó 23-as magic számú
// EURGBP BUY pozíció a lezárt tradek közül
UtolsoPozicio (OP_BUY, 23, MODE_HISTORY, "EURGBP");
 
// Az utolsó 23-as magic számú
// EURGBP pozíció a lezárt tradek közül
UtolsoPozicio (OP_ALL, 23, MODE_HISTORY, "EURGBP");

A függvény lefutásakor nem ír ki semmit, visszatérési értéke pedig a keresett ticket azonosító lesz. Ezt már elrakhatjuk egy változóba, felhasználhatjuk if feltételekben, kiírathatjuk a Print() függvénnyel. Ha visszatérési értéke nulla, akkor vagy nem talált megfelelő pozíciót a feltételek alapján, vagy nincs nyitott pozíció, stb.

A legelső pozíció megkeresése

Házi feladat: létrehozni a legelső pozíció kikeresésére vonatkozó függvényt. Talán már el sem kell árulnom, hogy a MathMin() függvényt érdemes használni a MathMax() helyett. Mivel azonban a ticket számot tároló változónk kiindulási értéke nulla, így mindenképpen értéket kell neki adnunk, hiszen a nulla egészen biztos, hogy minden ticket számnál kisebb lesz. A ciklus megfelelő if feltételén belül tehát hozzunk létre egy újabbat, ami csak arra figyel, hogy a végeredményt tároló változónk értéke nulla-e. Amennyiben igen, beállítjuk neki az első ciklusfordulóban érintett pozíció ticket számát – a többi ciklusfordulóban pedig ehhez képest keressük a kisebb ticket számot.

A számlatörténet korlátjai

Fontos figyelembe venni, hogy az OrdersHistoryTotal() figyelembe veszi a Metatrader terminálban a felhasználó által beállított múltbéli adatok mennyiségét. Azaz ha a Számlatörténet panelen az “Utolsó hónap” opció van beállítva, akkor semmilyen módon nem férhetünk hozzá az aktuális hónapnál régebben lezárt pozíciókhoz.

Az aktuális pozícióadatok pontossága

Felmerülhet a kérdés, hogy mi van akkor, ha az éppen aktuális tick-re elindult programunk futása közben a pozícióindex változik – azaz megszűnik vagy épp születik egy új pozíció. Megnyugtatásként: egy tickre éppen futó példány futási ideje alatt nem változik a pozíciódarabszám. Ha egy pozíció bróker által megszületik vagy lezárul, ez a történés hamarabb történik meg, mint a program futásának kezdete.

Természetesen ha mi zárunk vagy nyitunk (piacon, azaz mintha kézzel tevékenykednénk) egy pozíciót, a pozícióindex természetesen azonnal megváltozik. Ezeket a tényeket a program tervezésénél mindenféleképpen figyelembe kell venni! Főleg zárásnál okozhat ez problémát – de erről majd legközelebb.

Egyéb információk lekérdezése

A fenti for ciklussal megépített függvények egyértelműen sok más feladatra is használhatóak. Mivel már képesek vagyunk szűrni a számunkra fontos pozíciókat, megtudhatjuk például a pozíciótömeg átlagárát, összlotját, stb. Ezen funkciókra javallott létrehozni egy-egy specifikus függvényt, kialakítva ezzel egy alapvető támogatást későbbi munkáinkhoz.

Zárszó

Remélem, tudtam segíteni új információkkal! Amennyiben úgy érzed, kihagytam egy olyan információt ami nélkül nem lehet élni, jelezd!

És persze lájkolj és/vagy kommentelj, ha szeretnél még több ilyen cikket:)