English Русский 日本語
preview
Vom Neuling zum Experten: Zeitlich gefilterter Handel

Vom Neuling zum Experten: Zeitlich gefilterter Handel

MetaTrader 5Handelssysteme |
139 0
Clemence Benjamin
Clemence Benjamin

Inhalt



Einführung

Indem wir die Möglichkeiten von MQL5 nutzen, können wir die Zeitpunkte, zu denen wir handeln wollen, genau festlegen und entsprechende Warnungen erhalten. In der vorangegangenen Diskussion haben wir die Grundlage für die Visualisierung von Börsensitzungen geschaffen, indem wir die Struktur jeder Börsensitzung aufgedeckt haben – ihren Körper, den oberen und den unteren Docht. Dieses Projekt ermöglichte es uns, die breitere Marktstruktur in Sitzungen mit höherem Zeitrahmen zu visualisieren und mit Daten mit niedrigerem Zeitrahmen zu synchronisieren, um die vergrößerten internen Preisbewegungen und die sich darin bildenden Muster aufzuzeigen.

Diese Konzepte sind zwar nicht erschöpfend, aber sie dienen als wertvolles Sprungbrett für andere ehrgeizige Forscher, die fortschrittlichere Marktanalysetools visualisieren und entwickeln wollen. Der Preis ist von Natur aus ein Zeitreihenphänomen – er entwickelt sich kontinuierlich, Tick für Tick. Unsere Expert Advisors und Indikatoren sind so konzipiert, dass sie diese Schwankungen unablässig überwachen und auf jedes Muster oder jede Bedingung reagieren, die sie erkennen. Ohne die Einführung von zeitbasierten Filtern werden sie jedoch wahllos Aktionen ausführen, sobald die technischen Bedingungen erfüllt sind – selbst in Zeiten geringer Liquidität, erratischer Spreads oder geringer Volumendrift.

Dies ist der Zeitpunkt, an dem der Zeitbezug entscheidend wird. Indem wir unsere Algorithmen in genau definierten Handelsfenstern oder -sitzungen verankern, leiten wir sie dazu an, nur dann zu handeln, wenn der Markt am aktivsten, strukturiertesten und sinnvollsten ist. Die Zeitfilterung dient daher als disziplinarischer Rahmen, der sicherstellt, dass die Ausführung erfolgt, wenn das Marktverhalten nicht nur mit der Logik der Strategie, sondern auch mit dem Rhythmus der Zeit selbst übereinstimmt.

Bekannte Kalenderereignisse können als wichtige Bezugspunkte für den zeitlich gefilterten Handel dienen. Wirtschaftliche Ereignisse wie Non-Farm Payrolls, CPI, FOMC-Entscheidungen oder BIP-Veröffentlichungen entwickeln sich im Laufe der Zeit und können dazu verwendet werden, genaue Handelsfenster zu definieren – Zeiträume, in denen unsere Tools entweder erlaubt oder eingeschränkt sind, Aufträge zu erteilen und Signale zu erzeugen.

So wie die Sitzungsmarker den Marktrhythmus bestimmen, definieren die Wirtschaftskalendermarker ereignisgesteuerte Volatilitätszonen. So könnte ein Algorithmus beispielsweise den Handel 30 Minuten vor einem wichtigen Ereignis automatisch aussetzen und erst wieder aufnehmen, wenn sich die Volatilität stabilisiert hat. MQL5 bietet über die Funktionen MqlalendarValue() und CalendarEventById() direkten Zugriff auf solche Daten, sodass EAs anstehende Veröffentlichungen erkennen und ihre Operationen entsprechend ausrichten können.

Neben der ereignisgesteuerten Zeitplanung ist die Uhrzeit selbst ein weiterer wichtiger Kontrollfaktor. Ein Händler oder Forscher könnte feste Zeitfenster wie 10:00-12:00 Uhr für die Signalgenerierung oder -ausführung festlegen. Dies reduziert nicht nur das Rauschen, sondern hilft auch, statistisch günstige Perioden innerhalb eines Handelstages zu isolieren.

TFT

Diese Mechanismen – kalenderbasierte Ereignisfilter und stundenbasierte Zeitfenster – verleihen unseren Systemen ein zweifaches Zeitbewusstsein: eines, das sich aus dem planmäßigen wirtschaftlichen Verhalten ableitet, und ein anderes aus dem strukturellen Rhythmus innerhalb eines Tages. Die Integration von beidem ermöglicht es unseren Expert Advisors, nicht nur auf die Preisbedingungen zu reagieren, sondern auch auf den zeitlichen Kontext, der beim automatisierten Handel oft den Ausschlag zwischen Präzision und Zufälligkeit gibt.

Der heutige Schwerpunkt liegt auf dem zeitlich gefilterten Handel. Wir werden untersuchen, wie wir die in unseren früheren Projekten festgelegten Zeitreferenzpunkte nutzen können, um die Handelsfrequenz zu filtern und die Signalausgabe zu steuern. Mithilfe von Algorithmen können wir bestimmte Zeitfenster festlegen, in denen Handelsaktivitäten erlaubt sind, was uns eine größere Kontrolle, Präzision und Anpassungsfähigkeit bei automatisierten Handelsstrategien ermöglicht. Es ermutigt uns, über bloße bedingungsabhängige Auslöser hinauszugehen und ein zeitliches Bewusstsein zu entwickeln, das es unseren Handelssystemen ermöglicht, sowohl das Verhalten des Marktes als auch den Zeitpunkt seines Verhaltens zu berücksichtigen.

Durch den Einsatz des MQL5-Rahmens können wir Systeme schaffen, die:

  • Nur während ausgewählter Sitzungen (z. B. in London oder New York) handeln.
  • Den Handel vor wichtigen Nachrichten oder zu Zeiten mit geringer Liquidität aussetzen.
  • Warnungen, Signale oder Einträge nur dann erzeugen, wenn die Uhrzeit und die Bedingungen übereinstimmen.

Das Konzept des Zeitfilters

Der Zeitfilter stellt sicher, dass die Logik Ihrer Strategie nur während bestimmter Zeitfenster ausgeführt wird. Anstatt einen EA oder Indikator kontinuierlich laufen zu lassen, können Sie seine Ausführung in Zeitprüfungen verpacken, die bestätigen, ob die aktuelle Marktzeit in den gewünschten Handelszeitraum fällt.

Dieser Ansatz ist nützlich für:
  • Das Vermeidung von Randzeiten oder illiquiden Zeiten (z. B. nach Börsenschluss in New York).
  • Dem Erfassen von sitzungsbasierten Volatilitätsausbrüchen (z. B. Überschneidungen zwischen London und New York).
  • Der Kontrolle der Signalfrequenz, um Rauschen oder Over-Trading zu reduzieren.

Bei unserem heutigen Ansatz liegt der Schwerpunkt ganz auf der Umsetzung. Zu diesem Zeitpunkt werden wir keine weiteren Upgrades für den Market Periods Synchronizer einführen. Stattdessen stützen wir uns auf die Arbeit, die wir in unserer früheren Veröffentlichung geleistet haben und die nun als Grundlage für die vorliegende Studie dient.

