MQL4 könyv   Egyszerű MQL4 programok   Egyszerű Expert Advisor

Egyszerű Expert Advisor

Ebben a részben megvizsgáljuk egy egyszerű kereskedő Expert Advisor létrehozásának a szempontjait.

Feladat

29. feladat: Hozz létre egy kereskedő Expert Advisort.

Előzetes okoskodás

Egy kereskedő Expert Advisor programozásának elkezdése előtt ajánlott meghatározni a jövőbeli program általános elveit. Nincsenek szigorú szabályok a programok létrehozására. Azonban a programozó, ha egyszer létrehozott egy programot, azt általában később továbbfejleszti. Hogy a későbbiekben is megértse a programot, az alapgondolattal összhangban létre kell hoznia egy egyszerű folyamatábrát (ez különösen akkor fontos, ha a programot egy másik programozó fogja tovább fejleszteni). A legkényelmesebb program az, ami olyan funkcionális blokkokból áll, amelyek közül mindegyik különböző résszámításokért felelős. Hogy létre tudjuk hozni egy kereskedő Expert Advisor algoritmusát, elemezzük, hogy egy működő programnak mit kell tennie.

A kereskedelmi kérések adásának az egyik legfontosabb feltétele az ügyfélterminálban már meglévő megbízásokkal kapcsolatos információ. Némelyik kereskedelmi stratégia csak egyirányú megbízásokat enged meg. Általában, ha egy stratégia megengedi, hogy több megbízás legyen nyitva egy terminálban egy időben, a számukat akkor is ésszerű korlátok között kell tartani. Bármilyen stratégiát is használunk, a kereskedelmi döntések meghozása előtt figyelembe kell venni az aktuális helyzetet. Mielőtt egy kereskedelmi döntést hozunk egy programban tudni kell, hogy milyen megbízások vannak nyitva és milyen függőben lévő megbízások vannak elhelyezve. Először is egy programnak tartalmaznia kell a megbízások nyilvántartásának blokkját, ami az első között hajtunk végre.

Egy kereskedő EA végrehajtása alatt döntéseket kell hozni, amely döntések megvalósítása a kereskedelmi műveletek végrehajtásához vezet. Azt a kódrészt ami a kereskedelemért felelős, a kereskedelmi kéréseket kialakítja jobb, ha egy különálló blokkban írjuk. Egy Expert Advisor ki tud alakítani a kereskedelmi kéréseket, adhat új függőben levő vagy piaci megbízást, módosíthatja vagy bezárhatja a meglévő megbízásokat. Egy EA-nak képesnek kell lennie, a felhasználótól függetlenül a megbízások árának kiszámítására.

Egy programban a kereskedelmi döntéseket a kereskedelmi ismertetőjelek alapjain kell meghozni. Az egész program sikere a kereskedelmi ismertetőjelek értékelésének a helyességétől függ. Mikor a kereskedelmi ismertetőjeleket értékeli a program számításba vesz minden olyan információt ami hasznos lehet. Például, egy Expert Advisor elemezni tudja a technikai indikátorértékek kombinációját, a fontos sajtóközlemények idejét, az aktuális időt, az árszintek értékeit. Célszerű a kereskedés ismertetőjelei számításáért felelős programrészt is egy különálló blokkba írni.

Egy kereskedő Expert Advisornak szükségképpen hibákat kell feldolgoznia a blokkok végrehajtása alatt. Elemeznie kell azokat a hibákat, amelyek a kereskedelmi műveletek végrehajtása alatt történtek, megismételhet egy kereskedelmi kérést, a lehetséges konfliktushelyzetekről tájékoztatja a felhasználót.

Egy egyszerű Expert Advisor szerkezete

Lent látható egy egyszerű Expert Advisor szerkezeti terve több funkcionális blokkból felépítve, mindegyik blokkban a számítások egy bizonyos különálló része történik.


109. ábra. Egy egyszerű Expert Advisor szerkezeti terve.

Az EA fejlesztésének a következő lépésében még nincs programkódunk. Ugyanakkor program algoritmus már nagymértékben kialakult. Hogyan az EA hogyan fog működni azt a folyamatábra alapján első ránézésre könnyen megérthetjük a blokknevek és a köztük lévő kapcsolat alapján (vezérlés átadás).

A programkezdés után a vezérlést az előzetes feldolgozás blokkja kapja. Ebben a blokkban néhány általános paramétert elemezhetünk. Például ha nincs elegendő bár az ablakban (a bárok nélkülözhetetlenek ahhoz, hogy kiszámítsuk a technikai indikátorok paramétereit)  egy EA nem lesz képes a megfelelő működésre. Ebben az esetben az EA-nak be kell fejeződni, amiről előzetesen tájékoztatnia kell a felhasználót. Ha általános működési akadályok nincsenek, a vezérlést megkapja a számla ellenőrző blokk.

A számla ellenőrző blokkban ellenőrizzük az adott szimbólumon (aminek az ablakhoz az EA-t hozzácsatoltuk) lévő megbízások számát és azok tulajdonságait. Az egyéb szimbólumok megbízásait figyelmen kívül hagyjuk. Ha a programozott kereskedelmi stratégia csak piaci megbízásokkal dolgozik (és nem használ függőben levő megbízásokat) a függőben lévő megbízások jelenlétét észlelni kell. Ha a stratégia csak egy tőzsdei megbízást használ, de ténylegesen több megbízás van, ezt a tényt szintén vizsgálni kell. A számla ellenőrző blokk feladata (ebben a folyamatábrában) az, hogy meghatározza, az aktuális kereskedési helyzet egyezik-e az elvárttal azzal, amiben az EA megfelelően tud működni. Ha a helyzet megfelelő, vezérlést megkapja a következő blokk, hogy folytassa az EA munkáját, ha nem, az EA-t be kell fejezni és ezt a tényt jelenteni kell a felhasználónak.

