English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Programmierungsgrundlagen für MQL5 Zeit

Programmierungsgrundlagen für MQL5 Zeit

MetaTrader 5Beispiele | 27 Juni 2016, 16:06
7 487 0
Dmitry Fedoseev
Dmitry Fedoseev

Inhalt


Einleitung

MQL5 bietet eine Vielzahl einfacher Funktionen zur Arbeit mit Zeit. Mit ihnen vertraut zu werden ist sicherlich auch für Sie nicht schwer. Es müssen nicht viele Aufgaben erledigt werden, um mit Zeit und Datum arbeiten zu können. Die hauptsächlichen Aufgaben sind:

  • Gewisse Handlungen zu einem gegebenen Zeitpunkt ausführen (Abb. 1). Das können Handlungen sein, die jeden Tag immer zur gleichen Uhrzeit oder zu einer festgelegten Tageszeit ausgeführt werden. Oder jede Woche zu einem festen Wochentag oder einfach an einem festgelegten Datum und zu einer bestimmten Uhrzeit.

    Abb. 1 Zeitpunkt.
    Abb. 1 Zeitpunkt.

  • Zur Aktivierung oder Deaktivierung bestimmter Handlungen während eines gegebenen Zeitraums (zeitlich begrenzte Sitzung). Das kann eine zeitlich begrenzte Sitzung innerhalb des Tages (täglich von einem Zeitpunkt bis zum nächsten), die Aktivierung/Deaktivierung bestimmter Handlungen an bestimmten Wochentagen, zeitlich begrenzte Sitzungen von einer bestimmten Zeit an einem Wochentag bis zu einer bestimmten Zeit an einem anderen Wochentag und einfach alle Handlungen umfassen, die in eine bestimmte Datums- und Zeitspanne fallen.

    Abb. 2 Zeitspanne.
    Abb. 2 Zeitspanne.

In der Praxis ist die Verwendung von Zeit reichlich komplex. Die Schwierigkeiten haben vor allem mit den Besonderheiten der Zeitmessung und der Arbeitsumgebung von Expert Advisors und Indikatoren zu tun:

  • Verpasste Bars auf dem Chart aufgrund nicht vorhandener Kurswechsel. Sie sind vor allem in kürzeren Zeitrahmen offensichtlich: M1, M5 und sogar M15. Verpasste Bars kann man jedoch auch in längeren Zeitrahmen beobachten.

  • Notierungen von einigen Handelszentren umfassen Bars an Sonntagen, die eigentlich zum Montag gehören sollten.

  • Wochenenden. Der Wochentag, der einem Montag vorausgeht ist Freitag und eben nicht Sonntag, Und auf einem Freitag folgt Montag, anstatt Samstag.

  • Zusätzlich zu Bars von Sonntagen, liefern einige Handelszentren ständig Notierungen, auch über das gesamte Wochenende. Kursaktivitäten sind also das gesamte Wochenende über präsent, obwohl sie, im Vergleich zu Wochentagen, ziemlich gering sind.

  • Unterschiedliche Zeitzonen zwischen dem Handelsserver und einem lokalen Computer (Computer des Händlers und Handelsterminal). Die Serverzeit unterschiedlicher Handelszentren kann ebenfalls variieren.

  • Sommerzeit.

Dieser Beitrag beginnt daher mit einigen allgemeinen theoretischen Betrachtungen zu Zeit. Danach sehen wir uns die Standard MQL5-Funktionen zur Arbeit mit Zeit an und betrachten gleichzeitig einige Programmiertechniken. Zum Abschluss werden praktische Probleme gelöst.

Dieser Beitrag ist ganz schön lang geworden. Programmierungsanfänger, die sich erst seit kurzem mit MQL5 beschäftigen, werden also kaum alle angesprochenen Punkte auf einmal schaffen können. Dazu sind mindestens 3 Tage vonnöten.


Besonderheiten der Zeitmessung

Wenden wir uns kurz vom eigentlichen Thema ab und beschäftigen uns mit Astronomie. Jedem ist bekannt, dass sich die Erde um die Sonne dreht und zugleich auch um ihre eigene Achse. Die Erdachse ist in Bezug zu ihrer Umlaufbahn um die Sonne leicht geneigt. Die für eine vollständige Umdrehung der Erde um ihre eigene Achse in astronomischen (Himmels, globalen) Koordinaten benötigte Zeit nennt man einen astronomischen oder Sterntag.

Uns normale Erdbewohner interessiert ein Sterntag nicht, das ist bei Astronomen jedoch anders. Doch weitaus wichtiger ist der Wechsel zwischen Tag und Nacht. Die für einen Tag-Nacht-Zyklus benötigte Zeit nennt man Sonnentag. Betrachtet man sich das Sonnensystem von oberhalb des Nordpols unserer Erde (Abb. 3) aus, kann man erkennen, dass sich die Erde um ihre eigene Achse dreht und die Sonne entgegen des Uhrzeigersinns umrundet. Daher muss sich die Erde, um sich auf ihrer Achse in Bezug zur Sonne einmal komplett zu drehen, etwas mehr als 360 Grad drehen. Daher ist logischerweise ein Sonnentag etwas länger als ein Sterntag.

Abb. 3 Richtung der Erdrotation um ihre Achse und um die Sonne (wenn von oberhalb des Nordpols unserer Erde aus betrachtet).
Abb. 3 Richtung der Erdrotation um ihre Achse und um die Sonne (wenn von oberhalb des Nordpols unserer Erde aus betrachtet).

Aus Gründen der Vereinfachung und Exaktheit nimmt man als Grundlage für Zeitmessungen einen Sonnentag her. Er wird in 24 Teile von jeweils 1 Stunde unterteilt , die 60 Minuten dauert, usw. Ein Sterntag dauert 23 Stunden, 56 Minuten und 4 Sekunden. Eine leichte Neigung der Erdachse in Bezug zu ihrer Umlaufbahn führt zu Veränderungen der Jahreszeiten, was jeder von uns ja kennt.

Die Zahl der Sonnentage während eines Jahres ist nicht ganz, sondern sogar ein bisschen mehr: 365 Tage und 6 Stunden. Und deshalb wird unser Kalender regelmäßig angepasst - einmal alle 4 Jahre - (in dem Jahr, das ein Vielfaches von 4 ergibt), indem man einen zusätzlichen Tag anhängt - den 29. Februar nämlich (Schaltjahr). Diese Anpassung jedoch ist nicht ganz exakt (etwas Extra-Zeit wird addiert), sodass einige Jahre, obwohl sie Vielfache von 4 sind, eben keine Schaltjahre sind. In den Jahren, die auf "00" enden (ein Vielfaches von 100), wird der Kalender nicht angepasst. Doch das ist noch nicht alles.

Ist das Jahr zugleich ein Vielfaches von 100 und 400, wird es als Schaltjahr betrachtet und der Kalender muss entsprechend angepasst werden. Das Jahr 1900 ist ein Vielfaches von 4 und 100, doch kein Vielfaches von 400 - und deshalb war es kein Schaltjahr. Das Jahr 2000 ist ein Vielfaches von 4, 100 und auch 400, sodass es ein Schaltjahr war. Das nächste Jahr, das ein Vielfaches von 4 und 100 ist, ist das Jahr 2100. Doch es ist kein Vielfaches von 400 - also wird es kein Schaltjahr sein. Es zeigt sich also, dass die Leser dieses Beitrags zufälligerweise in einer Zeit leben, in der jedes Jahr, das ein Vielfaches von 4 ist, auch ein Schaltjahr ergibt. Das nächste Jahr, das ein Vielfaches von 100 und zugleich auch ein Schaltjahr ist, ist das Jahr 2400.


Zeitzonen

Die Erde dreht sich um ihre eigene Achse, was zum Wechsel zwischen Tag und Nacht führt. So kommt es, dass es Tag oder Nacht ist, bzw. an unterschiedlichen Orten auf der Erde gleichzeitig sowohl Tag als Nacht ist. Da ein Tag 24 Stunden besitzt, wird der Erdumfang in 24 Bereiche à 15 Grad unterteilt - die sog. Zeitzonen. Ebenfalls aus Gründen der Bequemlichkeit folgen die Grenzen der Zeitzonen nicht immer den Längengraden, sondern verlaufen stattdessen entlang der politischen Grenzen territorialer Aufteilung: Ländergrenzen, regionaler Grenzen, usw.

Der Referenzpunkt hier ist der sog. Nullmeridian, auch Greenwich Meridian genannt. Er verläuft genau durch Greenwich, ein Stadtviertel Londons, nach dem er benannt ist. Die Greenwich-Zeit erstreckt sich um je 7,5 Grad zu beiden Seiten des Greenwich Meridians. Östlich der Greenwich-Zeit werden also 12 Zeitzonen gemessen (von +1 bis +12) und westlich davon analog ebenfalls 12 Zeitzonen (von -1 bis -12). Die Zeitzonen -12 und +12 sind in der Tat nur 7,5 Grad statt 15 Grad breit. Die Zeitzonen -12 und +12 befinden sich links und rechts des 180-Meridians, der sog. Internationalen Datumsgrenze.

Wenn es also in Greenwich Mittag ist (12:00), ist es in der Zeitzone -12 00:00 (der Tag fängt also gerade an) und in der Zeitzone +12 24:00 (der Tag geht gerade zu Ende). Das gilt genauso für jede andere Uhrzeit - die Uhrzeit mag auf den Uhren die gleiche sein, doch das Kalenderdatum ist ein anderes. Die Zeitzone -12 wird tatsächlich nicht verwendet und stattdessen durch +12 ersetzt. Genauso verhält es sich bei Zeitzone -11, sie wird durch Zeitzone +13 ersetzt. Das hängt wahrscheinlich mit den Besonderheiten der wirtschaftlichen Beziehungen zusammen: So hat der Unabhängige Staat Samoa in Zeitzone +13 starke Wirtschaftsbeziehungen mit Japan, sodass die Zeit, die der Japan-Zeit näherliegt, hier als bequemer betrachtet wird.

Darüber hinaus gibt es auch unübliche Zeitzonen wie -04:30, +05:45, usw. Alle unter Ihnen, die jetzt neugierig geworden sind, finden in den Windows-Zeiteinstellungen ein Liste aller vorhandenen Zeitzonen.


Sommerzeit

In vielen Ländern wird die Uhr Ende März um 1 Stunde vorgestellt, die sog. Sommerzeit. Damit will man länger vom Sonnenlicht profitieren und dabei möglichst Energie einsparen. Fast 80 Länder halten sich an die Sommerzeit-Umstellung. Andere wiederum tun dies nicht. Einige Länder, die die Sommerzeit großflächig einhalten, haben jedoch Regionen, die diese Praxis nicht übernommen haben (so z.B. gewisse Bundesstaaten der USA). Sommerzeit wird in den wichtigen wirtschaftlich entwickelten Ländern und Regionen eingehalten: fast alle Länder Europas (inkl. Deutschland, Großbritannien und der Schweiz), USA (New York, Chicago), Australien (Sydney) und Neuseeland (Wellington). Japan hat keine Sommerzeit. Russland hat am 27. März 2011 seine Uhren zum letzten Mal um eine Stunde vorgestellt und seither nie mehr seine Uhren auf Normalzeit im Oktober zurückgestellt. Seitdem nimmt Russland an der Zeitumstellung zur Sommerzeit nicht mehr teil - sondern hat quasi dauerhaft Sommerzeit.

