Erzeugung von Multi-Expert Advisors auf Grundlage von Handelsmodellen

Vasiliy Sokolov | 14 März, 2016

Einleitung

Die technischen Fähigkeiten des MetaTrader 5 Terminals samt seinem Strategie-Tester bestimmen die Arbeit und die Tests von Multi-Currency Handelssystemen. Die Komplexität in der Entwicklung solcher Systeme für MetaTrader 4 war zu allererst durch die Unfähigkeit von zeitgleichem Tick-per-Tick Testen auf unterschiedlichen Handelstools, bedingt. Zusätzlich erlaubte die beschränkten Sprachressourcen der MQL4-Programmiersprache keine Organisierung der komplexen dAtensutrukturen sowie eine effizientes Datenverwaltung.

Seit Markteinführung von MQL5 hat sich die Lage drastisch verändert. MQL5 unterstützt ab jetzt den Objekt-orientierten Ansatz, basiert auf einem entwickelten Mechanismus von Hilfsfunktionen und besitzt sogar eine Reihe Standard Library Basisklassen zur Erleichterung der täglichen Aufgaben der Anwender - angefangen vom Organisieren von Daten bis zu den Arbeitsschnittstellen für Standard-Systemfunktionen.

Und obwohl die technischen Spezifikationen des Strategie-Testers und des Terminals die Verwendung von Multi-Currency EAs gestatten, haben sie keine eingebauten Methoden für die Parallelisierung der Arbeit eines EA zeitgleich auf unterschiedlichen Instrumenten oder Zeitrahmen. Wie zuvor auch müssen Sie einen EA, damit er im einfachsten Falle arbeiten kann, im Fenster des Symbols laufen lassen, da den Namen des Handelsinstrument und seinen Zeitrahmen festlegt. Das Ergebnis ist, dass die Arbeitsmethodologie, die seit der Zeit von MetaTrader 4 akzeptiert worden ist, es nicht gestattet, die Vorteile des Strategie-Testers und des MetaTrader 5 Terminals voll zu nutzen.

Dies wird noch weiter durch die Tatsache verkompliziert, dass nur eine kumulative Position für jedes Instrument, die der Gesamtmenge an Abschlüssen auf diesem Instrument gleichen muss, zugelassen ist. Der Übergang zu einer Netto-Position ist allerdings auf jeden Fall korrekt und rechtzeitig. Die Netto-Position kommt einer perfekten Repräsentation des Interesses eines Händlers in einem bestimmten Markt am nächsten.

Doch eine derartige Organisation der Abschlüsse macht den Handelsprozess nicht leichter und einfach visuell abzubilden. Bislang war es ausreichend für einen EA seine offene Order auszuwählen (so konnte diese Order z.B. mittels der magischen Zahl identifiziert werden) und die notwendige Handlung zu implementieren. Doch jetzt bedeutet noch nicht einmal das Fehlen einer Netto-Position auf einem Instrument, dass eine bestimmte Instanz des EAs auf ihr im Augenblick, nicht auf dem Markt ist!

Entwickler von Dritten bieten verschiedene Wege das Problem mit den Netto-Positions zu lösen - angefangen vom Schrieben spezieller Verwalter virtueller Order (vgl. den Beitrag Ein Virtueller Order-Manager zum Nachverfolgen von Orders innerhalb der Position-zentrischen MT5 Umgebung) bis hin zur Integration der Eingaben in eine aggregierte Position mit Hilfe der magischen Zahl (vgl. Die optimale Methode zur Berechnung des Gesamtvolumens der Position mittels der spezifizierten Magischen Zahl oder Die Verwendung von ORDER_MAGIC für Handel mit unterschiedlichen Expert Advisors auf einem einzigen Instrument).

Zusätzlich zum Problem mit den aggregierten Positions gibt es noch das Problem der sog. Multi-Currency (Mehrfach-Währung), wenn der gleiche EA auf mehreren Instrumenten handeln muss. Die Lösung dieses Problems steht in dem Beitrag Erzeugung eines Expert Advisors, der auf mehreren Instrumenten handelt.

All diese vorgeschlagenen Methoden haben jeweils ihre Nachteile. Ihr größtes Problem jedoch besteht darin, dass jede dieser Methoden das jeweilige Problem von ihrem eigenen Standpunkt aus angeht und Lösungen bietet, die z.B. gut geeignet für simultanes Handeln durch mehrere EAs auf einem einzigen Instrument sind, sich jedoch für Mulit-Currency Lösungen nicht eignen.

Dieser Beitrag versucht all diese Probleme mit einer einzigen Lösung zu beheben. Die Verwendung dieser Lösung kann das Probleme der Multi-Currency, ja sogar das Multi-System Testen der Interaktion zwischen verschiedenen EAs auf einem einzigen Instrument lösen. Das scheint zunächst schwer oder fast unmöglich zu bewerkstelligen, doch in Wirklichkeit ist das nicht so unmöglich.

Stellen Sie sie bloß vor, ein einziger EA führt simultan mit mehreren Dutzend Handelsstrategien Handel auf allen zur Verfügung stehenden Instrumenten und in allen möglichen Zeitrahmen aus!  Des weiteren kann Ihr EA im Tester problemlos auf alle dieser, in dieser Zusammenstellung eingeschlossenen, Strategien getestet werden und besitzt zudem ein oder mehrere Arbeitssysteme zur Geldverwaltung.

Das sind also unsere Hauptaufgaben, die wir lösen müssen:

  1. Der EA muss auf Basis verschiedener Handelssysteme zeitgleich Handel ausführen können. Zusätzlich muss er genauso leicht auf einem einzigen und auch auf mehreren Handelssystemen Handel ausführen können;
  2. Keines der, in den EA implementierten Handelssysteme, darf einem anderen in die Quere kommen. Jedes Handelssystem darf nur seinen eigenen Beitrag zu gesamten Netto-Position verarbeiten und auch nur seine eigenen Order;
  3. Jedes System sollte gleichermaßen leicht Handel auf einem einzigen Zeitrahmen des Instruments sowie auch auf allen Zeitrahmen auf einmal ausführen können.
  4. Jedes System sollte gleichermaßen leicht Handel auf einem einzigen Handelsinstrument sowie auch auf allen zur Verfügung stehenden Instrument auf einmal ausführen können.

Wenn wir uns dieses 'Lastenheft' genau ansehen, dann führt das unweigerlich zu einem dreidimensionalen Array. Die erste Dimension des Arrays - die Anzahl der Handelssysteme; die zweite Dimension - die Anzahl der Zeitrahmen auf denen das spezifische HS arbeiten muss, und die dritte Dimension - die Anzahl der Handelsinstrumente für das HS. Eine einfache Berechnung zeigt, dass selbst so ein einfacher EA wie das MACD-Muster 152 unabhängige Lösungen hat, wenn er gleichzeitig auf 8 wichtigen Währungspaaren arbeitet: 1 EA * 8 Paare * 19 Zeitrahmen (wöchentliche und monatliche Zeitrahmen nicht berücksichtigt).

Ist das Handelssystem noch weit größer und das Handels-Portfolio der EAs entsprechend erheblich umfassender, kann die Anzahl der Lösungen leicht auf mehr als 500, ja manchmal sogar auf über 1000 ansteigen! Und das kann man unmöglich manuell konfigurieren und dann jede Kombination extra hochladen. Daher ist der Bau eines Systems notwendig, das jede Kombination automatisch anpasst, sie ins Memory des EA lädt und der EA danach Handel auf Grundlage der Regeln einer spezifischen Instanz dieser Kombination betreibt.

Begriffe und Konzepte

Ab jetzt werde ich den Begriff "Handelsstrategie" durch einen spezifischen Begriff ersetzen: Handelsmodell oder einfach nur Modell. Ein Handelsmodell ist eine spezielle Klasse, die nach bestimmten Regeln gebaut ist und die Handelsstrategie vollständig beschreibt: die beim Handel verwendeten Indikatoren, die Handelsbedingungen für Eintritt und Austritt, die Methoden der Geldverwaltung, usw. Jedes Handelsmodell ist abstrakt und legt für seine Arbeit keine spezifischen Parameter fest.

Als einfaches Beispiel kann die Handelstaktik gelten, die auf dem Kreuzung zweier gleitender Durchschnitte basiert. Wenn der schnelle gleitende Durchschnitt den langsamen aufwärts kreuzt, dann wird ein Buy-Abschluss eröffnet, im gegenteiligen Fall, als bei Kreuzung abwärts, wird ein Sell-Abschluss eröffnet. Diese Formulierung genügt, um ein Handelsmodell zu schreiben, das entsprechend handelt.

Wird jedoch so ein Modell beschrieben, müssen die Methoden der gleitenden Durchschnitte mit ihrenDurchschnittsperioden, die Perioden des Datenfensters und das Instrument festgelegt werden, auf dem dieses Modell Handel ausführen wird. Generell enthält dieses abstrakte Modell Parameter, die dann befüllt werden müssen, wenn Sie eine spezifische Modellinstanz erzeugen müssen. Es liegt auf der Hand, dass im Rahmen dieses Ansatzes eine abstraktes Modell die "Elterninstanz" mehrerer Instanzen des Modells sein kann, die sich in ihren Parametern unterscheiden.

Komplette Ablehnung der Bilanzierung einer Netto-Position 

Viele Entwickler versuchen die aggregierte Position nicht aus den Augen zu verlieren. Wir können jedoch aufgrund der obigen Erläuterungen klar erkennen, dass weder die Größe der aggregierten Position noch ihre Dynamik für eine bestimmte Instanz des Modells relevant sind. Das Modell kann Short sein, und die aggregierte Position existierte vielleicht noch nicht einmal (neutrale aggregierte Position). Oder umgekehrt: die aggregierte Position ist Short, das Modell jedoch hat eine Long Position.

Sehen wir uns diese Szenarien mal genauer an. Angenommen ein Instrument handelt mit drei unterschiedlichen Handelstaktiken, von denen jede ihr eigenes, unabhängiges Geldverwaltungssystem besitzt. Und nehmen wir weiter an, dass das erste dieser drei Systeme beschlossen hat, drei Kontrakte ohne Deckung zu verkaufen - oder einfacher - eine Short Position mit dem Volumen dreier Kontrakte einzurichten. Nach Abschluss dieser Abschlüsse besteht die Netto-Position einzig und allein aus den Abschlüssen des ersten Handelssystems, sein Volumen ist um drei Kontrakte kleiner, oder eben drei Short-Kontrakte ohne Deckung. Nach einiger Zeit beschließt das zweite Handelssystem 4 Kontrakte der gleichen Anlage zu kaufen, sodass sich

infolgedessen die Netto-Position verändert und jetzt aus 1 Long-Kontrakt besteht. Und jetzt umfasst es die Beteiligen zweier Handelssysteme. Und dann tritt auch noch das dritte Handelssystem auf den Plan und macht mit der gleichen Anlage eine Short Position mit dem Volumen eines Standard-Kontrakts. Die Netto-Position wird nun neutral, denn -3 Shorts+ 4 Longs - 1 Short ergibt nun mal "0".

Bedeutet das Nichtvorhandensein der Netto-Position nun, dass alle drei Handelssysteme ncith auf dem Markt sind? Nein, überhaupt nicht. Zwei halten 4 Kontrakte ohne Deckung, d.h. die Deckung wird im Laufe der Zeit gemacht. Das dritte System jedoch hält 4 Long-Kontrakte, die noch zurück verkauft werden müssen. Und nur wenn die komplette Rückzahlung der 4 Short-Kontrakte abgeschlossen ist und eine Verkauf mit Deckung der 4 Long-Kontrakte gemacht wird, bedeutet die neutrale Position ein tatsächliches Fehlen von Positions in allen drei Systemen.

Wir können jedoch jedes Mal die gesamte Handlungsabfolge für jedes Modell rekonstruieren und somit seinen spezifischen Beitrag zur Größe der aktuellen Position festlegen. Doch das geht auch noch viel einfacher. Nämlich, wenn man die Bilanzierung der aggregierten Positions vollkommen außer Acht lässt, die jedwede Größe haben können und sowohl von externen (z.B. manuelles Handeln) als auch internen Faktoren (die Arbeit anderer Modelle des EAs auf einem Instrument) abhängen können. Da wir uns ja auf die aktuelle aggregierte Position nicht stützen können, wie berücksichtigen wird dann die Handlungen einer spezifischen Modellinstanz?

Der einfachste und effektivste Weg ist, jede Instanz eines Modells mit seiner eigenen Tabelle an Orders auszustatten, die dann alle Orders berücksichtigen würde: sowohl pending Orders als auch, die durch einen Abschluss initiierten oder die stornierten. Umfangreiche Information zu Orders befindet sich im Handelsserver. Wenn wir das Order-Ticket kennen, können wir fast jede Information zu einer Order erhalten - angefangen von ihrer Eröffnungszeit bis zu ihrem Volumen.

Das einzige was wir hierbei tun müssen, ist die Ticket-Order mit einer spezifischen Instanz des Modells zu verknüpfen. Jede Modellinstanz muss seine individuelle Instanz einer spezielle Klasse enthalten - die Tabelle der Orders, die eine Liste der aktuellen Orders enthält, so wie sie von der Modellinstanz eingerichtet worden ist.

Entwurf eines abstrakten Handelsmodells

Versuchen wir jetzt die gemeinsame, abstrakte Klasse des Modells zu beschrieben, auf der dann spezifische Handelstaktiken beruhen werden. Da der EA ja mit mehreren (oder unbegrenzten) Modellen arbeiten soll, muss diese Klasse natürlich eine einheitliche Schnittstelle haben, durch die ein externen Power Expert die Signale geben wird.