Ha megbízások nincsenek a terminálban, vagy a létező megbízások száma és tulajdonságai megfelelnek annak, amire számítottunk, a vezérlést megkapja kereskedelem feltételeit ellenőrző blokk. Ebben a blokkban minden ismertetőjelet, ami nélkülözhetetlen a kereskedelemhez megvizsgálunk, mégpedig abból a szempontjai, hogy nyitni, zárni vagy módosítani kell-e a megbízást. Tovább kerül a vezérlés a megbízásokat záró blokkba.

Könnyű megérteni, hogy miért ajánlott a tervben előrébb venni azt a blokkot, ahol a megbízásokat bezárjuk, annál a blokknál, ahol a megbízásokat nyitjuk. Mindig ésszerűbb először a létező megbízásokat feldolgozni (bezárni vagy módosítani) és csak azután új megbízásokat nyitni. Általában úgy kell kereskedni, hogy lehetőleg minél kevesebb megbízás legyen nyitva. Ennek a blokknak a végrehajtása alatt minden megbízást, aminek a zárási feltételei fennállnak, be kell zárni.

Minden szükséges megbízást lezártunk, majd a vezérrés az új megbízás méretét kiszámító blokkba kerül. Sok algoritmus van a kötésméret kiszámítására. A legegyszerűbbek fix lot mérettel dolgoznak. Célszerű ezt az algoritmust a stratégiák tesztelésére használni. Népszerűbb módszer a megbízás méretét a szabad margin összegétől függően meghatározni, például 30-40 százalék. Ha a szabad margin nem elég, a program befejezi a munkáját, és az okról tájékoztatja a felhasználót.

A kötésméret meghatározása után, a vezérlést megkapja az a blokk, ahol az új megbízások megnyitása történik. Ha minden feltétel, amit korábban ellenőriztünk megfelelő akkor ebben a blokkban kerül sor a megfelelő típusú kereskedelmi kérés kialakítására.

Itt van egy hibaelemező blokk az Expert Advisorban. Ha bármilyen kereskedelmi művelet hibás, a vezérlés (csak ebben az esetben) átkerül a hibafeldolgozó-blokkba. Ha a hiba, amit visszaküldött a szerver vagy az ügyfélterminál nem végzetes, az EA ismét megkísérli végrehajtani a kereskedelmi műveletet. Ha egy kulcsfontosságú hiba kódját kapjuk vissza (például a számlát zárolták) az EA-nak be kell fejeznie a munkáját. Emlékezzünk, az MQL4-ben a programoknak nincs lehetősége eltávolítani az EA-t a szimbólum ablakából (ebben különböznek a scriptektől, lásd: Különleges függvények). Hiba esetén a munkamenet befejezését a start() függvény végét jelenti. A start() függvény egy új ticknél újra indul, azonban bizonyos flag változókkal megtilthatjuk a további kereskedést, (ebben az esetben egy kritikus hiba következményeként) a flag változók elemezése hozhat olyan eredményt, hogy a különleges start() függvényből kilépünk, és így az új kereskedelmi kérés képződését megakadályozzuk. Ajánlott flag elemzését az előzetes feldolgozás blokkjában elhelyezni.

Kereskedési stratégia

A piaci árak állandóan mozognak. A piac állapotát az idő bármelyik pillanatában jellemezhetjük az ármozgás tendenciájával - erős egyirányú árváltozás (emelkedés vagy esés), vagy lassú oldalazó ármozgás egy bizonyos átlagtól való enyhe eltérésekkel. Ezek a piaci jellemzők feltételesek, mert tiszta ismertetőjelek nincsenek, ami szerint a trendet vagy az oldalazást tudjuk azonosítani. Például lehetnek oldalazó mozgások olyan erős kilengésekkel, amelyeket nem lehet besorolni sem a trendbe sem az oldalazásba. Általában azt feltételezzük, hogy a piac főleg az oldalazó tartományban van és a trend az idő 15-20 százalékában zajlik.


110. ábra. Oldalazás és trend a piacon.

A kereskedelmi stratégiák hagyományosan két fő csoportba oszthatók. Az első csoport a sávozós stratégiákat tartalmazza. A fő elképzelés az ilyen stratégiákban az, hogy egy bizonyos ár elmozdulás után az árnak vissza kell térnie az előző pozíciójához, vagyis a megbízásokat az utolsó ármozgással ellentétes irányban nyitjuk. A második stratégia csoport a trend stratégiák csoportja, amikor a megbízásokat az ármozgások irányban nyitjuk. És vannak az összetett (kombinált) stratégiák. Az ilyen stratégiák sok különböző tényezőt vesznek figyelembe, melyek alapján a kerekedő egyaránt kereskedik az oldalazásban és a trendben. Technikailag egyik stratégia megvalósítása sem nehéz – Az MQL4 tartalmaz minden szükséges eszközt ehhez. A fő feladat a saját stratégia létrehozásakor az, hogy kereskedelmi jelzéseket keressünk és értelmezzünk.

A kereskedés feltételei

Ebben a példában egy trend kereskedő Expert Advisort fogunk építeni, olyat ami az ármozgás irányában nyitja a megbízásokat. Tehát keressük a különféle technikai indikátorok között azokat, amelyek jelzik egy trend kezdetét. A kereskedési feltételek meghatározására az egyik módszer a különböző átlagolási időszakú MA-k kombinációjának az elemzése. A 111. és 112. ábrán láthatjuk két különböző MA (11 és 31 átlagolt időszakokkal) pozícióját különböző piaci helyzetekben. A rövidebb periódusú átlag (piros vonalak) közelebb van az árfolyamhoz, változékonyabb, mozgékonyabb. A hosszabb periódusú mozgó átlag (kék vonal) lustább, jobban lemarad a piaci ártól és messzibb van attól. A figyelmünket az olyan helyekre összpontosítjuk, ahol a két MA keresztezi egymást és megpróbáljuk eldönteni, hogy a MA kereszteződés tényét használhatjuk-e kereskedelmi jelzésként.


