Erstellen multimodularer Expert Advisors

9 Juli 2018, 14:18
Sergey Pavlov
0
219

Einleitung

In der Programmierung gibt es mehrere Ansätze: den modularen, objektorientierten und strukturierten. Der Artikel beschäftigt sich mit der modularen Programmierung in Bezug auf Handelsroboter.

Die modulare Programmierung stellt eine Entwicklungsmethode dar, die auf dem Teilen eines Programms in unabhängige Module basiert.

Die Grundregel der modularen Programmierung lautet "teile und herrsche". Der Vorteil einer modularen Architektur besteht darin, dass man jedes Modul aktualisieren (ersetzen) kann, ohne den Rest des Systems ändern zu müssen.

Die modulare Programmierung basiert auf drei Prinzipien.

  • Geheimnisprinzip von Parnas. Ein Modul dient zum Verbergen von Informationen und des Lösungsalgorithmus einer konkreten Aufgabe. Das Modul kann später durch ein anderes ersetzt werden.
  • Axiom der Modularität von Cowen. Ein Modul ist eine unabhängige Programmeinheit, die zur Ausführung einer bestimmten Funktion des Programms dient.
  • Assembly-Programmierung von Tseitin. Module stellen Bausteine dar, aus denen ein Programm gebaut wird.

Die einzige Alternative zu Modularität ist ein monolithisches Programm. Es ist jedoch nicht besonders praktisch: wenn Sie einige Funktionen des Programms ändern oder ergänzen wollen, muss der Code des Expert Advisors modifiziert werden. In den meisten Fällen kann das aber nur der Autor des Codes oder ein erfahrener Programmierer implementieren. Wenn das monolithische Programm dazu noch kompiliert wurde, kann dieses Produkt nur vom Urheberrechtsinhaber geändert werden. Vor diesem Hintergrund sieht viel praktischer die Möglichkeit aus, wichtige Funktionen des Programms selbst oder mit Hilfe von Drittentwicklern zu modifizieren, ohne den Autor involvieren zu müssen.

Abb. 1. Modularer Handelsroboter.
Abb. 1. Abstraktes Schema eines modularen Handelsroboters

Grundsatz der Modularität

Die modulare Programmierung ist die Kunst, eine Aufgabe in Unteraufgaben aufzuteilen, die als einzelne Module (Dateien) implementiert werden. Im Allgemeinen stellt ein Programmmodul ein separates Programm oder eine funktional vollständige autark kompilierbare Programmeinheit dar, die von einem anderen Modul aufgerufen und mit ihm identifiziert und verbunden wird. Mit anderen Worten stellt ein Modul ein funktional vollständiges Fragment eines Programms dar, realisiert als eine separate kompilierbare Datei, die für die Anwendung in anderen Programmen gedacht ist.

Beim Festlegen des Sets von Modulen, die Funktionen eines konkreten Algorithmus implementieren, ist es wichtig das Folgende zu berücksichtigen:

  • jedes Modul wird durch ein übergeordnetes Modul aufgerufen. Nach dem Abschluss seiner Arbeit gibt es dem Modul die Kontrolle zurück, von dem es aufgerufen wurde;
  • die wichtigsten Entscheidungen im Algorithmus werden auf der höchsten Ebene in der Hierarchie getroffen;
  • Module hängen nicht voneinander ab in Hinsicht auf Daten;
  • Module hängen nicht von vorherigen Aufrufen ab.

Zusammengefasst wird als Modulprogramm ein Programm bezeichnet, in welchem jeder Teil der logischen Struktur geändert werden kann, ohne dass dies zu Änderungen in den anderen Teilen führt.

Grundlegende Charakteristiken des Moduls:

  • Ein Input, ein Output — bei der Eingabe erhält das Programmmodul einen bestimmten Set von Ausgangsdaten, verarbeitet sie und gibt einen Set der resultierenden Daten zurück, d.h. es wird das EVA-Prinzip implementiert (Eingabe-Verarbeitung-Ausgabe);
  • funktionale Vollständigkeit — um eine Funktion auszuführen, führt das Modul alle festgelegten Operationen aus, die für den Abschluss der angefangenen Verarbeitung erforderlich sind;
  • logische Unabhängigkeit — das Ergebnis der Ausführung eines Programmmoduls hängt nur von Ausgangsdaten und nicht von der Ausführung der anderen Module ab;
  • geringe Verbindungen zwischen Programmmodulen — der Datenaustausch zwischen den Modulen muss möglichst minimiert werden.

In MQL5 kann man drei Programmtypen schreiben: Expert Advisors, Indikatoren und Skripts. Für das Hauptmodul ist am besten der Typ "Expert Advisor" geeignet, in dem die Steuerung aller Module implementiert wird und Handelsfunktionen gespeichert werden. Andere Module können beispielsweise als Indikatoren implementiert werden. Indikatoren passen tatsächlich perfekt für die Bildung eines Moduls: Daten, die nach einem vorgegebenen Algorithmus berechnet wurden, können in Indikatorpuffern gespeichert und dem multimodularen Expert Advisor übergeben werden wenn nötig. Der Expert Advisor kann diese Daten verwenden oder ignorieren, je nach gesetzter Aufgabe. In einigen Projekten können Expert Advisors als externe Module verwendet werden, dabei muss jedoch der Datenaustauschmechanismus gut durchdacht sein.

Sie haben bestimmt Modultechnologien in Ihren Expert Advisors schon mehrmals verwendet: zum Beispiel benutzerdefinierte Indikatoren als Module für die Erzeugung und Filterung von Handelssignalen.