Diese Schnittstelle kann z.B. die Processing() Funktion sein. Jede CModel Klasse hat also ihre Processing() Funktion Diese Funktion wird bei jedem Tick (Kursschwankung) oder jeder Minute aufgerufen, oder beim Auftauchen eines neuen Handelsereignisses.

Dies ist ein einfaches Beispiel, wie man die Aufgabe lösen kann:

class CModel
{
protected:
   string            m_name;
public:
   void             CModel(){m_name="Model base";}
   bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

cmodel_macd     *macd;
cmodel_moving   *moving;

Finden wir heraus, wie dieser Code funktioniert. Die CModel Basisklasse enthält eine geschützte Variable des Typs String, die m_name Variable. Durch das "geschützte" Schlüsselwort kann diese Variable auch von den Klassen-Erben verwendet werden, sodass alle Nachkommen diese Variable bereits enthalten. Zudem definiert die Basisklasse auch die virtuelle Processing() Funktion. Das Wort 'virtuell' bezeichnet in diesem Fall, dass es sich um eine 'Hülle' oder der Schnittstelle zwischen dem Expert Advisor und der spezifischen Instanz des Modells handelt.

Jede, vom CModel geerbte Klasse, hat auf jeden Fall die Processing() Schnittstelle für ihre Interaktion. Die Implementierung des Codes dieser Funktion wird an die Nachkommen delegiert. Dieses Delegieren ist offensichtlich, da die inneren Funktionsweisen der Modelle erheblich voneinander abweichen können und es daher keine gemeinsame Verallgemeinerungen gibt, die in einem allgemeinen Level- CModel gefunden werden könnten.

Des weiteren müssen zwei Klassen beschrieben werden: cmodel_macd und cmodel_moving, die beide aus der CModel Klasse generiert sind und daher beide ihre eigenen Instanzen der Processing() Funktion sowie die m_name Variable besitzen. Bitte beachten Sie, dass die interne Implementierung der Processing() Funktion bei beiden Modellen anders ist. Im ersten Modell besteht sie aus Drucken ("It is cmodel_macd. Modell name is ", m_name), im zweiten aus Drucken("It is cmodel_moving. Modell name is ", m_name). Anschließend werden zwei Zeiger erzeugt, von denen jeder auf eine spezifischen Instanz des Modells zeigen kann - einer auf die Klasse des Typs cmodel_macd und der andere auf die des cmodel_moving Typs.

In der OnInit Funktion erben diese Zeiger die dynamisch erzeugten Klassen-Modelle, nach denen innerhalb der OnEvent() Funktion eine Processing() Funktion aufgerufen wird, die in jeder Klasse enthalten ist. Beide Zeiger werden auf einem globalen Level deklariert, sodass, nach dem Verlassen der OnInit() Funktion, die in ihr erzeugten Klassen, nicht gelöscht werden, sondern auf einem globalen Level weiter bestehen. Die OnTimer() Funktion wird nun alle fünf Sekunden beide Modelle jeweils abfragen und in ihnen die entsprechende Processing() Funktion aufrufen.

Diese einfache Abfragesystem der Modelle, das wir gerade geschaffen haben, ist nicht sonderlich flexibel und kann nicht aufgerüstet werden. Was machen wir, wenn wir mit mehreren Dutzend solcher Modelle arbeiten wollen? Jeweils einzeln mit ihnen zu arbeiten ist unbequem. Alle Modelle in einer einzigen Gemeinschaft zu sammeln, z.B. einem Array, wäre viel einfacher. Dann könnte man wiederholt über alle Elemente in diesem Array gehen, und zwar durch Aufruf der Processing() Funktion für jedes Element.

Doch das Problem ist, dass die Organisation der Arrays verlangt, dass die in ihnen abgelegten Daten, vom gleichen Typ sind. Zwar sind in unserem Fall die Modelle cmodel_macd und cmodel_moving einander sehr ähnlich, aber dennoch nicht identisch, sodass ihre Verwendung in Arrays damit automatisch hinfällig wird.

Glücklicherweise sind Arrays nicht die einzige Möglichkeit, Daten zusammenzufassen - es gibt noch andere flexiblere und skalierbare Verallgemeinerungen. Eine davon ist die Technik verknüpfter Listen. Ihr Arbeitsprinzip ist ganz einfach. Jedes Element, das in der insgesamten Liste umfasst ist, sollte zwei Zeiger haben: einer zeigt auf das vorherige Element der Liste und der andere auf das nächste. 

Wenn man nun die Indexnummer des Element kennt, kann man immer darauf verweisen. Will man ein Element hinzufügen oer löschen, genügt es seine und die Zeiger der benachbarten Elemente neu zu bauen, damit sie weiterhin konsequent aufeinander verweisen. Die interne Organisation dieser Gemeinschaften muss man nicht kennen, es genügt, wenn man ihr gemeinsames 'Gerät' versteht.

Die Standardinstallierung des MetaTrader 5 beinhaltete eine spezielle CList Hilfsklasse, mit deren Hilfe man mit verknüpften Listen arbeiten kann. Hierbei kann das Element dieser Liste nur ein Objekt des Typs CObject sein, da nur sie über die speziellen Zeiger zur Arbeit mit verknüpften Listen verfügen. Für sich genommen ist die CObject Klasse eher primitiv und nichts weiter als eine Schnittstelle zur Interaktion mit der CList Klasse.

Dies kann man erkennen, wenn man sich ihre Implementierung betrachtet:

//+------------------------------------------------------------------+
//|                                                       Object.mqh |
//|                      Copyright © 2010, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//|                                              Revision 2010.02.22 |
//+------------------------------------------------------------------+
#include "StdLibErr.mqh"
//+------------------------------------------------------------------+
//| Class CObject.                                                   |
//| Purpose: Base class element storage.                             |
//+------------------------------------------------------------------+
class CObject
  {
protected:
   CObject          *m_prev;               // previous list item
   CObject          *m_next;               // next list item

public:
                     CObject();
   //--- methods of access to protected data
   CObject          *Prev()                { return(m_prev); }
   void              Prev(CObject *node)   { m_prev=node;    }
   CObject          *Next()                { return(m_next); }
   void              Next(CObject *node)   { m_next=node;    }
   //--- methods for working with files
   virtual bool      Save(int file_handle) { return(true);   }
   virtual bool      Load(int file_handle) { return(true);   }
   //--- method of identifying the object
   virtual int       Type() const          { return(0);      }

protected:
   virtual int       Compare(const CObject *node,int mode=0) const { return(0); }
  };
//+------------------------------------------------------------------+
//| Constructor CObject.                                             |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CObject::CObject()
  {
//--- initialize protected data
   m_prev=NULL;
   m_next=NULL;
  }
//+------------------------------------------------------------------+

Die Basis dieser Klasse hat zwei Zeiger für die die typischen Features implementiert werden.

Und jetzt kommt das Wichtigste. Aufgrund des Vererbungsmechanismus lässt sich diese Klasse in das Handelsmodell mit einschließen, d.h.: die Klasse des Handelsmodell kann in eine Liste des TypsCList mit aufgenommen werden! Und das machen wir jetzt mal.

Und deshalb machen wir unsere abstrakte CModel Klasse als ein Nachkommen der CObject Klasse:

class CModel : public CObject

Da unsere Klassen cmodel_moving und cmodel_average von der CModel Klasse geerbt sind, umfassen sie die Daten und Methoden der CObject Klasse und können daher in die Liste des CList Typs mit eingeschlossen werden. Der Quellcode, der die zwei bedingten Handelsmodelle erzeugt, platziert sie in die Liste und sequentiell jede Schwankung abfragt, steht unten:

//+------------------------------------------------------------------+
//|                                            ch01_simple_model.mq5 |
//|                            Copyright 2010, Vasily Sokolov (C-4). |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Vasily Sokolov (C-4)."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Arrays\List.mqh>

// Base model
class CModel:CObject
{
protected:
   string            m_name;
public:
        void              CModel(){m_name="Model base";}
        bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

//Create list of models
CList *list_models;

void OnInit()
{
   int rezult;
   // Great two pointer
   cmodel_macd          *m_macd;
   cmodel_moving        *m_moving;
   list_models =        new CList();
   m_macd   =           new cmodel_macd();
   m_moving =           new cmodel_moving();
   //Check valid pointer
   if(CheckPointer(m_macd)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_macd);
      if(rezult!=-1)Print("Model MACD successfully created");
      else          Print("Creation of Model MACD has failed");
   }
   //Check valid pointer
   if(CheckPointer(m_moving)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_moving);
      if(rezult!=-1)Print("Model MOVING AVERAGE successfully created");
      else          Print("Creation of Model MOVING AVERAGE has failed");
   }
}

void OnTick()
{
   CModel               *current_model;
   for(int i=0;i<list_models.Total();i++){
      current_model=list_models.GetNodeAtIndex(i);
      current_model.Processing();
   }
}

void OnDeinit(const int reason)
{
   delete list_models;
}

Sobald dieses Programm erstellt ist und läuft, sollten ähnliche Zeilen, die den normalen Betrieb des EAs anzeigen im "Experts" Logbuch erscheinen.

2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Prosessing Moving Average...
2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Processing MACD Model...
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MOVING AVERAGE was created successfully
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MACD was created successfully  

Sehen wir uns im einzelnen an, wie dieser Code funktioniert. Wie oben bereits erwähnt, ist unser grundlegendes Handelsmodell CModel von der CObject abgeleitet, sodass wir die Nachkommen des grundlegenden Modells in der Liste des Typs CList einschließen können:

rezult=list_models.Add(m_macd);
rezult=list_models.Add(m_moving);

Und zur Organisation der Daten sind Zeiger erforderlich. Sind die Zeiger spezifischer Modelle erst einmal im lokalen Level der OnInit() Funktion erzeugt und in die globalen list_Listenmodelle eingegeben, dann werden sie nicht mehr benötigt und können, zusammen mit anderen Variablen dieser Funktion sicher vernichtet werden.

Im unterscheidt sich das vorgeschlagene Modell durch ein Feature: die einzige globale Variable (zusätzlich zu den Modellklassen an sich) ist eine dynamisch verknüpfte Liste dieser Modelle. Daher besteht bereits von Anfang an Unterstützung durch ein hohes Maß an Verkapselung des Projekts.

Sollte die Erzeugung des Modells aus irgendeinem Grund fehlschlagen (z.B. weil die Werte der erforderlichen Parameter falsch gelistet wurden), dann wird dieses Modell nicht in die Liste mit aufgenommen. Das wirkt sich auf die insgesamte Arbeit des EA nicht aus, da er nur mit den Modellen arbeitet, die der Liste erfolgreich hinzugefügt wurden.

Das Abtasten der erzeugen Modelle geschieht in der OnTick() Funktion. Sie besteht aus einer Schleife. In dieser Schleife ist die Anzahl der Elemente festgelegt, nach der ein serieller Übergang vom ersten Element des Zyklus (i = 0) zum letzten (i <list_models.Total();i++) stattfindet:

CModel               *current_model;
for(int i=0;i<list_models.Total();i++){
   current_model=list_models.GetNodeAtIndex(i);
   current_model.Processing();
}

Der Zeiger auf die CModel Basisklasse fungiert hier als universeller Adapter. Dies gewährleistet, dass jede Funktion, die von diesem Indikator unterstützt wird, auch den abgeleiteten Modellen zur Verfügung steht. In diesem Fall brauchen wir die nur die Processing() Funktion. Jedes Modell verfügt über seine eigene Version von Processing(), deren interne Implementierung sich von ähnlichen Funktionen anderer Modelle unterscheiden kann. Ein Überladen dieser Funktion ist nicht nötig, da sie nur in einer Form existieren kann - sie besitzt keine Eingabeparameter und liefert einen booleschen Wert.

Und die Aufgaben, die auf den "Schultern" dieser Funktion ruhen, sind umfangreich:

  1. Sie sollte unabhängig die aktuelle Marktsituation auf Grundlage ihrer eigenen Handelsmodellen bestimmen.
  2. Nachdem die Entscheidung in den Markt einzusteigen, gefällt wurde, sollte die Funktion unabhängig den nötigen Sicherheitsbetrag berechnen, der an dem Abschluss beteiligt ist (also die Marge), sowie das Volumen des Abschlusses und den Wert des maximal möglichen Verlust- oder Gewinn-Levels.
  3. Das Verhalten des Modells sollte in zu seinen vorigen Handlungen in Bezug stehen. Wenn also z.B. eine vom Modell in Gang gesetzte Short Position besteht, dann kann ihr weitere Ausbau in der Zukunft unmöglich sein. All diese Verifizierungen sollten innerhalb der Processing() Funktion ausgeführt werden,
  4. Jede dieser Funktionen sollte auf die gemeinsamen Parameter, wie z.B. den Account-Status, Zugriff haben. Auf Grundlage dieser Daten sollte diese Funktion, mit Hilfe der in sein Modell eingebetteten Parameter, ihre eigene Geldverwaltung ausführen. Erfolgt z.B. die Geldverwaltung in einem der Modelle mit Hilfe der Möglichkeiten der Optimum-Formel f, sollte ihr Wert für jedes ihrer Modelle unterschiedlich sein.

Aufgrund des Umfangs ihrer Aufgaben, verlässt sich die Processing() Funktion ganz klar auf den entwickelten Apparat der Hilfsklassen, diejeneigen, die im MetaTrader 5 eingeschlossen sind, sowie auch auf diejenigen, die speziell für diese Lösung entworfen wurden..

Wie wir sehen wird der Großteil der Arbeit an spezifische Instanzen des Modells delegiert. Das externe Level des EA übergibt seinerseits die Kontrolle an jedes Modell und seine Arbeit wird im Modell abgeschlossen. Was das spezifische Modell nun tut, hängt von seiner internen Logik ab.

Das Interaktionssystem, das wir gebaut haben, kann ganz allgemein mit dem folgenden Schema erklärt werden:

Bitte beachten Sie, dass obwohl die Sortierung der Modelle, wie im obigen Code dargestellt, innerhalb der OnTick() Funktion abläuft, dies nicht notwendigerweise so sein muss. Der Sortierungszyklus kann problemlos in jede andere gewünschte Funktion platziert werden, wie z.B. OnTrade() oder OnTimer()

 

Die Tabelle virtueller Orders - die Grundlage des Modells

Wenn wir einmal alle Handelsmodell in einer einzigen Liste vereint haben, dann müssen wir den Vorgang des Handels beschreiben. Dazu gehen wir zur CModel Klasse zurück und versuchen sie mit weiteren Daten und Funktionen zu ergänzen, auf die sie während eines Handels beruhen könnte.

Wie bereits erwähnt, legt da neue Netto-Position Paradigma die unterschiedlichen Regeln für die Arbeit mit Orders und Abschlüssen fest. In MetaTrader 4, ist jeder Abschluss von seiner Order begleitet, die auf der Registerkarte "Handel" vom Moment ihrer Ausgabe bis zur ihrem Abschluss oder dem durch sie initiierten Schließen des Abschlusses, bestanden hat.

In MetaTrader 5 bestehen pending Orders nur bis zum Moment des tatsächlichen Abschlusses des Abschlusses. Nachdem der Abschluss oder der Einstieg in den Markts auf ihm, implementiert ist, werden diese Order in die History der Order übertragen, die sich im Handelsserver befindet. Diese Situation führt zu Unsicherheit. Angenommen, der EA hat eine Order ausgegeben, die ausgeführt wurde. Daraufhin hat sich die aggregierte Position verändert. Und nach einiger Zeit muss der EA seine Position schließen.

Das Schließen der spezifischen Order, wie es in MetaTrader 4 gemacht werden konnte, geht jetzt nicht mehr, da das Konzept des Schließens von Orders nichz mehr vorhanden ist. Wir können die Position oder Teile von ihr schließen. Und da entsteht die Frage: welcher Teil der Position sollte geschlossen werden? Alternativ können wir auch alle historischen Orders durchgehen, all diejenigen auswählen, die von dem EA ausgegeben wurden und dieses dann mit der aktuellen Marktistuation in Zusammenhang bringen und ggf. ihre Gegen-Orders blockieren. Doch dies birgt viele Schwierigkeiten in sich.

Wie können wir z.B. feststellen, dass die Orders nicht bereits in der Vergangenheit schon blockiert wurden? Wir können einen anderen Weg einschlagen und annehmen, dass die aktuelle Position ausschließlich zum aktuellen EA gehört. Diese Option kann aber nur dann verwendet werden, wenn man mit einem EA und nur nach einer Strategie handeln möchte. Und all diese Methoden können die Herausforderungen, denen wir gegenüberstehen, dennoch nicht elegant lösen.

Die naheliegendste und einfachste Lösung wäre, die gesamte notwendige Information über die Orders des aktuellen Modells (also Orders, die nicht durch entgegengesetzte Abschlüsse blockiert sind) innerhalb des Modells selbst abzulegen.

Wenn das Modell beispielsweise eine Order ausgegeben hat, sein Ticket in einem speziellen Bereich des Memory dieses Modells erfasst ist, kann sie mit Hilfe eines uns bereits bekannten Systems verknüpfter Listen organisiert werden.

Da wir das Order-Ticket kennen, können wir fast jede Information zur Order finden, sodass wir eben nur das Order-Ticket mit dem Modell, das sie ausgegeben hat, verknüpfen müssen. Sagen wir, das Order-Ticket ist in der speziellen Klasse CTableOrder abgelegt. Zusätzlich zum Ticket kann diese Klasse auch die wichtigsten Informationen umfassen, z. B. das Volumen der Orders, die Zeit ihrer Installierung, die magische Zahl usw..

Sehen wir uns an, wie diese Klasse aufgebaut ist:

#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"

#include <Trade\_OrderInfo.mqh>
#include <Trade\_HistoryOrderInfo.mqh>
#include <Arrays\List.mqh>
class CTableOrders : CObject
{
private:
   ulong             m_magic;       // Magic number of the EA that put out the order
   ulong             m_ticket;      // Ticket of the basic order
   ulong             m_ticket_sl;    // Ticket of the simulated-Stop-Loss order, assigned with the basic order
   ulong             m_ticket_tp;    // Ticket of the simulated-Take-Profit, assigned with the basic order
   ENUM_ORDER_TYPE   m_type;         // Order type
   datetime          m_time_setup;  // Order setup time
   double            m_price;       // Order price
   double            m_sl;          // Stop Loss price
   double            m_tp;          // Take Profit price
   double            m_volume_initial;  // Order Volume
public:
                     CTableOrders();
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);
   double            StopLoss(void){return(m_sl);}
   double            TakeProfit(void){return(m_tp);}
   ulong             Magic(){return(m_magic);}
   ulong             Ticket(){return(m_ticket);}
   int               Type() const;
   datetime          TimeSetup(){return(m_time_setup);}
   double            Price(){return(m_price);}
   double            VolumeInitial(){return(m_volume_initial);}
};

CTableOrders::CTableOrders(void)
{
   m_magic=0;
   m_ticket=0;
   m_type=0;
   m_time_setup=0;
   m_price=0.0;
   m_volume_initial=0.0;
}