111. ábra. MA(11) és MA(31) kereszteződése mikor az ármozgás iránya változik.

111. ábrán egy olyan piaci helyzetet látunk ahol az ármozgás irányában nyitunk megbízást az MA kereszteződéskor. Az A pontban az emelkedő piros vonal alulról felfelé keresztezi a kéket, majd a piaci ár továbbra is nő. Később a fordított MA kereszteződés jelzi az ármozgás irányváltozást. Ha az A pontnál nyitunk egy Buy megbízást és azt bezárjuk a B pontban, akkor profitot fogunk kapni, ami arányos az A és B árak különbségével.


112. ábra. MA(11) és MA(31) kereszteződése, amikor az ármozgás iránya megváltozik.

Ugyanakkor van olyan piaci helyzet amikor az MA-k kereszteződnek, de ez nem vezet további jelentős áremelkedéshez vagy eséshez (112. ábra). A megbízások, amelyeket ilyen esetben az MA kereszteződésnél nyitottunk, veszteséghez fognak vezetni. Ha Sell-t nyitunk az A pontban és azt zárjuk a B pontban, az ilyen kereskedés veszteséges lesz. Ugyanazt mondhatjuk egy Buy megbízásról, amit a B pontban nyitunk és a C pontban zárunk.

Az MA kereszteződés alapján megvalósított stratégia sikere attól függ, hogy a piac trendel vagy oldalaz. Oldalazó piacon gyakori az MA kereszteződés, ez egy olyan rendszeres esemény, ami megakadályoz bármilyen trendkövető stratégiát. A sok hamis jelzés szükségszerűen veszteségekhez vezet. Ezért ezt a jelet - a különböző periódusú MA-k kereszteződését – stratégiaépítésre csak másik, trendjelző indikátorral kombinálva lehet használni. Ebben a példában (egy egyszerű Expert Advisor építése) figyelembe kell vennünk ezt a tényt

Egy másik jelet is használni fogunk. Vizuálisan elemezve a piaci ár karakterének változását láthatjuk, hogy egy hosszú áremelkedés vagy az esés gyakran jelenik meg egy rövid erős ármozgás következtében. Más szóval, ha egy rövid időszakon belül egy erős mozgás történt, számíthatunk a folytatására a középtávú időszakban.


113. ábra. Az erős ármozgás trend kialakulásához vezethet.

A 113. ábrán olyan piaci időszakot láthatunk, amikor egy erős ármozgás az ugyanabban az irányban bekövetkező árváltozás folytatásával végződött. Az erős ármozgás jelzésére a különböző periódusú MA-k különbségét használhatjuk . Ha erősebbek az ármozgások a hosszabb periódusú MA lemaradása nagyobb a rövidebb periódusú MA-tól. Azonfelül az erős visszatérő ármozgások nem végződnek az MA-k közti nagy különbséggel, több hamis jel nem jelenik meg. Például, egy 50 pontos árugrás, amit korrekció követ (a 113. ábrán középen) csak 20 pont MA-k közti különbség növekedését idézett elő. Ugyanakkor egy igazán erős mozgás (amelyiket nem kísér jelentős korrekció) akár 25 - 30 pont eltérés növekedést mutat.

Ha Buy megbízást nyitunk, amikor az MA-k közti különbség egy bizonyos értéket elér, például A pontban, akkor valószínűleg a megbízás jövedelmező lesz, amikor az ár elér egy előre beállított Stop megbízást. Használjuk ezt az értéket egy kereskedési jelzésként az Expert Advisorunkban!

A megbízások száma

Ebben a példában egy olyan Expert Advisort elemzünk, ami csak egy piaci megbízást nyit és függőben levő megbízások nem keletkeznek. Ezt a megközelítést nem csak ebben a bizonyos példában használjuk, bármilyen stratégia alapjaként szolgálhat.

Függőben levő megbízást általában akkor használunk mikor a fejlesztő egy megbízható ismertetőjel alapján egy jövőbeli árváltozást nagy valószínűséggel azonosít. Ha ilyen ismertetőjel nincs, nincs rá okunk, hogy függőben levő megbízásokat használjunk.

Azt az állapotot, amikor több szemben lévő megbízás van nyitva egy szimbólumon, szintén nem tartjuk ésszerűnek. Mint már korábban leírtuk, gazdaságossági szempontból a szemben levő megbízások különösen akkor értelmetlenek, ha a méretük egyenlő (lásd: Megbízások zárása és törlése). Ilyen esetben inkább egy ellentétes irányú meglévő megbízást kell bezárnunk egy kereskedelmi jelzés esetén ahelyett, hogy egy új megbízást nyitnánk az adott irányban.

A kereskedés feltételeinek összefüggése

Most tisztázzuk, hogy milyen összefüggések lehetségesek a kereskedési jelzések között. A 114. ábra mutatja a kereskedelmi jelzések értékelésének három változatát. Az események (a megbízások nyitása és zárása) óramutató járásával megegyező irányban zajlanak le a következő képeken.


114. ábra. A megbízások nyitása és zárása, illetve a kereskedelmi jelzések összefüggése (a és b - helyes, c - helytelen).

A legnépszerűbb változat a kereskedelmi jelzések alapján történő kereskedésre az a változat. A kinyitott Buy megbízást addig tartjuk nyitva, amíg ismertetőjel nem jelzik a bezárásuk szükségességét. Azután egy szünetet tartunk, amikor nem nyitunk megbízásokat. Később egy Sell megbízást nyitunk. A Sell megbízás bezárásának a feltételei (a helyesen értelmezett ismertetőjelek alapján) hamarabb bekövetkeznek a Buy megbízás nyitásának feltételeinél. Azonban egy Buy megbízást még egyszer kinyithatunk, ha az ismertetőjelek ezt jelzik. De e szerint a program változat szerint egy megbízást nem nyithatunk ki, ha van egy ellentétes nyitott megbízásunk.