Meiner Ansicht nach sieht die rationellste Lösung wie folgt aus: alle Basisfunktionen befinden sich im Hauptmodul und brauchen keine externen Module. Externe Module werden wiederum für die Anpassung an verschiedene Marktbedingungen und die Verbesserung der Handelsstrategie benötigt. Den Set der Funktionen eines Programms bestimmt nicht der Autor des Codes oder der Strategie, sondern der Trader — der Nutzer des Handelsroboters. Es ist wichtig zu betonen, dass dabei die Rechte von keinem verletzt werden.

Hauptmodul — ein Expert Advisor

Das Hauptmodul ist das wichtigste Modul in der Hierarchie eines Expert Advisors. Dieses Modul umfasst die Steuerung des ganzen Projekts. In diesem Modul müssen Handelsfunktionen abgelegt werden, ohne welche jede Handelsstrategie sinnlos ist.

Der Artikel betrachtet die Erstellung eines multimodularen Expert Advisors anhand eines Beispiels aus der CodeBase. Der ursprüngliche Expert Advisor handelt mit einer festen Lotgröße im Kanal des Indikators iBands mit einer Umkehr der Position an den Kanalgrenzen. Der Expert Advisor ist autark und benötigt keine externen Programme.

Nicht jeder Expert Advisor kann multimodular sein, sondern nur der, dessen Algorithmus diese Option beinhaltet.

Was muss dem Code hinzugefügt werden, damit er zu einem modularen Projekt wird?

  1. Externe Module (Indikatoren) deklarieren, die der Nutzer später verwenden kann. 
  2. Die benötigte Funktionalität für die Integration der Module hinzufügen.
  3. Dokumentation für Entwickler externer Module vorbereiten (die Funktion der Erzeugung der Dokumentation in einer separaten Datei aktivieren). Für die Entwicklung externer Module werden Informationen über die Struktur von Daten benötigt, die vom Hauptmodul richtig verwendet werden können. In dem zu betrachtenden Beispiel muss das Modul für Money Management dem Expert Advisor die Lotgröße übergeben, und das Modul für die Verwaltung der Position — den Abstand vom aktuellen Preis in Punkten.

Als Ergebnis erhalten wir einen modularen Expert Advisor, zu dem man bis zu sieben externe Module hinzufügen kann.

  • Modul 1 — Modul für Money Management. Ausgegeben wird die Lotgröße.
  • Modul 2 — Modul für die Verwaltung von Positionen und das Setzen von SL. Ausgegeben wird der Abstand zwischen dem Stop Loss und dem Eröffnungspreis der Position in Punkten.
  • Modul 3 — Modul für die Verwaltung von Positionen und das Setzen von TP. Ausgegeben wird der Abstand zwischen dem Take Profit und dem Eröffnungspreis der Position in Punkten.
  • Modul 4 — Modul für die Verwaltung von Positionen und das Setzen von Trailing Stop. Ausgegeben wird der Abstand zwischen dem Stop Loss und dem aktuellen Preis in Punkten.
  • Modul 5 — Modul für die Erzeugung von Handelssignalen. Ausgegeben wird der Wert des Signals.
  • Modul 6 — Modul für das Filtern von Handelssignalen. Ausgegeben wird der Wert des Filters.
  • Modul 7 — Modul für die Verwaltung der Position und für das Setzen eines Breakeven-Levels. Ausgegeben wird der Abstand zwischen Stop Loss und dem Eröffnungspreis der Position.


Abb. 2. Die Funktion OnInit() und die Initialisierung externer Module

 

Abb. 2. Die Funktion OnInit() und die Initialisierung externer Module

Abb. 3. Die Funktion OnTick() und das Lesen von Daten aus externen Modulen.
Abb. 3. Die Funktion OnTick() und das Lesen von Daten aus externen Modulen

Abb. 4. Die Funktion OnTrade() und das Lesen von Daten aus externen Modulen.

Abb. 4. Die Funktion OnTrade() und das Lesen von Daten aus externen Modulen

Abb. 5. Funktion der Erzeugung von Handelssignalen und das Lesen von Daten aus externen Modulen.


Abb. 5. Die Funktion der Erzeugung von Handelssignalen und das Lesen von Daten aus externen Modulen

 

//****** project (module expert): test_module_exp.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project TEST Main module."
#property link      "The project uses 7 external modules."
//---
#include <Trade\Trade.mqh>
//---
MqlTick    last_tick;
CTrade     trade;
//---
input int                  e_bands_period=80;            // Moving average period
int                        e_bands_shift=0;              // shift
input double               e_deviation=3.0;              // Number of standard deviations
input ENUM_APPLIED_PRICE   e_applied_price=PRICE_CLOSE;  // Price type
input bool                 on_module=false;              // whether or not to use plug-ins
//---
double lot=0.01;           // Fixed lot
double min_lot=0.01;       // Minimum allowable lot
bool   on_trade=false;     // Trade function flag
//--- Variable for storing the indicator iBands handle 
int    handle_Bands;
//--- module 1
bool   on_lot=false;
int    handle_m1;
//--- module 2
bool   on_SL=false;
int    handle_m2;
//--- module 3
bool   on_TP=false;
int    handle_m3;
//--- module 4
bool   on_Trail=false;
int    handle_m4;
//--- module 5
bool   on_signals=false;
int    handle_m5;
//--- module 6
bool   on_Filter=false;
int    handle_m6;
//--- module 7
bool   on_Breakeven=false;
int    handle_m7;
//+------------------------------------------------------------------+
//| Structure of trading signals                                     |
//+------------------------------------------------------------------+
struct sSignal
  {
   bool              Buy;    // Buy signal
   bool              Sell;   // Sell signal
  };