bool CTableOrders::Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit)
{
   if(HistoryOrderSelect(history_order_info.Ticket())){
      m_magic=history_order_info.Magic();
      m_ticket=history_order_info.Ticket();
      m_type=history_order_info.Type();
      m_time_setup=history_order_info.TimeSetup();
      m_volume_initial=history_order_info.VolumeInitial();
      m_price=history_order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

bool CTableOrders::Add(COrderInfo &order_info, double stop_loss, double take_profit)
{
   if(OrderSelect(order_info.Ticket())){
      m_magic=order_info.Magic();
      m_ticket=order_info.Ticket();
      m_type=order_info.Type();
      m_time_setup=order_info.TimeSetup();
      m_volume_initial=order_info.VolumeInitial();
      m_price=order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

int   CTableOrders::Type() const
{
   return((ENUM_ORDER_TYPE)m_type);
}

Ähnlich wie die CModel Klasse, wird auch die CTableOrders Klasse vom CObject geerbt. Und genauso wie bei den Klassen des Modells, platzieren wir Instanzen vonCTableOrders in die ListTableOrders Liste des CList Typs.

Zusätzlich zu ihrem eigenen Order-Ticket (m_tiket), enthält die Klasse Informationen zur magischen Zahl (ORDER_MAGIC) des EAs, der sie ausgegeben hat, ihre Art, den Eröffnungskurs, das Volumen und das Level der geschätzten Überlappung von Orders: StopLoss (m_sl) und TakeProfit (m_tp). Die letzten beiden Werte müssen wir separat erläutern. Es ist klar, dass jeder Abschluss früher oder später von einem entgegengesetzten Abschluss geschlossen werden sollte. Der entgegengesetzte Abschluss kann auf Grundlage der aktuellen Marktsituation oder dem teilweisen Schließen der Position zu einem vorab festgelegten Kurs zum Zeitpunkt seines Schließens, initiiert werden.

In MetaTrader4, sind derartige "bedingungslose Austritte aus Positions" spezielle Arten eines Austritts: StopLoss und TakeProfit. Das Feature, das bei MetaTrader 4 den Unterschied macht, ist die Tatsache, dass diese Levels auf eine spezifische Order angewendet werden können. Tritt z.B. in einer der aktiven Orders ein Stop auf, sind die anderen offenen Orders auf diesem Instrument davon nicht betroffen.

In MetaTrader 5 verhält sich das ein bisschen anders. Obwohl man für jede der eingerichteten Orders u.a. einen Preis für StopLoss und TakeProfit festlegen kann, arbeiten diese Levels nicht gegen eine spezifische Order in der diese Preise eingerichtet wurden, sondern im Hinblick auf die gesamte Position auf diesem Instrument.

Angenommen, es gibt eine offene BUYPosition für EURUSD von 1 Standardposten ohne StopLoss und TakeProfit Levels. Etwas später wird eine weitere Order für EURUSD ausgegeben, 0,1 Posten zu kaufen, diesmal mit eingerichteten StopLoss und TakeProfit Levels - beide im Abstand von 100 Punkten vom aktuellen Preis. Und nach einiger Zeit erreicht der Preis das StopLoss-Level oder das des TakeProfit. Sobald dies geschieht, wird die gesamte Position mit eienr Größe von 1,1 Posten auf EURUSDgeschlossen.

Anders ausgedrückt: StopLoss und TakeProfit können nur in Bezug auf die aggregierte Position eingerichtet werden und nicht gegen eine bestimmte Order. Vor diesem Hintergrund wird es unmöglich, diese Order in einem Multi-System EA zu verwenden. Ist ja klar, denn, wenn ein System seine eigenen StopLoss und TakeProfit ausgibt, betrifft das auch alle anderen Systeme, deren Zinsen ja bereits in der aggregierten Position des Instruments mit eingeschlossen sind!

Daher sollte konsequenterweise jedes Subsystem des handelnden EA nur seine eigenen, internen StopLoss und TakeProfit verwenden und zwar für jede Order einzeln. Zudem kann diese Konzept auch von der Tatsache abgeleitet werden, dass selbt innerhalb desselben Handelssystem, unterschiedliche Orders verschiedene StopLoss und TakeProfit Levels haben können, und diese Ausgaben können, wie oben bereits angesprochen, in MetaTrader 5, eben nicht einzelnen Orders zugewiesen werden.

Wenn wir innerhalb von virtuellen Orders, ynthetische StopLoss und TakeProfit Levels platzieren, kann der EA unabhängig die bestehenden Orders blockieren, sobald der Kurs diese Levels erreicht oder übersteigt. Und diese Orders können, nachdem sie blockiert wurden, problemlos aus der Liste der aktiven Order entfernt werden. Wie das geht, beschreibe ich im Folgenden.

Neben ihren eigenen Daten,. enthält die CTableOrders Klasse eine extrem wichtige Funktion: Add() . Diese Funktion erhält das Order-Ticket, dass dann in die Tabelle aufgenommen werden muss. Zusätzlich zum Order-Ticket, erhält diese Funktion die Levels der virtuellen StopLoss und TakeProfit. Die Add() Funktion versucht zuerst die Order unter den historischen Orders, die auf dem Server abgelegt sind, bereitzustellen. Wenn sie das kann, gibt sie die Information auf das Ticket in die Instanz der Klasse history_order_info, und beginnt dann, damit die Information durch die Klasse in das neue TableOrders-Element einzugeben. Des Weiteren wird dieses Element der Orders-Liste hinzugefügt. Konnte die Order nicht erfolgreich ausgewählt werden, haben wir vielleicht mit einer pending Order zu tun, sodass wir diese neue Order aus den aktuellen Orders bereitstellen versuchen müssen und zwar via der OrderSelect() Funktion. Konnte diese Order erfolgreich ausgewählt werden, laufen dieselben Handlungen ab wie bei der historischen Order.

Zum Zeitpunkt wenn noch keine Strukur eingeführt ist, die das HandelsEreignis beschreibt, ist die Arbeit mit pending Orders für einen Multi-System EA schwierig. Doch nach der Einführung dieser Struktur ist es absolut möglich, EAS auf Grundlage von pending Orders zu konzipieren. Und wenn ferner noch eine Orders-Tabelle vorhanden ist, kann buchstäblich jede Handelsstrategie mit pending Orders auf dem Markt in die Tat umgesetzt werden. Aus diesem Grund besitzen alle in diesem Artikel vorgestellten Handelsmodelle eine Ausführung auf dem Markt (ORDER_TYPE_BUY oder ORDER_TYPE_SELL).

CModel - die Basisklasse des Handelsmodells

Da nun die Order-Tabelle vollständig entwickelt ist, können wir uns nun der Beschreibung der Vollversion des Basismodells CModel zuwenden:

class CModel : public CObject
{
protected:
   long              m_magic;
   string            m_symbol;
   ENUM_TIMEFRAMES   m_timeframe;
   string            m_model_name;
   double            m_delta;
   CTableOrders      *table;
   CList             *ListTableOrders;
   CAccountInfo      m_account_info;
   CTrade            m_trade;
   CSymbolInfo       m_symbol_info;
   COrderInfo        m_order_info;
   CHistoryOrderInfo m_history_order_info;
   CPositionInfo     m_position_info;
   CDealInfo         m_deal_info;
   t_period          m_timing;
public:
                     CModel()  { Init();   }
                     ~CModel() { Deinit(); }
   string            Name(){return(m_model_name);}
   void              Name(string name){m_model_name=name;}
   ENUM_TIMEFRAMES    Timeframe(void){return(m_timeframe);}
   string            Symbol(void){return(m_symbol);}
   void              Symbol(string set_symbol){m_symbol=set_symbol;}
   bool virtual      Init();
   void virtual      Deinit(){delete ListTableOrders;}
   bool virtual      Processing(){return (true);}
   double            GetMyPosition();
   bool              Delete(ENUM_TYPE_DELETED_ORDER);
   bool              Delete(ulong Ticket);
   void              CloseAllPosition();
   //bool virtual      Trade();
protected:
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);

   void              GetNumberOrders(n_orders &orders);
   bool              SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, double lot,
                              double price, double stop_loss, double take_profit, string comment);
};

Die Daten aus dieser Klasse enthalten die grundlegenden Konstanten jedes Handelsmodells, nämlich

die magische Zahl (m_magic), das Symbol auf dem das Modell gestartet wird, (m_symbol), den Zeitrahmen (m_timeframe) sowie den Namen des am meisten gehandelten Modells (m_name).

Darüber hinaus enthält das Modell die uns bereits bekannte Klasse der Order-Tabelle, (CTableOrders * table) sowie die Liste, in der die Instanzen dieser Tabelle aufbewahrt werden - eine Kopie für jede Order(CList*ListTableOrders). Da all diese Daten, sobald Bedarf besteht, dynamisch erzeugt werden, wird die Arbeit mit diesen Daten mittels Zeiger ausgeführt.

Dem folgt die Variable m_delta. Sie sollte den speziellen Koeffizienten zur Berechnung des aktuellen Postens in den Formeln zur Geldverwaltung enthalten. Bei der fest-fraktionalen Kapitalisierungsformel, kann diese Variable beispielsweise einen Anteil des Accounts, der aufs Spiel gesetzt werden kann, aufbewahren. Also muss diese Variable für ein Risiko von 2% des Accounts dem Wert 0,02 entsprechen. Bei aggressiveren Methoden, z.B, für die optimale Methode, optimal_fist diese Variable natürlich entsprechend größer.

Was bei dieser Variable wichtig ist, ist die Tatsache, dass man mit ihr für jedes Modell, das Teil eines einzigen EAs ist, das Risiko individuell wählen kann. Wird die Formel zur Kapitalisierung nicht verwendet, muss diese Variable nicht gefüllt werden.Ihre Standardeinstellung = 0,0.

Als nächstes folgt die Aufnahme aller Hilfs-Handelsklassen, die den Empfang und die Verarbeitung aller notwendigen Informationen erleichtern sollen, angefangen von der Account-Information bis zu den Informationen über Position(s). Es wird davon ausgegangen, dass die Derivative spezifischer Handelsmodelle diese Hilfsklassen, und nicht die normalen Features des Typs OrderSelect or OrderSend aktiv nutzen müssen.

Die Variable m_timing muss extra beschrieben werden. Während der Durchführung der Arbeit des EAs müssen gewisse Ereignisse zu bestimmten Zeitintervallen aufgerufen werden. Die OnTimer() Funktion eignet sich dafür nicht, da ja unterschiedliche Modelle in unterschiedlichen Zeitintervallen existieren können.

So müssen z.B. gewisse Ereignisse bei jedem neuen Balken aufgerufen werden. Für das Modell, das auf einem stündlichen Graph handelt, sollten diese Ereignisse stündlich aufgerufen werden; für ein Modell, das auf einem täglichen Graph handelt, bei jedem neuen Tagesbalken. Es ist klar, dass diese Modell unterschiedliche Zeiteinstellungen haben und jede muss jeweils in ihrem eigenen Modell gespeichert sein. Die Struktur t_period, die in der CModel Klasse mit eingeschlossen ist, erlaubt die Speicherung dieser Einstellungen separat, jede in ihrem Modell.

Und so sieht diese Struktur aus:

struct t_period
{
   datetime m1;
   datetime m2;
   datetime m3;
   datetime m4;
   datetime m5;
   datetime m6;
   datetime m10;
   datetime m12;
   datetime m15;
   datetime m20;
   datetime m30;
   datetime h1;
   datetime h2;
   datetime h3;
   datetime h4;
   datetime h6;
   datetime h8;
   datetime h12;
   datetime d1;
   datetime w1;
   datetime mn1;  
   datetime current; 
};

Wie man sieht, enthält sie die übliche Auflistung der Zeitrahmen. Um zu sehen, ob ein neuer Balken aufgetaucht ist, muss man die Zeit des letzten Balkens mit der Zeit vergleichen, die in der Struktur t_period aufgezeichnet wurde. Stimmen beide nicht überein, ist ein neuer Balken aufgetreten und die Zeitstruktur muss auf die Zeit des aktuellen Balkens aktualisiert werden und ein positives Ergebnis liefern('true'). Sind beide Zeiten identisch, dann ist noch kein neuer Balken aufgetaucht und es muss ein negatives Ergebnis geliefert werden ('false').

Hier ist eine Funktion, die auf Grundlage des beschriebenen Algorithmus funktioniert:

bool timing(string symbol, ENUM_TIMEFRAMES tf, t_period &timeframes)
{
   int rez;
   MqlRates raters[1];
   rez=CopyRates(symbol, tf, 0, 1, raters);
   if(rez==0)
   {
      Print("Error timing");
      return(false);
   }
   switch(tf){
      case PERIOD_M1:
         if(raters[0].time==timeframes.m1)return(false);
         else{timeframes.m1=raters[0].time; return(true);}
      case PERIOD_M2:
         if(raters[0].time==timeframes.m2)return(false);
         else{timeframes.m2=raters[0].time; return(true);}
      case PERIOD_M3:
         if(raters[0].time==timeframes.m3)return(false);
         else{timeframes.m3=raters[0].time; return(true);}
      case PERIOD_M4:
         if(raters[0].time==timeframes.m4)return(false);
         else{timeframes.m4=raters[0].time; return(true);}
     case PERIOD_M5:
         if(raters[0].time==timeframes.m5)return(false);
         else{timeframes.m5=raters[0].time; return(true);}
     case PERIOD_M6:
         if(raters[0].time==timeframes.m6)return(false);
         else{timeframes.m6=raters[0].time; return(true);}
     case PERIOD_M10:
         if(raters[0].time==timeframes.m10)return(false);
         else{timeframes.m10=raters[0].time; return(true);}
     case PERIOD_M12:
         if(raters[0].time==timeframes.m12)return(false);
         else{timeframes.m12=raters[0].time; return(true);}
     case PERIOD_M15:
         if(raters[0].time==timeframes.m15)return(false);
         else{timeframes.m15=raters[0].time; return(true);}
     case PERIOD_M20:
         if(raters[0].time==timeframes.m20)return(false);
         else{timeframes.m20=raters[0].time; return(true);}
     case PERIOD_M30:
         if(raters[0].time==timeframes.m30)return(false);
         else{timeframes.m30=raters[0].time; return(true);}
     case PERIOD_H1:
         if(raters[0].time==timeframes.h1)return(false);
         else{timeframes.h1=raters[0].time; return(true);}
     case PERIOD_H2:
         if(raters[0].time==timeframes.h2)return(false);
         else{timeframes.h2=raters[0].time; return(true);}
     case PERIOD_H3:
         if(raters[0].time==timeframes.h3)return(false);
         else{timeframes.h3=raters[0].time; return(true);}
     case PERIOD_H4:
         if(raters[0].time==timeframes.h4)return(false);
         else{timeframes.h4=raters[0].time; return(true);}
     case PERIOD_H6:
         if(raters[0].time==timeframes.h6)return(false);
         else{timeframes.h6=raters[0].time; return(true);}
     case PERIOD_H8:
         if(raters[0].time==timeframes.h8)return(false);
         else{timeframes.h8=raters[0].time; return(true);}
     case PERIOD_H12:
         if(raters[0].time==timeframes.h12)return(false);
         else{timeframes.h12=raters[0].time; return(true);}
     case PERIOD_D1:
         if(raters[0].time==timeframes.d1)return(false);
         else{timeframes.d1=raters[0].time; return(true);}
     case PERIOD_W1:
         if(raters[0].time==timeframes.w1)return(false);
         else{timeframes.w1=raters[0].time; return(true);}
     case PERIOD_MN1:
         if(raters[0].time==timeframes.mn1)return(false);
         else{timeframes.mn1=raters[0].time; return(true);}
     case PERIOD_CURRENT:
         if(raters[0].time==timeframes.current)return(false);
         else{timeframes.current=raters[0].time; return(true);}
     default:
         return(false);
   }
}

Im Moment gibt es keine Möglichkeit einer sequentiellen Sortierung der Strukturen. Eine derartige Sortierung kann nötig werden, sobald man multiple Instanzen in einem Zyklus desselben Handelsmodells erzeugt, die auf unterschiedlichen Zeitrahmen handeln. Deshalb musste ich einen speziellen Funktions-Sortierer im Hinblick auf Struktur t_period schreiben.

Dies ist der Quellcodedieser Funktion:

int GetPeriodEnumerator(uchar n_period)
{
   switch(n_period)
   {
      case 0: return(PERIOD_CURRENT);
      case 1: return(PERIOD_M1);
      case 2: return(PERIOD_M2);
      case 3: return(PERIOD_M3);
      case 4: return(PERIOD_M4);
      case 5: return(PERIOD_M5);
      case 6: return(PERIOD_M6);
      case 7: return(PERIOD_M10);
      case 8: return(PERIOD_M12);
      case 9: return(PERIOD_M15);
      case 10: return(PERIOD_M20);
      case 11: return(PERIOD_M30);
      case 12: return(PERIOD_H1);
      case 13: return(PERIOD_H2);
      case 14: return(PERIOD_H3);
      case 15: return(PERIOD_H4);
      case 16: return(PERIOD_H6);
      case 17: return(PERIOD_H8);
      case 18: return(PERIOD_H12);
      case 19: return(PERIOD_D1);
      case 20: return(PERIOD_W1);
      case 21: return(PERIOD_MN1);
      default:
         Print("Enumerator period must be smallest 22");
         return(-1);
   }
}

All diese Funktionen sind ganz bequem in einer einzigen Datei im Ordner \\Include zusammengefasst. Nennen wir sie einfach Zeit.mqh.

Und das wird in unserer Basisklasse CModel mit aufgenommen:

#incude <Time.mqh>

Zusätzlich zur einfachen Funktion get/set des Typs Name(), Zeitrahmen() und Symbol(), enthält die Klasse CModel komplexe Funktionen des Typs  Init(), GetMyPosition(), Delete(), CloseAllPosition() и Processing(). Die Bezeichnung der letzten Funktion sollte bereits bekannt sein, ihre interne Struktur besprechen wir später ausführlicher. Jetzt jedoch beginnen wir mit der Beschreibung der Hauptfunktionen der Basisklasse CModel.

Die CModel::Add() function erzeugt dynamisch eine Instanz der Klasse CTableOrders, und füllt sie dann mit Hilfe der entsprechenden CTabeOrders::Add() Funktion. Ihr Arbeitsprinzip wurde oben bereits beschrieben. Nachdem sie befüllt ist, wird dieses Element in die allegmeine Liste aller Orders des aktuellen Modells aufgenommen (ListTableOrders.Add (t)).

Die CModel::Delete() function, löscht seinerseits das Element vom Typ CTableOrders aus der Liste der aktiven Orders. Dazu muss das Ticket der Orders angegeben werden, die gelöscht werden müssen Das Arbeitsprinzip ist einfach: Die Funktion geht sequentiell die gesamte Order-Tabelle durch und sucht nach der Order mit dem richtigen Ticket. Findet sie so eine Order, wird diese gelöscht.

Die CModel::GetNumberOrders() Funktion zählt die Anzahl der aktiven Orders zusammen und füllt die spezielle Struktur n_orders:

struct n_orders
{
   int all_orders;
   int long_orders;
   int short_orders;
   int buy_sell_orders;
   int delayed_orders;
   int buy_orders;
   int sell_orders;
   int buy_stop_orders;
   int sell_stop_orders;
   int buy_limit_orders;
   int sell_limit_orders;
   int buy_stop_limit_orders;
   int sell_stop_limit_orders;
};

Wie man sieht, kann man, nachdem sie aufgerufen ist, herausfinden, wie viele spezifische Arten von Orders eingerichtet worden sind. Um z.B. die Zahl aller Short Orders zu erhalten, muss man die Werte der short_ordersder Instanzn_orders lesen.

Die CModel::SendOrder() Funktion ist die grundlegende und einzige Funktion zum tatsächlichen Senden von Orders zum Handelsserver. Anstatt dass jedes bestimmte Modell seinen eigenen Algorithmus zum Abschicken von Orders an den Server hat, legt die SendOrder() Funktion den generellen Ablauf für das Abschicken fest. Ungeachtet des Modells steht das Verfahren der Ausgabe von Orders mit denselben Prüfungen in Zusammenhang, die effizient an einem zentralen Ort ausgeführt werden.

Machen wir uns nun vertraut mit dem Quellcode dieser Funktion:

bool CModel::SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, 
                          double lot, double price, double stop_loss, double take_profit, string comment)
{
   ulong code_return=0;
   CSymbolInfo symbol_info;
   CTrade      trade;
   symbol_info.Name(symbol);
   symbol_info.RefreshRates();
   mm send_order_mm;
   
   double lot_current;
   double lot_send=lot;
   double lot_max=m_symbol_info.LotsMax();
   //double lot_max=5.0;
   bool rez=false;
   int floor_lot=(int)MathFloor(lot/lot_max);
   if(MathMod(lot,lot_max)==0)floor_lot=floor_lot-1;
   int itteration=(int)MathCeil(lot/lot_max);
   if(itteration>1)
      Print("The order volume exceeds the maximum allowed volume. It will be divided into ", itteration, " deals");
   for(int i=1;i<=itteration;i++)
   {
      if(i==itteration)lot_send=lot-(floor_lot*lot_max);
      else lot_send=lot_max;
      for(int i=0;i<3;i++)
      {
         //Print("Send Order: TRADE_RETCODE_DONE");
         symbol_info.RefreshRates();
         if(op_type==ORDER_TYPE_BUY)price=symbol_info.Ask();
         if(op_type==ORDER_TYPE_SELL)price=symbol_info.Bid();
         m_trade.SetDeviationInPoints(ulong(0.0003/(double)symbol_info.Point()));
         m_trade.SetExpertMagicNumber(m_magic);
         rez=m_trade.PositionOpen(m_symbol, op_type, lot_send, price, 0.0, 0.0, comment); 
         // Sleeping is not to be deleted or moved! Otherwise the order will not have time to get recorded in m_history_order_info!!!
         Sleep(3000);
         if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE_PARTIAL||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
         {
               //Print(m_trade.ResultComment());
               //rez=m_history_order_info.Ticket(m_trade.ResultOrder());
               if(op_mode==ORDER_ADD){
                  rez=Add(m_trade.ResultOrder(), stop_loss, take_profit);
               }
               if(op_mode==ORDER_DELETE){
                  rez=Delete(ticket);
               }
               code_return=m_trade.ResultRetcode();
               break;
         }
         else
         {
            Print(m_trade.ResultComment());
         }
         if(m_trade.ResultRetcode()==TRADE_RETCODE_TRADE_DISABLED||
            m_trade.ResultRetcode()==TRADE_RETCODE_MARKET_CLOSED||
            m_trade.ResultRetcode()==TRADE_RETCODE_NO_MONEY||
            m_trade.ResultRetcode()==TRADE_RETCODE_TOO_MANY_REQUESTS||
            m_trade.ResultRetcode()==TRADE_RETCODE_SERVER_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_CLIENT_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_ORDERS||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_VOLUME)
         {
            break;
         }
      }
   }
   return(rez);
}

Als erstes überprüft diese Funktion die Möglichkeit, das angegebene Volumen des Handelsservers auszuführen. Dies geschieht mit Hilfe der CheckLot() Funktion. Hierbei kann es durchaus einigen Handelsbeschränkungen bei der GRöße der Position geben, man also berücksichtigen muss.

Nehmen wir folgenden Fall an: Es gibt ein Limit bei der Größe von Handels-Positions von 15 Standardposten in beiden Richtungen. Die aktuelle Position ist eine Long Position und entspricht 3 Posten. Das Handelsmodell möchte, auf Grundlage seines Geldverwaltungs systems, eine Long Position mit einem Volumen von 18,6 Posten eröffnen.Die CheckLot() Funktion liefert daraufhin das berichtigte Volumen des Abschlusses. In diesem Fall sind das 12 Posten ( da ja 3 von 15 Posten bereits durch andere Abschlüsse besetzt sind). Wenn die aktuelle Open Position eine Short anstatt einer Long Position war, würde die Funktion 15 Posten anstelle von 18,6 liefern, da dies das maximal mögliche Positions-Volumen ist.

Nach Ausgabe von 15 Buy-Posten, entspricht die Netto-Position 12 Posten (3 - Sell, 15 - Buy). Sobald ein anderes Modell seine ursprüngliche Short Position von 3 Buy-Posten aufhebt, wird die aggregierte Position zur maximal möglichen - 15 Posten. Andere Buy-Signale werden solange nicht verarbeitet, bis das Modell einige oder alle seiner 15 Buy-Posten aufhebt. Wurde ein mögliches Volumen für den angefragten Abschluss überschritten, liefert die Funktion einen konstanten LEERER_WERT, und so ein Signal muss übertragen werden.

Ist die Prüfung der Möglichkeit des eingerichteten Volumens positiv verlaufen, wird der Wert der erforderlichen Marge berechnet. Kann ja sein, dass der Account nicht genügend Geldmittel für das angegebene Volumen hat. Und genau deshalb gibt es die CheckMargin() Funktion. Ist die Marge eben nicht ausreichend, versucht diese Funktion das Ordervolumen zu korrigieren, sodass die aktuelle frei Marge es zu öffnen erlaubt. Ist die Marge noch nicht einmal zur Eröffnung des Minimalbetrags ausreichend, befinden wir uns im Zustand des Margen-Aufrufs.

Gibt es derzeit keine Positions und wir die Marge nicht benutzt, heißt das nur eins: technischer Margen-Aufruf - ein Zustand, in dem kein Abschluss eröffnet werden kann. Ohne unseren Account mit Geld zu füttern, können wir also nicht weiter machen. Wird eine Marge immer noch verwendet, können wir nur warten, bis die Transaktion, die diese Marge verwendet, geschlossen ist. Auf jeden Fall liefert fehlende Marge LEERER_WERT.

Ein charakteristisches Feature dieser Funktion ist ihre Fähigkeit, eine aktuelle Order in mehrer unabhängige Abschlüsse aufzuspalten. Wenn die Handelsmodellen ein System zur Kapitalisierung des Accounts verwenden,kann der erforderliche Betrag leicht alle vorstellbaren Limits übersteigen (so kann das Kapitalisierungsystem z.B. eine Eröffnung eines Abschlusses mit einem Volumen von mehreren Hundert, ja Tausend Standardposten verlangen). Und für einen einzigen Abschluss kann man eine derartige Menge ganz klar auf keinen Fall sicherstellen. In der Regel bestimmen die Handels Bedingungen die Größe eine Maximalabschlusses von 100 Posten, doch einige Handelsserver haben andere Beschränkungen, wie z.B. der Server der MetaQuotes Meisterschaft 2010, er war auf 5 Posten beschränkt. Es ist klar, dass derartige Beschränkungen berücksichtigt werden müssen und auf ihrer Grundlage das tatsächliche Volumen des Abschlusses korrekt berechnet werden muss.

Zuerst wird die Anzahl der Orders, die für die Implementierung des eingerichteten Volumens notwendig sind, gezählt. Übersteigt die eingerichtete Menge nicht die Maximalmenge der Transaktion kann mit nur einem Aufruf diese Order ausgegeben werden. Übersteigt das gewünschte Volumen der Transaktion jedoch das maximal mögliche Volumen, wird dieses in mehrere Teile geteilt. Sie wollen z.B. 11,3 Posten EURUSD kaufen. Die Maximalgröße der Transaktion auf diesem Instrument ist jedoch 5,0 Posten. Die OrderSend Funktion teilt daraufhin dieses Volumen in drei Orders auf: 1. Order: 5,0 Posten, 2. Order: 5,0 Posten und 3. Order: 1,3 Posten.

Also hat man statt einer Order nun drei, von denen jede einzelne in der Order-Tabelle ausgeführt wird und ihre eigene, unabhängigen Einstellungen sowie auch die virtuellen Stop Loss und Take Profit Werte, die magische Zahl und andere Parameter besitzt. Die Verarbeitung dieser Orders sollte problemlos vonstatten gehen, da die Handelsmodelle ja so konzipiert sind, dass sie jede Anzahl Orders in ihren Listen verarbeiten können.

Und all diese Orders haben in der Tat die gleichenTakeProfitund StopLoss Werte. Und jede wird zudem sequentiell mittels der LongClose und ShortClose Funktionen sortiert. Sobald die für ein Schließen geeigneten Bedingungen auftauchen oder die SL und TP Schwellen erreicht werden, werden sie alle geschlossen.

Mit Hilfe derOrderSend Funktion der CTrade Klasse wird jede einzelnen Order dann an den Server geschickt. Das interessanteste Detail dieses Vorgehens ist unten versteckt, nämlich

die Tatsache, dass die Zuweisung einer Order zweifach sein kann. Die Buy- oder Sell-Order kann beim Auftauchen des Signals abgeschickt werden. Oder es handelt sich um eine Order zur Blockierung der vorherigen Order. Die OrderSend Funktion muss die Art de gesendeten Order kennen, da diese Funktion ja tatsächlich alle Orders in die Order-Tabelle platziert oder sie beim Auftauchen gewisser Ereignisse aus der Tabelle entfernt.

Ist die Order, die man hinzufügen möchte, vom Typ ADD_ORDER, also eine unabhängige Order, die in die Order-Tabelle platziert werden muss, ergänzt die Funktion die Order-Tabelle mit Informationen zu dieser Order. Wird die Order ausgegeben, um eine zuvor platzierte Order aufzuheben (z.B. beim Auftauchen eines virtuellen Stop-Loss), dann muss sie vom Typ DELETE_ORDER sein. Nachdem sie ausgegeben wurde, entfernt die Funktion OrderSend manuell alle Informationen über die Order, mit denen sie mit der Order-Tabelle verknüpft ist. Denn, zusätzlich zum Order-Typ erbt diese Funktion ein Order-Ticket mit dem sie verknüpft ist Handelt es sich um eine ADD_ORDER, kann das Ticket mit einer einfachen 0 gefüllt werden.


Das erste Handelsmodell, basierend auf der Kreuzung gleitender Durchschnitte

Wir haben nun alle wichtigen Elemente der CModel Basisklasse besprochen. Nun wollen wir uns eine eine spezifische Handelsklasse ansehen.

Dazu erzeugen wir zunächst ein einfaches Handelsmodell auf GRundlage des einfachen MACD Indikators.

Diese Modell besitzt immer die Long oder die Short Position. Sobald die rasche Linie die langsame Linie nach unten hin kreuzt, eröffnen wir eine Short Position und schließen eine Long Position, sollte es eine geben. Findet dieses Kreuzen nach oben hin statt, eröffnen wir eine Long Position und schließen eine Short Position, sollte es eine geben. Wir verwenden in diesem Modell keine schützenden Stops und Profit-Levels.

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model uses MACD indicator.                                      |
//| Buy when it crosses the zero line downward                           |
//| Sell when it crosses the zero line upward                            |
//+----------------------------------------------------------------------+  
struct cmodel_macd_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               fast_ema;
   int               slow_ema;
   int               signal_ema;
};
   