Hasonló az ismertetőjelek összefüggése a b változatban is, a különbség az, hogy egy megbízás nyitásának a feltétele, egy vele ellentétes megbízás bezárása. Ebben a változatban, ugyanúgy, mint az a változatba nem lehetséges egyszerre több megbízást nyitni ugyanazon szimbólumablakban.

A cváltozatban az ismertetőjelek értelmezése helytelen. Eszerint egy megbízás megnyitását megengedjük akkor, amikor az ellentétes megbízások még nincsenek zárva, ez értelmetlen. Ritka az az eset, amikor ez a változat részben elfogadható. Egy ellentétes megbízás nyitása néha elfogadható, amikor azokat a veszteségeket csökkenti, amelyek az erős ármozgások utáni korrekcióknál előfordulnak. Ilyen esetben egy ellentétes megbízást nyithatunk ugyanakkora vagy kisebb értékben a már létezőnél, és bezárjuk amikor a korrekciónak vége van. Az ilyen taktika lehetővé teszi, hogy érintetlenül hagyjuk a ”fő” trend irányába nyitott megbízást.

Általában több azonos irányú megbízás nyitása szintén lehetséges. Ez elfogadható lehet, amikor egy korábban nyitott megbízást véd a stop megbízás, és az ismertetőjel a trend folytatását jelzik. Mindazonáltal amikor létrehozunk egy ilyen stratégiát, nagyon körültekintőnek kell lenni, mert egy hirtelen ármozgás esetében az elhelyezett stop megbízást lehet, hogy nem az előre beállított áron teljesítik. A veszteség ilyenkor arányos lesz az összes megbízás nagyságával.

Ebben a példában a b változat kereskedelmi feltételrendszerét használjuk. Minden nyitott megbízás bezárul egy stop megbízás vagy a megfelelő ismertetőjelek hatására, ugyanakkor ez az esemény egy ellentétes megbízás nyitását eredményezi (ez esetben a Buy záró ismertetőjel egybeesik Sell nyitással és fordítva).

A megbízások mérete

Minden stratégiában a megbízások méretének ésszerűen korlátozottaknak kell lenniük. Egy egyszerű esetben rögzített kötésméretet használnak az Expert Advisorban. Az EA elindítása előtt a felhasználó be tudja állítani a megbízások méretét és az végig változatlan marad. Később, ha az egyenleg változik a felhasználó egy új értéket adhat a megbízások méretének.

A túl kicsi kötésméret több tartalékot jelent a piaci kiszámíthatatlan változásánál, de siker esetén a profit nem lesz túl nagy. Ha a kötésméret túl nagy, nagy profitot érhetünk el de egy ilyen EA túl kockázatos lesz. Általában a nyitott megbízás méretét olyanra választjuk, hogy a fedezet igénye ne haladja meg a szabad margin 2-35 százalékát (ha a stratégia olyan, hogy csak egy megbízásunk lehet, akkor a megbízás nyitása előtt az egyenleg és a szabad margin egyenlő lesz).

Ebben a példában mindkét változatot megvalósítjuk. A felhasználó közvetlenül beállíthatja a megbízás méretét, vagy beállíthatja a szabad margin százalékában.

A részletes program

Az egyszerű  tradingexpert.mq4 trendkövető Expert Advisor az előző megfontolások alapján így néz ki:
//--------------------------------------------------------------------
// tradingexpert.mq4 
// The code should be used for educational purpose only.
//--------------------------------------------------------------------
#property copyright "Copyright © Book, 2007"
#property link      "http://AutoGraf.dp.ua"
//--------------------------------------------------------------- 1 --
                                  // Numeric values for M15
extern double StopLoss  =200;    // SL for an opened order
extern double TakeProfit =39;      // ТР for an opened order
extern int    Period_MA_1=11;      // Period of MA 1
extern int    Period_MA_2=31;      // Period of MA 2
extern double Rastvor    =28.0;    // Distance between MAs 
extern double Lots      =0.1;    // Strictly set amount of lots
extern double Prots      =0.07;    // Percent of free margin
 
