Bewertung von Handelssystemen - die Effektivität von Einstieg, Ausstieg und Handel im Allgemeinen

Mykola Demko | 14 Januar, 2016

Einleitung

Es gibt eine Menge Maßnahmen zur Bestimmung der Effektivität eines Handelssystems, und Händler wählen diejenigen, die ihnen am besten passen. Dieser Beitrag befasst sich mit den, im Buch "Statistika dlya traderov" ("Statistics for Traders") von S.V. Bulashev beschriebenen Vorgehensweisen. Leider ist dieses Werk nicht in ausreichender Auflage vorhanden und wurde seit geraumer Zeit auch nicht wieder neu aufgelegt, doch seine EDV-Version ist auf vielen Websites immer noch abrufbar.


Vorbemerkung

Ich möchte an dieser Stelle daran erinnern, dass dieses Buch bereits 2003 veröffentlicht wurde, also zur Zeit von MetaTrader 3 mit der MQL-II Programmiersprache. Für damalige Verhältnisse war die Plattform reichlich fortschrittlich, daher können wir die Veränderungen der Handelsbedingungen an sich durch ihren Vergleich mit modernen MetaTrader 5 Client-Terminal verfolgen. Des Weiteren sollte darauf hingewiesen werden, dass der Autor dieses Buchs für viele Händler-Generationen quasi zu einem 'Guru' geworden ist (und das bei dem raschen Wechsel Generationen in diesem Bereich). Doch die Zeit steht nicht still, daher müssen die entsprechenden Ansätze, trotz der Tatsache, dass die in diesem Buch beschriebenen Prinzipien immer noch anwendbar sind, durchaus angepasst werden.

S.V. Bulashev verfasste dieses Buch zunächst auf Grundlage damals aktueller Handelsbedingungen, deswegen können wir die vom Autor beschriebenen Statistiken eben nicht ohne Umwandlung anwenden. Zur Verdeutlichung: Erinnern wir uns an die Handelsmöglichkeiten - marginales Handeln auf einem Spotmarkt impliziert, dass sich der Kauf einer Währung zum Zweck eines spekulativen Gewinns, nach gewisser Zeit in ihren Verkauf wandelt.

Das sind die Grundlagen, daher sollte man sie auch nicht vergessen. Und diese exakte Interpretation wurde verwendet, als das Buch "Statistics for Traders" entstand. Jeder Abschluss von 1 Posten sollte durch den umgekehrten Abschluss desselben Volumens geschlossen werden. Doch nach zwei Jahren (2005) bedurften diese Statistiken bereits einer Neuorganisation. Grund war, dass das teilweise Schließen von Abschlüssen in MetaTrader 4 möglich wurde. Um daher, die von Bulashev beschriebenen Statistiken anzuwenden, müssen wir das Interpretationssystem erweitern, insbesondere sollte die Interpretation vor dem Hintergrund der Tatsache des Schließens und nicht der Eröffnung stattfinden.

Und nach weiteren 5 Jahren veränderte sich die Situation dramatisch. Was passierte mit dem so bekannten Begriff Order? Er verschwand. Angesichts der Unmengen Fragen in diesem Forum, ist es besser, das exakte System der Interpretation inMetaTrader5 zu beschreiben.

Heutzutage gibt es also keine Order im klassischen Sinne mehr; heute ist eine Order eine Handelsanfrage an einen Makler-Server, die entweder von einem Händler oder MTS getätigt wird, und die Eröffnung oder Veränderung einer Handelsposition umfasst. Sie ist jetzt eine Position, und um ihre Bedeutung zu verstehen, habe ich den marginalen Handel erwähnt. Tatsache ist, dass marginales Handeln mit geliehenem Geld abläuft, und es deshalb eine Position so lange gibt, wie es auch dieses geliehene Geld gibt.

Sobald man dem Geldgeber durch Abschluss dieser Position die Außenstände bezahlt und als Ergebnis einen Gewinn/Verlust macht, besteht dieses Position nicht mehr. Übrigens ist das auch die Erklärung dafür, warum eine Umkehrung der Position diese nicht schließt, denn das geliehene Geld bleibt nämlich sowieso bestehen und deshalb macht es keinen Unterschied, ob man sich Geld zum Kaufen oder Verkaufen geliehen hat. Ein Abschluss ist nur die Historie einer ausgeführten Order.

Doch kommen wir nun zu den Merkmalen des Handelns. Derzeit kann man inMetaTrader5 sowohl eine Handelsposition teilweise schließen oder eine bestehende erhöhen. Und deshalb gehört das klassische Interpretationssystem, in dem jede Eröffnung einer Position eines gewissen Volumens vom Schließen desselben Volumens gefolgt wird, der Vergangenheit an. Doch kann man sie wirklich nicht mehr von den, inMetaTrader5 gespeicherten Informationen, erneut erhalten? Also organisieren wir als ersten Schritt die Interpretation neu.


Die Effektivität des Einstiegs

Jeder weiß, dass viele Menschen ihren Handel effektiver machen wollen, doch wie beschreibt man den Begriff 'Effektivität' formal? Geht man davon aus, dass ein Abschluss ein Pfad ist, der durch den Kurs verfolgt wird, wird es schnell klar, dass sich auf diesem Pfad zwei Extrempunkte befinden: Minimal- und Maximalkurs innerhalb des beobachteten Abschnitts. Jeder versucht so nahe wie möglich am Minimalkurs in den Markt einzusteigen (zumindest beim Kaufen), und dies kann als Hauptregel jedes Handels gelten - bei niedrigem Kurs kaufen, bei hohem verkaufen.

Die Effektivität des Einstiegs legt fest, wie nahe am Minimalkurs man kauft. Die Effektivität des Einstiegs ist also das Verhältnis des Abstands zwischen dem Maximalkurs und dem Kurs bei Einstieg in den kompletten Pfad. Warum messen wir den Abstand zum Minimum mit Hilfe der Differenz des Maximums? Für uns muss die Effektivität beim Einstieg zum Minimum = 1 sein (und = 0 beim Einstieg zum Maximum).

Deshalb nehmen wir für unser Verhältnis den restlichen Abstand und nicht den Abstand zwischen Minimum und Einstieg selbst. Lassen Sie mich an dieser Stelle darauf hinweisen, dass die Situation für Verkauf im Vergleich zum Kauf spiegelbildlich ist.

 

Die Effektivität des Einstiegs bei einer Position zeigt, wie gut ein MTS den potenziellen Gewinn im Verhältnis zum Einstiegskurs während eines gewissen Handels realisiert. Seiner Berechnung liegen folgende Formeln zugrunde:

for long positions
enter_efficiency=(max_price_trade-enter_price)/(max_price_trade-min_price_trade);

for short positions
enter_efficiency=(enter_price-min_price_trade)/(max_price_trade-min_price_trade);

The effectiveness of entering can have a value within the range from 0 to 1.


Die Effektivität des Ausstiegs

Bei einem Ausstieg verhält es sich ähnlich:


Die Effektivität des Ausstiegs von einer Position zeigt, wie gut ein MTS den potenziellen Gewinn im Verhältnis zum Ausstiegskurs während eines gewissen Handels realisiert. Seiner Berechnung liegen folgende Formeln zugrunde:


for lone positions
exit_efficiency=(exit_price - min_price_trade)/(max_price_trade - min_price_trade);

for short positions
exit_efficiency=(max_price_trade - exit_price)/(max_price_trade - min_price_trade);

The effectiveness of exiting can have a value withing the range from 0 to 1.


Die Effektivität eines Handels

Insgesamt wird die Effektivität eines Handels durch sowohl Einstieg als auch Ausstieg bestimmt, und kann als das Verhältnis des Pfads zwischen Einstieg und Ausstieg zum maximalen Abstand währendes Handels berechnet werden (also die Differenz zwischen Minimum und Maximum). Die Effektivität eines Handels lässt sich daher auf zweierlei Arten berechnen: direkt, mit Hilfe der primären Informationen über den Handel, oder mittels der bereits berechneten Ergebnisse von früher bewerteten Einstiegen und Ausstiegen (mit einer Intervallverschiebung).

Die Effektivität eines Handels zeigt, wie gut ein MTS den potenziellen Gewinn während eines gewissen Handels realisiert, Seiner Berechnung liegen folgende Formeln zugrunde:

for long positions
trade_efficiency=(exit_price-enter_price)/(max_price_trade-min_price_trade);

for short positions
trade_efficiency=(enter_price-exit_price)/(max_price_trade-min_price_trade);

general formula
trade_efficiency=enter_efficiency+exit_efficiency-1;

The effectiveness of trade can have a value within the range from -1 to 1.
The effectiveness of trade must be greater than 0,2. 
The analysis of effectiveness visually shows the direction for enhancing the system, because it allows evaluating the quality of signals for entering and exiting a position separately from each other.


Die Umwandlung der Interpretation

Um Verwirrungen zu vermeiden, müssen wir zunächst die Namen der Objekte der Interpretation klären. Da sowohl in MetaTrader5, als auch bei Bulachev mit denselben Begriffen gearbeitet wird - Order, Abschluss, Position - müssen wir sie unterscheiden. In diesem Beitrag werde ich das Objekt der Interpretation von Bulachev "Handel" nennen. Ein Handel ist also ein Abschluss. Bulachev verwendet dafür auch den Begriff "Order"; in diesem Kontext sind beide identisch. Bulachev nennt einen nicht abgeschlossenen Abschluss 'Position', wir bezeichnen ihn als nicht geschlossenen Handel.

Man sieht hier klar, dass alle drei Begriffe leicht im Wort "Handel" verschwinden. Die Interpretation in MetaTrader5 werden wir nicht umbenennen, und die Bedeutung dieser drei Begriffe beleibt genauso, wie sie von den Entwicklern des Client-Terminals konzipiert wurden. Wir haben daher also vier Begriffe, mit denen wir arbeiten werden: Position, Abschluss, Order und Trade.

Da eine Order ja ein Befehl an den Server ist, eine Position zu eröffnen/verändern, betrifft sie die Statistiken nicht direkt, indirekt jedoch schon, und zwar durch einen Abschluss (das liegt daran, dass das Absenden einer Order nicht immer zur Ausführung des entsprechenden Abschlusses eines spezifischen Volumens und Kurses führt). Daher ist es angebracht, die Statistiken nach Abschlüssen und nicht nach Order zu sammeln.

Betrachten wir uns ein Beispiel einer Interpretation derselben Position (um das oben Erklärte klarer zu machen):

interpretation in МТ-5
deal[ 0 ]  in      0.1   sell   1.22218   2010.06.14 13:33
deal[ 1 ]  in/out  0.2   buy    1.22261   2010.06.14 13:36
deal[ 2 ]  in      0.1   buy    1.22337   2010.06.14 13:39
deal[ 3 ]  out     0.2   sell   1.22310   2010.06.14 13:41
interpretation by Bulachev
trade[ 0 ]  in 0.1  sell 1.22218 2010.06.14 13:33   out 1.22261 2010.06.14 13:36
trade[ 1 ]  in 0.1  buy  1.22261 2010.06.14 13:36   out 1.22310 2010.06.14 13:41
trade[ 2 ]  in 0.1  buy  1.22337 2010.06.14 13:39   out 1.22310 2010.06.14 13:41


Jetzt zeige ich Ihnen wie diese Bearbeitungen ausgeführt wurden. Abschluss[ 0 ] öffnet die Position: wir schreiben dies als Start des neuen Handels:

trade[ 0 ]  in 0.1  sell 1.22218 2010.06.14 13:33

Danach folgt die Umkehrung der Position, d.h. alle vorigen Handel sollten geschlossen werden, und entsprechend wird die Information über den Umkehr- Abschluss[ 1 ] sowohl beim Abschluss als auch bei der Eröffnung des neuen Handels berücksichtigt. Sobald alle nicht abgeschlossenen Handel vor dem Abschluss mit Richtung hinein/heraus geschlossen sind, müssen wir den neuen Handel eröffnen. Das heißt, wir verwenden zum Schließen nur die Kurs- und die Zeitinformation des für ein Schließen ausgewählten Abschlusses, eben genau entgegengesetzt wie bei der Eröffnung eines Handels, wenn Art und Volumen zusätzlich verwendet werden. An dieser Stelle muss muss darauf hingewiesen werden, dass nun ein bislang noch nicht verwendeter Begriff in der neuen Interpretation auftaucht - die Richtung des Abschlusses, Zuvor haben wir mit "Richtung" entweder einen Kauf oder Verkauf gemeint, die gleiche Bedeutung wie der Begriff "Art". Doch ab jetzt und in Zukunft sind Art und Richtung zwei unterschiedliche Begriffe:

Art ist ein Kauf oder Verkauf; Richtung hingegen bezeichnet den Einstieg oder Ausstieg einer Position. Deshalb wird eine Position immer mit einem Abschluss der Richtung in eröffnet, und mit einem der Richtung out geschlossen, Doch ist die Richtung nicht nur auf Eröffnung oder Schließen von Positionen begrenzt. Mit diesem Begriff werden auch der Anstieg des Volumens einer Position (wenn der "in" Abschluss nicht der erste in der Liste ist) und das teilweise Schließen einer Position (die "out" Abschlüsse sind nicht die letzten in der Liste) bezeichnet. Da uns jetzt auch teilweises Schließen zur Verfügung steht, ist es nur logisch, ebenfalls die Umkehrung einer Position einzuführen. Eine Umkehrung tritt dann auf, wenn ein Gegen-Abschluss mit einer Größe ausgeführt wird, die die aktuelle Position übersteigt - also ein in/out Abschluss,

Daher haben wir die zuvor eröffneten Handel geschlossen (um die Position umzukehren):

trade[ 0 ]  in 0.1  sell 1.22218 2010.06.14 13:33   out 1.22261 2010.06.14 13:36

Das Restvolumen ist 0,1 Posten und wird zur Eröffnung des neuen Handels verwendet:

trade[ 1 ]  in 0.1  buy  1.22261 2010.06.14 13:36

Nun folgt Abschluss[ 2 ] mit der Richtung in, zur Eröffnung eines weiteren Handels:

trade[ 2 ]  in 0.1  buy  1.22337 2010.06.14 13:39

Und schließlich der Abschluss, der die Position schließt - Abschluss[ 3 ] schließt alle Handel in der Position, die bislang noch offen sind.

trade[ 1 ]  in 0.1  buy  1.22261 2010.06.14 13:36   out 1.22310 2010.06.14 13:41
trade[ 2 ]  in 0.1  buy  1.22337 2010.06.14 13:39   out 1.22310 2010.06.14 13:41

Die o.g. Beschreibung verdeutlicht die Quintessenz, der von Bulachev verwendeten Interpretation: jeder offene Handel hat einen gewissen Einstiegspunkt und einen gewissen Ausstiegspunkt, und er hat sein Volumen und seine Art. Doch dieses Interpretationssystem berücksichtigt eine Nuance eben nicht - das teilweise Schließen. Sieht man genauer hin, erkennt man, dass die Anzahl der Handel gleich der Anzahl der in Abschlüsse ist (unter Berücksichtigung der In/Out Abschlüsse). In diesem Fall sollte man nach in Abschlüssen interpretieren. Doch beim teilweisen Schließen wird es mehr out Abschlüsse geben (es kann aber auch die Situation eintreten, wo die Anzahl der In- und Out-Abschlüsse gleich ist, sie sich jedoch in Bezug auf ihr Volumen unterscheiden).

Um alle out Abschlüsse zu verarbeiten, sollten wir nach out Abschlüssen interpretieren. Und dieser Widerspruch scheint unlösbar. wenn wir die Abschlüsse separat verarbeiten - also zuerst alle in und danach alle out Abschlüsse (oder umgekehrt). Doch verarbeiten wir die Abschlüsse sequentiell. und wenden bei jedem Abschluss eine bestimmte Verarbeitungsregel an, dann kommt es zu keinen Widersprüchen mehr.

Hier folgt ein Beispiel, wo die Anzahl der out Abschlüsse größer ist als die der in Abschlüsse (mit Beschreibung):