class cmodel_macd : public CModel
{
private:
   int               m_slow_ema;
   int               m_fast_ema;
   int               m_signal_ema;
   int               m_handle_macd;
   double            m_macd_buff_main[];
   double            m_macd_current;
   double            m_macd_previous;
public:
                     cmodel_macd();
   bool              Init();
   bool              Init(cmodel_macd_param &m_param);
   bool              Init(string symbol, ENUM_TIMEFRAMES timeframes, int slow_ma, int fast_ma, int smothed_ma);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_macd_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
};

cmodel_macd::cmodel_macd()
{
   m_handle_macd=INVALID_HANDLE;
   ArraySetAsSeries(m_macd_buff_main,true);
   m_macd_current=0.0;
   m_macd_previous=0.0;
}
//this default loader
bool cmodel_macd::Init()
{
   m_magic      = 148394;
   m_model_name =  "MACD MODEL";
   m_symbol     = _Symbol;
   m_timeframe  = _Period;
   m_slow_ema   = 26;
   m_fast_ema   = 12;
   m_signal_ema = 9;
   m_delta      = 50;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_macd::Init(cmodel_macd_param &m_param)
{
   m_magic      = 148394;
   m_model_name = "MACD MODEL";
   m_symbol     = m_param.symbol;
   m_timeframe  = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_fast_ema   = m_param.fast_ema;
   m_slow_ema   = m_param.slow_ema;
   m_signal_ema = m_param.signal_ema;
   if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}


bool cmodel_macd::CheckParam(cmodel_macd_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT))
   {
      Print("Symbol ", m_symbol, " selection has failed. Check symbol name");
      return(false);
   }
   if(m_fast_ema == 0)
   {
      Print("Fast EMA must be greater than 0");
      return(false);
   }
   if(m_slow_ema == 0)
   {
      Print("Slow EMA must be greater than 0");
      return(false);
   }
   if(m_signal_ema == 0)
   {
      Print("Signal EMA must be greater than 0");
      return(false);
   }
   return(true);
}

bool cmodel_macd::InitIndicators()
{
   if(m_handle_macd==INVALID_HANDLE)
   {
      Print("Load indicators...");
      if((m_handle_macd=iMACD(m_symbol,m_timeframe,m_fast_ema,m_slow_ema,m_signal_ema,PRICE_CLOSE))==INVALID_HANDLE)
      {
         printf("Error creating MACD indicator");
         return(false);
      }
   }
   return(true);
}

bool cmodel_macd::Processing()
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   return(true);
}

bool cmodel_macd::LongOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   //Print("LongOpened");
   if(m_macd_current>0&&m_macd_previous<=0&&m_orders.buy_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_ADD, 0, lot, m_symbol_info.Ask(), 0, 0, "MACD Buy");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::ShortOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   if(m_macd_current<=0&&m_macd_previous>=0&&m_orders.sell_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_ADD, 0, lot, m_symbol_info.Bid(), 0, 0, "MACD Sell");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close buy stop-loss");
      }
      if(m_macd_current<0&&m_macd_previous>=0)
      {
         //Print("Long position closed by Order Send");
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close by signal");
      }
   }
   return(rez);
}

bool cmodel_macd::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close buy stop-loss");
      }
      if(m_macd_current>0&&m_macd_previous<=0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close by signal");
      }
   }
   return(rez);
}

Die CModel Basisklasse verfügt über keinerlei Einschränkungen in Bezug auf den internen Inhalt seiner Nachkommen. Das einzige was sie wirklich zwingend erfordert, ist die Verwendung der Processing() Schnittstellen-Funktion. Alle Probleme der interne Organisation dieser Funktion werden auf eine spezifische Klasse von Modellen delegiert. Es gibt keinen universellen Algorithmus, den man in eine Processing() Funktion platzieren könnte, daher gibt es auch keinen Grund, den Nachkommen seine Methode auf zu zwingen, wie ein bestimmtes Modell angeordnet werden sollte. Die selbstverständlich kann interne Struktur buchstäblich jeden Modells standardisiert werden. So eine Standardisierung erleichtert das Verständnis eines externenund sogar Ihres Codes enorm und lässt das Modell mehr zu einer "Formel" werden"

Jedes Modell muss seinen eigenen Initialisierer haben. Der Initialisierer des Modells ist zuständig für das Laden der korrekten Parameter, die für seinen Ablauf notwendig sind. Damit das Modell arbeiten kann, müssen wir z.B. die MACD-Indikatorwerte auswählen, einen Handle des entsprechenden Puffers erhalten und zudem natürlich auchdas Handelsinstrument und den Zeitrahmen für das Modell bestimmen. Dies alles muss der Initialisierer machen.

Die Modell-Initialisierer sind einfache überladene Methoden ihrer Klassen. Sie haben einen gemeinsamen Namen: Init. Tatsache ist, dass MQL5 überladenen Constructors nicht unterstützt, sodass es keine Möglichkeit gibt einen Initialisierer des Modells in seinem Constructor zu erzeugen, da die Überladung für die Eingabe-Parameter nötig ist. Doch nichts hindert uns daran, im Constructor des Modells seine grundlegenden Parameter anzugeben.

