Modellierung von Zeitreihen unter Verwendung nutzerdefinierter Symbole nach festgelegten Verteilungsgesetzen

31 Oktober 2018, 15:37
Aleksey Zinovik
0
137

Inhalt

Einführung

Das MetaTrader 5 Trading Terminal ermöglicht die Erstellung und Verwendung nutzerdefinierter Symbole für die Arbeitsumgebung. Händler haben die Möglichkeit, eigenen Währungspaare und andere Finanzinstrumente zu testen. Der Artikel schlägt Möglichkeiten zum Erstellen und Entfernen nutzerdefinierter Symbole, zum Erzeugen von Ticks und Balken gemäß den angegebenen Verteilungsgesetzen vor.

Es werden auch Methoden zur Simulation eines Trends und verschiedener Chartmuster vorgeschlagen. Vorgeschlagene vorgefertigte Skripte für die Arbeit mit nutzerdefinierten Symbolen mit minimalen Einstellungen ermöglichen es Händlern, die keine MQL5-Programmierkenntnisse haben, das volle Potenzial nutzerdefinierte Symbole zu nutzen.

Erstellen und entfernen von nutzerdefinierten Symbolen

Dieser frühere Artikel bietet eine Möglichkeit, nutzerdefinierte Symbole im Fenster "Symbole" in MetaTrader 5 basierend auf bestehenden Symbolen zu erstellen. 

Wir empfehlen die Automatisierung dieses Prozesses mit einer einfachen Einstellung bei minimaler Konfiguration.

Das Skript hat vier Eingabeparameter:

  • Name des nutzerdefinierten Symbols,
  • Kurzbezeichnung des Währungspaares oder Finanzinstruments,
  • vollständiger Name des Währungspaares oder Finanzinstruments,
  • Kurzname der Basiswährung oder des Finanzinstruments, wenn das Symbol basierend auf dem Basissymbol erstellt wird.
Hier ist der Code des Skripts (das Skript befindet sich in der Datei CreateSymbol.mq5 im Anhang des Artikels):

//+------------------------------------------------------------------+
//|                                                 CreateSymbol.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property script_show_inputs 
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
input string SName="ExampleCurrency";
input string CurrencyName="UCR";
input string CurrencyFullName="UserCurrency";
input string BaseName="EURUSD";
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   ResetLastError();
// Symbol erstellen
   if(!CustomSymbolCreate(SName,"\\Forex"))
     {
      if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))
         Print("Symbol ",SName," already exists!");
      else
         Print("Error creating symbol. Error code: ",GetLastError());
     }
   else
     {
      if(BaseName=="")// Neuerstellen
        {
         // Eigenschaften in Form von Zeichenketten
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && // Basiswährung
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&&                         // Gewinnwährung
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"USD",""))&&                         // Margenwährung
            (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyName,""))&&                      // Beschreiobung des Symbols (vollständiger Name)
            (SetProperty(SName,SYMBOL_BASIS,"","")) &&                                     // Name des zugrunde liegenden Wertpapier des aktuellen Symbols
            (SetProperty(SName,SYMBOL_FORMULA,"","")) &&                                   // die Formel für die Preise des nutzerdefinierten Symbols
            (SetProperty(SName,SYMBOL_ISIN,"","")) &&                                      // der Name des Handelssymbols im ISIN-System
            (SetProperty(SName,SYMBOL_PAGE,"","")) &&                                      // die Website mit Informationen zum Symbol
            // Eigenschaften in Form von Ganzzahlen
            (SetProperty(SName,SYMBOL_CHART_MODE,SYMBOL_CHART_MODE_BID,"")) &&             // angezeigter Charttyp der Bid-Preise
            (SetProperty(SName,SYMBOL_SPREAD,3,"")) &&                                     // Spread
            (SetProperty(SName,SYMBOL_SPREAD_FLOAT,true,"")) &&                            // veränderliches Spread
            (SetProperty(SName,SYMBOL_DIGITS,5,"")) &&                                     // Genauigkeit
            (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,10,"")) &&                           // Tiefe des Orderbuchs
            (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,White,""))&&                        // Hintergrundfarbe des Symbols im Market Watch
            (SetProperty(SName,SYMBOL_TRADE_MODE,SYMBOL_TRADE_MODE_FULL,""))&&             // Typ der Orderdurchführung: Ohne Einschränkung
            (SetProperty(SName,SYMBOL_TRADE_EXEMODE,SYMBOL_TRADE_EXECUTION_INSTANT,""))&&  // Modus des Dealsabschlusses: Sofortige Ausführung
            (SetProperty(SName,,SYMBOL_ORDERS_GTC,""))&&              // Gültigkeitsdauer von StopLoss und TakeProfit Ordern: Gültig bis Abbruch
            (SetProperty(SName,SYMBOL_FILLING_MODE,SYMBOL_FILLING_FOK,""))&&               // Orderausführung: Alles oder Nichts
            (SetProperty(SName,SYMBOL_EXPIRATION_MODE,SYMBOL_EXPIRATION_GTC,""))&&         // Orderauslaufmodus: zeitlich unbeschränkt, bis explizit widerrufen
            (SetProperty(SName,SYMBOL_ORDER_MODE,127,"")) &&                               // Auftragsart: alle Auftragsarten
            (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,SYMBOL_CALC_MODE_FOREX,""))&&        // Methode zur Berechnung des Kontraktwertes
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,false,""))&&                   // Berechnung der Hedgemarge mittels des größeren Teils
            (SetProperty(SName,SYMBOL_SWAP_MODE,SYMBOL_SWAP_MODE_POINTS,""))&&             // Modell der Swapberechnung: Berechnung des Swap in Points
            (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,WEDNESDAY,"")) &&                 // Triple-Swap Tag
            (SetProperty(SName,SYMBOL_OPTION_MODE,0,"")) &&                                // Optionstyp
            (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,"")) &&                               // Optionsrecht
            (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,"")) &&                          // Mindestabstand in Points vom aktuellen Schlusskurs zum Setzen der Stopps
            (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,"")) &&                         // Distanz der Einfrierung der Handelsoperationen (in Points)

            (SetProperty(SName,SYMBOL_START_TIME,0,"")) &&                                 // Zeitpunkt des Handelsbeginns (normalerweise für Futures)
            (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,"")) &&                            // Zeitpunkt des Handelsendes (normalerweise für Futures)
            // Eigenschaften in Form von Dezimalzahlen
            (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,"")) &&                              // Ausübungspreis der Option
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,"")) &&                    // der Mindestpreis der Session
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,"")) &&                    // der Maximalpreis der Session
            (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,"")) &&                   // der Abrechnungspreis der aktuellen Session
            (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,"")) &&                     // aufgelaufener Zins (für Anleihen)
            (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,"")) &&                           // Nennwert (für Anleihen)
            (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,"")) &&                       // Liquiditätsquote (verwendet für besichernde Symbole)
            (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0.00001,"")) &&                      // kleinstmögliche Preisänderung
            (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,1,"")) &&                           // Tickwert
            (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,100000,"")) &&                   // Handelskontraktgröße
            (SetProperty(SName,SYMBOL_POINT,0.00001,"")) &&                                // Pointwert
            (SetProperty(SName,SYMBOL_VOLUME_MIN,0.01,"")) &&                              // das Mindestvolumen für eine Ausführung
            (SetProperty(SName,SYMBOL_VOLUME_MAX,500.00,"")) &&                            // das Maximalvolumen für eine Ausführung
            (SetProperty(SName,SYMBOL_VOLUME_STEP,0.01,"")) &&                             // Minimaler Schritt der Volumenveränderung für Dealsabschluss 

            (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,"")) &&                               // Die maximale zulässige gesamte Volumen von einer offenen Position und schwebende Ordern in einer Richtung (Kauf oder Verkauf) für das Symbol.
            (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,"")) &&                             // Initiale Marge
            (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,"")) &&                         // Maintenance-Marge
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED,100000,"")) &&                         // Die Größe des Kontrakts oder der Marge für einen Lot in umgekehrter Richtung des Symbols. 
            (SetProperty(SName,SYMBOL_SWAP_LONG,-0.7,"")) &&                               // Swapwert beim Kauf
            (SetProperty(SName,SYMBOL_SWAP_SHORT,-1,"")))                                  // Swapwert beim Verkauf
            Print("Symbol ",SName," created successfully");
         else
            Print("Error setting symbol properties. Error code: ",GetLastError());
        }
      else// erstellt mit dem Basissymbol
        {
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && 
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyFullName,"")) && 
            (SetProperty(SName,SYMBOL_BASIS,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_FORMULA,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_ISIN,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_PAGE,"",BaseName)) && 

            (SetProperty(SName,SYMBOL_CHART_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SPREAD,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SPREAD_FLOAT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_DIGITS,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_EXEMODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_ORDER_GTC_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_FILLING_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_EXPIRATION_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_ORDER_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_OPTION_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_START_TIME,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,BaseName)) && 

            (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_POINT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_MIN,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_MAX,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_STEP,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_LONG,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_SHORT,0,BaseName)))
            Print("Symbol ",SName," created successfully");
         else
            Print("Error setting symbol properties. Error code: ",GetLastError());
        }
      if(SymbolSelect(SName,true))
         Print("Symbol ",SName," selected in Market Watch");
      else
         Print("Error selecting symbol in Market Watch. Error code: ",GetLastError());
     }
  }