interpretation in МТ-5
deal[ 0 ]  in      0.3   sell      1.22133   2010.06.15 08:00
deal[ 1 ]  out     0.2   buy       1.22145   2010.06.15 08:01
deal[ 2 ]  in/out  0.4   buy       1.22145   2010.06.15 08:02
deal[ 3 ]  in/out  0.4   sell      1.22122   2010.06.15 08:03
deal[ 4 ]  out     0.1   buy       1.2206    2010.06.15 08:06
interpretation by Bulachev                                       
trade[ 0 ]  in 0.2  sell    1.22133 2010.06.15 08:00   out 1.22145 2010.06.15 08:01   
trade[ 1 ]  in 0.1  sell    1.22133 2010.06.15 08:00   out 1.22145 2010.06.15 08:02   
trade[ 2 ]  in 0.3  buy     1.22145 2010.06.15 08:02   out 1.22122 2010.06.15 08:03   
trade[ 3 ]  in 0.1  sell    1.22122 2010.06.15 08:03   out 1.2206  2010.06.15 08:06    

Folgende Situation: ein schließender Abschluss kommt vor einem eröffnenden Abschluss, besitzt jedoch nicht das ganze Volumen, sondern nur einen Teil (0,3 Posten sind eröffnet und 0,2 sind geschlossen). Was macht man jetzt? Wird jeder Handel mit demselben Volumen geschlossen, kann man die Situation als Eröffnung mehrerer Handel mit einem einzelnen Abschluss betrachten. Sie haben daher alle die gleichen Eröffnungspunkte und unterschiedlichen Abschlusspunkte (es ist klar, dass das Volumen jedes Handels durch sein Abschlussvolumen bestimmt ist). Nehmen wir z.B. Abschluss[ 0 ] für eine Verarbeitung und öffnen den Handel:

trade[ 0 ]  in 0.3  sell 1.22133 2010.06.15 08:00

Dann wählen wir Abschluss[ 1 ], schließen den offenen Handel, und finden während des Schließens heraus, dass das Volumen nicht ausreicht., Jetzt legt man eine Kopie des zuvor eröffneten Handels an und gibt in seinem "Volumen"-Parameter genau das fehlende Volumen an. Anschließend schließt man den ersten Handel mit dem Abschlussvolumen (d.h. wir ändern das Volumen des ersten Handels, das bei der Eröffnung angegeben wurde, durch das Abschlussvolumen):

trade[ 0 ]  in 0.2  sell 1.22133 2010.06.15 08:00   out 1.22145 2010.06.15 08:01   
trade[ 1 ]  in 0.1  sell 1.22133 2010.06.15 08:00

So eine Umwandlung mag einem Händler nicht sinnvoll erscheinen, da er einen anderen Handel schließen möchte, jedoch nicht diesen. Egal, die Bewertung von Systemen wird als Folge einer korrekten Umwandlung nicht beeinträchtigt. Das einzige was beeinträchtigt werden kann, ist das Vertrauen des Händlers in Handel ohne Verluste in MetaTrader 4, denn dieses System der Neuberechnung zeigt jetzt alle Täuschungen auf.

Das von Bulachev in seinem Buch beschriebene System der statistischen Interpretation ist komplett emotionslos und erlaubt die ehrliche Bewertung von Entscheidungen bzgl. der Position bei Einstieg, Ausstieg und beide Verhältnisse insgesamt. Und die Möglichkeit einer Umwandlung der Interpretation (eins ins andere ohne Datenverlust) belegt klar, dass die Behauptung, ein für MetaTrader 4 entwickeltes MTS könne nicht für das Interpretationssystem des MetaTrader5 umgemodelt werden, nicht stimmt. Der einzige Verlust bei der Umwandlung der Interpretation ist, dass das Volumen nun zu unterschiedlichen Ordern gehört (MetaTrader 4). Doch wenn keine weiteren Order (im alten Begriffssinn) ausgewiesen werden müssen, dann ist das nur jeden Händlers eigene, subjektive Schätzung.


Code für die Umwandlung der Interpretation

Sehen wir uns nun den Code an sich an. Zur Vorbereitung eines Übersetzers brauchen wir das Vererbungs-Feature des OOP. Deshalb empfehle ich jedem, der sich damit noch nicht gut auskennt, den MQL5 Benutzerleitfaden zu öffnen und etwas Theorie zu studieren. Zunächst beschreiben wir also die Struktur der Interpretation eines Abschlusses (wir könnten das Schrieben des Codes ein bisschen beschleunigen, und uns diese Werte direkt mittels der Standardfunktion von MQL5 holen, doch sins sie weniger gut lesbar und könnten verwirren).

//+------------------------------------------------------------------+
//| structure of deal                                                |
//+------------------------------------------------------------------+
struct S_Stat_Deals
  {
public:
   ulong             DTicket;         // ticket of deal   
   ENUM_DEAL_TYPE     deals_type;      // type of deal
   ENUM_DEAL_ENTRY    deals_entry;     // direction of deal 
   double            deals_volume;    // volume of deal    
   double            deals_price;     // price of opening of deal   
   datetime          deals_date;      // time of opening of deal  

                     S_Stat_Deals(){};
                    ~S_Stat_Deals(){};
  };

Diese Struktur umfasst alle wichtigen Details über einen Abschluss; abgeleitete Details sind nicht enthalten, da wir sie ggf. berechnen können. Da die Entwickler bereits viele Methoden von Bulachevs Statistiken im Strategie-Tester implementiert haben, müssen wir ihn nur noch durch angepassten Methoden ergänzen. Und deshalb implementieren wir sie als die Effektivität eines Handels insgesamt ,und als die Effektivität einer Eröffnung und eines Abschlusses.

Um diese Werte zu erhalten, müssen wir die Interpretation der Primärinformationen implementieren, wie z.B. Eröffnungs-/Abschlusskurs, Eröffnungs-/Abschlusszeit, Minimum-/Maximalkurs während eines Handels. Wenn wir derartige Primärinformationen haben, können wir daraus eine Menge abgeleiteter Informationen erhalten. Ich möchte Sie hier auch auf die unten beschriebene Handelsstruktur aufmerksam machen: sie ist die Hauptstruktur auf der alle Interpretations-Umwandlungen beruhen.

//+------------------------------------------------------------------+
//| structure of trade                                               |
//+------------------------------------------------------------------+
struct S_Stat_Trades
  {
public:
   ulong             OTicket;         // ticket of opening deal
   ulong             CTicket;         // ticket of closing deal     
   ENUM_DEAL_TYPE     trade_type;     // type of trade
   double            trade_volume;    // volume of trade
   double            max_price_trade; // maximum price of trade
   double            min_price_trade; // minimum price of trade
   double            enter_price;     // price of opening of trade
   datetime          enter_date;      // time of opening of trade
   double            exit_price;      // price of closing of trade/s22>
   datetime          exit_date;       // time of closing of trade

   double            enter_efficiency;// effectiveness of entering
   double            exit_efficiency; // effectiveness of exiting
   double            trade_efficiency;// effectiveness of trade

                     S_Stat_Trades(){};
                    ~S_Stat_Trades(){};
  };


Da wir jetzt bereits zwei Hauptstrukturen erzeugt haben, definieren wir nun die neue Klasse C_Pos, die die Interpretation umwandelt. Deklarieren wir also als erstes die Zeiger für die Interpretationsstrukturen von Abschlüssen und Handel. Da diese Information bei vererbten Funktionen wichtig sein kann, sollte sie als public deklariert werden. Und da es eine Menge Abschlüsse und Handel geben kann, sollte man als Struktur-Zeiger ein Array anstelle einer Variable verwenden. Denn dann ist die Information strukturiert und von überall aus verfügbar,

Als nächstes müssen wir die History in separate Postionen unterteilen und alle Umwandlungen innerhalb einer Position wie in einem kompletten Handelszyklus ausführen. Dazu muss man die Variablen für die Interpretation der Eigenschaften der Position deklarieren (Position-ID, Symbole einer Position, Anzahl der Abschlüsse und Anzahl der Handel).

