Anlegen eines systemübergreifenden mehrwährungsfähigen automatischen Handelssystems

Maxim Khrolenko | 1 Juni, 2016

Einleitung

Ich denke, es werden sich nicht wenige Devisenhändler finden, die mit mehr als nur einem Währungspaar (Kürzel) handeln und mehrere Strategien verfolgen. Eine solche Herangehensweise ermöglicht nicht nur eine mögliche Steigerung der Gewinne, sondern durch eine durchdachte Kapitalverwaltung auch eine beträchtliche Verringerung des Risikos größerer Einbußen. Beim Aufbau eines jeden Expert-Systems besteht der erste Schritt bei der Überprüfung der Effektivität der in dem Programm angelegten Strategie in dessen Optimierung mit dem Ziel der Ermittlung der optimalen Eingangsparameter.

Sobald die Parameterwerte ermittelt sind, kann das Expert-System für den Handel eingerichtet werden, wobei allerdings eine nicht ganz unwichtige Frage noch einer Antwort harrt: Wie würden die Prüfergebnisse aussehen, wenn der Händler all seine Strategien in einem einzigen Expert-System bündeln könnte? Manchmal kommt das „böse Erwachen“, wenn man feststellen muss, dass die Rückgänge bei einigen Kürzeln oder Strategien sich irgendwann derart aufsummieren, dass sie zu einem entsetzlichen Gesamtverlust bis hin zu einer Nachschussforderung führen können.

In dem hier vorliegenden Beitrag wird ein Modell zur Erstellung eines systemübergreifenden mehrwährungsfähigen Expert-Systems vorgestellt, dass dabei helfen soll, eine Antwort auf diese entscheidende Frage zu finden.


1. Das Gerüst des Expert-Systems

Grob gezeichnet sieht das Gerüst etwa so aus:

Abb. 1. Gerüst eines systemübergreifenden mehrwährungsfähigen automatischen Handelssystems

Abb. 1. Gerüst eines systemübergreifenden mehrwährungsfähigen automatischen Handelssystems

Wie zu sehen ist, bildet der Arbeitsgang for die Grundlage für das Programm. Jede Strategie wird in einem Arbeitsuntergang (Zyklus) untergebracht, in dem jede Iteration für den Handel mit jedem Kürzel gesondert zuständig ist. So kann eine unbegrenzte Zahl Strategien aufgenommen werden. Hauptsache unser Rechner verfügt über die entsprechenden Kapazitäten, um ein solches Programm „zu schlucken“.

Es sei daran erinnert, dass in MetaTrader 5 zu jedem gehandelten Kürzel nur eine Position vorhanden sein darf. Diese ist die Summe der Posten aller zuvor abgeschlossenen Käufe und Verkäufe. Deshalb wird das Ergebnis der gleichzeitigen Prüfung mehrerer Strategien an einem Kürzel nicht mit der Summe der einzelnen Prüfungen jeder dieser Strategien an demselben Kürzel übereinstimmen.

Zur eingehenderen Untersuchung des Aufbaus eines entsprechenden Expert-Systems verwenden wir zwei Strategien für den Handel mit jeweils zwei Kürzeln:

Strategie A:

Strategie В:

Um von dem Auftreten neuer Kursbewegungen (Ticks) bei dem Kürzel, für das das Expert-System geprüft oder betrieben wird, unabhängig zu sein, empfiehlt es sich, für den Handel mit mehreren Währungen die Funktion OnTimer() zu verwenden.

Dazu geben wir bei der Bereitstellung des Expert-Systems mithilfe der Funktion EventSetTimer() an, mit welcher Frequenz das Ereignis zum Aufruf der Berechnung des Programms erzeugt werden soll, und weisen das Ausgabegerät (Terminal) bei seiner Bereinigung mit der Funktion EventKillTimer() an, die Erzeugung von Ereignissen zu beenden:

// Include standard libraries
// Create external parameters
// Create arrays, variables, indicator handles, etc.

//--- Initialization of the Expert Advisor
int OnInit()
  {
   //--- Set event generation frequency
   EventSetTimer(1); // 1 second
   // ...
   return(0);
  }
void OnTimer()
  {
   // ...
  }
//--- Deinitialization of the Expert Advisor
void OnDeinit(const int reason)
  {
   //--- Stop event generation
   EventKillTimer();
   // ...
  }