Jedes Modell sollte drei Initialisierer haben. Erstens: den Standard-Initialisierer. Er muss das Modell standardmäßig konfigurieren und laden, ohne nach Parametern zu fragen. Das kann ganz wunderbar für Tests in Modus "so wie vorhanden" sein. So wählt z.B. der Standard-Initialisierer für unser Modell als Instrumente-Tool den aktuellen Graphen und als Zeitrahmen den aktuellen Zeitrahmen aus.

Die Einstellungen des MACD-Indikators sind ebenfalls standardmäßig: schneller EMA = 12, langsamer EMA = 26, Signal MA = 9; Ist es erforderlich, dass das Modell auf eine bestimmte Art konfiguriert werden muss, eignet sich so ein Initialisierer nicht mehr. Dazu brauchen wir Initialisierer mit Parametern. Hier ist es wünschenswert (jedoch nicht zwingend) zwei Arten zu machen. Die erste erhält ihre Parameter als eine klassische Funktion: Init (type param1, type param2, ..., type paramN). Die zweite ermittelt die Parameter des Modells mit Hilfe einer speziellen Struktur, die diese dann speichert. Diese Option ist manchmal empfehlenswerter, da die Anzahl der Parameter manchmal ganz schön groß sein kann und es in diesem Fall weitaus bequemer ist, sie durch die Strukturen zu übertragen.

Jedes Modell hat die Struktur seiner Parameter. Diese Struktur kann jeden Namen bekommen, doch ist es ratsam, sie mit dem Muster modelname_param aufzurufen. Die Konfiguration des Modells ist ein sehr wichtiger Schritt bei der Verwendung der Möglichkeiten des Multi-Timeframe/Multi-System/Multi-Currency Handels. Denn in dieser Phase wird festgelegt, wie und auf welchen Instrumenten dieses Modell seine Handelsoperationen ausführen wird.

Unser Handelsmodell besitzt nur vier Handelsfunktionen. Die Funktion zum Öffnen einer Long Position: LongOpen, die Funktion zur Eröffnung einer Short Position: ShortOpen, die Funktion zum Schließen einer Long Position: LongClosed, sowie die Funktion zum Schließen einer Short Position: ShortClosed. Die Arbeit der Funktionen LongOpen und ShortOpen ist banal. Beide erhalten den Indikatorwert des MACD vorherigen Balkens, der mit dem Wert der zwei Balken davor verglichen wird. Um eine "Neuzeichnung" zu vermeiden, wird der aktuelle (Null) Balken nicht verwendet.

Besteht ein Kreuzen nach unten hin, berechnet ShortOpendie Funktion mittels der in der Header-Datei mm.mqh beinhalteten Funktionen, infolgedessen dann der notwendige Posten seinen Befehl an die OrderSend Funktion gibt. LongClose hingegen, schließt in diesem Moment alle Long Positions im Modell. Dies passiert, weil die Funktion alle aktuell offenen Orders in der Order-Tabelle des Modells sequentiell durchgeht. Wird also eine Long Order gefunden, wird sie von der Funktion mit einer Gegen-Order geschlossen. Und genau das gleiche, allerdings in umgekehrter Richtung, macht auch die ShortClose() Funktion. Die Arbeit dieser Funktionen findet sich in der oben zur Verfügung gestellten Auflistung.

Sehen wir uns nun detaillierter an, wie der aktuelle Posten im Handelsmodell errechnet wird.

Wie bereits erwähnt, verwenden wir zur Kapitalisierung des Accounts spezielle Funktionen. Und zusätzlich zu den Kapitalisierungsformeln, beinhalten diese Funktionen die Bestätigung der Berechnung des Postens, auf Grundlage der Ebene der verwendeten Marge und vor dem Hintergrund der Beschränkung der Größe der Handels-Positions.Für die Größe der Position kann es durchaus Beschränkungen geben. Sie müssen berücksichtigt werden.

Nehmen wir folgenden Fall an: Es gibt ein Limit bei der Größe von Handels-Positions von 15 Standardposten in beiden Richtungen. Die aktuelle Position ist eine Long Position und entspricht 3 Posten. Das Handelsmodell möchte, auf Grundlage seines Kapitalverwaltungssystems, eine Long Position mit einem Volumen von 18,6 Posten eröffnen. Die Funktion CheckLot() liefert daraufhin das berichtigte Volumen des Abschlusses. In diesem Fall sind das 12 Posten ( da ja 3 von 15 Posten bereits durch andere Abschlüsse besetzt sind). Wenn die aktuelle Open Position eine Short anstatt einer Long Position war, würde die Funktion 15 Posten anstelle von 18,6 liefern, da dies das maximal mögliche Positions-Volumen ist.

Nach Ausgabe von 15 Buy-Posten, entspricht die Netto-Position 12 Posten (3 - Sell, 15 - Buy). Sobald ein anderes Modell seine ursprüngliche Short Position von 3 Buy-Posten aufhebt, wird die aggregierte Position zur maximal möglichen - 15 Posten. Andere Buy-Signale werden solange nicht verarbeitet, bis das Modell einige oder alle seiner 15 Buy-Posten aufhebt. Ist das für die angefragte Transaktion verfügbare Volumenaufgebraucht, liefert die Funktion eine LEERER_WERT Konstante. Dieses Signal muss übertragen werden.

Ist die Prüfung der Möglichkeit des eingerichteten Volumens positiv verlaufen, wird der Wert der erforderlichen Marge berechnet. Kann ja sein, das die Geldmittel im Account für das eingerichtete Volumen nicht ausreichen. Und genau deshalb gibt es die CheckMargin() Funktion. Ist die Marge eben nicht ausreichend, versucht diese Funktion die angegebene Menge der Transaktion zu korrigieren, sodass die aktuelle frei Marge es zu öffnen erlaubt. Ist die Marge noch nicht einmal zur Eröffnung des Minimalvolumens ausreichend, befinden wir uns im Zustand des Margen-Aufrufs.

Gibt es derzeit keine Positions und wird die Marge nicht benutzt, heißt das nur eins: technischer Margen-Aufruf - ein Zustand, in dem kein Abschluss eröffnet werden kann. Ohne unseren Account mit Geld zu füttern, können wir also nicht weiter machen.. Wird eine Marge immer noch verwendet, können wir nur warten, bis die Position, die diese Marge verwendet, geschlossen wird. Auf jeden Fall liefert eine fehlende Marge die Konstante LEERER_WERT.

Die Funktionen zur Kontrolle der Postengröße und der Margen werden meist nicht direkt verwendet, sondern von speziellen Funktionen der Kapitalverwaltung aufgerufen Diese Funktionen implementieren die Formeln zur Kapitalisierung von Accounts. Die Datei mm.mqh enthält nur zwei Basisfunktionen für Kapitalverwaltung - eine, berechnet auf Grundlage eines festen Bruchteils des Account - und die andere, berechnet auf Grundlage der von Ryan Jones vorgeschlagenen Methode, der sog. Methode der festen Anteile.

Ziel der ersten Methode ist die Bestimmung eines festen Teils der Accounts, der aufs Spiel gesetzt werden kann. Beträgt z.B. das zulässige Risiko 2% des Accounts und der Account beläuft sich auf USD $10.000, dann können maximal USD $ 200 riskiert werden. Zur Berechnung welcher Posten für einen 200-Dollar Stop verwendet werden sollte, muss man genau wissen, welcher Maximalabstand vom Kurs gegenüber der geöffneten Position erreicht werden kann. Um also mittels dieser Formel den Posten berechnen zu können, müssen wir den Stop Loss und das Kurs-Level, bei dem der Abschluss gemacht wird, exakt berechnen.

Die von Ryan Jones vorgeschlagene Methode unterscheidet sich von der vorausgegangenen, da sie grundlegend die Kapitalisierung per Funktion ausführt, die durch einen Spezialfall einer Quadratgleichung bestimmt ist.

Hier ist die Lösung:

x=((1.0+MathSqrt(1+4.0*d))/2)*Schritt;

wobei: x = die Untergrenze des Übergangs auf die nächste Ebene d = (Gewinn / delta) * 2.0 Schritt= ein Schritt von delta, wie z.B. 0,1 Posten.

Je kleiner delta ist, umso aggressiver versucht die Funktion die Anzahl an Positions zu erhöhen.Weitere Details zur Bauweise dieser Funktion finden Sie in Ryan Jones' Buch: The Trading Game: Playing by the Numbers to Make Millions.

Sollen die Funktionen der Kapitalverwaltung nicht verwendet werden, muss man die Funktionen zur Kontrolle von Posten und Marge direkt aufrufen.

Jetzt haben wir alle Elemente unseres einfachen EAs untersucht. Dann wollen wir mal die Früchte unserer Arbeit ernten.

Dazu erzeugen wir zunächst vier Modelle. Ein Modell soll auf EURUSD Standardparametern handeln und das zweite handelt ebenfalls auf EURUSD, doch in einem Zeitrahmen von 15 Minuten. Das dritte Modell starten wir auf dem GBPUSD Graph mit Standardparametern. Und das vierte auf USDCHF in einem zweistündigen Graph mit folgenden Parametern: LangsamerEMA= 6 SchnellerEMA = 12 SignalEMA = 9. Der Zeitraum fürs Testen = H1 und die Testmethode = alle Kursschwankungen im Zeitraum vom 01.01.2010 bis zum 09.01.2010.

Doch bevor wir dieses Modell in vier unterschiedlichen Modi laufen lassen, versuchen wir es für jedes Instrument und jeden Zeitrahmen separat zu testen.

Und dies ist die Tabelle auf der die Test-Hauptindikatoren gemacht sind:

System Anzahl der Abschlüsse Gewinn ($)
MACD(9,12,26)H1 EURUSD 123 1092
MACD (9,12,26) EURUSD M15 598 -696
MACD(9,6,12) USDCHF H2 153 -1150
MACD(9,12,26) GBPUSD H1 139 -282
Alle Systeme 1013 -1032

Die Tabelle zeigt, das die Gesamtzahl der Abschlüsse für alle Modelle 1913 sein und sich der Gesamtgewinn auf USD $ -1032 belaufen sollte.

Konsequenterweise sollten wir genau diese Werte auch erhalten, wenn wir diese Systeme zur gleichen Zeit testen. Die Ergebnisse sollten nicht abweichen, obwohl dennoch kleinere Abweichungen stets vorkommen.

Und das ist der endgültige Test:

Wie man erkennt, gibt es nur einen Abschluss weniger und die Gewinne unterscheiden sich nur durch USD $10, was bei einem Posten von 0,1 einer m Unterschied von nur 10 Punkten entspricht 0.1. Hier sei angemerkt, dass sich die Ergebnisse des kombinierten Testens, sollte ein System zur Geldverwaltung verwendet werden, von derMenge der Testergebnisse jedes Modells einzeln, radikal unterscheiden werden. Das liegt daran, dass die Dynamik des Saldos jedes der Systeme beeinflusst, sodass die berechneten Werte der Posten variieren werden. 

Trotz der Tatsache, dass Ergebnisse für sich allein von wenig Interesse sind, haben wir eine komplizierte, doch sehr flexible und kontrollierbare Struktur des EAs erzeugt. Und diese Struktur sehen wir uns kurz an.

Dazu konzentrieren wir uns auf das unten gezeigte Schema:

Klassenverweis

Das Schema bildet die grundlegende Struktur der Instanz des Modells ab.

Sobald eine Klasseninstanz des Handelsmodell erzeugt ist, rufen wir die überladene Init() Funktion auf: Sie initialisiert die notwendigen Parameter, bereitet die Daten vor und lädt die Indikatoren-Handels, wenn diese benutzt werden. All dies geschieht während der Initialisierung des EA, also innerhalb der OnInit() Funktion. Beachten Sie bitte, das die Daten Instanzen der Basisklassen umfassen, die zur Erleichterung des Handels konzipiert sind. Wir gehen davon aus, dass Handelsmodelle diese Klassen aktiv verwenden müssen, anstatt die Standardfunktionen von MQL5. Nachdem die Modellklasse erfolgreich erzeugt und initialisiert wurde, gehört sie zur Modellliste CList. Weitere Kommunikation mit ihr erfolgt durch den den universellen Adapter CObject.

Nach dem Auftreten von OnTrade() oder OnTick() Ereignissen, erfolgt eine sequentielle Sortierung aller Instanzen der Modelle in der Liste. Die Kommunikation mit ihnen erfolgt durch Aufruf der Processing() Funktion. Des weiteren ruft sie die Handelsfunktionen ihres eigenen Modells auf (die blaue Gruppe der Funktionen). Ihre Liste und Namen sind nicht strikt definiert, doch empfiehlt es sich, die Standardnamen zu verwenden wie z.B. LongOpened(), ShortClosed(), usw. Auf Grundlage ihrer eingebetteten Logik wählen diese Funktionen die Zeit zum Abschluss eines Abschlusses und senden dann eine speziell formulierte Anfrage zur Eröffnung oder dem Schließen des Abschlusses der SendOrder() Funktion.

Sie führt die nötigen Prüfungen durch und gibt dann die Orders auf den Markt aus. Die Handelsfunktionen verlassen sich auf Hilfsfunktionen des Modells (grüne Gruppe), die ihrerseits wiederum aktiv die grundlegenden Hilfsklassen (violette Gruppe) verwenden. Alle Hilfsklassen sind als Instanzen von Klassen im Datenbereich vorhanden (rosa Gruppe). Die Interaktion zwischen den Gruppen ist durch dunkelblaue Pfeile dargestellt.