//+------------------------------------------------------------------+
//| class for transforming deals into trades                         |
//+------------------------------------------------------------------+
class C_Pos
  {
public:
   S_Stat_Deals      m_deals_stats[];  // structure of deals
   S_Stat_Trades     m_trades_stats[]; // structure of trades
   long              pos_id;          // id of position
   string            symbol;          // symbol of position
   int               count_deals;     // number of deals
   int               count_trades;    // number of trades
   int               trades_ends;     // number of closed trades
   int               DIGITS;          // accuracy of minimum volume by the symbols of position  
                     C_Pos()
     {
      count_deals=0;
      count_trades=0;
      trades_ends=0;
     };
                    ~C_Pos(){};
   void              OnHistory();         // creation of history of position
   void              OnHistoryTransform();// transformation of position history into the new system of interpretation
   void              efficiency();        // calculation of effectiveness by Bulachev's method

private:
   void              open_pos(int c);
   void              copy_pos(int x);
   void              close_pos(int i,int c);
   double            nd(double v){return(NormalizeDouble(v,DIGITS));};// normalization to minimum volume
   void              DigitMinLots(); // accuracy of minimum volume
   double            iHighest(string          symbol_name,// symbol name
                              ENUM_TIMEFRAMES  timeframe,  // period
                              datetime         start_time, // start date
                              datetime         stop_time   // end date
                              );
   double            iLowest(string          symbol_name,// symbol name
                             ENUM_TIMEFRAMES  timeframe,  // period
                             datetime         start_time, // start date
                             datetime         stop_time   // end date
                             );
  };


Die Klasse besitzt drei public Methoden, die die Positionen verarbeiten.

OnHistory() erzeugt die Positions-History:
//+------------------------------------------------------------------+
//| filling the structures of history deals                          |
//+------------------------------------------------------------------+
void C_Pos::OnHistory()
  {
   ArrayResize(m_deals_stats,count_deals);
   for(int i=0;i<count_deals;i++)
     {
      m_deals_stats[i].DTicket=HistoryDealGetTicket(i);
      m_deals_stats[i].deals_type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(m_deals_stats[i].DTicket,DEAL_TYPE);   // type of deal
      m_deals_stats[i].deals_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(m_deals_stats[i].DTicket,DEAL_ENTRY);// direction of deal
      m_deals_stats[i].deals_volume=HistoryDealGetDouble(m_deals_stats[i].DTicket,DEAL_VOLUME);              // volume of deal
      m_deals_stats[i].deals_price=HistoryDealGetDouble(m_deals_stats[i].DTicket,DEAL_PRICE);                // price of opening
      m_deals_stats[i].deals_date=(datetime)HistoryDealGetInteger(m_deals_stats[i].DTicket,DEAL_TIME);        // time of opening
     }
  };

Die Methode erzeugt für jeden Abschluss eine Kopie der Struktur und befüllt diese mit der Information zum Abschluss. Und genau das habe ich gemeint, als ich vorhin sagte, dass wir auch ohne sie auskommen können, dass es jedoch mit ihr viel bequemer ist (diejenigen, die jede Zeitersparnis in Mikrosekunden berechnen, ersetzen den Aufruf dieser Strukturen einfach durch die Zeile, die rechts neben dem "=" Zeichen steht).

OnHistoryTransform() wandelt die Positions-History in das neue Interpretationssystem um:

Ich empfehle diese Methode überall dort einzusetzen, wo es nötig ist - Zuweisung des Memorys, jedes Mal wenn ein neues Interpretationsobjekt auftaucht. Wird keine präzise Information über die Memory-Menge verlangt, müssen wir ihm einem ungefähren Wert zuweisen. Das geht auf jeden Fall schneller, als bei jedem Schritt das gesamte Array erneut zuweisen zu müssen.

Danach folgt die Schleife, in der alle Abschlüsse einer Position mit Hilfe von drei Filtern gefiltert werden: ob der der Abschluss in, in/out oder out ist. Für jede Variante werden spezifische Handlungen implementiert. Die Filter sind sequentiell und verschachtelt. Wenn also ein Filter false liefert, dann, und nur dann, prüfen wir den nächsten Filter. Eine derartige Bauweise ist bei Ressourcen wirtschaftlich, da alle unnötigen Handlungen unterbunden werden. Für eine bessere Lesbarkeit des Codes werden viele Handlungen den in der Klasse als private Funktionen deklarierten Funktionen übertragen. Ach übrigens: Während der Entwicklung waren diese Funktionen public, doch ich habe später gemerkt habe, dass sie in anderen Teilen des Codes nicht gebraucht werden, und daher wurden sie als private um-deklariert. So leicht kann man den Geltungsbereich von Daten in OOP verändern.

Also: Im in Filter wird die Erzeugung eines neuen Handels ausgeführt (die open_pos() Funktion). Deshalb erhöhen wir die Größe des Zeiger-Arrays um 1 und kopieren die Struktur des Abschlusses in die entspr. Felder der Struktur des Handels. Und da überdies die Struktur des Handels zweimal mehr Felder für Kurs und Zeit hat, werden nur Eröffnungsfelder bei der Eröffnung eines Handels befüllt, damit er als nicht abgeschlossen gilt. Dies wird angesichts der Differenz von count_trades und trades_ends einsichtig. Worum es hier geht ist, dass die Zähler zu Beginn Null-Werte haben. Sobald ein Handel auftaucht, erhöht sich der count_trades Zähler; wird ein Handel abgeschlossen, erhöht sich der trades_ends Zähler. Daher zeigt die Differenz zwischen count_trades und trades_ends an, wie viele Handel zu jedem beliebigen Zeitpunkt eben nicht abgeschlossen sind.

Die open_pos() Funktion ist ziemlich einfach: sie öffnet nur Handel und löst den entspr. Zähler aus. Andere ähnliche Funktionen sind nicht so einfach. Wenn also ein Abschluss kein in Abschluss ist, kann er entweder ein in/out oder out Abschluss sein. Von diesen zwei Varianten sollte zunächst diejenige überprüft werden, die leichter ausgeführt wird (zwar kein grundlegendes Problem, doch habe ich die Prüfung in der Reihenfolge zunehmender Schwierigkeit ihrer Ausführung angelegt).

Die Funktion, die den in/out Filter verarbeitet, summiert die offenen Positionen nach allen nicht abgeschlossen Handel (ich habe bereits darauf hingewiesen, wie man mittels der Differenz zwischen count_trades und trades_ends herausfindet, welche Handel noch offen sind). Also berechnen wir das Gesamtvolumen, das durch jeden gegebenen Abschluss geschlossen wird (und das restliche Volumen wird erneut eröffnet, doch mit der Art des aktuellen Abschlusses). Hier muss darauf hingewiesen werden, dass der Abschluss die in/out Richtung hat, d.h. sein Volumen nicht das Gesamtvolumen aller zuvor eröffneten Positionen übersteigt. Nur logisch also, die Differenz zwischen der Position und des in/out Abschlusses zu berechnen, um das Volumen des neuen Handels zu kennen, der erneut eröffnet werden soll.

Weist ein Abschluss die out Richtung auf, wird es leider noch komplizierter. Zunächst besitzt der letzte Abschluss in einer Position immer die out Richtung, also sollten wir hier mal eine Ausnahme machen - ist es der letzte Abschluss, wird alles was wir haben, geschlossen. Ansonsten (wenn der Abschluss nicht der letzte ist), sind zwei Varianten möglich. Da der Abschluss keine in/out Richtung besitzt, sondern ein out-Abschluss ist, ergeben sich folgende Varianten: die erste Variante ist exakt die gleiche wie die Eröffnungs-Variante, d.h. das Volumen des Eröffnungs-Abschlusses ist gleich dem Volumen des schließenden Abschlusses; Variante 2 ist: beide Volumen sind nicht gleich.

Die erste Variante wird durch Schließen verarbeitet. Die zweite Variante ist komplizierter - hier gibt es wieder zwei Möglichkeiten: entweder das Volumen ist größer oder das Volumen ist geringer als das der Eröffnung. Ist das Volumen größer, schließen wir den nächsten Handel, bis das Volumen des Schließens gleich oder größer wird als das Volumen des Öffnens. Reicht das Volumen für einen Schließen des kompletten nächsten Handels nicht aus (weniger Volumen), dann tritt teilweises Schließen in Kraft. Hier müssen wir den Handel mit dem neuen Volumen schließen (dasjenige, das nach vorherigen Operationen noch übrig ist). Doch zuvor sollte man eine Kopie des Handels mit dem fehlenden Volumen anlegen. Und natürlich die Zähler nicht vergessen!

Beim Handeln kann oft eine Situation auftreten, in der nach der Wiedereröffnung eines Handels bereits spätere Handel für teilweises Schließen in der Warteschlange stehen. Um hier Verwirrung zu vermeiden, sollten sie alle um einen verschoben werden, damit die Chronologie des Schließens beibehalten wird.

