English Русский 日本語
preview
Vom Neuling zum Experten: Implementierung von Fibonacci-Strategien im Post-NFP-Handel

Vom Neuling zum Experten: Implementierung von Fibonacci-Strategien im Post-NFP-Handel

MetaTrader 5Beispiele |
15 0
Clemence Benjamin
Clemence Benjamin

Inhalt


Einführung

Das Verpassen des genauen Zeitpunkts einer wichtigen Wirtschaftsmeldung – wie z. B. der NFP (Non-Farm Payroll, nicht-landwirtschaftliche Löhne und Gehälter) – löst häufig FOMO (Fear of Missing Out, also die Angst, etwas zu verpassen) aus. Dies geschieht in der Regel, weil die Händler sich beeilen, die Bewegung zu erwischen, auch wenn sie bereits beendet ist. In den meisten Fällen dauert der anfängliche Ausreißer (spike)weniger als eine Minute und bewegt viele Pips in die vom Markt bevorzugte Richtung. Ein später Einstieg ist in solchen Situationen äußerst riskant, da der Anstieg bereits abgeschlossen ist und der Kurs sich schnell umkehren kann, was zu erheblichen Verlusten führt.

Professionelle Händler jagen diesen Ausreißern jedoch nicht hinterher. Stattdessen bereiten sie sich entweder im Voraus auf frühe, sichere Einstiege vor oder warten geduldig darauf, dass sich der Markt zurückentwickelt und neue Gelegenheiten zu gut kalkulierten Einstiegsniveaus bietet. Ihr Vorteil liegt darin, dass sie die Fibonacci-Retracement-Prinzipien verstehen und die Disziplin beherrschen, zu warten, bis die richtigen Bedingungen hergestellt sind.

In diesem Beitrag werden wir uns mit den Herausforderungen des Handels nach nachrichtengetriebenen Ausreißern befassen, indem wir die Fibonacci-Prinzipien auf algorithmische Weise anwenden. Um die Grundlage zu schaffen, wird im nächsten Abschnitt eine kurze Einführung in Fibonacci für diejenigen gegeben, die mit dem Konzept noch nicht vertraut sind, bevor wir uns mit der Entwicklung und Umsetzung der Strategie befassen.

Ein kurzer Blick auf das Fibonacci Retracement

Die von dem italienischen Mathematiker Leonardo Bonacci (allgemein bekannt als Fibonacci) vor vielen Jahrhunderten entwickelte Fibonacci-Folge ist zu einem der einflussreichsten Instrumente der modernen technischen Analyse geworden. In der Mathematik ist die Reihenfolge einfach: Jede Zahl ist die Summe der beiden vorhergehenden Zahlen (1, 1, 2, 3, 5, 8, 13, ...). Doch sein Auftreten in der Natur, in der Architektur und sogar im menschlichen Verhalten fasziniert Gelehrte seit Generationen.

Auf den Finanzmärkten wird die Fibonacci-Sequenz durch Fibonacci-Retracement-Levels angewandt – eine Methode, die Händler verwenden, um potenzielle Umkehr- oder Fortsetzungszonen während Marktkorrekturen zu identifizieren. Die am häufigsten verwendeten Retracement-Verhältnisse sind 23,6 %, 38,2 %, 50 %, 61,8 % und 78,6 %, die sich jeweils aus den Beziehungen innerhalb der Fibonacci-Sequenz ableiten. Diese Niveaus fungieren als psychologische Marker, bei denen Händler Kursreaktionen vorwegnehmen, die entweder innehalten, abprallen oder sich umkehren.

Das Prinzip des Retracements besteht darin, dass sich die Märkte nie in einer geraden Linie bewegen. Nach einer starken Aufwärts- oder Abwärtsbewegung geben die Kurse in der Regel nach, bevor sie ihren Trend wieder aufnehmen. Das Fibonacci-Retracement bietet einen Rahmen für die Messung der wahrscheinlichen Tiefe solcher Rücksetzer. Zum Beispiel a:

  • Das 38,2 %-Retracement deutet oft auf einen flachen Rückschlag in einem starken Trend hin.
  • Das 50%-Retracement (auch wenn es keine echte Fibonacci-Zahl ist) wird häufig als Mittelwertkorrektur verwendet.
  • Das 61,8%-Retracement, auch bekannt als „Goldener Schnitt“, gilt als das kritischste Niveau, bei dem es häufig zu größeren Umkehrungen kommt.

In der Handelspraxis werden die Fibonacci-Levels selten isoliert verwendet. Sie werden noch aussagekräftiger, wenn sie mit Kursbewegungen, Unterstützungs- und Widerstandsfaktoren, gleitenden Durchschnitten oder ereignisgesteuertem Marktverhalten kombiniert werden – wie z. B. Rücksetzern, die kurz nach wichtigen Nachrichten auftreten.

Bei dieser Strategie konzentrieren wir uns auf den Handel nach dem NFP, wobei das Fibonacci-Retracement dazu beiträgt, das Rauschen des anfänglichen Anstiegs herauszufiltern und die Händler zu strukturierten Einstiegspunkten mit höherer Wahrscheinlichkeit zu führen. Anstatt dem FOMO zu erliegen, lernt der Händler: „Warte auf den Rücksetzer“ und erst bei nachhaltigeren Niveaus einzusteigen. Es gibt eine Fülle von Informationen über Fibonacci, die auf der MQL5-Community-Plattform zur weiteren Lektüre zur Verfügung stehen.

Zugänglichkeit auf dem MetaTrader 5 Terminal

In der Standardoberfläche von MetaTrader 5 ist die Charting-Toolbox priorisiert und sofort verfügbar – die am häufigsten verwendeten analytischen Objekte sind vorgeladen und können direkt auf den Chart gezogen werden. Gezeichnete Werkzeuge (z. B. das Fibonacci-Retracement-Objekt) verfügen über bewegliche Eckpunkte, die Sie auf Umkehrpunkten platzieren können, um Preisniveaus visuell darzustellen. Händler nutzen diese Objekte, um zu überprüfen, wie der Kurs in der Vergangenheit auf die Niveaus reagiert hat, und um wahrscheinliche Reaktionszonen für zukünftige Rückschritte zu prognostizieren.

Praktische Tipps:

  • Ziehen Sie das Werkzeug auf ein klaren hohen und tiefen Umkehrpunkt (oder umgekehrt), um das Retracement an sinnvollen Referenzpunkten zu verankern.
  • Verwenden Sie die Scheitelpunkte des Objekts, um die Platzierung fein abzustimmen, sodass die Ebenen mit den Dochten oder den Schlusskursen der Kerzen übereinstimmen, je nachdem, welche Regel Sie bevorzugen.
  • Speichern Sie häufig verwendete Vorlagen oder Profile, damit Ihre bevorzugten Fibonacci-Einstellungen und das Chart-Layout in allen Charts sofort verfügbar sind.

Siehe Abbildung 1 unten, in der das Fibonacci-Retracement auf dem Chart eingezeichnet ist.

Zeichnen des Fibonacci Retracement im MetaTrader 5 Terminal

Abbildung 1. Zeichnen des Fibonacci Retracement im MetaTrader 5 Terminal

Konzeptstudie unter Verwendung der Bekanntgabe der Non-Farm Payroll vom 5. September 2025:

Eine manuelle Überprüfung des Kursgeschehens nach der NFP-Ankündigung am 5. September 2025 unterstützt die Idee, dass Fibonacci-Retracement-Levels eine sinnvolle Orientierungshilfe für den Einstieg nach dem Ereignis sein können. In den beigefügten Abbildungen wird das Fibonacci-Retracement-Tool verwendet, um hervorzuheben, wie der Kurs nach dem anfänglichen Anstieg bestimmte Niveaus getestet und respektiert hat, was dabei hilft, Zonen mit hoher Wiedereinstiegswahrscheinlichkeit von Rauschen zu unterscheiden.

Diese Beobachtungen ermöglichen es uns, diszipliniertere Regeln für die Auftragsplatzierung und das Risikomanagement zu entwickeln, die sich an den Retracement-Bändern und den nächstgelegenen Referenzpreisen (Basislinie vor dem Ausreißer und dessen Extremwert) orientieren. Der Übersichtlichkeit halber habe ich drei Charts von Währungspaaren analysiert, die den US-Dollar einschließen, da USD-Paare in der Regel stärkere und konsistentere Bewegungen im Zusammenhang mit wichtigen US-Konjunkturdaten aufweisen.