Statt EventSetTimer() kann auch die Funktion EventSetMillisecondTimer() verwendet werden. In ihr kann die Frequenz millisekundengenau eingestellt werden, allerdings sollte sie nicht zu übermäßig häufigen Aufrufen zur Berechnung des Programms missbraucht werden.

Um auf die Eigenschaften des Kontos, der Position und des Kürzels sowie auf die Funktionen für den Handel zugreifen zu können, verwenden wir die Klassen CAccountInfo, CPositionInfo, CSymbolInfo bzw. CTrade. Wir verknüpfen sie mit unserem Expert-System:

//--- Include standard libraries
#include <Trade\AccountInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\Trade.mqh>

Da das Expert-System auf Arbeitsgängen der Art „for“ beruht, müssen die externen Parameter in Datenfeldern abgelegt werden. Zunächst legen wir eine der Anzahl der Kürzel für jede Strategie entsprechende Anzahl von Konstanten an.

//--- Number of traded symbols for each strategy
#define Strategy_A 2
#define Strategy_B 2

Danach erstellen wir die externen Parameter. Anhand der Konstanten bestimmen wir die Dimension der Datenfelder, in die sie kopiert werden. Außerdem legen wir die Bezeichner für die Indikatoren und weitere globale Variablen an.

Es folgt ein Beispiel für die Strategie A mit nur einem Kürzel:

//------------------- External parameters of strategy A
input string          Data_for_Strategy_A="Strategy A -----------------------";
//--- Symbol 0
input string          Symbol_A0      = "EURUSD";   // Symbol
input bool            IsTrade_A0     = true;       // Permission for trading
//--- Bollinger Bands (BB) parameters
input ENUM_TIMEFRAMES Period_A0      = PERIOD_H1;  // ВВ period
input uint            BBPeriod_A0    = 20;         // Period for calculation of the moving average of BB
input int             BBShift_A0     = 0;          // Horizontal shift of ВВ
input double          BBDeviation_A0 = 2.0;        // Number of standard deviations of BB
//...
//--- General parameters of strategy A
input double          DealOfFreeMargin_A = 1.0;    // Percent of free margin for a deal
input uint            MagicNumber_A      = 555;    // Magic number
input uint            Slippage_A         = 100;    // Permissible slippage for a deal
//...
//------------- Set variables of strategy A -----
//--- Arrays for external parameters
string          Symbol_A[Strategy_A];
bool            IsTrade_A[Strategy_A];
ENUM_TIMEFRAMES Period_A[Strategy_A];
int             BBPeriod_A[Strategy_A];
int             BBShift_A[Strategy_A];
double          BBDeviation_A[Strategy_A];
//--- Arrays for global variables
double          MinLot_A[Strategy_A],MaxLot_A[Strategy_A];
double          Point_A[Strategy_A],ContractSize_A[Strategy_A];
uint            DealNumber_A[Strategy_A];
datetime        Locked_bar_time_A[Strategy_A],time_arr_A[];
//--- Indicator handles
int             BB_handle_high_A[Strategy_A];
int             BB_handle_low_A[Strategy_A];
//--- Arrays for indicator values
double          BB_upper_band_high[],BB_lower_band_high[];
double          BB_upper_band_low[],BB_lower_band_low[];
//--- Class
CTrade          Trade_A;
//...
//--- Set global variables for all strategies
long            Leverage;
//--- Classes
CAccountInfo    AccountInfo;
CPositionInfo   PositionInfo;
CSymbolInfo     SymbolInfo;

Um die Möglichkeit zu haben, den Handel mit einem bestimmten Kürzel auszuschließen, wird die logische Variable IsTrade_A0 angelegt und ganz am Anfang des Arbeitsgangs for platziert.


2. Die Bereitstellung des Expert-Systems

Zunächst beziehen wir die für alle Strategien benötigten Werte, zum Beispiel den Umfang der Hebelwirkung. Da die Hebelwirkung für das Handelskonto unabhängig von der jeweiligen Strategie oder des Kürzels gilt, besteht keine Notwendigkeit, ihren Wert in die Datenfelder zu kopieren:

//--- Get the leverage for the account
   Leverage=AccountInfo.Leverage();

Wir kopieren die externen Variablen in die Datenfelder.