//+------------------------------------------------------------------+
//| transformation of deals into trades (engine classes)             |
//+------------------------------------------------------------------+
void C_Pos::OnHistoryTransform()
  {
   DigitMinLots();// fill the DIGITS value
   count_trades=0;trades_ends=0;
   ArrayResize(m_trades_stats,count_trades,count_deals);
   for(int c=0;c<count_deals;c++)
     {
      if(m_deals_stats[c].deals_entry==DEAL_ENTRY_IN)
        {
         open_pos(c);
        }
      else// else in
        {
         double POS=0;
         for(int i=trades_ends;i<count_trades;i++)POS+=m_trades_stats[i].trade_volume;

         if(m_deals_stats[c].deals_entry==DEAL_ENTRY_INOUT)
           {
            for(int i=trades_ends;i<count_trades;i++)close_pos(i,c);
            trades_ends=count_trades;
            open_pos(c);
            m_trades_stats[count_trades-1].trade_volume=m_deals_stats[c].deals_volume-POS;
           }
         else// else in/out
           {
            if(m_deals_stats[c].deals_entry==DEAL_ENTRY_OUT)
              {
               if(c==count_deals-1)// if it's the last deal
                 {
                  for(int i=trades_ends;i<count_trades;i++)close_pos(i,c);
                  trades_ends=count_trades-1;
                 }
               else// if it's not the last deal
                 {
                  double out_vol=nd(m_deals_stats[c].deals_volume);
                  while(nd(out_vol)>0)
                    {
                     if(nd(out_vol)>=nd(m_trades_stats[trades_ends].trade_volume))
                       {
                        close_pos(trades_ends,c);
                        out_vol-=nd(m_trades_stats[trades_ends].trade_volume);
                        trades_ends++;
                       }
                     else// if the remainder of closed position is less than the next trade
                       {
                        // move all trades forward by one
                        count_trades++;
                        ArrayResize(m_trades_stats,count_trades);
                        for(int x=count_trades-1;x>trades_ends;x--)copy_pos(x);
                        // open a copy with the volume equal to difference of the current position and the remainder
                        m_trades_stats[trades_ends+1].trade_volume=nd(m_trades_stats[trades_ends].trade_volume-out_vol);
                        // close the current trade with new volume, which is equal to remainder
                        close_pos(trades_ends,c);
                        m_trades_stats[trades_ends].trade_volume=nd(out_vol);
                        out_vol=0;
                        trades_ends++;
                       }
                    }// while(out_vol>0)
                 }// if it's not the last deal
              }// if out
           }// else in/out
        }// else in
     }
  };


Berechnung der Effektivität

Ist das Interpretationssystem umgewandelt, können wir die Effektivität der Handel gemäß Bulachevs Methode durchführen. Die für eine solche Bewertung erforderlichen Funktionen befinden sich in der efficiency() Methode, und das Befüllen der Handelsstruktur mit den berechneten Daten findet ebenfalls dort statt. Die Effektivität von Einstieg und Ausstieg wird von 0 - 1 und für den gesamten Handel von -1 - 1gemessen.

//+------------------------------------------------------------------+
//| calculation of effectiveness                                     |
//+------------------------------------------------------------------+
void C_Pos::efficiency()
  {
   for(int i=0;i<count_trades;i++)
     {
      m_trades_stats[i].max_price_trade=iHighest(symbol,PERIOD_M1,m_trades_stats[i].enter_date,m_trades_stats[i].exit_date); // maximal price of trade
      m_trades_stats[i].min_price_trade=iLowest(symbol,PERIOD_M1,m_trades_stats[i].enter_date,m_trades_stats[i].exit_date);  // minimal price of trade
      double minimax=0;
      minimax=m_trades_stats[i].max_price_trade-m_trades_stats[i].min_price_trade;// difference between maximum and minimum
      if(minimax!=0)minimax=1.0/minimax;
      if(m_trades_stats[i].trade_type==DEAL_TYPE_BUY)
        {
         //Effectiveness of entering a position
         m_trades_stats[i].enter_efficiency=(m_trades_stats[i].max_price_trade-m_trades_stats[i].enter_price)*minimax;
         //Effectiveness of exiting from a position
         m_trades_stats[i].exit_efficiency=(m_trades_stats[i].exit_price-m_trades_stats[i].min_price_trade)*minimax;
         //Effectiveness of trade
         m_trades_stats[i].trade_efficiency=(m_trades_stats[i].exit_price-m_trades_stats[i].enter_price)*minimax;
        }
      else
        {
         if(m_trades_stats[i].trade_type==DEAL_TYPE_SELL)
           {
            //Effectiveness of entering a position
            m_trades_stats[i].enter_efficiency=(m_trades_stats[i].enter_price-m_trades_stats[i].min_price_trade)*minimax;
            //Effectiveness of exiting from a position
            m_trades_stats[i].exit_efficiency=(m_trades_stats[i].max_price_trade-m_trades_stats[i].exit_price)*minimax;
            //Effectiveness of trade
            m_trades_stats[i].trade_efficiency=(m_trades_stats[i].enter_price-m_trades_stats[i].exit_price)*minimax;
           }
        }
     }
  }


Diese Methode verwendet zwei private Methoden: iHighest() und iLowest(), Sie ähneln sich sehr, ihr einziger Unterschied liegt in den angefragten Daten und der Suchfunktion fmin oder fmax.

//+------------------------------------------------------------------+
//| searching maximum within the period start_time --> stop_time     |
//+------------------------------------------------------------------+
double C_Pos::iHighest(string           symbol_name,// symbols name
                       ENUM_TIMEFRAMES  timeframe,  // period
                       datetime         start_time, // start date
                       datetime         stop_time   // end date
                       )
  {
   double  buf[];
   datetime  start_t=(start_time/60)*60;// normalization of time of opening
   datetime  stop_t=(stop_time/60+1)*60;// normaliztion of time of closing  
   int period=CopyHigh(symbol_name,timeframe,start_t,stop_t,buf);
   double res=buf[0];
   for(int i=1;i<period;i++)
      res=fmax(res,buf[i]);
   return(res);
  }

Diese Methode sucht nach dem Maximum innerhalb der Zeitspanne zwischen zwei festgelegter Daten, die dann an die Funktion als start_time und stop_time Parameter übertragen werden. Und da die Handelsdaten an die Funktion übertragen werden und eine Handels-Anfrage sogar in der Mitte eines 1-Minuten Bars auftauchen kann, werden die Daten auf den nächsten Wert des Bars innerhalb der Funktion normalisiert. Das gleiche passiert auch in der iLowest() Funktion. Mit der nun entwickelten efficiency()-Methode haben wir die komplette Funktionalität vor uns, um mit einer Position arbeiten zu können. Die Position an sich ist bislang allerdings noch nicht angerührt worden. Dies kann durch die Festlegung einer neuen Klasse übergangen werden, der alle vorherigen Methoden zur Verfügung stehen, daher muss diese Klasse als ein Derivativ der C_Pos deklariert werden.


Derivative Klasse (Maschinenklassen)

class C_PosStat:public C_Pos

Zur Berücksichtigung von statistischer Information muss eine Struktur erzeugt werden, die der neuen Klasse gegeben wird, und natürlich

//+------------------------------------------------------------------+
//| structure of effectiveness                                       |
//+------------------------------------------------------------------+
struct S_efficiency

  {
   double            enter_efficiency; // effectiveness of entering
   double            exit_efficiency;  // effectiveness of exiting
   double            trade_efficiency; // effectiveness of trade
                     S_efficiency()
     {
      enter_efficiency=0;
      exit_efficiency=0;
      trade_efficiency=0;
     };
                    ~S_efficiency(){};
  };


der Körper der Klasse selbst:

