English Русский 中文 Español 日本語 Português
preview
Visuelle Bewertung und Anpassung des Handels im MetaTrader 5

Visuelle Bewertung und Anpassung des Handels im MetaTrader 5

MetaTrader 5Beispiele |
107 16
Artyom Trishkin
Artyom Trishkin

Inhalt



Einführung

Stellen wir uns eine Situation vor: Auf einem Konto wird seit geraumer Zeit mehr oder weniger aktiv mit verschiedenen Instrumenten gehandelt, wobei verschiedene EAs und in einigen Fällen sogar manuell eingesetzt werden. Und jetzt, nach einiger Zeit, wollen wir die Ergebnisse all dieser Arbeit sehen. Natürlich können wir die Standard-Handelsberichte im Terminal durch Drücken der Tastenkombination Alt+E einsehen. Wir können auch Handelssymbole in unser Diagramm laden und Einstiegs- und Ausstiegszeiten für unsere Positionen sehen. Was aber, wenn wir die Dynamik unseres Handels sehen wollen, wo und wie Positionen eröffnet und geschlossen wurden? Wir können uns jedes Symbol einzeln oder alle auf einmal ansehen, einschließlich der Eröffnung und Schließung von Positionen, der Niveaus, auf denen Stop-Orders platziert wurden, und ob ihre Größe gerechtfertigt war. Wie wäre es, wenn wir uns die Frage stellen: „Was wäre passiert, wenn...“ (und hier gibt es viele Möglichkeiten – verschiedene Stopps, die Verwendung verschiedener Algorithmen und Kriterien, die Verwendung von Trailing-Positionen oder das Verschieben von Stopps zum Breakeven usw.) und dann alle unsere „Wenns“ mit einem klaren, sichtbaren Ergebnis testen. Wie könnte sich der Handel verändern, wenn...

Es stellt sich heraus, dass bereits alles vorhanden ist, um solche Probleme zu lösen. Alles, was wir tun müssen, ist, die Kontohistorie in eine Datei zu laden – alle abgeschlossenen Handelsgeschäfte – und dann einen EA im Strategietester laufen zu lassen, der Handelsgeschäfte aus der Datei liest und Positionen im Strategietester des Client-Terminals öffnet/schließt. Mit einem solchen EA können wir Code hinzufügen, um die Bedingungen für das Verlassen von Positionen zu ändern und zu vergleichen, wie sich der Handel verändert, und was passiert wäre, wenn...

Wie kann das für uns von Nutzen sein? Ein weiteres Instrument, um die besten Ergebnisse zu finden und Anpassungen am Handel vorzunehmen, der bereits seit einiger Zeit auf einem Konto läuft. Die visuelle Prüfung ermöglicht es uns, dynamisch zu sehen, ob die Positionen für ein bestimmtes Instrument korrekt geöffnet wurden, ob sie zum richtigen Zeitpunkt geschlossen wurden, usw. Und was am wichtigsten ist: Ein neuer Algorithmus kann einfach zum EA-Code hinzugefügt, getestet, die Ergebnisse ermittelt und die EAs, die auf diesem Konto arbeiten, angepasst werden.

Lassen Sie uns die folgende Logik für das Verhalten des EA erstellen:

  • Wenn der EA auf dem Chart eines beliebigen Instruments gestartet wird, sammelt er die gesamte Historie der Handelsgeschäfte auf dem aktuellen Konto, speichert alle Handelsgeschäfte in einer Datei und tut dann – nichts;
  • Wenn der EA im Tester gestartet wird, liest er die in der Datei aufgezeichnete Handelshistorie und wiederholt während des Tests alle Handelsgeschäfte aus der Datei mit Eröffnen und Schließen der Positionen.

So bereitet der EA zunächst eine Datei mit der Handelshistorie vor (wenn er auf einem Chart ausgeführt wird) und führt dann Handelsgeschäfte aus der Datei aus, wobei er den Handel auf dem Konto vollständig wiederholt (wenn er im Strategietester ausgeführt wird).

Als Nächstes werden wir Änderungen am EA vornehmen, um unterschiedliche StopLoss- und TakeProfit-Werte für die im Tester geöffneten Positionen festlegen zu können.



Speichern der Handelshistorie in einer Datei

Im Terminalverzeichnis \MQL5\Experts\ erstellen wir einen neuen Ordner TradingByHistoryDeals, der eine neue EA-Datei namens TradingByHistoryDeals.mq5 enthält.

Der EA sollte die Möglichkeit haben, auszuwählen, welches Symbol und welche magische Zahl getestet werden soll. Wenn mehrere EAs für mehrere Symbole oder magische Zahlen auf dem Konto arbeiten, können wir in den Einstellungen auswählen, an welchem Symbol oder welcher magischen Zahl wir interessiert sind (oder an allen gleichzeitig).

//+------------------------------------------------------------------+
//|                                        TradingByHistoryDeals.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Expert                                                           |
//+------------------------------------------------------------------+
//--- input parameters
input    string   InpTestedSymbol   =  "";      /* The symbol being tested in the tester        */ 
input    long     InpTestedMagic    =  -1;      /* The magic number being tested in the tester  */ 
sinput   bool     InpShowDataInLog  =  false;   /* Show collected data in the log               */ 

Die Standardwerte für das Symbol und die magische Zahl sind der leere String und -1. Mit diesen Werten sortiert der EA den Handelsverlauf nicht nach Symbolen oder magischen Zahlen – es wird der gesamte Handelsverlauf getestet. Die dritte Zeichenfolge weist den EA an, Beschreibungen aller in der Datei gespeicherten Handelsgeschäfte in das Journal auszugeben (oder nicht), damit wir die Richtigkeit der gespeicherten Daten eindeutig überprüfen können.

Jedes Handelsgeschäft besteht aus einer ganzen Reihe verschiedener Parameter, die durch unterschiedliche Handelsgeschäftseigenschaften beschrieben werden. Am einfachsten ist es, alle Handelsgeschäftseigenschaften in einer Struktur aufzuschreiben. Um eine große Anzahl von Handelsgeschäften in eine Datei zu schreiben, ist es notwendig, ein Array von Strukturen zu verwenden. Dann speichern wir dieses Array in der Datei. Die MQL5-Sprache hat alles, was man dazu braucht. Die Logik für die Speicherung des Handelsgeschäftsverlaufs in einer Datei sieht folgendermaßen aus:

  • sich in einer Schleife durch die historischen Handelsgeschäfte bewegen;
  • Empfang des nächsten Handelsgeschäfts und Zuweisung von dessen Daten in die Struktur;
  • Speichern der erstellten Handelsgeschäftsstruktur im Handelsgeschäftsfeld;
  • Am Ende der Schleife wird das vorbereitete Array von Strukturen in der Datei gespeichert.

Alle zusätzlichen Codes – Strukturen, Klassen, Enumerationen – werden in eine separate Datei geschrieben. Benennen wir sie nach der zukünftigen Klasse des Symbolhandelsobjekts.

Erstellen wir im selben Ordner eine neue eingebundene Datei namens SymbolTrade.mqh.

Wir implementieren die Makro-Substitutionen für den Namen des Ordners, der die Verlaufsdatei enthalten soll, den Dateinamen und den Dateipfad und fügen wir alle erforderlichen Standardbibliotheksdateien hinzu:

//+------------------------------------------------------------------+
//|                                                  SymbolTrade.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#define   DIRECTORY  "TradingByHistoryDeals"
#define   FILE_NAME  "HistoryDealsData.bin"
#define   PATH       DIRECTORY+"\\"+FILE_NAME

#include <Arrays\ArrayObj.mqh>
#include <Trade\Trade.mqh>

Als Nächstes werden wir die Struktur für die Handelsgeschäfte bzw. Deals schreiben:

//+------------------------------------------------------------------+
//|  Deal structure. Used to create a deal history file              |
//+------------------------------------------------------------------+
struct SDeal
  {
   ulong             ticket;                 // Deal ticket
   long              order;                  // Order the deal is based on
   long              pos_id;                 // Position ID
   long              time_msc;               // Time in milliseconds
   datetime          time;                   // Time
   double            volume;                 // Volume
   double            price;                  // Price
   double            profit;                 // Profit
   double            commission;             // Deal commission
   double            swap;                   // Accumulated swap at closing
   double            fee;                    // Payment for the deal is accrued immediately after the deal is completed
   double            sl;                     // Stop Loss level
   double            tp;                     // Take Profit level
   ENUM_DEAL_TYPE    type;                   // Type
   ENUM_DEAL_ENTRY   entry;                  // Position change method
   ENUM_DEAL_REASON  reason;                 // Deal reason or source
   long              magic;                  // EA ID
   int               digits;                 // Symbol digits
   ushort            symbol[16];             // Symbol
   ushort            comment[64];            // Deal comment
   ushort            external_id[256];       // Deal ID in an external trading system (on the exchange)
   
//--- Set string properties
   bool              SetSymbol(const string deal_symbol)          { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length());                }
   bool              SetComment(const string deal_comment)        { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length());             }
   bool              SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); }
                       
//--- Return string properties
   string            Symbol(void)                                 { return(::ShortArrayToString(symbol));                                                   }
   string            Comment(void)                                { return(::ShortArrayToString(comment));                                                  }
   string            ExternalID(void)                             { return(::ShortArrayToString(external_id));                                              }
  };

Da wir die Deal-Strukturen in einer Datei speichern werden und nur einfache Typ-Strukturen in eine Datei geschrieben werden können (siehe FileWriteArray()), sollten alle String-Variablen durch ushort-Arrays ersetzt und Methoden zum Schreiben und Zurückgeben von Struktur-String-Eigenschaften erstellt werden.

Die erstellte Struktur ist nur notwendig, um den Handelsgeschäftsverlauf in einer Datei zu speichern und den aufgezeichneten Verlauf aus der Datei zu lesen. Im EA selbst wird eine Liste von Objekten erstellt, in der die Objekte der Deal-Klasse gespeichert werden. Um das gewünschte Handelsgeschäft in der Liste zu suchen und das Array zu sortieren, müssen wir die Eigenschaft des Handelsgeschäfts angeben, nach der es in der Liste gesucht werden soll. Um nach einem Handelsgeschäft zu suchen, sollte die Liste der Objekte nach der gewünschten Eigenschaft sortiert werden.

Schreiben wir eine Liste aller Eigenschaften des Handelsgeschäftsobjekts, anhand derer wir eine Suche durchführen können:

//--- Deal sorting types
enum ENUM_DEAL_SORT_MODE
  {
   SORT_MODE_DEAL_TICKET = 0,          // Mode of comparing/sorting by a deal ticket
   SORT_MODE_DEAL_ORDER,               // Mode of comparing/sorting by the order a deal is based on
   SORT_MODE_DEAL_TIME,                // Mode of comparing/sorting by a deal time
   SORT_MODE_DEAL_TIME_MSC,            // Mode of comparing/sorting by a deal time in milliseconds
   SORT_MODE_DEAL_TYPE,                // Mode of comparing/sorting by a deal type
   SORT_MODE_DEAL_ENTRY,               // Mode of comparing/sorting by a deal direction
   SORT_MODE_DEAL_MAGIC,               // Mode of comparing/sorting by a deal magic number
   SORT_MODE_DEAL_REASON,              // Mode of comparing/sorting by a deal reason or source
   SORT_MODE_DEAL_POSITION_ID,         // Mode of comparing/sorting by a position ID
   SORT_MODE_DEAL_VOLUME,              // Mode of comparing/sorting by a deal volume
   SORT_MODE_DEAL_PRICE,               // Mode of comparing/sorting by a deal price
   SORT_MODE_DEAL_COMMISSION,          // Mode of comparing/sorting by commission
   SORT_MODE_DEAL_SWAP,                // Mode of comparing/sorting by accumulated swap on close
   SORT_MODE_DEAL_PROFIT,              // Mode of comparing/sorting by a deal financial result
   SORT_MODE_DEAL_FEE,                 // Mode of comparing/sorting by a deal fee
   SORT_MODE_DEAL_SL,                  // Mode of comparing/sorting by Stop Loss level
   SORT_MODE_DEAL_TP,                  // Mode of comparing/sorting by Take Profit level
   SORT_MODE_DEAL_SYMBOL,              // Mode of comparing/sorting by a name of a traded symbol
   SORT_MODE_DEAL_COMMENT,             // Mode of comparing/sorting by a deal comment
   SORT_MODE_DEAL_EXTERNAL_ID,         // Mode of comparing/sorting by a deal ID in an external trading system
   SORT_MODE_DEAL_TICKET_TESTER,       // Mode of comparing/sorting by a deal ticket in the tester
   SORT_MODE_DEAL_POS_ID_TESTER,       // Mode of comparing/sorting by a position ID in the tester 
  };