//--- Copy external variables to arrays
   Symbol_A[0]     =Symbol_A0;
   IsTrade_A[0]    =IsTrade_A0;
   Period_A[0]     =Period_A0;
   BBPeriod_A[0]   =(int)BBPeriod_A0;
   BBShift_A[0]    =BBShift_A0;
   BBDeviation_A[0]=BBDeviation_A0;

Wenn ein externer Parameter in einer Art vorgegeben ist, die in eine andere umgewandelt werden muss, sollte dies praktischerweise beim Kopieren in die Datenfelder geschehen.

In diesem Fall sehen wir, dass BBPeriod_A0 als uint angelegt wurde, um den Anwender daran zu hindern, einen negativen Wert anzugeben. Wir wandeln ihn an dieser Stelle in die Art int um und kopieren ihn in das ebenfalls als int angelegte Datenfeld. Andernfalls gibt der Compiler eine Warnmeldung aus, wenn in den Indikatorbezeichner ein Parameter der Art uint eingestellt werden soll.

Im weiteren Verlauf prüfen wir, ob das gehandelte Kürzel in der „Marktübersicht“ vorhanden ist und ob es im Rahmen einer bestimmten Strategie mehr als einmal verwendet wurde:

//--- Check for the symbol in the Market Watch
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      if(IsSymbolInMarketWatch(Symbol_A[i])==false)
        {
         Print(Symbol_A[i]," could not be found on the server!");
         ExpertRemove();
        }
     }

//--- Check whether the symbol is used more than once
   if(Strategy_A>1)
     {
      for(int i=0; i<Strategy_A-1; i++)
        {
         if(IsTrade_A[i]==false) continue;
         for(int j=i+1; j<Strategy_A; j++)
           {
            if(IsTrade_A[j]==false) continue;
            if(Symbol_A[i]==Symbol_A[j])
              {
               Print(Symbol_A[i]," is used more than once!");
               ExpertRemove();
              }
           }
        }
     }
//--- The IsSymbolInMarketWatch() function
bool IsSymbolInMarketWatch(string f_Symbol)
  {
   for(int s=0; s<SymbolsTotal(false); s++)
     {
      if(f_Symbol==SymbolName(s,false))
         return(true);
     }
   return(false);
  }

Wenn die Kürzel richtig gewählt wurden, überprüfen wir jedes von ihnen auf Fehler in den Eingangsparametern, erstellen Bezeichner für die Indikatoren, ermitteln die zur Berechnung des Postens erforderlichen Daten und führen ggf. weitere für die gegebene Strategie charakteristischen Handlungen aus.

Das alles setzen wir in dem Arbeitsgang for um.

//--- General actions
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      //--- Check for errors in input parameters
      //...
      //--- Set indicator handles
      BB_handle_high_A[i]=iBands(Symbol_A[i],Period_A[i],BBPeriod_A[i],BBShift_A[i],BBDeviation_A[i],
                                 PRICE_HIGH);
      if(BB_handle_high_A[i]<0)
        {
         Print("Failed to create a handle for Bollinger Bands based on High prices for ",Symbol_A[i]," . Handle=",INVALID_HANDLE,
               "\n Error=",GetLastError());
         ExpertRemove();
        }
      //...
      //--- Calculate data for the Lot
      //--- set the name of the symbol for which the information will be obtained
      SymbolInfo.Name(Symbol_A[i]);
      //--- minimum and maximum volume size in trading operations
      MinLot_A[i]=SymbolInfo.LotsMin();
      MaxLot_A[i]=SymbolInfo.LotsMax();
      //--- point value
      Point_A[i]=SymbolInfo.Point();
      //--- contract size
      ContractSize_A[i]=SymbolInfo.ContractSize();

      //--- Set some additional parameters
     }

Danach legen wir mithilfe des Objektes Trade_A der Klasse CTrade die Einstellungen für die Handelsoperationen der Strategie A fest.

//--- Set parameters for trading operations
//--- set the magic number
   Trade_A.SetExpertMagicNumber(MagicNumber_A);
//--- set the permissible slippage in points upon deal execution
   Trade_A.SetDeviationInPoints(Slippage_A);
//--- order filling mode, use the mode that is allowed by the server
   Trade_A.SetTypeFilling(ORDER_FILLING_RETURN);
//--- logging mode, it is advisable not to call this method as the class will set the optimal mode by itself
   Trade_A.LogLevel(1);
//--- the function to be used for trading: true - OrderSendAsync(), false - OrderSend().
   Trade_A.SetAsyncMode(true);