// Funktion der Einstellungen der Eigenschaften des Symbols
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_STRING SProp,string PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetString(SymName,SProp,PropValue))
         return true;
      else
         Print("Error setting symbol property: ",SProp,". Error code: ",GetLastError());
     }
   else
     {
      string SValue=SymbolInfoString(BaseSymName,SProp);
      if(CustomSymbolSetString(SymName,SProp,SValue))
         return true;
      else
         Print("Error setting symbol property: ",SProp,". Error code: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_INTEGER IProp,long PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetInteger(SymName,IProp,PropValue))
         return true;
      else
         Print("Error setting symbol property: ",IProp,". Error code: ",GetLastError());
     }
   else
     {
      long IValue=SymbolInfoInteger(BaseSymName,IProp);
      if(CustomSymbolSetInteger(SymName,IProp,IValue))
         return true;
      else
         Print("Error setting symbol property: ",IProp,". Error code: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_DOUBLE DProp,double PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetDouble(SymName,DProp,PropValue))
         return true;
      else
         Print("Error setting symbol property: ",DProp,". Error code: ",GetLastError());
     }
   else
     {
      double DValue=SymbolInfoDouble(BaseSymName,DProp);
      if(CustomSymbolSetDouble(SymName,DProp,DValue))
         return true;
      else
         Print("Error setting symbol property: ",DProp,". Error code: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+

Wenden wir uns dem Code des Skripts im Detail zu. Zuerst wird der Versuch unternommen ein Symbol mit der Funktion CustomSymbolCreate zu erzeugen:

if(!CustomSymbolCreate(SName,"\\Forex"))
     {
      if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))
         Print("Symbol ",SName," already exists!");
      else
         Print("Error creating symbol. Error code: ",GetLastError());
     }

Das Symbol wird im Ordner Custom/Forex erstellt. Wenn Sie einen nutzerdefinierten Unterordner (nutzerdefinierte Gruppe) im Ordner Custom erstellen möchten, geben Sie dessen Namen im zweiten Parameter der Funktion CustomSymbolCreate an.

Als nächstes werden die Eigenschaften des erstellten Symbols festgelegt. Wenn der Parameter BasisName nicht definiert ist, werden die nutzerdefinierten Parameter des Symbols gesetzt. Beispielsweise werden die Eigenschaften des Währungspaares EURUSD aufgelistet:

    // Eigenschaften in Form von Zeichenketten
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && // Basiswährung
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&&                         // Gewinnwährung
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"UCR",""))&&                         // Währung der Marge
...

Aus Gründen der Übersichtlichkeit sind die Eigenschaften in Gruppen unterteilt. Zuerst werden die Eigenschaften des Typs String gesetzt, dann Integer und dann Double. Wenn diese Eigenschaften erfolgreich eingestellt wurden, wird eine Meldung über die erfolgreiche Erstellung eines Symbols in das Protokoll geschrieben. Andernfalls wird der Code des Fehlers, der beim Setzen der Symboleigenschaften aufgetreten ist, in das Log geschrieben. 

Wenn der Wert des Parameters BaseName nicht leer ist, werden die Symboleigenschaften aus den Eigenschaften des Basissymbols kopiert, dessen Name durch den Parameter BaseName definiert ist. Beispielsweise könnten es EURUSD, USDCAD, GBPUSD und andere sein.

Die Eigenschaften des Symbols werden durch die Funktion SetProperty festgelegt, die nach dem Code der Funktion im Hauptskript beschrieben wird. Diese Funktion wird in anderen Skripten nicht verwendet, daher wird sie nicht in eine eigene, zu ladende Klasse verschoben.

Für Eigenschaften vom Typ String, Integer und Double werden separate Instanzen der Funktion SetProperty erzeugt. Die Funktionen CustomSymbolSetString, CustomSymbolSetInteger, CustomSymbolSetDouble dienen zum Einstellen der Eigenschaften des nutzerdefinierten Symbols. Die Funktionen SymbolInfoString, SymbolInfoInteger, SymbolInfoDouble werden verwendet, um die Eigenschaften des Basissymbols zu erfrage.

Nach erfolgreicher Einstellung der Eigenschaften des erstellten nutzerdefinierten Symbols wird es in der Marktüberwachung mit der Funktion SymbolSelect ausgewählt:

  if(SymbolSelect(SName,true))
         Print("Symbol ",SName," selected in Market Watch");
      else
         Print("Error selecting symbol in Market Watch. Error code: ",GetLastError());

Um die Grafik des erstellten Symbols zu öffnen, ist es notwendig, Ticks oder Balken in das Symbol zu laden. Das Skript zum Erzeugen der Ticks und Balken wird im Folgenden erläutert. 

Betrachten wir nun den Prozess des Löschens eines nutzerdefinierten Symbols. Wenn wir ein nutzerdefiniertes Symbol löschen möchten, indem wir es in der Registerkarte Symbole auswählen, können wir dies nicht immer tun:

Versuch, ein Symbol zu löschen.

Abb. 1. Versuch, ein Symbol zu löschen, das im Market Watch ausgewählt wurde.

Um ein Symbol zu löschen, muss es aus dem Market Watch entfernt werden — dies geschieht durch Doppelklick auf das Symbol im Fenster Symbole. Gleichzeitig sollte es keine offenen Charts und Positionen für das Symbol geben, das gelöscht werden soll. Daher ist es notwendig, alle Charts und Positionen mit diesem Symbol manuell zu schließen. Dies ist kein schneller Prozess, besonders wenn viele Charts für dieses Symbol geöffnet sind. Hier ist ein Beispiel für das Löschen von Symbolen, das als kleines Skript implementiert ist (das Skript ist an den Artikel in der Datei DeleteSymbol.mq5 angehängt):

//+------------------------------------------------------------------+
//|                                                 DeleteSymbol.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property script_show_inputs 
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
input string SName="ExampleCurrency";
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
   ResetLastError();
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))// wenn das Symbol existiert
     {
      if(!CustomSymbolDelete(SName))// Löschversuch
        {
         if(SymbolInfoInteger(SName,SYMBOL_SELECT))// wenn es im Market Watch ausgewählt wurde
           {
            if(SymbolSelect(SName,false))// Versuch der Deaktivierung und Löschung
              {
               if(!CustomSymbolDelete(SName))
                  Print("Error deleting symbol ",SName,". Error code: ",GetLastError());
               else
                  Print("Symbol ",SName," deleted successfully");
              }
            else
              {
               // Versuch das Chart mit dem Symbol zu schließen
               int i=0;
               long CurrChart=ChartFirst();
               int i_id=0;
               long ChartIDArray[];
               while(CurrChart!=-1)
                 {
                  // Schleife über die Liste der Charts, um die Identifikatoren zu sichern, um Charts mit dem Symbol SName zu öffenen
                  if(ChartSymbol(CurrChart)==SName)
                    {
                     ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1);
                     ChartIDArray[i_id]=CurrChart;
                     i_id++;
                    }
                  CurrChart=ChartNext(CurrChart);
                 }
               // Schließen aller Charts mit dem Symbol SName
               for(i=0;i<i_id;i++)
                 {
                  if(!ChartClose(ChartIDArray[i]))
                    {
                     Print("Error closing chart of symbol ",SName,". Error code: ",GetLastError());
                     return;
                    }
                 }
               // deaktivieren und löschen des Symbols 
               if(SymbolSelect(SName,false))
                 {
                  if(!CustomSymbolDelete(SName))
                     Print("Error deleting symbol ",SName,". Error code: ",GetLastError());
                  else
                     Print("Symbol ",SName," deleted successfully");
                 }
               else
                  Print("Error disabling symbol ",SName," in Market Watch. Error code: ",GetLastError());
              }//end else SymbolSelect 
           } //end if(SymbolSelect(SName,false))
         else
            Print("Error deleting symbol ",SName,". Error code: ",GetLastError());
        }
      else
         Print("Symbol ",SName," deleted successfully");
     }
   else
      Print("Symbol ",SName," does not exist");
  }
//+------------------------------------------------------------------+

Hier ist die Ausführungsreihenfolge des Skripts:

  • Überprüfen wir zunächst, ob ein Symbol mit dem Namen SName vorhanden ist.
  • Wenn das Symbol gefunden wird, versuchen wir, das Symbol mit der Funktion CustomSymbolDelete zu löschen.
  • Wenn das Symbol nicht gelöscht werden konnte, versuchen wir, es im Market Watch mit der Funktion SimbolSelect zu deaktivieren.
  • Wenn das Symbol im Market Watch nicht deaktiviert werden konnte, schließen wir alle geöffneten Charts des Symbols.
Schleifen Sie dazu alle geöffneten Charts durch und speichern Sie die Bezeichner der geöffneten Charts für das Symbol mit dem Namen SName:
               while(CurrChart!=-1)
                 {
                  // Schleife über die Liste der Charts, um die Identifikatoren zu sichern, um Charts mit dem Symbol SName zu öffenen
                  if(ChartSymbol(CurrChart)==SName)
                    {
                     ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1);
                     ChartIDArray[i_id]=CurrChart;
                     i_id++;
                    }
                  CurrChart=ChartNext(CurrChart);
                 }

Schließen aller Charts mit dem Identifikatoren, die im Array ChartIDArray gespeichert sind:

              for(i=0;i<i_id;i++)
                 {
                  if(!ChartClose(ChartIDArray[i]))
                    {
                     Print("Error closing chart of symbol ",SName,". Error code: ",GetLastError());
                     return;
                    }
                 }
  • Nachdem wir alle Charts geschlossen haben, versuchen wir erneut, das Symbol im Market Watch zu deaktivieren und das Symbol zu löschen, ansonsten wird eine Fehlermeldung in Log geschrieben.