Insbesondere werden wir auf die SessionVisualizer-Header-Datei verweisen – das Modul, das für die Definition und Markierung unserer Marktsitzungszeiträume verantwortlich ist. Die darin angelegten Zeitmarken werden als wichtige Bezugspunkte für dieses neue Konzept des zeitgefilterten Handels dienen. Durch die Verwendung dieser Marker können wir nun Handelsfenster definieren und kontrollieren, die vor oder nach dem Beginn oder dem Ende einer Sitzung liegen.

Im nächsten Abschnitt werden wir eine detaillierte Umsetzung dieser Idee untersuchen und zeigen, wie zeitbasierte Kontrollen direkt in unsere Handelslogik integriert werden können. Im Folgenden werden die einzelnen Schritte zur Umsetzung erläutert:

  1. Aufbau einer modularen TimeFilter-Schicht, die mit dem SessionVisualizer zusammenarbeitet, um sitzungsbasierte, uhrzeitbasierte und (optional) ereignisbasierte Zeitfenster bereitzustellen.
  2. Erstellen von einem Beispiel eines Expert Advisors, der Handelsgeschäfte nur dann ausführt, wenn die aktiven Zeitfilter den Handel zulassen.
  3. Erstellen von einem Beispiel eines Indikators, der Signale nur innerhalb der zulässigen Zeitfilterfenster aufzeichnet oder auslöst.



Umsetzung

Wir werden nun zunächst die Klasse TimeFilter implementieren, sie dann in zwei Beispielprojekte – einen Expert Advisor und einen Indikator – integrieren und schließlich unsere Testergebnisse zusammen mit den vollständigen Quelldateien präsentieren.

ZeitFilter

1. Modul-Kopfzeile und Include Guard

Wir beginnen damit, diese Datei als wiederverwendbare Bibliothek zu behandeln, nicht als wegwerfbares Codefragment. Die Verwendung von #property strict erzwingt sicherere Kompilierungsregeln, während der include guard doppelte Definitionen verhindert, wenn die Datei in mehreren Projekten verwendet wird. Wir fügen auch SessionVisualizer.mqh bei, weil unser Zeitfilter so konzipiert ist, dass er mit Ihrer bestehenden Sitzungsvisualisierungslogik zusammenarbeitet. Dies ist die Art von struktureller Disziplin, die eine Codebasis skalierbar macht.

// ============================================================================
// TimeFilters.mqh — modular helpers for time-filtered trading
// Author—Clemence Benjamin
// ============================================================================

#property strict
#ifndef __TIMEFILTERS_MQH__
#define __TIMEFILTERS_MQH__

#include "SessionVisualizer.mqh"

2. Eingänge und nutzergesteuerte Einstellungen

Als Nächstes legen wir ein kleines Bedienfeld mit Eingabeparametern frei. Anstatt das Verhalten fest zu kodieren, lassen wir den Händler (oder Tester) entscheiden, ob er nach Sitzungen, festen Stunden oder Ereignissen filtern möchte. Start-/Ende-Zeit, Auffüllungen vor/nach der Sitzung und (künftige) Nachrichtenfilter lassen sich ohne Eingriff in die Quelle ändern. Die Namen sind durchgängig mit Inp vorangestellt und jeder Input ist dokumentiert – genau so machen Sie Ihre Module in der MetaTrader 5 Registerkarte „Inputs“ lesbar und im Code selbsterklärend.

// --------------------------- Inputs / Settings ------------------------------
input bool   InpUseSessionFilter   = true;      // gate by session times
input bool   InpUseClockFilter     = true;      // gate by fixed clock window
input bool   InpUseEventFilter     = false;     // gate by economic calendar (stubbed safe)

input int    InpClockStartHour     = 10;        // e.g., 10:00
input int    InpClockEndHour       = 12;        // e.g., 12:00
input int    InpPreSessionMins     = 0;         // allow N minutes before session start
input int    InpPostSessionMins    = 0;         // allow N minutes after session end

// Event filter parameters (used when you enable InpUseEventFilter)
input int    InpCalLookAheadMin    = 60;
input int    InpCalBlockBeforeMin  = 30;
input int    InpCalBlockAfterMin   = 30;
input int    InpCalMinImportance   = 2;         // 0=low,1=medium,2=high

3. Zeitfilter-Kontext-Verschalung

Anstatt globale Variablen in Ihrem EA zu verstreuen, kapseln wir den zeitbezogenen Zustand in CTimeFilterContext. Diese Klasse kann optional einen Zeiger auf CSessionVisualizer und den aktiven gmt_offset speichern. Das bedeutet, dass jeder EA oder Indikator ein einziges leichtgewichtiges Kontextobjekt weitergeben kann, und alle Zeitentscheidungen bleiben konsistent. SetGMTOffset leitet den Offset an den Visualizer weiter, wenn er vorhanden ist – so bleiben Ihre Zeichnungen und Ihre Filter ohne doppelte Logik synchronisiert.

// --------------------------- Context wrapper --------------------------------
class CTimeFilterContext
{
public:
   CSessionVisualizer *viz;   // optional visualizer pointer
   int                 gmt_offset;

   CTimeFilterContext() : viz(NULL), gmt_offset(0) {}

   void AttachVisualizer(CSessionVisualizer &ref) { viz = &ref; }

   void SetGMTOffset(int off)
   {
      gmt_offset = off;
      if(viz != NULL)
      {
         // safest explicit dereference form
         (*viz).SetGMTOffset(off);
      }
   }
};

4. Kernuhr-Fenster-Filter

Das Zeitfenster ist unsere erste und einfachste Zeitbarriere: „Arbeite nur zu diesen Stunden“. Hier wandeln wir die Serverzeit in eine strukturierte Form um und vergleichen sie mit dem nutzerdefinierten Bereich. Zwei Verhaltensweisen sind erwähnenswert: Wenn startHour == endHour ist, interpretieren wir dies als „keine Einschränkung“, und wenn startHour > endHour ist, behandeln wir das Fenster als über Mitternacht hinausgehend (z. B. 22:00-02:00). Viele Anfänger vergessen den Umschlag; wenn wir ihn hier behandeln, kann sich der Rest Ihrer Logik getrost auf dieses Dienstprogramm verlassen.

// --------------------------- Utility: Clock Window --------------------------
bool InClockWindow(const int startHour, const int endHour)
{
   MqlDateTime t;
   TimeToStruct(TimeCurrent(), t);

   if(startHour == endHour)
      return true; // treat as "always on"

   if(startHour < endHour)
      return (t.hour >= startHour && t.hour < endHour);

   // wrap across midnight, e.g., 22..02
   return (t.hour >= startHour || t.hour < endHour);
}

5. Sitzungsbasierter Fensterfilter

Diese Funktion verbindet unsere Zeitfilter-Engine mit der Sitzungslogik. Er erwartet den CTimeFilterContext (für die Konfiguration) und einen SESSION_TYPE (Sydney, Tokio, London, New York). Abhängig von Ihrer Implementierung gibt GetTodaySessionWindow (in Ihrem SessionVisualizer) den Beginn und das Ende der Sitzung des aktuellen Tages zurück. Darüber hinaus fügen wir ein minutenweises Ausfüllen der Pre- und Post-Session hinzu, in denen Händler üblicherweise Regeln wie „beginne zu suchen 15 Minuten vor London“ oder „vermeide die letzten 30 Minuten von New York“ kodieren. Beachten Sie, wie wir offen scheitern: Wenn wir kein Sitzungsfenster bekommen können, geben wir true zurück, damit ein fehlender Visualizer oder eine fehlende Konfiguration nicht den gesamten Handel beendet.