Wir wenden auf alle Strategien dasselbe Verfahren an, das heißt:

  1. wir kopieren die externen Variablen in Datenfelder,
  2. überprüfen, ob die Kürzel korrekt ausgewählt wurden,
  3. überprüfen sie auf Fehler, legen die Bezeichner für die Indikatoren an, berechnen die Daten für den Posten und machen alles, was die jeweilige Strategie erfordert, und
  4. legen die Parameter für die Handelsoperationen fest.

Zum Abschluss wäre es nicht das Schlechteste zu überprüfen, ob ein und dasselbe Kürzel für mehrere Strategien ausgewählt worden ist (es folgt ein Beispiel für zwei Strategien):

//--- Check whether one and the same symbol is used in several strategies
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      for(int j=0; j<Strategy_B; j++)
        {
         if(IsTrade_B[j]==false) continue;
         if(Symbol_A[i]==Symbol_B[j])
           {
            Print(Symbol_A[i]," is used in several strategies!");
            ExpertRemove();
           }
        }
     }


3. Handelsarbeitsgänge der Art „for“

So sieht das Gerüst aus Arbeitsgängen der Art for im Inneren der Funktion OnTimer() aus:

void OnTimer()
  {
//--- Check if the terminal is connected to the trade server
   if(TerminalInfoInteger(TERMINAL_CONNECTED)==false) return;

//--- Section A: Main loop of the FOR operator for strategy A -----------
   for(int A=0; A<Strategy_A; A++)
     {
      //--- A.1: Check whether the symbol is allowed to be traded
      if(IsTrade_A[A]==false)
         continue; // terminate the current FOR iteration

     }

//--- Section В: Main loop of the FOR operator for strategy В -----------
   for(int B=0; B<Strategy_B; B++)
     {
      //--- B.1: Check whether the symbol is allowed to be traded
      if(IsTrade_B[B]==false)
         continue; // terminate the current FOR iteration

     }
  }


Wenn in einem Expert-System für nur eine Währung und lediglich eine Strategie eine Bedingung vorgesehen ist, bei deren Eintreten alle weiteren Berechnungen ausgesetzt werden, kommt der Operator return zur Anwendung. In diesem Fall müssen wir lediglich die aktuelle Iteration abbrechen und zur nächsten Iteration für das nachfolgende Kürzel übergehen. Dazu eignet sich am besten der Operator continue.

Wenn die Strategie in ein mehrwährungsfähiges Expert-System eingebettet werden soll, das über einen Arbeitsgang der Art for verfügt, innerhalb dessen die Bedingung für die Einstellung aller weiteren Berechnungen vorgeschrieben ist, kann folgendes Muster verwendet werden:

//--- Section N: Main loop of the FOR operator for strategy N -----------
for(int N=0; N<Strategy_N; N++)
  {

   //...
   bool IsInterrupt=false;
   for(int i=0; i<Number; i++)
     {
      if(...) // terminate all calculations
        {
         IsInterrupt=true;
         break;
        }
     }
   if(IsInterrupt=true)
      continue; // terminate the current FOR iteration
   //...

  }

Nach der Erstellung des Gerüstes aus for-Arbeitsgängen übertragen wir lediglich den Code aus den anderen Expert-Systemen dorthin und ersetzen einige Variablen durch Elemente aus den Datenfeldern.

Wir ersetzen beispielsweise die vordefinierten Variablen _Symbol durch Symbol_A[i] bzw. _Point durch Point_A[i]. Die Werte dieser Variablen sind für dieses Kürzel charakteristisch, weshalb sie bei der Bereitstellung in die Datenfelder kopiert worden sind.

Wir ermitteln beispielsweise den Wert des Indikators:

 //--- A.3: Lower band of BB calculated based on High prices
 if(CopyBuffer(BB_handle_high_A[A],LOWER_BAND,BBShift_A[A],1,BB_lower_band_high)<=0)
    continue; // terminate the current FOR iteration
 ArraySetAsSeries(BB_lower_band_high,true);