Unsere Studie stützt sich auf die jüngsten NFP-Veröffentlichungen, die zum Zeitpunkt der Erstellung dieser Studie vorlagen. Für die Fallstudie analysierten wir drei auf den USD ausgerichtete Paare – GBP/USD, USD/JPY und EUR/USD – bevor wir die Idee in einen Algorithmus umwandelten. Nachfolgend finden Sie Bildschirmabbildungen, die zeigen, wie die Technik in den einzelnen Beispielen funktioniert.

EURUSD nach dem NFP 05/09/25

Abbildung 2. EURUSD, M5, Kursentwicklung nach NFP-Ankündigung

In Abbildung 2 (EUR/USD, M5) beachtet der Kurs eindeutig die Gesetze des Retracement. Die Markierungen A und B bezeichnen die Basislinie vor dem Ausreißer und das durch die NFP-Ankündigung ausgelöste Extremum des Ausreißers. Das Fibonacci-Retracement wurde für die erste M5-Kerze nach der Veröffentlichung von A (Basislinie) nach B (Höchststand des Ausreißers) gezogen. Der Preis zog daraufhin zurück, um die Fibonacci-Bänder zu testen, und die Retracement-Niveaus werden sichtbar respektiert – in diesem Beispiel wurde das 50%-Niveau erreicht und diente als sinnvoller Reaktionspunkt. Der Preis bewegte sich zurück und testete das 61,8 %-Fibonacci-Retracement und schob sich sogar leicht über dieses Band hinaus, obwohl er nicht bis zur Basislinie (100 %) zurückging. Nach dieser leichten Überschreitung kehrte die Dynamik zurück, und der Markt erreichte einen neuen Höchststand, der das ursprüngliche Extremum des Ausreißers übertraf, was die Fortsetzung des Aufwärtstrends bestätigte.

Abbildung 2: GBPUSD, M5, Kursentwicklung nach der NFP-Ankündigung.

Abbildung 3. GBPUSD, M5, Kursentwicklung nach NFP-Ankündigung.

EUR/USD ist stark mit GBP/USD korreliert, und GBP/USD reagierte ähnlich – wenn auch nicht identisch – auf die Ankündigung. Abbildung 3 (oben) zeigt GBP/USD mit der gleichen Fibonacci-Überlagerung wie in Abbildung 2, die veranschaulicht, wie der Preis auf die Nachrichten für dieses Paar reagierte, und die Konsistenz des Retracement-Verhaltens zwischen den Paaren unterstreicht.

USDJPY, M5

Abbildung 4. USDJPY, M5, Kursentwicklung nach NFP-Ankündigung

Abbildung 4 (USD/JPY) zeigt eine deutlich rückläufige Reaktion auf die Veröffentlichung des Nationalen Konjunkturberichts. Nach der anfänglichen Abwärtsspirale (Punkte A→B) zog sich der Kurs bis etwa zum 50 %-Fibonacci-Retracement-Niveau zurück, bevor er seinen Rückgang wieder aufnahm und neue Tiefststände erreichte. Zusammengenommen zeigen die drei Charts (EUR/USD, GBP/USD und USD/JPY) eine gleichbleibende Wahrscheinlichkeit, dass die Kurse nach einer Nachrichtenspitze in die Fibonacci-Zonen zurückgehen – selbst wenn die Richtung zwischen den Paaren unterschiedlich ist.

Implikation & High-Level-Ansatz (vor der Kodierung)

Mit den drei Beispielen (EUR/USD, GBP/USD und USD/JPY) im Hinterkopf können wir einen kompakten Plan auf hoher Ebene definieren, um die Idee in einen Algorithmus umzusetzen:

Definitionen
  • P_base – die Basislinie vor dem Ausreißer (Preis, bei dem die schnelle Bewegung ihren Ursprung hatte).
  • P_spike – das Extremum des Ausreißers (höchster Preis für einen Sprung nach oben, niedrigster Preis für einen Abwärtssprung).
  • Richtung des Ausreißers – aufwärts, wenn P_spike > P_base (Aufwärtsausreißer); abwärts, wenn P_spike < P_base (Abwärtsausreißer).
Eingangslogik (beide Richtungen)
1. Zeichnen Sie Fibonacci-Retracement-Levels zwischen P_base und P_spike nach der ersten abgeschlossenen Referenzkerze nach dem Ereignis. 2. Warten Sie auf ein strukturiertes Retracement in die Zielzone (z.B. 38,2-61,8%) mit der von Ihnen gewählten Bestätigung (Schlusskurs der Kerze, Ablehnung des Dochts, Tick-Volumen-Normalisierung usw.). 3. Für eine Aufwärtsausreißer (der Kurs stieg an und ging dann zurück):
  • Einstieg: Verkaufen Sie, wenn der Kurs in die gewählte Fib-Zone zurückgeht und eine Bestätigung erfolgt.
  • Take Profit (TP): primärer TP bei P_spike (dem Extremum des Ausreißers). Sekundäre/erweiterte TPs können Fib-Erweiterungen oder ATR-Multiples verwenden.
  • Stop Loss (SL): Platzieren Sie den SL bei oder knapp unter P_base (der Basis/dem Ursprung der schnellen Bewegung). Fügen Sie einen kleinen Puffer hinzu (z. B. Spread + X Pips), um zu vermeiden, dass Sie durch Mikrorauschen gestoppt werden.
4. Bei einem Abwärtsausreißer (der Kurs ist nach unten gesprungen und hat sich dann zurückgebildet):
  • Einstieg: Verkaufen Sie, wenn der Kurs in die gewählte Fib-Zone zurückgeht und eine Bestätigung erfolgt.
  • TP: primäre TP bei P_spike (der untere Extremwert des Abwärtsausreißers).
  • SL: Platzieren Sie SL an oder knapp über P_base (dem Ursprung der schnellen Abwärtsbewegung), mit einem kleinen Puffer.
Warum diese Zuordnung?
  • Die Verwendung von P_spike als TP richtet das Handelsziel auf den letzten nachweisbaren Extremwert aus – wir versuchen, den erneuten Test des Ausreißer-Extremwerts zu erfassen (die „Rückkehr zum Ausreißer“).
  • Bei der Verwendung von P_base als SL wird der Ausgangspunkt der schnellen Bewegung als logische Entwertung verwendet: Wenn der Preis über den Ausgangspunkt hinaus zurückgeht, wird die These des Wiedereinstiegs nach dem Ausreißer gebrochen.

Praktische Anpassungen und Risikokontrollen

Wir puffern den SL um einen kleinen festen Betrag (z.B. einige Pips oder Spread × 1,5) und berechnen die Losgröße aus Kontorisiko % × Abstand (SL-Eröffnung), um das absolute Risiko klein zu halten (Ziel 0,5%). Wenn P_base zu weit entfernt ist, wird ein alternativer engerer SL (next Fib oder ATR(5m) × Faktor) als wählbarer Parameter angeboten. Der EA unterstützt Teilausstiege bei P_spike, Trailing der verbleibenden Position, automatische Stornierung veralteter Limit-Orders nach M Minuten und einen optionalen Bestätigungs-Toggle für korrelierte Paare.

Strategisches Vorgehen mit Fibonacci

Abbildung 5. Visueller Überblick über die Post-NFP-Fibonacci-Wiedereinstiegsstrategie

Abbildung 5 (oben) ist eine konzeptionelle Visualisierung der Strategie. Bei einem Aufwärts-Setup platzieren Sie den Stop-Loss knapp unter Punkt A (der Basis) und den Take-Profit über Punkt B (dem Extremum); bei einem Abwärts-Setup platzieren Sie den Stop-Loss über A und den Take-Profit unter B (typisches Beispiel Abbildung 4 USDJPY). Fügen Sie dem Stop-Loss einen kleinen Puffer hinzu (z. B. einige Pips oder Spread × 1,5), um ein Stop-Out durch das Rauschen zu reduzieren.

Grenzfälle

Wenn der Kurs vollständig auf 100 % zurückgeht (d. h. zurück zu P_base) und dann weitergeht, sollten Sie die ursprüngliche These widerrufen – der Rücklauf ist abgeschlossen, und die Bewegung könnte sich umkehren.

Wenn die Richtung des Ausreißers unklar ist oder die Schwankungsbreite < Minimum Bars/Pips, brechen Sie den Handel ab (zu laut).


Strategie zur Umsetzung