Wie man sehen kann, sieht das Skript kein automatisches Schließen von Positionen auf dem ausgewählten Symbol vor. Dies geschieht deshalb, damit ein zufälliger Start des Skripts keine Auswirkungen auf die Handelsoperationen des Nutzers hat. Wenn wir ein Symbol mit offenen Positionen löschen möchten, ist es notwendig, diese vorher manuell zu schließen.

Nun, da die Erstellung und Löschung von Symbolen besprochen wurde, kommen wir jetzt zum Prozess der Erzeugung von Ticks und Balken.

Erzeugen von Ticks und Balken

Nach dem Erstellen des Symbols ist es notwendig, eine Handelshistorie zu laden: Man kann Balken laden und Expert Advisors und Indikatoren mit dem im Strategy Tester integrierten Tick-Erzeugungsmodus testen oder Ticks und Balken laden und Tests basierend auf den geladenen Ticks durchführen. Das Verfahren zum Laden von Ticks und Balken basiert auf vorhandene Preisdaten, wie es in einem früheren Artikel dargestellt wird. Dieser Artikel wird Skripte zur automatischen Generierung von Ticks und Balken nach vorgegebenen Verteilungsgesetzen vorschlagen.

Hier ist der Code zum Erstellen der Balken (die Skriptdatei ist GetCandle.mq5):

//+------------------------------------------------------------------+
//|                                                    GetCandle.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property link      ""
#property version   "1.00"
#property script_show_inputs 

#include </Math/Stat/Beta.mqh>
#include </Math/Stat/Binomial.mqh>
#include </Math/Stat/Cauchy.mqh>
#include </Math/Stat/ChiSquare.mqh>
#include </Math/Stat/Exponential.mqh>
#include </Math/Stat/F.mqh>
#include </Math/Stat/Gamma.mqh>
#include </Math/Stat/Geometric.mqh>
#include </Math/Stat/Hypergeometric.mqh>
#include </Math/Stat/Logistic.mqh>
#include </Math/Stat/Lognormal.mqh>
#include </Math/Stat/NegativeBinomial.mqh>
#include </Math/Stat/NoncentralBeta.mqh>
#include </Math/Stat/NoncentralChiSquare.mqh>
#include </Math/Stat/NoncentralF.mqh>
#include </Math/Stat/NoncentralT.mqh>
#include </Math/Stat/Normal.mqh>
#include </Math/Stat/Poisson.mqh>
#include </Math/Stat/T.mqh>
#include </Math/Stat/Uniform.mqh>
#include </Math/Stat/Weibull.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum Distribution
  {
   Beta,
   Binomial,
   Cauchy,
   ChiSquare,
   Exponential,
   F,
   Gamma,
   Geometric,
   Hypergeometric,
   Logistic,
   Lognormal,
   NegativeBinomial,
   NoncentralBeta,
   NoncentralChiSquare,
   NoncentralF,
   NoncentralT,
   Normal,
   Poisson,
   T,
   Uniform,
   Weibull
  };
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
/*input params*/
input string SName="ExampleCurrency";
input datetime TBegin=D'2018.01.01 00:00:00';    // Anfangszeitpunkt der Erstellung des Balkens
input datetime TEnd=D'2018.02.01 00:00:00';      // Endzeitpunkt der Erstellung des Balkens
input int BarForReplace=1000;                    // Anzahl der Balken, um sie zu ersetzen
input double BaseOCHL=1;                         // Basiswert der Preise OCHL
input double dOCHL=0.001;                        // Schrittweite der Preisänderung für OCHL
input ulong BaseRealVol=10000;                   // Basiswert des Volumens
input ulong dRealVol=100;                        // Schrittweite der Volumenänderung
input ulong BaseTickVol=100;                     // Basiswert des Volumens
input ulong dTickVol=10;                         // Schrittweite der Änderung des Tick-Volumens
input ulong BaseSpread=0;                        // Basiswert des Spreads
input ulong dSpread=1;                           // Schrittweite der Änderung des Spread
input Distribution DistOCHL=Normal;              // Type of distribution for OCHL prices
input Distribution DistRealVol = Normal;         // Verteilungstyp für das Real_Volume
input Distribution DistTickVol = Normal;         // Verteilungstyp des Tick-Volumens
input Distribution DistSpread = Uniform;         // Verteilungstyp des Spread
input bool DiffCandle=false;                     // Erzeugen von Balken eines anderen Typs
input double DistOCHLParam1=0;                   // Parameter 1 der Verteilung der Preise OCHL
input double DistOCHLParam2=1;                   // Parameter 2 der Verteilung der Preise OCHL
input double DistOCHLParam3=0;                   // Parameter 3 der Verteilung der Preise OCHL
input double DistRealParam1=0;                   // Parameter 1 der Verteilung der Real_Volumens
input double DistRealParam2=1;                   // Parameter 2 der Verteilung der Real_Volumens
input double DistRealParam3=0;                   // Parameter 3 der Verteilung der Real_Volumens
input double DistTickParam1=0;                   // Parameter 1 der Verteilung des Tick-Volumens
input double DistTickParam2=1;                   // Parameter 2 der Verteilung des Tick-Volumens
input double DistTickParam3=0;                   // Parameter 3 der Verteilung des Tick-Volumens
input double DistSpreadParam1=0;                 // Parameter 1 der Verteilung des Spread
input double DistSpreadParam2=50;                 // Parameter 2 der Verteilung des Spread
input double DistSpreadParam3=0;                 // Parameter 3 der Verteilung des Spread
input bool FiveDayOfWeek=true;                   // true - keine Ticks an einem Wochenende
/*----Eingabeparameter----*/
int i_bar=0;                                     // Zähler der Minutenbalken
MqlRates MRatesMin[];                            // Array für die Balken als Balken
MqlDateTime  StructCTime;                        // Struktur für die Arbeit mit Zeit
int DistErr=0;                                   // Fehlernummer
bool IsErr=false;                                // Fehler
double DistMass[4];                              // Array für die Werte von OCHL
int ReplaceBar=0;                                // Anzahl der ersetzten Balken
double BValue[1];                                // Array für das Kopieren des letzten Schlusskurses
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   int i=0;                                      // Schleifenzähler
   double MaxVal,MinVal;                         // Hoch und Tief
   int i_max,i_min;                              // Indices des größten und niedrigsten Wertes des Arrays DistMass
   datetime TCurrent=TBegin;
   BValue[0]=BaseOCHL;
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))// Wenn das Symbol existiert
     {
      while(TCurrent<=TEnd)
        {
         if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6)))
              {
               if(StructCTime.day_of_week==0)
                  TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               else
                  TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               if(TCurrent>=TEnd)
                 {
                  if(ReplaceBar==0)
                     Print("No trades in the specified range");
                  return;
                 }
              }
           }
         ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);
         MRatesMin[i_bar].open=0;
         MRatesMin[i_bar].close=0;
         MRatesMin[i_bar].high=0;
         MRatesMin[i_bar].low=0;
         // Eröffnungspreis zuweisen      
         if(i_bar>0)
            MRatesMin[i_bar].open=MRatesMin[i_bar-1].close;
         else
           {
            if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1))
               MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            else
               MRatesMin[i_bar].open=BValue[0];
           }
         // erzeugen von Hoch und Tief
         MaxVal=2.2250738585072014e-308;
         MinVal=1.7976931348623158e+308;
         i_max=0;
         i_min=0;
         for(i=0;i<3;i++)
           {
            DistMass[i]=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            if(IsErrCheck(DistErr)) return;
            if(MaxVal<DistMass[i])
              {
               MaxVal=DistMass[i];
               i_max=i;
              }
            if(MinVal>DistMass[i])
              {
               MinVal=DistMass[i];
               i_min=i;
              }
           }
         if(MaxVal<MRatesMin[i_bar].open)
            MRatesMin[i_bar].high=MRatesMin[i_bar].open;
         else
            MRatesMin[i_bar].high=MaxVal;
         if(MinVal>MRatesMin[i_bar].open)
            MRatesMin[i_bar].low=MRatesMin[i_bar].open;
         else
            MRatesMin[i_bar].low=MinVal;
         // Schlusskurs zuweisen
         for(i=0;i<3;i++)
            if((i!=i_max) && (i!=i_min))
              {
               MRatesMin[i_bar].close=DistMass[i];
               break;
              }
         // Volumen und Spread erzeugen
         MRatesMin[i_bar].real_volume=(long)(BaseRealVol+dRealVol*GetDist(DistRealVol,DistRealParam1,DistRealParam2,DistRealParam3));
         if(IsErrCheck(DistErr)) return;
         MRatesMin[i_bar].tick_volume=(long)(BaseTickVol+dTickVol*GetDist(DistTickVol,DistTickParam1,DistTickParam2,DistTickParam3));
         if(IsErrCheck(DistErr)) return;
         MRatesMin[i_bar].spread=(int)(BaseSpread+dSpread*GetDist(DistSpread,DistSpreadParam1,DistSpreadParam2,DistSpreadParam3));
         if(IsErrCheck(DistErr)) return;
         // sichern der Zeit
         MRatesMin[i_bar].time=TCurrent;
         if(DiffCandle)
           {
            i=MathRand()%5;
            switch(i)
              {
               case 0:// Doji
                 {
                  MRatesMin[i_bar].close=MRatesMin[i_bar].open;
                  break;
                 }
               case 1:// Hammer
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                    }
                  break;
                 }
               case 2:// Star
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                    }
                  break;
                 }
               case 3:// Maribozu
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                    {
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                    }
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                       {
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                       }
                    }
                  break;
                 }
               default: break;
              }
           }
         // Prüfen, ob die Balken ersetzt werden sollten  
         if(i_bar>=BarForReplace-1)
           {
            ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time);
            TCurrent=TCurrent+60;
            BValue[0]=MRatesMin[i_bar].close;
            i_bar=0;
            ArrayFree(MRatesMin);
           }
         else
           {
            i_bar++;
            TCurrent=TCurrent+60;
           }
        }
      if(i_bar>0)
        {
         i_bar--;
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time);
        }
     }
   else
      Print("Symbol ",SName," does not exist");
  }