Das Handelsmodell, basierend auf dem Indikator der Bollinger-Bänder

Da nun die generelle Struktur von Daten und Methoden klar wird, erzeugen wir ein weiteres Handelsmodell, das auf Basis der Trendindikatoren der Bollinger-Bänder arbeitet. Als Grundlage dieses Handelsmodells haben wir einen einfachen EA benutzt, den Ein Expert Advisor auf Basis der Bollinger-Bänder von Andrei Kornishkin. Bollinger-Bänder sind Levels, die einer gewissen Größe der Standardabweichungen vom einfachen gleitenden Durchschnitt entsprechen. Mehr Details dazu, wie dieser Indikator gebaut wird, findet man im Hilfe-Abschnitt der technischen Analyse, die dem MetaTrader 5 Terminal beigefügt ist.

Die Kernidee dieses Handelskonzepts ist einfach: der Kurs hat die Eigenschaft einer Rendite, d.h. er hat eine gewisse Ebene erreicht und wird sich nun aller Wahrscheinlichkeit nach, in die entgegengesetzte Richtung drehen. Diese These kann durch einen Test bei einer normalen Verteilung auf jedem echten Handelsinstrument belegt werden: die normale Verteilungskurve ist leicht verlängert. Die Bollinger-Bänder legen die wahrscheinlichsten Höhepunkte der Kurslevels fest. Sobald diese erreicht werden (die oberen und unteren Bollinger-Bänder), wird sich der Kurs sehr wahrscheinlich in die entgegengesetzte Richtung wenden.

Wir vereinfachen diese Handelstaktik leicht und arbeiten nicht mit dem Hilfsindikator - den doppelten exponentiellen gleitenden Durchschnitt (oder DEMA). Doch wir verwenden strikte schützende Stops - virtuelles Stop Loss. Dadurch wird der Handelsvorgang stabiler und zugleich können wir ein Beispiel besser verstehen, in dem jedes Handelsmodell sein eigenes, unabhängiges Level an schützenden Stops verwendet.

Für das Level der schützenden Stops, nehmen wir den aktuellen Kurs plus oder minus der Indikatorwerts der Volatilität ATR. Entspricht der aktuelle ATR-Wert 68 Punkten und gibt es ein Signal zu einem Preis von 1,25720 zu verkaufen, ist der virtuelle Stop Loss für diesen Abschluss 1,25720 + 0,0068 = 1,26400. Das gleiche (nur eben in umgekehrter Richtung) gilt fürs Kaufen: 1,25720 - 0,0068 = 1,25040.

Der Quellcode für dieses Modell steht unten:

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model use Bollinger bands.
//| Buy when price is lower than lower band
//| Sell when price is higher than upper band
//+----------------------------------------------------------------------+  
struct cmodel_bollinger_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               period_bollinger;
   double            deviation;
   int               shift_bands;
   int               period_ATR;
   double            k_ATR;
   double            delta;
};
   
class cmodel_bollinger : public CModel
{
private:
   int               m_bollinger_period;
   double            m_deviation;
   int               m_bands_shift;
   int               m_ATR_period;
   double            m_k_ATR;
   //------------Indicators Data:-------------
   int               m_bollinger_handle;
   int               m_ATR_handle;
   double            m_bollinger_buff_main[];
   double            m_ATR_buff_main[];
   //-----------------------------------------
   MqlRates          m_raters[];
   double            m_current_price;
public:
                     cmodel_bollinger();
   bool              Init();
   bool              Init(cmodel_bollinger_param &m_param);
   bool              Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES TimeFrame, double delta,
                          uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_bollinger_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
   bool              CloseByStopSignal();
};

cmodel_bollinger::cmodel_bollinger()
{
   m_bollinger_handle   = INVALID_HANDLE;
   m_ATR_handle         = INVALID_HANDLE;
   ArraySetAsSeries(m_bollinger_buff_main,true);
   ArraySetAsSeries(m_ATR_buff_main,true);
   ArraySetAsSeries(m_raters, true);
   m_current_price=0.0;
}
//this default loader
bool cmodel_bollinger::Init()
{
   m_magic              = 322311;
   m_model_name         =  "Bollinger Bands Model";
   m_symbol             = _Symbol;
   m_timeframe          = _Period;
   m_bollinger_period   = 20;
   m_deviation          = 2.0;
   m_bands_shift        = 0;
   m_ATR_period         = 20;
   m_k_ATR              = 2.0;
   m_delta              = 0;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(cmodel_bollinger_param &m_param)
{
   m_magic              = 322311;
   m_model_name         = "Bollinger Model";
   m_symbol             = m_param.symbol;
   m_timeframe          = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_bollinger_period   = m_param.period_bollinger;
   m_deviation          = m_param.deviation;
   m_bands_shift        = m_param.shift_bands;
   m_ATR_period        = m_param.period_ATR;
   m_k_ATR              = m_param.k_ATR;
   m_delta              = m_param.delta;
   //if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES timeframe, double delta,
                           uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR)
{
   m_magic           = magic;
   m_model_name      = name;
   m_symbol          = symbol;
   m_timeframe       = timeframe;
   m_delta           = delta;
   m_bollinger_period= bollinger_period;
   m_deviation       = deviation;
   m_bands_shift     = bands_shift;
   m_ATR_period      = ATR_period;
   m_k_ATR           = k_ATR;
   if(!InitIndicators())return(false);
   return(true);
}


/*bool cmodel_bollinger::CheckParam(cmodel_bollinger_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT)){
      Print("Symbol ", m_symbol, " select failed. Check valid name symbol");
      return(false);
   }
   if(m_ma == 0){
      Print("Fast EMA must be bigest 0. Set MA = 12 (default)");
      m_ma=12;
   }
   return(true);
}*/

bool cmodel_bollinger::InitIndicators()
{
   m_bollinger_handle=iBands(m_symbol,m_timeframe,m_bollinger_period,m_bands_shift,m_deviation,PRICE_CLOSE);
   if(m_bollinger_handle==INVALID_HANDLE){
      Print("Error in creation of Bollinger indicator. Restart the Expert Advisor.");
      return(false);
   }
   m_ATR_handle=iATR(m_symbol,m_timeframe,m_ATR_period);
   if(m_ATR_handle==INVALID_HANDLE){
      Print("Error in creation of ATR indicator. Restart the Expert Advisor.");
      return(false);
   }
   return(true);
}

bool cmodel_bollinger::Processing()
{
   //if(timing(m_symbol,m_timeframe, m_timing)==false)return(false);
   
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   //m_symbol_info.Name(m_symbol);
   //m_symbol_info.RefreshRates();
   //Copy last data of moving average
 
   GetNumberOrders(m_orders);
   
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   if(m_orders.all_orders!=0)CloseByStopSignal();
   return(true);
}

bool cmodel_bollinger::LongOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   //Print("Model Bollinger: ", m_orders.buy_orders);
   bool rezult, time_buy=true;
   double lot=0.1;
   double sl=0.0;
   double tp=0.0;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   //lot=open_mm.optimal_f(m_symbol,OP_BUY,m_symbol_info.Ask(),sl,delta);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      sl=NormalizeDouble(m_symbol_info.Ask()-m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_BUY,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::ShortOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, time_sell=true;
   double lot=0.1;
   double sl=0.0;
   double tp;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {   
      sl=NormalizeDouble(m_symbol_info.Bid()+m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_SELL,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_BUY)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_SELL)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::CloseByStopSignal(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   bool rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL&&t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyRates(m_symbol,m_timeframe,0,3,m_raters);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.Type()==ORDER_TYPE_BUY)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by stop");
         continue;
      }
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.Type()==ORDER_TYPE_SELL)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by stop");
         continue;
      }
   }
   return(rez);
}

Wie man sieht, ist der Code des Handelsmodells dem Quellcode der vorherigen Handelstaktik sehr ähnlich. Die Hauptvariation hier ist das Auftauchen der virtuellen Stop Orders und der Funktion cmodel_bollinger:: CloseByStopSignal(), die diesen schützenden Stops dient.

Will man schützende Stops setzen müssen in der Tat ihre Werte einfach nur an die SendOrder() Funktion übertragen werden. Diese Funktion gibt dann diese Levels in die Order-Tabelle ein. Sobald der Kurs diese Levels erreicht oder kreuzt, schließt die Funktion CloseByStopSignal() den Abschluss mit einer Gegen-Order und entfernt ihn aus der Liste der aktiven Orders.

Die Vereinigung von Handelsmodellen, Instrumenten und Zeitrahmen in ein einzige Einheit

Da wir jetzt zwei Handelsmodelle haben, müssen wir sie nun auch simultan testen. Bevor wir ein Layout der Modelle machen, wäre es sinnvoll ihre wirkungsvollsten Parameter festzulegen. Und dazu müssen wir bei jedem Modell separat eine Optimierung durchführen.

Diese Optimierung demonstriert auf welchem Markt und in welchem Zeitrahmen das Modell am effektivsten ist. Für jedes Modell werden dazu nur zwei der besten Zeitrahmen und drei der besten Instrumente gewählt. Als Ergebnis erhalten wir 12 unabhängige Lösungen (2 Modelle * 3 Instrumente * 2 Zeitrahmen), die allesamt gemeinsam getestet werden. Natürlich leidet die ausgewählte Optimierungsmethode unter der sog. "Anpassung" der Ergebnisse, doch das ist für unsere Zwecke hier nicht sonderlich wichtig.

Die Graphen unten zeigen die besten Resultate für unsere Stichproben:

1.1 MACD EURUSD M30

MACD EURUSD M30

1.2 . MACD EURUSD H3


MACD EURUSD H3

1.3 MACD AUDUSD H4

MACD AUDUSD H4

1.4 . MACD AUDUSD H1


MACD AUDUSD H1

1.5 MACD GBPUSD H12


MACD GBPUSD H12

1.6 MACD GBPUSD H6


MACD GBPUSD H6

2.1 Bollinger GBPUSD M15


Bollinger GBPUSD M15

2.2 Bollinger GBPUSD H1


Bollinger GBPUSD H1

2.3 Bollinger EURUSD M30

Bollinger EURUSD M30

 

2.4  Bollinger EURUSD H4


Bollinger EURUSD H4

 

2.5 Bollinger USDCAD M15


Bollinger USDCAD M15

 

2.6 Bollinger USDCAD H2

Bollinger USDCAD H2

Jetzt kennen wir die optimalen Ergebnisse und müssen nur noch eine Kleinigkeit erledigen - all diese Ergebnisse in eine einzige Einheit aufnehmen.

Unten steht der Quellcode des Funktions-Loaders, der die oben gezeigten 12 Handelsmodelle erzeugt, wonach der EA konsequent auf diesen Modellen zu handeln beginnt:

bool macd_default=true;
bool macd_best=false;
bool bollinger_default=false;
bool bollinger_best=false;