// --------------------------- Utility: Session Window ------------------------
bool InSessionWindow(CTimeFilterContext &ctx, const SESSION_TYPE sType,
                     const int preMins = 0, const int postMins = 0)
{
   if(ctx.viz == NULL)
      return true; // no visualizer attached -> don't block

   datetime ss, se;

   if(!(*ctx.viz).GetTodaySessionWindow(sType, ss, se))
      return true; // session not available -> don't block

   // Apply pre/post-minute padding
   ss -= (preMins * 60);
   se += (postMins * 60);

   datetime now = TimeCurrent();
   return (now >= ss && now <= se);
}

6. Teil des Ereignisfilters (Erweiterbarer Haken)

Hier definieren wir EventWindowAllowed als einen sicheren Programmteil: Er gibt vorerst immer true zurück. Dies ist eine bewusste Entscheidung. Viele Leser betreiben Broker oder Tester ohne wirtschaftliche Kalenderunterstützung; eine feste Abhängigkeit würde die Portabilität beeinträchtigen. Indem wir diesen Hook mit der richtigen Signatur versehen, machen wir es trivial, später echte kalenderbasierte Logik einzubauen (Blockierung um NFP, FOMC, CPI usw.), ohne irgendeinen EA-Code zu ändern, der bereits auf IsTradingAllowed basiert.

// --------------------------- Utility: Economic Calendar ---------------------
bool EventWindowAllowed(const int /*lookAheadMin*/,
                        const int /*blockBeforeMin*/,
                        const int /*blockAfterMin*/,
                        const int /*minImportance*/)
{
   // Stubbed: always allow for safe compilation
   return true;
}

7. Zusammengesetzte Entscheidung: Ist der Handel erlaubt?

Schließlich werden alle Teile zu einer einzigen ordentlichen Funktion zusammengeführt: IsTradingAllowed. Dies ist die Methode, die Ihr EA oder Indikator bei jedem Tick oder bei jedem Signal aufrufen sollte. Er wendet die aktivierten Filter nacheinander an:

  • Wenn der Zeitfilter eingeschaltet ist, müssen wir uns innerhalb der erlaubten Stunden befinden.
  • Wenn der Sitzungsfilter aktiviert ist, müssen wir uns innerhalb mindestens eines konfigurierten (ausgefülltes) Sitzungsfensters befinden.
  • Wenn der Ereignisfilter eingeschaltet ist, müssen wir die (zukünftige) Ereignisregel übergeben.

Das Muster ok = ok && ... hält die Logik lesbar: Sobald ein Filter fehlschlägt, geben wir weiterhin false zurück, ohne spezielle Verzweigungen. Das Ergebnis ist ein sauberes Boolesches Gatter, das Ihre Rohstrategie mit einem einzigen Aufruf in eine zeitbewusste Strategie verwandelt.

// --------------------------- Composite Gate --------------------------------
bool IsTradingAllowed(CTimeFilterContext &ctx)
{
   bool ok = true;

   if(InpUseClockFilter)
      ok = ok && InClockWindow(InpClockStartHour, InpClockEndHour);

   if(InpUseSessionFilter)
   {
      bool any = false;
      any = any || InSessionWindow(ctx, SESSION_LONDON,  InpPreSessionMins, InpPostSessionMins);
      any = any || InSessionWindow(ctx, SESSION_NEWYORK, InpPreSessionMins, InpPostSessionMins);
      any = any || InSessionWindow(ctx, SESSION_TOKYO,   InpPreSessionMins, InpPostSessionMins);
      any = any || InSessionWindow(ctx, SESSION_SYDNEY,  InpPreSessionMins, InpPostSessionMins);
      ok = ok && any;
   }

   if(InpUseEventFilter)
      ok = ok && EventWindowAllowed(InpCalLookAheadMin,
                                    InpCalBlockBeforeMin,
                                    InpCalBlockAfterMin,
                                    InpCalMinImportance);

   return ok;
}

#endif // __TIMEFILTERS_MQH__

Beispiel für einen Expert Advisor mit Zeitfiltern

1. EA-Kopfzeile und übergeordnete Intention

Jeder professionelle EA sollte sich an der Spitze deutlich vorstellen. Hier erklären wir die Eigentumsverhältnisse, beschreiben den Zweck und legen die Version fest. Dies ist mehr als nur Kosmetik: Wenn Sie Monate später zurückkommen – oder wenn jemand anderes Ihren Quellcode aus der CodeBase herunterlädt – zeigen diese Felder, dass es sich nicht um ein zufälliges Experiment handelt, sondern um ein strukturiertes, dokumentiertes System, das speziell für den zeitgefilterten Handel mit sauberer CTrade-Integration entwickelt wurde.

// TimeFilteredEA.mq5 -                                             
#property copyright  "Clemence Benjamin"
#property description "Professional Time-Filtered EA with streamlined CTrade usage."
#property version     "1.0"

2. Die Strategie umfasst: Handel, Symbolinformationen, Sitzungen, Zeitfilter

Als Nächstes nehmen wir selektiv nur die Bausteine auf, von denen dieser EA wirklich abhängt. Trade.mqh liefert uns den High-Level-CTrade-Wrapper; SymbolInfo.mqh hilft uns bei maklerspezifischen Symboleigenschaften; SessionVisualizer.mqh behandelt unseren visuellen Kontext; TimeFilters.mqh liefert das zentrale IsTradingAllowed-Gatter. Wir setzen das Verhalten aus wohldefinierten Modulen zusammen und erfinden nicht alles in einer einzigen aufgeblähten Datei neu.

#include <Trade/Trade.mqh>
#include <Trade/SymbolInfo.mqh>
#include <SessionVisualizer.mqh>
#include <TimeFilters.mqh>

3. Freiliegende Eingänge: Konfigurierbar

Hier bieten wir ein sauberes Eingabefeld für Händler. Wir verbergen keine Losgröße, Stop-Loss oder Visualisierungsflags innerhalb der Logik. Stattdessen deklarieren wir sie als Eingabevariablen, damit sie im EA-Einstellungsdialog erscheinen. Beachten Sie, dass jede Eingabe mit einem klaren Kommentar versehen ist, der ihren Zweck erklärt. Wir verwenden auch eine spezielle magische Zahl und eine Abweichung, beides grundlegende Best Practices für die sichere Verwaltung von Aufträgen in Multi-EA-Umgebungen.

input int    InpGMTOffsetHours = 0;     // GMT offset for session alignment
input bool   InpDrawSessions    = true; // Enable session visualization
input int    InpLookbackDays    = 5;    // Days to draw sessions
input double InpLotSize         = 0.01; // Fixed lot size
input int    InpStopLossPips    = 50;   // SL in pips (0 = none; TP auto-set to 2x if >0)
input int    InpTakeProfitPips  = 0;    // TP in pips (0 = auto 2x SL if SL>0)
input int    InpMagicNumber     = 12345; // Magic number for trades
input int    InpDeviationPips   = 10;   // Max slippage in pips

4. Kernobjekte und Zustand

