
Modellierung von Zeitreihen unter Verwendung nutzerdefinierter Symbole nach festgelegten Verteilungsgesetzen
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.
//+------------------------------------------------------------------+ //| 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:
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.
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:
(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:
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.
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:
Trendmodell | Formel |
---|---|
Linear | |
Hyperbolisch | |
Exponentiell | |
Potenz | |
Parabolisch | |
Linear periodisch | |
Linear stochastisch |
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:
Abb. 3. Parameter des Skripts GetTrendCandle
Nach Ausführung des Skripts, eröffnen wir das Minutenchart des Symbols ExampleCurrency:
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:
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:
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.
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.
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.
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.
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:
Abb. 9. Stundenchart des Symbols ExampleCurrency mit zufällig sich ändernden Trends
Setzen Sie den Parameter RandomTrendCoeff = 0.7 und starten Sie das Skript:
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:
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:
Abb. 11. "Double Top" Muster
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
Lauf #2:
- Startzeit der Balkengenerierung: 13:00 02.01.2018
- Ende der Balkengenerierung: 12:00 03.01.2018
- Trendkoeffizient: 0.85
- Startzeit der Balkengenerierung: 13:00 02.01.2018
- Ende der Balkengenerierung: 12:00 03.01.2018
- Trendkoeffizient: 0.15
- 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.
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.
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 Ltd.
Originalartikel: https://www.mql5.com/ru/articles/4566





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.