Der Zeitpunkt der Umstellung auf Sommerzeit ist von Land zu Land verschieden. In den USA wird die Uhr um 02:00 Ortszeit am zweiten Sonntag im März auf 03:00 vorgestellt und am ersten Sonntag im November wieder auf 02:00 zurückgestellt. In Europa wird die Uhr um 02:00 Ortszeit am letzten Sonntag im März auf 03:00 vorgestellt und am letzten Sonntag im Oktober um 03:00 wieder auf 02:00 zurückgestellt. Diese Umstellung findet in allen europäischen Ländern zeitgleich statt, also eben nicht zu den jeweiligen Ortszeiten, d.h. um 02:00 in London, um 03:00 in Berlin, etc., je nach Zeitzonen. Und genauso werden die Uhren auch wieder auf die Normalzeit zurückgestellt, nämlich um 03:00 in London, um 04:00 in Berlin, usw.

Australien und Neuseeland liegen auf der Südhalbkugel, sodass dort der Sommer beginnt, wenn bei uns auf der Nordhalbkugel der Winter Einzug hält. Daher werden in Australien logischerweise die Uhren am ersten Sonntag im Oktober auf Sommerzeit und am ersten Sonntag im April dann wieder zurück auf Normalzeit gestellt. In Bezug auf die Sommerzeit in Australien kann man nur schwer genauer sein, da ihr Anfangs- und Enddatum in den unterschiedlichen Teilen dieses riesigen Kontinents nicht immer miteinander übereinstimmen. In Neuseeland beginnt die Sommerzeit am letzten Sonntag im September um 02:00 und endet um 03:00 am ersten Sonntag im April.


Zeitstandard

Wie bereits schon erwähnt, wird als Grundlage der Zeitmessungen ein Sonnentag hergenommen. Die Greenwich-Zeit gilt als der Zeitstandard, auf dessen Grundlage Uhrzeit in allen anderen Zeitzonen festgelegt wird. Die Abkürzung für Greenwich-Zeit lautet GMT.

Doch da wir vorhin ja festgestellt haben, dass sich die Erde nicht komplett einheitlich dreht, wurde zur Messung der Zeit eine Atomuhr hergenommen, sodass UTC ( Koordinierte Weltzeit) zum neuen Zeitstandard geworden ist. Derzeit ist UTC der primäre Zeitstandard für die gesamte Welt und liegt allen Zeitmessungen in allen Zeitzonen zugrunde, wobei die notwendigen Anpassungen für die Sommerzeit entsprechend vorgenommen werden. UTC ist nicht von der Sommerzeit betroffen.

Aufgrund der Tatsache, dass die Sonnen-basierte GMT nicht komplett exakt mit der UTC übereinstimmt, die ihrerseits auf Atomuhren beruht, kommt es zu einer Zeitdifferenz zwischen UTC und GMT von ca. 1 Sekunde alle 500 Tage. Und genau deswegen findet von Zeit zu Zeit, am 30. Juni oder am 31. Dezember, eine Zeitangleichung von 1 Sekunde statt.


Datums- und Zeitformate

Datumsformate sind ebenfalls von Land zu Land verschieden. In Russland schreibt man z.B. zuerst den Tag, gefolgt vom Monat und dem Jahr; die Ziffern sind hierbei durch Punkte getrennt - also wäre der 1. Dezember 2012 der 01.12.2012. In den USA ist das Datumsformat Monat/Tag/Jahr, wobei die Ziffern hier durch einen Schrägstrich "/" getrennt sind. Zusätzlich zu Punkten und Schrägstrichen verwenden einige Formate auch einen Bindestrich ("-") zur Trennung von Ziffern in einem Datum. Notiert man die Uhrzeit, werden Stunden, Minuten und Sekunden durch einen Doppelpunkt ":" getrennt - also steht 12:15:30 z.B. für 12 Uhr, 15 Minuten und 30 Sekunden.

Datums- und Zeitformate können ganz einfach angegeben werden. So bedeutet "TT.MM.YYYY", dass zuerst der Tag (das Monatsdatum bestehend aus zwei Ziffern und, wenn es sich um ein Datum vom 1. bis 9. des Monats handelt, wird hier eine 0 hinzugefügt), gefolgt vom Monat (muss aus zwei Ziffern bestehen) und dann dem Jahr, mit seinen vier Ziffern, notiert wird. "T-M-JJ" bedeutet, dass der Tag (kann aus einer Ziffer bestehen) zuerst kommt, danach der Monat (auch hier ist eine Ziffer zulässig) und schließlich das Jahr, bestehend aus zwei Ziffern. Also steht 1/12/12 - für den 1. Dezember 2012. Tag, Monat und Jahr werden mit einem Bindestrich "-" getrennt.

Die Zeit wird vom Datum durch ein Leerzeichen abgetrennt. Das Zeitformat verwendet "h" für Stunden, "m" für Minuten und "s" für Sekunden, und legt dabei zugleich die entsprechende Zahl an Ziffern fest. So bedeutet z.B. "hh:mm:ss", dass wir zuerst die Stunden notieren (vor 1 - 9 sollte einen "0" stehen), dann die Minuten (2 Ziffern notwendig) und dann die Sekunden (2 Ziffern). Die jeweiligen Werte werden jeweils durch einen Doppelpunkt getrennt.

Das Datums- und Zeitformat, das vom Standpunkt eines Programmierers am exaktesten ist. lautet "JJJJ.MM.TT hh:mi:ss". Wenn man Strings mit Datumsangaben mittels dieser Schreibweise sortiert, können sie leicht chronologisch angeordnet werden. Angenommen, Sie speichern jeden Tag Informationen in Textdateien ab und legen diese in demselben Ordner ab. Wenn Sie Ihre Dateien in diesem Format benennen, werden alle Dateien im Ordner bequem sortiert und in der richtigen Reihenfolge geordnet.

So das wäre alles zur Theorie - wir können uns also nun der Implementierung zuwenden.


Zeit in MQL5

In MQL5 wird Zeit in den Sekunden gemessen, die seit Beginn der sog. Unix-Epoche (seit dem 1. Januar 1970) verstrichen sind. Zur Speicherung von Zeit verwenden wir Variablen des Typs datetime. Der Mindestwert einer Variable vom Datetime-Typ ist " 0" (entsprechend dem Datum des Epochenbeginns); der Maximalwert ist 32.535.244.799 (entsprechend 23:59:59 Uhr am 31. Dezember 3000).


Die aktuelle Serverzeit bestimmen

Zur Feststellung der aktuellen Zeit, verwenden wir die TimeCurrent()-Funktion. Sie liefert uns die letzte bekannte Serverzeit:

datetime tm=TimeCurrent();
//--- output result
Alert(tm);

Die gleiche Serverzeit wird verwendet, um die Zeit der Bars im Chart anzugeben. Die letzte bekannte Serverzeit ist die Zeit der letzten Änderung im Kurs jedes der im Marktbeobachtungsfenster geöffneten Symbole. Befindet sich nur EURUSD im Marktbeobachtungsfenster, liefert die TimeCurrent() Funktion den letzten Kurswechsel auf EURUSD. Da das Marktbeobachtungsfenster in der Regel eine beträchtliche Anzahl an Symbolen anzeigt, liefert die Funktion grundsätzlich die aktuelle Serverzeit. Doch da sich Kurse am Wochenende nicht ändern, weicht der von der Funktion gelieferte Wert sehr stark von der aktuellen Serverzeit ab.

Wenn Sie die Serverzeit durch ein bestimmtes Symbol herausfinden müssen (Uhrzeit der letzten Kursänderung), können Sie dazu die SymbolInfoInteger() Funktion mit dem Identifikator SYMBOL_TIME verwenden:

datetime tm=(datetime)SymbolInfoInteger(_Symbol,SYMBOL_TIME);
//--- output result
Alert(tm);


Die aktuelle Ortszeit bestimmen

Die Ortszeit (von der Uhr des Benutzer-PCs angezeigt) wird durch die TimeLocal() Funktion festgelegt:

datetime tm=TimeLocal();
//--- output result
Alert(tm);

Bei der Programmierung von EAs und Indikatoren verwendet man in der Praxis jedoch meistens Serverzeit. Die Ortszeit ist prima bei Warnungen und Protokolleinträgen, denn es ist für den Anwender weitaus bequemer, eine Meldung oder einen Eingangs-Zeitstempel mit der von der Uhr des PCs angezeigten Uhrzeit zu vergleichen, will man wissen, wann diese Meldung oder der Eintrag registriert worden ist. Der MetaTrader 5 Terminal versieht alle Meldungen und Protokolleinträge, die ausgegeben werden, mittels der Warnung() und Print() Funktionen automatisch mit einem Zeitstempel. Daher kommt es höchst selten vor, dass man die TimeLocal() Funktion verwenden muss.


Zeitausgabe

Bitte beachten Sie, dass der Wert der tm-Variable im oben stehenden Code mittels der Warnung() Funktion ausgegeben wird. Vor diesem Hintergrund wird der Wert in einem leicht zu lesenden Format angezeigt, z.B. "05.12.2012 22:31:57". Das hängt mit der Tatsache zusammen, dass die Warnung() Funktion, die an sie übertragenen Parameter in den String-Typ umwandelt (das gilt übriges auch bei der Verwendung der Print()- und Kommentar()-Funktion und bei der Ausgabe in Text- oder csv-Dateien). Bei der Erzeugung einer Textnachricht, die den Wert einer Variable vom Typ 'datetime' enthält, müssen Sie diese Umwandlung vornehmen. Wandeln Sie also in den String-Typ um, wenn Sie die formatierte Zeit brauchen oder in den long-Typ, gefolgt von einem String-Typ, wenn Sie einen numerischen Wert wollen:

datetime tm=TimeCurrent();
//--- output result
Alert("Formatted: "+(string)tm+", in seconds: "+(string)(long)tm);

Da die Wertebereiche der Variablen vom Typ 'long' und 'ulong' den Wertbereich der Variable vom Type 'datetime' abdecken, kann man mit ihrer Hilfe auch Zeit speichern, doch müssen Sie in diesem Fall, um die formatierte Zeit ausgeben zu können, den 'long'-Typ zunächst in den 'datetime'-Typ umwandeln und dann in den 'string'-Typ, oder nur in den 'string'-Typ, wenn Sie einen numerischen Wert ausgeben möchten:

long tm=TimeCurrent();
//--- output result
Alert("Formatted: "+(string)(datetime)tm+", in seconds: "+(string)tm);


Zeitformatierung

Mit Zeitformatierung hat sich bereits der Beitrag "MQL5-Programmierungsgrundlagen: Strings" in dem Abschnitt beschäftigt, wo es um die Umwandlung von verschiedenen Variablen in einen String ging. Heben wir dennoch hier kurz die wichtigsten Punkte nochmals heraus. Zusätzlich zur Umwandlung von Typen, gibt es in MQL5 eine Funktion, mit der Sie das Datums- und Zeitformat bei der Umwandlung in einen String angeben können - die TimeToString() Funktion:

datetime tm=TimeCurrent();
string str1="Date and time with minutes: "+TimeToString(tm);
string str2="Date only: "+TimeToString(tm,TIME_DATE);
string str3="Time with minutes only: "+TimeToString(tm,TIME_MINUTES);
string str4="Time with seconds only: "+TimeToString(tm,TIME_SECONDS);
string str5="Date and time with seconds: "+TimeToString(tm,TIME_DATE|TIME_SECONDS);
//--- output results
Alert(str1);
Alert(str2);
Alert(str3);
Alert(str4);
Alert(str5);

Die TimeToString() Funktion kann auf Variablen des 'datetime'-Typs sowie auch auf Variablen des 'long'- und 'ulong'-Typs und einige anderer Typen ganzzahliger Variablen angewandt werden, doch sollten sie nicht zur Speicherung von Zeit verwendet werden.

Bei der Formatierung einer Textnachricht mittels der StringFormat()-Funktion, dürfen Sie die Typ-Umwandlung nicht vergessen

  • Bei der Speicherung von Zeit in einer Variable des 'datetime'-Typs:

    datetime tm=TimeCurrent();
    //--- generating a string
    string str=StringFormat("Formatted: %s, in seconds: %I64i",(string)tm,tm);
    //--- output result
    Alert(str);
  • Bei der Speicherung von Zeit in einer Variable des 'long'-Typs:

    long tm=TimeCurrent();
    //--- generating a string
    string str=StringFormat("Formatted: %s, in seconds: %I64i",(string)(datetime)tm,tm);
    //--- output result
    Alert(str);
  • Oder mittels der TimeToString() Funktion:

    datetime tm=TimeCurrent();
    //--- generating a string
    string str=StringFormat("Date: %s",TimeToString(tm,TIME_DATE));
    //--- output result
    Alert(str);


Umwandlung von Zeit in Ziffern. Addition und Subtraktion von Zeit

Die StringToTime()-Funktion wird zur Umwandlung eines formatierten Datums (String) in eine Ziffer (Anzahl der verstrichenen Sekunden seit Beginn der Epoche) verwendet:

datetime tm=StringToTime("2012.12.05 22:31:57");
//--- output result
Alert((string)(long)tm);

Als Ergebnis des oben stehenden Codes sieht die Ausgabe der Zeit in Sekunden so aus: "1354746717", entsprechend dem Datum, das so ausgegeben wird "05.12.2012 22:31:57".

Wird Zeit als Ziffer dargestellt, werden viele Arbeiten mit ihr leicht und bequem. Sie können z.B. problemlos das Datum und die Uhrzeit in der Vergangenheit oder der Zukunft finden. Da Zeit ja in Sekunden gemessen wird, sollten Sie einen Zeitraum ebenfalls in Sekunden ausdrücken. Da wir wissen, dass 1 Minute 60 Sekunden und 1 Stunde 60 Minuten oder eben 3600 Sekunden hat, ist die Berechnung der Dauer jedes Zeitraums keine große Sache.

Subtrahieren Sie 1 Stunde (3600 Sekunden) von der aktuellen Zeit (oder addieren sie) und Sie erhalten die Zeit vor bzw. in einer Stunde.

datetime tm=TimeCurrent();
datetime ltm=tm-3600;
datetime ftm=tm+3600;
//--- output result
Alert("Current: "+(string)tm+", an hour ago: "+(string)ltm+", in an hour: "+(string)ftm);

Das an die StringToTime() Funktion übertragene Datum muss nicht vollständig sein. Sie können des weiteren auch das Datum ohne Uhrzeit oder die Uhrzeit ohne Datum übertragen. Wenn Sie das Datum ohne Uhrzeit übertragen, liefert die Funktion den Wert 00:00:00 des angegebenen Datums:

datetime tm=StringToTime("2012.12.05");
//--- output result
Alert(tm);

Wenn Sie nur die Uhrzeit übertragen, liefert die Funktion den Wert, der der angegebenen Uhrzeit des aktuellen Datums entspricht:

datetime tm=StringToTime("22:31:57");
//--- output result
Alert((string)tm);

Uhrzeit kann auch ohne Sekunden übertragen werden. Wenn Sie das Datum übertragen, können als einzige Zeitkomponenten nur Stunden übertragen werden. Doch das wird in der Praxis so gut wie nie verlangt. Doch wenn Sie neugierig geworden sind, dann probieren Sie doch einfach selbst herum.


Datums- und Zeitkomponenten

Zur Feststellung der Werte der einzelnen Datums- und Zeitkomponenten (Jahr, Monat, Datum, usw.) verwenden wir die TimeToStruct()-Funktion und die MqlDateTime-Struktur. Die Struktur wird per Verweis an die Funktion übertragen. Nach Ausführung der Funktion, wird die Struktur mit Werten der an sie übertragenen Datumskomponenten gefüllt:

datetime    tm=TimeCurrent();
MqlDateTime stm;
TimeToStruct(tm,stm);
//--- output date components
Alert("Year: "        +(string)stm.year);
Alert("Month: "      +(string)stm.mon);
Alert("Day: "      +(string)stm.day);
Alert("Hour: "        +(string)stm.hour);
Alert("Minute: "     +(string)stm.min);
Alert("Second: "    +(string)stm.sec);
Alert("Day of the week: "+(string)stm.day_of_week);
Alert("Day of the year: "  +(string)stm.day_of_year);

Beachten Sie, dass die Struktur neben den Datumskomponenten auch ein paar zusätzliche Felder enthält: Wochentag (das Feld 'Tag_der_Woche') und Kalendertag ('Tag_des_Jahres'). Wochentage werden von "0" ab gezählt (0 = Sonntag, 1 = Montag, usw.). Kalendertage werden ebenfalls von "0" ab gezählt. Die anderen Werte folgen der allgemein akzeptierten Zählungsreihenfolge (Monate und auch Tage werden ab 1 gezählt).

Doch die TimeCurrent()-Funktion kann noch auf eine andere Art aufgerufen werden. Eine Struktur des Typs MqlDateTime wird per Verweis an die Funktion übertragen. Nach Ausführung der Funktion wird die Struktur mit den aktuellen Datumskomponenten gefüllt:

MqlDateTime stm;
datetime tm=TimeCurrent(stm);
//--- output date components
Alert("Year: "        +(string)stm.year);
Alert("Month: "      +(string)stm.mon);
Alert("Day: "      +(string)stm.day);
Alert("Hour: "        +(string)stm.hour);
Alert("Minute: "     +(string)stm.min);
Alert("Second: "    +(string)stm.sec);
Alert("Day of the week: "+(string)stm.day_of_week);
Alert("Day of the year: "  +(string)stm.day_of_year);

So kann auch die TimeLocal()-Funktion aufgerufen werden.


Ein Datum aus seinen Komponenten generieren

Man kann ganz genauso auch umgekehrt die MqlDateTime-Struktur in einen 'datetime'-Typ umwandeln. Dazu verwenden wir die StructToTime()-Funktion.

Legen wir mal die Zeit vor exakt einem Monat fest. Die Anzahl der Tage pro Monat ist nicht immer gleich: manche Monate haben 30 oder 31 Tage und der Februar hat vielleicht nur 28 oder 29 Tage. Daher eignet sich hier die Methode der Addition oder Subtraktion von Uhrzeit, die wir vorhin betrachtet haben, nicht besonders. Daher müssen wir das Datum in seine Komponenten herunterbrechen und den Monatswert um "1" vermindern und, sollte der Monatswert "1" betragen, müssen wir ihn auf "12" stellen und den Jahreswert um "1" vermindern:

datetime tm=TimeCurrent();
MqlDateTime stm;
TimeToStruct(tm,stm);
if(stm.mon==1)
  {
   stm.mon=12;
   stm.year--;
  }
else
  {
   stm.mon--;
  }
datetime ltm=StructToTime(stm);
//--- output result
Alert("Current: "+(string)tm+", a month ago: "+(string)ltm);


Bar-Zeit bestimmen

Bei der Entwicklung eines Indikators erzeugt MetaEditor automatisch eine der zwei Versionen der OnCalculate()-Funktion.

Version 1:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   return(rates_total);
  }

Version 2:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   return(rates_total);
  }

Die Parameter der ersten Version der Funktion umfassen das Zeit[]-Array, dessen Elemente die Zeit aller Bars enthalten.

Bei der Arbeit mit der zweiten Version sowie immer bei der Programmierung von EAs und der Anordnung des Zugriffs von Indikatoren auf die Bar-Zeit auf anderen Zeitrahmen, verwenden wir die CopyTime()-Funktion. Diese Funktion gibt es in drei Versionen. In allen drei Versionen definieren die ersten zwei Funktionsparameter das Symbol und den Chart-Zeitrahmen auf dem die Bar-Zeit festgestellt wird. Der letzte Parameter definiert ein Array, das die gelieferten Werte speichert, und die beiden mittleren Parameter hängen von der Funktionsversion ab, mit der gerade gearbeitet wird.

CopyTime - Version 1 Sie müssen den Bar-Index und die Anzahl der zu kopierenden Elemente angeben:

//--- variables for function parameters
int start = 0; // bar index
int count = 1; // number of bars
datetime tm[]; // array storing the returned bar time
//--- copy time 
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
//--- output result
Alert(tm[0]);

Dieses Beispiel zeigt die Implementierung des Kopierens der Zeit des letzten Bars auf dem D1-Zeitrahmen. Die Funktionsversion, die wir verwenden, hängt von dem Typ der an sie übertragenen Parameter ab. Im obigen Beispiel geht es um Variablen des 'int'-Typs, d.h. wird müssen wir die Zeit durch die Bar-Ziffer für die angegebene Anzahl von Bars bekommen.

Wird die CopyTime() Funktion verwendet, werden die Bars von Null an und von rechts nach links gezählt. Ein dynamisches Array, das für die erhaltenen Werte (der letzte Parameter) verwendet wird, wird von der CopyTime()-Funktion selbst auf die nötige Größe skaliert. Sie können auch ein statisches Array verwenden, doch in diesem Fall muss die Arraygröße der Zahl der angefragten Elemente genau entsprechen (der Wert des 4. Parameters).

Wenn die Zeit von mehreren Bars gleichzeitig erhalten wird, ist es ist wichtig, die Reihenfolge der Elemente im gelieferten Array zu verstehen. Zwar werden die Bars im Chart von rechts nach links gezählt, angefangen beim spezifizierten Bar, doch die Elemente im Array werden von links nach rechts angeordnet:

//--- variables for function parameters
int start = 0; // bar index
int count = 2; // number of bars
datetime tm[]; // array storing the returned bar time
//--- copy time 
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
//--- output result
Alert("Today: "+(string)tm[1]+", yesterday: "+(string)tm[0]);

Als Ergebnis dieses Codes, wird die Zeit des gestrigen Bars im "0"-Element des 'tm'-Arrays gespeichert, doch das 1. Element enthält die Zeit des heutigen Bars.