Das Schließen der Kaufposition programmieren wir wie folgt:

 //--- A.7.1: Calculate the current Ask and Bid prices
 SymbolInfo.Name(Symbol_A[A]);
 SymbolInfo.RefreshRates();
 double Ask_price=SymbolInfo.Ask();
 double Bid_price=SymbolInfo.Bid();

 if(PositionSelect(Symbol_A[A]))
   {
    //--- A.7.2: Closing a BUY position
    if(PositionInfo.PositionType()==POSITION_TYPE_BUY)
      {
       if(Bid_price>=BB_lower_band_high[0] || DealNumber_A[A]==0)
         {
          if(!Trade_A.PositionClose(Symbol_A[A]))
            {
             Print("Failed to close the Buy ",Symbol_A[A]," position. Code=",Trade_A.ResultRetcode(),
                   " (",Trade_A.ResultRetcodeDescription(),")");
             continue; // terminate the current FOR iteration
            }
          else
            {
             Print("The Buy ",Symbol_A[A]," position closed successfully. Code=",Trade_A.ResultRetcode(),
                   " (",Trade_A.ResultRetcodeDescription(),")");
             continue; // terminate the current FOR iteration
            }
         }
      }

    //...
   }

Eröffnen einer Kaufposition:

 //--- A.9.1: for a Buy
 if(Ask_price<=BB_lower_band_low[0])
   {
    //...

    //--- A.9.1.3: Execute a deal
    if(!Trade_A.Buy(OrderLot,Symbol_A[A]))
      {
       Print("The Buy ",Symbol_A[A]," has been unsuccessful. Code=",Trade_A.ResultRetcode(),
             " (",Trade_A.ResultRetcodeDescription(),")");
       continue; // terminate the current FOR iteration
      }
    else
      {
       Print("The Buy ",Symbol_A[A]," has been successful. Code=",Trade_A.ResultRetcode(),
             " (",Trade_A.ResultRetcodeDescription(),")");
       continue; // terminate the current FOR iteration
      }
   }

Bei der Außerfunktionssetzung (Bereinigung) des Expert-Systems dürfen wir nicht vergessen, die Erzeugung von Zeitgeberereignissen auszusetzen und die Indikatorbezeichner zu löschen.


4. Testergebnisse

Nachdem wir das Expert-System fertiggestellt haben, überprüfen wir es einzeln zu jeder Strategie und jedem Kürzel, und vergleichen die Ergebnisse mit dem, das wir erhalten, wenn wir es im gleichzeitigen Handel mit allen Strategien und Kürzeln testen.

Wir setzen dabei voraus, dass der Anwender bereits die besten Werte für die Eingangsparameter ausgewählt hat.


Es folgen die Einstellungen für das Strategieprüfprogramm:

Abb. 2. Einstellungen für das Strategieprüfprogramm

Abb. 2. Einstellungen für das Strategieprüfprogramm

Prüfergebnis der Strategie A für das Kürzel EURUSD:

Abb. 3. Prüfergebnis der Strategie A für das Kürzel EURUSD

Abb. 3. Prüfergebnis der Strategie A für das Kürzel EURUSD

Prüfergebnis der Strategie A für das Kürzel GBPUSD:

Abb. 4. Prüfergebnis der Strategie A für das Kürzel GBPUSD

Abb. 4. Prüfergebnis der Strategie A für das Kürzel GBPUSD

Prüfergebnis der Strategie B für das Kürzel AUDUSD:

Abb. 5. Prüfergebnis der Strategie B für das Kürzel AUDUSD

Abb. 5. Prüfergebnis der Strategie B für das Kürzel AUDUSD

Prüfergebnis der Strategie B für das Kürzel EURJPY:

Abb. 6. Prüfergebnis der Strategie В für das Kürzel EURJPY

Abb. 6. Prüfergebnis der Strategie В für das Kürzel EURJPY

Prüfergebnis mit allen Strategien und Kürzeln

Abb. 7. Prüfergebnis mit allen Strategien und Kürzeln

Abb. 7. Prüfergebnis mit allen Strategien und Kürzeln


Fazit

Wir haben ein praktisches und gleichzeitig einfaches Modell für ein systemübergreifendes mehrwährungsfähiges automatisches Handelssystem erhalten, in dem sich nahezu jede Strategie unterbringen lässt.

Ein solches „Expert-System“ trägt dazu bei, dass die Auswirkungen des Handels in Bezug auf alle Strategien besser eingeschätzt werden können. Es kann auch in dem Fall von Nutzen sein, wenn auf einem Konto nur ein automatisches Handelssystem eingesetzt werden kann. Zum Zweck des Studiums des oben dargelegten Materials ist der Quellcode des Expert-Systems im Anhang zu diesem Beitrag beigefügt.