Hier gibt es zusätzlich zu den Standardeigenschaften des Handelsgeschäfts zwei weitere: das Handelsgeschäftsticket im Tester und die Positions-ID im Tester. Der Punkt ist, dass wir im Tester auf der Grundlage von Daten aus realen Handelsgeschäften handeln werden, während im Tester eröffnete Positionen und dementsprechend ihre entsprechenden Handelsgeschäfte ein völlig anderes Ticket und eine andere ID im Tester haben. Um ein echtes Handelsgeschäft mit einem Handelsgeschäft im Tester (sowie der ID) vergleichen zu können, müssen wir das Ticket und die Positions-ID im Tester in den Eigenschaften des Handelsgeschäftsobjekts speichern und dann anhand dieser gespeicherten Daten das Handelsgeschäft im Tester mit dem echten Handelsgeschäft im Verlauf vergleichen.

Lassen wir diese Datei erst einmal beiseite und gehen wir zu der EA-Datei über, die wir vorhin erstellt haben. Fügen wir das Array der Strukturen hinzu, in das wir die Strukturen aller Handelsgeschäfte in der Historie einfügen werden:

//--- input parameters
input    string   InpTestedSymbol   =  "";      /* The symbol being tested in the tester        */ 
input    long     InpTestedMagic    =  -1;      /* The magic number being tested in the tester  */ 
sinput   bool     InpShowDataInLog  =  false;   /* Show collected data in the log               */ 

//--- global variables
SDeal          ExtArrayDeals[]={};

Und wir werden Funktionen für die Arbeit mit historischen Handelsgeschäften schreiben.

Funktion, die den Handelsgeschäftsverlauf in einem Array speichert:

//+------------------------------------------------------------------+
//| Save deals from history into the array                           |
//+------------------------------------------------------------------+
int SaveDealsToArray(SDeal &array[], bool logs=false)
  {
//--- deal structure
   SDeal deal={};
   
//--- request the deal history in the interval from the very beginning to the current moment 
   if(!HistorySelect(0, TimeCurrent()))
     {
      Print("HistorySelect() failed. Error ", GetLastError());
      return 0;
     }
   
//--- total number of deals in the list 
   int total=HistoryDealsTotal(); 

//--- handle each deal 
   for(int i=0; i<total; i++) 
     { 
      //--- get the ticket of the next deal (the deal is automatically selected to get its properties)
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket==0)
         continue;
      
      //--- save only balance and trading deals
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);
      if(deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE)
         continue;
      
      //--- save the deal properties in the structure
      deal.ticket=ticket;
      deal.type=deal_type;
      deal.order=HistoryDealGetInteger(ticket, DEAL_ORDER);
      deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket, DEAL_ENTRY);
      deal.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(ticket, DEAL_REASON);
      deal.time=(datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
      deal.time_msc=HistoryDealGetInteger(ticket, DEAL_TIME_MSC);
      deal.pos_id=HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
      deal.volume=HistoryDealGetDouble(ticket, DEAL_VOLUME);
      deal.price=HistoryDealGetDouble(ticket, DEAL_PRICE);
      deal.profit=HistoryDealGetDouble(ticket, DEAL_PROFIT);
      deal.commission=HistoryDealGetDouble(ticket, DEAL_COMMISSION);
      deal.swap=HistoryDealGetDouble(ticket, DEAL_SWAP);
      deal.fee=HistoryDealGetDouble(ticket, DEAL_FEE);
      deal.sl=HistoryDealGetDouble(ticket, DEAL_SL);
      deal.tp=HistoryDealGetDouble(ticket, DEAL_TP);
      deal.magic=HistoryDealGetInteger(ticket, DEAL_MAGIC);
      deal.SetSymbol(HistoryDealGetString(ticket, DEAL_SYMBOL));
      deal.SetComment(HistoryDealGetString(ticket, DEAL_COMMENT));
      deal.SetExternalID(HistoryDealGetString(ticket, DEAL_EXTERNAL_ID));
      deal.digits=(int)SymbolInfoInteger(deal.Symbol(), SYMBOL_DIGITS);
      
      //--- increase the array and
      int size=(int)array.Size();
      ResetLastError();
      if(ArrayResize(array, size+1, total)!=size+1)
        {
         Print("ArrayResize() failed. Error ", GetLastError());
         continue;
        }
      //--- save the deal in the array
      array[size]=deal;
      //--- if allowed, display the description of the saved deal to the journal
      if(logs)
         DealPrint(deal, i);
     }
//--- return the number of deals stored in the array
   return (int)array.Size();
  }

Der Funktionscode ist ausführlich kommentiert. Wir wählen die gesamte Handelshistorie vom Anfang bis zum aktuellen Zeitpunkt aus, holen uns jedes aufeinanderfolgende historische Handelsgeschäft, speichern dessen Eigenschaften in den Strukturfeldern und weisen die Strukturvariable einem Array zu. Am Ende der Schleife durch die Handelsgeschäftshistorie geben wir die Größe des resultierenden Arrays von Handelsgeschäften zurück. Um den Fortschritt der Erfassung von Handelsgeschäften in einem Array zu überwachen, können wir jedes bearbeitete Handelsgeschäft im Journal ausdrucken. Dazu müssen wir beim Aufruf der Funktion das Flag logs auf true setzen.

Die Funktion, die alle Handelsgeschäfte aus einem Array von Handelsgeschäften in das Journal druckt:

//+------------------------------------------------------------------+
//| Display deals from the array to the journal                      |
//+------------------------------------------------------------------+
void DealsArrayPrint(SDeal &array[])
  {
   int total=(int)array.Size();
//--- if an empty array is passed, report this and return 'false'
   if(total==0)
     {
      PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__);
      return;
     }
//--- In a loop through the deal array, print out a description of each deal
   for(int i=0; i<total; i++)
     {
      DealPrint(array[i], i);
     }
  }

Wir wollen mehrere Funktionen implementieren, um die Handelsgeschäftsbeschreibung im Journal anzuzeigen.

Die Funktion, die eine Beschreibung der Transaktionsart zurückgibt:

//+------------------------------------------------------------------+
//| Return the deal type description                                 |
//+------------------------------------------------------------------+
string DealTypeDescription(const ENUM_DEAL_TYPE type)
  {
   switch(type)
     {
      case DEAL_TYPE_BUY                     :  return "Buy";
      case DEAL_TYPE_SELL                    :  return "Sell";
      case DEAL_TYPE_BALANCE                 :  return "Balance";
      case DEAL_TYPE_CREDIT                  :  return "Credit";
      case DEAL_TYPE_CHARGE                  :  return "Additional charge";
      case DEAL_TYPE_CORRECTION              :  return "Correction";
      case DEAL_TYPE_BONUS                   :  return "Bonus";
      case DEAL_TYPE_COMMISSION              :  return "Additional commission";
      case DEAL_TYPE_COMMISSION_DAILY        :  return "Daily commission";
      case DEAL_TYPE_COMMISSION_MONTHLY      :  return "Monthly commission";
      case DEAL_TYPE_COMMISSION_AGENT_DAILY  :  return "Daily agent commission";
      case DEAL_TYPE_COMMISSION_AGENT_MONTHLY:  return "Monthly agent commission";
      case DEAL_TYPE_INTEREST                :  return "Interest rate";
      case DEAL_TYPE_BUY_CANCELED            :  return "Canceled buy deal";
      case DEAL_TYPE_SELL_CANCELED           :  return "Canceled sell deal";
      case DEAL_DIVIDEND                     :  return "Dividend operations";
      case DEAL_DIVIDEND_FRANKED             :  return "Franked (non-taxable) dividend operations";
      case DEAL_TAX                          :  return "Tax charges";
      default                                :  return "Unknown deal type: "+(string)type;
     }
  }

Je nach der an die Funktion übergebenen Handelsgeschäftsart wird die entsprechende Zeichenfolge angezeigt.

Die Funktion retourniert die Beschreibung der Positionsänderungsmethode:

//+------------------------------------------------------------------+
//| Return position change method                                    |
//+------------------------------------------------------------------+
string DealEntryDescription(const ENUM_DEAL_ENTRY entry)
  {
   switch(entry)
     {
      case DEAL_ENTRY_IN      :  return "Entry In";
      case DEAL_ENTRY_OUT     :  return "Entry Out";
      case DEAL_ENTRY_INOUT   :  return "Entry InOut";
      case DEAL_ENTRY_OUT_BY  :  return "Entry OutBy";
      default                 :  return "Unknown entry: "+(string)entry;
     }
  }

Je nach der Methode zur Änderung der Position, die der Funktion übergeben wird, wird die entsprechende Zeichenfolge angezeigt.

Die Funktion, die eine Beschreibung eines Handelsgeschäfts zurückgibt:

//+------------------------------------------------------------------+
//| Return deal description                                          |
//+------------------------------------------------------------------+
string DealDescription(SDeal &deal, const int index)
  {
   string indexs=StringFormat("% 5d", index);
   if(deal.type!=DEAL_TYPE_BALANCE)
      return(StringFormat("%s: deal #%I64u %s, type %s, Position #%I64d %s (magic %I64d), Price %.*f at %s, sl %.*f, tp %.*f",
                          indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type),
                          deal.pos_id, deal.Symbol(), deal.magic, deal.digits, deal.price,
                          TimeToString(deal.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS), deal.digits, deal.sl, deal.digits, deal.tp));
   else
      return(StringFormat("%s: deal #%I64u %s, type %s %.2f %s at %s",
                          indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type),
                          deal.profit, AccountInfoString(ACCOUNT_CURRENCY), TimeToString(deal.time)));
  }

Handelt es sich um ein Saldengeschäft, so wird eine Beschreibung in folgender Form angezeigt:

    0: deal #190715988 Entry In, type Balance 3000.00 USD at 2024.09.13 21:48

Andernfalls wird die Handelsgeschäftsbeschreibung in einem anderen Format angezeigt:

    1: deal #190724678 Entry In, type Buy, Position #225824633 USDCHF (magic 600), Price 0.84940 at 2024.09.13 23:49:03, sl 0.84811, tp 0.84983

Die Funktion, die eine Handelsgeschäftsbeschreibung in das Journal druckt:

//+------------------------------------------------------------------+
//| Print deal data in the journal                                   |
//+------------------------------------------------------------------+
void DealPrint(SDeal &deal, const int index)
  {
   Print(DealDescription(deal, index));
  }

Hier ist alles klar – wir drucken einfach die Zeichenkette aus, die wir mit der Funktion DealDescription() erhalten haben.

Wir schreiben Funktionen zum Schreiben und Lesen eines Arrays der Deals in/aus einer Datei.

Die Funktion, die eine Datei zum Schreiben öffnet:

//+------------------------------------------------------------------+
//| Open a file for writing, return a handle                         |
//+------------------------------------------------------------------+
bool FileOpenToWrite(int &handle)
  {
   ResetLastError();
   handle=FileOpen(PATH, FILE_WRITE|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError());
      return false;
     }
//--- successful
   return true;
  }

Die Funktion öffnet eine Datei zum Lesen:

//+------------------------------------------------------------------+
//| Open a file for reading, return a handle                         |
//+------------------------------------------------------------------+
bool FileOpenToRead(int &handle)
  {
   ResetLastError();
   handle=FileOpen(PATH, FILE_READ|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError());
      return false;
     }
//--- successful
   return true;
  }

Die Funktionen öffnen eine Datei zum Lesen bzw. Schreiben. In den formalen Parametern wird als Referenz eine Variable übergeben, der das Datei-Handle zugewiesen wurde. Es wird true zurückgegeben, wenn die Datei erfolgreich geöffnet wurde, und false, wenn ein Fehler aufgetreten ist.

Die Funktion, die Deal-Daten aus einem Array in der Datei speichert:

//+------------------------------------------------------------------+
//| Save deal data from the array to the file                        |
//+------------------------------------------------------------------+
bool FileWriteDealsFromArray(SDeal &array[], ulong &file_size)
  {
//--- if an empty array is passed, report this and return 'false'
   if(array.Size()==0)
     {
      PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__);
      return false;
     }
     
//--- open the file for writing, get its handle
   int handle=INVALID_HANDLE;
   if(!FileOpenToWrite(handle))
      return false;
   
//--- move the file pointer to the end of the file
   bool res=true;
   ResetLastError();
   res&=FileSeek(handle, 0, SEEK_END);
   if(!res)
      PrintFormat("%s: FileSeek(SEEK_END) failed. Error %d",__FUNCTION__, GetLastError());
   
//--- write the array data to the end of the file 
   file_size=0;
   res&=(FileWriteArray(handle, array)==array.Size());
   if(!res)
      PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError());
   else
      file_size=FileSize(handle);

//--- close the file 
   FileClose(handle);
   return res;
  }