//+------------------------------------------------------------------+

void ReplaceHistory(datetime DBegin,datetime DEnd)
  {
   ReplaceBar=CustomRatesReplace(SName,DBegin,DEnd,MRatesMin);
   if(ReplaceBar<0)
      Print("Error replacing bars. Error code: ",GetLastError());
   else
      PrintFormat("Price history for period: %s to %s generated successfully. Created %i bars, added (replaced) %i bars",TimeToString(DBegin),TimeToString(DEnd),i_bar+1,ReplaceBar);
  }
//+------------------------------------------------------------------+

double GetDist(Distribution d,double p1,double p2,double p3)
  {
   double res=0;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
   switch(d)
     {
/*Beta-Verteilung*/
      //p1,p2 - erster und zweiter Parameter 
      case Beta: {res=MathRandomBeta(p1,p2,DistErr); break;}
/*Binominalverteilung*/
      //p1 - Anzahl der Tests, p2 - Erfolgswahrscheinlichkeit von jedem Test    
      case Binomial: {res=MathRandomBinomial(p1,p2,DistErr); break;};
/*Cauchy-Verteilung*/
      //p1 - Ortskoeffizient, p2 - Skalenparameter
      case Cauchy: {res=MathRandomCauchy(p1,p2,DistErr); break;};
/*Chi-Quadrat-Verteilung*/
      //p1 - Anzahl der Freiheitsgrade
      case ChiSquare: {res=MathRandomChiSquare(p1,DistErr); break;};
/*Exponentielle Verteilung*/
      //p1 - Parameter der Verteilung (lambda) 
      case Exponential: {res=MathRandomExponential(p1,DistErr); break;};
/*Fisher Verteilung*/
      //p1, p2 - Anzahl der Freiheitsgrade
      case F: {res=MathRandomF(p1,p2,DistErr); break;};
/*Gamma Verteilung*/
      //p1 - Verteilungsparameter (integer), p2 - Skalenparameter
      case Gamma: {res=MathRandomGamma(p1,p2,DistErr); break;};
/*Geometrische Verteilung*/
      //p1 - Erfolgswahrscheinlichkeit (Auftrittswahrscheinlichkeit des Ereignisses im Test)
      case Geometric: {res=MathRandomGeometric(p1,DistErr); break;};
/*Hypergeometrische Verteilung*/
      //p1 - Gesamtzahl der Objekte, p2 - Anzahl der Objekte mit der gesuchten Eigenschaft, p3 - Anzahl der Objekte in der Stichprobe
      case Hypergeometric: {res=MathRandomHypergeometric(p1,p2,p3,DistErr); break;};
/*Logistische Verteilung*/
      //p1 - Erwartungswert, p2 - Skalenparameter 
      case Logistic: {res=MathRandomLogistic(p1,p2,DistErr); break;};
/*Lognormale Verteilung*/
      //p1 - Logarithmus des Erwartungswerts, p2 - Logarithmus der Standardabweichung
      case Lognormal: {res=MathRandomLognormal(p1,p2,DistErr); break;};
/*Negative Binomialverteilung*/
      //p1 - Anzahl der erfolgreichen Tests, p2 - Erfolgswahrscheinlichkeit  
      case NegativeBinomial: {res=MathRandomNegativeBinomial(p1,p2,DistErr); break;};
/*Nichtzentrale Beta Verteilung*/
      //p1,p2 - erster und zweiter Parameter, p3 - Nichtzentralitätsparameter       
      case NoncentralBeta: {res=MathRandomNoncentralBeta(p1,p2,p3,DistErr); break;};
/*Nichtzentrale Chi-squared Verteilung*/
      //p1 - Anzahl der Freiheitsgrade, p2 - Nichtzentralitätsparameter
      case NoncentralChiSquare: {res=MathRandomNoncentralChiSquare(p1,p2,DistErr); break;};
/*Nichtzentrale F-Verteilung*/
      //p1, p2 - Anzahl der Freiheitsgrade, p3 - Nichtzentralitätsparameter
      case NoncentralF: {res=MathRandomNoncentralF(p1,p2,p3,DistErr); break;};
/*Nichtzentrale t-Verteilung*/
      //p1 - Anzahl der Freiheitsgrade, p2 - Nichtzentralitätsparameter
      case NoncentralT: {res=MathRandomNoncentralT(p1,p2,DistErr); break;};
/*Normalverteilung*/
      //p1 - Erwartungswert, p2 - Standardabweichung
      case Normal: {res=MathRandomNormal(p1,p2,DistErr); break;};
/*Poissonverteilung*/
      //p1 - Erwartungswert
      case Poisson: {res=MathRandomPoisson(p1,DistErr); break;};
/*Studentische t-Verteilung*/
      //p1 - Anzahl der Freiheitsgrade
      case T: {res=MathRandomT(p1,DistErr); break;};
/*Uniforme Verteilung*/
      //p1 - Untergrenze der Spanne, p2 - Obergrenze der Spanne
      case Uniform: {res=MathRandomUniform(p1,p2,DistErr); break;};
/*Weibull-Verteilung*/
      //p1 -  Formparameter, p2 - Skalenparameter
      case Weibull: {res=MathRandomWeibull(p1,p2,DistErr); break;};
     }
   if(DistErr!=0)
      return -1;
   else
      return res;
  }
//+------------------------------------------------------------------+
bool IsErrCheck(int Err)
  {
// Fehlerprüfung beim Erstellen der Zufallszahlen
   switch(DistErr)
     {
      case(1):
        {
         MessageBox("Specified distribution parameters are not real numbers","Input parameters error",MB_ICONWARNING);
         return true;
        }
      case(2):
        {
         MessageBox("Specified distribution parameters are invalid","Input parameters error",MB_ICONWARNING);
         return true;
        }
      case(4):
        {
         MessageBox("Zero divide error","Input parameters error",MB_ICONWARNING);
         return true;
        }
     }
   return false;
  }
//+------------------------------------------------------------------+

Schauenh wir uns das Skript an. Das Skript verwendet Dateien der Standardbibliothek aus dem Verzeichnis Statistics, die verschiedene statistische Verteilungen bereitstellen. Die Enumeration Distribution wurde erstellt, um das Verteilungsgesetz (Typ) für die Erzeugung der Pseudozufallszahlen auszuwählen, um die Werte für jeden Balken zu ermitteln.

Die Pseudozufallszahlen werden verwendet, um die Preise für Close, High, Low, dem Realvolumen, dem Tickvolumen und die Spreads nach der folgenden Formel zu generieren:

form1 (1)

wobei P(i) - Parameterwert, Base - Basiswert des Parameters, step - Skalenkoeffizient (Schrittweite) der Änderung der Pseudozufallsvariablen, DistValue(i) - erzeugte Pseudozufallsvariable, die nach dem angegebenen Gesetz verteilt ist. Die Parameter Base und step werden vom Nutzer festgelegt. Betrachten wir zum Beispiel den Code für die Bildung des Eröffnungspreises:

         if(i_bar>0)
            MRatesMin[i_bar].open=MRatesMin[i_bar-1].close;
         else
           {
            if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1))
               MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            else
               MRatesMin[i_bar].open=BValue[0];
           }

Fehlen die Balken vor dem Start des Skripts oder konnte die Funktion CopyClose den letzten Balken der Preishistorie aus irgendeinem Grund nicht kopieren, wird der Eröffnungspreis des ersten Balkens nach der oben beschriebenen Formel gebildet:

  • BaseOCHL - Basiswert für OCHL-Preise,
  • dOCHL - Skalierungsfaktor der OCHL-Preisänderungen.

Für nachfolgende Balken entspricht der Eröffnungspreis dem vorherigen Schlusskurs, d.h. ein neuer Balken öffnet sich am Ende des vorherigen. 

Die Erzeugung des Wertes der Pseudozufallsvariablen wird von der Funktion GetDist übernommen, die die Art der Verteilung und die Werte ihrer Parameter als Eingabe nimmt.

Die Funktion IsErrCheck wurde entwickelt, um Fehler zu behandeln, die bei der Generierung der Pseudozufallsvariablen auftreten. Als Eingabe erhält die Funktion den Fehlercode, der bei der Ausführung der Funktion GetDist ermittelt wurde. Wenn ein Fehler auftritt, wird die Ausführung des Skripts unterbrochen und eine Fehlermeldung ins Log geschrieben. Der Code der Funktionen GetDist und IsErrCheck ist am Ende des Skripts angegeben.   

Das Skript generierte Minuten-Ticks und verfügt über die folgenden Funktionen:

1) Ticks werden nur im angegebenen Zeitraum generiert, es ist möglich, Ticks am Wochenende nicht zu generieren (Eingabeparameter FiveDayOfWeek=true).

Die Deaktivierung der Tick-Erzeugung am Wochenende ist im folgenden Code implementiert:

if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6)))
              {
               if(StructCTime.day_of_week==0)
                  TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               else
                  TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               if(TCurrent>=TEnd)
                 {
                  if(ReplaceBar==0)
                     Print("No trades in the specified range");
                  return;
                 }
              }
           }

Fällt die aktuelle Zeit in ein Wochenende, wird alles auf den nächsten Handelstag verschoben. 