Um den Entwicklungsprozess zu vereinfachen, habe ich in MetaEditor 5 einen eigenen Klassenkopf implementiert, der für die Verwaltung der Zeitstempel der Nonfarm Payrolls (NFP)-Ankündigungen und deren korrekte Konvertierung zwischen Eastern Time (ET), UTC und der Serverzeit des Brokers zuständig ist. Diese Kopfzeile zentralisiert die Handhabung der Sommerzeit, die Berechnung des ET-Offsets und die Konvertierungsprogramme, sodass andere Module zeitzonenunabhängig bleiben.

Anhand dieses Headers habe ich einen Expert Advisor entwickelt, der:

  • die erste M5-Kerze ermittelt, die nach der NFP-Veröffentlichung geschlossen wird,
  • ein an dieser Kerze verankertes Fibonacci-Retracement zeichnet,
  • schwebende Aufträge auf den Niveaus 38,2 %, 50,0 % und 61,8 % platziert.
  • robuste Maklerkontrollen (Mindestabstände, Normalisierung, Verfall) anwendet und
  • den Lebenszyklus von Chart-Objekten und schwebenden Aufträgen (Bereinigung bei Füllung oder Verfall) verwaltet.

In den nächsten beiden Schritten werden wir detaillierte, entwicklerfreundliche Erklärungen des Codes erstellen: zuerst den NFPTimeManager-Header (Design-Entscheidungen, APIs, DST- und Zeitzonen-Logik), dann den EA (Datenfluss, Auftragsverwaltung und Sicherheitsüberprüfungen). Diese Erläuterungen zeigen, wie das System in MQL5 entworfen, implementiert und getestet wurde.

Schritt 1: NFPTimeManager-Header

1.1. Datei-Header und Metadaten

Diese Datei beginnt mit einem Standard-Header-Block und MQL5 #Property-Attributen. Der Header dokumentiert die Urheberschaft und die Versionierung, sodass andere Entwickler (oder Sie selbst) sofort den Zweck und die Herkunft der Datei erkennen können. Die Eigenschaften ermöglichen es dem MQL5-Build-System, Metadaten in MetaEditor 5 anzuzeigen.

//+------------------------------------------------------------------+ 
//|                                               NFPTimeManager.mqh  |
//|                        Copyright 2025, Clemence Benjamin  |
//|                                       https://www.mql5.com/en/users/billionaire2024/seller|
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property version   "1.0"

1.2. Klassendeklaration und private Zeitzonenfelder

Wir kapseln alle NFP-bezogenen Dienstprogramme in der Klasse CNFPTimeManager. Zwei private Integer-Felder erfassen die optionale Zeitzone des Händlers und die Zeitzone des Brokers/Servers. Die Beibehaltung dieser internen Felder macht die Konvertierungsfunktionen deterministisch und vermeidet eine Streuung der Zeitzonenlogik über die gesamte Codebasis.

class CNFPTimeManager
{
private:
   int      m_traderTimeZone; // Trader time zone offset from UTC (hours) - optional
   int      m_serverTimeZone; // Server time zone offset from UTC (hours)

1.3. GetETOffset – Berechnung der Zeitverschiebung mit DST von „Eastern-Time“

Das NFP wird zu einer festen Lokalzeit (08:30 Uhr ET) veröffentlicht. GetETOffset berechnet, ob ein bestimmtes Datum die Eastern Standard Time (UTC-5) oder die Eastern Daylight Time (UTC-4) verwendet. Es verwendet U.S. Sommerzeitregeln (2. Sonntag März bis 1. Sonntag November) und gibt den ganzzahligen UTC-Verschiebung zurück. So bleibt die Kalenderberechnung über die Jahreszeiten hinweg korrekt.

   // returns ET offset (-4 for EDT, -5 for EST) for a given datetime (trader time context)
   int GetETOffset(datetime time)
   {
      MqlDateTime dt;
      TimeToStruct(time, dt);
      int year = dt.year;

      // DST: 2nd Sunday of March to 1st Sunday of November (US rules)
      datetime dst_start = GetNthDayOfWeek(year, 3, 0, 2); // 2nd Sunday of March
      datetime dst_end   = GetNthDayOfWeek(year, 11, 0, 1); // 1st Sunday of November
      // Note: We interpret the DST boundaries as local US/Eastern dates at 02:00 local time,
      // but for our purpose using whole-day thresholds is acceptable (NFP at 8:30 ET).
      if(time >= dst_start && time < dst_end)
         return -4; // EDT
      return -5;    // EST
   }

1.4. GetNthDayOfWeek – robuste monatliche Kalendermathematik

Kalenderarithmetik wie „zweiter Sonntag im März“ kann erstaunlich fehleranfällig sein. GetNthDayOfWeek bildet das Datum für den 1. des Monats, findet den Wochentag dieses Tages und berechnet den Abstand zum gewünschten N-ten Wochentag. Die Funktion gibt eine genaue Datumsangabe für das angeforderte Ereignis zurück und wird von den Zeitstempelberechnungen der Sommerzeit und des NFP wiederverwendet.

   // correctly compute the N-th day_of_week (0=Sunday..6=Saturday) in given month/year
   datetime GetNthDayOfWeek(int year, int month, int day_of_week, int n)
   {
      MqlDateTime dt;
      // build date for the 1st of month
      dt.year = year;
      dt.mon  = month;
      dt.day  = 1;
      dt.hour = 0;
      dt.min  = 0;
      dt.sec  = 0;
      datetime first = StructToTime(dt);
      MqlDateTime dt2;
      TimeToStruct(first, dt2);
      int dow_first = dt2.day_of_week; // 0..6
      int delta = (day_of_week - dow_first + 7) % 7;
      int days_to_nth = delta + (n - 1) * 7;
      return first + days_to_nth * 86400;
   }

1.5. Konstruktor und Zeitzonensetzer

Ein kurzer Konstruktor initialisiert die Zeitzonenfelder auf Null. Zwei öffentliche Setter ermöglichen es dem aufrufenden Code, die Zeitzone des Händlers oder des Servers explizit festzulegen, wenn die automatische Erkennung nicht erwünscht ist oder wenn Sie die Erkennung für Tests oder entfernte Server außer Kraft setzen müssen.

public:
   CNFPTimeManager()
   {
      m_traderTimeZone = 0;
      m_serverTimeZone = 0;
   }

   // set trader timezone if you need to convert between trader/local and server
   void SetTraderTimeZone(int timezone_hours)
   {
      m_traderTimeZone = timezone_hours;
   }

   void SetServerTimeZone(int timezone_hours)
   {
      m_serverTimeZone = timezone_hours;
   }

1.6. DetectServerTimeZone und GetServerTime – Hilfsprogramme für die Server-Erkennung

DetectServerTimeZone ist eine Funktion, die TimeCurrent() (Serverzeit) mit TimeLocal() (lokale Rechnerzeit) vergleicht und die ganzzahlige Verschiebung der Stunden speichert. GetServerTime umhüllt einfach TimeCurrent(), sodass andere Module eine einzige Quelle für den aktuellen Zeitstempel des Servers aufrufen.

   // attempt to auto-detect server timezone (difference between server and local)
   int DetectServerTimeZone()
   {
      datetime local = TimeLocal();
      datetime server = TimeCurrent();
      int offset_seconds = (int)(server - local);
      int offset_hours = offset_seconds / 3600;
      m_serverTimeZone = offset_hours;
      return offset_hours;
   }

   datetime GetServerTime()
   {
      return TimeCurrent();
   }

1.7. GetNFPTimestampTrader – NFP-Lokalzeit von ET

Diese Methode gibt eine Datumszeit zurück, die das NFP-Ereignis um 08:30 Uhr ET am ersten Freitag eines bestimmten Monats/Jahres darstellt. Die Funktion arbeitet in „Händlerzeit“ (ET-Wanduhr) und wird separat in Serverzeit umgerechnet. Durch diese Trennung der Bereiche bleibt die Funktion einfach und vorhersehbar.