Die Funktion erhält ein Array von Strukturen, die in einer Datei gespeichert werden müssen. Die Variable, die die Größe der erstellten Datei erhält, wird in den formalen Parametern der Funktion als Referenz übergeben. Wir öffnen die Datei, verschieben den Dateizeiger an das Ende der Datei und schreiben Daten aus dem Array von Strukturen in die Datei, beginnend mit dem Zeiger. Wenn der Schreibvorgang abgeschlossen ist, wird die Datei geschlossen.

Nachdem das Array der Handelsgeschäftsstrukturen in der Datei gespeichert wurde, können alle Handelsgeschäfte aus dieser Datei in das Array zurückgelesen werden, um dann Listen von Handelsgeschäften zu erstellen und mit ihnen im Tester zu arbeiten.

Die Funktion, die die Handelsgeschäftsdaten aus der Datei in das Array lädt:

//+------------------------------------------------------------------+
//| Load the deal data from the file into the array                  |
//+------------------------------------------------------------------+
bool FileReadDealsToArray(SDeal &array[], ulong &file_size)
  {
//--- open the file for reading, get its handle
   int handle=INVALID_HANDLE;
   if(!FileOpenToRead(handle))
      return false;
   
//--- move the file pointer to the end of the file 
   bool res=true;
   ResetLastError();
   
//--- read data from the file into the array
   file_size=0;
   res=(FileReadArray(handle, array)>0);
   if(!res)
      PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError());
   else
      file_size=FileSize(handle);

//--- close the file 
   FileClose(handle);
   return res;
  }

Auf der Grundlage der oben erstellten Funktionen werden wir eine Funktion schreiben, die den Verlauf der Handelsgeschäfte liest und in die Datei schreibt.

Die Funktion, die eine Datei mit historischen Handelsgeschäften vorbereitet:

//+------------------------------------------------------------------+
//| Prepare a file with history deals                                |
//+------------------------------------------------------------------+
bool PreparesDealsHistoryFile(SDeal &deals_array[])
  {
//--- save all the account deals in the deal array
   int total=SaveDealsToArray(deals_array);
   if(total==0)
      return false;
      
//--- write the deal array data to the file
   ulong file_size=0;
   if(!FileWriteDealsFromArray(deals_array, file_size))
      return false;
      
//--- print in the journal how many deals were read and saved to the file, the path to the file and its size
   PrintFormat("%u deals were saved in an array and written to a \"%s\" file of %I64u bytes in size",
               deals_array.Size(), "TERMINAL_COMMONDATA_PATH\\Files\\"+ PATH, file_size);
   
//--- now, to perform a check, we will read the data from the file into the array
   ArrayResize(deals_array, 0, total);
   if(!FileReadDealsToArray(deals_array, file_size))
      return false;
      
//--- print in the journal how many bytes were read from the file and the number of deals received in the array
   PrintFormat("%I64u bytes were read from the file \"%s\" and written to the deals array. A total of %u deals were received", file_size, FILE_NAME, deals_array.Size());
   return true;
  }

Die Kommentare im Code machen die Logik deutlich. Die Funktion wird in OnInit() gestartet und bereitet die Datei mit den Handelsgeschäften für die weitere Arbeit vor:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- If the EA is not running in the tester
   if(!MQLInfoInteger(MQL_TESTER))
     {
      //--- prepare a file with all historical deals
      if(!PreparesDealsHistoryFile(ExtArrayDeals))
         return(INIT_FAILED);
         
      //--- print all deals in the journal after loading them from the file 
      if(InpShowDataInLog)
         DealsArrayPrint(ExtArrayDeals);
         
      //--- get the first balance deal, create the message text and display it using Alert
      SDeal    deal=ExtArrayDeals[0];
      long     leverage=AccountInfoInteger(ACCOUNT_LEVERAGE);
      double   start_money=deal.profit;
      datetime first_time=deal.time;
      string   start_time=TimeToString(deal.time, TIME_DATE);
      string   message=StringFormat("Now you can run testing\nInterval: %s - current date\nInitial deposit: %.2f, leverage 1:%I64u", start_time, start_money, leverage);
      
      //--- notify via alert of the recommended parameters of the strategy tester for starting the test
      Alert(message);
     }
     
//--- All is successful
   return(INIT_SUCCEEDED);
  }

Zusätzlich zur Speicherung aller historischen Abschlüsse in einer Datei wird eine Warnung mit einer Meldung über die empfohlenen Einstellungen des Testers angezeigt – Anfangssaldo, Hebel und die Startzeit des Tests, die dem Datum des ersten Abschlusses entspricht. Sie könnte zum Beispiel so aussehen:

Alert: Now you can run testing
Interval: 2024.09.13 - current date
Initial deposit: 3000.00, leverage 1:500

Diese Einstellungen des Testers führen dazu, dass das Endergebnis im Tester dem tatsächlichen Ergebnis am nächsten kommt.

Die Struktur des Handelsgeschäftssatzes in der Datei \MQL5\Experts\TradingByHistoryDeals\SymbolTrade.mqh ist nur zum Speichern des Handelsgeschäfts in der Datei und zum Lesen der gespeicherten Historie aus der Datei gedacht. Um unsere Arbeit fortzusetzen, müssen wir eine Klasse der Deals erstellen, deren Objekte in Listen gespeichert werden sollen. Die Listen selbst werden in den Objekten der Handelsklasse für den Tester gespeichert. Handelsobjekte wiederum sind ebenfalls Klassenobjekte, die in einer eigenen Liste gespeichert werden. Jedes Handelsobjekt wird durch seine Zugehörigkeit zu einem bestimmten Symbol bestimmt – die Anzahl der am Handel beteiligten Symbole bestimmt die Anzahl der Handelsobjekte. Die Handelsobjekte selbst enthalten eine Liste von Transaktionen nur für ihr Symbol und ihre eigenen Objekte der Klasse CTrade aus der Standardbibliothek. Dadurch kann jedes Handelsobjekt der Klasse CTrade an die Bedingungen des gehandelten Symbols angepasst werden.

Schreiben wir eine Handelsklasse in die Datei \MQL5\Experts\TradingByHistoryDeals\SymbolTrade.mqh.

//+------------------------------------------------------------------+
//| Deal class. Used for trading in the strategy tester              |
//+------------------------------------------------------------------+
class CDeal : public CObject
  {
protected:
//--- Integer properties
   ulong             m_ticket;            // Deal ticket. Unique number assigned to each deal
   long              m_order;             // Deal order number
   datetime          m_time;              // Deal execution time
   long              m_time_msc;          // Deal execution time in milliseconds since 01.01.1970
   ENUM_DEAL_TYPE    m_type;              // Deal type
   ENUM_DEAL_ENTRY   m_entry;             // Deal entry - entry in, entry out, reverse
   long              m_magic;             // Magic number for a deal (see ORDER_MAGIC)
   ENUM_DEAL_REASON  m_reason;            // Deal execution reason or source
   long              m_pos_id;            // The ID of the position opened, modified or closed by the deal
   
//--- Real properties
   double            m_volume;            // Deal volume
   double            m_price;             // Deal price
   double            m_commission;        // Deal commission
   double            m_swap;              // Accumulated swap when closing
   double            m_profit;            // Deal financial result
   double            m_fee;               // Fee for making a deal charged immediately after performing a deal
   double            m_sl;                // Stop Loss level
   double            m_tp;                // Take Profit level

//--- String properties
   string            m_symbol;            // Name of the symbol for which the deal is executed
   string            m_comment;           // Deal comment
   string            m_external_id;       // Deal ID in an external trading system (on the exchange)
   
//--- Additional properties
   int               m_digits;            // Symbol digits
   double            m_point;             // Symbol point
   ulong             m_ticket_tester;     // Position ticket in the tester
   long              m_pos_id_tester;     // Position ID in the tester
   
public:
//--- Set deal propertie
   void              SetTicket(const ulong ticket)             { this.m_ticket=ticket;          }
   void              SetOrder(const long order)                { this.m_order=order;            }
   void              SetTime(const datetime time)              { this.m_time=time;              }
   void              SetTimeMsc(const long value)              { this.m_time_msc=value;         }
   void              SetType(const ENUM_DEAL_TYPE type)        { this.m_type=type;              }
   void              SetEntry(const ENUM_DEAL_ENTRY entry)     { this.m_entry=entry;            }
   void              SetMagic(const long magic)                { this.m_magic=magic;            }
   void              SetReason(const ENUM_DEAL_REASON reason)  { this.m_reason=reason;          }
   void              SetPositionID(const long id)              { this.m_pos_id=id;              }
   void              SetVolume(const double volume)            { this.m_volume=volume;          }
   void              SetPrice(const double price)              { this.m_price=price;            }
   void              SetCommission(const double commission)    { this.m_commission=commission;  }
   void              SetSwap(const double swap)                { this.m_swap=swap;              }
   void              SetProfit(const double profit)            { this.m_profit=profit;          }
   void              SetFee(const double fee)                  { this.m_fee=fee;                }
   void              SetSL(const double sl)                    { this.m_sl=sl;                  }
   void              SetTP(const double tp)                    { this.m_tp=tp;                  }
   void              SetSymbol(const string symbol)            { this.m_symbol=symbol;          }
   void              SetComment(const string comment)          { this.m_comment=comment;        }
   void              SetExternalID(const string ext_id)        { this.m_external_id=ext_id;     }
   void              SetTicketTester(const ulong ticket)       { this.m_ticket_tester=ticket;   }
   void              SetPosIDTester(const long pos_id)         { this.m_pos_id_tester=pos_id;   }
   
//--- Return deal properties
   ulong             Ticket(void)                        const { return this.m_ticket;          }
   long              Order(void)                         const { return this.m_order;           }
   datetime          Time(void)                          const { return this.m_time;            }
   long              TimeMsc(void)                       const { return this.m_time_msc;        }
   ENUM_DEAL_TYPE    TypeDeal(void)                      const { return this.m_type;            }
   ENUM_DEAL_ENTRY   Entry(void)                         const { return this.m_entry;           }
   long              Magic(void)                         const { return this.m_magic;           }
   ENUM_DEAL_REASON  Reason(void)                        const { return this.m_reason;          }
   long              PositionID(void)                    const { return this.m_pos_id;          }
   double            Volume(void)                        const { return this.m_volume;          }
   double            Price(void)                         const { return this.m_price;           }
   double            Commission(void)                    const { return this.m_commission;      }
   double            Swap(void)                          const { return this.m_swap;            }
   double            Profit(void)                        const { return this.m_profit;          }
   double            Fee(void)                           const { return this.m_fee;             }
   double            SL(void)                            const { return this.m_sl;              }
   double            TP(void)                            const { return this.m_tp;              }
   string            Symbol(void)                        const { return this.m_symbol;          }
   string            Comment(void)                       const { return this.m_comment;         }
   string            ExternalID(void)                    const { return this.m_external_id;     }

   int               Digits(void)                        const { return this.m_digits;          }
   double            Point(void)                         const { return this.m_point;           }
   ulong             TicketTester(void)                  const { return this.m_ticket_tester;   }
   long              PosIDTester(void)                   const { return this.m_pos_id_tester;   }
   
//--- Compare two objects by the property specified in 'mode'
   virtual int       Compare(const CObject *node, const int mode=0) const
                       {
                        const CDeal *obj=node;
                        switch(mode)
                          {
                           case SORT_MODE_DEAL_TICKET          :  return(this.Ticket() > obj.Ticket()          ?  1  :  this.Ticket() < obj.Ticket()           ? -1  :  0);
                           case SORT_MODE_DEAL_ORDER           :  return(this.Order() > obj.Order()            ?  1  :  this.Order() < obj.Order()             ? -1  :  0);
                           case SORT_MODE_DEAL_TIME            :  return(this.Time() > obj.Time()              ?  1  :  this.Time() < obj.Time()               ? -1  :  0);
                           case SORT_MODE_DEAL_TIME_MSC        :  return(this.TimeMsc() > obj.TimeMsc()        ?  1  :  this.TimeMsc() < obj.TimeMsc()         ? -1  :  0);
                           case SORT_MODE_DEAL_TYPE            :  return(this.TypeDeal() > obj.TypeDeal()      ?  1  :  this.TypeDeal() < obj.TypeDeal()       ? -1  :  0);
                           case SORT_MODE_DEAL_ENTRY           :  return(this.Entry() > obj.Entry()            ?  1  :  this.Entry() < obj.Entry()             ? -1  :  0);
                           case SORT_MODE_DEAL_MAGIC           :  return(this.Magic() > obj.Magic()            ?  1  :  this.Magic() < obj.Magic()             ? -1  :  0);
                           case SORT_MODE_DEAL_REASON          :  return(this.Reason() > obj.Reason()          ?  1  :  this.Reason() < obj.Reason()           ? -1  :  0);
                           case SORT_MODE_DEAL_POSITION_ID     :  return(this.PositionID() > obj.PositionID()  ?  1  :  this.PositionID() < obj.PositionID()   ? -1  :  0);
                           case SORT_MODE_DEAL_VOLUME          :  return(this.Volume() > obj.Volume()          ?  1  :  this.Volume() < obj.Volume()           ? -1  :  0);
                           case SORT_MODE_DEAL_PRICE           :  return(this.Price() > obj.Price()            ?  1  :  this.Price() < obj.Price()             ? -1  :  0);
                           case SORT_MODE_DEAL_COMMISSION      :  return(this.Commission() > obj.Commission()  ?  1  :  this.Commission() < obj.Commission()   ? -1  :  0);
                           case SORT_MODE_DEAL_SWAP            :  return(this.Swap() > obj.Swap()              ?  1  :  this.Swap() < obj.Swap()               ? -1  :  0);
                           case SORT_MODE_DEAL_PROFIT          :  return(this.Profit() > obj.Profit()          ?  1  :  this.Profit() < obj.Profit()           ? -1  :  0);
                           case SORT_MODE_DEAL_FEE             :  return(this.Fee() > obj.Fee()                ?  1  :  this.Fee() < obj.Fee()                 ? -1  :  0);
                           case SORT_MODE_DEAL_SL              :  return(this.SL() > obj.SL()                  ?  1  :  this.SL() < obj.SL()                   ? -1  :  0);
                           case SORT_MODE_DEAL_TP              :  return(this.TP() > obj.TP()                  ?  1  :  this.TP() < obj.TP()                   ? -1  :  0);
                           case SORT_MODE_DEAL_SYMBOL          :  return(this.Symbol() > obj.Symbol()          ?  1  :  this.Symbol() < obj.Symbol()           ? -1  :  0);
                           case SORT_MODE_DEAL_COMMENT         :  return(this.Comment() > obj.Comment()        ?  1  :  this.Comment() < obj.Comment()         ? -1  :  0);
                           case SORT_MODE_DEAL_EXTERNAL_ID     :  return(this.ExternalID()  >obj.ExternalID()  ?  1  :  this.ExternalID()  <obj.ExternalID()   ? -1  :  0);
                           case SORT_MODE_DEAL_TICKET_TESTER   :  return(this.TicketTester()>obj.TicketTester()?  1  :  this.TicketTester()<obj.TicketTester() ? -1  :  0);
                           case SORT_MODE_DEAL_POS_ID_TESTER   :  return(this.PosIDTester() >obj.PosIDTester() ?  1  :  this.PosIDTester() <obj.PosIDTester()  ? -1  :  0);
                           default                             :  return(WRONG_VALUE);
                          }
                       }
   
//--- Constructors/destructor
                     CDeal(const ulong ticket, const string symbol) : m_ticket(ticket), m_symbol(symbol), m_ticket_tester(0), m_pos_id_tester(0)
                       { this.m_digits=(int)::SymbolInfoInteger(symbol, SYMBOL_DIGITS); this.m_point=::SymbolInfoDouble(symbol, SYMBOL_POINT); }
                     CDeal(void) {}
                    ~CDeal(void) {}
  };