//+------------------------------------------------------------------+
//| Trading signals generator                                        |
//+------------------------------------------------------------------+
sSignal Buy_or_Sell()
  {
   sSignal res={false,false};
//--- MODULE 5
   if(on_signals)
     { // If there is an additional module
      double buffer_m5[];
      ArraySetAsSeries(buffer_m5,true);
      if(CopyBuffer(handle_m5,0,0,1,buffer_m5)<0) return(res);
      if(buffer_m5[0]<-1) res.Sell=true;
      if(buffer_m5[0]>1) res.Buy=true;
     }
//--- MODULE 6
   if(on_Filter)
     { // If there is an additional module
      double buffer_m6[];
      ArraySetAsSeries(buffer_m6,true);
      if(CopyBuffer(handle_m6,0,0,1,buffer_m6)<0) return(res);
      lot=buffer_m6[0];
      if(buffer_m6[0]<1) res.Buy=false;
      if(buffer_m6[0]>-1) res.Sell=false;
     }
//---
//--- Indicator buffers
   double         UpperBuffer[];
   double         LowerBuffer[];
   double         MiddleBuffer[];
   ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer);
   ArraySetAsSeries(UpperBuffer,true);  CopyBuffer(handle_Bands,1,0,1,UpperBuffer);
   ArraySetAsSeries(LowerBuffer,true);  CopyBuffer(handle_Bands,2,0,1,LowerBuffer);
//--- Timeseries
   double L[];
   double H[];
   ArraySetAsSeries(L,true); CopyLow(_Symbol,_Period,0,1,L);
   ArraySetAsSeries(H,true); CopyHigh(_Symbol,_Period,0,1,H);
   if(H[0]>UpperBuffer[0]&& L[0]>MiddleBuffer[0]) res.Sell=true;
   if(L[0]<LowerBuffer[0] && H[0]<MiddleBuffer[0]) res.Buy=true;
//---
   return(res);
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create the indicator handle
   handle_Bands=iBands(_Symbol,_Period,e_bands_period,e_bands_shift,e_deviation,e_applied_price);
   if(handle_Bands==INVALID_HANDLE)
      return(INIT_FAILED);
   else
      on_trade=true;
   if(on_module)
     {
      //--- MODULE 1
      //--- check: whether there is an external module?
      handle_m1=iCustom(NULL,0,"Market\\test_module_MM");
      if(handle_m1!=INVALID_HANDLE)
         on_lot=true;
      //--- MODULE 2
      //--- check: whether there is an external module?
      handle_m2=iCustom(NULL,0,"Market\\test_module_SL");
      if(handle_m2!=INVALID_HANDLE)
         on_SL=true;
      //--- MODULE 3
      //--- check: whether there is an external module?
      handle_m3=iCustom(NULL,0,"Market\\test_module_TP");
      if(handle_m3!=INVALID_HANDLE)
         on_TP=true;
      //--- MODULE 4
      //--- check: whether there is an external module?
      handle_m4=iCustom(NULL,0,"Market\\test_module_Trail");
      if(handle_m4!=INVALID_HANDLE)
         on_Trail=true;
      //--- MODULE 5
      //--- check: whether there is an external module?
      handle_m5=iCustom(NULL,0,"Market\\test_module_signals");
      if(handle_m5!=INVALID_HANDLE)
         on_signals=true;
      //--- MODULE 6
      //--- check: whether there is an external module?
      handle_m6=iCustom(NULL,0,"Market\\test_module_Filter");
      if(handle_m6!=INVALID_HANDLE)
         on_Filter=true;
      //--- MODULE 7
      //--- check: whether there is an external module?
      handle_m7=iCustom(NULL,0,"Market\\test_module_Breakeven");
      if(handle_m7!=INVALID_HANDLE)
         on_Breakeven=true;
     }
//--- Minimum allowable volume for trading operationsn
   min_lot=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   double Equity=AccountInfoDouble(ACCOUNT_EQUITY);
//--- MODULE 1
   if(on_lot)
     { // If there is an additional module
      double buffer_m1[];
      ArraySetAsSeries(buffer_m1,true);
      if(CopyBuffer(handle_m1,0,0,1,buffer_m1)<0) return;
      lot=buffer_m1[0];
     }
//--- MODULE 4
   if(on_Trail)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m4[];
            ArraySetAsSeries(buffer_m4,true);
            if(CopyBuffer(handle_m4,0,0,1,buffer_m4)<0) return;
            double TR=buffer_m4[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
           }
//--- MODULE 7
   if(on_Breakeven)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m7[];
            ArraySetAsSeries(buffer_m7,true);
            if(CopyBuffer(handle_m7,0,0,1,buffer_m7)<0) return;
            double TRB=buffer_m7[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TRB*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-5*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TRB*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+5*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
           }
//---
   if(lot<min_lot) lot=min_lot;