Wir deklarieren nun die vom EA verwendeten Schlüsselobjekte: CTrade für die Auftragsausführung, CSymbolInfo für Spread-/Tick-/Volumendaten, CSessionVisualizer zum Zeichnen von Sitzungsblöcken und CTimeFilterContext zur Koordinierung von zeitbezogenen Entscheidungen. Wir bereiten auch MA-Handles und gLastSignalBar vor, um sicherzustellen, dass wir keine doppelten Signale auf derselben Kerze auslösen. Dieses Muster ist ordentlich: Werkzeuge auf Umgebungsebene sind global, während Handelslogik und Filter modular bleiben.

CTrade              trade;
CSymbolInfo         gSymbolInfo;
CSessionVisualizer  gSV("TF_SESS_");
CTimeFilterContext  gCTX;

// MA handles
int       gFastMAHandle   = INVALID_HANDLE;
int       gSlowMAHandle   = INVALID_HANDLE;
datetime  gLastSignalBar  = 0;

5. Professioneller Handelsmanager: Kapselung der Ausführungslogik

Anstatt die reinen Aufrufe von trade.Buy() überall im EA abzufeuern, führen wir CTradeManager ein. Diese Klasse zentralisiert Lot-Normalisierung, Slippage, Zuweisung der magischen Zahl, SL/TP-Berechnung und Kontovalidierung. So halten Sie Ihren Strategiecode sauber – Ihre Signallogik fordert einen Kauf an, und der Manager kümmert sich um alle Details an einer Stelle, einschließlich einer zuverlässigen Protokollierung der Fehlergründe.

//+------------------------------------------------------------------+
//| Professional Trade Manager Wrapper                               |
//+------------------------------------------------------------------+
class CTradeManager
{
private:
   CTrade* m_trade;
   int     m_magic;
   int     m_deviation;
   double  m_minVolume;
   double  m_maxVolume;
   double  m_volumeStep;
   int     m_digits;
   double  m_point;

public:
   CTradeManager() : m_trade(NULL), m_magic(0), m_deviation(0) {}
   ~CTradeManager() {}

   bool Init(int magic, int deviation)
   {
      m_trade = new CTrade();
      if(m_trade == NULL) return false;
      
      m_magic = magic;
      m_deviation = deviation;
      
      m_trade.SetExpertMagicNumber(m_magic);
      m_trade.SetDeviationInPoints(m_deviation);
      m_trade.LogLevel(LOG_LEVEL_ERRORS);  // Match reference: log errors only
      
      // Cache symbol props (no filling set - default to broker/symbol)
      m_minVolume  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
      m_maxVolume  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
      m_volumeStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
      m_digits     = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
      m_point      = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
      
      Print("TradeManager initialized: Magic=", m_magic,
            " Deviation=", m_deviation, "pts (default filling)");
      return true;
   }

   void Deinit()
   {
      if(m_trade != NULL)
      {
         delete m_trade;
         m_trade = NULL;
      }
   }

6. Volumensicherheit und Kontovalidierung

Bevor ein stabiler EA einen Handel platziert, muss er zwei Fragen beantworten: Ist dieses Volumen für dieses Symbol gültig? Und ist dieses Konto in einem gesunden Zustand? IsValidVolume und NormalizeVolume passen das angeforderte Volumen an die SYMBOL_VOLUME_STEP-Beschränkungen des Maklers an. ValidateAccount bestätigt, dass der Saldo, das Kapital und die freie Marge nicht in unsinnigen Bereichen liegen. 

   bool IsValidVolume(double volume)
   {
      if(volume < m_minVolume || volume > m_maxVolume) return false;
      double normalized = NormalizeDouble(volume / m_volumeStep, 0) * m_volumeStep;
      return (MathAbs(volume - normalized) < m_point);
   }

   double NormalizeVolume(double volume)
   {
      return NormalizeDouble(
               MathMax(m_minVolume,
                       MathMin(m_maxVolume,
                               NormalizeDouble(volume / m_volumeStep, 0) * m_volumeStep)),
               2);
   }

   bool ValidateAccount()
   {
      double balance    = AccountInfoDouble(ACCOUNT_BALANCE);
      double equity     = AccountInfoDouble(ACCOUNT_EQUITY);
      double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
      
      if(balance <= 0 || equity <= 0 || freeMargin < 0)
      {
         Print("TradeManager: Invalid account state - Balance=", balance,
               " Equity=", equity, " FreeMargin=", freeMargin);
         return false;
      }
      
      Print("TradeManager: Account validated - Balance=", balance,
            " FreeMargin=", freeMargin);
      return true;
   }

7. ExecuteBuy: SL/TP-Logik, Spread-Berücksichtigung und Fehlerberichterstattung

In ExecuteBuy laufen alle Fäden zusammen. Wir validieren das Konto, normalisieren und verifizieren das Volumen, holen frische Symbolpreise ein und berechnen Stop-Loss und Take-Profit unter Berücksichtigung von Spread, Pip-Größe und Mindeststopps. Wenn nur SL angegeben wird und TP gleich Null ist, leitet der EA automatisch ein 1:2-Risiko-Rendite-TP ab – dies führt eine gute Praxis direkt in den Rahmen ein. Und wenn etwas schief geht, gibt die Funktion sowohl die ResultRetcodeDescription() als auch GetLastError() aus.

   bool ExecuteBuy(double volume, double sl = 0, double tp = 0, string comment = "")
   {
      if(!ValidateAccount()) return false;
      
      volume = NormalizeVolume(volume);
      if(!IsValidVolume(volume))
      {
         Print("TradeManager: Invalid volume ", volume);
         return false;
      }
      
      gSymbolInfo.Name(_Symbol);
      gSymbolInfo.RefreshRates();
      double ask    = gSymbolInfo.Ask();
      double bid    = gSymbolInfo.Bid();
      double spread = ask - bid;
      
      double price      = NormalizeDouble(ask, m_digits);
      double stoploss   = 0.0;
      double takeprofit = 0.0;
      
      double pipValue = (m_digits == 3 || m_digits == 5) ? m_point * 10 : m_point;

      // --- SL/TP logic (spread-aware, 1:2 RR auto if desired) ---
      if(InpStopLossPips > 0)
      {
         if(spread >= InpStopLossPips * m_point)
         {
            PrintFormat("StopLoss (%d points) <= current spread = %.0f points. Spread value will be used",
                        InpStopLossPips, spread / m_point);
            stoploss = NormalizeDouble(price - spread, m_digits);
         }
         else
         {
            stoploss = NormalizeDouble(price - InpStopLossPips * pipValue, m_digits);
         }

         if(InpTakeProfitPips == 0)
         {
            takeprofit = NormalizeDouble(price + (InpStopLossPips * 2 * pipValue), m_digits);
            Print("TradeManager: Auto-set TP for 1:2 RR: ", takeprofit);
         }
         else
         {
            if(spread >= InpTakeProfitPips * m_point)
            {
               PrintFormat("TakeProfit (%d points) < current spread = %.0f points. Spread value will be used",
                           InpTakeProfitPips, spread / m_point);
               takeprofit = NormalizeDouble(price + spread, m_digits);
            }
            else
            {
               takeprofit = NormalizeDouble(price + InpTakeProfitPips * pipValue, m_digits);
            }
         }
      }
      else if(InpTakeProfitPips > 0)
      {
         if(spread >= InpTakeProfitPips * m_point)
         {
            PrintFormat("TakeProfit (%d points) < current spread = %.0f points. Spread value will be used",
                        InpTakeProfitPips, spread / m_point);
            takeprofit = NormalizeDouble(price + spread, m_digits);
         }
         else
         {
            takeprofit = NormalizeDouble(price + InpTakeProfitPips * pipValue, m_digits);
         }
      }

      // Directional sanity checks
      if(stoploss > 0 && stoploss >= price)
      {
         Print("TradeManager: Invalid SL for BUY - resetting to 0");
         stoploss = 0;
      }
      if(takeprofit > 0 && takeprofit <= price)
      {
         Print("TradeManager: Invalid TP for BUY - resetting to 0");
         takeprofit = 0;
      }

      // Respect broker minimum stop levels
      long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
      if(stopsLevel > 0)
      {
         double minDist = stopsLevel * m_point;
         if(stoploss > 0 && (price - stoploss) < minDist)
         {
            stoploss = NormalizeDouble(price - minDist, m_digits);
            Print("TradeManager: SL adjusted to min dist: ", stoploss);
         }
         if(takeprofit > 0 && (takeprofit - price) < minDist)
         {
            takeprofit = NormalizeDouble(price + minDist, m_digits);
            Print("TradeManager: TP adjusted to min dist: ", takeprofit);
         }
      }

      Print("TradeManager: Executing BUY - Entry=", price,
            " Vol=", volume, " SL=", stoploss, " TP=", takeprofit);

      ResetLastError();
      bool result = m_trade.Buy(volume, _Symbol, price, stoploss, takeprofit, comment);
      
      if(!result)
      {
         uint   retcode = m_trade.ResultRetcode();
         string ret_desc = m_trade.ResultRetcodeDescription();
         PrintFormat("Failed %s buy %G at %G (sl=%G tp=%G) Retcode=%u (%s) MQL Error=%d",
                     _Symbol, volume, price, stoploss, takeprofit,
                     retcode, ret_desc, GetLastError());
         m_trade.PrintResult();
         Print("   ");
      }
      else
      {
         Print("TradeManager: BUY success - Deal=", m_trade.ResultDeal(),
               " Price=", m_trade.ResultPrice());
      }
      
      return result;
   }
};