   // NFP candidate time in trader (ET) zone: first Friday of month at 08:30 ET
   datetime GetNFPTimestampTrader(int year, int month)
   {
      // first Friday (5) of the month
      datetime first_friday = GetNthDayOfWeek(year, month, 5, 1);
      MqlDateTime dt;
      TimeToStruct(first_friday, dt);
      dt.hour = 8; dt.min = 30; dt.sec = 0;
      return StructToTime(dt); // this is in server timescale semantics but represents the ET timestamp
   }

1.8. TraderETtoServer – ET-Zeitstempel in Serverzeit umrechnen

Dies ist die entscheidende Konvertierungsfunktion: Sie nimmt einen Zeitstempel der ET-Lokalzeit (z. B. 2025-09-05 08:30 ET), berechnet, ob bei ET Sommerzeit war, konvertiert dies in UTC und stellt dann auf die konfigurierte Serverzeitzone um. Das Ergebnis ist der absolute Serverzeitpunkt, an dem der EA die Planung und die Taktindizierung ausrichten sollte.

   // Convert a trader ET timestamp to server absolute time using ET offset and server tz
   datetime TraderETtoServer(datetime nfp_trader_time)
   {
      int et_offset = GetETOffset(nfp_trader_time); // UTC offset for ET at that date (-4/-5)
      // nfp_trader_time is interpreted in ET (i.e., wall-clock ET). To get UTC we add -ET offset:
      // UTC = ET - ET_offset_hours
      // Server local = UTC + server_tz
      int server_tz = m_serverTimeZone;
      datetime nfp_server_time = nfp_trader_time - et_offset * 3600 + server_tz * 3600;
      return nfp_server_time;
   }

1.9. GetNextNFPServerTime und GetLastNFPServerTime – Suche vorwärts/rückwärts

Diese Helfer suchen vorwärts (nächster) oder rückwärts (jüngster) Zeitstempel des NFP-Servers und scannen bis zu 13 Monate. Sie konvertieren den ET-Kandidaten in Serverzeit und vergleichen ihn mit TimeCurrent(). Die Funktionen sind sicher für Randfälle an Jahresgrenzen, da sie Monat/Jahr korrekt inkrementieren oder dekrementieren.

   // Return the next NFP server datetime strictly greater than now (search up to 13 months)
   datetime GetNextNFPServerTime()
   {
      datetime server_time = GetServerTime();
      MqlDateTime sd; TimeToStruct(server_time, sd);
      int year = sd.year, month = sd.mon;
      for(int i=0;i<13;i++)
      {
         datetime nfp_trader = GetNFPTimestampTrader(year, month);
         datetime nfp_server = TraderETtoServer(nfp_trader);
         if(nfp_server > server_time) return(nfp_server);
         month++; if(month>12){ month=1; year++; }
      }
      return(0);
   }

   // Return most recent NFP server datetime <= now (search backwards)
   datetime GetLastNFPServerTime()
   {
      datetime server_time = GetServerTime();
      MqlDateTime sd; TimeToStruct(server_time, sd);
      int year = sd.year, month = sd.mon;
      for(int i=0;i<13;i++)
      {
         datetime nfp_trader = GetNFPTimestampTrader(year, month);
         datetime nfp_server = TraderETtoServer(nfp_trader);
         if(nfp_server <= server_time) return(nfp_server);
         month--; if(month<1){ month=12; year--; }
      }
      return(0);
   }

1.10. IsNFPEventActive – schnelle Überprüfung des Aktivitätsfensters

Ein einfaches boolesches Hilfsmittel prüft, ob der letzte NFP innerhalb eines konfigurierbaren Zeitfensters (± standardmäßig eine Stunde) liegt. Es ist nützlich, wenn Sie möchten, dass sich der Code während des NFP-Fensters anders verhält (z. B. den Handel unterbrechen oder Ereignisse protokollieren).

   // Detect if an NFP event is currently within ±window_seconds of server time (default ±3600s)
   bool IsNFPEventActive(int window_seconds = 3600)
   {
      datetime server_time = GetServerTime();
      datetime last_nfp = GetLastNFPServerTime();
      if(last_nfp == 0) return(false);
      if(MathAbs((long)(server_time - last_nfp)) <= window_seconds) return(true);
      return(false);
   }

1.11. FirstM5OpenAfterNFP – Zuordnung der Ereigniszeit zu M5 open

Die Handelslogik reagiert auf den Abschluss der ersten M5-Kerze nach dem NFP. Dieses Dienstprogramm gibt die M5-Eröffnungszeit zurück, die das Ereignis enthält (der Abschluss ist der erste Abschluss nach dem Zeitstempel). Wir verwenden eine einfache Floor-Arithmetik (Zeitstempel / 300 * 300), damit ist sie schnell und eindeutig.

   // Utility: compute the M5 open time for the bar that contains the NFP or the first bar after it.
   // Returns the M5 open time for the bar whose close is the first > nfp_server_time.
   datetime FirstM5OpenAfterNFP(datetime nfp_server_time)
   {
      if(nfp_server_time <= 0) return(0);
      // PeriodSeconds for M5 = 300
      int period = PERIOD_M5;
      int psec = 60 * 5;
      // floor open time:
      long open_floor = (long)(nfp_server_time / psec) * psec;
      // if the event falls exactly at an open boundary, then the bar with open=open_floor is the one that includes event
      // its close = open_floor + psec -> that is the first close after event
      return (datetime)open_floor;
   }

1.12. GetM5BarIndexByOpenTime – konvertiert die Öffnungszeit in den Balkenindex

Schließlich wird mit dieser Funktion iBarShift umgewandelt, um den Bar-Index der M5-Serie (0-basiert vom aktuellen Bar) für eine bestimmte M5-Eröffnungszeit zurückzugeben. Er gibt -1 zurück, wenn keine exakte Übereinstimmung gefunden wird. Diese Funktion ist praktisch, wenn der EA CopyRates aufrufen oder historische Balken indexieren muss.

   // Convenience: compute the M5 bar index (iBarShift) for an M5 open time (exact match)
   int GetM5BarIndexByOpenTime(string symbol, datetime m5_open_time)
   {
      if(m5_open_time <= 0) return(-1);
      // prefer exact match - returns index where rates[].time == m5_open_time
      int idx = iBarShift(symbol, PERIOD_M5, m5_open_time, true);
      return idx; // -1 if not found
   }
};

Schritt 2:  Post-NFP Fibonacci Trader EA

2.1. Datei-Header, Einbinden und Eingaben 

Dieser Top-Block deklariert die Metadaten des EA und importiert den Trading Helper und den NFPTimeManager-Header. Alle Eingabezeilen sind die Regler, die Sie dem Händler zur Verfügung stellen: Farben, Benennung, ob der EA Aufträge automatisch platziert, die Losgröße, Puffer für SL/TP, Strenge und wie lange der Fib leben soll. Halten Sie diese Werte beim Testen (Demo) einfach und stimmen Sie sie später pro Symbol ab.

//+------------------------------------------------------------------+
//|             Post-NFP Fibonacci Trader EA                        |
//|  Post-NFP fib + pending orders with robust SL/TP buffer & object |
//|  lifecycle: destroy object when any order fills OR after expiry  |
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property version   "1.0"
#property strict

#include <Trade\Trade.mqh>
#include <NFPTimeManager.mqh>

// --- visual / fib inputs
input color  InpFiboColor = clrDodgerBlue;
input string InpPrefix = "FIBO_NFP_";
input bool   InvertFiboOrientation = true;
input bool   InpRayRight = true;

// Trading inputs
input double InpLots = 0.01;
input int    InpSlippage = 10;
input ulong  InpMagic = 123456;
input int    InpOrderExpirationMinutes = 0;
input bool   InpAutoPlaceOrders = true;
input bool   InpRequireStrictSLAt100 = false; // strict mode
input double InpSLBufferPoints = 20.0; // SL moved away from 100% by this many points
input double InpTPBufferPoints = 20.0; // TP moved away from 0% by this many points
input int    InpFibExpiryHours = 3;    // how many hours before fib auto-deletes

2.2. Globaler Zustand – wo die Laufzeitdaten gespeichert werden

Diese globalen Variablen enthalten den Live-Status, den der EA zwischen den Aufrufen benötigt: die Fib-Anker, ob der ursprüngliche Balken aufwärts war, verfolgte Pending Tickets, den aktuellen Fib-Objektnamen und seine Erstellungszeit (für den Verfall), die festen Fib-Levels und die M5-Buchführung. Sie werden vom Entwickler deklariert und zur Laufzeit aktualisiert.

// --- internal
CTrade trade;
CNFPTimeManager nfpManager;

double g_price0 = 0.0;
double g_price100 = 0.0;
bool   g_fibBullish = false;

ulong g_pendingTickets[];                  // tracked pending tickets
string g_currentFibName = "";              // current fib object name
datetime g_fibCreateTime = 0;              // server time when fib created