//---
   if(on_trade)
     {
      sSignal signal=Buy_or_Sell();
      //--- The value of the required and free margin
      double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE);
      //--- BUY
      if(signal.Buy)
        {
         if(!PositionSelect(_Symbol))
           {
            SymbolInfoTick(_Symbol,last_tick);
            if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,NormalizeDouble(lot,2),last_tick.ask,margin))
               if(margin<Equity)
                  trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(lot,2),last_tick.ask,0,0,"BUY: new position");
           }
         else
           {
            if(PositionGetDouble(POSITION_PROFIT)<0) return;
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               trade.PositionClose(_Symbol);
               SymbolInfoTick(_Symbol,last_tick);
               if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,NormalizeDouble(lot,2),last_tick.ask,margin))
                  if(margin<Equity)
                     trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(lot,2),last_tick.ask,0,0,"BUY: reversal");
              }
           }
        }
      //--- SELL
      if(signal.Sell)
        {
         if(!PositionSelect(_Symbol))
           {
            SymbolInfoTick(_Symbol,last_tick);
            if(OrderCalcMargin(ORDER_TYPE_SELL,_Symbol,NormalizeDouble(lot,2),last_tick.bid,margin))
               if(margin<Equity)
                  trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(lot,2),last_tick.bid,0,0,"SELL: new position");
           }
         else
           {
            if(PositionGetDouble(POSITION_PROFIT)<0) return;
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               trade.PositionClose(_Symbol);
               SymbolInfoTick(_Symbol,last_tick);
               if(OrderCalcMargin(ORDER_TYPE_SELL,_Symbol,NormalizeDouble(lot,2),last_tick.bid,margin))
                  if(margin<Equity)
                     trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(lot,2),last_tick.bid,0,0,"SELL: reversal");
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Trade function                                                   |
//+------------------------------------------------------------------+
void OnTrade()
  {
   if(on_SL && on_TP) // If there is an additional module
     {
      //--- MODULE 2
      double buffer_m2[];
      ArraySetAsSeries(buffer_m2,true);
      if(CopyBuffer(handle_m2,0,0,1,buffer_m2)<0) return;
      double SL=buffer_m2[0];
      //--- MODULE 3
      double buffer_m3[];
      ArraySetAsSeries(buffer_m3,true);
      if(CopyBuffer(handle_m3,0,0,1,buffer_m3)<0) return;
      double TP=buffer_m3[0];
      //--- Position modification
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_SL)==0)
           {
            //--- BUY
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)+TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)-SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
            //--- SELL
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)-TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)+SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
           }
     }
  }

Wenn in dem im Programm angegebenen Verzeichnis die benötigten Module (Dateien) nicht vorhanden sind, verwendet der Handelsroboter die Standardfunktionalität. Das Nichtvorhandensein der externen Module wirkt sich nicht kritisch auf die Funktionsfähigkeit des Expert Advisors.

Besonders wichtige Module

Wie kann man ein monolithisches Programm in einen multimodularen Expert Advisor umwandeln? Ein modulares Projekt beginnt mit der Analyse der allgemeinen Aufgabe und der Bestimmung funktional vollständiger Fragmente, die man später als kompilierte Module realisieren kann. Dabei muss man besonders typische Funktionen auswählen, die die Funktionsweise des Expert Advisors wesentlich ändern können und auf verschiedenen Algorithmen basieren. Es ist bekannt, dass in den meisten Expert Advisors die gleichen Verfahren verwendet werden: 

  • Modul für Money (Risiko) Management;
  • Modul für die Verwaltung von Positionen (SL und TP);
  • Modul für Trailing Stop;
  • Modul für die Erzeugung von Handelssignalen;
  • Modul für das Filtern von Signalen.

Es gibt eine Vielzahl von Varianten, jedes der aufgelisteten Module zu implementieren. In diesem Artikel zeigen wir die einfachsten Lösungen, denn für uns ist der Ansatz der modularen Programmierung wichtiger als mehrzeilige Funktionen.

Methode der Erstellung von Hilfsmodulen

Ein Hilfsmodul (externes Modul) ist ein Indikator, der eine bestimmte Funktion ausführt und Ausgabedaten in Indikatorpuffern speichert. Das Hauptmodul verwendet diese Daten, wenn nötig. Auf diese Weise passt sich der Expert Advisor an die Anforderungen des Händlers an, der diese Handelsstrategie verwendet. Einer und derselbe Expert Advisor kann für jedes konkrete Finanzinstrument oder für jeden Broker neu zusammengestellt werden. Der Händler bekommt quasi Bausteine, aus welchen er eine unbegrenzte Anzahl an Expert Advisors zusammenstellen kann.

Programmieren ist ein arbeitsaufwändiger Prozess. Obwohl es einen kreativen Anteil gibt, beinhaltet es viele Routineoperationen, die man am besten automatisiert. Die Automatisierung erhöht unter anderem die Leistung und reduziert die Anzahl von Fehlern.

Dem Artikel ist der Generator der Module angehängt, mit dem man bis zu 8 Dateien in wenigen Sekunden erzeugen kann, die zu einem multimodularen Projekt verbunden werden. Dies erleichtert und beschleunigt den Prozess der Entwicklung und der Zusammenstellung (s. Video).

Control Panel des Generators der multimodularen Projekte

Video 1. Control Panel des Generators der multimodularen Projekte

Im Panel kann man angeben, welche Module für das zu erstellende Projekt erzeugt werden müssen. In unserem Beispiel wird das Projekt "test" erstellt. Ja nach der ausgewählten Kombination von Modulen erzeugt der Generator einen Code, in dem es keine unnötigen Blöcke und Dateien gibt.

Die erzeugten Dateien werden im Order "Files" (s. Abb. 2) gespeichert. Der Name des Hauptmoduls "test_module_exp.mq5" setzt sich aus dem Namen des Projekts "test" und "_module_exp.mq5" zusammen. Speichern Sie es im Ordner "Experts" und alle anderen externen Module —  im Ordner "Indicators\Market".

Abb. 6. Erzeugte Dateien des Projekts "test".

Abb. 6. Erzeugte Dateien des Projekts "test".

Danach kompilieren wir alle Dateien und beginnen mit dem Testen des multimodularen Projekts.

Video 2. Kompilierung der erzeugten Dateien des Projekts "test"
  