8. Initialisierung: Verbindung von Zeitfiltern, Visualisierung, Handelsmanager und EMAs

OnInit ist der Moment, in dem der EA zum Leben erwacht. Wir binden den CSessionVisualizer an gCTX an, stellen den GMT-Offset ein, damit sowohl die Visualisierung als auch die Logik mit der Zeit übereinstimmen, und initialisieren den CTradeManager mit unserer magischen Zahl und Abweichung. Wir validieren auch das Konto (besonders nützlich in Strategy Tester) und erstellen MA-Handles, die unsere Signallogik steuern. Die gedruckten Meldungen sind beabsichtigt – sie teilen dem Nutzer nicht nur mit, dass der EA „bereit“ ist, sondern auch, welches Risikomodell und welche Konfiguration er verwendet.

// Global Trade Manager
CTradeManager gTradeMgr;

//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit()
{
   Print("=== PIONEER EA INITIALIZATION (v2.11) ===");
   
   // Time filter setup
   gCTX.AttachVisualizer(gSV);
   gCTX.SetGMTOffset(InpGMTOffsetHours);
   if(InpDrawSessions)
      gSV.RefreshSessions(InpLookbackDays);
   
   // Initialize Trade Manager
   if(!gTradeMgr.Init(InpMagicNumber, InpDeviationPips))
   {
      Print("FAIL: TradeManager initialization failed");
      return INIT_FAILED;
   }
   
   // Validate account early
   if(!gTradeMgr.ValidateAccount())
   {
      Print("FAIL: Account validation failed - Check deposit in tester");
      return INIT_FAILED;
   }
   
   // MA indicators
   gFastMAHandle = iMA(_Symbol, _Period, 9, 0, MODE_EMA, PRICE_CLOSE);
   gSlowMAHandle = iMA(_Symbol, _Period, 21, 0, MODE_EMA, PRICE_CLOSE);
   
   if(gFastMAHandle == INVALID_HANDLE || gSlowMAHandle == INVALID_HANDLE)
   {
      Print("FAIL: MA handles creation failed");
      return INIT_FAILED;
   }
   
   Print("SUCCESS: Pioneer EA ready - Clean CTrade integration active (default filling)");
   Print("RR Logic: SL=", InpStopLossPips, " pips; TP=",
         (InpStopLossPips > 0 && InpTakeProfitPips == 0
            ? InpStopLossPips * 2
            : InpTakeProfitPips),
         " pips (1:2 auto if TP=0)");
   return INIT_SUCCEEDED;
}

9. Aufräumen: Ressourcenmanagement

In OnDeinit geben wir Indikator-Handles frei, deinitialisieren den Trade Manager und löschen Session-Objekte. Dieser EA räumt hinter sich selbst auf. 

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(gFastMAHandle != INVALID_HANDLE) IndicatorRelease(gFastMAHandle);
   if(gSlowMAHandle != INVALID_HANDLE) IndicatorRelease(gSlowMAHandle);
   gTradeMgr.Deinit();
   gSV.ClearAll();
}

10.Eingabelogik: EMA Crossover mit Ein-Signal-pro-Balken

Die Signallogik ist absichtlich einfach: ein Kreuzen des 9-EMA über den 21-EMA. Es kommt darauf an, wie sie umgesetzt wird. Wir ziehen zwei Werte aus jedem MA-Puffer, erkennen das Kreuz mit vorherigen vs. aktuellen Werten, und überwachen dann mit gLastSignalBar, sodass wir nicht mehrere Eröffnungen auf der gleichen Kerze tätigen. 

//+------------------------------------------------------------------+
//| Signal Detection: EMA Crossover                                  |
//+------------------------------------------------------------------+
bool EntrySignalDetected()
{
   double fast[2], slow[2];
   if(CopyBuffer(gFastMAHandle, 0, 0, 2, fast) != 2 ||
      CopyBuffer(gSlowMAHandle, 0, 0, 2, slow) != 2)
      return false;
   
   bool crossover = (fast[1] <= slow[1] && fast[0] > slow[0]);
   if(!crossover) return false;
   
   datetime barTime = iTime(_Symbol, _Period, 0);
   if(barTime == gLastSignalBar) return false;
   gLastSignalBar = barTime;
   return true;
}

11. Positionsfilterung nach Magie und Symbol

HasOpenPosition durchsucht die offenen Positionen und überprüft sowohl das Symbol als auch die magische Zahl. Dadurch wird sichergestellt, dass der EA nur auf seine eigenen Trades reagiert und nicht mit anderen Systemen interferiert. Es ist wichtig, dass Sie Ihre Strategie immer mit Hilfe von magischen Zahlen isolieren; gehen Sie niemals davon aus, dass Ihnen jede Position auf dem Konto „gehört“.