Manchmal scheint es bequemer, die Zeit im Array in der gleichen Reihenfolge arrangiert zu haben, wie die Bars im Chart gezählt werden. Und das übernimmt die ArraySetAsSeries()-Funktion:

//--- variables for function parameters
int start = 0; // bar index
int count = 2; // number of bars
datetime tm[]; // array storing the returned bar time
ArraySetAsSeries(tm,true); // specify that the array will be arranged in reverse order
//--- copy time 
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
//--- output result
Alert("Today: "+(string)tm[0]+", yesterday: "+(string)tm[1]);

Jetzt befindet sich die Zeit des heutigen Bars im Element mit Index "0" und die Zeit des gestrigen Bars im Element mit dem Index "1".

CopyTime - Version 2 Beim Aufrufen der CopyTime()-Funktion, müssen wir hier die Zeit des Bars angeben, wo das Kopieren beginnen soll sowie die Anzahl der zu kopierenden Bars. Diese Version eignet sich zur Feststellung der Zeit eines höheren Zeitrahmen, der einen Bar eines niedrigeren Zeitrahmens enthält:

//--- get the time of the last bar on M5
int m5_start=0; 
int m5_count=1;
datetime m5_tm[];
CopyTime(_Symbol,PERIOD_M5,m5_start,m5_count,m5_tm);
//--- determine the bar time on H1 that contains the bar on M5
int h1_count=1;
datetime h1_tm[];
CopyTime(_Symbol,PERIOD_H1,m5_tm[0],h1_count,h1_tm);
//--- output result
Alert("The bar on М5 with the time "+(string)m5_tm[0]+" is contained in the bar on H1 with the time "+(string)h1_tm[0]);

Müssen Sie die Zeit des Bars auf einem niedrigeren Zeitrahmen feststellen, der der Anfang des Bars auf einem höheren Zeitrahmen ist, wird das Ganze schon komplizierter. Ein Bar mit der gleichen Zeit als die Zeit des Bars auf einem höheren Zeitrahmen, kann auf einem niedrigeren Zeitrahmen ggf. fehlen. In diesem Fall erhalten wir die Zeit des letzten Bars auf einem niedrigeren Zeitrahmen, der in einem vorherigen Bar auf dem höheren Zeitrahmen enthalten ist. Daher müssen wir die Ziffer des Bars auf einem niedrigeren Zeitrahmen feststellen und die Zeit des nächsten Bars bekommen.

Unten steht die Implementierung des oben Beschriebenen in Form einer komplett einsatzbereiten Funktion:

bool LowerTFFirstBarTime(string aSymbol,
                         ENUM_TIMEFRAMES aLowerTF,
                         datetime aUpperTFBarTime,
                         datetime& aLowerTFFirstBarTime)
  {
   datetime tm[];
//--- determine the bar time on a lower time frame corresponding to the bar time on a higher time frame 
   if(CopyTime(aSymbol,aLowerTF,aUpperTFBarTime,1,tm)==-1)
     {
      return(false);
     }
   if(tm[0]<aUpperTFBarTime)
     {
      //--- we got the time of the preceding bar
      datetime tm2[];
      //--- determine the time of the last bar on a lower time frame
      if(CopyTime(aSymbol,aLowerTF,0,1,tm2)==-1)
        {
         return(false);
        }
      if(tm[0]<tm2[0])
        {
         //--- there is a bar following the bar of a lower time frame  that precedes the occurrence of the bar on a higher time frame
         int start=Bars(aSymbol,aLowerTF,tm[0],tm2[0])-2;
         //--- the Bars() function returns the number of bars from the bar with time tm[0] to
         //--- the bar with time tm2[0]; since we need to determine the index of the bar following 
         //--- the bar with time tm2[2], subtract 2
         if(CopyTime(aSymbol,aLowerTF,start,1,tm)==-1)
           {
            return(false);
           }
        }
      else
        {
         //--- there is no bar of a lower time frame contained in the bar on a higher time frame
         aLowerTFFirstBarTime=0;
         return(true);
        }
     }
//--- assign the obtained value to the variable 
   aLowerTFFirstBarTime=tm[0];
   return(true);
  }

Funktionsparameter:

  • aSymbol - Symbol;
  • aLowerTF - niedriger Zeitrahmen;
  • aUpperTFBarTime - Bar-Zeit auf einem höheren Zeitrahmen;
  • aLowerTFFirstBarTime - gelieferter Wert eines niedrigeren Zeitrahmens.

Die Funktion prüft im gesamten Code, ob der Aufruf der CopyTime()-Funktion erfolgreich ist und liefert im Falle eines Fehlers 'false'. Die Bar-Zeit wird durch den aLowerTFFirstBarTime Parameter per Verweis geliefert.

Ein Verwendungsbeispiel der Funktion wird unten gezeigt:

//--- time of the bar on the higher time frame H1
   datetime utftm=StringToTime("2012.12.10 15:00");
//--- variable for the returned value
   datetime val;
//--- function call
   if(LowerTFFirstBarTime(_Symbol,PERIOD_M5,utftm,val))
     {
      //--- output result in case of successful function operation
      Alert("val = "+(string)val);
     }
   else
     {
      //--- in case of an error, terminate the operation of the function from which the LowerTFFirstBarTime() function is called
      Alert("Error copying the time");
      return;
     }

Erhält die Funktion die Zeit eines nicht-existierenden Bars eines höheren Zeitrahmens, kann die Situation auftreten, dass sich auf dem niedrigeren Zeitrahmen kein Bar befindet, der in einem Bar eines höheren Zeitrahmens enthalten ist. In diesem Fall liefert die Funktion 'true' und der Zeitwert "0" wird in die aLowerTFFirstBarTime Variable geschrieben. Existiert ein Bar eines höheren Zeitrahmens, gibt es immer mind. einen entsprechenden Bar in jedem der niedrigeren Zeitrahmen.

Die Zeit des letzten Bars auf einem niedrigeren Zeitrahmen zu finden, der in einem Bar eines höheren Zeitrahmens enthalten ist, ist ziemlich einfach. Wir berechnen die Zeit des nächsten Bars auf einem höheren Zeitrahmen und verwenden den erhaltenen Wert zur Feststellung der Zeit des entsprechenden Bars auf einem niedrigeren Zeitrahmen. Ist die entsprechende Zeit gleich der errechneten Zeit auf dem höheren Zeitrahmen, müssen wir die Zeit des vorherigen Bars auf einem niedrigeren Zeitrahmen feststellen. Ist die entsprechende Zeit geringer, dann haben wir die richtige Zeit.

Unten steht die Implementierung des oben Beschriebenen in Form einer komplett einsatzbereiten Funktion:

bool LowerTFLastBarTime(string aSymbol,
                        ENUM_TIMEFRAMES aUpperTF,
                        ENUM_TIMEFRAMES aLowerTF,
                        datetime aUpperTFBarTime,
                        datetime& aLowerTFFirstBarTime)
  {
//--- time of the next bar on a higher time frame
   datetime NextBarTime=aUpperTFBarTime+PeriodSeconds(aUpperTF);
   datetime tm[];
   if(CopyTime(aSymbol,aLowerTF,NextBarTime,1,tm)==-1)
     {
      return(false);
     }
   if(tm[0]==NextBarTime)
     {
      //--- There is a bar on a lower time frame corresponding to the time of the next bar on a higher time frame.
      //--- Determine the time of the last bar on a lower time frame
      datetime tm2[];
      if(CopyTime(aSymbol,aLowerTF,0,1,tm2)==-1)
        {
         return(false);
        }
      //--- determine the preceding bar index on a lower time frame
      int start=Bars(aSymbol,aLowerTF,tm[0],tm2[0]);
      //--- determine the time of this bar
      if(CopyTime(aSymbol,aLowerTF,start,1,tm)==-1)
        {
         return(false);
        }
     }
//--- assign the obtain value to the variable 
   aLowerTFFirstBarTime=tm[0];
   return(true);
  }

Funktionsparameter:

  • aSymbol - Symbol;
  • aUpperTF - höherer Zeitrahmen;
  • aLowerTF - niedrigerer Zeitrahmen;
  • aUpperTFBarTime - Bar-Zeit auf einem höheren Zeitrahmen;
  • aLowerTFFirstBarTime - gelieferter Wert eines niedrigeren Zeitrahmens.

Die Funktion prüft im gesamten Code, ob der Aufruf der CopyTime()-Funktion erfolgreich ist und liefert im Falle eines Fehlers 'false' Die Bar-Zeit wird durch den aLowerTFFirstBarTime Parameter per Verweis geliefert.

Ein Verwendungsbeispiel der Funktion wird unten gezeigt:

//--- time of the bar on the higher time frame H1
datetime utftm=StringToTime("2012.12.10 15:00");
//--- variable for the returned value
datetime val;
//--- function call
if(LowerTFLastBarTime(_Symbol,PERIOD_H1,PERIOD_M5,utftm,val))
  {
//--- output result in case of successful function operation
   Alert("val = "+(string)val);
  }
else
  {
//--- in case of an error, terminate the operation of the function from which the LowerTFFirstBarTime() function is called
   Alert("Error copying the time");
   return;
  }

Achtung! Es wird angenommen, dass die an die Funktion übertragene Zeit die exakte Zeit eines höheren Zeitrahmens ist. Ist die exakte Zeit des Bars nicht bekannt und kennt man nur die Zeit einer bestimmten Kursschwankung auf diesem Bar oder einen Bar auf einem niedrigeren Zeitrahmen, der in einem Bar eines höheren Zeitrahmens enthalten ist, dann ist eine Zeitnormalisierung erforderlich. Die im MetaTrader 5 Terminal verwendeten Zeitrahmen brechen den Tag in eine ganzzahlige Anzahl an Bars herunter. Wir stellen die Bar-Zahl seit Beginn der Epoche fest und multiplizieren sie mit der Länge des Bars in Sekunden:

datetime BarTimeNormalize(datetime aTime,ENUM_TIMEFRAMES aTimeFrame)
  {
   int BarLength=PeriodSeconds(aTimeFrame);
   return(BarLength*(aTime/BarLength));
  }

Funktionsparameter:

  • aTime - Zeit;
  • aTimeFrame - Zeitrahmen.

Ein Verwendungsbeispiel der Funktion wird unten gezeigt:

//--- the time to be normalized
datetime tm=StringToTime("2012.12.10 15:25");
//--- function call
datetime tm1=BarTimeNormalize(tm,PERIOD_H1);
//--- output result
Alert(tm1);

Zur Feststellung der Zeit auf einem höheren Zeitrahmen mittels der Zeit auf einem niedrigeren Zeitrahmen gibt es jedoch nicht nur die Methode zur Zeitnormalisierung, sondern noch eine weitere.

CopyTime - Version 3 In diesem Fall spezifizieren wir beim Aufruf der CopyTime() Funktion den Zeitrahmen in Bezug auf die Bars von denen Zeit kopiert werden muss. Mit dieser Methode bekommen wir leicht alle Bars eines niedrigeren Zeitrahmen, die in einem Bar eines höheren Zeitrahmens enthalten sind:

//--- time of the bar start on H1
datetime TimeStart=StringToTime("2012.12.10 15:00");
//--- the estimated time of the last bar on
//--- M5
datetime TimeStop=TimeStart+PeriodSeconds(PERIOD_H1)-PeriodSeconds(PERIOD_M5);
//--- copy time
datetime tm[];
CopyTime(_Symbol,PERIOD_M5,TimeStart,TimeStop,tm);
//--- output result 
Alert("Bars copied: "+(string)ArraySize(tm)+", first bar: "+(string)tm[0]+", last bar: "+(string)tm[ArraySize(tm)-1]);


Tagesbeginn-Zeit und Menge der verstrichenen Zeit seit Tagesbeginn bestimmen

Der klarste Weg die Tagesbeginn-Zeit durch eine gegebene Zeit festzustellen ist, die Zeit in ihre Komponenten herunter zu brechen, Stunden, Minuten und Sekunden zu nullen und sie später wieder zusammen zu zählen. Doch das geht noch leichter. Ein Tag hat 86400 Sekunden. Als Ergebnis der Teilung der Zeit durch die Anzahl an Sekunden in einem Tag müssen wir eine ganze Zahl bekommen und diese mit der Anzahl der Sekunden in einem Tag multiplizieren:

datetime tm=TimeCurrent();
tm=(tm/86400)*86400;
//--- output result
Alert("Day start time: "+(string)tm);

Achtung! Dieser Kniff funktioniert nur mit ganzzahligen Variablen. Sollten in irgendeiner Ihrer Berechnungen Variablen des Typs 'double' oder 'float' auftauchen, dann müssen Sie mit Hilfe der MathFloor()-Funktion den Bruchteil abtrennen:

MathFloor(tm/86400)*86400

Nach der Multiplikation sollten die Werte mit Hilfe der NormalizeDouble()-Funktion normalisiert werden. Da der nun erhaltenen Werte eine ganze Zahl sein muss, können Sie eine Funktion zum Auf- oder Abrunden verwenden: MathRound():

MathRound(MathFloor(tm/86400)*86400)

Werden ganzzahlige Variablen verwendet fällt der Rest automatisch weg. Wenn man mit Zeit arbeitet, braucht man kaum Variablen des 'double'- oder 'float'-Typs, sodass ihre Verwendung höchstwahrscheinlich eher auf einen komplett falschen Ansatz hindeutet.

Zur Feststellung der Anzahl Sekunden, die seit dem Beginn des Tages verstrichen sind, müssen wir nur den Rest der Zeitteilung durch 86400 hernehmen:

datetime tm=TimeCurrent();
long seconds=tm%86400;
//--- output result
Alert("Time elapsed since the day start: "+(string)seconds+" sec.");

Ähnliche Methoden können zur Umrechnung der erhaltenen Zeit in Sekunden auf Stunden, Minuten und Sekunden verwendet werden. Implementierung als eine Funktion:

int TimeFromDayStart(datetime aTime,int &aH,int &aM,int &aS)
  {
//--- Number of seconds elapsed since the day start (aTime%86400),
//--- divided by the number of seconds in an hour is the number of hours
   aH=(int)((aTime%86400)/3600);
//--- Number of seconds elapsed since the last hour (aTime%3600),
//--- divided by the number of seconds in a minute is the number of minutes 
   aM=(int)((aTime%3600)/60);
//--- Number of seconds elapsed since the last minute 
   aS=(int)(aTime%60);
//--- Number of seconds since the day start
   return(int(aTime%86400));
  }

Der erste übertragene Parameter ist Zeit. Andere Parameter werden für die gelieferten Werte verwendetet: aH - Stunden, aM - Minuten, aS - Sekunden. Die Funktion an sich liefert die Gesamtzahl an Sekunden seit Tagesbeginn. Prüfen wir die Funktion:

datetime tm=TimeCurrent();
int t,h,m,s;
t=TimeFromDayStart(tm,h,m,s);
//--- output result
Alert("Time elapsed since the day start ",t," s, which makes ",h," h, ",m," m, ",s," s ");

Sie können auch die Zahl des Tages berechnen, die in Indikatoren zur Bestimmung des Bars zu Tagesbeginn verwendet werden soll:

bool NewDay=(time[i]/86400)!=(time[i-1]/86400);

Hierbei wird davon ausgegangen, dass die Bars von links nach rechts indiziert werden, wobei Zeit[i] die Zeit des aktuellen Bars und Zeit[i-1] die des vorherigen ist.


Wochenbeginn-Zeit und Menge der verstrichenen Zeit seit Wochenbeginn bestimmen

Die Bestimmung der Wochenbeginn-Zeit ist etwas komplexer als die des Tagesbeginns. Zwar ist die Zahl der Tage einer Woche konstant und man kann die Dauer einer Woche in Sekunden berechnen (604800 Sekunden), doch reicht es nicht aus, einfach die ganze Zahl der Wochen zu berechnen, die seit Beginn der Epoche verstrichen sind und sie mit der Dauer der Woche zu multiplizieren.

Denn das Problem ist: in den meisten Ländern beginnt eine Woche am Montag, in einigen anderen jedoch bereits am Sonntag (USA, Kanada, Israel und andere). Nur zur Erinnerung: die Epoche der Zeitmessung beginnt am Donnerstag. Wäre also der Donnerstag der erste Tage der Woche, hätten derartig einfache Berechnungen genügt.

Aus Gründen der Bequemlichkeit betrachten wir die Besonderheiten der Feststellung des Wochenbeginns mittels des Beispiels des ersten Tags der Epoche, der dem Werte "0" entspricht. Wir müssen so einen Wert finden, der, wenn er zur Zeit addiert wird, den ersten Tag der Epoche (01.01.1970 00:00) - von Null an gezählt -, zum vierten Tag ändern würde, d.h. wir müssen die Dauer von vier Tagen addieren. Beginnt die Woche am Montag, dann ist Donnerstag der vierte Tag, also müssen wir die Dauer von drei Tagen addieren. Beginnt die Woche jedoch am Sonntag, dann ist Donnerstag der fünfte Tag, also müssen wir die Dauer von vier Tagen addieren.

Schreiben wir nun eine Funktion zur Berechnung der Zahl einer Woche :

long WeekNum(datetime aTime,bool aStartsOnMonday=false)
  {
//--- if the week starts on Sunday, add the duration of 4 days (Wednesday+Tuesday+Monday+Sunday),
//    if it starts on Monday, add 3 days (Wednesday, Tuesday, Monday)
   if(aStartsOnMonday)
     {
      aTime+=259200; // duration of three days (86400*3)
     }
   else
     {
      aTime+=345600; // duration of four days (86400*4)  
     }
   return(aTime/604800);
  }

Diese Funktion kann in Indikatoren zur Feststellung des ersten Bars der neuen Woche nützlich sein:

bool NewWeek=WeekNum(time[i])!=WeekNum(time[i-1]);

Hierbei wird davon ausgegangen, dass die Bars von links nach rechts indiziert werden, wobei Zeit[i] die Zeit des aktuellen Bars und Zeit[i-1] die des vorherigen ist.

Jetzt können wir die Zeit des Wochenbeginns berechnen. Da bei der Berechnung der Zahl der Woche davon ausgegangen wurde, dass die Epoche drei (oder eben vier) Tage früher beginnt, müssen wir nun eine umgekehrte Zeitkorrektur durchführen:

long WeekStartTime(datetime aTime,bool aStartsOnMonday=false)
  {
   long tmp=aTime;
   long Corrector;
   if(aStartsOnMonday)
     {
      Corrector=259200; // duration of three days (86400*3)
     }
   else
     {
      Corrector=345600; // duration of four days (86400*4)
     }
   tmp+=Corrector;
   tmp=(tmp/604800)*604800;
   tmp-=Corrector;
   return(tmp);
  }  

Die Funktion liefert einen Wert des long-Typs, da der Wert für die allererste Woche evtl. negativ sein kann (drei oder vier Tage vor dem Beginn der Epoche). Der zweite Parameter der Funktion kann feststellen, ob die Woche am Sonntag oder Montag beginnt.

Da wir jetzt die Zeit des Wochenbeginns haben, können wir die Zahl der Sekunden berechnen, die seit dem Wochenbeginn verstrichen sind:

long SecondsFromWeekStart(datetime aTime,bool aStartsOnMonday=false)
  {
   return(aTime-WeekStartTime(aTime,aStartsOnMonday));
  }

Sekunden können in Tage, Stunden,Minuten und Sekunden umgerechnet werden. Obwohl es nicht schwer war, die Stunden, Minuten und Sekunden seit Tagesbeginn zu berechnen, ist es in diesem Fall dennoch leichter, hierzu die TimeToStruct()-Funktion zu verwenden:

long sfws=SecondsFromWeekStart(TimeCurrent());
MqlDateTime stm;
TimeToStruct(sfws,stm);
stm.day--;
Alert("Time elapsed since the week start "+(string)stm.day+" d, "+(string)stm.hour+" h, "+(string)stm.min+" m, "+(string)stm.sec+" s");

Bitte beachten Sie: der 'stm.day'-Wert wird um "1" vermindert. Die Tages eines Monats werden von "1" ab gezählt, da wir ja die Zahl der ganzen Tage feststellen müssen. Vielleicht finden einigen unter Ihnen die Funktionen dieses Abschnitts hier nutzlos, wenn man sie von einem praktischen Standpunkt aus betrachtet, doch das Verständnis dieser Funktionen ist für Sie von großem Wert in Bezug auf Erfahrung in der Arbeit mit Zeit.


Kalenderwoche ab einem festen Datum, Jahresbeginn oder Monatsbeginn bestimmen

Wenn wir uns die Felder der MqlDateTime-Struktur ansehen, insb. das Feld 'Tag_des_Jahres', möchte man gerne Funktionen erzeugen, die die Zahl der Woche seit Jahresbeginn und Monatsbeginn feststellen können. Hier ist es besser, eine allgemeine Funktion zur Feststellung der Zahl der Woche seit einem festen Datum zu schreiben. Die Funktion arbeitet nach einem ähnlichen Prinzip wie die, die wir zur Feststellung der Zahl der Woche seit Beginn der Epoche verwendet haben:

long WeekNumFromDate(datetime aTime,datetime aStartTime,bool aStartsOnMonday=false)
  {
   long Time,StartTime,Corrector;
   MqlDateTime stm;
   Time=aTime;
   StartTime=aStartTime;
//--- determine the beginning of the reference epoch
   StartTime=(StartTime/86400)*86400;
//--- determine the time that elapsed since the beginning of the reference epoch
   Time-=StartTime;
//--- determine the day of the week of the beginning of the reference epoch
   TimeToStruct(StartTime,stm);
//--- if the week starts on Monday, numbers of days of the week are decreased by 1,
//    and the day with number 0  becomes a day with number 6
   if(aStartsOnMonday)
     {
      if(stm.day_of_week==0)
        {
         stm.day_of_week=6;
        }
      else
        {
         stm.day_of_week--;
        }
     }
//--- calculate the value of the time corrector 
   Corrector=86400*stm.day_of_week;
//--- time correction
   Time+=Corrector;
//--- calculate and return the number of the week
   return(Time/604800);
  }