Das manuelle Erstellen eines ähnlichen Projekt wird viel Zeit in Anspruch nehmen, und beginnen sollte man offensichtlich mit dem Hauptmodul. Nach dem die externen Module definiert sind, die später zum Projekt hinzugefügt werden können, fahren wir mit der Projektierung und Programmierung fort. Besonders wichtig ist es, die Werte der Ausgangsdaten von Hilfsmodulen, auf die das Hauptmodul wartet, zu kontrollieren. Da die Module Indikatoren sind, und Indikatorpuffer Werte nur reelen Typs beinhalten, muss im Hauptmodul die Umwandlung der Variablen vom reelen Typ in den Typ, der dem Algorthmus entspricht, vorgesehen sein.

Externe Module sollten so programmiert werden, dass sie ohne Eingabeparameter im Hauptmodul aufgerufen werden können, mit anderen Worten standardmäßig. Solcher Aufrufmechanismus erleichtert die Entwicklung des Verwaltungssystems für externe Daten.

Schauen wir uns genauer an, welche externe Module in Handelsstrategien besonders wichtig sind.

Beispiel 1: Modul für Money Management

Dieses externe Modul berechnet die Lotgröße für die Eröffnung einer Order. Unten ist die einfachste Variante für die Berechnung des Handelsvolumens (in Prozent von Equity) implementiert:

//****** project (module MM): test_module_MM_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module MM"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
input double   lot_perc=0.1;  // Percentage of Equity value
double         Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double Equity=AccountInfoDouble(ACCOUNT_EQUITY);
//--- calculation of the lot of equity
   Buffer1[0]=NormalizeDouble(Equity*lot_perc/1000.0,2); // Lot size determination function
   if(Buffer1[0]<0.01) Buffer1[0]=0.01;
   return(rates_total);
  };

Wenn man dieses Modul standardmäßig aufruft (ohne Eingabeparameter), erhält das Hauptmodul die erlaubte Lotgröße für die Ausführung eines Trades in Höhe 0,1% von Equity. Ein Beispiel für den Aufruf des Moduls aus dem Hauptprogramm:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   double Equity=AccountInfoDouble(ACCOUNT_EQUITY);
//--- MODULE 1
   if(on_lot)
     { // If there is an additional module
      double buffer_m1[];
      ArraySetAsSeries(buffer_m1,true);
      if(CopyBuffer(handle_m1,0,0,1,buffer_m1)<0) return;
      lot=buffer_m1[0];
     }
  
  ...
  
  }

Beispiel 2: Modul für die Verwaltung von Positionen (SL, TP und Trailing Stop)

Das Setzen von Stop-Loss (SL) und Take-Profit (TP) ist eine der Methoden der Verwaltung einer offenen Position. Da in verschiedenen Handelsstrategien verschiedene Kombinationen für die Berechnung und das Setzen von SL und TP verwendet werden, drängt sich die Variante mit dem Teilen in zwei Module auf: für SL und TP. Aber wenn wir uns entscheiden, SL und TP in einem Modul zu vereinen, sollten ihre Werte in verschiedenen Indikatorpuffern platziert werden.

Modul für das Setzen von SL:

//****** project (module SL): test_module_SL_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module SL"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double SL=100; // SL in points
//--- calculation of the SL
   Buffer1[0]=SL;
   return(rates_total);
  };

Modul für das Setzen von TP:

//****** project (module TP): test_module_TP_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module TP"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double TP=100; // TP in points
//--- calculation of the TP
   Buffer1[0]=TP;
   return(rates_total);
  };

Die angeführten Codes stellen eine besonders anschauliche Variante für die Berechnung von SL und TP dar — in Punkten. Genauer gesagt werden die Levels nicht berechnet, sondern durch eine Konstante gesetzt. Dabei werden die Werte direkt im Programm und nicht in den Eingabeparametern angegeben. Das wurde getan, um die Variante der Umsetzung externer Module ohne Eingabedaten zu demonstrieren. Solchen Code kann jeder angehender Programmierer schreiben.

Ich empfehle, die betrachteten Module in der Funktion OnTrade aufzurufen. Das sieht ungefähr so aus:

//+------------------------------------------------------------------+
//| Trade function                                                   |
//+------------------------------------------------------------------+
void OnTrade()
  {
   if(on_SL && on_TP) // If there is an additional module
     {
      //--- MODULE 2
      double buffer_m2[];
      ArraySetAsSeries(buffer_m2,true);
      if(CopyBuffer(handle_m2,0,0,1,buffer_m2)<0) return;
      double SL=buffer_m2[0];
      //--- MODULE 3
      double buffer_m3[];
      ArraySetAsSeries(buffer_m3,true);
      if(CopyBuffer(handle_m3,0,0,1,buffer_m3)<0) return;
      double TP=buffer_m3[0];
      //--- Position modification
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_SL)==0)
           {
            //--- BUY
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)+TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)-SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
            //--- SELL
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)-TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)+SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
           }
     }
  }

Außer den statischen Werten von SL und TP, die direkt nach der Eröffnung einer Position gesetzt werden, werden Trailing Stop oder Floating SL häufig verwendet. Am häufigsten wird ein Trailing Stop gesetzt, nachdem eine Position profitabel wird. Schauen Sie sich die offensichtlichste Variante dessen Implementierung an: wir setzen den Abstand zwischen SL und dem aktuellen Preis in Punkten.

//****** project (module Trail): test_module_Trail_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Trail"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double TR=50;  // Trail in points
//--- calculation of Trail
   Buffer1[0]=TR;
   return(rates_total);
  };

Hier wie in den vorherigen Codes für SL und TP wird der Abstand für die Berechnung von Trailing Stop durch eine Konstante gesetzt, um das Programm und das Lesen zu vereinfachen.