static const double S_levels[] = {38.2, 50.0, 61.8};

datetime lastM5Open = 0;
datetime lastProcessedNFP = 0;

2.3. OnInit() – Ersteinrichtung und Verknüpfung mit dem NFP-Header

Wenn der EA angehängt wird, erkennt OnInit die Zeitzone des Servers anhand des Headers (damit die ET-Server-Umrechnungen korrekt sind), konfiguriert die CTrade-Standardwerte (magische Zahl und Slippage), löscht alle vorherigen Ticket-Array-Speicher und zeichnet die aktuelle M5-Eröffnungszeit auf. Dies bereitet den EA darauf vor, den nächsten M5-Schlusskurs nach dem NFP zu erkennen.

int OnInit()
{
   // initialize NFP manager tz
   int detected = nfpManager.DetectServerTimeZone();
   PrintFormat("EA: Detected server timezone UTC%+d", detected);

   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpSlippage);

   ArrayFree(g_pendingTickets);

   // initialize last M5 open time
   MqlRates r[];
   if(CopyRates(_Symbol, PERIOD_M5, 0, 1, r) == 1) lastM5Open = r[0].time;
   else lastM5Open = 0;

   Print("EA: Initialized v1.50 — monitoring NFP, will create fib on first M5 close after NFP.");

   return(INIT_SUCCEEDED);
}

2.4. OnDeinit() – Herunterfahren und Aufräumen

OnDeinit stellt sicher, dass der EA beim Entfernen keine verwaisten schwebenden Aufträge oder Chart-Objekte hinterlässt. Er storniert alle verfolgten ausstehenden Aufträge, löscht den aktuellen fib (falls vorhanden) und gibt das Ticket-Array frei. Dies ist ein wichtiger Schritt für die sichere Durchführung von Tests und die Verwendung unter Spannung.

void OnDeinit(const int reason)
{
   // cleanup: cancel tracked pending orders and delete fib
   CancelExistingPendingOrders();
   DeleteCurrentFibIfExists();
   ArrayFree(g_pendingTickets);
}

2.5. OnTradeTransaction() – Füllungen erkennen und sofort reagieren

Dieser Callback hört auf die Ereignisse des Handelssystems. Wenn ein Handelsgeschäft hinzugefügt wird (ein Fill), prüft der EA, ob der gefüllte Auftrag mit einem seiner verfolgten Pending Tickets übereinstimmt. Wenn ja, löscht es den fib und storniert die verbleibenden ausstehenden Aufträge (und leert seine Ticketliste). Außerdem werden Tickets aus der Verfolgung entfernt, wenn sie extern gelöscht werden. Dadurch wird sichergestellt, dass der visuelle Fib und die verfolgten schwebenden Aufträge mit den tatsächlichen Geschäften synchronisiert bleiben.

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
   // We care about deals added (fills) and order deletions possibly made externally
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
   {
      ulong orderTicket = trans.order;
      if(orderTicket == 0) return;

      for(int i = ArraySize(g_pendingTickets)-1; i >= 0; i--)
      {
         if(g_pendingTickets[i] == orderTicket)
         {
            PrintFormat("EA: Detected fill for tracked order %I64u -> destroying fib and cancelling remaining pending orders.", orderTicket);
            ArrayRemove(g_pendingTickets, i);
            CancelExistingPendingOrders();
            DeleteCurrentFibIfExists();
            ArrayFree(g_pendingTickets);
            return;
         }
      }
   }
   else if(trans.type == TRADE_TRANSACTION_ORDER_DELETE)
   {
      ulong orderTicket = trans.order;
      if(orderTicket == 0) return;
      for(int i = ArraySize(g_pendingTickets)-1; i>=0; i--)
      {
         if(g_pendingTickets[i] == orderTicket)
         {
            ArrayRemove(g_pendingTickets, i);
            PrintFormat("EA: Tracked order %I64u was deleted externally — removed from internal list.", orderTicket);
            break;
         }
      }
   }
}

2.6. OnTick() – Herzschlag-Logik: Verfallsprüfung und M5-Close-Trigger

OnTick wird bei jedem Markttick ausgeführt. Es werden zwei Hauptprüfungen durchgeführt: (A) wenn der aktuelle Fib existiert und seine Lebensdauer (z.B. 3 Stunden) abgelaufen ist – lösche ihn und storniere die schwebenden Aufträge; (B) erkenne, wenn ein neuer M5-Balken eröffnet wird (der vorherige M5-Balken wurde geschlossen). Bei diesem M5-Schließungsereignis fragt es den Header nach der letzten NFP-Serverzeit, prüft, ob der geschlossene M5 der erste M5 nach dem NFP ist, und ruft, falls ja, die Fib + Orderplatzierungsroutinen auf. Diese Konstruktion stellt sicher, dass der Fib nach der NFP-Auslösung an einer fertigen M5-Kerze verankert wird.

void OnTick()
{
   // 1) Check for fib expiry by time
   if(StringLen(g_currentFibName) > 0 && g_fibCreateTime > 0)
   {
      datetime now = TimeCurrent();
      if(now >= (datetime)(g_fibCreateTime + InpFibExpiryHours * 3600))
      {
         PrintFormat("EA: Fib '%s' expired after %d hours -> deleting object and cancelling pending orders.",
                     g_currentFibName, InpFibExpiryHours);
         CancelExistingPendingOrders();
         DeleteCurrentFibIfExists();
      }
   }

   // 2) Detect M5 new bar open (previous M5 bar closed)
   MqlRates m5rt[];
   if(CopyRates(_Symbol, PERIOD_M5, 0, 1, m5rt) != 1) return;
   datetime currentM5Open = m5rt[0].time;
   if(currentM5Open == lastM5Open) return;

   // previous M5 open is the bar that just closed
   datetime prevOpen = lastM5Open;
   lastM5Open = currentM5Open;

   // find last NFP server time (the release to react to)
   datetime lastNFP = nfpManager.GetLastNFPServerTime();
   if(lastNFP == 0) return;

   // if already processed this NFP skip
   if(lastProcessedNFP == lastNFP) return;

   // compute bar open floor (M5) that contains the event
   int psec = 60*5;
   long floorOpen = (long)(lastNFP / psec) * psec;
   datetime firstM5OpenAfterNFP = (datetime)floorOpen;

   // we want prevOpen == firstM5OpenAfterNFP (i.e. the bar that closed is that target)
   if(prevOpen != firstM5OpenAfterNFP) return;

   // find M5 index of that open time
   int m5Index = iBarShift(_Symbol, PERIOD_M5, prevOpen, true);
   if(m5Index < 0)
   {
      PrintFormat("EA: Could not find M5 index for open time %s", TimeToString(prevOpen, TIME_DATE|TIME_MINUTES));
      return;
   }

   // update fib using that M5 bar and create pending orders
   if(!UpdateFibFromM5Bar(m5Index)) { Print("EA: UpdateFibFromM5Bar failed."); return; }
   if(InpAutoPlaceOrders) PlaceOrRefreshPendingOrders();

   // mark processed
   lastProcessedNFP = lastNFP;
   PrintFormat("EA: Processed NFP at %s (server time). Used M5 open %s as anchor.",
               TimeToString(lastNFP, TIME_DATE|TIME_MINUTES), TimeToString(prevOpen, TIME_DATE|TIME_MINUTES));
}

2.7. UpdateFibFromM5Bar() – M5-Balken lesen, Anker berechnen, die Fib zeichnen

Diese Funktion liest den ausgewählten M5-Balken mit CopyRates, bestimmt, ob diese Kerze stieg oder fiel (Close vs. Open), berechnet die Anmker von 0% und 100% entsprechend Ihrer Zuordnung, invertiert sie optional für visuelle Präferenzen, speichert sie für den Handel, entfernt alte Präfix-Objekte und erstellt ein eindeutig benanntes OBJ_FIBO, das genau diese Kerze abdeckt. Sie erfasst die Erstellungszeit für die Ablauflogik.