//+------------------------------------------------------------------+
//| class of statistics of trade in whole                            |
//+------------------------------------------------------------------+
class C_PosStat:public C_Pos
  {
public:
   int               PosTotal;         // number of positions in history
   C_Pos             pos[];            // array of pointers to positions
   int               All_count_trades; // total number of trades in history
   S_efficiency      trade[];          // array of pointers to the structure of effectiveness of entering, exiting and trades
   S_efficiency      avg;              // pointer to the structure of average value of effectiveness of entering, exiting and trades
   S_efficiency      stdev;            // pointer to the structure of standard deviation from
                                       // average value of effectiveness of entering, exiting and trades

                     C_PosStat(){PosTotal=0;};
                    ~C_PosStat(){};
   void              OnPosStat();                         // engine classes
   void              OnTradesStat();                      // gathering information about trades into the common array
   
   // functions of writing information to a file
   void              WriteFileDeals(string folder="deals");
   void              WriteFileTrades(string folder="trades");
   void              WriteFileTrades_all(string folder="trades_all");
   void              WriteFileDealsHTML(string folder="deals");
   void              WriteFileDealsHTML2(string folder="deals");
   void              WriteFileTradesHTML(string folder="trades");
   void              WriteFileTradesHTML2(string folder="trades");
   string            enum_translit(ENUM_DEAL_ENTRY x,bool latin=true);// transformation of enumeration into string
   string            enum_translit(ENUM_DEAL_TYPE x,bool latin=true); 
                                                              // transformation of enumeration into string (overloaded)
private:   

   S_efficiency      AVG(int count);                                        // arithmetical mean
   S_efficiency      STDEV(const S_efficiency &mo,int count);               // standard deviation
   S_efficiency      add(const S_efficiency &a,const S_efficiency &b);      //add
   S_efficiency      take(const S_efficiency &a,const S_efficiency &b);     //subtract
   S_efficiency      multiply(const S_efficiency &a,const S_efficiency &b); //multiply
   S_efficiency      divided(const S_efficiency &a,double b);               //divide
   S_efficiency      square_root(const S_efficiency &a);                    //square root
   string            Head_style(string title);
  };  


Mein Vorschlag: eine Analyse dieser Klasse in umgekehrter Richtung, also vom Ende bis zum Anfang. Denn alles endet mit dem Schreiben einer Abschluss- und Handel-Tabelle in Dateien. Dazu wird eine Reihe von Funktionen geschrieben (ihr jeweiliger Zweck lässt sich aus dem jeweiligen Namen der Funktion ablesen). Diese Funktionen erstellen einen csv-Bericht zu Abschlüssen und Handel sowie auch html-Berichte zweierlei Arten (unterscheiden sich nur vom Aussehen her, ihr Inhalt ist gleich).

      void              WriteFileDeals();      // writing csv report on deals
      void              WriteFileTrades();     // writing csv report on trade
      void              WriteFileTrades_all(); // writing summary csv report of fitness functions
      void              WriteFileDealsHTML2(); // writing html report on deals, 1 variant
      void              WriteFileTradesHTML2();// writing html report on trades, 2 variant

Die enum_translit() Funktion übernimmt die Umwandlung der Werte der Aufzählungen in String-Typen, um sie in die Log-Datei zu schreiben. Der private Bereich enthält mehrere Funktionen der S_efficiency-Struktur. All diese Funktionen bügeln die Nachteile der Sprache aus, insb. die arithmetischen Operationen mit Strukturen. Da die Meinungen zur Implementierung dieser Methoden auseinander gehen, können sie auf unterschiedliche Art und Weise realisiert werden. Ich habe mich für Methoden arithmetischer Operationen an den Strukturfeldern entschieden. Klar kann man jetzt sagen, es sei besser jedes Strukturfeld einzeln mit Hilfe einer individuellen Methode zu verarbeiten. Hier gilt wahrscheinlich, dass es so viele Meinungen gibt wie Programmierer. Ich hoffe sehr, dass wir in Zukunft derartige Operationen mit Hilfe eingebauter Methoden durchführen können.

Die AVG() Methode berechnet den arithmetischen Mittelwert eines übertragenen Arrays, zeigt jedoch nicht das gesamte Bild der Verteilung an. Deswegen wird sie durch eine andere Methode, der STDEV()-Methode, die die Standard-Abweichungen berechnet, unterstützt. Die OnTradesStat() Funktion holt sich die Werte der Effektivität (zuvor in OnPosStat() berechnet) und verarbeitet sie mit Statistikmethoden. Und zu guter Letzt, die Hauptfunktion der Klasse - OnPosStat().

Diese Funktion sollte man sich detaillierter ansehen. Sie besteht aus zwei Teilen und kann daher leicht unterteilt werden. Ihr erster Teil sucht nach allen Positionen und verarbeitet ihre ID via Speichern in das temporäre Array id_pos. Gehen wir Schritt für Schritt vor: die gesamte, vorhandene History auswählen, die Anzahl der Abschlüsse berechnen und den Verarbeitungszyklus der Abschlüsse ablaufen lassen. Verarbeitungszyklus: handelt es sich um einen Abschluss der 'Saldo'-Art, kann er übersprungen werden (ein Start-Abschluss muss nicht interpretiert werden). Ansonsten bitte die Positions-ID in der Variable speichern und die Suche ausführen, Gibt es die gleiche ID in der Basis bereits (das id_pos Array), zum nächsten Abschluss gehen; ansonsten die ID in die Basis schreiben. Auf diese Art haben wir nach der Verarbeitung aller Abschlüsse ein Array, das mit den bestehendenID s der Positionen und ihrer Anzahl befüllt ist.

   long  id_pos[];// auxiliary array for creating the history of positions
   if(HistorySelect(0,TimeCurrent()))
     {
      int HTD=HistoryDealsTotal();
      ArrayResize(id_pos,PosTotal,HTD);
      for(int i=0;i<HTD;i++)
        {
         ulong DTicket=(ulong)HistoryDealGetTicket(i);
         if((ENUM_DEAL_TYPE)HistoryDealGetInteger(DTicket,DEAL_TYPE)==DEAL_TYPE_BALANCE)
            continue;// if it's a balance deal, skip it
         long id=HistoryDealGetInteger(DTicket,DEAL_POSITION_ID);
         bool present=false; // initial state, there's no such position           
         for(int j=0;j<PosTotal;j++)
           { if(id==id_pos[j]){ present=true; break; } }// if such position already exists break

         if(!present)// write id as a new position appears
           {
            PosTotal++;
            ArrayResize(id_pos,PosTotal);
            id_pos[PosTotal-1]=id;
           }
        }
     }
   ArrayResize(pos,PosTotal);

Ihr zweiter Teil zeigt uns alle zuvor in der Basis-Klasse C_Pos beschriebenen Methoden. Sie besteht aus einem Zyklus, der die Positionen durchgeht und die entsprechenden Methoden zur Verarbeitung der Positionen ablaufen lässt. Eine Beschreibung der Methode findet sich im unten stehenden Code,

   for(int p=0;p<PosTotal;p++)
     {
      if(HistorySelectByPosition(id_pos[p]))// select position
        {
         pos[p].pos_id=id_pos[p]; // assigned id of position to the corresponding field of the class C_Pos
         pos[p].count_deals=HistoryDealsTotal();// assign the number of deal in position to the field of the class C_Pos
         pos[p].symbol=HistoryDealGetString(HistoryDealGetTicket(0),DEAL_SYMBOL);// the same actions with symbol
         pos[p].OnHistory();          // start filling the structure sd with the history of position
         pos[p].OnHistoryTransform(); // transformation of interpretation, filling the structure st.
         pos[p].efficiency();         // calculation of the effectiveness of obtained data
         All_count_trades+=pos[p].count_trades;// save the number of trades for displaying the total number
        }
     }


Aufruf der Methoden der Klasse

Jetzt haben wir also die komplette Klasse behandelt. Bleibt noch, ein Beispiel eines Aufrufs zu präsentieren. Um alle Möglichkeiten des Bauens offen zuhalten, habe ich den Aufruf absichtlich nicht in einer Funktion deklariert. Zusätzlich können Sie die Klasse für Ihren Bedarf erweitern und neue Methoden statistischer Datenverarbeitung implementieren. Dies ist ein Beispiel des Aufrufs der Methode der Klasse aus dem Script:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
#include <Bulaschev_Statistic.mqh> void OnStart()
  {
   C_PosStat  start;
   start.OnPosStat();
   start.OnTradesStat();
   start.WriteFileDeals();
   start.WriteFileTrades();
   start.WriteFileTrades_all();
   start.WriteFileDealsHTML2();
   start.WriteFileTradesHTML2();
   Print("cko tr ef=" ,start.stdev.trade_efficiency);
   Print("mo  tr ef=" ,start.avg.trade_efficiency);
   Print("cko out ef=",start.stdev.exit_efficiency);
   Print("mo  out ef=",start.avg.exit_efficiency);
   Print("cko in ef=" ,start.stdev.enter_efficiency);
   Print("mo  in ef=" ,start.avg.enter_efficiency);
  }