2) Das Skript ermöglicht das Ersetzen von Balken in Teilen und gibt Speicherplatz frei, nachdem die erzeugten Balken ersetzt wurden.

Die Anzahl der Balken, nach denen das Array der erzeugten Ticks auf Null gesetzt wird, wird in der Variablen BarForReplace angegeben. Das Ersetzen von Balken ist im folgenden Code implementiert:

if(i_bar>=BarForReplace)
           {
            ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
            i_bar=0;
            ArrayFree(MRatesMin);
           }

Die Funktion ReplaceHistory ersetzt die Balken, ihr Code wird am Ende des Skripts angegeben. Das Ersetzen der Balken erfolgt durch die Funktion CustomRatesReplace. Nach dem Ersetzen der Balken wird der Zähler auf Null gesetzt und der Puffer des dynamischen Arrays MRatesMin, das die erstellten Balken speichert, wird freigegeben. 

3) Das Skript erlaubt es, Balken verschiedener Typen zu erzeugen.

Die Erzeugung verschiedener Balkentypen ist wie folgt implementiert (Parameter DiffCande = true):

 if(DiffCandle)
           {
            i=MathRand()%5;
            switch(i)
              {
               case 0:// Doji
                 {
                  MRatesMin[i_bar].close=MRatesMin[i_bar].open;
                  break;
                 }
               case 1:// Hammer
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                    }
                  break;
                 }
               case 2:// Star
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                    }
                  break;
                 }
               case 3:// Maribozu
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                    {
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                    }
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                       {
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                       }
                    }
                  break;
                 }
               default: break;
              }
           }

So erlaubt das Skript das Erzeugen von normalen langen oder kurzen Balken, "Doji", "Hammer", "Star" und "Maribozu" mit der gleichen Wahrscheinlichkeit.

Demonstrieren wir den Ablauf des Skripts demonstrieren. Führen Sie dazu das Skript mit den folgenden Eingabeparametern aus:

Eingaben

Abb. 1. Eingabeparameter des Skripts

Als Ergebnis der Skriptausführung erscheint ein neues Symbol, ExampleCurrency. 33121 Minutenbalken wurden im Prozess der Skriptausführung erzeugt. Abbildung 2 zeigt ein Fragment des Minutendiagramms des Symbols ExampleCurrency.

ExChart

Abb. 2. Minutendiagramm des Symbols ExampleCurrency

Manchmal kann es zu wenig Minutenbalken geben, um einen Expert Advisor oder einen Indikator zu testen, und die Prüfung wird mit echten oder simulierten Ticks durchgeführt. 

Betrachten wir ein Skript, das Ticks simuliert und Minutenbalken basierend auf simulierten Ticks generiert. Der vollständige Code des Skripts ist in der Datei GetTick.mq5 im Anhang des Artikels verfügbar. Hier ist der Code der Funktion OnStart() mit Beschreibungen:

void OnStart()
  {
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))// wenn das Symbol existiert
     {
      MqlDateTime  StructCTime;
      long TBeginMSec=(long)TBegin*1000;      // Anfangszeitpunkt der Tickerzeugung in ms
      long TEndMSec=(long)TEnd*1000;          // Endzeitpunkt der Tickerzeugung in ms
      int ValMsec=0;                          // Variable für die Erzeugung eines zufälligen Zeitabstandes in ms
      int SumSec=0;                           // Sekundenzähler
      int SumMSec=0;                          // Millisekundenzähler
      int PrevTickCount=0;                    // Variable zur Aufnahme der vorherigen Anzahl von Ticks in einer Minute
      datetime TCurrent=TBegin;
      bool   NewMinute=false;
       // Kopieren des Preises bei dem das Erzeugen der Ticks beginnt
      if(CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1)
         BValue[0]=Base;
      // Zuweisen der Struktur LastTick
      LastTick.ask=BValue[0];
      LastTick.bid=BValue[0];
      LastTick.last=BValue[0];
      LastTick.volume=baseVol;

      while(TBeginMSec<=TEndMSec)
        {
         if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if((StructCTime.day_of_week==0) || (StructCTime.day_of_week==6))
              {
               if(StructCTime.day_of_week==0)
                 {
                  TCurrent=TCurrent+86400;
                  TBeginMSec=TBeginMSec+86400000;
                 }
               else
                 {
                  TCurrent=TCurrent+2*86400;
                  TBeginMSec=TBeginMSec+2*86400000;
                 }
               if(TBeginMSec>=TEndMSec)
                  break;
              }
           }
         GetTick(TCurrent,TBeginMSec);
         if(IsErrCheck(DistErr)) return;
         i_tick++;

         if(RandomTickTime)
           {
            // Erzeugen eines zufälligen Zeitabstandes
            
            ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1);
            SumSec=SumSec+ValMsec;
            SumMSec=SumMSec+ValMsec;
            if(i_tick-PrevTickCount>=MaxTickInMinute)
              {
               TimeToStruct(TCurrent,StructCTime);
               StructCTime.sec=0;
               TCurrent=StructToTime(StructCTime)+60;
               TBeginMSec=TBeginMSec+60000-SumSec+ValMsec;
               SumSec=0;
               SumMSec=0;
               NewMinute=true;
              }
            else
              {
               if(SumSec>=60000)
                 {
                  // Zähler der Ticks auf Null setzen je Minute
                  SumSec=SumSec-60000*(SumSec/60000);
                  NewMinute=true;
                 }
               // Erstellen einer neuen Tick-Zeit    
               TBeginMSec=TBeginMSec+ValMsec;
               if(SumMSec>=1000)
                 {
                  TCurrent=TCurrent+SumMSec/1000;
                  SumMSec=SumMSec-1000*(SumMSec/1000);
                 }
              }
           }
         else
           {
            TBeginMSec=TBeginMSec+60000/MaxTickInMinute;
            SumSec=SumSec+60000/MaxTickInMinute;
            SumMSec=SumMSec+60000/MaxTickInMinute;
            if(SumMSec>=1000)
              {
               TCurrent=TCurrent+SumMSec/1000;
               SumMSec=SumMSec-1000*(SumMSec/1000);
              }
            if(SumSec>=60000)
              {
               SumSec=SumSec-60000*(SumSec/60000);
               NewMinute=true;
              }
           }
         if(NewMinute)
           {
            // Dem Array einen neuen Balken hinzufügen 
            ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);
            if(ArraySize(MRatesMin)==1)// wenn es die erste Minute ist
              {
               MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick;
              }
            else
              {
               MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount;
              }
            MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits);
            if(ValHigh>MRatesMin[i_bar].open)
               MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits);
            else
               MRatesMin[i_bar].high=MRatesMin[i_bar].open;
            if(ValLow<MRatesMin[i_bar].open)
               MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits);
            else
               MRatesMin[i_bar].low=MRatesMin[i_bar].open;
            MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume;
            MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point);
            TimeToStruct(MTick[i_tick-1].time,StructCTime);
            StructCTime.sec=0;
            MRatesMin[i_bar].time=StructToTime(StructCTime);
            i_bar++;
            PrevTickCount=i_tick;
            ValHigh=2.2250738585072014e-308;
            ValLow=1.7976931348623158e+308;
            NewMinute=false;
            if(i_bar>=BarForReplace)
              {
               ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
               LastTick.bid=MTick[i_tick-1].bid;
               LastTick.ask=MTick[i_tick-1].ask;
               LastTick.last=MTick[i_tick-1].last;
               LastTick.volume=MTick[i_tick-1].volume;
               i_tick=0;
               i_bar=0;
               PrevTickCount=0;
               ArrayFree(MRatesMin);
               ArrayFree(MTick);
              }
           }
        }//end while
      if(i_bar>0)
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
     }
   else
      Print("Symbol ",SName," does not exist");
  }

Die Variablen werden zu Beginn der Funktion OnStart() initialisiert. Das Erstellen der Ticks und Balken geschieht in der Hauptschleife des Skripts:

while(TBeginMSec<=TEndMSec)
{
...
}

Zu Beginn der Schleife, wenn FiveDayOfWeek = true, ist das Erstellen von Ticks am Wochenende deaktiviert, während sich die Tick-Zeit um 1 Tag (wenn die Tick-Zeit Sonntag entspricht) oder 2 Tage (wenn die Tick-Zeit Samstag entspricht) verschiebt:

if(FiveDayOfWeek)
{
...
}

Danach wird zum Erstellen der Ticks die Funktion GetTick verwendet:

 GetTick(TCurrent,TBeginMSec);
	if(IsErrCheck(DistErr)) return;
 	i_tick++;

Tritt beim Erzeugen der Ticks ein Fehler auf (Wert der Funktion IsErrCheck = true), wird die Skriptausführung unterbrochen. Die Funktion IsErrCheck ist oben im Code der Funktion GetCandle beschrieben.

Betrachten wir die Funktion GetTick:

void GetTick(datetime TDate,long TLong)
  {
   ArrayResize(MTick,ArraySize(MTick)+1);
// Zuweisen der neuen Zeit  
   MTick[i_tick].time=TDate;
   MTick[i_tick].time_msc=TLong;
// Zuweisen der Werte des vorherigen Ticks zum aktuellen Tick
   if(ArraySize(MTick)>1)
     {
      MTick[i_tick].ask=MTick[i_tick-1].ask;
      MTick[i_tick].bid=MTick[i_tick-1].bid;
      MTick[i_tick].volume=MTick[i_tick-1].volume;
      MTick[i_tick].last=MTick[i_tick-1].last;
     }
   else
     {
      MTick[i_tick].ask=LastTick.ask;
      MTick[i_tick].bid=LastTick.bid;
      MTick[i_tick].last=LastTick.last;
      MTick[i_tick].volume=LastTick.volume;
     }
// Zuweisungen des aktuellen Ticks  
   if(RandomTickValue)
     {
      double RBid=MathRandomUniform(0,1,DistErr);
      double RAsk=MathRandomUniform(0,1,DistErr);
      double RVolume=MathRandomUniform(0,1,DistErr);
      if(RBid>=0.5)
        {
         if(i_tick>0)
            MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
         MTick[i_tick].last=MTick[i_tick].bid;
         MTick[i_tick].flags=10;
         if(RAsk>=0.5)
           {
            MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
            MTick[i_tick].flags=MTick[i_tick].flags+4;
           }
         if(RVolume>=0.5)
           {
            MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
            MTick[i_tick].flags=MTick[i_tick].flags+16;
           }
        }
      else
        {
         if(RAsk>=0.5)
           {
            MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
            MTick[i_tick].flags=4;
            if(RVolume>=0.5)
              {
               MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
               MTick[i_tick].flags=MTick[i_tick].flags+16;
              }
           }
        }
     }//end if(RandomTickValue)
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
   else
     {
      MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
      MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
      MTick[i_tick].last=MTick[i_tick].bid;
      MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
      MTick[i_tick].flags=30;
     }//end if(RandomTickValue)  
  // Sichern des Höchst - und des Tiefstwertes für das Erstellen des Minutenbalkens  
   if(MTick[i_tick].bid>ValHigh)
      ValHigh=MTick[i_tick].bid;
   if(MTick[i_tick].bid<ValLow)
      ValLow=MTick[i_tick].bid;
  }//end 

Als Eingabe nimmt die Funktion die Tickzeit im Datum/Uhrzeitformat und in Millisekunden an. Zuerst wird der aktuelle Tick mit den Werten des vorherigen Tick gefüllt, dann werden die aktuellen Tick-Werte wie folgt geändert:

1) Wenn der Parameterwert RandomTickValue wahr ist, wird jeder der Parameter Ask, Bid und Volume mit einer Wahrscheinlichkeit von 0.5 geändert. Dazu werden 3 gleichmäßig verteilte Zufallsvariablen erzeugt:

      double RBid=MathRandomUniform(0,1,DistErr);
      double RAsk=MathRandomUniform(0,1,DistErr);
      double RVolume=MathRandomUniform(0,1,DistErr);

Wenn RBid>0.5, RAsk>0.5 - Bid und/oder Ask werden gemäß der beschriebenen Formel (1) und/oder für das Skript GetCandle geändert. Die Volumenänderung erfolgt nach der Formel (1), wenn sich Bid oder Ask geändert hat und das RV-Volumen > 0.5. Dem letzten Preis wird bei jeder Änderung dem Bid zugewiesen.

2) Wenn RandomTickValue = falsch, werden die Werte der Parameter Ask, Bid und Volume nach Formel (1) berechnet. 

Der Wert des Ticks-Flags (Flags) werden wie folgt gesetzt:

  • geändertes Bid - flags=flags+2
  • geändertes Ask - flags=flags+4
  • geändertes Last - flags=flags+8
  • geändertes Volume - flags=flags+16

Nach der Änderung des Werte von Ask, Bid oder Volumen werden die Maximal- und Minimalwerte der Bids in den Variablen ValHigh und ValLow gespeichert. Der Wert der Variablen ValHigh und ValLow wird zur Generierung von Hoch und Tief Minutenbalken verwendet. 

Denken wir weiter über den Code der Funktion OnStart() nach.

Sobald der aktuelle Tick generiert wurde, wird die neue Tick-Auftrittszeit gebildet:

1) Wenn der Parameter RandomTickTime = true, wird die neue Tickzeit wie folgt gebildet:

ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1);

Anschließend überprüft die Funktion das Auftreten eines neuen Minutenbalkens und passt die aktuelle Zeit um die generierte Anzahl von Sekunden an. Die Anzahl der Ticks, die während eines einzelnen Minutenbalkens erzeugt werden können, wird durch die Variable MaxTickInMinute begrenzt. Wenn die Anzahl der erzeugten Ticks den Wert der Variablen MaxTickInMinute überschreitet, werden die Zähler von Sekunden (SumSec) und Millisekunden (SumMSec) auf Null gestellt und ein neuer Minutenbalken gebildet (NewMinute = true). 

2) Wenn der Parameter RandomTickTime = false, dann wird in jeder Minutenleiste die gleiche Anzahl von Ticks erzeugt, die in der Variablen MaxTickInMinute angegeben sind.

Der Minutenbalken basierend auf den erzeugten Ticks wird wie folgt ausgeführt:

  • das Array der Minutenbalken wird um 1 erhöht

 ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);

  • ein neuer Eröffnungspreis und Tick-Volumen des aktuellen Balkens wird erzeugt:

            if(ArraySize(MRatesMin)==1)// wenn es die erste Minute ist
              {
               MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick;
              }
            else
              {
               MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount;
              }

Beim Bilden des ersten Minutenbalkens erhält der Eröffnungspreis den Bid-Preis des vorherigen Tick aus dem vorherigen Tick-Ersatz (das Ersetzen von Ticks und Barren erfolgt durch die Funktion ReplaceHistory) oder den Basiswert des Bid-Preises (Parameter Base), falls das Ersetzen von Balken und Ticks noch nicht durchgeführt wurde. Beim Bilden der nachfolgenden Minutenbalken wird dem Eröffnungspreis der normierte Wert des Bid-Preises aus dem letzten Tick (Schlusskurs) der Vorminute zugeordnet. Die Normalisierung gilt als Rundung auf die Messgenauigkeit des Symbols auf der aktuellen Grafik, auf der das Skript läuft. 

  • Der Schlusskurswert wird gebildet - der normalisierte Bid-Preis des letzten Ticks:

MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits);

  • Es werden Hoch und Tief gebildet. Dabei wird berücksichtigt, dass der Eröffnungspreis größer als der höchste (ValHigh) oder kleiner als der kleinste (ValLow) Bid-Preis der erzeugten Ticks sein kann:

 if(ValHigh>MRatesMin[i_bar].open)
     MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits);
 else
     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
 if(ValLow<MRatesMin[i_bar].open)
     MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits);
 else
     MRatesMin[i_bar].low=MRatesMin[i_bar].open;

  • Wert und Spread werden gebildet:
MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume;
MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point);

Dem Volumen des aktuellen Balkens wird der Wert des Volumens des letzten Tick zugewiesen, der Spread berechnet sich aus der Differenz zwischen Ask und Bid des letzten Tick. 

  • Die Öffnungszeit des Balkens wird dem Erstellungszeitwert des letzten Häkchens mit dem Wert für nullte Sekunden zugeordnet:
TimeToStruct(MTick[i_tick-1].time,StructCTime);
StructCTime.sec=0;
MRatesMin[i_bar].time=StructToTime(StructCTime);

Damit ist der Prozess der Bildung der Parameter der aktuellen Minutenleiste abgeschlossen, der Zähler der Minutenbalken (Variable i_bar) wird erhöht, den Variablen ValHigh und ValLow werden die Minimal- und Maximalwerte des Datentyps double zugewiesen, das Flag der Minutenbalken (NewMinute) wird zurückgesetzt. Als nächstes wird geprüft, ob es an der Zeit ist, die gebildete Anzahl von Minutenstrichen und Häkchen zu ersetzen. Die Anzahl der Stangen, nach deren Bildung sie ersetzt werden, wird in der Variablen BarForReplace eingestellt. 

Nach dem Verlassen der Hauptschleife werden die restlichen Balken ersetzt, falls vorhanden:

     if(i_bar>0)
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);

Das Ersetzen von Ticks und Balken ist in der Funktion ReplaceHistory implementiert. Die Funktion CustomTicksReplace ersetzt die Ticks, die Funktion CustomRatesReplace die Balken.

Das oben beschriebene Skript GetTick ermöglicht es also, Ticks nach den angegebenen Verteilungsgesetzen zu erzeugen und aus den Ticks Minutenbalken zu bilden und die erzeugten Daten in die Preishistorie des nutzerdefinierten Symbols zu übertragen.

Simulieren des Trends

Die im vorherigen Abschnitt betrachteten Skripte GetCandle und GetTick ermöglichen es, die Preisentwicklung ohne starke Preisschwankungen, d.h. die einer Seitwärtsbewegung, zu generieren. Um komplexere Marktsituationen zu bilden und Expert Advisors und Indikatoren anhand dieser zu testen, ist es notwendig, eine Trendpreisbewegung zu simulieren.

Zu diesem Zweck wurden die Skripte GetCandleTrend (angehängt an den Artikel in der Datei GetCandleTrend.mq5) und GetTickTrend (angehängt an den Artikel in der Datei GetTickTrend.mq5) erstellt. Sie ermöglichen die Simulation von auf- und absteigenden Trends nach einem bestimmten Gesetz der Preisbewegung. Das Skript GetCandleTrend wurde entwickelt, um steigende oder sinkende Minutenbalken zu erzeugen. Das Skript GetTickTrend erzeugt steigende oder fallende Ticks und bildet dann Minutenbalken, ähnlich dem Skript GetCandleTrend

Betrachten wir die Funktionsweise des Skripts GetCandleTrend. Die Generierung von Minutenbalken ist ähnlich wie im Skript GetCandle, daher wird nur die Methode zur Trendgenerierung berücksichtigt. Die Skript-Eingabedaten enthalten die folgenden Parameter des Trends:

input TrendModel TModel = Linear;                // Trendmodel
input TrendType TType =  Increasing;             // Trendtype (steigend/fallend, zufällig)
input double RandomTrendCoeff=0.5;               // Trendkoeffizient (wenn RandomTrendCoeff<0.5 überwiegt ein fallender Trend; wenn RandomTrendCoeff>0.5 - steigend)
input double Coeff1=0.1;                         // Koeffizient k1 des Trendmodels
input double Coeff2=0.1;                         // Koeffizient k2 des Trendmodels
input double Coeff3=0.1;                         // Koeffizient k3 des Trendmodels
input int CountCandle=60;                        // Intervall der Zufallsänderung der Trendrichtung (in Balken)

Der Nutzer wird aufgefordert, eines in der Enumeration TrendModel möglichen Trendmodelle auszuwählen:

enum TrendModel
  {
   Linear,
   Hyperbolic,
   Exp,
   Power,
   SecondOrderPolynomial,
   LinearAndPeriodic,
   LinearAndStochastic
  };

und der Typ des Trends über die Enumeration TrendType:

enum TrendType
  {
   Increasing,
   Decreasing,
   Random
  };

Der Trend wird nach folgenden Modellen gebildet: linear, hyperbolisch, exponentiell, Potenz, parabolisch, linear periodisch und linear stochastisch. Die Formeln zur Generierung des Trends sind in Tabelle 1 aufgeführt:

TrendmodellFormel
LinearLinear
Hyperbolischf2
Exponentiellf3
Potenzf4
Parabolischf5
Linear periodischf6
Linear stochastischf7

T(i) - der aktuelle Trendwert; k1, k2, k3 - Koeffizienten, die die Steigung des Trends (steigen, fallen) beeinflussen; N(0,1) - Zufallsvariable, die nach dem normalen Gesetz mit dem erwarteten Wert von Null und der Einheitsvarianz verteilt ist.

Als Trendtyp kann der Nutzer zwischen steigendem, fallendem oder zufälligem Trend wählen. Ein zufälliger Trend ist ein Trend, dessen Richtung sich nach einer bestimmten Anzahl von Kerzen ändert (die Anzahl der Kerzen wird im Parameter Countcandle eingestellt). 

Die Trendbildung wird in der Funktion ChooseTrend implementiert:

double ChooseTrend()
  {
   switch(TType)
     {
      case 0: return NormalizeDouble(BValue[0]+dOCHL*(GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
      case 1: return NormalizeDouble(BValue[0]+dOCHL*(-GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
      case 2:
        {
         if((i_trend%CountCandle==0) && (i_trend!=0))
           {
            if(i_bar!=0)
               BValue[0]=MRatesMin[i_bar-1].close;
            LastRand=MathRandomUniform(0,1,DistErr);
            i_trend=0;
           }
         if(LastRand>RandomTrendCoeff)
            return NormalizeDouble(BValue[0]+dOCHL*(GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
         else
            return NormalizeDouble(BValue[0]+dOCHL*(-GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
        }
      default:return 0;
     }
  }

Im Falle eines zufälligen Trends wird für jeden N-Minuten-Balken, die im Parameter CountCandle eingestellt ist, eine Zufallsvariable LastRand erzeugt, die gleichmäßig im Bereich von 0 bis 1 verteilt ist. Wenn der Wert LastRand größer als der Parameter RandomTrendCoeff ist, steigt der Trend, ansonsten - fällt er. Die Wahrscheinlichkeit RandomTrendCoeff ermöglicht die Variation der Wahrscheinlichkeit von Trendänderungen. Wenn RandomTrendCoeff<0.5 gibt es überwiegend einen Aufwärtstrend, wenn RandomTrendCoeff>0.5 — ein Abwärtstrend. 

Die Generierung eines Trends mit dem angegebenen Modell ist in der Funktion GetModelTrend implementiert:

double GetModelTrend()
  {
   switch(TModel)
     {
      case Linear: return Coeff1+Coeff2*i_trend;
      case Hyperbolic:
        {
         if(i_trend==0)
            return Coeff1;
         else
            return Coeff1+Coeff2/i_trend;
        }
      case Exp:
        {
         if(i_trend==0)
            return Coeff1;
         else
            return Coeff1+MathExp(Coeff2*i_trend);
        }
      case Power:return Coeff1+MathPow((double)i_trend,Coeff2);
      case SecondOrderPolynomial:return Coeff1+Coeff2*i_trend+Coeff3*i_trend*i_trend;
      case LinearAndPeriodic: return Coeff1*i_trend+sin(Coeff2*i_trend)+cos(Coeff3*i_trend);
      case LinearAndStochastic:
        {
         LastValue=Coeff1*i_trend+MathSqrt(Coeff2*(1-MathPow(exp(-Coeff3),2)))*MathRandomNormal(0,1,DistErr)+exp(-Coeff3)*LastValue;
         return LastValue;
        }
      default:
         return -1;
     }
  }

In dieser Funktion werden die in Tabelle 1 dargestellten Trendmodelle implementiert.

Betrachten wir die Erstellung von Preisdiagrammen für verschiedene Trendmodelle. Wir erzeugen einen linearen Trend, indem wir das Skript GetTrendCandle mit den folgenden Parametern ausführen:

Eingaben2

Abb. 3. Parameter des Skripts GetTrendCandle

Nach Ausführung des Skripts, eröffnen wir das Minutenchart des Symbols ExampleCurrency:

Linear

Abb. 4. Minutenchart des Symbols ExampleCurrency des Modells eines linearen Trends

Aus der Grafik ist ersichtlich, dass ein linearer Trend gebildet wurde, der Steigungswinkel des Trends (steigend/fallend) kann durch Variation der Koeffizienten k1 und k2 verändert werden. 

Legen wir das hyperbolische Trendmodell für das Skript fest: TModel = Hyperbolisch, Koeffizient1 = 1, Koeffizient2 = 1000. Nach dem Ausführen des Skripts wird das folgende Chart angezeigt:

Parameter

Abb. 4. Minutencharts des Symbols ExampleCurrency für das hyperbolische Trendmodell

Dieses Modell hat diese Eigenschaften: Da die hyperbolische Funktion zur Klasse der inversen Funktionen gehört, wird der Trend bei der Wahl eines aufsteigenden Trends (TType = Increasing) fallen. 

Betrachten wir das exponentielle Trendmodell: TModel =Exp, Coeff1 = 1, Coeff2 = 0.1. Nach dem Ausführen des Skripts wird das folgende Chart angezeigt:

exp

Abb. 5. Minutenchart des Symbols ExampleCurrency für das exponentielle Trendmodell

Wie zu erwarten, zeigt die Grafik einen exponentiell steigenden Trend mit zunehmender Größe der Kerzen.

Betrachten wir andere Trendmodelle:

Power-Trendmodell: TModel =Power, Coeff1 = 1, Coeff2 = 2. 

Potenz

Abb. 6. Minutenchart des Symbols ExampleCurrency für das Potenz-Trendmodell

Es ist zu erkennen, dass das Diagramm dem exponentiellen Trendmodell ähnlich ist, aber sanfter ansteigt.

Parabolisches Trendmodell: TModel = SecondOrderPolynomial, Coeff1 = 1, Coeff2 = 0.05, Coeff3 = 0.05. 

Parabolisch

Abb. 7. Minutenchart des Symbols ExampleCurrency für das parabolische Trendmodell

Das parabolische Modell ist ähnlich dem Potenz-Modell, aber die Änderung des Steigungswinkels des Trends ist größer.

Linear-periodische Trendmodel: TModel = LinearAndPeriodic, Coeff1 = 0.05, Coeff2 = 0.1, Coeff3 = 0.1.

Periodisch

Abb. 8. Minutenchart des Symbols ExampleCurrency des Modells eines linear parabolischen Trends 

Im linearen periodischen Modell ändert der Trend mit zunehmender oder abnehmender Tendenz seine Richtung nach dem periodischen Gesetz.

Linear stochastisches Trendmodell: TModel = LinearAndStochastic, Coeff1 = 0.05, Coeff2 = 1, Coeff3 = 1.

Stochastisch

Abb. 8. Minutenchart des Symbols ExampleCurrency des Modells eines linear parabolischen Trends 

Im linear stochastischen Modell erhöht oder verringert sich der Trend durch zufällige Schwankungen um eine Gerade herum, deren Steigung durch den k1-Koeffizienten definiert ist.

Die oben diskutierten Trendmodelle haben die spezifizierten Eigenschaften nur in winzigen Zeitfenstern. Die für diese Modelle generierten Preisdiagramme sehen aus wie lineare Funktionen auf den Zeitfenstern M15, M30. H1 und höher. Um einen Trend zu erhalten, der seine Richtung in anderen Zeitfenstern als M1 ändert, ist es notwendig, den zufälligen Trendtyp (TType = Random) auszuwählen und die Anzahl der Minutenbalken anzugeben, nach denen versucht wird, die Trendrichtung zu ändern.

Wir führen das Skript mit den folgenden Parametern aus: TModel = LinearAndStochastic, TType = Random, Coeff1 = 0.05, Coeff2 = 1, Coeff3 = 1, RandomTrendCoeff=0.5, CountCandle=60. Die folgende Tabelle über den Zeitrahmen von H1 wird angezeigt:

H1_1

Abb. 9. Stundenchart des Symbols ExampleCurrency mit zufällig sich ändernden Trends

Setzen Sie den Parameter RandomTrendCoeff = 0.7 und starten Sie das Skript:

H1_low

Abb. 10. Stundenchart des Symbols ExampleCurrency mit zufällig sich ändernden Trends mit RandomTrendCoeff = 0.7

Wie man sieht ist ein fallender Trend vorherrschend, ändern wir RandomTrendCoeff auf 0.3 erhalten wir einen steigenden Trend:

H1_high

Abb. 10. Stundencharts des Symbols ExampleCurrency mit zufälliger Trendänderung mit RandomTrendCoeff = 0.3

So ist es möglich, einen Trend in höheren Zeitrahmen mit dem Skript GetCandleTrend zu simulieren und doch Minutenbalken zu erzeugen. 

Das Skript GetTickTrend ermöglicht das Erzeugen von Ticks und deren Verwendung zur Bildung von Minutenbalken. Es hat auch die gleichen Funktionen wie das Skript GetCandleTrend.

Simulieren von Chartmuster

Chartmuster werden in der technischen Analyse des Marktes häufig verwendet. Viele Händler verwenden typische Muster, um nach Markteintritts- oder Ausstiegspunkten zu suchen. Außerdem werden verschiedene Indikatoren und Experten entwickelt, um Muster auf dem Preisdiagramm zu analysieren. 

In diesem Abschnitt wird gezeigt, wie Sie mit den oben beschriebenen Skripten Diagrammmuster erstellen können. Betrachten Sie als Beispiel den Prozess der Erstellung der Muster "Double Top" und "Double Bottom". Das Aussehen der Muster ist in den folgenden Abbildungen dargestellt:

Muster2

Abb. 11. "Double Top" Muster

Muster2

Abb. 12. "Double Bottom" Muster

Das Skript GetCandleTrend wird zum Erstellen von Mustern verwendet. Die Muster werden auf dem Zeitrahmen H1 gebildet. Um jedes Muster zu bilden, ist es notwendig, das Skript GetCandleTrend viermal mit verschiedenen Eingabeparametern auszuführen. Wählen Sie die folgenden Zeitintervalle, die in den Abbildungen 11 und 12 als t1-t5 angezeigt werden:

  • t1 - 00:00 02.01.2018
  • t2 - 13:00 02.01.2018
  • t3 - 13:00 03.01.2018
  • t4 - 13:00 04.01.2018
  • t5 - 00:00 05.01.2018

Legen Sie die folgenden Skripteinstellungen fest, um das Muster "Double Top" zu erzeugen:

  • Startzeit der Balkengenerierung: 00:00 02.01.2018
  • Ende der Balkengenerierung: 12:00 02.01.2018
  • Trendmodell: LinearAndStochastisch
  • Trendtyp: Zufällig
  • Trendkoeffizient: 0.15 
  • Koeffizient k1 des Trendmodells: 0.15
  • Koeffizient k2 des Trendmodells: 1 
  • Koeffizient k3 des Trendmodells: 1 
  • Intervall der zufälligen Änderung in der Trendrichtung: 60
Belassen Sie die Standardwerte der übrigen Parameter und führen Sie das Skript aus. Als nächstes ändern Sie für den zweiten, dritten und vierten Durchlauf des Skripts die folgenden Einstellungen:

Lauf #2:

  • Startzeit der Balkengenerierung: 13:00 02.01.2018
  • Ende der Balkengenerierung: 12:00 03.01.2018
  • Trendkoeffizient: 0.85 
Lauf #3:

  • Startzeit der Balkengenerierung: 13:00 02.01.2018
  • Ende der Balkengenerierung: 12:00 03.01.2018
  • Trendkoeffizient: 0.15 
Run #4:

  • Startzeit der Balkengenerierung: 13:00 02.01.2018
  • Ende der Balkengenerierung: 00:00 03.01.2018
  • Trendkoeffizient: 0.85 

Dadurch erhalten wir nach viermaligem Ausführen des Skripts GetCandleTrend das in Abbildung 13 dargestellte Preischart.

2Top

Abb. 13. Simuliertes "Double Top"-Muster auf dem Zeitrahmen H1

Das "Double Bottom"-Muster wird ähnlich simuliert. Führen Sie dazu das Skript GetCandleTrend viermal mit den für das Muster "Double Top" angegebenen Einstellungen aus und ändern Sie nur den Trendfaktor: 0.85 für den ersten Lauf, 0.15, 0.85, 0.15, 0.15 für die nächsten. Das Ergebnis des Skripts ist in Abbildung 14 dargestellt.

2Bottum

Abb. 14. Simuliertes "Double Bottom"-Muster auf dem Zeitrahmen H1

Ebenso ist es möglich, andere Muster zu simulieren. Die realistischsten Muster werden auf einem Minutendiagramm dargestellt. Um Muster für andere Zeitrahmen zu bilden, ist es notwendig, die Anzahl der Minutenbalken, die im ausgewählten Zeitrahmen enthalten sind, im Parameter "Interval of random change in the trend direction" des Skripts GetCandleTrend anzugeben. Zum Beispiel für den Zeitrahmen H1 - 60. für den Zeitrahmen H4 - 240.

Schlussfolgerung

Nutzerdefinierte Symbole sind ein praktisches und nützliches Werkzeug zum Testen von Experten und Indikatoren. In diesem Artikel wurden Skripte mit den folgenden Features erstellt und berücksichtigt:

1) Erstellen und entfernen von nutzerdefinierten Symbolen

Es werden Methoden zum Erstellen eines nutzerdefinierten Symbols basierend auf einem bestehenden oder neuen Symbol mit den manuell festgelegten Eigenschaften angezeigt. Das Skript, das das Entfernen eines Symbols implementiert, ermöglicht es, alle Charts mit dem Symbol zu schließen und aus der Market Watch zu entfernen.

2) Erzeugen von Ticks und Balken

Die Skripte ermöglichen die Generierung von Minutenbalken im angegebenen Zeitintervall, mit der Möglichkeit, Balken an Wochenendtagen (Nicht-Handelstagen) zu generieren. Es wird die Erzeugung verschiedener Balken implementiert: lange oder kurze Balken, "Doji", "Hammer", "Stern" und "Maribozu". Das Ersetzen von Balken und Ticks in Teilen wird ebenfalls implementiert, um Speicherplatz zu sparen, wenn große Arrays von Balken und Ticks erzeugt werden.

3) Simulieren des Trends

Die Skripte ermöglichen die Simulation des Trends nach verschiedenen Modellen: linear, hyperbolisch, exponentiell, Potenz, parabolisch, linear periodisch und linear stochastisch. Es ist auch möglich, einen Trend über verschiedene Zeitrahmen zu simulieren.

4) Simulieren von Chartmuster

Ein Beispiel zeigt die Verwendung des Skripts GetCandleTrend zum Erstellen der Muster "Double Top" und "Double Bottom".

Die vorgeschlagenen Skripte können verwendet werden, um eine nutzerdefinierte Preishistorie aus Minutenbalken und Ticks zu erstellen, Experten und Indikatoren zu testen und zu optimieren. 


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

Beigefügte Dateien |
CreateSymbol.mq5 (28.79 KB)
DeleteSymbol.mq5 (6.96 KB)
GetCandle.mq5 (33.06 KB)
GetCandleTrend.mq5 (40.66 KB)
GetTick.mq5 (39.14 KB)
GetTickTrend.mq5 (46.32 KB)
Automatisierte Optimierung eines EAs mit dem MetaTrader 5 Automatisierte Optimierung eines EAs mit dem MetaTrader 5

Der Artikel beschreibt die Implementation eines selbst-optimierenden Mechanismus unter dem MetaTrader 5.

Verwendung von Indikatoren zur Optimierung von Expert Advisors in Echtzeit Verwendung von Indikatoren zur Optimierung von Expert Advisors in Echtzeit

Die Effizienz eines jeden Handelsroboters hängt von der richtigen Auswahl seiner Parameter ab (Stichwort Optimierung). Jedoch können Parameter, die sich für ein Zeitintervall als optimal erwiesen, ihre Wirksamkeit in einer anderen Zeitspanne der Handelshistorie nicht bestätigen. Außerdem erweisen sich EAs, die während der Tests Gewinne zeigen, als verlustbringend in Echtzeit. Damit rückt das Thema der kontinuierlichen Optimierung in den Vordergrund. Bei vielen Routinearbeiten suchen Menschen immer nach Möglichkeiten, diese zu automatisieren. In diesem Artikel schlage ich einen nicht standardisierten Ansatz zur Lösung dieses Problems vor.

Umkehrung: Der heilige Gral oder eine gefährliche Täuschung? Umkehrung: Der heilige Gral oder eine gefährliche Täuschung?

In diesem Artikel werden wir die umkehrende Martingaltechnik studieren und versuchen zu verstehen, ob es sich lohnt, sie zu verwenden, sowie, ob sie helfen kann, Ihre Handelsstrategie zu verbessern. Wir werden einen Expert Advisor einrichten, der mit historischen Daten arbeitet und prüft, welche Indikatoren für die Umkehrtechnik am besten geeignet sind. Wir werden auch prüfen, ob es ohne Indikator als unabhängiges Handelssystem eingesetzt werden kann. Darüber hinaus werden wir prüfen, ob die Umkehrung ein verlustbringendes Handelssystem in ein profitables umwandeln kann.

Hier sind der neue MetaTrader 5 und MQL5 Hier sind der neue MetaTrader 5 und MQL5

Dies ist nur ein kurzer Überblick über MetaTrader 5. Ich kann nicht alle neuen Funktionen des Systems in so kurzer Zeit beschreiben. Die Tests begannen am 09.09.2009. Das ist ein symbolisches Datum und ich bin sicher, dass es eine Glückszahl werden wird. Es sind ein paar Tage vergangen, seit ich die Beta-Version des MetaTrader-5-Terminals und MQL5 bekommen habe. Ich konnte noch nicht alle Funktionen ausprobieren, doch ich bin jetzt schon beeindruckt.