bool UpdateFibFromM5Bar(int m5Index)
{
   if(m5Index < 0) return(false);

   MqlRates r[];
   int copied = CopyRates(_Symbol, PERIOD_M5, m5Index, 1, r);
   if(copied != 1)
   {
      PrintFormat("EA: CopyRates for M5 index %d failed (copied=%d).", m5Index, copied);
      return(false);
   }

   double bar_high = r[0].high;
   double bar_low  = r[0].low;
   double bar_open = r[0].open;
   double bar_close= r[0].close;
   datetime bar_time = r[0].time;

   bool bullish = (bar_close > bar_open);
   g_fibBullish = bullish;

   // user's mapping:
   double price0 = bullish ? bar_high : bar_low;   // 0% anchor
   double price100= bullish ? bar_low  : bar_high; // 100% anchor

   if(InvertFiboOrientation) { double t = price0; price0 = price100; price100 = t; }

   // store anchors for trading
   g_price0 = price0;
   g_price100 = price100;

   // delete any existing prefix objects to avoid clutter
   int tot = ObjectsTotal(0);
   for(int i = tot-1; i >= 0; i--)
   {
      string nm = ObjectName(0, i);
      if(StringFind(nm, InpPrefix) == 0) ObjectDelete(0, nm);
   }

   // create a unique name for this fib
   g_currentFibName = InpPrefix + _Symbol + "_" + IntegerToString((int)bar_time);
   bool created = ObjectCreate(0, g_currentFibName, OBJ_FIBO, 0, bar_time, price0, (datetime)(bar_time + PeriodSeconds(PERIOD_M5)), price100);
   if(!created)
   {
      PrintFormat("EA: Failed to create Fibo '%s' (err=%d)", g_currentFibName, GetLastError());
      g_currentFibName = "";
      return(false);
   }

   ObjectSetInteger(0, g_currentFibName, OBJPROP_COLOR, InpFiboColor);
   ObjectSetInteger(0, g_currentFibName, OBJPROP_RAY_RIGHT, InpRayRight ? 1 : 0);
   ObjectSetInteger(0, g_currentFibName, OBJPROP_SELECTABLE, true);
   ObjectSetInteger(0, g_currentFibName, OBJPROP_HIDDEN, false);

   string desc = bullish ? "Fibo anchored to bullish M5 bar" : "Fibo anchored to bearish M5 bar";
   if(InvertFiboOrientation) desc += " (inverted)";
   if(InpRayRight) desc += " (ray-right)";
   ObjectSetString(0, g_currentFibName, OBJPROP_TEXT, desc);

   g_fibCreateTime = TimeCurrent();

   PrintFormat("EA: Drew Fibo '%s' on M5 index %d (open=%s) high=%.5f low=%.5f",
               g_currentFibName, m5Index, TimeToString(bar_time, TIME_DATE|TIME_MINUTES), bar_high, bar_low);

   return(true);
}

2.8. DeleteCurrentFibIfExists() – einzelne Stelle zum Entfernen der Fib

Ein kleines Hilfsmittel, das das aktuelle Fib-Objekt löscht, falls es existiert, und den Status des EAs über dieses Objekt löscht. Durch die Zentralisierung des Löschvorgangs wird die Duplizierung reduziert und es werden subtile Fehler vermieden, bei denen der Name nicht gelöscht wird.

void DeleteCurrentFibIfExists()
{
   if(StringLen(g_currentFibName) == 0) return;
   if(ObjectFind(0, g_currentFibName) >= 0)
   {
      ObjectDelete(0, g_currentFibName);
      PrintFormat("EA: Deleted fib object '%s'.", g_currentFibName);
   }
   g_currentFibName = "";
   g_fibCreateTime = 0;
}

2.9. CancelExistingPendingOrders() – Track-and-Clean-Strategie

Der EA verfolgt die von ihm erstellten Tickets in g_pendingTickets. Diese Routine durchläuft diese Liste und ruft trade.OrderDelete für jedes Ticket auf. Die Verwendung von verfolgten Tickets ist sicherer als das Löschen nach Symbolen oder Kommentaren, da es nur Aufträge betrifft, die im aktuellen EA-Lauf erstellt wurden (es sei denn, Sie fügen später Persistenz hinzu).

void CancelExistingPendingOrders()
{
   if(ArraySize(g_pendingTickets) == 0) return;

   for(int i = ArraySize(g_pendingTickets)-1; i >= 0; i--)
   {
      ulong ticket = g_pendingTickets[i];
      if(ticket == 0) { ArrayRemove(g_pendingTickets, i); continue; }
      bool del = trade.OrderDelete(ticket);
      if(!del) PrintFormat("EA: Failed to delete pending ticket %I64u (err=%d)", ticket, GetLastError());
      else PrintFormat("EA: Deleted pending ticket %I64u", ticket);
      ArrayRemove(g_pendingTickets, i);
   }
}

2.10. PlaceOrRefreshPendingOrders() – Berechnung von Einträgen, SL/TP und Platzierung von Aufträgen (der Handelskern)

Dies ist das Herz des Handels. Für jede Fib-Stufe (38,2, 50, 61,8) wird es:

  • Berechnet den Einstiegspreis durch lineare Interpolation zwischen g_price0 und g_price100.
  • Berechnet die gewünschten SL und TP, die bei 100% bzw. 0% verankert sind, und verschiebt sie dann um InpSLBufferPoints / InpTPBufferPoints vom Einstieg weg, je nachdem, ob der ursprüngliche M5-Balken stieg oder fiel. (Dadurch wird sichergestellt, dass der SL unter 100 % und der TP über 0 % liegt, wenn es sich um einen Aufwärtstrend handelt – und umgekehrt, wenn es sich um eine Abwärtstrend handelt).
  • Normalisiert die Preise auf Symbolziffern.
  • Fragt Broker-Grenzwerte ab (SYMBOL_TRADE_STOPS_LEVEL, SYMBOL_POINT) und erzwingt minimale Stopp-/Hängeabstände. Falls erforderlich, wird SL/TP angepasst (oder der Pegel übersprungen, wenn der strenge Modus aktiviert ist).
  • Entscheidet pro Ebene, ob ein BUY_LIMIT (Einstieg unter dem Markt) oder ein SELL_LIMIT (Einstieg über dem Markt) angebracht ist, platziert die schwebende Order unter Verwendung von CTrade mit Ablauf/Zeit-Typ, falls konfiguriert, protokolliert Erfolg/Misserfolg und speichert das Ticket zur Bereinigung.

Dieser Ansatz minimiert die Ablehnungen von Maklern und sorgt für logisch korrekte Preisbeziehungen.