Der Aufruf des Trailing Stop Moduls muss in der Funktion OnTick implementiert werden, denn der aktuelle Preis ändert sich bei jedem Tick und der Stop-Level muss ununterbrochen kontrolliert werden. Ob er geändert wird, entscheidet das Hauptmodul. Wenn wir den Wert der Verschiebung in Punkten angeben, modifiziert der Expert Advisor die Position und verschiebt den SL Level in die Richtung, wo der Gewinn steigt.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

   ...

//--- MODULE 4
   if(on_Trail)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m4[];
            ArraySetAsSeries(buffer_m4,true);
            if(CopyBuffer(handle_m4,0,0,1,buffer_m4)<0) return;
            double TR=buffer_m4[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
           }

   ...

  }

Es gibt eine weitere Methode der Verwaltung von Positionen — das Setzen von SL an der Gewinnschwelle. Wenn SL ausgelöst wird, wird die Position mit einem Null-Ergebnis oder einem vorgegebenen Gewinn geschlossen. Das Modul kann wie folgt aussehen:

//****** project (module Breakeven): test_module_Breakeven_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Breakeven"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double Breakeven=100; // Breakeven in points
//--- calculation of the Breakeven
   Buffer1[0]=Breakeven;
   return(rates_total);
  };

In diesem Modul wird der Abstand zwischen dem aktuellen Preis und dem Open-Preis der Position in Punkten für das Setzen von SL an die Gewinnschwelle angegeben. Der Aufruf des Breakeven-Modul muss auch in der Funktion OnTick platziert werden.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

   ...

//--- MODULE 7
   if(on_Breakeven)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m7[];
            ArraySetAsSeries(buffer_m7,true);
            if(CopyBuffer(handle_m7,0,0,1,buffer_m7)<0) return;
            double TRB=buffer_m7[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TRB*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-5*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TRB*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+5*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
           }

   ...
   
  }

Beispiel 3: Modul für die Erzeugung von Handelssignalen

Das ist wohl das komplizierteste Modul hinsichtlich der Implementierung. Das Modul muss Signale für die Ausführung von Handelsoperationen senden: Orders platzieren, Positionen schließen usw. Die Schwierigkeit der Entwicklung besteht darin, dass fast alle Indikatoren für Handelsbedingungen angepasst werden müssen. Es gibt kaum Indikatoren mit den gleichen Eingabeparametern, die Signale für verschiedene Symbole generieren.

Offensichtlich sollte das Hauptprogramm die Signalmodule nicht selbständig einstellen, denn wegen einer hohen Anzahl der Module kann die Arbeit des modularen Projekts gelähmt werden. Daraus folgt, dass die Indikatoren, die Handelssignale erzeugen, im Voraus vorbereitet sein müssen, bevor sie ins Projekt eingebunden werden. Darauf gehen wir im Kapitel über die Optimierung von multimodularen Expert Advisors ein. Und jetzt schauen Sie sich den Code des Moduls von Handelssignalen an:

//****** project (module signals): test_module_signals_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Trading signals"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//--- Variable for storing the indicator iBands handle 
int    handle_Bands;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   handle_Bands=iBands(_Symbol,_Period,20,0,3.5,PRICE_CLOSE);
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double signal=0.0;
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
//--- Indicator buffers
   double         UpperBuffer[];
   double         LowerBuffer[];
   ArraySetAsSeries(UpperBuffer,true);  CopyBuffer(handle_Bands,1,0,1,UpperBuffer);
   ArraySetAsSeries(LowerBuffer,true);  CopyBuffer(handle_Bands,2,0,1,LowerBuffer);
//--- calculation of the Trading signals
   if(high[0]>UpperBuffer[0]) signal=-2.0;
   if(low[0]<LowerBuffer[0]) signal=2.0;
   Buffer1[0]=signal;
   return(rates_total);
  };

In den Indikatorpuffer des Signal-Moduls werden die folgenden Werte geschrieben:

  • 2.0 — wenn ein BUY-Signal gebildet wurde;
  • 0.0 — wenn es keine Handelssignale gibt;
  • -2.0 — wenn ein SELL-Signal gebildet wurde.

Die erhaltenen Werte des Moduls der Handelssignale sind am besten in einer spezielle Funktion des Hauptmoduls zu verwenden — zum Beispiel wie folgt:

//+------------------------------------------------------------------+
//| Trading signals generator                                        |
//+------------------------------------------------------------------+
sSignal Buy_or_Sell()
  {
   sSignal res={false,false};
//--- MODULE 5
   if(on_signals)
     { // If there is an additional module
      double buffer_m5[];
      ArraySetAsSeries(buffer_m5,true);
      if(CopyBuffer(handle_m5,0,0,1,buffer_m5)<0) return(res);
      if(buffer_m5[0]<-1) res.Sell=true;
      if(buffer_m5[0]>1) res.Buy=true;
     }
//--- MODULE 6
   if(on_Filter)
     { // If there is an additional module
      double buffer_m6[];
      ArraySetAsSeries(buffer_m6,true);
      if(CopyBuffer(handle_m6,0,0,1,buffer_m6)<0) return(res);
      lot=buffer_m6[0];
      if(buffer_m6[0]<1) res.Buy=false;
      if(buffer_m6[0]>-1) res.Sell=false;
     }
//---
//--- Indicator buffers
   double         UpperBuffer[];
   double         LowerBuffer[];
   double         MiddleBuffer[];
   ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer);
   ArraySetAsSeries(UpperBuffer,true);  CopyBuffer(handle_Bands,1,0,1,UpperBuffer);
   ArraySetAsSeries(LowerBuffer,true);  CopyBuffer(handle_Bands,2,0,1,LowerBuffer);