//+------------------------------------------------------------------+
//| Position Check                                                   |
//+------------------------------------------------------------------+
bool HasOpenPosition()
{
   int total = PositionsTotal();
   for(int i = 0; i < total; i++)
   {
      if(PositionGetSymbol(i) == _Symbol &&
         PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
         return true;
   }
   return false;
}

12. OnTick: Erst Zeitfilter, dann Logik

Der Hauptteil von OnTick ist absichtlich kurz und lesbar – das ist das Ergebnis all dieser Abstraktionen. Zuerst werden die Sitzungsbilder aktualisiert (falls aktiviert). Dann durchlaufen wir das Zeitfilter-Gate: Wenn IsTradingAllowed(gCTX) false zurückgibt, vermerkt der EA einfach „Trading OFF“ und beendet den Handel. Erst wenn die zeitlichen Bedingungen erfüllt sind, prüfen wir, ob es keine offene Position gibt und ob ein EMA-Signal vorliegt. Wenn beides zutrifft, übergeben wir die Ausführung an gTradeMgr.ExecuteBuy

//+------------------------------------------------------------------+
//| OnTick                                                           |
//+------------------------------------------------------------------+
void OnTick()
{
   if(InpDrawSessions)
      gSV.RefreshSessions(InpLookbackDays);
   
   if(!IsTradingAllowed(gCTX))
   {
      Comment("Pioneer EA: Trading OFF");
      return;
   }
   
   Comment("Pioneer EA: Trading ON | Positions: ", PositionsTotal());
   
   if(!HasOpenPosition() && EntrySignalDetected())
   {
      Print("=== SIGNAL: EMA Crossover Detected ===");
      gTradeMgr.ExecuteBuy(InpLotSize, 0, 0, "Pioneer Buy");
   }
}

Beispielindikator mit TimeFilters

1. Indikatorübersicht – ein visueller Begleiter des zeitgefilterten EA

Zunächst deklarieren wir einen Chart-Fenster-Indikator mit zwei Darstellungen: einen für Aufwärtssignale und einen für Aufwärtssignale. Die Eigenschaften teilen MetaTrader 5 mit, dass wir die Pfeile direkt auf den Preis zeichnen und nicht in einem separaten Unterfenster. 

// ============================================================================
// Author: Clemence Benjamin
// File: TimeFilteredSignal.mq5
// Purpose: Time-filtered RSI signal + alerts indicator
// Notes : Uses large Wingdings arrows for visual clarity
//         Signals only appear when IsTradingAllowed(iCTX) == true
// ============================================================================

#property strict
#property indicator_chart_window

// We use two plots: one for bullish arrows, one for bearish arrows
#property indicator_plots   2
#property indicator_buffers 2

// --- Plot 1: Bullish RSI signal (up arrow) ---
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrLime
#property indicator_width1  2
#property indicator_label1  "TF_RSI_Bull"

// --- Plot 2: Bearish RSI signal (down arrow) ---
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrRed
#property indicator_width2  2
#property indicator_label2  "TF_RSI_Bear"

2. Wiederverwendung der Zeitinfrastruktur – Sitzungen und Filter, nicht neu erfunden

Hier verwenden wir die gleichen Bausteine wie beim EA: SessionVisualizer.mqh und TimeFilters.mqh. Das ist das Schlüsselmuster: eine zentralisierte Zeitpolitik, viele Verbraucher (EAs, Indikatoren, Dashboards). Wir deklarieren auch Eingaben für das Zeichnen von Sitzungen und die RSI-Konfiguration sowie das Verhalten bei Warnungen. Wir können Sitzungen ein- und ausschalten, den RSI-Level einstellen und wählen, wie laut oder leise Benachrichtigungen sein sollen.

#include <SessionVisualizer.mqh>
#include <TimeFilters.mqh>

// --------------------------- Inputs -----------------------------------------
// Visual session context (indicator-side)
input bool   InpDrawSessions_i    = true;
input int    InpGMTOffsetHours_i  = 0;
input int    InpLookbackDays_i    = 5;

// RSI configuration
input int    InpRSIPeriod         = 14;
input int    InpRSIOverbought     = 70;
input int    InpRSIOversold       = 30;

// Alert behavior
input bool   InpAlertOnCross      = true;      // enable popup alert on RSI cross
input bool   InpSendPush          = false;     // send mobile push
input bool   InpSendEmail         = false;     // send email
input string InpAlertPrefix       = "TF-RSI";  // prefix tag in messages

3. Gemeinsamer Kontext und Puffer – Einmal-Intelligenz und zwei Pfeile

Der Indikator verwendet denselben CTimeFilterContext, den wir zuvor entworfen haben, und verweist auf seine eigene CSessionVisualizer-Instanz. Dadurch bleibt die Umsetzung mit dem EA konsistent: beide ziehen dieselbe Vorstellung von „erlaubter Zeit“ heran. Wir holen uns zwei Puffer: BuffUp für Aufwärtspfeile und BuffDn für Abwärtspfeile. Diese Trennung ermöglicht eine sofortige Interpretation auf dem Chart – grüner Pfeil nach oben: gefiltertes Aufwärtsereignis; roter Pfeil nach unten: gefiltertes Abwärtsereignis.

// --------------------------- Globals ----------------------------------------
CSessionVisualizer  iSV("TFI_SESS_");
CTimeFilterContext  iCTX;

// Two buffers: one for bullish arrows, one for bearish arrows
double BuffUp[];
double BuffDn[];

int      rsiHandle            = INVALID_HANDLE;
datetime gLastRSIAlertBarTime = 0;   // avoid duplicate alerts per bar

4. Alarm-Assistent – Eine Nachricht, mehrere Kanäle

Anstatt die Aufrufe von Alert() in unserer Schleife zu verstreuen, zentralisieren wir die Benachrichtigungslogik in FireRSIAlert. Das hält den Signalcode sauber und macht Erweiterungen (Präfixänderungen, Formatierung, zusätzliche Daten) trivial. Dieses Muster ist skalierbar – heute sind es RSI-Meldungen, morgen können es „Orderflow-Cluster erkannt“ oder „Custom Regime Shift bestätigt“ sein, ohne dass der gesamte Indikator überarbeitet werden muss.

// --------------------------- Alert helper -----------------------------------
void FireRSIAlert(const string direction, const double rsiValue, const datetime barTime)
{
   string timeStr = TimeToString(barTime, TIME_DATE|TIME_SECONDS);

   string msg = StringFormat("%s | %s | %s | RSI=%.2f | Time=%s (inside allowed window)",
                             InpAlertPrefix,
                             _Symbol,
                             direction,
                             rsiValue,
                             timeStr);

   Alert(msg);

   if(InpSendPush)
      SendNotification(msg);

   if(InpSendEmail)
      SendMail(InpAlertPrefix + " " + _Symbol, msg);

   Print("TimeFilteredSignal: ", msg);
}

5. OnInit-Bindung von Pfeilen an Puffer und Zeit an Sitzungen

In OnInit führen wir die strukturelle Verbindungen durch:

  • Wir deklarieren die beiden Zeichnungs-Puffer,
  • wählen die Wingdings-Pfeilcodes (233 aufwärts, 234 abwärts),
  • verbinden den Visualizer mit dem Kontext und wenden die GMT-Zeitverschiebung an.
  • Optional können wir die Sitzungen zeichnen.
  • Erstellen des RSI-Handles und Initialisieren der Puffer.

 Der Indikator sollte schnell fehlschlagen, wenn eine Kernressource (RSI, Symbolinfo) nicht erstellt werden kann.

// --------------------------- OnInit -----------------------------------------
int OnInit()
{
   // Bind buffers
   SetIndexBuffer(0, BuffUp, INDICATOR_DATA);
   SetIndexBuffer(1, BuffDn, INDICATOR_DATA);

   // Use large Wingdings arrows:
   // 233 = up arrow, 234 = down arrow
   PlotIndexSetInteger(0, PLOT_ARROW, 233); // Bullish arrow up
   PlotIndexSetInteger(1, PLOT_ARROW, 234); // Bearish arrow down

   // Time filter context
   iCTX.AttachVisualizer(iSV);
   iCTX.SetGMTOffset(InpGMTOffsetHours_i);

   if(InpDrawSessions_i)
      iSV.RefreshSessions(InpLookbackDays_i);

   // Create RSI handle
   rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE)
   {
      Print("TimeFilteredSignal: Failed to create RSI handle. Error = ", GetLastError());
      return(INIT_FAILED);
   }

   // Initialize buffers as empty
   ArrayInitialize(BuffUp, EMPTY_VALUE);
   ArrayInitialize(BuffDn, EMPTY_VALUE);

   return(INIT_SUCCEEDED);
}