Die Klasse wiederholt fast vollständig die zuvor erstellte Handelsgeschäftsstruktur. Zusätzlich zu den Handelsgeschäftseigenschaften wurden weitere Eigenschaften hinzugefügt – Ziffern und Punkt des Symbols, für das das Handelsgeschäft getätigt wurde. Dies vereinfacht die Ausgabe der Handelsgeschäftsbeschreibung, da diese Daten sofort bei der Erstellung des Objekts im Konstruktor des Handelsgeschäfts festgelegt werden, wodurch die Notwendigkeit entfällt, diese Eigenschaften für jedes Handelsgeschäft (falls sie benötigt werden) beim Zugriff darauf zu ermitteln.
Außerdem wird hier eine virtuelle Methode Compare() für den Vergleich von zwei Handelsgeschäftsobjekten erstellt – sie wird beim Sortieren der Listen von Handelsgeschäften verwendet, um das gewünschte Handelsgeschäft anhand der angegebenen Eigenschaft zu finden.

Lassen Sie uns nun eine Handelssymbolklasse erstellen. Die Klasse speichert eine Liste der Handelsgeschäfte, die unter Verwendung des in den Objekteigenschaften eingestellten Symbols durchgeführt wurden, und der Tester fordert diese Handelsgeschäfte zum Kopieren an. Im Allgemeinen wird diese Klasse die Grundlage für das Kopieren von Handelsgeschäften sein, die auf einem Konto nach Symbolen im Strategietester getätigt werden:

//+------------------------------------------------------------------+
//|  Class for trading by symbol                                     |
//+------------------------------------------------------------------+
CDeal DealTmp; // Temporary deal object for searching by properties

class CSymbolTrade : public CObject
  {
private:
   int               m_index_next_deal;                  // Index of the next deal that has not yet been handled
   int               m_deals_processed;                  // Number of handled deals
protected:
   MqlTick           m_tick;                             // Tick structure
   CArrayObj         m_list_deals;                       // List of deals carried out by symbol
   CTrade            m_trade;                            // Trading class
   string            m_symbol;                           // Symbol name
public:
//--- Return the list of deals
   CArrayObj        *GetListDeals(void)                  { return(&this.m_list_deals);       }
   
//--- Set a symbol
   void              SetSymbol(const string symbol)      { this.m_symbol=symbol;             }
   
//--- (1) Set and (2) returns the number of handled deals
   void              SetNumProcessedDeals(const int num) { this.m_deals_processed=num;       }
   int               NumProcessedDeals(void)       const { return this.m_deals_processed;    }
   
//--- Add a deal to the deal array
   bool              AddDeal(CDeal *deal);
   
//--- Return the deal (1) by time in seconds, (2) by index in the list,
//--- (3) opening deal by position ID, (4) current deal in the list
   CDeal            *GetDealByTime(const datetime time);
   CDeal            *GetDealByIndex(const int index);
   CDeal            *GetDealInByPosID(const long pos_id);
   CDeal            *GetDealCurrent(void);
   
//--- Return (1) the number of deals in the list, (2) the index of the current deal in the list
   int               DealsTotal(void)              const { return this.m_list_deals.Total(); }
   int               DealCurrentIndex(void)        const { return this.m_index_next_deal;    }
   
//--- Return (1) symbol and (2) object description
   string            Symbol(void)                  const { return this.m_symbol;             }
   string            Description(void) const
                       {
                        return ::StringFormat("%s trade object. Total deals: %d", this.Symbol(), this.DealsTotal() );
                       }

//--- Return the current (1) Bid and (2) Ask price, time in (3) seconds, (4) milliseconds
   double            Bid(void);
   double            Ask(void);
   datetime          Time(void);
   long              TimeMsc(void);
   
//--- Open (1) long, (2) short position, (3) close a position by ticket
   ulong             Buy(const double volume, const ulong magic, const double sl, const double tp, const string comment);
   ulong             Sell(const double volume, const ulong magic, const double sl, const double tp, const string comment);
   bool              ClosePos(const ulong ticket);

//--- Return the result of comparing the current time with the specified one
   bool              CheckTime(const datetime time)      { return(this.Time()>=time);        }
//--- Sets the index of the next deal
   void              SetNextDealIndex(void)              { this.m_index_next_deal++;         }
   
//--- OnTester handler. Returns the number of deals processed by the tester.
   double            OnTester(void)
                       {
                        ::PrintFormat("Symbol %s: Total deals: %d, number of processed deals: %d", this.Symbol(), this.DealsTotal(), this.NumProcessedDeals());
                        return this.m_deals_processed;
                       }

//--- Compares two objects to each other (comparison by symbol only)
   virtual int       Compare(const CObject *node, const int mode=0) const
                       {
                        const CSymbolTrade *obj=node;
                        return(this.Symbol()>obj.Symbol() ? 1 : this.Symbol()<obj.Symbol() ? -1 : 0);
                       }
//--- Constructors/destructor
                     CSymbolTrade(void) : m_index_next_deal(0), m_deals_processed(0) {}
                     CSymbolTrade(const string symbol) : m_symbol(symbol), m_index_next_deal(0), m_deals_processed(0)
                       {
                        this.m_trade.SetMarginMode();
                        this.m_trade.SetTypeFillingBySymbol(this.m_symbol);
                       }
                    ~CSymbolTrade(void) {}
  };

Schauen wir uns einige Methoden an.

  • SetNumProcessedDeals() und NumProcessedDeals() setzen und geben die Anzahl der historischen Handelsgeschäfte zurück, die bereits vom Tester aus der Liste der Handelsgeschäfte aus der Datei bearbeitet wurden. Sie sind notwendig, um die Gültigkeit der Bearbeitung von historischen Handelsgeschäften zu überprüfen und um endgültige Statistiken über die Anzahl der vom Tester bearbeiteten Handelsgeschäfte zu erhalten;
  • GetDealCurrent() gibt einen Zeiger auf das aktuelle historische Handelsgeschäft zurück, das vom Tester bearbeitet werden muss und als bearbeitet markiert ist;
  • DealCurrentIndex() gibt den Index des historischen Handelsgeschäfts zurück, das derzeit für die Bearbeitung durch den Tester ausgewählt ist;
  • SetNextDealIndex() setzt nach Abschluss der Bearbeitung des aktuellen historischen Handelsgeschäfts den Index des nächsten vom Tester zu bearbeitenden Handelsgeschäfts. Da alle historischen Handelsgeschäfte in der Liste nach der Zeit in Millisekunden sortiert sind, wird der Index des nächsten Handelsgeschäfts gesetzt, nachdem der Tester die Bearbeitung des vorherigen abgeschlossen hat. Auf diese Weise werden nacheinander alle Handelsgeschäfte in der Historie ausgewählt, die vom Tester zu dem Zeitpunkt bearbeitet werden, der in den Eigenschaften des aktuell ausgewählten Handelsgeschäfts festgelegt ist;
  • CheckTime() prüft den Zeitpunkt des Auftretens der Zeit, die in den Eigenschaften des aktuellen historischen Handelsgeschäfts festgelegt wurde, im Tester. Die Logik ist wie folgt: Es gibt ein ausgewähltes Handelsgeschäft, das im Tester bearbeitet werden muss. Solange die Zeit im Tester kürzer ist als die im Handelsgeschäft aufgezeichnete Zeit, tun wir nichts – wir gehen einfach zum nächsten Tick über. Sobald die Zeit im Tester gleich oder größer als die Zeit im aktuell ausgewählten Handelsgeschäft wird (die Zeit im Tester darf nicht mit der Zeit im Handelsgeschäft übereinstimmen, daher wird die Zeit auch auf „größer“ geprüft), wird das Handelsgeschäft vom Tester abhängig von seinem Typ und der Art der Positionsänderung durch das Handelsgeschäft behandelt. Anschließend wird dieser Vorgang als erledigt markiert, der Index des nächsten Handelsgeschäfts wird gesetzt, und das von der Methode verwaltete Warten geht weiter, allerdings auf das nächste Handelsgeschäft:
  • Die Funktion OnTester() wird von der Standardfunktion OnTester() des EA aufgerufen, zeigt den Symbolnamen im Journal sowie die Anzahl der historischen und vom Tester bearbeiteten Handelsgeschäfte an und gibt die Anzahl der bearbeiteten Handelsgeschäfte nach Handelsobjektsymbol zurück.

Die Klasse hat zwei Konstruktoren – Standard und parametrisch.

In den formalen Parametern des parametrischen Konstruktors wird der Name des Handelsobjekt-Symbols übergeben, der bei der Erstellung des Objekts verwendet werden soll, während das Handelsobjekt der Klasse CTrade den Modus für die Berechnung der Marge entsprechend den aktuellen Kontoeinstellungen sowie die Art der Auftragsausführung entsprechend den Einstellungen des Handelsobjekt-Symbols erhält:

//--- Constructors/destructor
                     CSymbolTrade(void) : m_index_next_deal(0), m_deals_processed(0) {}
                     CSymbolTrade(const string symbol) : m_symbol(symbol), m_index_next_deal(0), m_deals_processed(0)
                       {
                        this.m_trade.SetMarginMode();
                        this.m_trade.SetTypeFillingBySymbol(this.m_symbol);
                       }

Die Methode, die ein Handelsgeschäft zum Array der Handelsgeschäfte hinzufügt:

//+------------------------------------------------------------------+
//| CSymbolTrade::Add a trade to the trades array                    |
//+------------------------------------------------------------------+
bool CSymbolTrade::AddDeal(CDeal *deal)
  {
//--- If the list already contains a deal with the deal ticket passed to the method, return 'true'
   this.m_list_deals.Sort(SORT_MODE_DEAL_TICKET);
   if(this.m_list_deals.Search(deal)>WRONG_VALUE)
      return true;
   
//--- Add a pointer to the deal to the list in sorting order by time in milliseconds
   this.m_list_deals.Sort(SORT_MODE_DEAL_TIME_MSC);
   if(!this.m_list_deals.InsertSort(deal))
     {
      ::PrintFormat("%s: Failed to add deal", __FUNCTION__);
      return false;
     }
//--- All is successful
   return true;
  }