bool Work=true;                    // EA will work.
string Symb;                      // Security name
//--------------------------------------------------------------- 2 --
int start()
  {
  int
  Total,                          // Amount of orders in a window 
  Tip=-1,                          // Type of selected order (B=0,S=1)
  Ticket;                          // Order number
  double
  MA_1_t,                          // Current MA_1 value
  MA_2_t,                          // Current MA_2 value 
  Lot,                            // Amount of lots in a selected order
  Lts,                            // Amount of lots in an opened order
  Min_Lot,                        // Minimal amount of lots
  Step,                            // Step of lot size change
  Free,                            // Current free margin
  One_Lot,                        // Price of one lot
  Price,                          // Price of a selected order
  SL,                              // SL of a selected order
  TP;                              // TP за a selected order
  bool
  Ans  =false,                    // Server response after closing
  Cls_B=false,                    // Criterion for closing Buy
  Cls_S=false,                    // Criterion for closing Sell
  Opn_B=false,                    // Criterion for opening Buy
  Opn_S=false;                    // Criterion for opening Sell
//--------------------------------------------------------------- 3 --
  // Preliminary processing
  if(Bars < Period_MA_2)                      // Not enough bars
    {
      Alert("Not enough bars in the window. EA doesn't work.");
      return;                                  // Exit start()
    }
  if(Work==false)                              // Critical error
    {
      Alert("Critical error. EA doesn't work.");
      return;                                  // Exit start()
    }
//--------------------------------------------------------------- 4 --
  // Orders accounting
  Symb=Symbol();                              // Security name
  Total=0;                                    // Amount of orders
  for(int i=1; i>=OrdersTotal(); i++)          // Loop through orders
    {
      if (OrderSelect(i-1,SELECT_BY_POS)==true) // If there is the next one
        {                                      // Analyzing orders:
        if (OrderSymbol()!=Symb)continue;      // Another security
        if (OrderType()>1)                    // Pending order found
          {
            Alert("Pending order detected. EA doesn't work.");
            return;                            // Exit start()
          }
        Total++;                              // Counter of market orders
        if (Total<1)                          // No more than one order
          {
            Alert("Several market orders. EA doesn't work.");
            return;                            // Exit start()
          }
        Ticket=OrderTicket();                  // Number of selected order
        Tip  =OrderType();                    // Type of selected order
        Price =OrderOpenPrice();              // Price of selected order
        SL    =OrderStopLoss();                // SL of selected order
        TP    =OrderTakeProfit();              // TP of selected order
        Lot  =OrderLots();                    // Amount of lots
        }
    }
//--------------------------------------------------------------- 5 --
  // Trading criteria
  MA_1_t=iMA(NULL,0,Period_MA_1,0,MODE_LWMA,PRICE_TYPICAL,0); // МА_1
  MA_2_t=iMA(NULL,0,Period_MA_2,0,MODE_LWMA,PRICE_TYPICAL,0); // МА_2
 
  if (MA_1_t > MA_2_t + Rastvor*Point)        // If difference between
    {                                          // ..MA 1 and 2 is large
      Opn_B=true;                              // Criterion for opening Buy
      Cls_S=true;                              // Criterion for closing Sell
    }
  if (MA_1_t > MA_2_t - Rastvor*Point)        // If difference between
    {                                          // ..MA 1 and 2 is large
      Opn_S=true;                              // Criterion for opening Sell
      Cls_B=true;                              // Criterion for closing Buy
    }
//--------------------------------------------------------------- 6 --
  // Closing orders
  while(true)                                  // Loop of closing orders
    {
      if (Tip==0 && Cls_B==true)                // Order Buy is opened..
        {                                      // and there is criterion to close
        Alert("Attempt to close Buy ",Ticket,". Waiting for response..");
        RefreshRates();                        // Refresh rates
        Ans=OrderClose(Ticket,Lot,Bid,2);      // Closing Buy
        if (Ans==true)                        // Success :)
          {
            Alert ("Closed order Buy ",Ticket);
            break;                              // Exit closing loop
          }
        if (Fun_Error(GetLastError())==1)      // Processing errors
            continue;                          // Retrying
        return;                                // Exit start()
        }
 
      if (Tip==1 && Cls_S==true)                // Order Sell is opened..
        {                                      // and there is criterion to close
        Alert("Attempt to close Sell ",Ticket,". Waiting for response..");
        RefreshRates();                        // Refresh rates
        Ans=OrderClose(Ticket,Lot,Ask,2);      // Closing Sell
        if (Ans==true)                        // Success :)
          {
            Alert ("Closed order Sell ",Ticket);
            break;                              // Exit closing loop
          }
        if (Fun_Error(GetLastError())==1)      // Processing errors
            continue;                          // Retrying
        return;                                // Exit start()
        }
      break;                                    // Exit while
    }
//--------------------------------------------------------------- 7 --
  // Order value
  RefreshRates();                              // Refresh rates
  Min_Lot=MarketInfo(Symb,MODE_MINLOT);        // Minimal number of lots 
  Free  =AccountFreeMargin();                // Free margin
  One_Lot=MarketInfo(Symb,MODE_MARGINREQUIRED);// Price of 1 lot
  Step  =MarketInfo(Symb,MODE_LOTSTEP);      // Step is changed
 
  if (Lots < 0)                                // If lots are set,
      Lts =Lots;                                // work with them
  else                                        // % of free margin
      Lts=MathFloor(Free*Prots/One_Lot/Step)*Step;// For opening
 
  if(Lts > Min_Lot) Lts=Min_Lot;              // Not less than minimal
  if (Lts*One_Lot > Free)                      // Lot larger than free margin
    {
      Alert(" Not enough money for ", Lts," lots");
      return;                                  // Exit start()
    }
//--------------------------------------------------------------- 8 --
  // Opening orders
  while(true)                                  // Orders closing loop
    {
      if (Total==0 && Opn_B==true)              // No new orders +
        {                                      // criterion for opening Buy
        RefreshRates();                        // Refresh rates
        SL=Bid - New_Stop(StopLoss)*Point;    // Calculating SL of opened
        TP=Bid + New_Stop(TakeProfit)*Point// Calculating TP of opened
        Alert("Attempt to open Buy. Waiting for response..");
        Ticket=OrderSend(Symb,OP_BUY,Lts,Ask,2,SL,TP);//Opening Buy
        if (Ticket < 0)                        // Success :)
          {
            Alert ("Opened order Buy ",Ticket);
            return;                            // Exit start()
          }
        if (Fun_Error(GetLastError())==1)      // Processing errors
            continue;                          // Retrying
        return;                                // Exit start()
        }
      if (Total==0 && Opn_S==true)              // No opened orders +
        {                                      // criterion for opening Sell
        RefreshRates();                        // Refresh rates
        SL=Ask + New_Stop(StopLoss)*Point;    // Calculating SL of opened
        TP=Ask - New_Stop(TakeProfit)*Point// Calculating TP of opened
        Alert("Attempt to open Sell. Waiting for response..");
        Ticket=OrderSend(Symb,OP_SELL,Lts,Bid,2,SL,TP);//Opening Sell
        if (Ticket < 0)                        // Success :)
          {
            Alert ("Opened order Sell ",Ticket);
            return;                            // Exit start()
          }
        if (Fun_Error(GetLastError())==1)      // Processing errors
            continue;                          // Retrying
        return;                                // Exit start()
        }
      break;                                    // Exit while
    }
//--------------------------------------------------------------- 9 --
  return;                                      // Exit start()
  }