void InitModels()
{
   list_model = new CList;             // Initialized pointer of the model list
   cmodel_macd *model_macd;            // Create the pointer to a model MACD
   cmodel_bollinger *model_bollinger;  // Create the pointer to a model Bollinger
   
//----------------------------------------MACD DEFAULT----------------------------------------
   if(macd_default==true&&macd_best==false)
    {
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      // Loading of the parameters was completed successfully
      if(model_macd.Init(129475, "Model macd M15", _Symbol, _Period, 0.0, Fast_MA,Slow_MA,Signal_MA))
      { 
      
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Загружаем модель в список моделей
      }
      else
      {
                                 // The loading of parameters was completed successfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//-------------------------------------------------------------------------------------------
//----------------------------------------MACD BEST------------------------------------------
   if(macd_best==true&&macd_default==false)
   {
      // 1.1 EURUSD H30; FMA=20; SMA=24; 
      model_macd = new cmodel_macd; // Initialize the pointer to the model MACD
      if(model_macd.Init(129475, "Model macd H30", "EURUSD", PERIOD_M30, 0.0, 20,24,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " created successfully");
         list_model.Add(model_macd);// load the model into the list of models
      }
      else
      {// Loading parameters was completed unsuccessfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.2 EURUSD H3; FMA=8; SMA=12; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H3", "EURUSD", PERIOD_H3, 0.0, 8,12,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
       {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.3 AUDUSD H1; FMA=10; SMA=18; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd M15", "AUDUSD", PERIOD_H1, 0.0, 10,18,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// The loading of parameters was unsuccessful                       
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.4 AUDUSD H4; FMA=14; SMA=15; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H4", "AUDUSD", PERIOD_H4, 0.0, 14,15,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else{// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.5 GBPUSD H6; FMA=20; SMA=33; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H6, 0.0, 20,33,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was  unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.6 GBPUSD H12; FMA=12; SMA=30; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H12, 0.0, 12,30,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//----------------------------------------------------------------------------------------------
//-------------------------------------BOLLINGER DEFAULT----------------------------------------
   if(bollinger_default==true&&bollinger_best==false)
   {
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger",_Symbol,PERIOD_CURRENT,0,
                             period_bollinger,dev_bollinger,0,14,k_ATR))
      {
         Print("Model ", model_bollinger.Name(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
//--------------------------------------BOLLLINGER BEST-----------------------------------------
   if(bollinger_best==true&&bollinger_default==false)
   {
      //2.1 Symbol: EURUSD M30; period: 15; deviation: 2,75; k_ATR=2,75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_M30,0,15,2.75,0,14,2.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
              ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.2 Symbol: EURUSD H4; period: 30; deviation: 2.0; k_ATR=2.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_H4,0,30,2.00,0,14,2.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.3 Symbol: GBPUSD M15; period: 18; deviation: 2.25; k_ATR=3.0;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","GBPUSD",PERIOD_M15,0,18,2.25,0,14,3.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.4 Symbol: GBPUSD H1; period: 27; deviation: 2.25; k_ATR=3.75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","GBPUSD",PERIOD_H1,0,27,2.25,0,14,3.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.5 Symbol: USDCAD M15; period: 18; deviation: 2.5; k_ATR=2.00;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_M15,0,18,2.50,0,14,2.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.6 Symbol: USDCAD M15; period: 21; deviation: 2.5; k_ATR=3.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_H2,0,21,2.50,0,14,3.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
}

Testen wir nun alle 12 Modelle gleichzeitig:


Kapitalisierung der Ergebnisse

Der Graph mit den Ergebnissen ist beeindruckend. Was hier jedoch wichtig ist, ist nicht das Ergebnis, sondern die Tatsache, dass alle Modelle simultan Handel ausführen, alle ihre jeweiligen schützenden Stops verwenden und alle unabhängig voneinander arbeiten. Versuchen wir nun den sich daraus ergebenden Graph zu kapitalisieren. Dazu verwenden wir die Standardfunktionen für Kapitalisierung: die fest-proportionale und die von Ryan Jones vorgeschlagene Methode.

Das sog. optimal f ist ein Spezialfall der fest-proportionalen Methode. Ihr Kern ist, dass jedem Abschluss ein Limit an Verlusten gegeben wird, der einem Prozentwert des Accounts entspricht. Konservative Kapitalisierungsstrategien setzen meistens ein Verlust-Limit von 2% fest: d.h. bei USD $10.000 wird die Größe der Position so berechnet, dass der Verlust, nachdem der Stop Loss übertragen wurde, nicht höher als USD $200 ist. Es gibt hier jedoch eine gewisse Funktion des Wachstums des endgültigen Saldos durch die Zunahme des Risikos. Sie hat die Form einer Glocke, also bei zunehmendem Risiko gibt es zunächst auch einen Anstieg des Gesamtgewinns. Doch gibt es bei jedem Abschluss eine Risikoschwelle, jenseits der der Gesamtgewinn abzunehmen beginnt und diese Schwelle ist eben das sog. optimal f.

Dieser Artikel beschäftigt sich nicht mit dem Thema Kapitalverwaltung, sodass wir nur wissen müssen, wie wir die fest-proportionale Methode einsetzen können müssen - nämlich die Ebene der schützenden Stops und des Prozentwerts des Accounts, den wir aus Spiel setzen können. Ryan Jones' Formel ist anders aufgebaut Für ihre Arbeit ist die Anwendung fixer, schützender Stops nicht vonnöten. Da das erste der vorgeschlagenen Modelle (das MACD-Modell) ziemlich primitiv ist und keine schützenden Stops hat, verwenden wir es zur Kapitalisierung dieses Modells. Für dieses Modell, basierend auf den Bollinger-Bändern, verwenden wir die fest-proportionale Methode der Proportionen.

Um die Kapitalisierungsformel anwenden zu können, müssen wir die Variable m_delta, die im grundlegenden Modell mit eingeschlossen ist, füllen

Bei der Arbeit mit der Formel der festen Proportionen, muss diese = dem Prozentsatz des Risikos für jeden Abschluss sein. Bei der Arbeit mit der Methode von Ryan Jones, muss sie = dem sog. delta Inkrement sein, d.h. gleich dem Geldbetrag, der erwirtschaftet werden muss, um eine höhere Ebene des Positions-Volumens zu erreichen.

Unten sehen Sie den Graph der Kapitalisierung:

Hier wird deutlich, dass alle Modelle ihre eigenen Kapitalisierung-Formeln haben (die fest-proportionale Methode oder die Methode von Ryan Jones).

Im gezeigten Beispiel haben wir für alle Modelle dieselben Werte für Maximalrisiko und delta verwendet. Doch können wir für jedes Modell individuelle Parameter zur Kapitalisierung auswählen. Die Feinabstimmung jedes dieser Modelle wird im Rahmen diese Beitrags nicht berücksichtigt.

Mit pending Orders arbeiten

Die vorgestellten Handelsmodellen verwenden keine sog. pending Orders. Das sind Orders, die nur unter bestimmten Bedingungen und Situationen ausgeführt werden.

In Wirklichkeit kann jede Handelsstrategie, die mit pending Orders arbeitet, auf die Verwendung von Orders im Markt angepasst werden. Pending Orders sind vielmehr für einen wesentlich zuverlässigeren Systembetrieb vonnöten, da im Falle des Absturzes des Roboters oder der Ausrüstung auf der er arbeitet, pendig Order immer noch weiterhin schützende Stops ausführen würden, oder, im umgekehrten Fall, auf Grundlage der zuvor festgelegten Preise immer noch in den Markt einsteigen würden.

Das vorgeschlagene Handelsmodell gestattet einem die Arbeit mit pending Handelsorders, obwohl der Kontrollprozess ihrer Präsentation in diesem Fall, viel, viel komplizierter ist. Um mit diesen Orders zu arbeiten, verwenden wir die überladene Methode Add (COrderInfo & order_info, double stop_loss, double take_profit) der CTableOrders Klasse. In diesem Fall enthält die Variable m_type dieser Klasse die entsprechende Art der pending order, z.B. also ORDER_TYPE_BUY_STOP oder ORDER_TYPE_SELL_LIMIT.

Wenn die pending Order später dann ausgegeben wird, muss man den Moment "abpassen", an dem ihre Arbeit ausgelöst wurde oder ihre Relevanz läuft ab. Das ist nicht ganz einfach, da es nicht genügt, einfach nur das Ereignis Handel zu kontrollieren, sondern man sollte auch wissen, wodurch es ausgelöst wurde.

Die MQL5-Programmiersprache entwickelt sich weiter, und derzeit wird an der Lösung des Problems eine spezielle Dienststruktur in die Sprache mit aufzunehmen, die das Ereignis Handel erklären würde, gearbeitet. Doch bis dahin müssen wir die Liste der Orders in der History duchgehen. Wird eine Order mit dem gleichen Ticket als eine pending Order in der Order-Tabelle in der History gefunden, dann sind einige Ereignisse passiert, die in der Tabelle abgebildet werden müssen. 

Der Code des Basismodells beinhaltet eine spezielle Methode CModel:: ReplaceDelayedOrders. Sie funktioniert nach folgendem Algorithmus. Zuerst werden alle aktiven Orders in der Order-Tabelle geprüft. Dann wird das Ticket dieser Orders mit dem Ticket der Orders in der History abgeglichen (HistoryOrderGetTicket()). Stimmt das Ticket der Order aus der History mit der pending Order in der Order-Tabelle überein, ist jedoch der Order-Status 'ausgeführt' (ORDER_STATE_PARTIAL oderORDER_STATE_FILLED), verändert sich der Status der pending Orders in der Order-Tabelle ebenfalls auf 'ausgeführt'.

Ist diese Order des weiteren nicht mit irgendwelchen pending Orders verknüpft, die die Arbeit von Stop Loss und Take Profit nachahmen, werden solche Orders ausgegeben und ihre Tickets in die entsprechenden Tabellenwerte eingegeben (TicketSL, TicketTP). Und die pending Orders, die die schützenden Stop und Profit-Levels nachgeahmt haben, werden zum, im Voraus mit Hilfe der Variablen m_sl und m_tp, festgesetzten Preis ausgegeben. Das bedeutet also, dass man diese Preise im Augenblick des Aufrufs der ReplaceDelayedOrders Methode kennen sollte.

Hier muss darauf hingewiesen werden, dass diese Methode auch auf den Umgang mit der Situation angepasst ist, wenn eine Order in den Markt ausgegeben wird, bei der die erforderliche pending Orders vom Typ Stop Loss und Take Profit sind.

Generell ist die Arbeit der vorgeschlagenen Methode nicht ohne und verlangt für einen sinnvollen Einsatz durchaus ein gewisses Verständnis.

bool CModel::ReplaceDelayedOrders(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int history_orders=HistoryOrdersTotal();
   ulong ticket;
   bool rez=false;
   long request;
   total_elements=ListTableOrders.Total();
   int try=0;
   if(total_elements==0)return(false);
   // View every order in the table
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      switch(t.Type())
      {
         case ORDER_TYPE_BUY:
         case ORDER_TYPE_SELL:
            for(int b=0;i<history_orders;b++)
            {
               ticket=HistoryOrderGetTicket(b);
               // If the ticket of the historical order is equal to one of the tickets 
               // Stop Loss or Take Profit, then the order was blocked, and it needs to be
               // deleted from the table of orders
               if(ticket==t.TicketSL()||ticket==t.TicketTP())
               {
                  ListTableOrders.DeleteCurrent();
               }
            }
            // If the orders, imitating the Stop Loss and Take Profit are not found in the history,
            // then perhaps they are not yet set. Therefore, they need to be inputted,
            // using the process for pending orders below:
            // the cycle  keeps going, the exit 'break' does not exist!!!
         case ORDER_TYPE_BUY_LIMIT:
         case ORDER_TYPE_BUY_STOP:
         case ORDER_TYPE_BUY_STOP_LIMIT:
         case ORDER_TYPE_SELL_LIMIT:
         case ORDER_TYPE_SELL_STOP:
         case ORDER_TYPE_SELL_STOP_LIMIT:
            for(int b=0;i<history_orders;b++)
            {
               ticket=HistoryOrderGetTicket(b);
               // If the ticket of the historical order is equal to the ticket of the pending order 
               // then the pending order has worked and needs to be put out
               // the pending orders, imitating the work of Stop Loss and Take Profit.
               // It is also necessary to change the pending status of the order in the table
               // of orders for the executed (ORDER_TYPE_BUY или ORDER_TYPE_SELL)
               m_order_info.InfoInteger(ORDER_STATE,request);
               if(t.Ticket()==ticket&&
                  (request==ORDER_STATE_PARTIAL||request==ORDER_STATE_FILLED))
                  {
                  // Change the status order in the table of orders:
                  m_order_info.InfoInteger(ORDER_TYPE,request);
                  if(t.Type()!=request)t.Type(request);
                  //------------------------------------------------------------------
                  // Put out the pending orders, imitating Stop Loss an Take Profit:
                  // The level of pending orders, imitating Stop Loss and Take Profit
                  // should be determined earlier. It is also necessary to make sure that
                  // the current order is not already linked with the pending order, imitating Stop Loss
                  // and Take Profit:
                  if(t.StopLoss()!=0.0&&t.TicketSL()==0)
                    {
                     // Try to put out the pending order:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put up a pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
                  if(t.TakeProfit()!=0.0&&t.TicketTP()==0){
                     // Attempt to put out the pending order, imitating Take Profit:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                           break;
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
               }
            }
            break;
         
      }
   }
   return(true);
}

Mit ihrer Hilfe können Sie mühelos ein Modell auf Grundlage von pending Orders erzeugen.

Fazit

Leider ist es unmöglich in einem einzigen Beitrag alle Feinheiten, Herausforderungen und Vorteile des vorgeschlagenen Ansatzes erschöpfend zu besprechen. So haben wir z.B. die Serialisierung von Daten nicht erwähnt - eine Methode mit der Sie die gesamte notwendig Information zum aktuellen Status der Modelle in Datendateien speichern, aufzeichnen und von dort abrufen können. Ebenfalls haben wir die Handelsmodelle, auf Grundlage des Handels mit synthetischen Spreads nicht berücksichtigt. Alles sehr interessante Themen, die sicherlich ihre eigenen wirksamen Lösungen für die vorgeschlagenen Konzepte haben.

Was uns in diesem Beitrag hauptsächlich gelungen ist, ist die Entwicklung einer vollkommen dynamischen und kontrollier- und verwaltbaren Datenstruktur. Das Konzept der verknüpften Listen ermöglicht ihre effektive Verwaltung und macht Handelstaktiken unabhängig und einzeln anpassbar. Ein weiterer wichtiger Vorteil dieses Ansatzes ist seine absolute und vollständige Universalität, da

es auf seiner Basis ausreicht zwei EAS zu erzeugen, die Handelsoperationen durchführen und sie auf das gleiche Instrument zu setzen. Beide werden dann automatisch nur mit ihren eigenen Orders arbeiten und nur mit ihrem eigenen System der Kapitalverwaltung. Daher gibt es hier eine Unterstützung der Abwärtskompatibilität, denn alles, was simultan in einem EA ausgeführt werden kann, kann auch auf mehrere Roboter verteilt werden. Diese Eigenschaft ist bei der Arbeit mit Netto-Positions extrem wichtig.

Das beschriebene Modell ist nicht nur reine Theorie, sondern umfasst einen fortgeschrittenen Apparat an Hilfsfunktionen, Funktionen zur Kapitalverwaltung und zur Prüfung aller Anforderungen an Margen. Das Sendesystem von Orders ist sog. Requoten und Zielabweichungen gegenüber resistent - beides Effekte, die in echtem Handeln oft auftreten.

Die Handels-Engine bestimmt die Maximalgröße der Position und ds Maximalvolumen der Abschlüsse. Ein spezieller Algorithmus trennt Handelsanfragen in mehrere unabhängige Abschlüsse auf, von denen jeder separat verarbeitet wird. Darüber hinaus hast sich das vorgestellte Modell in der Automatisiertes Handeln - Meisterschaft 2010 als äußert gut erwiesen- alle Abschlüsse wurden exakt und in Übereinstimmung mit dem Handelsplan ausgeführt und die in der Meisterschaft präsentierten Handelsmodelle werden unter unterschiedlichen Risikoausmaßen und auf unterschiedlichen System der Geldverwaltung verwaltet.

Der vorgestellte Ansatz stellt eine annähernd vollständige Lösung zur Teilnahme an Meisterschaften sowie auf für parallele Handelsoperationen auf mehreren Instrumenten und Zeitrahmen und mit verschiedenen Handelstaktiken dar. Die einzige Schwierigkeit, sich mit diesem Ansatz wirklich vertraut zu machen, liegt in seiner Komplexität begründet.