Das Script erzeugt 5 Berichtsdateien, gemäß der Zahl der Funktionen, die Daten in die Datei im Files\OnHistory Directory schreiben. Hier findet man die folgenden Hauptfunktionen: OnPosStat() und OnTradesStat(). Mit ihnen werden alle notwendigen Methoden aufgerufen. Das Script schließt mit dem Ausdruck des erhaltenen Effektivitätswerts des Handels insgesamt. Jeder einzelne Wert kann für eine genetische Optimierung genutzt werden.

Da es nicht nötig ist, jeden Bericht währen der Optimierung in eine Datei zu schreiben, sieht der Aufruf der Klasse in einem Expert Advisor etwas anders aus. Zunächst kann ein Expert Advisor, im Gegensatz zu einem Script, im Tester laufen gelassen werden (dafür haben wir ihn ja auch vorbereitet). Doch die Arbeit im Strategie-Tester hat so ihre Besonderheiten. Bei der Optimierung haben wir Zugriff auf die OnTester() Funktion, da sie hier vor der Ausführung der OnDeinit() Funktion ausgeführt wird. Daher kann der Aufruf der Hauptmethoden der Umwandlung getrennt werden. Aus Gründen einer bequemen Modifizierung der Eignungsfunktion von den Parametern eines Expert Advisors aus, habe ich eine Aufzählung gobal deklariert und nicht als Teil der Klasse. So steht diese Aufzählung auf dem gleichen Blatt wie die Methoden der Klasse C_PosStat.

//+------------------------------------------------------------------+
//| enumeration of fitness functions                                 |
//+------------------------------------------------------------------+
enum Enum_Efficiency
  {
   avg_enter_eff,
   stdev_enter_eff,
   avg_exit_eff,
   stdev_exit_eff,
   avg_trade_eff,
   stdev_trade_eff
  };

 Und das sollte der Überschrift des Expert Advisors hinzugefügt werden.

#include <Bulaschev_Statistic.mqh>
input Enum_Efficiency result=0;// Fitness function


Jetzt können wir die Übertragung der notwendigen Parameter mittels des Switch Operators beschreiben.

//+------------------------------------------------------------------+
//| Expert optimization function                                     |
//+------------------------------------------------------------------+
double OnTester()
  {
   start.OnPosStat();
   start.OnTradesStat();
   double res;
   switch(result)
     {
      case 0: res=start.avg.enter_efficiency;    break;
      case 1: res=-start.stdev.enter_efficiency; break;
      case 2: res=start.avg.exit_efficiency;     break;
      case 3: res=-start.stdev.exit_efficiency;  break;
      case 4: res=start.avg.trade_efficiency;    break;
      case 5: res=-start.stdev.trade_efficiency; break;
      default : res=0; break;
     }  
   return(res);
  }

Hier möchte ich besonders darauf hinweisen. dass die OnTester() Funktion zur Maximierung der angepassten Funktion verwendet wird. Wenn Sie also das Minimum der angepassten Funktion finden müssen, empfiehlt es sich, die Funktion als solches umzukehren und mit -1 zu multiplizieren. Wie im Beispiel mit der Standardabweichung, versteht hier jeder, dass je kleiner die stdev (Standardabweichung) ist, umso geringer auch die Differenz zwischen der Effektivität von Handel ist, und somit ihre Stabilität höher. Daher sollte stdev minimiert werden. Wir haben uns jetzt mit dem Aufruf der Klassenmethode beschäftigt, daher werfen wir nun einen Blick auf das Schreiben von Berichten in eine Datei.

Ich habe vorhin die Klassenmethoden erwähnt, die den Bericht erzeugen. Sehen wir uns jetzt an, wo und wann man sie aufrufen sollte. Die Berichte sollten nur dann erzeugt werden, wenn der EA für einen einzigen Ablauf gestartet wird. Denn sonst erzeugt der Expert Advisor die Dateien im Optimierungs-Modus, d.h. anstatt einer Datei erzeugt er eine Menge Dateien (wenn unterschiedliche Dateinamen jedes Mal übertragen werden) oder nur eine, doch dies ist dann die letzte mit demselben Namen für alle Durchläufe. Und das ist absolut bedeutungslos, da für Informationen, die später sowieso gelöscht werden, hier nur Ressourcen verschwendet werden.

Also, bitte keine Berichtsdateien während der Optimierung erzeugen. Sollten Sie viele Dateien mit unterschiedlichen Namen erhalten, dann wollen Sie die meisten sowieso nicht öffnen. Die zweite Variante verschwendet eben nur Unmengen Ressourcen zum Erhalt von Informationen, die sofort wieder gelöscht wird.

Daher ist es am besten einen Filter anzulegen (den Bericht also nur im Optimierung [deaktiviert] Modus starten). Dadurch wird die Festplatte des Rechners nicht mit Berichten, die sich niemand jemals ansieht, zugemüllt. Zudem erhöht sich auch die Optimierungsgeschwindigkeit (jeder weiß doch, dass die langsamsten Abläufe Datei-Abläufe sind). Also bleibt die Möglichkeit, schnell einen Bericht mit den notwendigen Parametern zu erhalten, gewahrt. Es spielt in der Tat keine Rolle, wo man den Filter platziert - entweder in der OnTester oder der OnDeinit Funktion. Das Wichtigste ist, dass dies die Klassenmethoden sind, die den Bericht erzeugen, und sie daher nach den Hauptmethoden, die die Umwandlung ausführen, aufgerufen werden sollten. Ich habe den Filter in OnDeinit() platziert, um den Code nicht zu überladen:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(!(bool)MQL5InfoInteger(MQL5_OPTIMIZATION))
     {
      start.WriteFileDeals();      // writing csv report on deals
      start.WriteFileTrades();     // writing csv report on trades
      start.WriteFileTrades_all(); // writing summary csv report on fitness functions
      start.WriteFileDealsHTML2(); // writing html report on deals
      start.WriteFileTradesHTML2();// writing html report on trades
     }
  }
//+------------------------------------------------------------------+

Die Abfolge des Aufrufs der Methoden ist ebenfalls unwichtig. Alles was zur Erstellung von Berichten erforderlich ist, wird in den OnPosStat und OnTradesStat Methoden vorbereitet. Es spielt ebenfalls keine Rolle, ob Sie alle Methoden zur Berichtsverfassung aufrufen oder nur einige. Jede Methode wird separat betrieben, es handelt sich nur um die Interpretation von Informationen, die bereits in der Klasse gespeichert sind.

Prüfung im Strategie-Tester

Unten stehen die Ergebnisse eines einzigen Testlaufs im Strategie-Tester:

Handels-Bericht Gleitende Mittelwert-Statistik
# Ticket Art Volumen Eröffnung Schluss Kurs Effizienz
offen geschlossen Kurs Zeit Kurs Zeit max min Einstieg Ausstieg Handel
Pos[0] ID 2 EURUSD
0 2 3 Buy 0,1 1,37203 15.03.2010 13:00:00 1,37169 15.03.2010 14:00:00 1,37236 1,37063 0,19075 0,61272 -0,19653
Pos[1] ID 4 EURUSD
1 4 5 Sell 0,1 1,35188 23.03.2010 08:00:00 1,35243 23.03.2010 10:00:00 1,35292 1,35025 0,61049 0,18352 -0,20599
Pos[2] ID 6 EURUSD
2 6 7 Sell 0,1 1,35050 23.03.2010 12:00:00 1,35343 23.03.2010 16:00:00 1,35600 1,34755 0,34911 0,30414 -0,34675
Pos[3] ID 8 EURUSD
3 8 9 Sell 0,1 1,35167 23.03.2010 18:00:00 1,33343 2010,03,26 05:00:00 1,35240 1,32671 0,97158 0,73842 0,71000
Pos[4] ID 10 EURUSD
4 10 11 Sell 0,1 1,34436 30.03.2010 16:00:00 1,33616 08.04.2010 23:00:00 1,35904 1,32821 0,52384 0,74213 0,26597
Pos[5] ID 12 EURUSD
5 12 13 Buy 0,1 1,35881 13.04.2010 08:00:00 1,35936 13.04.2010 15 10:00:00 1,36780 1,35463 0,68261 0,35915 0,04176
Pos[6] ID 14 EURUSD
6 14 15 Sell 0,1 1,34735 13.04.2010 20 04:00:00 1,34807 13.04.2010 20 10:00:00 1,34890 1,34492 0,61055 0,20854 -0,18090
Pos[7] ID 16 EURUSD
7 16 17 Sell 0,1 1,34432 13.04.2010 20 18:00:00 1,33619 13.04.2010 23 17:00:00 1,34491 1,32016 0,97616 0,35232 0,32848
Pos[8] ID 18 EURUSD
8 18 19 Sell 0,1 1,33472 13.04.2010 27 10:00:00 1,32174 13.04.2010 29 05:00:00 1,33677 1,31141 0,91916 0,59267 0,51183
Pos[9] ID 20 EURUSD
9 20 21 Sell 0,1 1,32237 03.05.2010 04:00:00 1,27336 07.05.2010 20:00:00 1,32525 1,25270 0,96030 0,71523 0,67553