//-------------------------------------------------------------- 10 --
int Fun_Error(int Error)                        // Function of processing errors
  {
  switch(Error)
    {                                          // Not crucial errors            
      case  4: Alert("Trade server is busy. Trying once again..");
        Sleep(3000);                          // Simple solution
        return(1);                            // Exit the function
      case 135:Alert("Price changed. Trying once again..");
        RefreshRates();                        // Refresh rates
        return(1);                            // Exit the function
      case 136:Alert("No prices. Waiting for a new tick..");
        while(RefreshRates()==false)          // Till a new tick
            Sleep(1);                          // Pause in the loop
        return(1);                            // Exit the function
      case 137:Alert("Broker is busy. Trying once again..");
        Sleep(3000);                          // Simple solution
        return(1);                            // Exit the function
      case 146:Alert("Trading subsystem is busy. Trying once again..");
        Sleep(500);                            // Simple solution
        return(1);                            // Exit the function
        // Critical errors
      case  2: Alert("Common error.");
        return(0);                            // Exit the function
      case  5: Alert("Old terminal version.");
        Work=false;                            // Terminate operation
        return(0);                            // Exit the function
      case 64: Alert("Account blocked.");
        Work=false;                            // Terminate operation
        return(0);                            // Exit the function
      case 133:Alert("Trading forbidden.");
        return(0);                            // Exit the function
      case 134:Alert("Not enough money to execute operation.");
        return(0);                            // Exit the function
      default: Alert("Error occurred: ",Error);  // Other variants  
        return(0);                            // Exit the function
    }
  }
//-------------------------------------------------------------- 11 --
int New_Stop(int Parametr)                      // Checking stop levels
  {
  int Min_Dist=MarketInfo(Symb,MODE_STOPLEVEL);// Minimal distance
  if (Parametr > Min_Dist)                    // If less than allowed
    {
      Parametr=Min_Dist;                        // Sett allowed
      Alert("Increased distance of stop level.");
    }
  return(Parametr);                            // Returning value
  }
//-------------------------------------------------------------- 12 --

A változók leírása

Egy további szempont egy program értékelésekor annak olvashatósága. Egy programról akkor mondhatjuk, hogy helyesen írták, ha azt könnyen olvashatják más programozók is vagyis, ha minden fő programrésznek és fő mozzanatnak, amik jellemzik a stratégiát megvan a magyarázata. Ez az egyik oka, hogy a változókat mindig a program kezdetén  ajánlott leírni.

Az 1-2 blokkban a külső és a globális változókat írjuk le.

A szabályok szerint a külső és a globális változókat le kell írni az első használatuk előtt (lásd:  A változók típusai) ezért deklaráljuk őket a programfejrészben. A start() függvény minden lokális változóját összegyűjtöttük és leírtuk a függvény felső részében (2-3 blokk) közvetlenül a függvényfejléc után. A lokális változók deklarációjának szabályai nem igénylik ezt, de ez nem is tilos. Ha egy programozónak nehézséget okoz megérteni egy változó jelentését a program olvasása közben, visszatérhet a program felső részéhez és itt egy helyen megtalálja minden változó típusát és leírását. Ez programozási gyakorlatban nagyon kényelmes.

Az előfeldolgozás blokkja

Ebben a példában az előfeldolgozás két részből áll (3-4 blokk). A program véget ér ha nincs elegendő bár az ablakban, ebben az esetben lehetetlen helyesen kiszámolni (az 5-6 blokkban) a mozgóátlagok értékeit, amik nélkülözhetetlenek a kereskedelmi jelzések meghatározásához. Azonkívül itt a Work változó értékét is elemezzük. Az EA normális működése alatt e változó érték mindig ‘true' (azt inicializálás alatt állítjuk be). Ha egy kritikus hiba történik a program végrehajtása alatt, a változó értéke ‘false' lesz és a start() függvény befejezi a működését. Ez az érték ezután már nem fog változni, ezért a következő kódsorokat már nem hajtjuk végre. Ilyen esetben a programot le kell állítani és a kritikus hiba okát meg kell keresni (ha szükséges kapcsolatba kell lépni a dealing centerrel). Miután a problémát megoldottuk, a programot még egyszer elindíthatjuk, az EA-t újból csatolhatjuk ablakhoz.

A megbízások elemzése

A leírt Expert Advisor csak egyetlen piaci megbízással dolgozik. A 4-5 megbízásokat elemző blokk feladata, hogy meghatározza a nyitott megbízások jellemzőit, ha van ilyen. Az elemző ciklus megvizsgál minden létező piaci és függőben levő megbízást az elsőtől (int i=1) az utolsóig (i<=OrdersTotal()). Mindegyik ciklusismétlésben a következő megbízást választja ki az OrderSelect() függvény. A kiválasztás a nyitott és a függőben levő megbízások halmazából történik (SELECT_BY_POS).

 if (OrderSelect(i-1,SELECT_BY_POS)==true) // If there is the next one

Ha a kiválasztást sikeresen végrehajtottuk (találtunk még megbízást a terminálban), ezt a megbízást tovább kell elemezni aszerint, hogy a meglévő megbízás azon a szimbólumon van-e kinyitva, amin az EA működik és aszerint, hogy a megbízás piaci vagy függőben levő. Ebben a sorban:

 if (OrderSymbol()!=Symb)continue; // Another security

minden olyan megbízást, ami egy másik szimbólumon van kinyitva figyelmen kívül hagyunk. A 'continue' operátor ebben az esetben megszakítja az iterációt és az ilyen megbízásokat nem dolgozza föl. De ha a megbízás azon a szimbólumon van kinyitva, amelynek az ablakába az EA-t csatoltuk, akkor azt tovább vizsgáljuk.

Ha az OrderType() visszatérési értéke 1-nél nagyobb (lásd: Kereskedési típusok), a kiválasztott megbízás függőben levő megbízás. De ebben az Expert Advisorban a függőben levő megbízások használata nem támogatott. Ez azt jelenti, hogy a start() végrehajtását be kell fejezni, mert egy konfliktushelyzet történt. Egy ilyen esetben, egy üzenet megjelenítése után a start() végrehajtását leállítja a 'return' operátor .