Der Zeiger auf das Deal-Objekt wird an die Methode übergeben. Wenn ein Handelsgeschäft mit einem solchen Ticket bereits in der Liste enthalten ist, wird true zurückgegeben. Andernfalls wird die Liste nach der Handelsgeschäftszeit in Millisekunden sortiert, und das Handelsgeschäft wird der Liste in der Sortierreihenfolge nach Zeit in ms hinzugefügt.

Die Methode, die einen Zeiger auf ein Deal-Objekt nach Zeit in Sekunden zurückgibt:

//+------------------------------------------------------------------+
//| CSymbolTrade::Return the deal object by time in seconds          |
//+------------------------------------------------------------------+
CDeal* CSymbolTrade::GetDealByTime(const datetime time)
  {
   DealTmp.SetTime(time);
   this.m_list_deals.Sort(SORT_MODE_DEAL_TIME_MSC);
   int index=this.m_list_deals.Search(&DealTmp);
   return this.m_list_deals.At(index);
  }

Die Methode erhält die erforderliche Zeit. Wir setzen die an die Methode übergebene Zeit auf das temporäre Deal-Objekt, die Liste wird nach der Zeit in Millisekunden sortiert, und es wird nach dem Index des Deals gesucht, dessen Zeit gleich der an die Methode übergebenen Zeit ist (auf das temporäre Objekt gesetzt). Anschließend wird ein Zeiger auf das Handelsgeschäft in der Liste mit dem gefundenen Index zurückgegeben. Gibt es in der Liste kein Handelsgeschäft mit einer solchen Zeit, so ist der Index gleich -1, während aus der Liste NULL zurückgegeben wird.

Interessant ist, dass das Handelsgeschäft nach der Zeit in Sekunden durchsucht wird, wir aber die Liste nach der Zeit in Millisekunden sortieren. Tests haben gezeigt, dass, wenn die Liste auch nach Sekunden sortiert ist, einige Angebote nicht darin enthalten sind, obwohl sie definitiv existieren. Dies ist höchstwahrscheinlich auf die Tatsache zurückzuführen, dass es in einer Sekunde mehrere Handelsgeschäfte gibt, wobei die Zeit in Millisekunden angegeben wird. Außerdem wird der Zeiger auf ein zuvor abgewickeltes Handelsgeschäft zurückgegeben, da mehrere Transaktionen die gleiche Zeit in Sekunden haben.

Die Methode, die den Zeiger auf die offenen Position nach Positions-ID zurück:

//+------------------------------------------------------------------+
//|CSymbolTrade::Return the opening trade by position ID             |
//+------------------------------------------------------------------+
CDeal *CSymbolTrade::GetDealInByPosID(const long pos_id)
  {
   int total=this.m_list_deals.Total();
   for(int i=0; i<total; i++)
     {
      CDeal *deal=this.m_list_deals.At(i);
      if(deal==NULL || deal.PositionID()!=pos_id)
         continue;
      if(deal.Entry()==DEAL_ENTRY_IN)
         return deal;
     }
   return NULL;
  }

Die Methode erhält die ID der Position, deren Eröffnungs-Deal gefunden werden muss. Als Nächstes wird in einer Schleife durch die Liste der Handelsgeschäfte das Handelsgeschäft ermittelt, dessen Positions-ID gleich der an die Methode übergebenen ist, und ein Zeiger auf das Handelsgeschäft zurückgegeben, dessen Positionsänderungsmethode gleich „Markteintritt“ (DEAL_ENTRY_IN) ist.

Die Methode, die den Zeiger auf das Deal-Objekt nach Index in der Liste zurückgibt:

//+------------------------------------------------------------------+
//| CSymbolTrade::Return the deal object by index in the list        |
//+------------------------------------------------------------------+
CDeal *CSymbolTrade::GetDealByIndex(const int index)
  {
   return this.m_list_deals.At(index);
  }

Wir geben einfach den Zeiger auf das Objekt in der Liste mit dem an die Methode übergebenen Index zurück. Wenn der Index falsch ist, wird NULL zurückgegeben.

Die Methode, die den Zeiger auf das Handelsgeschäft zurück, auf das der aktuelle Handelsgeschäftsindex zeigt:

//+------------------------------------------------------------------+
//| Return the deal pointed to by the current deal index             |
//+------------------------------------------------------------------+
CDeal *CSymbolTrade::GetDealCurrent(void)
  {
   this.m_list_deals.Sort(SORT_MODE_DEAL_TIME_MSC);
   return this.GetDealByIndex(this.m_index_next_deal);
  }

Die Liste der Handelsgeschäfte wird nach Zeit in Millisekunden sortiert, und der Zeiger auf das Handelsgeschäft, dessen Index in die Klassenvariable m_index_next_deal geschrieben wird, wird zurückgegeben.

Die Methode, die den aktuellen Geldkurs (Bid) zurückgibt:

//+------------------------------------------------------------------+
//| CSymbolTrade::Return the current Bid price                       |
//+------------------------------------------------------------------+
double CSymbolTrade::Bid(void)
  {
   ::ResetLastError();
   if(!::SymbolInfoTick(this.m_symbol, this.m_tick))
     {
      ::PrintFormat("%s: SymbolInfoTick() failed. Error %d",__FUNCTION__, ::GetLastError());
      return 0;
     }
   return this.m_tick.bid;
  }

Wir holen uns die Daten des letzten Ticks in die m_tick Preisstruktur und geben den Geldkurs daraus zurück.

Die Methode liefert den aktuellen Briefkurs (Ask):

//+------------------------------------------------------------------+
//| CSymbolTrade::Return the current Ask price                       |
//+------------------------------------------------------------------+
double CSymbolTrade::Ask(void)
  {
   ::ResetLastError();
   if(!::SymbolInfoTick(this.m_symbol, this.m_tick))
     {
      ::PrintFormat("%s: SymbolInfoTick() failed. Error %d",__FUNCTION__, ::GetLastError());
      return 0;
     }
   return this.m_tick.ask;
  }

Wir holen uns die Daten des letzten Ticks in die m_tick Preisstruktur und geben daraus den Ask Preis zurück.

Die Methode gibt die aktuelle Zeit in Sekunden zurück:

//+------------------------------------------------------------------+
//| CSymbolTrade::Return the current time in seconds                 |
//+------------------------------------------------------------------+
datetime CSymbolTrade::Time(void)
  {
   ::ResetLastError();
   if(!::SymbolInfoTick(this.m_symbol, this.m_tick))
     {
      ::PrintFormat("%s: SymbolInfoTick() failed. Error %d",__FUNCTION__, ::GetLastError());
      return 0;
     }
   return this.m_tick.time;
  }

Wir holen uns die Daten des letzten Ticks in die m_tick Preisstruktur und geben die Zeit daraus zurück.

Die Methode gibt die aktuelle Zeit in Millisekunden zurück:

//+------------------------------------------------------------------+
//| CSymbolTrade::Return the current time in milliseconds            |
//+------------------------------------------------------------------+
long CSymbolTrade::TimeMsc(void)
  {
   ::ResetLastError();
   if(!::SymbolInfoTick(this.m_symbol, this.m_tick))
     {
      ::PrintFormat("%s: SymbolInfoTick() failed. Error %d",__FUNCTION__, ::GetLastError());
      return 0;
     }
   return this.m_tick.time_msc;
  }

Wir holen die Daten des letzten Ticks in die m_tick-Preisstruktur und geben die Zeit in Millisekunden zurück.

Die Methode zur Eröffnung einer Kaufposition:

//+------------------------------------------------------------------+
//| CSymbolTrade::Open a long position                               |
//+------------------------------------------------------------------+
ulong CSymbolTrade::Buy(const double volume, const ulong magic, const double sl, const double tp, const string comment)
  {
   this.m_trade.SetExpertMagicNumber(magic);
   if(!this.m_trade.Buy(volume, this.m_symbol, 0, sl, tp, comment))
     {
      return 0;
     }
   return this.m_trade.ResultOrder();
  }

Die Methode empfängt die Parameter der eröffneten Kaufposition, setzt die erforderliche magische Positionsnummer des Handelsobjekts und sendet den Auftrag zur Eröffnung einer Kaufposition mit den angegebenen Parametern. Wenn die Eröffnung einer Position fehlschlägt, wird Null zurückgegeben, während bei Erfolg das Auftragsticket zurückgegeben wird, auf dem die eröffnete Position basiert.

Die Methode zur Eröffnung einer Verkaufsposition:

//+------------------------------------------------------------------+
//| CSymbolTrade::Open a short position                              |
//+------------------------------------------------------------------+
ulong CSymbolTrade::Sell(const double volume, const ulong magic, const double sl, const double tp, const string comment)
  {
   this.m_trade.SetExpertMagicNumber(magic);
   if(!this.m_trade.Sell(volume, this.m_symbol, 0, sl, tp, comment))
     {
      return 0;
     }
   return this.m_trade.ResultOrder();
  }

Ähnlich wie bei der vorherigen Methode, jedoch wird eine Verkaufsposition eröffnet.

Die Methode zur Schließung einer Position per Ticket:

//+------------------------------------------------------------------+
//| CSymbolTrade::Close position by ticket                           |
//+------------------------------------------------------------------+
bool CSymbolTrade::ClosePos(const ulong ticket)
  {
   return this.m_trade.PositionClose(ticket);
  }

Gibt das Ergebnis des Aufrufs der Methode PositionClose() des Handelsobjekts der Klasse CTrade zurück.

Die Klasse der Handelssymbole ist fertig. Nun wollen wir sie in den EA implementieren, um die in der Datei gespeicherten historischen Handelsgeschäfte zu behandeln.


Analyse der Handelshistorie aus der Datei im Tester

Gehen wir nun zur EA-Datei \MQL5\Experts\TradingByHistoryDeals\TradingByHistoryDeals.mq5 und fügen ein temporäres Objekt der neu erstellten Handelssymbolklasse hinzu – es wird benötigt, um das gewünschte Objekt in der Liste zu finden , in der die Zeiger auf solche Objekte gespeichert sind:

//+------------------------------------------------------------------+
//| Expert                                                           |
//+------------------------------------------------------------------+
//--- input parameters
input    string   InpTestedSymbol   =  "";      /* The symbol being tested in the tester        */ 
input    long     InpTestedMagic    =  -1;      /* The magic number being tested in the tester  */ 
sinput   bool     InpShowDataInLog  =  false;   /* Show collected data in the log               */ 

//--- global variables
CSymbolTrade   SymbTradeTmp;
SDeal          ExtArrayDeals[]={};
CArrayObj      ExtListSymbols;

Wir haben ein Array historischer Handelsgeschäfte, auf dessen Grundlage wir eine Liste von Handelsobjekten erstellen können, in der sich Listen von Handelsgeschäften befinden, die zu dem Objektsymbol gehören. Das Deal-Array speichert Strukturen, die Deals beschreiben. Da das Handelsobjekt Listen von Handelsgeschäftsobjekten enthalten wird, müssen wir eine Funktion erstellen, die ein neues Handelsgeschäftsobjekt anlegt und die Handelsgeschäftseigenschaften aus den Feldern der Struktur, die das Handelsgeschäft beschreibt, auffüllt:

//+------------------------------------------------------------------+
//| Create a deal object from the structure                          |
//+------------------------------------------------------------------+
CDeal *CreateDeal(SDeal &deal_str)
  {
//--- If failed to create an object, inform of the error in the journal and return NULL
   CDeal *deal=new CDeal(deal_str.ticket, deal_str.Symbol());
   if(deal==NULL)
     {
      PrintFormat("%s: Error. Failed to create deal object");
      return NULL;
     }
//--- fill in the deal properties from the structure fields
   deal.SetOrder(deal_str.order);               // Order the deal was based on
   deal.SetPositionID(deal_str.pos_id);         // Position ID
   deal.SetTimeMsc(deal_str.time_msc);          // Time in milliseconds
   deal.SetTime(deal_str.time);                 // Time
   deal.SetVolume(deal_str.volume);             // Volume
   deal.SetPrice(deal_str.price);               // Price
   deal.SetProfit(deal_str.profit);             // Profit
   deal.SetCommission(deal_str.commission);     // Deal commission
   deal.SetSwap(deal_str.swap);                 // Accumulated swap when closing
   deal.SetFee(deal_str.fee);                   // Fee for making a deal charged immediately after performing a deal
   deal.SetSL(deal_str.sl);                     // Stop Loss level
   deal.SetTP(deal_str.tp);                     // Take Profit level
   deal.SetType(deal_str.type);                 // Type
   deal.SetEntry(deal_str.entry);               // Position change method
   deal.SetReason(deal_str.reason);             // Deal execution reason or source
   deal.SetMagic(deal_str.magic);               // EA ID
   deal.SetComment(deal_str.Comment());         // Deal comment
   deal.SetExternalID(deal_str.ExternalID());   // Deal ID in an external trading system (on the exchange)
//--- Return the pointer to a created object
   return deal;
  }