void PlaceOrRefreshPendingOrders()
{
   // clear previously tracked tickets first
   CancelExistingPendingOrders();

   if(g_price0 == 0.0 || g_price100 == 0.0)
   {
      Print("EA: anchors not set - skipping placement");
      return;
   }

   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpSlippage);

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

   long stops_level = 0;
   if(!SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL, stops_level)) stops_level = 0;
   double min_stop_distance = (double)stops_level * point;
   if(min_stop_distance < point) min_stop_distance = point;
   double min_pending_distance = min_stop_distance;

   // compute buffer values in price units (points * point)
   double sl_buffer_price = InpSLBufferPoints * point;
   double tp_buffer_price = InpTPBufferPoints * point;

   ENUM_ORDER_TYPE_TIME place_time_type = (InpOrderExpirationMinutes > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC;
   datetime place_expiration = (InpOrderExpirationMinutes > 0) ? (TimeCurrent() + InpOrderExpirationMinutes * 60) : 0;

   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   int levelsCount = ArraySize(S_levels);

   for(int i=0;i<levelsCount;i++)
   {
      double pct = S_levels[i] / 100.0;
      double entry = g_price0 + pct * (g_price100 - g_price0);
      // base desired SL and TP anchored at 100% and 0%
      double sl_desired = g_price100;
      double tp_desired = g_price0;

      // Apply buffers depending on the original fib bullish/bearish orientation
      if(g_fibBullish)
      {
         sl_desired = (g_price100 - sl_buffer_price);
         tp_desired = (g_price0 + tp_buffer_price);
      }
      else
      {
         sl_desired = (g_price100 + sl_buffer_price);
         tp_desired = (g_price0 - tp_buffer_price);
      }

      // normalize values
      entry = NormalizeDouble(entry, digits);
      sl_desired = NormalizeDouble(sl_desired, digits);
      tp_desired = NormalizeDouble(tp_desired, digits);

      // decide pending type by comparing entry price with market and min_pending_distance
      bool wantBuy = false, wantSell = false;
      if(entry <= (ask - min_pending_distance)) wantBuy = true;
      else if(entry >= (bid + min_pending_distance)) wantSell = true;
      else
      {
         PrintFormat("EA: Skipping level %.2f: entry(%.5f) too close to market (Ask=%.5f Bid=%.5f) min_pending_dist=%.5f",
                     S_levels[i], entry, ask, bid, min_pending_distance);
         continue;
      }

      ENUM_ORDER_TYPE otype = (wantBuy ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_SELL_LIMIT);
      bool adjusted = false;

      // Validate/adjust stops unless strict mode requested
      if(otype == ORDER_TYPE_BUY_LIMIT)
      {
         // For BUY_LIMIT we need sl < entry < tp
         if(!(sl_desired < entry - min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because SL would be adjusted (desired SL %.5f not < entry-%.5f).",
                           S_levels[i], sl_desired, (entry - min_stop_distance));
               continue;
            }
            double old = sl_desired;
            sl_desired = NormalizeDouble(entry - min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted SL BUY level %.2f old=%.5f -> new=%.5f", S_levels[i], old, sl_desired);
         }
         if(!(tp_desired > entry + min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because TP would be adjusted (desired TP %.5f not > entry+%.5f).",
                           S_levels[i], tp_desired, (entry + min_stop_distance));
               continue;
            }
            double old = tp_desired;
            tp_desired = NormalizeDouble(entry + min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted TP BUY level %.2f old=%.5f -> new=%.5f", S_levels[i], old, tp_desired);
         }
      }
      else // SELL_LIMIT
      {
         // For SELL_LIMIT we need tp < entry < sl
         if(!(sl_desired > entry + min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because SL would be adjusted (desired SL %.5f not > entry+%.5f).",
                           S_levels[i], sl_desired, (entry + min_stop_distance));
               continue;
            }
            double old = sl_desired;
            sl_desired = NormalizeDouble(entry + min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted SL SELL level %.2f old=%.5f -> new=%.5f", S_levels[i], old, sl_desired);
         }
         if(!(tp_desired < entry - min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because TP would be adjusted (desired TP %.5f not < entry-%.5f).",
                           S_levels[i], tp_desired, (entry - min_stop_distance));
               continue;
            }
            double old = tp_desired;
            tp_desired = NormalizeDouble(entry - min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted TP SELL level %.2f old=%.5f -> new=%.5f", S_levels[i], old, tp_desired);
         }
      }

      // final relation sanity
      bool valid = true;
      if(otype == ORDER_TYPE_BUY_LIMIT) valid = (sl_desired < entry && tp_desired > entry);
      else valid = (sl_desired > entry && tp_desired < entry);
      if(!valid)
      {
         PrintFormat("EA: Skipping level %.2f due to invalid final SL/TP relation entry=%.5f SL=%.5f TP=%.5f",
                     S_levels[i], entry, sl_desired, tp_desired);
         continue;
      }

      // build order comment
      string comment = InpPrefix + DoubleToString(S_levels[i], 2);

      // Place pending with proper signature (includes time type and expiration)
      bool placed = false;
      if(otype == ORDER_TYPE_BUY_LIMIT)
         placed = trade.BuyLimit(InpLots, entry, _Symbol, sl_desired, tp_desired, place_time_type, place_expiration, comment);
      else
         placed = trade.SellLimit(InpLots, entry, _Symbol, sl_desired, tp_desired, place_time_type, place_expiration, comment);

      if(!placed)
      {
         int err = GetLastError();
         PrintFormat("EA: Failed to place %s at %.5f (lvl %.2f) err=%d ret=%d",
                     (otype==ORDER_TYPE_BUY_LIMIT ? "BUY_LIMIT":"SELL_LIMIT"),
                     entry, S_levels[i], err, (int)trade.ResultRetcode());
      }
      else
      {
         ulong ticket = trade.ResultOrder();
         PrintFormat("EA: Placed %s ticket=%I64u lvl=%.2f entry=%.5f SL=%.5f TP=%.5f adjusted=%s",
                     (otype==ORDER_TYPE_BUY_LIMIT ? "BUY_LIMIT":"SELL_LIMIT"),
                     ticket, S_levels[i], entry, sl_desired, tp_desired, (adjusted ? "yes":"no"));

         // track ticket for cleanup and fill detection
         ArrayResize(g_pendingTickets, ArraySize(g_pendingTickets) + 1);
         g_pendingTickets[ArraySize(g_pendingTickets)-1] = ticket;
      }
   } // levels loop
}

2.11. Wie der EA den NFPTimeManager-Header verwendet – einfache Aufrufpunkte

Der Header übernimmt die schwere Datums-/Zeitarbeit: Der EA ruft DetectServerTimeZone() in OnInit() auf, um die Konvertierung einzurichten, und GetLastNFPServerTime() bei jedem M5-Schluss, um die Serverzeit des letzten NFP zu ermitteln. Anhand dieses Zeitstempels berechnet der EA die erste M5-Eröffnung nach dem NFP und reagiert, wenn diese M5 schließt. Dadurch bleibt die Kernlogik des EA sauber und zeitzonensicher.

// example header calls in OnInit/OnTick
int detected = nfpManager.DetectServerTimeZone();
datetime lastNFP = nfpManager.GetLastNFPServerTime();


Ergebnisse

Mit der Klasse NFPTimeManager identifiziert der EA zuverlässig die Veröffentlichung der Nonfarm Payrolls (NFP) auch auf historischen Daten, sodass wir die Strategie nach erfolgreicher Kompilierung im Strategy Tester ausführen und validieren können. Nachfolgend finden Sie animierte Bilder, die den Testprozess im Strategy Tester veranschaulichen.

metatester64_nDEhinFAuB

Abbildung 6. 7. Juni 2024, NFP-Ankündigung

In Abbildung 6 erkennt der EA während der NFP-Ankündigung am 7. Juni 2024 erfolgreich den Ereignistag und gleicht die NFP-Veröffentlichungszeit korrekt mit der lokalen Serverzeit ab, wie der Strategy Tester bestätigt. Das Fibonacci-Retracement-Tool wurde wie vorgesehen gezeichnet und ausgeführt. Die ausstehenden Orders wurden jedoch nicht ausgelöst, da der Rücksetzer zu flach war und die Einstiegslevels nicht erreicht wurden.

Ein weiteres Problem war die fehlerhafte Platzierung von Stop-Loss- und Take-Profit-Werten bei den schwebenden Aufträgen, die nicht vollständig mit unserer beabsichtigten Spezifikation übereinstimmten. Dies kann entweder im Code oder durch Anpassung der Eingabeparameter korrigiert werden.

Trotz dieser Einschränkungen ist das wichtigste Ergebnis, dass der EA in der Lage war, das Ereignis zu erkennen, die Zeitzonen genau abzubilden und die Fibonacci-Platzierung zu automatisieren. Diese Errungenschaften bilden eine solide Grundlage, die in künftigen Iterationen noch weiter verfeinert werden kann. Unten sehen Sie ein weiteres Bild für die Veranstaltung am 1. November 2024.

 metatester64_lfPG2lDG5m

Abbildung 7. 1. November 2024, NFP-Ankündigung



Schlussfolgerung

Das Fibonacci-Retracement-Tool kann algorithmisch angewandt werden, um Non-Farm-Payroll-Ereignisse (NFP) mit einem strukturierten, regelbasierten Ansatz zu handeln. Anstatt dem ersten Anstieg hinterherzulaufen, steuert diese Methode den Markt einige Minuten nach der Veröffentlichung – während der natürlichen Korrekturphase. Konzeptionell können zwischen 30 % und 60 % des ursprünglichen fünfminütigen Kursanstiegs als Gewinn mitgenommen werden, selbst wenn ein Händler die erste Bewegung verpasst hat. Ermöglicht wird dies durch die Verwendung von Fibonacci-Ratios, die das gesamte Ausmaß des Ausreißers zu 100 % abbilden und präzise Retracement-Levels für die Handelsplatzierung liefern.

Unter diesen zeigt das 38,2%-Fibonacci-Niveau oft die höchste Wahrscheinlichkeit, einen Gewinn zu erzielen, der mit der 38,2%-Größe des anfänglichen Ausreißers vergleichbar ist. Es ist jedoch zu beachten, dass sich der Markt nicht immer an dieses Verhalten hält und kein Setup garantiert ist. Unsere Tests zeigten vielversprechende Ergebnisse in mehreren Szenarien, obwohl der Ansatz als strukturierte Wahrscheinlichkeit und nicht als Gewissheit betrachtet werden sollte.