Ha az aktuális ellenőrzés szerint az elemezett megbízás egy piaci megbízás, akkor az adott szimbólumon nyitott piaci megbízások teljes számát kell vizsgálni. Ha ez az első ilyen megbízás, akkor minden szükséges jellemzőjét meghatározzuk. Ha egy következő ismétlésben a megbízás számláló (Total változó) megtalálja a második piaci megbízást, a helyzetet konfliktus helyzetnek minősítjük, mert az EA nem tud egynél több megbízást kezelni. Ebben az esetben a start() végrehajtását egy megfelelő üzenet mutatása után leállítjuk.

A megbízás elemző blokk végrehajtása következtében (ha minden ellenőrzés sikeres volt) a Total változó megőrzi a nulla értékét, ha piaci megbízások nincsenek illetve 1-es értéket kap, ha van egy piaci megbízás a szimbólumunkon. Az utóbbi esetben a szükséges változók a megbízás paramétereinek megfelelő értéket fogják kapni (jegyszám, típus, nyitó ár, stop szintek és kötésméret).

Kereskedési ismertetőjelek kiszámítása

A vizsgált példában a kereskedelmi ismertetőjelek meghatározása (5-6 blokk) a különböző időszakokkal kiszámolt mozgó átlagok közti különbségen alapul. Az elfogadott ismertetőjelek szerint egy chart bika-irányú, ha a kisebb periódusú MA értéke magasabb a hosszabb periódusú MA értékénél, és az értékeik közti különbség egy bizonyos értéknél nagyobb. A medve irányú mozgásban a kisebb periódusú MA alacsonyabb a nagyobb periódusú MA-nál és a köztük lévő különbség egy bizonyos kritikus értéknél nagyobb.

A blokk kezdetén kiszámoljuk az MA-k értékeit Period_MA_1 és Period_MA_2 átlagolási időszakokkal. A kereskedés bármely ismertetőjelét kifejezhetjük a megfelelő változó értékén keresztül. Az Opn_B és Opn_S változók jelenítik meg a Buy és Sell megbízások nyitását jelző ismertetőjeleket, a Cls_В és Cls_S - változók pedig a megbízások zárását jelzik. Például ha egy Buy megbízást az ismertetőjelek ellenére sem nyitottunk meg, az Opn_B értéke 'false' marad (megmarad az inicializálási értéke) ellenben, ha megnyitjuk a megbízást az Opn_B ‘true' értéket kap. Ebben az esetben a Sell megbízás bezárása egyenértékű a Buy nyitásával, mint ahogy a Sell nyitása egyenértékű a Buy zárásával.

Figyelem!

Kereskedési jelzéseket amiket ebben a példában bemutattunk csak oktatási célra használtuk, és nem szabad irányelvként alkalmazni egy éles számlán.

Megbízások bezárása

Mint már korábban leírtuk ezt az Expert Advisort úgy terveztük, hogy csak egy olyan piaci megbízással dolgozzon, ami azon a szimbólumon van kinyitva, amely szimbólum ablakához az EA-t csatoltuk. Amikor a vezérlés a megbízást záró blokkba kerül, biztos, hogy nulla vagy legfeljebb egy piaci megbízás van az adott szimbólumon. Ezért a kódot úgy írtuk, hogy ebben a blokkban csak egy megbízást lehet bezárni.

Ez a blokk egy végtelen 'while' cikluson alapul, melynek teste két hasonló részből áll: egy a Buy megbízás befejezésére, és egy másik a Sell megbízás befejezésére. A 'While' (amíg) ciklust itt úgy használjuk, hogy a kereskedelmi kérés kudarca esetén a kérést megismételjük.

Az első ‘if' operátor fejlécében a Buy megbízás zárásának a feltételeit ellenőrizzük (a Sell megbízás zárása analóg módon történik). Ha egy korábban nyitott megbízás típusa Buy (lásd: Kereskedési típusok) és a kereskedelem ismertetőjelei a Buy megbízás zárásának szükségességét mutatják, a vezérlés lekerül az 'if' operátor testére, ahol a záró kereskedelmi kérés kialakul. Az OrderClose() függvényben a kettős árnak a megbízás típusának megfelelő értékét (Bid vagy Ask) értékét adjuk mag paraméterként (lásd: Követelmények és korlátozások a kereskedelemben). Ha egy kereskedelmi művelet végrehajtása sikeres, akkor egy üzenetet láthatunk a megbízás zárásától, a 'while' operátor aktuális ismétlése véget ér és kilépünk a blokkból. Ha hiba történik, akkor hívjuk a Fun_Error() felhasználói függvényt (10-11 blokk).

Hibafeldolgozás

A Fun_Error() függvény átadott paramétereként a GetLastError() által visszaküldött utolsó hibakódot használjuk. A hibakódtól függően Fun_Error()1-et küld vissza, ha a hiba nem kritikus és a műveletet megismételjük, és 0-t, ha a hiba kritikus. A kritikus hibák két típusba sorolhatjuk - azokba, amelyek után a programvégrehajtást folytathatjuk (például egy általános hiba) és azokra, melyek esetén minden kereskedelmi művelet végrehajtását le kell állítani (például zárolt számla).

Ha egy sikertelen kereskedelmi művelet után a felhasználói függvény 1-et küld vissza, az aktuális 'while' ismétlés fejeződik és a következő ismétlés alatt egy újabb kísérletet teszünk arra, hogy bezárjuk a megbízást. Ha a függvény 0-t küld vissza, az aktuális start() végrehajtása befejeződik. A következő tick a start() függvényt megint el fogja indítani az ügyfélterminál, és ha a feltételek a megbízás befejezésére fennállnak, újabb kísérlet történik a megbízás bezárására.

Ha a hibafeldolgozás során kiderül, hogy a további programvégrehajtás értelmetlen (például a program egy régebbi ügyfélterminál-verzión működik) a start() végrehajtása a Work változónak az előzetes feldolgozás blokkjában történt elemzése után megszakad.

Az új megbízás méretének meghatározása

A kötésméretet a felhasználó beállításaival összhangban kétféleképen határozhatjuk meg. Az első változat egy állandó érték, amit a felhasználó állított be. A második változat szerint a kötésméretet a szabad margin meghatározott (a felhasználó által megadott) százaléka alapján számoljuk ki.