Die Funktion empfängt die Handelsgeschäftsstruktur, ein neues Handelsgeschäftsobjekt wird erstellt und seine Eigenschaften werden mit Werten aus den Strukturfeldern gefüllt.
Die Funktion gibt einen Zeiger auf das neu erstellte Objekt zurück. Wenn bei der Erstellung des Objekts ein Fehler auftritt, wird NULL zurückgegeben.

Wir wollen eine Funktion schreiben, die eine Liste von Handelssymbolen erstellt:

//+------------------------------------------------------------------+
//| Create an array of used symbols                                  |
//+------------------------------------------------------------------+
bool CreateListSymbolTrades(SDeal &array_deals[], CArrayObj *list_symbols)
  {
   bool res=true;                      // result
   int total=(int)array_deals.Size();  // total number of deals in the array
   
//--- if the deal array is empty, return 'false'
   if(total==0)
     {
      PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__);
      return false;
     }
   
//--- in a loop through the deal array
   CSymbolTrade *SymbolTrade=NULL;
   for(int i=0; i<total; i++)
     {
      //--- get the next deal and, if it is neither buy nor sell, move on to the next one
      SDeal deal_str=array_deals[i];
      if(deal_str.type!=DEAL_TYPE_BUY && deal_str.type!=DEAL_TYPE_SELL)
         continue;
      
      //--- find a trading object in the list whose symbol is equal to the deal symbol
      string symbol=deal_str.Symbol();
      SymbTradeTmp.SetSymbol(symbol);
      list_symbols.Sort();
      int index=list_symbols.Search(&SymbTradeTmp);
      
      //--- if the index of the desired object in the list is -1, there is no such object in the list
      if(index==WRONG_VALUE)
        {
         //--- we create a new trading symbol object and, if creation fails,
         //--- add 'false' to the result and move on to the next deal
         SymbolTrade=new CSymbolTrade(symbol);
         if(SymbolTrade==NULL)
           {
            res &=false;
            continue;
           }
         //--- if failed to add a symbol trading object to the list,
         //--- delete the newly created object, add 'false' to the result
         //--- and we move on to the next deal
         if(!list_symbols.Add(SymbolTrade))
           {
            delete SymbolTrade;
            res &=false;
            continue;
           }
        }
      //--- otherwise, if the trading object already exists in the list, we get it by index
      else
        {
         SymbolTrade=list_symbols.At(index);
         if(SymbolTrade==NULL)
            continue;
        }
         
      //--- if the current deal is not yet in the list of deals of the symbol trading object
      if(SymbolTrade.GetDealByTime(deal_str.time)==NULL)
        {
         //--- create a deal object according to its sample structure
         CDeal *deal=CreateDeal(deal_str);
         if(deal==NULL)
           {
            res &=false;
            continue;
           }
         //--- add the result of adding the deal object to the list of deals of a symbol trading object to the result value
         res &=SymbolTrade.AddDeal(deal);
        }
     }
//--- return the final result of creating trading objects and adding deals to their lists
   return res;
  }

Die Logik der Funktion ist in den Kommentaren ausführlich beschrieben. Wir analysieren jedes aufeinanderfolgende Handelsgeschäft in einer Schleife durch die Liste der historischen Handelsgeschäfte. Wir prüfen das Symbol, und wenn es noch kein Handelsobjekt für dieses Symbol gibt, erstellen wir ein neues Handelsobjekt und speichern es in der Liste. Wenn es bereits existiert, holen wir uns einfach den Zeiger auf das Symbolhandelsobjekt eines Handelsgeschäfts aus der Liste. Anschließend prüfen wir auf die gleiche Weise, ob ein solches Handelsgeschäft in der Liste der Handelsgeschäfte des Handelsobjekts vorhanden ist, und fügen es der Liste hinzu, wenn es dort nicht vorhanden ist. Als Ergebnis des Durchlaufs durch alle historischen Handelsgeschäfte erhalten wir eine Liste von Handelsobjekten nach Symbolen, die Listen von Handelsgeschäften enthalten, die zu dem Objektsymbol gehören.

Die Liste der Handelsobjekte kann mit der folgenden Funktion an das Journal gesendet werden:

//+------------------------------------------------------------------+
//| Display a list of symbol trading objects in the journal          |
//+------------------------------------------------------------------+
void SymbolsArrayPrint(CArrayObj *list_symbols)
  {
   int total=list_symbols.Total();
   if(total==0)
      return;
   Print("Symbols used in trading:");
   for(int i=0; i<total; i++)
     {
      string index=StringFormat("% 3d", i+1);
      CSymbolTrade *obj=list_symbols.At(i);
      if(obj==NULL)
         continue;
      PrintFormat("%s. %s",index, obj.Description());
     }
  }

In einer Schleife durch die Liste der Handelssymbole wird das nächste Objekt ermittelt und seine Beschreibung im Journal angezeigt. Im Journal sieht das etwa so aus:

Symbols used in trading:
  1. AUDUSD trade object. Total deals: 218
  2. EURJPY trade object. Total deals: 116
  3. EURUSD trade object. Total deals: 524
  4. GBPUSD trade object. Total deals: 352
  5. NZDUSD trade object. Total deals: 178
  6. USDCAD trade object. Total deals: 22
  7. USDCHF trade object. Total deals: 250
  8. USDJPY trade object. Total deals: 142
  9. XAUUSD trade object. Total deals: 118

Wir haben jetzt ein Objekt der Klasse Deal. Fügen wir noch eine Funktion hinzu, die eine Handelsgeschäftsbeschreibung zurückgibt:

//+------------------------------------------------------------------+
//| Return deal description                                          |
//+------------------------------------------------------------------+
string DealDescription(CDeal *deal, const int index)
  {
   string indexs=StringFormat("% 5d", index);
   if(deal.TypeDeal()!=DEAL_TYPE_BALANCE)
      return(StringFormat("%s: deal #%I64u %s, type %s, Position #%I64d %s (magic %I64d), Price %.*f at %s, sl %.*f, tp %.*f",
                          indexs, deal.Ticket(), DealEntryDescription(deal.Entry()), DealTypeDescription(deal.TypeDeal()),
                          deal.PositionID(), deal.Symbol(), deal.Magic(), deal.Digits(), deal.Price(),
                          TimeToString(deal.Time(), TIME_DATE|TIME_MINUTES|TIME_SECONDS), deal.Digits(), deal.SL(), deal.Digits(), deal.TP()));
   else
      return(StringFormat("%s: deal #%I64u %s, type %s %.2f %s at %s",
                          indexs, deal.Ticket(), DealEntryDescription(deal.Entry()), DealTypeDescription(deal.TypeDeal()),
                          deal.Profit(), AccountInfoString(ACCOUNT_CURRENCY), TimeToString(deal.Time())));
  }

Diese Funktion wiederholt vollständig die Logik der gleichen Funktion, die eine Beschreibung der Handelsgeschäftsstruktur zurückgibt. Hier wird jedoch der Zeiger auf das Handelsgeschäftsobjekt an die Funktion übergeben und nicht die Handelsgeschäftsstruktur.

Lassen Sie uns nun die Ereignisbehandlung von OnInit() bis zur logischen Schlussfolgerung erstellen.

Wir fügen die Handhabung des EA-Starts im Tester hinzu, erstellen die Liste der Handelsobjekte und greifen auf jedes Symbol zu, das im Handel verwendet wurde, um seine Historie zu laden und Chart-Fenster für diese Symbole im Tester zu öffnen:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- If the EA is not running in the tester
   if(!MQLInfoInteger(MQL_TESTER))
     {
      //--- prepare a file with all historical deals
      if(!PreparesDealsHistoryFile(ExtArrayDeals))
         return(INIT_FAILED);
         
      //--- print all deals in the journal after loading them from the file 
      if(InpShowDataInLog)
         DealsArrayPrint(ExtArrayDeals);
         
      //--- get the first balance deal, create the message text and display it using Alert
      SDeal    deal=ExtArrayDeals[0];
      long     leverage=AccountInfoInteger(ACCOUNT_LEVERAGE);
      double   start_money=deal.profit;
      datetime first_time=deal.time;
      string   start_time=TimeToString(deal.time, TIME_DATE);
      string   message=StringFormat("Now you can run testing\nInterval: %s - current date\nInitial deposit: %.2f, leverage 1:%I64u", start_time, start_money, leverage);
      
      //--- notify via alert of the recommended parameters of the strategy tester for starting the test
      Alert(message);
     }
//--- The EA has been launched in the tester
   else
     {
      //--- read data from the file into the array
      ulong file_size=0;
      ArrayResize(ExtArrayDeals, 0);
      if(!FileReadDealsToArray(ExtArrayDeals, file_size))
        {
         PrintFormat("Failed to read file \"%s\". Error %d", FILE_NAME, GetLastError());
         return(INIT_FAILED);
        }
         
      //--- report the number of bytes read from the file and writing the deals array in the journal.
      PrintFormat("%I64u bytes were read from the file \"%s\" and written to the deals array. A total of %u deals were received", file_size, FILE_NAME, ExtArrayDeals.Size());
     }
     
//--- Create a list of trading objects by symbols from the array of historical deals
   if(!CreateListSymbolTrades(ExtArrayDeals, &ExtListSymbols))
     {
      Print("Errors found while creating symbol list");
      return(INIT_FAILED);
     }
//--- Print the created list of deals in the journal
   SymbolsArrayPrint(&ExtListSymbols);
   
//--- Access each symbol to start downloading historical data
//--- and opening charts of traded symbols in the strategy tester
   datetime array[];
   int total=ExtListSymbols.Total();

   for(int i=0; i<total; i++)
     {
      CSymbolTrade *obj=ExtListSymbols.At(i);
      if(obj==NULL)
         continue;
      CopyTime(obj.Symbol(), PERIOD_CURRENT, 0, 1, array);
     }
     
//--- All is successful
   return(INIT_SUCCEEDED);
  }

In OnDeinit() des EA löschen wir die erstellten Arrays und Listen mit Hilfe des EA:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- clear the created lists and arrays
   ExtListSymbols.Clear();
   ArrayFree(ExtArrayDeals);
  }

In OnTick() des EA verarbeiten wir die Liste der Handelsgeschäfte aus der Datei im Tester:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- work only in the strategy tester
   if(!MQLInfoInteger(MQL_TESTER))
      return;
      
//---  Handle the list of deals from the file in the tester
   TradeByHistory(InpTestedSymbol, InpTestedMagic);
  }

Betrachten wir diese Funktion im Detail. Im Allgemeinen wurde die Logik für den Umgang mit historischen Handelsgeschäften ursprünglich wie folgt dargestellt:

  1. die Tick-Zeit abrufen,
  2. einen Deal mit einer solchen Zeit abrufen,
  3. ein Handelsgeschäft im Tester abwickeln.

Auf den ersten Blick ist die einfache und logische Struktur bei der Umsetzung völlig gescheitert. Das Problem ist, dass im Tester die Tick-Zeit nicht immer mit der Handelsgeschäftszeit übereinstimmt. Sogar in Millisekunden. Beim Testen aller Ticks auf der Grundlage echter Ticks, die vom selben Server stammen, gingen daher Abschlüsse verloren. Wir könnten die Tick-Zeit kennen, wir könnten sicher wissen, dass es genau zu diesem Zeitpunkt ein Handelsgeschäft gibt, aber der Tester sieht es nicht – es gibt keinen Tick mit der gleichen Zeit wie das Handelsgeschäft. Aber es gibt einen Tick mit der Zeit vor und mit der Zeit nach der Handelsgeschäftszeit. Dementsprechend kann die Logik nicht um Ticks und deren Zeitpunkt, sondern um Handelsgeschäfte aufgebaut werden:

  1. Die Handelsgeschäfte sind in der Liste nach der Zeit ihres Erscheinens in Millisekunden sortiert. Setzt den Index des allerersten Handelsgeschäfts auf den Index des aktuellen Handelsgeschäfts.
  2. Ein Handelsgeschäft über den aktuellen Handelsgeschäftsindex auswählen und seine Zeit abfragen.
  3. Mit dieser Zeit auf einen Tick warten:
    1. Wenn die Tick-Zeit kleiner ist als die Deal-Zeit, wird auf den nächsten Tick gewartet.
    2. Wenn die Tickzeit gleich oder größer ist als die Handelsgeschäftszeit, wird das Handelsgeschäft abgewickelt, die Tatsache registriert, dass es bereits abgewickelt wurde, und der Index des nächsten Handelsgeschäfts als Index des aktuellen gesetzt.
  4. Ab Punkt 2 wiederholen, bis der Test beendet ist.