//--- Timeseries
   double L[];
   double H[];
   ArraySetAsSeries(L,true); CopyLow(_Symbol,_Period,0,1,L);
   ArraySetAsSeries(H,true); CopyHigh(_Symbol,_Period,0,1,H);
   if(H[0]>UpperBuffer[0]&& L[0]>MiddleBuffer[0]) res.Sell=true;
   if(L[0]<LowerBuffer[0] && H[0]<MiddleBuffer[0]) res.Buy=true;
//---
   return(res);
  }

Es gibt eine Vielzahl von Handelsstrategien, jede von ihnen hat eigene Signale. Deswegen muss die Arbeit des Moduls der Handelssignale so organisiert werden, dass sie in die gegebene Strategie passen. Die Handelsstrategie sollte in der Dokumentation zum Expert Advisor beschrieben werden, damit die Entwickler von Signal-Modulen entsprechend den technischen Anforderungen des modularen Projekts vorgehen.

Beispiel 4: Modul für das Filtern von Signalen

Um die Profitabilität von Handelsrobotern zu erhöhen, werden häufig Filter von Handelssignalen verwendet. Dazu gehören, zum Beispiel, Trend, Handelszeit, Nachrichten, zusätzliche Signal-Indikatoren usw.

//****** project (module Filter): test_module_Filter_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Filter"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//--- Variable for storing the indicator iBands handle 
int    handle_Bands;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   handle_Bands=iBands(_Symbol,_Period,35,0,4.1,PRICE_CLOSE);
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double filtr=0.0;
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
//--- Indicator buffers
   double         MiddleBuffer[];
   ArraySetAsSeries(MiddleBuffer,true);  CopyBuffer(handle_Bands,0,0,1,MiddleBuffer);
//--- calculation of the filter
   if(high[0]<MiddleBuffer[0]) filtr=2.0;
   if(low[0]>MiddleBuffer[0]) filtr=-2.0;
   Buffer1[0]=filtr;
   return(rates_total);
  };

Wir haben verschiedene Varianten der Implementierung der externen Module und das Prinzip deren Integration in einen modularen Expert Advisor betrachtet.

Optimierung multimodularer Expert Advisors

Die Optimierung multimodularer Expert Advisors ist wohl eine der zentralen Fragen. Wie sind die Eingabeparameter der Module im Strategietester zu optimieren? Wenn sie nicht im Hauptmodul angegeben wurden, dann können sie nicht optimiert werden (oder fast nicht). Man kann versuchen, die Eingabeparameter der externen Module diskret zu setzen und den Expert Advisor danach zu testen. Das ist aber sehr aufwendig und wahrscheinlich auch sinnlos. Das passt uns nicht. Was tun?

Eine der möglichen Varianten ist es, sich selbst optimierende Indikatoren als externe Module zu nutzen. Zum Thema der automatisierten Optimierung wurden viele Artikel und Beispiele geschrieben. Ich möchte auch meinen Beitrag zu diesem Thema leisten. Nehmen wir die Ideen aus dem Artikel "Visuelles Testen der Rentabilität von Indikatoren und Alerts". Der Autor des Artikels schlägt vor, als Ausführungspreis eines virtuellen Trades den maximalen Wert der Kerze für eine BUY-Position und den minimalen Wert — für SELL zu verwenden. D.h. es werden die schlechtesten Handelsbedingungen ausgewählt, so werden die Eingabeparameter optimiert. Es wird angenommen, dass das Ergebnis im realen Handel bei den erhaltenen optimalen Werte nicht schlechter sein wird (im selben Zeitraum der historischen Daten). Im realen Handel kann der Gewinn nach keiner Optimierung garantiert werden.

Die Strategie unseres Expert Advisors basiert auf dem Handel im Kanal des Indikators Bollinger mit einer Umkehr der Position an den Kanalgrenzen. Nehmen wir einen anderen Indikator und den Kanal zeichnen wir basierend auf dem Envelope Indikator: vom MA Indikator bilden wir die Grenzen, die vom MA gleich entfernt sind. Der neue Signalindikator wird vor der Anwendung sich automatisch optimieren. Als Eingabeparameter werden optimale Werte verwendet, bei welchen der höchste Gewinn erzielt wurde. Für die Optimierung wurden zwei Parameter ausgewählt — die MA-Periode und der Abstand zwischen den Grenzen und dem MA.

Der Algorithmus der Erstellung eines Signal-Indikators mit der Funktion der Selbstoptimierung:

  1. Parameter und das Kriterium der Optimierung festlegen. In unserem Fall dienen als Parameter die MA-Periode und der Abstand für die Verschiebung der Grenzen, als Kriterium dient der maximale Gewinn.
  2. Erstellen wir einen Optimierungsblock im Indikator. In dem Beispiel wird über alle Eingabedaten im angegebenen Zeitraum mit einem festen Schritt iteriert. Die MA-Periode - von 10 bis 100 mit einem Schritt von 10, für die Verschiebung wird es über die Werte von 1000 bis 10000 mit einem Schritt von 1000 iteriert.
//+------------------------------------------------------------------+
//|                           Copyright 2018, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Trading signals"
//---
#include <MovingAverages.mqh>
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//+------------------------------------------------------------------+
//| Struktur der Optimierungsergebnisse                                |
//+------------------------------------------------------------------+
struct Opt
  {
   int               var1;          // optimaler Wert des Parameters 1
   int               var2;          // optimaler Wert des Parameters 2
   double            profit;        // Gewinn
  };
//---
double      Buffer1[];
bool        optimum=false;
Opt         test={NULL,NULL,NULL};
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   optimum=false;
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   double signal=0.0;
   Buffer1[0]=signal;