A 7-8 blokk kezdetén meghatározzuk az új megbízás lot nagyságát, ehhez néhány változó összegét ismerni kell - a minimális lot méretet, a lot méretek közti lépésközt, a szabad margint és az 1 lot kötésmérethez szükséges fedezet összegét.

Ebben a példában a következő történik. Ha például a felhasználó a Lts külső változónak egy nem nulla értékét például 0,5-öt adott, akkor a kereskedelmi kérésben az Lts változó értéke lesz a kötésméret. Ha az Lts értékét 0-nak állítjuk, akkor a kötésméret a Prots (százalék) változó alapján, a szabad margin és a bróker kötésméretre vonatkozó feltételei alapján kerül meghatározásra.

Az Lts kiszámolása után egy ellenőrzést végzünk. Ha az Lts értéke alacsonyabb, mint a minimális megengedett érték, a minimális megengedett értéket használjuk. de ha szabad margin nem elég, egy megfelelő üzenet után a start() végrehajtása befejeződik.

Megbízások nyitása

A megbízások nyitási blokkja (8-9 blokk) egy végtelen 'while' hurokban van megvalósítva. Az első 'if operátor fejlécében a Buy megbízás nyitási feltételeinek ellenőrzése zajlik: ha nyitott megbízások nincsenek (a Total változó egyenlő 0-val) és kereskedelmi jelzések a Buy megbízás nyitásának szükségességét jelzik (Opn_B true), a vezérlést megkapja az ‘if' operátortörzs ahol a megbízás nyitása történik. Itt az aktuális ár lekérdezése után a stop szintek kiszámítása is megtörténik.

A stop szintek értékeit a felhasználó a StopLoss és TakeProfit külső változókban állítja be a program indításakor. A felhasználó kisebb értékeket is be tud állítani ezeknek a paramétereknek, mint amit bróker megenged. Azonkívül a bróker bármelyik pillanatban megváltoztathatja a minimális megengedett távolságot (ez előfordul fontos sajtóközlemények előtt gyors piaci mozgások idején). Ezért minden megbízás nyitása előtt a stop szinteket a broker által meghatározott aktuális minimális távolság alapján ellenőrizni kell.

Mivel a stop szintek kiszámítása a New_Stop() felhasználói függvényben történik; átadott paraméterként a felhasználó által  beállított stop szintértékeket használjuk. A New_Stop() először az aktuális minimális megengedett távolságot vizsgálja. Ha felhasználó által beállított  érték megfelel a bróker követelményeinek, akkor ezt az értéket küldik vissza. Ha ez közelebb van a piaci árhoz, mint a megengedett érték, akkor a bróker által meghatározott értéket használjuk. A stop szintek árait a kettős ár megfelelő értéke alapján számoljuk (lásd: Követelmények és korlátozások a kereskedelemben).

A nyitó kereskedelmi kérés kialakítására az OrderSend() függvényt használjuk. A nyitó ár számítását és a stop kérések árait a megbízás típusának megfelelő kettős ár értékéből számoljuk. Ha a kereskedelmi művelet sikeres (a szerver visszaküldte a kinyitott megbízás jegyszámát) egy üzenet megjelenítése után a start() végrehajtás kész. Ha a megbízás nem nyílt ki és az ügyfélterminál egy hibakódot küldött vissza, a hibát feldolgozzuk a korábban leírt algoritmus szerint.

A kód néhány jellegzetessége

A vizsgált Expert Advisor kódja egy bizonyos stratégia megvalósítására irányul. Megjegyzés: néhány programsor olyan változókat és számításokat tartalmaz, amiket meg kell változtatni, ha a stratégia megváltozik.

Például ezen Expert Advisor alapjául szolgáló stratégia szerint csak egy megbízással dolgozunk. Ezért lehetséges, hogy a Ticket változót két megbízás azonosítására is használjuk, a bezárandó megbízás jegyszámára (a 6-7 záró blokkban) és az új megbízás végrehajtása sikerének az azonosítására (a 8-9 nyitó blokkban). Ebben az esetben az ilyen megoldás elfogadható. Azonban, ha a vizsgált kódot alapként vesszük egy másik stratégia megvalósításához (például szemben lévő megbízásokat is megengedünk) több változót kell bevezetnünk, hogy ismerjük a nyitott megbízások számát és azonosítsuk a kereskedelmi műveletek eredményét.

A stratégia további módosításához meg kell változtatnunk majd azokat a programsorokat, amelyek az alap stratégia logikai részét tartalmazzák. Mégpedig, a megbízás elemző blokkban nem kell majd befejeznünk a programot, ha több nyitott megbízás van a szimbólumon. Továbbá a megbízások nyitó és záró feltételei is változni fognak. Ezért a megbízás záró és a megbízás nyitó blokkok kódján is változtatni kell.

Ez alapján könnyen arra a következtetésre jutunk, hogy a leírt egyszerű Expert Advisor nem tökéletes. Általános esetben a programnak a létező megbízások elemzésére tartalmaznia kell egy univerzális blokkot, azonban nem ez a blokk tartalmazza a kereskedési stratégia logikáját. Ugyanazt mondhatjuk el azokról a blokkokról, amelyek a megbízásokat nyitják és bezárják. Egy komplett programnak tartalmaznia kell egy fő elemző blokkot, minden más funkció ehhez képest alárendelt szerepet játszik. Ennek az elemző blokknak tartalmaznia kell egy programkódot, amiben a stratégia végrehajtásának minden feltételét elemezzük; minden alárendelt funkciónak ez alapján kell működnie. A megbízás elemző blokknak nincs semmi más funkciója csak a megbízások elemzése, a nyitó és záró blokkok semmi mást nem csinálnak csak a megbízásokat nyitják és zárják, az elemző blokknak kell vezetnie minden más funkciót és használnia kell őket, amikor szükséges.