Diese Struktur ermöglicht es uns, auf den Zeitpunkt jedes nachfolgenden Handelsgeschäfts zu warten und es im Tester auszuführen. In diesem Fall achten wir nicht auf den Preis des Handelsgeschäfts, sondern kopieren es einfach, wenn es soweit ist. Selbst wenn die Tickzeit im Tester etwas später ist als in der Realität, ist das in Ordnung. Die Hauptsache ist, den Handel zu kopieren. Die Tatsache, dass der Vorgang bereits vom Tester bearbeitet wurde, wird durch einen Wert ungleich Null der Eigenschaft „position ticket in tester“ angezeigt. Ist dieser Wert gleich Null, so bedeutet dies, dass dieser Vorgang im Tester noch nicht bearbeitet wurde. Nachdem dieses Handelsgeschäft im Tester ausgeführt wurde, wird das Ticket der Position, zu der dieses Handelsgeschäft im Tester gehört, in diese Eigenschaft eingetragen.

Fügen wir nun die Funktion hinzu, die die oben beschriebene Logik implementiert:

//+------------------------------------------------------------------+
//| Trading by history                                               |
//+------------------------------------------------------------------+
void TradeByHistory(const string symbol="", const long magic=-1)
  {
   datetime time=0;
   int total=ExtListSymbols.Total();   // number of trading objects in the list
   
//--- in a loop by all symbol trading objects
   for(int i=0; i<total; i++)
     {
      //--- get another trading object
      CSymbolTrade *obj=ExtListSymbols.At(i);
      if(obj==NULL)
         continue;
      
      //--- get the current deal pointed to by the deal list index
      CDeal *deal=obj.GetDealCurrent();
      if(deal==NULL)
         continue;
      
      //--- sort the deal by magic number and symbol
      if((magic>-1 && deal.Magic()!=magic) || (symbol!="" && deal.Symbol()!=symbol))
         continue;
      
      //--- sort the deal by type (only buy/sell deals)
      ENUM_DEAL_TYPE type=deal.TypeDeal();
      if(type!=DEAL_TYPE_BUY && type!=DEAL_TYPE_SELL)
         continue;
      
      //--- if this is a deal already handled in the tester, move on to the next one
      if(deal.TicketTester()>0)
         continue;
      
      //--- if the deal time has not yet arrived, move to the next trading object of the next symbol
      if(!obj.CheckTime(deal.Time()))
         continue;

      //--- in case of a market entry deal
      ENUM_DEAL_ENTRY entry=deal.Entry();
      if(entry==DEAL_ENTRY_IN)
        {
         //--- open a position by deal type
         double sl=0;
         double tp=0;
         ulong ticket=(type==DEAL_TYPE_BUY  ? obj.Buy(deal.Volume(), deal.Magic(), sl, tp, deal.Comment()) : 
                       type==DEAL_TYPE_SELL ? obj.Sell(deal.Volume(),deal.Magic(), sl, tp, deal.Comment()) : 0);
         
         //--- if a position is opened (we received its ticket)
         if(ticket>0)
           {
            //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object 
            obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
            deal.SetTicketTester(ticket);
            //--- get the position ID in the tester and write it to the properties of the deal object
            long pos_id_tester=0;
            if(HistoryDealSelect(ticket))
              {
               pos_id_tester=HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
               deal.SetPosIDTester(pos_id_tester);
              }
           }
        }
      
      //--- in case of a market exit deal
      if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT || entry==DEAL_ENTRY_OUT_BY)
        {
         //--- get a deal a newly opened position is based on
         CDeal *deal_in=obj.GetDealInByPosID(deal.PositionID());
         if(deal_in==NULL)
            continue;

         //--- get the position ticket in the tester from the properties of the opening deal
         //--- if the ticket is zero, then most likely the position in the tester is already closed
         ulong ticket_tester=deal_in.TicketTester();
         if(ticket_tester==0)
           {
            PrintFormat("Could not get position ticket, apparently position #%I64d (#%I64d) is already closed \n", deal.PositionID(), deal_in.PosIDTester());
            obj.SetNextDealIndex();
            continue;
           }
         //--- if the position is closed by ticket
         if(obj.ClosePos(ticket_tester))
           {
            //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object 
            obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
            deal.SetTicketTester(ticket_tester);
           }
        }
      //--- if a ticket is now set in the deal object, then the deal has been successfully handled -
      //--- set the deal index in the list to the next deal
      if(deal.TicketTester()>0)
        {
         obj.SetNextDealIndex();
        }
     }
  }

Dieser Code kopiert vollständig den ursprünglichen Handel auf dem Konto, dessen Handelsgeschäfte in der Datei registriert sind. Alle Positionen werden ohne Stop-Order eröffnet. Mit anderen Worten, die Werte für StopLoss und TakeProfit werden nicht von echten Handelsgeschäften in die Positionseröffnungsmethoden kopiert. Dies vereinfacht die Nachverfolgung von Handelsgeschäften, da auch geschlossene Handelsgeschäfte aufgelistet werden und der Tester sie unabhängig davon behandelt, wie die Position geschlossen wurde – durch StopLoss oder TakeProfit.

Kompilieren Sie den EA und starten Sie ihn auf dem Chart. Als Ergebnis wird die Datei HistoryDealsData.bin im freigegebenen Ordner der Client-Terminals mit dem Pfad „C:\Users\UserName\AppData\Roaming\MetaQuotes\Terminal\Common\Files“ im Unterordner „TradingByHistoryDeals“ erstellt, und im Chart wird eine Warnung mit einer Meldung über die gewünschten Testereinstellungen angezeigt:

Führen wir nun den EA im Tester aus, indem wir den angegebenen Datumsbereich, die Ersteinlage und den Hebel in den Testereinstellungen auswählen:

Führen Sie den Test für alle gehandelten Symbole und den magischen Zahlen durch:

Es stellte sich heraus, dass der gesamte Handel uns einen Verlust von 550 USD einbrachte. Ich frage mich, was passieren würde, wenn wir unterschiedliche Stop-Orders setzen würden?

Schauen wir uns das mal an.


Anpassung von Stop-Aufträgen

Speichern Sie den EA in demselben Ordner \MQL5\Experts\TradingByHistoryDeals\ als TradingByHistoryDeals_SLTP.mq5.

Fügen Sie eine Enumeration von Testmethoden hinzu und unterteilen Sie die Eingaben in Gruppen, indem Sie eine Gruppe für die Einstellung von Stop-Order-Parametern sowie zwei neue Variablen auf globaler Ebene hinzufügen, über die StopLoss- und TakeProfit-Werte an Handelsobjekte übergeben werden können:

//+------------------------------------------------------------------+
//|                                   TradingByHistoryDeals_SLTP.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "SymbolTrade.mqh"

enum ENUM_TESTING_MODE
  {
   TESTING_MODE_ORIGIN,    /* Original trading                          */ 
   TESTING_MODE_SLTP,      /* Specified StopLoss and TakeProfit values  */ 
  };

//+------------------------------------------------------------------+
//| Expert                                                           |
//+------------------------------------------------------------------+
//--- input parameters
input    group             "Strategy parameters"
input    string            InpTestedSymbol   =  "";                  /* The symbol being tested in the tester        */ 
input    long              InpTestedMagic    =  -1;                  /* The magic number being tested in the tester  */ 
sinput   bool              InpShowDataInLog  =  false;               /* Show collected data in the log               */ 

input    group             "Stops parameters"
input    ENUM_TESTING_MODE InpTestingMode    =  TESTING_MODE_ORIGIN; /* Testing Mode                                 */ 
input    int               InpStopLoss       =  300;                 /* StopLoss in points                           */ 
input    int               InpTakeProfit     =  500;                 /* TakeProfit in points                         */ 

//--- global variables
CSymbolTrade   SymbTradeTmp;
SDeal          ExtArrayDeals[]={};
CArrayObj      ExtListSymbols;
int            ExtStopLoss;
int            ExtTakeProfit;


Im OnInit()-Handler werden die Werte der durch die Eingaben gesetzten Stop-Orders angepasst und in Variablen geschrieben:

int OnInit()
  {
//--- Adjust the stops
   ExtStopLoss  =(InpStopLoss<1   ? 0 : InpStopLoss);
   ExtTakeProfit=(InpTakeProfit<1 ? 0 : InpTakeProfit);
   
//--- If the EA is not running in the tester

Fügen wir die Funktionen hinzu, die die korrekten Werte für die StopLoss- und TakeProfit-Kurse in Bezug auf das für das Symbol festgelegte StopLevel berechnen:

//+------------------------------------------------------------------+
//| Return correct StopLoss relative to StopLevel                    |
//+------------------------------------------------------------------+
double CorrectStopLoss(const string symbol_name, const ENUM_ORDER_TYPE order_type, const int stop_loss, const int spread_multiplier=2)
  {
   if(stop_loss==0 || (order_type!=ORDER_TYPE_BUY && order_type!=ORDER_TYPE_SELL))
      return 0;
   int lv=StopLevel(symbol_name, spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name, SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name, SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name, SYMBOL_BID) : SymbolInfoDouble(symbol_name, SYMBOL_ASK));
   return
     (order_type==ORDER_TYPE_BUY ?
      NormalizeDouble(fmin(price-lv*pt, price-stop_loss*pt), dg) :
      NormalizeDouble(fmax(price+lv*pt, price+stop_loss*pt), dg)
     );
  }
//+------------------------------------------------------------------+
//| Return correct TakeProfit relative to StopLevel                  |
//+------------------------------------------------------------------+
double CorrectTakeProfit(const string symbol_name, const ENUM_ORDER_TYPE order_type, const int take_profit, const int spread_multiplier=2)
  {
   if(take_profit==0 || (order_type!=ORDER_TYPE_BUY && order_type!=ORDER_TYPE_SELL))
      return 0;
   int lv=StopLevel(symbol_name, spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name, SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name, SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name, SYMBOL_BID) : SymbolInfoDouble(symbol_name, SYMBOL_ASK));
   return
     (order_type==ORDER_TYPE_BUY ?
      NormalizeDouble(fmax(price+lv*pt, price+take_profit*pt), dg) :
      NormalizeDouble(fmin(price-lv*pt, price-take_profit*pt), dg)
     );
  }