6. OnCalculate – Zeitbezogenes Kreuzen des RSI mit Pfeilen

Hier macht der Indikator seinem Namen alle Ehre. Wir iterieren durch Balken, aber die Logik ist absichtlich einfach und vielschichtig:

  1. Pfeile werden standardmäßig gelöscht (EMPTY_VALUE blendet sie aus).
  2. Aktualisiert optional die Sitzungsdarstellung, wenn neue Balken eintreffen.
  3. Ruft die RSI-Werte über CopyBuffer ab.
Für jeden Balken:

  1. Prüfung durch IsTradingAllowed(iCTX) – bei negativem Zeitfilter wird es übersprungen.
  2. Erkennen von RSI-Kreuzen:
    • Aufwärts: Verlassen des überverkauften Bereichs nach oben → grünen Aufwärtspfeil unter den Balken zeichnen.
    • Abwärts: Verlassen des überkauften Bereichs nach unten → roten Abwärtspfeil über den Balken zeichnen.

Allein während des letzten Balkens werden die Alarme einmal mit gLastRSIAlertBarTime ausgelöst.

Die Signale sind nicht nur „RSI crosses“ (RSI kreuzt), sondern „RSI crosses that respect our professional trading hours, sessions, and (future) event blocks.“ (RSI-Kreuzungen, die unsere professionellen Handelszeiten, Sitzungen und (zukünftigen) Ereignisblöcke berücksichtigen).

// --------------------------- OnCalculate ------------------------------------
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[])
{
   if(rates_total <= 2 || rsiHandle == INVALID_HANDLE)
      return(rates_total);

   // Refresh session drawings when new bars appear
   if(InpDrawSessions_i && rates_total != prev_calculated)
      iSV.RefreshSessions(InpLookbackDays_i);

   // Prepare RSI buffer
   static double rsiBuffer[];
   ArrayResize(rsiBuffer, rates_total);

   int copied = CopyBuffer(rsiHandle, 0, 0, rates_total, rsiBuffer);
   if(copied <= 0)
      return(prev_calculated > 0 ? prev_calculated : rates_total);

   // Recalc from prev-1 so last bar updates smoothly
   int start = (prev_calculated > 1 ? prev_calculated - 1 : 1);

   for(int i = start; i < rates_total; ++i)
   {
      // Default: hide both arrows on this bar
      BuffUp[i] = EMPTY_VALUE;
      BuffDn[i] = EMPTY_VALUE;

      // Respect global time filters (same logic as EA)
      if(!IsTradingAllowed(iCTX))
         continue;

      double rsi_prev = rsiBuffer[i - 1];
      double rsi_curr = rsiBuffer[i];

      bool bullCross =
         (rsi_prev < InpRSIOversold && rsi_curr >= InpRSIOversold);

      bool bearCross =
         (rsi_prev > InpRSIOverbought && rsi_curr <= InpRSIOverbought);

      // Bullish RSI recovery: big green up arrow below bar
      if(bullCross)
      {
         BuffUp[i] = low[i] - (_Point * 5);

         if(InpAlertOnCross && i == rates_total - 1 && time[i] != gLastRSIAlertBarTime)
         {
            FireRSIAlert("RSI cross UP from oversold", rsi_curr, time[i]);
            gLastRSIAlertBarTime = time[i];
         }
      }

      // Bearish RSI rejection: big red down arrow above bar
      if(bearCross)
      {
         BuffDn[i] = high[i] + (_Point * 5);

         if(InpAlertOnCross && i == rates_total - 1 && time[i] != gLastRSIAlertBarTime)
         {
            FireRSIAlert("RSI cross DOWN from overbought", rsi_curr, time[i]);
            gLastRSIAlertBarTime = time[i];
         }
      }
   }

   return(rates_total);
}

Skalierbarkeit: Jenseits von RSI

RSI ist nur ein Platzhalter. Die Struktur, die wir aufgebaut haben, unterstützt jeden Signalgeber mit fast keinen Änderungen:

Ersetzen Sie den RSI-Block durch:

  • Gleitender Durchschnitt der Kreuzcluster.
  • Ausbruchsbedingungen (Hoch/Tief der Sitzung, VWAP-Abweichungen).
  • Kerzenmuster (pin bars, engulfing) nur während London.
  • Volatilitätsregime (ATR-Filter).
  • Auftragsbuch/Proxy-Metriken, Ergebnisse des maschinellen Lernens, was immer Sie wollen.

Behalten Sie alles andere:

  • IsTradingAllowed(iCTX) als universelles Zeittor.
  • Pfeile (oder Markierungen) auf dem Chart.
  • Optionale Alarme über FireRSIAlert (umbenennen in einen generischen FireSignalAlert).

In diesem Beispiel haben wir RSI-Kreuze verwendet, weil sie einfach zu erkennen sind. Der wahre Wert liegt nicht in der Wahl des Oszillators, sondern im Rahmen: Jedes Signal kann durch dieselbe TimeFilter-Ebene geleitet werden. Sie legen fest, wann Ihre Strategie zu Wort kommen darf, und geben dann ein, was sie sagen soll.



Tests

Nachdem wir die Quelldateien erfolgreich kompiliert hatten, setzten wir sowohl den TimeFilteredEA- als auch den TimeFilteredSignal-Indikator im Strategy Tester ein, um das Gesamtverhalten des Frameworks zu überprüfen. Die Testausführung war merklich langsam – was angesichts der übereinander geschichteten Zeitfilter, des Sitzungsrenderings und der Signallogik nicht überrascht – aber sie wurde zuverlässig abgeschlossen und lieferte klare, interpretierbare Ergebnisse. Die folgenden Screenshots zeigen, dass Handelsgeschäfte und RSI-basierte Pfeile nur innerhalb der zulässigen Zeitfenster ausgelöst wurden, genau wie vorgesehen.

Strategie-Tester

Abb. 1. Testen des TimeFilteredEA im Strategy Tester


zeitgefiltertes Signal

Abb. 2. Testen des Indikators TimeFilteredSignal




Schlussfolgerung

Es ist durchaus möglich zu kontrollieren, wann unsere Handelssysteme arbeiten dürfen, indem wir eine strukturierte MQL5-Zeitfilterlogik implementieren, wie z. B. das in diesem Projekt entwickelte Framework. Anstatt die Bedingungen über willkürliche if-Anweisungen zu verstreuen, haben wir saubere Abstraktionen, gemeinsamen Kontext und sitzungsspezifische Dienstprogramme verwendet, um das Verhalten sowohl vorhersehbar als auch wiederverwendbar zu machen. Durch die Verankerung unserer Logik an klaren Referenzpunkten – Marktsitzungen, Zeitspannen und (optional) Wirtschaftskalenderfenster – können wir mit minimalem Aufwand präzise Handels- und Signalisierungsfenster definieren.