Auf Basis dieser Funktion, schreiben wir nun zwei Funktionen zur Feststellung der Zahl der Woche seit Jahres- und seit Monatsbeginn. Dazu müssen wir zuerst die Zeit des Jahresbeginns und die Zeit des Monatsbeginns ermitteln. Wir brechen also die Zeit in ihre Komponenten herunter, passen die Werte einiger Felder an und wandeln die Komponente in eine Zeitschreibweise zurück.

  • Die Funktion zur Feststellung der Zeit des Jahresbeginns:

    datetime YearStartTime(datetime aTime)
          {
           MqlDateTime stm;
           TimeToStruct(aTime,stm);
           stm.day=1;
           stm.mon=1;
           stm.hour=0;
           stm.min=0;
           stm.sec=0;
           return(StructToTime(stm));
          }
  • Die Funktion zur Feststellung der Zeit des Monatsbeginns:

    datetime MonthStartTime(datetime aTime)
          {
           MqlDateTime stm;
           TimeToStruct(aTime,stm);
           stm.day=1;
           stm.hour=0;
           stm.min=0;
           stm.sec=0;
           return(StructToTime(stm));
          }

Unten stehen jetzt die Funktionen zur Feststellung der Zahl der Woche seit Jahresbeginn und Monatsbeginn.

  • Seit Jahresbeginn:

    long WeekNumYear(datetime aTime,bool aStartsOnMonday=false)
          {
           return(WeekNumFromDate(aTime,YearStartTime(aTime),aStartsOnMonday));
          }
  • Seit Monatsbeginn:

    long WeekNumMonth(datetime aTime,bool aStartsOnMonday=false)
          {
           return(WeekNumFromDate(aTime,MonthStartTime(aTime),aStartsOnMonday));
          }

Endlich kommen wir nun zu rein praktischen Aufgaben.


Ein experimentelles Toolset zusammenstellen

Wie bereits erwähnt, gibt es Handelszentren, deren Notierungen Sonntags-Bars enthalten und die das komplette Wochenende über Notierungen liefern. Wir sollten daher sicherstellen, dass die notwendigen Funktionen in all diesen Fällen auch korrekt funktionieren. Wir können selbstverständlich auch die passenden Handelszentren im Internet finden und die Arbeitsweise der Funktionen mittels Notierungen auf Demo-Konten testen. Doch neben der Suche nach dem richtigen Handelszentrum müssen wir auch nach den geeigneten Orten im Chart suchen, wo wir die erforderlichen Tests laufen lassen können.

Legen wir dazu also unseren eigenen Testbereich zum Testen der Funktionen an. Uns interessiert insbesondere: Freitag, Wochenenden und Montag. Wir legen also ein Array an, das die Zeit von Freitag-, Montag- und Wochenende-Bars enthält, und erhalten insgesamt vier Optionen:

  1. Keine Wochenende-Bars.
  2. Einige Bars am Ende des Sonntags, sagen wir 4.
  3. Ständige Notierungen am Wochenende.
  4. Samstag-Bars, aber keine Sonntag-Bars.

Und damit das Array nicht zu umfangreich wird, verwenden wir den H1-Zeitrahmen. Die maximale Größe des Arrays beträgt 96 Elemente (24 Bars/Tag für 4 Tage). Das Array selbst passt auf das Chart, wenn es mit Hilfe grafischer Objekte gezeichnet wird. Also erhalten wir so etwas wie einen Indikator-Puffer mit Zeit und Wiederholung über das Array in einer Schleife auf eine Art, die der ersten Ausführung der OnCalculate()-Funktion beim Start eines Indikators ähnelt. Somit können wir die Arbeitsweise der Funktion visualisieren.

Dieses Tool wird in Form eines Scripts implementiert, das an diesen Beitrag angehängt ist (Datei sTestArea.mq5). Die Vorbereitung findet in der OnStart()-Funktion des Scripts statt. Mit der 'Variant'-Variable ganz am Anfang des Funktionscodes können Sie eine der vier oben aufgeführten Optionen auswählen. Unter der OnStart()-Funktion erkennen Sie die LikeOnCalculate()-Funktion, die der OnCalculate()-Funktion von Indikatoren ähnelt. Diese Funktion besitzt zwei Parameter: rates_total - Anzahl der Bars und Zeit[] - Array mit der Zeit der Bars.

Wir arbeiten nun in dieser Funktion ganz genauso weiter, als wenn wir einen Indikator schreiben würden. Durch Aufrufen der SetMarker()-Funktion können Sie von der Funktion aus eine Markierung setzen. Die an die SetMarker()-Funktion übertragenen Parameter sind: Bar-Index, Puffer-Index (die Reihe wo die Markierung angezeigt wird) und Farbe der Markierung.

Abb. 4 zeigt die Ergebnisse der Leistung des Scripts, wobei die 'Variant'-Variable = 2 ist und unter jedem Bar zwei Reihen Markierungen gesetzt sind (Bars sind mit den relevanten Zeitstempeln versehen). Die für alle Chartelemente eingerichtete Farbe ist nicht sichtbar.

Abb. 4 Leistung des sTestArea.mq5 Scripts.  
Abb. 4 Leistung des sTestArea.mq5 Scripts.

Bar-Zeitstempel nehmen die Farbe je nach Wochentag an: Freitag = Rot, Samstag = Magenta, Sonntag = Grün, Montag = Blau. Jetzt können wir mit dem Schreiben verschiedener Funktionen beginnen, die einen speziellen Ansatz für Wochenende-Bars erfordern, sodass wir ihre Arbeit tatsächlich visuell kontrollieren können.


Pivot-Indikator - Option 1

Erzeugen wir also zunächst einen einfachen Pivot-Indikator. Zur Berechnung einer einfachen Pivot-Linie müssen wir den Schlusskurs von gestern sowie die Maximal- und Minimalkurse von gestern kennen. Der Indikatorwert wird als Mittelwert dieser drei Werte errechnet. Wir identifizieren neue Hochs und Tiefs während des Tages, errechnen den Pivot-Wert zu Beginn des neuen Tages und zeichnen und zeigen zudem die Ebene während des ganzen Tages an.

Wir sehen zwei Versionen der Funktionsweise des Indikators vor:

  1. Pivot wird jeden neuen Tag berechnet (es wird davon ausgegangen, dass es keine Wochenende-Bars gibt). Sollte es sie doch geben, werden Samstag- und Sonntag-Bars unterschiedlich behandelt.
  2. Samstag-Bars werden zu Freitag gezählt und Sonntag-Bars zu Montag (dies gilt für Fälle, wo Notierungen ständig über das gesamte Wochenende geliefert werden und wo es nur Sonntag-Bars gibt). Hier sollten Sie daran denken, dass es am Wochenende auch durchaus keine Bars geben kann.

In der ersten Version genügt es, nur den Beginn des neuen Tages zu bestimmen. Wir übertragen die aktuelle Zeit (aTimeCur) und die vorausgegangene Zeit (aTimePre) an die Funktion, berechnen die Anzahl der Tage seit Beginn der Epoche und, sollten sie nicht übereinstimmen, folgern wir daraus, dass der neue Tag begonnen hat:

bool NewDay1(datetime aTimeCur,datetime aTimePre)
  {
   return((aTimeCur/86400)!=(aTimePre/86400));
  }

Die zweite Version. Hat der Samstag begonnen, sollte der Tagesbeginn ignoriert werden. Hat der Sonntag begonnen, legen wir den Tagesbeginn fest (einfach nur ein ganz normaler neuer Tag). Hat Montag nach einem Sonntag begonnen, können wir den Tagesbeginn überspringen Ist dem Montag ein anderer Wochentag vorausgegangen, z.B. ein Samstag oder Freitag, müssen wir den Tagesbeginn definieren. Und dann bekommen wir folgende Version:

bool NewDay2(datetime aTimeCur,datetime aTimePre)
  {
   MqlDateTime stm;
//--- new day
   if(NewDay1(aTimeCur,aTimePre))
     {
      TimeToStruct(aTimeCur,stm);
      switch(stm.day_of_week)
        {
         case 6: // Saturday
            return(false);
            break;
         case 0: // Sunday
            return(true);
            break;
         case 1: // Monday
            TimeToStruct(aTimePre,stm);
            if(stm.day_of_week!=0)
              { // preceded by any day of the week other than Sunday
               return(true);
              }
            else
              {
               return(false);
              }
            break;
         default: // any other day of the week
            return(true);
        }
     }
   return(false);
  }

Und das ist die allgemeine Funktion, je nach Version:

bool NewDay(datetime aTimeCur,datetime aTimePre,int aVariant=1)
  {
   switch(aVariant)
     {
      case 1:
         return(NewDay1(aTimeCur,aTimePre));
         break;
      case 2:
         return(NewDay2(aTimeCur,aTimePre));
         break;
     }
   return(false);
  }

Testen wir nun die Arbeitsweise der Funktion mit Hilfe des Tools "sTestArea" (die angehängte Datei sTestArea_Pivot1.mq5 ; Tagesbeginn ist braun markiert). Sie müssen dazu 8 Tests laufen lassen: Zwei Funktionsversionen für 4 Optionen der Bar-Erzeugung. Wenn dann klar ist, dass die Funktionen korrekt arbeiten, können wir guten Gewissens mit der Entwicklung eines Indikators beginnen. Doch da es in diesem Beitrag ja nicht um die Entwicklung von Indikatoren geht, haben wir unten einen einsatzbereiten Indikator angehängt (Datei Pivot1.mq5), dessen schwierigster Teil seiner Entwicklung detailliert betrachtet wird.


Die Zeit-Sitzung bestimmen

Unser Expert Advisor muss innerhalb der festgelegten Zeitspanne während des Tages handeln können und das an jedem Tag in der gleichen Zeitspanne. Wir legen die Stunden und Minuten des Beginns der Handelssitzung fest sowie die Stunden und Minuten ihres Endes. Wenn wir Stunden und Minuten separat angeben, anstatt als string-Variablen, mit der Zeit in Form von "14:00" festgelegt, können wir im Strategietester Optimierungen durchführen, wenn die Funktion im Expert Advisor verwendet wird.

Die Festlegung der Zeit-Sitzung funktioniert folgendermaßen:

  1. Berechnen Sie die Zeit in Sekunden seit dem Tagesbeginn für den Startpunkt der Zeit und machen das gleiche für den Endpunkt der Zeit.
  2. Berechnen Sie die aktuelle Zeit in Sekunden seit dem Tagesbeginn.
  3. Vergleichen Sie die aktuelle Zeit mit der Start- und Endzeit.

Es ist bei einer Handelssitzung durchaus möglich, dass sie an einem Tag beginnt und an einem anderen Tag endet, d.h. wenn eine Handelssitzung über Mitternacht hinausgeht. In diesem Fall beträgt die seit Tagesbeginn errechnete Endzeit weniger als die Startzeit. Daher müssen wir zwei Prüfungen durchführen. Wir erhalten folgende Funktion:

bool TimeSession(int aStartHour,int aStartMinute,int aStopHour,int aStopMinute,datetime aTimeCur)
  {
//--- session start time
   int StartTime=3600*aStartHour+60*aStartMinute;
//--- session end time
   int StopTime=3600*aStopHour+60*aStopMinute;
//--- current time in seconds since the day start
   aTimeCur=aTimeCur%86400;
   if(StopTime<StartTime)
     {
      //--- going past midnight
      if(aTimeCur>=StartTime || aTimeCur<StopTime)
        {
         return(true);
        }
     }
   else
     {
      //--- within one day
      if(aTimeCur>=StartTime && aTimeCur<StopTime)
        {
         return(true);
        }
     }
   return(false);
  }

Sollte die Sitzung über Mitternacht hinausgehen, sollte die aktuelle Zeit größer oder gleich der Startzeit der Sitzung ODER geringer als die Endzeit der Sitzung sein. Findet die Sitzung innerhalb des Tages statt, sollte die aktuelle Zeit größer oder gleich der Startzeit der Sitzung UND geringer als die Endzeit der Sitzung sein.

Ein zum Testen der Arbeitsweise der Funktion erzeugter Indikator ist am Ende des Beitrags angehängt (Datei Session.mq5). Er kann wie jeder andere Indikator der Anwendung nicht nur für Tests, sondern auch für andere praktische Zwecke verwendet werden.


Einen Zeitpunkt während des Tages bestimmen

Eine einfache Prüfung auf die Gleichheit der spezifizierten Zeit wird nicht ordentlich funktionieren, da Kursschwankungen (Ticks) nicht in regelmäßigen Abständen auftreten und es zu Verzögerungen von einigen Sekunden bis zu einigen Minuten kommen kann. Es ist also gut möglich, dass zu der angegebenen Zeit schlichtweg einfach keine Kursschwankung im Markt auftritt. Hier müssen wir nach der Überschneidung eines gegebenen Zeitstempels suchen.

Die aktuelle Zeit sollte größer oder gleich der angegebene Zeit sein; die vorherige Zeit sollte hingegen kleiner als die angegebene Zeit sein. Da es notwendig ist, einen Zeitpunkt innerhalb des Tages zu bestimmen, müssen wir die aktuelle Zeit (so wie aus die vorherige Zeit) seit Tagesbeginn in Sekunden umwandeln. Und analog sollten auch die gegebenen Zeitparameter (Stunden und Minuten) in Sekunden umgewandelt werden. Es kann gut sein, dass die vorherige Zeit, wenn sie seit dem Tagesbeginn in Sekunden umgewandelt wird, auf den vorangeganenen Tag fällt, d.h. größer ist als die aktuelle Zeit. In diesem Fall machen wir genauso weiter wie bei der Festlegung der Zeitsitzung - wir führen zwei Prüfungen durch.

Wir erhalten folgende Funktion:

bool TimeCross(int aHour,int aMinute,datetime aTimeCur,datetime aTimePre)
  {
//--- specified time since the day start
   datetime PointTime=aHour*3600+aMinute*60;
//--- current time since the day start
   aTimeCur=aTimeCur%86400;
//--- previous time since the day start
   aTimePre=aTimePre%86400;
   if(aTimeCur<aTimePre)
     {
      //--- going past midnight
      if(aTimeCur>=PointTime || aTimePre<PointTime)
        {
         return(true);
        }
     }
   else
     {
      if(aTimeCur>=PointTime && aTimePre<PointTime)
        {
         return(true);
        }
     }
   return(false);
  }

Es gibt einen auf Basis dieser Funktion erzeugten Indikator, der diesem Beitrag angehängt ist (Datei TimePoint.mq5 ).


Pivot-Indikator - Option 2

Da wir jetzt wissen, wie man einen Zeitpunkt festlegt, können wir unseren Pivot-Indikator noch etwas verfeinern. Anstatt um 00:00, soll unser Tag nun zu jeder gegebenen Uhrzeit beginnen. Einen solchen Tag nennen wir einen benutzerdefinierten Tag. Zur Bestimmung des Beginns eines benutzerdefinierten Tages verwenden wir die zuvor beschriebene TimeCross()-Funktion. Angesichts der unterschiedlichen Optionen zur Erzeugung von Bars an Wochenenden müssen wir einige Tage einfach übergehen. An dieser Stelle gleich alle Regeln zur Prüfung zu erklären ist schwierig, also gehen wir einfach Schritt für Schritt vor. Wichtig ist, etwas zur Hand zu haben, womit man beginnen kann und auch über die Optionen zu verfügen, wie man weitermacht. Wir haben ein Test-Script, sTestArea.mq5, daher kann die richtige Lösung sogar durch Experimentieren herausgefunden werden.

Der Fall "keine Bars an Wochenenden" ist am einfachsten: ein neuer Tag beginnt an der Überschneidung eines gegebenen Zeitstempels mit der Zeit.

Für den Fall, dass es am Ende des Sonntags nur ein paar Bars gibt, definiert die TimeCross()-Funktion den ersten Sonntag-Bar als Tagesbeginn, wenn es irgendwelche Funktionsparameter gibt Es wird davon ausgegangen, dass am Wochenende keine Notierungen erfolgen (Sonntag-Bars gehören also zu Montag), also kann man den Sonntag vergessen. Wenn eine gegebene Zeit irgendwo in die Mitte einer Reihe von Sonntag-Bars fällt, sollte sie ebenfalls ignoriert werden, da der neue Tagesbeginn ja bereits am Freitag registriert worden ist

Ständige Notierungen am Wochenende: Fällt der Beginn eines benutzerdefinierten Tages in die Mitte eines Kalendertages (Abb. 5),

Abb. 5 Ein benutzerdefinierter Tag beginnt in der Mitte eines Kalendertags. Freitag = Rot, Samstag = Magenta, Sonntag = Grün, Montag = Blau.
Abb. 5 Ein benutzerdefinierter Tag beginnt in der Mitte eines Kalendertags.
Freitag = Rot, Samstag = Magenta, Sonntag = Grün, Montag = Blau.

Eine Hälfte des Sonntags kann wie Freitag behandelt werden und eine Hälfte wie Montag. Doch da gibt es einige Bars von Mitte Samstag bis Mitte Sonntag, die nirgendwo hingehören. Natürlich könnte man die Zeitspanne von Samstag bis Sonntag in gleiche Teile unterteilen und eine Hälfte als Freitag und die andere als Montag behandeln. Dies würde jedoch einen sehr einfachen Indikator erheblich verkomplizieren, obwohl Notierungen am Wochenende ja gar nicht so wichtig sind.

Die vernünftigste Lösung ist hier: alle Samstag- und Sonntag-Bars als einen benutzerdefinierten Tag zu betrachten, der von Freitag bis Montag reicht. Das bedeutet: die benutzerdefinierten Tage, die am Samstag und Sonntag beginnen, werden übersprungen.

Wir erhalten folgende Funktion:

bool NewCustomDay(int aHour,int aMinute,datetime aTimeCur,datetime aTimePre)
  {
   MqlDateTime stm;
   if(TimeCross(aHour,aMinute,aTimeCur,aTimePre))
     {
      TimeToStruct(aTimeCur,stm);
      if(stm.day_of_week==0 || stm.day_of_week==6)
        {
         return(false);
        }
      else
        {
         return(true);
        }
     }
   return(false);
  }

Es gibt einen auf Basis dieser Funktion erzeugten Indikator, der diesem Beitrag angehängt ist (Datei Pivot2.mq5).


Die Handelstage der Woche bestimmen

Einen Expert Advisor so einzustellen, dass er nur an bestimmten Wochentagen handelt, geht recht leicht. Wir brechen die Zeit mittels der TimeToStruct()-Funktion in ihre Komponenten herunter und deklarieren in den Parametern des Expert Advisors für jeden Wochentag Variablen vom bool-Type. Je nach Wochentag liefert die Funktion dann den Wert der entsprechenden Variable.

Doch das kann man noch besser machen. Man kann bei der Initialisierung eines Expert Advisosr oder Indikators, ein Array mit den Werten von Variablen füllen, die den Handel an bestimmten Tagen zulassen oder eben nicht. Dann prüft man einfach den Wert des dem Wochentag entsprechenden Array-Elements. Wir bekommen zwei Funktionen: eine wird während der Initialisierung aufgerufen und die andere je nach Bedarf.

Variablen:

input bool Sunday   =true; // Sunday
input bool Monday   =true; // Monday
input bool Tuesday  =true; // Tuesday 
input bool Wednesday=true; // Wednesday
input bool Thursday =true; // Thursday
input bool Friday   =true; // Friday
input bool Saturday =true; // Saturday

bool WeekDays[7];

Die Initialisierungsfunktion:

void WeekDays_Init()
  {
   WeekDays[0]=Sunday;
   WeekDays[1]=Monday;
   WeekDays[2]=Tuesday;
   WeekDays[3]=Wednesday;
   WeekDays[4]=Thursday;
   WeekDays[5]=Friday;
   WeekDays[6]=Saturday;
  }

Die Hauptfunktion:

bool WeekDays_Check(datetime aTime)
  {
   MqlDateTime stm;
   TimeToStruct(aTime,stm);
   return(WeekDays[stm.day_of_week]);
  }

Ein auf Basis dieser Funktion erzeugte Indikator steht am Ende des Beitrags zur Verfügung (die Datei namens TradeWeekDays.mq5).


Die Handelszeit der Woche bestimmen

Wir müssen Handelssitzungen von einer bestimmten Uhrzeit an einem Wochentag bis zu einer bestimmten Uhrzeit an einem anderen Wochentag feststellen. Diese Funktion ähnelt der TimeSession()-Funktion, jedoch mit dem einen Unterschied, dass ihre Berechnungen auf der seit Wochenbeginn verstrichenen Zeit beruhen. Wir bekommen folgende Funktion:

bool WeekSession(int aStartDay,int aStartHour,int aStartMinute,int aStopDay,int aStopHour,int aStopMinute,datetime aTimeCur)
  {
//--- session start time since the week start
   int StartTime=aStartDay*86400+3600*aStartHour+60*aStartMinute;
//--- session end time since the week start
   int StopTime=aStopDay*86400+3600*aStopHour+60*aStopMinute;
//--- current time in seconds since the week start
   long TimeCur=SecondsFromWeekStart(aTimeCur,false);
   if(StopTime<StartTime)
     {
      //--- passing the turn of the week
      if(TimeCur>=StartTime || TimeCur<StopTime)
        {
         return(true);
        }
     }
   else
     {
      //--- within one week
      if(TimeCur>=StartTime && TimeCur<StopTime)
        {
         return(true);
        }
     }
   return(false);
  }

Ein auf Basis dieser Funktion erzeugte Indikator steht am Ende des Beitrags zur Verfügung ( Datei SessionWeek.mq5).

Wir haben uns jetzt buchstäblich alle der häufigsten Aufgaben im Zusammenhang mit Zeit und auch die relevanten Programmierungstechniken angesehen sowie die zu Lösung dieser Aufgaben notwendigen Standard MQL5-Funktionen .