//--- optimization of input parameters
   if(!optimum)
     {
      ArraySetAsSeries(close,false);
      int count=rates_total;
      int total=0;
      int total_profit=0;
      for(int d=1000;d<10001;d+=1000)
         for(int j=10;j<101;j+=10)
           {
            double shift=d*_Point;
            bool open_buy=false;
            bool open_sell=false;
            double price_buy=0;
            double price_sell=0;
            double profit=0;
            int order=0;
            for(int i=j+1;i<count;i++)
              {
               double ma=SimpleMA(i,j,close);
               double sell=ma+shift;
               double buy=ma-shift;
               //--- BUY
               if(buy>close[i] && !open_buy)
                 {
                  price_buy=high[i]+spread[i]*_Point;
                  if(order==0) profit=0;
                  else profit+=price_sell-price_buy;
                  order++;
                  open_buy=true;
                  open_sell=false;
                 }
               //--- SELL
               if(sell<close[i] && !open_sell)
                 {
                  price_sell=low[i]-spread[i]*_Point;
                  if(order==0) profit=0;
                  else profit+=price_sell-price_buy;
                  order++;
                  open_sell=true;
                  open_buy=false;
                 }
               //---
              }
            if(profit>0)
               if(profit>test.profit)
                 {
                  test.var1=j;
                  test.var2=d;
                  test.profit=profit;
                  total_profit++;
                 }
            //---
            Comment("Optimierung der Eingabeparameter..."," Durchläufe=",total," // Profitable =",total_profit);
            total++;
           }
      //---
      Print(" Optimierung abgeschlossen: ",test.var1," ",test.var2);
      Comment("Optimierung abgeschlossen");
      optimum=true;
     }
//---
   if(optimum)
      if(test.profit>0)
        {
         ArraySetAsSeries(close,true);
         double ma=SimpleMA(0,test.var1,close);
         double sell=ma+test.var2*_Period;
         double buy=ma-test.var2*_Period;
         //--- calculation of the Trading signals
         if(buy>close[0]) signal=2.0;
         if(sell<close[0]) signal=-2.0;
        }
//--- Indicator buffers
   Buffer1[0]=signal;
   return(rates_total);
  };

Natürlich kann die Optimierung einige Zeit in Anspruch nehmen, während der der Expert Advisor keine Trades ausführen kann. Wenn der modulare Expert Advisor rund um die Uhr handelt, sollte eine Verzögerung wegen der Selbstoptimierung die allgemeine Handelszeit nicht wesentlich beeinflussen.

Fazit

  1. Es ist möglich, einen multimodularen Expert Advisor zu schreiben, und in einigen Fällen ist das sogar wirtschaftlich vorteilhaft.
  2. Der Artikel schildert ein primitives Konzept eines Handelsroboters mit externen Modulen. Nichtsdestotrotz erlaubt die modulare Programmierung, komplexe Projekte zu erstellen, zu welchen man Drittentwickler einladen kann. Bei der Erstellung von Modulen können sie den Code nicht offenlegen und auf diese Weise ihre Urheberrechte auf Algorithmen schützen.
  3. Die Frage über die Optimierung modularer Projekte bleibt offen. Die Selbstoptimierung von Signal-Indikatoren, die als Signal-Module oder Filter verwendet werden, ist ein Thema, das weiterentwickelt werden muss.

Hinweis: Die angehängte Datei erlaubt es, Quellcodes des modularen Projekts in der benötigten Konfiguration zu erzeugen.

Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/3133

Beigefügte Dateien |
Synchronisierung mehrerer Charts eines Symbols auf verschiedenen Zeitrahmen Synchronisierung mehrerer Charts eines Symbols auf verschiedenen Zeitrahmen

Häufig muss man während des Handels Charts auf gleichzeitig mehreren Zeitrahmen analysieren, um Handelsentscheidungen zu treffen. Dabei sind häufig Objekte der grafischen Analyse auf diesen Charts vorhanden. Es ist aber unbequem, allen Charts die gleichen Objekte hinzuzufügen. In diesem Artikel schlage ich vor, das "Klonen" von Objekten nach Charts zu automatisieren.

ZUP - Universeller ZigZag mit Pesavento-Mustern. Suche nach Mustern ZUP - Universeller ZigZag mit Pesavento-Mustern. Suche nach Mustern

Die Indikator-Plattform ZUP erlaubt es, nach einer Vielzahl bekannter Muster zu suchen, deren Parameter bereits festgelegt wurden. Man kann solche Parameter auch an eigene Anforderungen anpassen. Darüber hinaus gibt es die Möglichkeit, neue Muster mithilfe des grafischen Interfaces des ZUP-Indikators zu erstellen und deren Parameter in einer Datei zu speichern. Danach kann man schnell überprüfen, ob neue Muster in den Charts entstehen.

Random Decision Forest und Reinforcement-Learning Random Decision Forest und Reinforcement-Learning

Random Forest (RF) mit dem Einsatz von Bagging ist eine der leistungsfähigsten maschinellen Lernmethoden, die dem Gradienten-Boosting etwas unterlegen ist. Dieser Artikel versucht, ein selbstlernendes Handelssystem zu entwickeln, das Entscheidungen basierend auf den Erfahrungen aus der Interaktion mit dem Markt trifft.

Die Behandlung der Ergebnisse der Optimierung mit einem grafischen Interface Die Behandlung der Ergebnisse der Optimierung mit einem grafischen Interface

Dies ist eine Fortsetzung der Idee der Verarbeitung und Analyse von Optimierungsergebnissen. Diesmal geht es darum, die 100 besten Optimierungsergebnisse auszuwählen und in einer GUI-Tabelle darzustellen. Der Benutzer kann eine Zeile in der Optimierungsergebnistabelle auswählen und erhält ein Saldo mehrerer Symbole und eine Drawdown-Grafik auf einer eigenen Seite.