Sobald diese Zeitfenster eingerichtet sind, beginnt die eigentliche Kreativität auf der Signalseite. Jede Strategielogik – RSI, gleitende Durchschnitte, Strukturbrüche, Volatilitätsfilter oder Orderflow-Konzepte – kann nun in einen disziplinierten Zeitplan eingefügt werden, anstatt 24/5 zu feuern. Dieser Ansatz reduziert direkt das Rauschen, das durch die kontinuierliche, kontextblinde Überwachung entsteht, und hilft uns, uns auf die Verbesserung der Ausführungsqualität zu konzentrieren, wo es am wichtigsten ist. In der nachstehenden Übersichtstabelle habe ich die wichtigsten Erkenntnisse aus dieser Diskussion hervorgehoben. Die vollständigen Quellcodes sind unter dem Artikel angehängt, damit Sie sie studieren, ändern und diese Grundlage mit Ihren eigenen Ideen erweitern können.


Wichtigste Erkenntnisse:

Wichtigste ErkenntnisseBeschreibung
1. Zentralisierte Logik der ZeitfilterungDie Implementierung einer dedizierten Zeitfilter-Schicht (anstelle von verstreuten if-Bedingungen) erleichtert die Wiederverwendung derselben Sitzungs-, Uhrzeit- und Ereignisregeln in mehreren EAs und Indikatoren, was ein konsistentes Verhalten und eine einfachere Wartung gewährleistet.
2. Verwendung von Kontextobjekten anstelle von Globalem ChaosDas CTimeFilterContext-Muster zeigt, wie die Konfiguration (GMT-Offset, Visualisierungslink) in einem einzigen Objekt gebündelt werden kann, um globale Abhängigkeiten zu reduzieren und Ihre Codebasis modularer, testbarer und skalierbarer zu machen.
3. Trennen von Signallogik von der AusführungslogikIndem wir die Handelsausführung in CTradeManager und die Signalerkennung in dedizierten Funktionen/Indikatoren unterbringen, trennen wir klar zwischen dem „Auslösen“ und dem „Senden der Aufträge“.
4. Design für visuelles Feedback und DiagnostikDer SessionVisualizer und die Wingdings-Pfeilsignale bieten eine sofortige visuelle Bestätigung, dass sich Zeitfilter und Signale wie erwartet verhalten, was für die Fehlersuche in komplexer Logik und das Vertrauen des Händlers in das System unerlässlich ist.
5. Aufbau erweiterbarer Signal-FrameworksDie Verwendung des RSI als Plug-in-Modul innerhalb eines zeitlich gefilterten Rahmens demonstriert ein skalierbares Design: Jede Future-Methode (Preisbewegung, MA-Kreuz, Volatilitätsfilter, nutzerdefinierte Modelle) kann mit minimalen Code-Änderungen hinter demselben IsTradingAllowed()-Tor integriert werden.

Anhänge:

QuelldateiVersionBeschreibung
SessionVisualizer.mqh1.02Modulare Session-Mapping- und Zeichenbibliothek, die die wichtigsten Forex-Handelssitzungen im Chart darstellt und wiederverwendbare Zeitfensterreferenzen für übergeordnete Tools bereitstellt.
TimeFilters.mqh1.00Kern-Framework zur Zeitfilterung, das uhr-, sitzungs- und (erweiterbare) ereignisbasierte Prüfungen über eine einheitliche IsTradingAllowed()-Schnittstelle und ein gemeinsames Kontextobjekt bietet.
TimeFilteredEA.mq51.0Beispiel für einen Expert Advisor, der die TimeFilters und SessionVisualizer verwendet, um EMA-basierte Eingaben nur innerhalb zulässiger Zeitfenster auszuführen, mit strukturiertem Handelsmanagement.
TimeFilteredSignal.mq51.0Chart-Indikator, der große Wingdings-Pfeile anzeigt und RSI-basierte Alarme ausschließlich innerhalb der definierten Zeitfilterfenster generiert und als visuelle und erweiterbare Signalebene dient.

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

Beigefügte Dateien |
TimeFilters.mqh (12.16 KB)
TimeFilteredEA.mq5 (12.07 KB)
Neuroboids Optimierungsalgorithmus (NOA) Neuroboids Optimierungsalgorithmus (NOA)
Eine neue bioinspirierte Metaheuristik zur Optimierung, NOA (Neuroboids Optimization Algorithm), kombiniert die Prinzipien der kollektiven Intelligenz und der neuronalen Netze. Im Gegensatz zu herkömmlichen Methoden verwendet der Algorithmus eine Population von selbstlernenden „Neuroboiden“, von denen jeder sein eigenes neuronales Netz hat, das seine Suchstrategie in Echtzeit anpasst. Der Artikel zeigt die Architektur des Algorithmus, die Mechanismen des Selbstlernens der Agenten und die Aussichten für die Anwendung dieses hybriden Ansatzes auf komplexe Optimierungsprobleme.
Vom Neuling zum Experten: Prädiktive Preispfade Vom Neuling zum Experten: Prädiktive Preispfade
Fibonacci-Levels bieten einen praktischen Rahmen, der von den Märkten oft beachtet wird und Preiszonen aufzeigt, in denen Reaktionen wahrscheinlicher sind. In diesem Artikel erstellen wir einen Expert Advisor, der die Logik des Fibonacci-Retracements anwendet, um wahrscheinliche künftige Bewegungen zu antizipieren und Rücksetzer mit schwebenden Aufträgen zu handeln. Erkunden Sie den gesamten Arbeitsablauf – von der Umkehr-Erkennung über die Pegelaufzeichnung und Risikokontrolle bis hin zur Ausführung.
Arbitrage-Handel im Forex: Ein einfacher synthetischer Market-Maker-Bot für den Einstieg Arbitrage-Handel im Forex: Ein einfacher synthetischer Market-Maker-Bot für den Einstieg
Heute werfen wir einen Blick auf meinen ersten Arbitrage-Roboter – einen Liquiditätsanbieter (wenn man ihn so nennen kann) für synthetische Vermögenswerte. Derzeit arbeitet dieser Bot erfolgreich als Modul in einem großen maschinellen Lernsystem, aber ich habe einen alten Forex-Arbitrage-Roboter aus der Cloud geholt, also lassen Sie uns einen Blick darauf werfen und darüber nachdenken, was wir heute damit machen können.
Forex Arbitrage-Handel: Analyse der Bewegungen synthetischer Währungen und ihrer mittleren Umkehrung Forex Arbitrage-Handel: Analyse der Bewegungen synthetischer Währungen und ihrer mittleren Umkehrung
In diesem Artikel werden wir die Bewegungen synthetischer Währungen mit Hilfe von Python und MQL5 untersuchen und herausfinden, wie praktikabel Forex-Arbitrage heute ist. Wir werden uns auch mit fertigem Python-Code für die Analyse synthetischer Währungen befassen und mehr Details darüber mitteilen, was synthetische Währungen im Devisenhandel sind.