Zusätzliche MQL5-Funktionen

Doch zur Arbeit mit Zeit gibt es noch einige weitere MQL5-Funktionen: TimeTradeServer(), TimeGMT(), TimeDaylightSavings() und TimeGMTOffset(). Die hauptsächliche Besonderheit dieser Funktionen ist, dass sie alle in den Uhr- und Zeiteinstellungen eines Benutzer-PCs verwendet werden.

Die TimeTradeServer() Funktion. Es wurde in diesem Beitrag bereits erwähnt, dass die TimeCurrent()-Funktion an Wochenenden die falsche Uhrzeit anzeigt (nämlich die Zeit, an der freitags die letzte Kursänderung passiert ist). Die TimeTradeServer() Funktion berechnet die korrekte Serverzeit:

datetime tm=TimeTradeServer();
//--- output result
Alert(tm);

Die TimeGMT() Funktion. Diese Funktion berechnet die GMT-Zeit auf Basis der Uhr- und Zeiteinstellungen im Computer eines Benutzers: Zeitzone und Sommerzeit:

datetime tm=TimeGMT();
//--- output result
Alert(tm);

Genauer gesagt, liefert die Funktion die Weltzeit (UTC-Zeit).

Die TimeDaylightSavings() Funktion. Diese Funktion liefert die Korrekturwerte für Sommerzeit von den Einstellungen des Benutzer-Computers..

int val=TimeDaylightSavings();
//--- output result
Alert(val);

Um die Uhrzeit ohne Korrekturwert für Sommerzeit zu erhalten, sollten Sie den Korrekturwert der Ortszeit hinzuzählen.

Die TimeGMTOffset()Funktion. Diese Funktion verschafft Ihnen die Zeitzone des Benutzer-Computers. Der Wert wird in Sekunden geliefert und muss der Ortszeit hinzugefügt werden, damit man die GMT-Zeit bekommt.

int val=TimeGMTOffset();
//--- output result
Alert(val);

Die Zeit auf einem Benutzer-Computer ist TimeGMT()-TimeGMTOffset()-TimeDaylightSavings():

datetime tm1=TimeLocal();
datetime tm2=TimeGMT()-TimeGMTOffset()-TimeDaylightSavings();
//--- output result
Alert(tm1==tm2);


Einige weitere nützliche Funktionen zur Arbeit mit Zeit

Funktion zur Feststellung eines Schaltjahres

bool LeapYear(datetime aTime)
  {
   MqlDateTime stm;
   TimeToStruct(aTime,stm);
//--- a multiple of 4 
   if(stm.year%4==0)
     {
      //--- a multiple of 100
      if(stm.year%100==0)
        {
         //--- a multiple of 400
         if(stm.year%400==0)
           {
            return(true);
           }
        }
      //--- not a multiple of 100 
      else
        {
         return(true);
        }
     }
   return(false);
  }

Das Prinzip zur Feststellung eines Schaltjahres wurde bereits oben unter "Besonderheiten der Zeitmessung" erklärt.

Funktion zur Feststellung der Anzahl an Tagen in einem Monat

int DaysInMonth(datetime aTime)
  {
   MqlDateTime stm;
   TimeToStruct(aTime,stm);
   if(stm.mon==2)
     {
      //--- February
      if(LeapYear(aTime))
        {
         //--- February in a leap year 
         return(29);
        }
      else
        {
         //--- February in a non-leap year 
         return(28);
        }
     }
   else
     {
      //--- other months
      return(31-((stm.mon-1)%7)%2);
     }
  }

Diese Funktion prüft, ob es sich um ein Schaltjahr handelt und liefert den entsprechenden Wert von entweder 18 oder 29 für Februar und errechnet die Anzahl der Tage für andere Monate. Die Anzahl der Tage in den ersten 7 Monaten wechselt folgendermaßen ab: 31, 30, 31, 30, usw. Das gilt auch für die Tage der übrigen 5 Monate. Daher berechnet diese Funktion den Rest per Teilung durch 7. Danach machen wir die ungerade Paritätskontrolle und die erhaltene Berichtigung wird von 31 abgezogen.


Besonderheiten der Arbeitsweise der Zeitfunktion im Strategietester

Der Strategietester generiert seinen eigenen Strom an Notierungen und Werten der TimeCurrent()-Funktion entsprechend des Stroms an Notierungen im Strategietester. Die Werte der TimeTradeServer()-Funktion entsprechen den Werten von TimeCurrent(). Ganz ähnlich entsprechend die Werte der TimeLocal()-Funktion denen von TimeCurrent(). Die TimeCurrent() Funktion im Strategietester berücksichtigt keine Zeitzonen und auch keine Korrekturen für Sommerzeit. Die Arbeit der Expert Advisors beruht auf Kursänderungen. Wenn Ihr Expert Advisor also mit Zeit arbeiten muss, sollten Sie die TimeCurrent()-Funktion verwenden. So können Sie Ihren Expert Advisor sicher im Strategietester testen.

Die TimeGMT(), TimeDaylightSavings() und TimeGMTOffset() Funktionen arbeiten ausschließlich auf Basis der aktuellen Einstellungen im Computer des Benutzers (Umstellung auf Sommerzeit und zurück werden im Strategietester nicht simuliert). Wenn Sie also beim Testen eines Expert Advisors die Zeitumstellungen auf Sommerzeit und zurück auf die Normalzeit simulieren müssen (falls das wirklich notwendig werden sollte), dann müssen Sie sich selbst darum kümmern. Dazu benötigen Sie Angaben zu den exakten Daten und der Uhrzeit für die Uhrumstellung sowie eine Menge umfassender Analyse.

Eine Lösung für dieses Problem vorzustellen, ist weit umfangreicher als es der Umfang eines einzigen Beitrags zulassen würde und wird daher hier jetzt nicht diskutiert. Arbeitet ein Expert Advisor innerhalb von europäischer oder amerikanischer Sitzungszeiten, sollte es zwischen Serverzeit und Ereigniszeit keinerlei Diskrepanzen geben. Ganz anders sieht es jedoch bei asiatischen/pazifischen Sitzungszeiten aus: Japan hat keine Sommerzeit und Australien stellt im November auf Sommerzeit um.


Fazit

Dieser Beitrag hat alle Standard MQL5-Funktionen zur Arbeit mit Zeit behandelt und Programmierungstechniken dargelegt, die im Umgang mit Zeit-relevanten Aufgaben verwendet werden. Er zeigte auch, wie man mehrere Indikatoren und einige nützliche Funktionen erzeugen kann und lieferte detaillierte Beschreibungen ihrer Funktionsprinzipien.

Alle Standardfunktionen zur Arbeit mit Zeit lassen sich in mehrere Kategorien unterteilen:

  1. TimeCurrent() und TimeLocal() sind die Hauptfunktionen zur Feststellung der aktuellen Zeit.
  2. TimeToString(), StringToTime(), TimeToStruct() und StructToTime() sind Funktionen zur Verarbeitung von Zeit.
  3. CopyTime() ist eine Funktion zur Arbeit mit Bar-Zeit.
  4. TimeTradeServer(), TimeGMT(), TimeDaylightSavings() und TimeGMTOffset() sind alles Funktionen, die von den Einstellungen des Computers eines Benutzers abhängen.


Angehängte Dateien

  • sTestArea.mq5 - ein Script zum Testen komplexer Zeitfunktionen.
  • sTestArea_Pivot1.mq5 - das sTestArea.mq5 Script, das zum Testen der Zeitfunktionen des Pivot1.mq5 Indikators verwendet wird.
  • Pivot1.mq5 - ein Pivot-Indikator, der Standardtage verwendet (die NewDay Funktion).
  • Session.mq5 - der Indikator der Handelssitzung eines Tages (die TimeSession Funktion).
  • TimePoint.mq5 - ein Indikator eines gegebenen Zeitpunkts (die TimeCross Funktion).
  • Pivot2.mq5 - ein Pivot-Indikator, der benutzerdefinierte Tage verwendet (die NewCustomDay Funktion).
  • TradeWeekDays.mq5 - ein Indikator der Handelstage einer Woche(die WeekDays_Check Funktion).
  • SessionWeek.mq5 - der Indikator der Handelssitzung einer Woche (die WeekSession Funktion).
  • TimeFunctions.mqh - Alle in diesem Beitrag angebotenen Zeitfunktionen in einer einzigen Datei.

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

Beigefügte Dateien |
stestarea.mq5 (4.96 KB)
pivot1.mq5 (4.5 KB)
session.mq5 (3.51 KB)
timepoint.mq5 (3.32 KB)
pivot2.mq5 (4.14 KB)
tradeweekdays.mq5 (3.59 KB)
sessionweek.mq5 (5.08 KB)
timefuncions.mqh (20.52 KB)
Wie man ein Anbieter von Signalen für MetaTrader 4 und MetaTrader 5 wird Wie man ein Anbieter von Signalen für MetaTrader 4 und MetaTrader 5 wird
Möchten Sie Ihre Handelssignale anbieten und Gewinne erzielen? Registrieren Sie sich auf der Website MQL5.com als Verkäufer, geben Sie Ihr Handelskonto an und bieten Sie Händlern ein Abonnement an, um Ihre Handelsgeschäfte zu kopieren.
Berechnung wesentlicher Merkmale von Indikator-Emissionen Berechnung wesentlicher Merkmale von Indikator-Emissionen
Indikator-Emissionen sind ein eher selten untersuchter Bereich der Markterforschung. Dies liegt hauptsächlich an ihrer schwierigen Analyse aufgrund der Verarbeitung sehr umfangreicher Arrays von zeitlich variablen Daten. Die existierende grafische Analyse ist zu Ressourcen-intensiv und hat demzufolge dazu geführt, dass ein sparsamer Algorithmus entwickelt wurde, der mit Zeitreihen von Emissionen arbeitet. Dieser Beitrag zeigt, wie die visuelle Analyse (intuitives Bild) durch das Studium wesentlicher Merkmale von Emissionen ersetzt werden kann. Dies kann sowohl für Händler als auch Entwickler von automatischen Handelssystemen interessant sein.
Wie teste ich einen Handelsroboter vor dem Kauf Wie teste ich einen Handelsroboter vor dem Kauf
Der Kauf eines Handelsroboters hat bestimmte Vorzüge gegenüber ähnlichen Möglichkeiten - ein automatisiertes System kann direkt im MetaTrader5-Terminal getestet werden. Vor dem Kauf kann und soll ein Expert Advisor sorgfältig in allen ungünstigen Modi im eingebauten Strategietester ausgeführt werden, um das System komplett zu verstehen.
Allgemeine Informationen zu Handelssignalen für MetaTrader 4 und MetaTrader 5 Allgemeine Informationen zu Handelssignalen für MetaTrader 4 und MetaTrader 5
MetaTrader 4 / MetaTrader 5 Handelssignale ist ein Service, mit dessen Hilfe Händler Handelsoperationen eines Signale-Anbieters kopieren können. Unser Ziel war es, diesen neuen, intensiv genutzten Service zu entwickeln und somit Abonnenten zu schützen und ihnen zugleich unnötige Kosten zu ersparen.