In dieser Studie haben wir keine umfassenden Leistungsanalysen des EA durchgeführt, sondern uns darauf konzentriert, das Konzept durch praktische Simulationen im Strategy Tester zu demonstrieren. Der Prototyp kann noch verfeinert und optimiert werden – sei es durch ein verbessertes Ordermanagement, statistische Validierung oder die Anpassung an andere Währungspaare und andere Handelskontexte als Nachrichten.

Ebenso wertvoll waren die Lektionen, die wir bei der Entwicklung der Strategie gelernt haben: bei Null anfangen, Modularisierung anwenden, um den Code sauber und wiederverwendbar zu halten, und vorausschauend an fortgeschrittene Erweiterungen denken. Dies ist eine Grundlage, auf der man aufbauen kann, und mit weiterer Entwicklung kann es sich zu einem leistungsfähigeren und flexibleren Handelsinstrument entwickeln.

Wir freuen uns über Ihre Meinung und Ihr Feedback in den unten stehenden Kommentaren. Außerdem finden Sie in den beigefügten Quelldokumenten und in unserer Tabelle mit den wichtigsten Erkenntnissen praktische Hinweise.


Wichtige Lektionen

Wichtige Lektion Beschreibung:
Logik modularisieren Aufteilung der Funktionalität in kleine, konzentrierte Einheiten (Klassen oder Funktionen). Beispiel: Ein spezieller NFPTimeManager für die Zeit- und Sommerzeitlogik hält den EA-Kern lesbar und wiederverwendbar.
Explizite Handhabung von Zeitzonen und Sommerzeit Konvertierung von Ereigniszeiten (ET) in Serverzeit nach deterministischen Regeln. Fehler in diesem Bereich führen dazu, dass die Anker bei historischen Tests und Live-Durchläufen falsch ausgerichtet werden – daher sollten Sie diese Logik zentralisieren und testen.
Verankerung an geschlossenen Balken Die Berechnungen basieren immer auf abgeschlossenen Kerzen (z. B. die erste M5, die nach dem NFP geschlossen wurde). Dies vermeidet Instabilitäten bei der Neuberechnung innerhalb des Balkens und macht die Ergebnisse im Strategy Tester reproduzierbar.
Broker-Limits respektieren Abfrage von SYMBOL_TRADE_STOPS_LEVEL, SYMBOL_POINT und SYMBOL_DIGITS zur Laufzeit und Validierung von SL/TP/hängenden Abständen, um abgelehnte Aufträge zu vermeiden.
Preise normalisieren und korrekte Genauigkeit verwenden Normalisieren der Eröffnung, SL und TP immer auf die Dezimalen des Symbols. Kleine Rundungsfehler führen leicht zur Ablehnung durch den Makler.
Sicheres Rückfall-Verhalten entwickeln Strenge und adaptive Modi: Überspringe entweder die Ebenen, die eine Änderung der Stops erfordern (streng), oder passe die Stops automatisch an, um die Regeln des Brokers zu erfüllen (adaptiv). Protokolliere beide Entscheidungen.
Robuste APIs für die Auftragserteilung verwenden Verwendung von CTrade-Hilfsmethoden mit den korrekten Signaturen (einschließlich Ablauf/Zeittyp), um spätere Änderungen zu vermeiden und die Komplexität der Fehlerbehandlung zu verringern.
Verfolgen und verwalten der eigenen Tickets Geführt werden interne Liste der erstellten Tickets, damit der EA nur die von ihm erstellten Aufträge zuverlässig stornieren oder überwachen kann (vermeiden Sie das Löschen von Aufträgen anderer Nutzer).
Objektlebenszyklus und Sauberkeit der Nutzeroberfläche Den Chart-Objekten werden kurze, eindeutige Namen (z. B. fib_SYMBOL_YYYMMDDhhmm) gegeben, ausführliche Informationen werden in OBJPROP_TEXT gespeichert und die Objekte werden gelöscht, wenn Aufträge erfüllt werden oder auslaufen.
OnTradeTransaction für Ausführungen Erkennen von Ausführungen und externe Löschungen mit OnTradeTransaction-Ereignissen, um sofort zu reagieren (Bereinigung, Protokollierung) und Verzögerungen durch Polling zu vermeiden.
Ausführliche Protokollierung zur Nachvollziehbarkeit Protokollierung von jeder Schlüsselaktion (Erstellen, Anpassen, Überspringen, Platzieren, Scheitern, Löschen). Eindeutige Protokolle sind für die Fehlersuche, Backtests und für aufsichtsrechtliche/auditorische Zwecke unerlässlich.
Tests zuerst in Strategie Tester & Demokonten Validieren Sie die Logik anhand historischer NFP-Daten im Strategy Tester (deterministisch) und führen Sie sie dann auf einem Demokonto aus. Verwenden Sie deterministische Verankerungen (geschlossene Balken) für reproduzierbare Tests.



Anlagen

Dateiname Version Beschreibung
.mqh
1.0 DST-fähige Zeitdienstleistungsklasse, die alle Zeitstempelberechnungen und Umrechnungen zwischen Eastern Time (ET), UTC und der Broker-/Serverzeit zentralisiert. Zu den wichtigsten Funktionen gehören die Erkennung der Zeitzone des Servers, die Berechnung der NFP-Lokalzeit (erster Freitag um 08:30 Uhr ET), die Umrechnung des ET-Servers, das Abrufen des jüngsten/nächsten NFP-Server-Zeitstempels sowie Hilfsfunktionen zur Zuordnung von NFP-Momenten zu M5-Eröffnungszeiten und Bar-Indizes. Entwickelt für Wiederverwendung und Unit-Tests, damit der EA zeitzonenunabhängig bleibt.
Fibonacci Trader EA.mq5
1.0 Vollständiger Expert Advisor, der den NFPTimeManager verwendet, um das Schließen der ersten M5-Kerzen nach einer NFP-Veröffentlichung zu erkennen, ein an dieser Kerze verankertes Fibonacci-Retracement zu zeichnen und schwebende Aufträge auf den Retracement-Niveaus 38,2 %, 50 % und 61,8 % zu platzieren. Zu den Funktionen gehören SL/TP-Puffer, strikte bzw. adaptive Stop-Behandlung, Broker-Limit-Validierung (SYMBOL_TRADE_STOPS_LEVEL, SYMBOL_POINT, Ziffern), Ticket-Verfolgung, automatische Bereinigung (bei Befüllung oder nach konfigurierbarem Ablauf) und ausführliche Protokollierung zur Nachvollziehbarkeit. Konfigurierbar über Eingaben; getestet im Strategy Tester und in der Demo-Umgebung.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19496

Einführung in MQL5 (Teil 21): Automatisiertes Erkennen von harmonischen Mustern Einführung in MQL5 (Teil 21): Automatisiertes Erkennen von harmonischen Mustern
Lernen Sie, wie Sie das harmonische Muster von Gartley im MetaTrader 5 mit MQL5 erkennen und anzeigen können. In diesem Artikel wird jeder Schritt des Prozesses erläutert, von der Identifizierung der Umkehrpunkte über die Anwendung der Fibonacci-Ratios bis hin zur Darstellung des gesamten Musters auf dem Chart zur eindeutigen visuellen Bestätigung.
Automatisieren von Handelsstrategien in MQL5 (Teil 32): Erstellung eines Price Action 5 Drives des harmonischen Mustersystems Automatisieren von Handelsstrategien in MQL5 (Teil 32): Erstellung eines Price Action 5 Drives des harmonischen Mustersystems
In diesem Artikel entwickeln wir ein 5-Drives-Mustersystem in MQL5, das steigende und fallende harmonische 5-Drives-Muster unter Verwendung von Umkehrpunkten und Fibonacci-Verhältnissen identifiziert und Handelsgeschäfte mit anpassbaren Einstiegs-, Stop-Loss- und Take-Profit-Levels basierend auf vom Nutzer ausgewählten Optionen ausführt. Wir verbessern den Einblick des Händlers mit visuellem Feedback durch Chart-Objekte wie Dreiecke, Trendlinien und Beschriftungen, um die A-B-C-D-E-F-Musterstruktur klar darzustellen.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Den Marktstimmungsindikator automatisieren Den Marktstimmungsindikator automatisieren
In diesem Artikel entwickeln wir einen nutzerdefinierten Indikator für die Marktstimmung, um die Bedingungen in aufwärts, abwärts, mehr und weniger Risiko oder neutral zu klassifizieren. Der Expert Advisor liefert Echtzeit-Einblicke in die vorherrschende Stimmung und vereinfacht den Analyseprozess für aktuelle Markttrends oder -richtungen.