//+------------------------------------------------------------------+
//| Return StopLevel in points                                       |
//+------------------------------------------------------------------+
int StopLevel(const string symbol_name, const int spread_multiplier)
  {
   int spread=(int)SymbolInfoInteger(symbol_name, SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(symbol_name, SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread*spread_multiplier : stop_level);
  }

Um StopLoss- und TakeProfit-Levels zu setzen, darf der Stop-Order-Kurs nicht näher als der aktuelle Kurs sein, um den StopLevel-Abstand. Wenn der StopLevel für ein Symbol den Wert Null hat, dann entspricht die verwendete StopLevel-Größe zwei oder manchmal drei für das Symbol festgelegten Spreads. Diese Funktionen verwenden einen Multiplikator zur Verdopplung des Spread. Dieser Wert wird in formalen Parametern von Funktionen übergeben und hat einen Standardwert von 2. Wenn es notwendig ist, den Wert des Multiplikators zu ändern, müssen wir den Funktionen beim Aufruf einen anderen erforderlichen Wert übergeben. Die Funktionen geben die korrekten Preise für StopLoss und TakeProfit zurück.

Fügen Sie in die Handelsfunktion TradeByHistory() neue Codeblöcke ein, die den Handelsmodus im Tester berücksichtigen und StopLoss- und TakeProfit-Werte festlegen, wenn der Test mit den angegebenen Stop-Order-Werten ausgewählt wurde. Im Positionsschließungsblock müssen wir Positionen nur dann schließen, wenn die Testart „ursprünglicher Handel“ ist. Wenn das Testen mit spezifizierten Stop-Order-Werten ausgewählt ist, sollte das Schließen von Handelsgeschäften ignoriert werden – der Tester schließt Positionen automatisch auf der Grundlage der spezifizierten StopLoss- und TakeProfit-Werte. Das Einzige, was wir beim Handel mit Stop-Orders tun müssen, wenn Abschlussgeschäfte abgewickelt werden, ist, sie als abgewickelt zu markieren und zum nächsten Handelsgeschäft überzugehen.

//+------------------------------------------------------------------+
//| Trading by history                                               |
//+------------------------------------------------------------------+
void TradeByHistory(const string symbol="", const long magic=-1)
  {
   datetime time=0;
   int total=ExtListSymbols.Total();   // number of trading objects in the list
   
//--- in a loop by all symbol trading objects
   for(int i=0; i<total; i++)
     {
      //--- get another trading object
      CSymbolTrade *obj=ExtListSymbols.At(i);
      if(obj==NULL)
         continue;
      
      //--- get the current deal pointed to by the deal list index
      CDeal *deal=obj.GetDealCurrent();
      if(deal==NULL)
         continue;
      
      //--- sort the deal by magic number and symbol
      if((magic>-1 && deal.Magic()!=magic) || (symbol!="" && deal.Symbol()!=symbol))
         continue;
      
      //--- sort the deal by type (only buy/sell deals)
      ENUM_DEAL_TYPE type=deal.TypeDeal();
      if(type!=DEAL_TYPE_BUY && type!=DEAL_TYPE_SELL)
         continue;
      
      //--- if this is a deal already handled in the tester, move on to the next one
      if(deal.TicketTester()>0)
         continue;
      
      //--- if the deal time has not yet arrived, move to the next trading object of the next symbol
      if(!obj.CheckTime(deal.Time()))
         continue;

      //--- in case of a market entry deal
      ENUM_DEAL_ENTRY entry=deal.Entry();
      if(entry==DEAL_ENTRY_IN)
        {
         //--- set the sizes of stop orders depending on the stop setting method
         double sl=0;
         double tp=0;
         if(InpTestingMode==TESTING_MODE_SLTP)
           {
            ENUM_ORDER_TYPE order_type=(deal.TypeDeal()==DEAL_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
            sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss);
            tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit);
           }
         //--- open a position by deal type
         ulong ticket=(type==DEAL_TYPE_BUY  ? obj.Buy(deal.Volume(), deal.Magic(), sl, tp, deal.Comment()) : 
                       type==DEAL_TYPE_SELL ? obj.Sell(deal.Volume(),deal.Magic(), sl, tp, deal.Comment()) : 0);
         
         //--- if a position is opened (we received its ticket)
         if(ticket>0)
           {
            //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object 
            obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
            deal.SetTicketTester(ticket);
            //--- get the position ID in the tester and write it to the properties of the deal object
            long pos_id_tester=0;
            if(HistoryDealSelect(ticket))
              {
               pos_id_tester=HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
               deal.SetPosIDTester(pos_id_tester);
              }
           }
        }
      
      //--- in case of a market exit deal
      if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT || entry==DEAL_ENTRY_OUT_BY)
        {
         //--- get a deal a newly opened position is based on
         CDeal *deal_in=obj.GetDealInByPosID(deal.PositionID());
         if(deal_in==NULL)
            continue;

         //--- get the position ticket in the tester from the properties of the opening deal
         //--- if the ticket is zero, then most likely the position in the tester is already closed
         ulong ticket_tester=deal_in.TicketTester();
         if(ticket_tester==0)
           {
            PrintFormat("Could not get position ticket, apparently position #%I64d (#%I64d) is already closed \n", deal.PositionID(), deal_in.PosIDTester());
            obj.SetNextDealIndex();
            continue;
           }
         //--- if we reproduce the original trading history in the tester,
         if(InpTestingMode==TESTING_MODE_ORIGIN)
           {
            //--- if the position is closed by ticket
            if(obj.ClosePos(ticket_tester))
              {
               //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object
               obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
               deal.SetTicketTester(ticket_tester);
              }
           }
         //--- otherwise, in the tester we work with stop orders placed according to different algorithms, and closing deals are skipped
         //--- accordingly, simply increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object
         else
           {
            obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
            deal.SetTicketTester(ticket_tester);
           }
        }
      //--- if a ticket is now set in the deal object, then the deal has been successfully handled -
      //--- set the deal index in the list to the next deal
      if(deal.TicketTester()>0)
        {
         obj.SetNextDealIndex();
        }
     }
  }

Im OnTester() des EA berechnen wir die Gesamtzahl der im Tester abgewickelten Handelsgeschäfte und geben Sie diese zurück:

//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester(void)
  {
//--- calculate and return the total number of deals handled in the tester
   double ret=0.0;
   int total=ExtListSymbols.Total();
   for(int i=0; i<total; i++)
     {
      CSymbolTrade *obj=ExtListSymbols.At(i);
      if(obj!=NULL)
         ret+=obj.OnTester();
     }
   return(ret);
  }

Darüber hinaus hat jedes Symbolhandelsobjekt seine eigene OnTester()-Funktion, die hier aufgerufen wird und seine Daten im Journal ausgibt. Am Ende des Tests erhalten wir die folgenden Meldungen im Testerprotokoll:

2025.01.22 23:49:15.951 Core 1  2025.01.21 23:54:59   Symbol AUDUSD: Total deals: 218, number of processed deals: 216
2025.01.22 23:49:15.951 Core 1  2025.01.21 23:54:59   Symbol EURJPY: Total deals: 116, number of processed deals: 114
2025.01.22 23:49:15.951 Core 1  2025.01.21 23:54:59   Symbol EURUSD: Total deals: 524, number of processed deals: 518
2025.01.22 23:49:15.951 Core 1  2025.01.21 23:54:59   Symbol GBPUSD: Total deals: 352, number of processed deals: 350
2025.01.22 23:49:15.951 Core 1  2025.01.21 23:54:59   Symbol NZDUSD: Total deals: 178, number of processed deals: 176
2025.01.22 23:49:15.951 Core 1  2025.01.21 23:54:59   Symbol USDCAD: Total deals: 22, number of processed deals: 22
2025.01.22 23:49:15.951 Core 1  2025.01.21 23:54:59   Symbol USDCHF: Total deals: 250, number of processed deals: 246
2025.01.22 23:49:15.951 Core 1  2025.01.21 23:54:59   Symbol USDJPY: Total deals: 142, number of processed deals: 142
2025.01.22 23:49:15.951 Core 1  2025.01.21 23:54:59   Symbol XAUUSD: Total deals: 118, number of processed deals: 118
2025.01.22 23:49:15.951 Core 1  final balance 3591.70 pips
2025.01.22 23:49:15.951 Core 1  OnTester result 1902

Kompilieren Sie den EA und führen Sie ihn mit denselben Testeinstellungen aus, legen Sie jedoch als Testtyp „Specified StopLoss and TakeProfit values“ fest und setzen Sie die Werte für StopLoss und TakeProfit auf 100 bzw. 500 Punkte:

Im letzten Test, als wir den ursprünglichen Handel testeten, hatten wir einen Verlust von 550 USD. Wenn wir nun den StopLoss für alle Positionen durch einen Wert von 100 Punkten und den TakeProfit durch 500 Punkte ersetzen, erhalten wir einen Gewinn von 590 Punkten. Dies wurde durch eine einfache Ersetzung von Stop-Orders erreicht, ohne dass die Besonderheiten der verschiedenen gehandelten Symbole berücksichtigt wurden. Wenn wir unsere eigenen Stop-Order-Größen für jedes der gehandelten Symbole wählen, kann das Testdiagramm höchstwahrscheinlich ausgeglichen werden.


Schlussfolgerung

In diesem Artikel haben wir ein kleines Experiment mit einer Handelshistorie im Stil von „Was wäre wenn...“ durchgeführt. Ich denke, dass solche Experimente durchaus zu interessanten Lösungen führen können, um den eigenen Handelsstil zu ändern. Im nächsten Artikel werden wir ein weiteres Experiment dieser Art durchführen, indem wir verschiedene Positions-Trailing-Stops einbeziehen. Die Dinge werden bald noch interessanter werden.

Alle hier besprochenen EAs und Klassen sind unten beigefügt. Sie können sie herunterladen und studieren sowie mit ihnen in Ihrem eigenen Handelskonto experimentieren. Sie können den Archivordner sofort in Ihr MQL5-Client-Terminalverzeichnis entpacken, und alle Dateien werden in den gewünschten Unterordnern abgelegt.

Die Programme dieses Artikels:

#
Name
 Typ Beschreibung
1
SymbolTrade.mqh
Klassenbibliothek
Handelsgeschäftsstruktur und Klassenbibliothek, Symbolhandelsklasse
2
TradingByHistoryDeals.mq5
Expert Advisor
Der EA zum Anzeigen von Handelsgeschäften und Abschlüssen, die auf einem Konto im Tester durchgeführt wurden
3
TradingByHistoryDeals_SLTP.mq5
Expert Advisor
Der EA zum Anzeigen und Ändern von Handelsgeschäften und Abschlüssen, die auf dem Konto im Tester unter Verwendung von StopLoss und TakeProfit durchgeführt werden
4
MQL5.zip
ZIP-Archiv
Das Archiv mit den oben genannten Dateien kann in das MQL5-Verzeichnis des Client-Terminals entpackt werden


Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/16952

Beigefügte Dateien |
SymbolTrade.mqh (53.86 KB)
MQL5.zip (22.3 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (16)
fxsaber
fxsaber | 31 Jan. 2025 in 15:32
Artyom Trishkin #:

Wo liegt der Fehler?

Wenn die Datei vor dem Lesen kleiner als das Array ist, ändert sich die Größe des Arrays nicht.

Ein ähnlicher Fehler kann bei der Verwendung von ArrayCopy auftreten.
Artyom Trishkin
Artyom Trishkin | 31 Jan. 2025 in 15:34
fxsaber #:
Sie ignorieren eine gute Funktion

Was ist der Vorteil?

fxsaber
fxsaber | 31 Jan. 2025 in 15:36
Artyom Trishkin #:

Was ist der Vorteil?

Prägnanz und Geschwindigkeit der Ausführung (ganz auf der MQ-Seite).

fxsaber
fxsaber | 31 Jan. 2025 in 15:52
Artyom Trishkin #:

Zeigen Sie bitte die standardmäßigen selbstdruckenden und selbstausfüllenden Strukturen.

Fast Standard (gemeinsame MQ-Felder).
Artyom Trishkin
Artyom Trishkin | 31 Jan. 2025 in 16:11
fxsaber #:

In der Kürze und Geschwindigkeit der Ausführung (ganz auf der MQ-Seite).

Danke! Vermisst
Artificial Tribe Algorithm (ATA) Artificial Tribe Algorithm (ATA)
In diesem Artikel werden die wichtigsten Komponenten und Innovationen des ATA-Optimierungsalgorithmus ausführlich besprochen. Dabei handelt es sich um eine evolutionäre Methode mit einem einzigartigen dualen Verhaltenssystem, das sich je nach Situation anpasst. ATA kombiniert individuelles und soziales Lernen und nutzt Crossover für Erkundungen und Migration, um Lösungen zu finden, wenn sie in lokalen Optima stecken.
Analyse des Binärcodes der Börsenkurse (Teil I): Ein neuer Blick auf die technische Analyse Analyse des Binärcodes der Börsenkurse (Teil I): Ein neuer Blick auf die technische Analyse
In diesem Artikel wird ein innovativer Ansatz für die technische Analyse vorgestellt, der auf der Umwandlung von Kursbewegungen in Binärcodes beruht. Der Autor zeigt, wie verschiedene Aspekte des Marktverhaltens – von einfachen Preisbewegungen bis hin zu komplexen Mustern – in einer Folge von Nullen und Einsen kodiert werden können.
Neuronale Netze im Handel: Ein hybrider Handelsrahmen mit prädiktiver Kodierung (letzter Teil) Neuronale Netze im Handel: Ein hybrider Handelsrahmen mit prädiktiver Kodierung (letzter Teil)
Wir setzen unsere Untersuchung des hybriden Handelssystems StockFormer fort, das prädiktive Kodierungs- und Verstärkungslernalgorithmen für die Analyse von Finanzzeitreihen kombiniert. Das System basiert auf drei Transformer-Zweigen mit einem diversifizierten Mehrkopf-Aufmerksamkeitsmechanismus (DMH-Attn), der die Erfassung komplexer Muster und Abhängigkeiten zwischen Assets ermöglicht. Zuvor haben wir uns mit den theoretischen Aspekten des Frameworks vertraut gemacht und die DMH-Attn-Mechanismus implementiert. Heute werden wir über die Modellarchitektur und das Training sprechen.
Neuronale Netze im Handel: Ein hybrider Handelsrahmen mit prädiktiver Kodierung (StockFormer) Neuronale Netze im Handel: Ein hybrider Handelsrahmen mit prädiktiver Kodierung (StockFormer)
In diesem Artikel wird das hybride Handelssystem StockFormer vorgestellt, das die Algorithmen von Predictive Coding und dem Reinforcement Learning (RL) kombiniert. Das Framework verwendet 3 Transformer-Zweige mit einem integrierten Diversified Multi-Head Attention (DMH-Attn)-Mechanismus, der das ursprüngliche Aufmerksamkeitsmodul mit einem mehrköpfigen Block des Vorwärtsdurchlaufs verbessert und es ermöglicht, diverse Zeitreihenmuster über verschiedene Teilräume hinweg zu erfassen.