Effektivitäts-Bericht
Eignungs-Funktion Mittelwert Standardabweichung
Einstieg 0,68 0,26
Ausstieg 0,48 0,21
Handel 0,16 0,37


Und so sieht der Saldo-Graph aus:


Man erkennt auf dem Chart eindeutig, dass die angepasste Optimierungsfunktion nicht versucht, die Parameter mit den größeren Mengen an Abschlüssen zu wählen, sondern die Abschlüsse mit längerer Dauer, denn da haben die Abschlüsse fast den gleichen Gewinn, d.h. die Sprünge sind nicht so hoch.

Da der Moving Averages Code die Merkmale des ansteigenden Volumens der Position oder ihres teilweisen Abschlusses nicht enthält, scheint das Ergebnis der Umwandlung nicht sehr nahe an dem zu liegen, was ich oben beschrieben habe. Doch wenn Sie das Script im Account starten, der extra zum Testen der Codes eröffnet wude, dann ergibt sich ein anderes Ergebnis. Und das sehen Sie hier:

Pos[286] ID 1019514 EURUSD
944 1092288 1092289 Buy 0,1 1,26733 08.07.2010 21:14:49 1,26719 08.07.2010 21:14:57 1,26752 1,26703 0,38776 0,32653 -0,28571
Pos[287] ID 1019544 EURUSD
945 1092317 1092322 Sell 0,2 1,26761 08.07.2010 21:21:14 1,26767 08.07.2010 21:22:29 1,26781 1,26749 0,37500 0,43750 -0,18750
946 1092317 1092330 Sell 0,2 1,26761 08.07.2010 21:21:14 1,26792 08.07.2010 21:24:05 1,26782 1,26749 0,36364 -0,30303 -0,93939
947 1092319 1092330 Sell 0,3 1,26761 08.07.2010 21:21:37 1,26792 2010,07,08 21:24:05 1,26782 1,26749 0,36364 -0,30303 -0,93939
Pos[288] ID 1019623 EURUSD
948 1092394 1092406 Buy 0,1 1,26832 08.07.2010 21:36:43 1,26843 08.07.2010 21:37:38 1,26882 1,26813 0,72464 0,43478 0,15942
Pos[289] ID 1019641 EURUSD
949 1092413 1092417 Buy 0,1 1,26847 08.07.2010 21:38:19 1,26852 08.07.2010 21:38:51 1,26910 1,26829 0,77778 0,28395 0,06173
950 1092417 1092433 Sell 0,1 1,26852 08.07.2010 21:38:51 1,26922 08.07.2010 21:39:58 1,26916 1,26829 0,26437 -0,06897 -0,80460
Pos[290] ID 1150923 EURUSD
951 1226007 1226046 Buy 0,2 1,31653 05.08.2010 16:06:20 1,31682 05.08.2010 16:10:53 1,31706 1,31611 0,55789 0,74737 0,30526
952 1226024 1226046 Buy 0,3 1,31632 05.08.2010 16:08:31 1,31682 05.08.2010 16:10:53 1,31706 1,31611 0,77895 0,74737 0,52632
953 1226046 1226066 Sell 0,1 1,31682 05.08.2010 16:10:53 1,31756 05.08.2010 16:12:49 1,31750 1,31647 0,33981 -0,05825 -0,71845
954 1226046 1226078 Sell 0,2 1,31682 05.08.2010 16:10:53 1,31744 05.08.2010 16:15:16 1,31750 1,31647 0,33981 0,05825 -0,60194
Pos[291] ID 1155527 EURUSD
955 1230640 1232744 Sell 0,1 1,31671 06.08.2010 13:52:11 1,32923 06.08.2010 17:39:50 1,33327 1,31648 0,01370 0,24062 -0,74568
956 1231369 1232744 Sell 0,1 1,32584 06.08.2010 14:54:53 1,32923 06.08.2010 17:39:50 1,33327 1,32518 0,08158 0,49938 -0,41904
957 1231455 1232744 Sell 0,1 1,32732 06.08.2010 14:58:13 1,32923 06.08.2010 17:39:50 1,33327 1,32539 0,24492 0,51269 -0,24239
958 1231476 1232744 Sell 0,1 1,32685 06.08.2010 14:59:47 1,32923 06.08.2010 17:39:50 1,33327 1,32539 0,18528 0,51269 -0,30203
959 1231484 1232744 Sell 0,2 1,32686 06.08.2010 15:00:20 1,32923 06.08.2010 17:39:50 1,33327 1,32539 0,18655 0,51269 -0,30076
960 1231926 1232744 Sell 0,4 1,33009 06.08.2010 15:57:32 1,32923 06.08.2010 17:39:50 1,33327 1,32806 0,38964 0,77543 0,16507
961 1232591 1232748 Sell 0,4 1,33123 06.08.2010 17:11:29 1,32850 06.08.2010 17:40:40 1,33129 1,32806 0,98142 0,86378 0,84520
962 1232591 1232754 Sell 0,4 1,33123 06.08.2010 17:11:29 1,32829 06.08.2010 17:42:14 1,33129 1,32796 0,98198 0,90090 0,88288
963 1232591 1232757 Sell 0,2 1,33123 06.08.2010 17:11:29 1,32839 06.08.2010 17:43:15 1,33129 1,32796 0,98198 0,87087 0,85285
Pos[292] ID 1167490 EURUSD
964 1242941 1243332 Sell 0,1 1,31001 10.08.2010 15:54:51 1,30867 10.08.2010 17:17:51 1,31037 1,30742 0,87797 0,57627 0,45424
965 1242944 1243333 Sell 0,1 1,30988 10.08.2010 15:55:03 1,30867 10.08.2010 17:17:55 1,31037 1,30742 0,83390 0,57627 0,41017
Pos[293] ID 1291817 EURUSD
966 1367532 1367788 Sell 0,4 1,28904 06.09.2010 00:24:01 1,28768 06.09.2010 02:53:21 1,28965 1,28710 0,76078 0,77255 0,53333


So sieht also die umgewandelte Information aus. Damit sich jeder Leser das alles hier ganz in Ruhe ansehen kann (und Verständnis kommt durch Vergleichen), speichere ich die Original-History der Abschlüsse in einer extra Datei. Das ist die History, die so viele Händler jetzt so sehr vermissen, die bislang daran gewöhnt waren, sie im Abschnitt [Ergebnisse] in MetaTrader 4 zu finden.

Fazit

Zusammenfassend möchte ich allen Entwicklern vorschlagen, eine Möglichkeit hinzuzufügen, Expert Advisors nicht nur durch angepasste Parameter optimieren zu können, sondern dies in Kombination mit den Standardparametern machen zu können, so wie es auch bei den anderen Optimierungsfunktionen der Fall ist. Als Zusammenfassung dieses Beitrag bleibt zu sagen, dass hier nur die Grundlagen angesprochen wurden - das Anfangspotenzial - und ich sehr hoffe, dass jeder Leser in der Lage sein wird, die Klasse je nach seinen eignen Bedürfnissen erweitern zu können. Viel Erfolg!