English Русский
preview
Trading-Disziplin in Code umsetzen (Teil 2): Entwicklung eines MQL5-Systems zur Durchsetzung eines täglichen Handelslimits für alle Trades in MQL5

Trading-Disziplin in Code umsetzen (Teil 2): Entwicklung eines MQL5-Systems zur Durchsetzung eines täglichen Handelslimits für alle Trades in MQL5

MetaTrader 5Beispiele |
25 0
Christian Benjamin
Christian Benjamin

Inhalt


Einführung

Die Begrenzung der Anzahl der Trades pro Tag ist eine der einfachsten Regeln zur Risikokontrolle, die ein Trader festlegen kann. In der Praxis ist es jedoch auch eine der Regeln, die am leichtesten verletzt werden. Dies gilt nicht nur für Trader, die nach eigenem Ermessen handeln, sondern auch für Konten, auf denen mehrere Expert Advisors gleichzeitig für verschiedene Symbole eingesetzt werden. Eine Regel wie „nicht mehr als N Eröffnungen pro Tag“ verliert schnell an Wirksamkeit, wenn der Marktdruck zunimmt, die Performance schwankt oder mehrere Strategien unabhängig voneinander und ohne Abstimmung agieren.

Das Kernproblem ist nicht mangelnde Disziplin, sondern die fehlende Durchsetzung auf Kontoebene. MT5 bietet standardmäßig keinen zentralen Mechanismus, der festlegt, was als Einstieg gilt, wie ein Handelstag gemessen wird oder was geschehen muss, sobald ein vordefiniertes Limit erreicht ist. Ohne genaue Definitionen – ob ein Einstieg auf DEAL_ENTRY_IN, Positionen oder Orders basiert; ob die Sitzung um Servermitternacht oder zu einem benutzerdefinierten Zeitpunkt zurückgesetzt wird; ob die Regelung für alle Symbole gilt – bleibt die Regel eher konzeptioneller als struktureller Natur.

In diesem Artikel wird ein „Daily Trade Limit Enforcer“ in MQL5 entwickelt, der auf Kontoebene eingesetzt werden soll. Das System zählt bestätigte Einstiegs-Deals innerhalb eines konfigurierbaren Sitzungsfensters, überwacht die Handelsaktivitäten für alle Symbole und setzt das Limit automatisch durch. Sobald der festgelegte Schwellenwert erreicht ist, werden neue ausstehende Orders gelöscht und neu eröffnete Positionen geschlossen, während bestehende Positionen, die vor Erreichen des Schwellenwerts eröffnet wurden, davon unberührt bleiben.

Im Mittelpunkt der Diskussion steht die Umsetzung einer verhaltensbezogenen Risikoregel in eine technisch durchgesetzte Beschränkung. Die Architektur, die Implementierungsdetails und die Testverfahren werden modular und reproduzierbar dargestellt. Der vollständige Quellcode – einschließlich der gemeinsamen Logikschicht, der Dashboard-Komponente und des „Enforcement Expert Advisor“ – ist am Ende des Artikels zu finden.


Die Disziplinlogik verstehen

Disziplin wird in der Fachliteratur zum Thema Trading ausführlich behandelt, doch in der Praxis bleibt sie eine der Eigenschaften, die am schwierigsten aufrechtzuerhalten sind. Es ist zwar einfach, Regeln festzulegen, sie zu dokumentieren und sogar andere dazu anzuhalten, sie zu befolgen, doch die konsequente Anwendung eben dieser Regeln unter realen Marktbedingungen stellt eine ganz andere Herausforderung dar. Die Handelsdisziplin lässt nicht deshalb nach, weil Händler ihre Regeln vergessen, sondern weil sich im Laufe der Zeit emotionaler und kognitiver Druck aufbaut. Jede Entscheidung beeinflusst die Wahrnehmung, das Selbstvertrauen und die Risikobereitschaft ein wenig. Infolgedessen weicht das Verhalten nach und nach vom ursprünglichen Plan ab, oft ohne dass man sich dessen sofort bewusst wird.

Faktoren, die die Handelsdisziplin beeinflussen

Mehrere Faktoren tragen unmittelbar zur Schwächung der Disziplin während der Handelssitzungen bei. Dazu gehören unter anderem:

  • Emotionale Verstärkung durch aufeinanderfolgende Gewinne oder Verluste
  • Zeitdruck, insbesondere in Phasen mit starken Marktschwankungen
  • Ergebnisfixierung, bei der aktuelle Ergebnisse die Entscheidungsfindung dominieren
  • Recovery-Bias – der Drang, Verluste noch in derselben Sitzung auszugleichen
  • Falsches Selbstvertrauen, das oft durch kurzfristige Erfolge ausgelöst wird

Jeder dieser Faktoren beeinflusst das Verhalten auf unterschiedliche Weise, doch sie haben alle eine gemeinsame Folge: Die Einhaltung der Regeln lässt gerade in den entscheidenden Momenten nach. Wichtig ist, dass dieser Druck nicht nur auf unerfahrene Teilnehmer beschränkt ist. Selbst erfahrene Profis erleben Phasen, in denen die Disziplin nachlässt – insbesondere bei lang andauernden Handelssitzungen oder unter ungewöhnlichen Marktbedingungen. Erfahrung mag zwar die Häufigkeit solcher Ausrutscher verringern, beseitigt jedoch nicht die Anfälligkeit.

Da diese psychischen Belastungen die bewusste Wahrnehmung überlagern können, kann ein System, das ausschließlich auf Warnungen basiert, gerade dann versagen, wenn es am dringendsten benötigt wird. Aus diesem Grund geht die Logik über die passive Überwachung hinaus und reicht bis zur aktiven Durchsetzung. Sobald das vordefinierte Limit erreicht ist, greift das System automatisch ein, wodurch impulsives Handeln verhindert und die strukturelle Disziplin gewahrt wird.

Automatisierung der Handelsdisziplin

Angesichts dieser Gegebenheiten reicht es nicht mehr aus, sich ausschließlich auf die eigene Selbstbeherrschung zu verlassen. Die menschliche Psyche ist nicht darauf ausgelegt, über einen längeren Zeitraum mit Unsicherheit, schnellem Feedback und finanziellen Risiken umzugehen. Genau aus diesem Grund spielt die Automatisierung eine entscheidende Rolle bei der Gewährleistung der Konsistenz.

Disziplin zu automatisieren bedeutet nicht, die Kontrolle an einen Algorithmus abzugeben. Vielmehr bedeutet dies, die Aufgaben der Überwachung, Zählung und Signalisierung an ein System zu übertragen, das von Emotionen unberührt bleibt. Durch die Auslagerung dieser Funktionen verringert der Trader seine kognitive Belastung und seine Entscheidungsmüdigkeit. Dieser Ansatz betrachtet Disziplin nicht mehr als persönlichen Kampf, sondern als strukturelle Absicherung.

Die Logik der Disziplin in diesem System

Das tägliche Trade-Limit-System basiert auf einer zweistufigen Logik: Transparenz durch Echtzeit-Feedback und Durchsetzung durch automatisierte Eingriffe. Das Design vermeidet Komplexität und sorgt für transparente Zustandsübergänge, während es gleichzeitig ein wichtiges Sicherheitsnetz bietet, falls der Grenzwert überschritten wird.

Im Betrieb erfüllt das System folgende Funktionen:

1. Erfasst die Gesamtzahl der innerhalb eines definierten Handelstages über das gesamte Konto hinweg ausgeführten Trades (unabhängig vom Symbol, ob manuell oder per EA).
2. Vergleicht die Anzahl mit einem benutzerdefinierten Höchstwert.
3. Stuft den aktuellen Handelsstatus anhand der Nähe zum Limit ein:

  • Zulässig – der Handel liegt innerhalb des Limits.
  • Achtung – die verbleibenden Trades sind auf oder unter einen Warnschwellenwert gefallen.
  • „Limit Reached“ – Die maximale Anzahl an Trades wurde ausgeführt.

4. Vermittelt dem Händler den aktuellen Status visuell und verbal über ein farbcodiertes Dashboard und optionale Benachrichtigungen (Pop-up, Push-Benachrichtigung, Ton).

5. Setzt das Limit durch, indem neue Trades aktiv blockiert werden, sobald der Status „Limit Reached“ eintritt:

  • Jede neue ausstehende Order (die nach Erreichen des Limits erteilt wurde) wird sofort gelöscht.
  • Jede neu eröffnete Position (sei es durch einen EA oder manuell) wird sofort geschlossen.
  • Bestehende Positionen, die vor Erreichen des Limits eröffnet wurden, bleiben unberührt – das System verhindert lediglich zusätzliche Marktexponierung.

Dieser zweistufige Ansatz stellt sicher, dass der Händler stets über seinen Status informiert ist; selbst wenn diese Aufmerksamkeit unter Druck nachlässt, verhindert die strukturelle Durchsetzung weitere übermäßige Handelsaktivitäten. Die Logik lässt sich wie folgt veranschaulichen:

Abbildung 1: Entscheidungsablauf vom täglichen Start bis zur Durchsetzung

Dieser Ablauf veranschaulicht, wie das System ausgeführte Trades kontinuierlich nachverfolgt, sie mit dem vordefinierten Tageslimit abgleicht und den aktuellen Handelsstatus klassifiziert. Der Prozess beginnt zu Beginn des Handelstages, wobei alle Transaktionen auf dem Konto überwacht werden. Nach jeder Aktualisierung prüft das System, ob der Handel weiterhin innerhalb der zulässigen Grenzen bleibt.

Sofern der Status dies zulässt, wird der Handel ohne Einschränkungen fortgesetzt. Wenn das System feststellt, dass sich der Grenzwert nähert, aber noch nicht erreicht ist, wechselt es in einen Warnzustand, in dem Warnmeldungen ausgegeben werden, während Aktivitäten weiterhin zulässig sind. Dadurch wird frühzeitig ein Bewusstsein geschaffen, ohne dass es zu unmittelbaren Einschränkungen kommt.

Sobald das Tageslimit erreicht ist, wechselt das System in den Status „Limit Reached“. In dieser Phase erfolgt die Durchsetzung nicht mehr nur informativ, sondern aktiv: Neue ausstehende Aufträge werden storniert und neu eröffnete Positionen sofort geschlossen, während bereits bestehende Positionen davon unberührt bleiben.

Dieser strukturierte Ablauf – von „erlaubt“ über „Vorsicht“ bis hin zur „erzwungenen Einschränkung“ – spiegelt praktische Kontrollsysteme wider, bei denen die Überwachung zur Klassifizierung führt und die Klassifizierung wiederum die Maßnahmen bestimmt. Das Ziel besteht nicht nur darin, zu informieren, sondern auch entschlossen einzugreifen, sobald vordefinierte Grenzen überschritten werden.


Überblick über den Systemaufbau

Bevor wir den Code im Detail betrachten, ist es wichtig, die Gesamtarchitektur des Moduls zur Durchsetzung des täglichen Handelslimits zu verstehen. Anstatt einen einzigen monolithischen Expert Advisor zu entwickeln, ist das System bewusst in drei unabhängige, aber zusammenwirkende Komponenten unterteilt.

Dieser modulare Ansatz stellt sicher, dass jede Komponente eine klar definierte Aufgabe hat und geändert, ersetzt oder erweitert werden kann, ohne die anderen zu beeinträchtigen. Die Kommunikation zwischen den Komponenten erfolgt über globale Terminalvariablen, die als gemeinsame Speicherschicht dienen, auf die jedes MQL5-Programm zugreifen kann.

Die drei Komponenten lauten wie folgt:

1. Gemeinsam genutzte Bibliothek – DailyTradeLimit.mqh

Diese Include-Datei enthält die Kernlogik des Systems. Sie ist zuständig für:

  • Zählung der während des aktuellen Handelstages ausgeführten Trades
  • Ermittlung des aktiven Handelstages anhand der konfigurierten Startzeit
  • Berechnung der verbleibenden Trades
  • Klassifizierung des Handelsstatus (Zulässig / Vorsicht / Limit erreicht)

Sie stellt eine Reihe von leichtgewichtigen Hilfsfunktionen wie IsTradingAllowed(), TradesToday() und GetState() bereit, die von jedem EA oder Indikator aufgerufen werden können.

Durch die Unterbringung der Logik in einer gemeinsam genutzten Bibliothek wird sichergestellt, dass alle Komponenten auf denselben Berechnungsregeln und konsistenten Daten basieren, wodurch Doppelarbeit vermieden und logische Abweichungen zwischen den Modulen verhindert werden.

2. Dashboard-Indikator – DailyTradeLimitDashboard.mq5

Das Dashboard dient als Benutzeroberfläche. Wenn es an ein Chart angehängt wird, hat es folgende Funktion:

  1. Liest benutzerdefinierte Eingaben ein (Tageslimit, Startzeit, Schwellenwert für den Warnstatus, Alarm-Einstellungen)
  2. Schreibt diese Einstellungen in globale Variablen
  3. Zeigt ein farbcodiertes Panel an, das Folgendes anzeigt:
    • Heute ausgeführte Trades
    • Verbleibende Trades
    • Aktueller Status

Das Dashboard wird jede Sekunde und unmittelbar nach Handelsereignissen aktualisiert, wodurch eine Echtzeit-Transparenz gewährleistet ist. Außerdem kann es Pop-up-, Push- oder akustische Benachrichtigungen auslösen, wenn sich der Handelsstatus ändert. Seine Aufgabe ist es, Transparenz und Bewusstsein für den aktuellen Status zu schaffen.

3. Enforcer EA – DailyTradeLimitEnforcer.mq5

Der Enforcer EA läuft als Durchsetzungsschicht kontinuierlich im Hintergrund. Er liest dieselben globalen Variablen aus, die vom Dashboard definiert wurden, und überwacht alle Handelsaktivitäten auf dem gesamten Konto. Sobald ein neuer Handel erkannt wird, wird geprüft, ob das Tageslimit bereits erreicht wurde.

Wird das Limit überschritten, reagiert der EA sofort

  1. Storniert alle neu erteilten ausstehenden Aufträge
  2. Schließt alle neu eröffneten Positionen

Dies gilt unabhängig davon, ob der Handel manuell oder durch einen anderen EA initiiert wurde. Entscheidend ist, dass Positionen, die vor Erreichen des Limits eröffnet wurden, unberührt bleiben. Das System verhindert ein zusätzliches Risiko – es greift jedoch nicht rückwirkend ein.

Kommunikation über globale Variablen

Alle drei Komponenten greifen auf die folgenden gemeinsamen globalen Variablen zurück:

  1. DTL_LIMIT – maximal zulässige Anzahl von Trades pro Tag (wird als Double-Wert gespeichert, aber als Ganzzahl behandelt).
  2. DTL_START_TIME – Beginn des Handelstages, kodiert im Format HHMM (z. B. 900 für 09:00 Uhr).
  3. DTL_AMBER – Schwellenwert für den Warnzustand (verbleibende Trades ≤ diesem Wert).

Das Dashboard schreibt diese Werte während der Initialisierung. Sowohl die Bibliothek als auch der Enforcer lesen sie aus, wann immer eine Neuberechnung erforderlich ist. Da sich die Logik zur Zählung der Transaktionen in der gemeinsam genutzten Bibliothek befindet und eine periodische Aktualisierungslogik verwendet, arbeiten alle Komponenten mit demselben synchronisierten Datenstand.

Das folgende Diagramm veranschaulicht den Interaktionsablauf zwischen dem Dashboard, der gemeinsam genutzten Bibliothek und der Durchsetzungs-EA.

Abbildung 2: Interaktionsablauf


Warum dieses Design?

Die Aufteilung des Systems in drei zusammenwirkende Module bietet mehrere strukturelle Vorteile:

Vorteil Erklärung
Wiederverwendbarkeit Jeder EA kann die gemeinsam genutzte Bibliothek einbinden und freiwillig die Funktion `IsTradingAllowed()` prüfen. Selbst wenn dies nicht der Fall ist, gewährleistet der Enforcer EA dennoch die Einhaltung der Vorschriften.
Unabhängigkeit Das Dashboard und der Enforcer können auf separaten Charts ausgeführt werden. Beides beeinträchtigt weder andere EAs noch manuelle Handelsabläufe.
Klarheit Jede Komponente folgt dem Prinzip der einzigen Verantwortung, wodurch das System leichter zu verstehen, zu testen und zu warten ist.
Erweiterbarkeit Zusätzliche Einschränkungen – wie beispielsweise tägliche Gewinnobergrenzen, Drawdown-Grenzen oder zeitliche Handelsfenster – lassen sich durch eine Erweiterung der gemeinsam genutzten Bibliothek unter Beibehaltung desselben Architekturmusters umsetzen.

Nachdem diese architektonische Grundlage nun geschaffen ist, wenden wir uns nun der schrittweisen Umsetzung der einzelnen Komponenten zu.


MQL5-Implementierung

In diesem Abschnitt gehen wir Schritt für Schritt den praktischen Prozess durch, bei dem das Disziplin-Framework in voll funktionsfähigen MQL5-Code umgesetzt wird. Das Ziel besteht nicht einfach darin, Transaktionen zu zählen, sondern ein System zu entwickeln, das unter Live-Bedingungen stabil, lesbar und flexibel einsetzbar ist.

Die Entwicklung beginnt in der MetaEditor-Umgebung, wo drei separate Dateien erstellt werden, denen jeweils eine klar definierte Aufgabe zugewiesen ist. Von Anfang an wird eine strikte Trennung zwischen Konfigurationsparametern, Zustandsverwaltung, Berechnungslogik und Elementen der Benutzeroberfläche gewahrt. Diese strukturelle Klarheit gewährleistet, dass das System modular, testbar und leicht erweiterbar bleibt, falls später zusätzliche Einschränkungen eingeführt werden.

A) DailyTradeLimit.mqh – Die gemeinsam genutzte Bibliothek

  • Dienstprogramme zur Zeitumrechnung

Die Bibliothek stellt die Funktionen „TimeToInt“ und „IntToTime“ bereit, um eine Brücke zwischen der für Menschen lesbaren Zeiteingabe und dem Speicherformat des Terminals zu schlagen. TimeToInt wandelt eine Zeichenfolge wie „09:00“ in eine Ganzzahl um, nämlich 900, was sich gut zum Speichern in einer globalen Variablen des Terminals eignet. IntToTime führt den umgekehrten Vorgang durch und wandelt 900 wieder in „09:00“ um, damit der Wert angezeigt oder weiterverarbeitet werden kann. Diese Darstellung sorgt für eine leichtgewichtige Konfiguration und ist gleichzeitig leicht verständlich.

//+------------------------------------------------------------------+
//| Convert "HH:MM" to HHMM (e.g., "09:00" -> 900)                   |
//+------------------------------------------------------------------+
int TimeToInt(string timeStr)
  {
   string parts[];
   if(StringSplit(timeStr,':',parts)!=2)
      return 0;
   int h=(int)StringToInteger(parts[0]);
   int m=(int)StringToInteger(parts[1]);
   return h*100+m;
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Convert HHMM to "HH:MM"                                           |
//+------------------------------------------------------------------+
string IntToTime(int value)
  {
   int h=value/100;
   int m=value%100;
   return StringFormat("%02d:%02d",h,m);
  }
//+------------------------------------------------------------------+

  • Parameterabruf

Die Konfiguration wird über die globalen Terminalvariablen „GetParamLimit“, „GetParamStartTime“ und „GetParamAmber“ eingelesen. Jede Funktion liest die ihr zugeordnete globale Variable (DTL_LIMIT, DTL_START_TIME, DTL_AMBER) aus und greift auf einen sinnvollen Standardwert zurück, falls die globale Variable nicht gesetzt ist. Dadurch wird sichergestellt, dass alle Komponenten – Dashboard und Enforcer – auf dieselbe Konfigurationsquelle zurückgreifen, ohne dass Logik dupliziert wird.

//+------------------------------------------------------------------+
//| Get the daily trade limit (max trades per day)                   |
//+------------------------------------------------------------------+
int GetParamLimit()
  {
   if(GlobalVariableCheck("DTL_LIMIT"))
      return (int)GlobalVariableGet("DTL_LIMIT");
   return DEFAULT_LIMIT;
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Get the configured start time as a string                        |
//+------------------------------------------------------------------+
string GetParamStartTime()
  {
   if(GlobalVariableCheck("DTL_START_TIME"))
     {
      int t=(int)GlobalVariableGet("DTL_START_TIME");
      return IntToTime(t);
     }
   return DEFAULT_START_TIME;
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Get the amber threshold (remaining trades before caution)        |
//+------------------------------------------------------------------+
int GetParamAmber()
  {
   if(GlobalVariableCheck("DTL_AMBER"))
      return (int)GlobalVariableGet("DTL_AMBER");
   return DEFAULT_AMBER;
  }
//+------------------------------------------------------------------+

  • Zeitberechnung

ParseTime wandelt eine Zeichenfolge im Format „HH:MM“ in separate Ganzzahlwerte für Stunde und Minute um, die GetDayStart zur Festlegung des aktuellen Handelstages verwendet. GetDayStart vergleicht die Broker-Zeit mit der konfigurierten Startzeit und verwendet, falls die aktuelle Zeit vor dem heutigen Start liegt, den gestrigen Tag als Anker; andernfalls wird der heutige Tag verwendet. Diese präzise Berechnung gewährleistet, dass die Anzahl der täglichen Trades in den verschiedenen Zeitzonen der Broker zum richtigen Zeitpunkt zurückgesetzt wird.

//+------------------------------------------------------------------+
//| Parse "HH:MM" into hour and minute integers                      |
//+------------------------------------------------------------------+
bool ParseTime(string txt,int &h,int &m)
  {
   string parts[];
   if(StringSplit(txt,':',parts)<2)
      return false;
   h=(int)StringToInteger(parts[0]);
   m=(int)StringToInteger(parts[1]);
   return (h>=0 && h<=23 && m>=0 && m<=59);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Calculate the start datetime of the current trading day          |
//+------------------------------------------------------------------+
datetime GetDayStart(datetime now,string startTime)
  {
   int h=0,m=0;
   if(!ParseTime(startTime,h,m))
     {
      h=0;
      m=0;
     }
   MqlDateTime t;
   TimeToStruct(now,t);

   //--- If current time is before today's start, use yesterday
   if(t.hour<h || (t.hour==h && t.min<m))
      now-=86400;

   TimeToStruct(now,t);
   t.hour=h;
   t.min=m;
   t.sec=0;
   return StructToTime(t);
  }
//+------------------------------------------------------------------+

  • Trade-Zählung

CountTrades erfasst alle Deals mit dem Tag DEAL_ENTRY_IN seit dem berechneten Tagesbeginn, indem es den Verlauf innerhalb des Tagesfensters durchsucht. Es nutzt HistorySelect, um die Suche einzugrenzen, und durchläuft die Deals, um diejenigen zu zählen, die den Status DEAL_ENTRY_IN haben.

//+------------------------------------------------------------------+
//| Count trades (DEAL_ENTRY_IN) since a given datetime              |
//+------------------------------------------------------------------+
int CountTrades(datetime from)
  {
   datetime to=from+86400;
   if(!HistorySelect(from,to))
      return 0;

   int total=HistoryDealsTotal();
   int cnt=0;
   for(int i=0; i<total; i++)
     {
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket>0 && HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_IN)
         cnt++;
     }
   return cnt;
  }
//+------------------------------------------------------------------+

  • Status-Berechnung

CalcState vergleicht die Anzahl der Trades mit dem Tageslimit und dem gelben Schwellenwert und gibt dabei STATE_ALLOWED, STATE_CAUTION oder STATE_LIMIT zurück. Dies ermöglicht eine einfache, einheitliche Zuordnung von einer numerischen Zählung zu einem eindeutigen Handelsstatus.

//+------------------------------------------------------------------+
//| Map the trade count to a state (allowed/caution/limit)           |
//+------------------------------------------------------------------+
ENUM_DTL_STATE CalcState(int trades,int limit,int amber)
  {
   if(trades>=limit)
      return STATE_LIMIT;
   if(limit-trades<=amber)
      return STATE_CAUTION;
   return STATE_ALLOWED;
  }
//+------------------------------------------------------------------+

  • Cache-Verwaltung

Die Bibliothek verwaltet interne Cache-Variablen für den Zeitpunkt der letzten Aktualisierung, den Tagesbeginn, die heutigen Trades und den aktuellen Status. Refresh hält ein Mindestintervall von einer Sekunde ein, um häufige Durchläufe der Historie zu vermeiden; ist der Cache veraltet, delegiert „Refresh“ die Aufgabe an „ForceRefresh“, das eine vollständige Neuauszählung durchführt und alle zwischengespeicherten Werte aktualisiert. ForceRefresh berechnet den Tagesbeginn, die heutigen Trades sowie den Status neu und speichert den Zeitstempel der Aktualisierung. Dieses Caching sorgt für ein ausgewogenes Verhältnis zwischen Reaktionsgeschwindigkeit und Leistung.

//+------------------------------------------------------------------+
//| Cached refresh – only performs a full update every 1 second      |
//+------------------------------------------------------------------+
bool Refresh()
  {
   datetime now=TimeCurrent();
   if(now-s_lastRefresh<REFRESH_INTERVAL)
      return false;
   return ForceRefresh();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Force a full recount and update all cache values                 |
//+------------------------------------------------------------------+
bool ForceRefresh()
  {
   datetime now=TimeCurrent();
   int limit=GetParamLimit();
   string st=GetParamStartTime();
   int amber=GetParamAmber();

   datetime newDayStart=GetDayStart(now,st);
   int newTrades=0;

   if(newDayStart!=s_dayStart)
     {
      s_dayStart=newDayStart;
      newTrades=CountTrades(s_dayStart);
     }
   else
     {
      newTrades=CountTrades(s_dayStart);
     }

   ENUM_DTL_STATE newState=CalcState(newTrades,limit,amber);
   bool changed=(newState!=s_state) || (newTrades!=s_tradesToday);

   s_tradesToday=newTrades;
   s_state=newState;
   s_lastRefresh=now;

   return changed;
  }
//+------------------------------------------------------------------+

  • Globale Parametereinstellung

SetParameters speichert die vom Benutzer eingegebenen Werte in den globalen Variablen des Terminals (DTL_LIMIT, DTL_START_TIME, DTL_AMBER) und stellt so sicher, dass alle Komponenten aus derselben Quelle lesen und sich einheitlich verhalten. Außerdem wird der Cache zurückgesetzt, um beim nächsten Zugriff eine Neuberechnung zu erzwingen.

//+------------------------------------------------------------------+
//| Store configuration into terminal globals                        |
//+------------------------------------------------------------------+
bool SetParameters(int limit,string startTime,int amber)
  {
   if(!GlobalVariableSet("DTL_LIMIT",limit))
      return false;
   int t=TimeToInt(startTime);
   if(!GlobalVariableSet("DTL_START_TIME",t))
      return false;
   if(!GlobalVariableSet("DTL_AMBER",amber))
      return false;
   s_lastRefresh=0; // force refresh next time
   return true;
  }
//+------------------------------------------------------------------+

B) DailyTradeLimitDashboard.mq5 – Das visuelle Dashboard

  • Initialisierung

Das Dashboard beginnt damit, die gemeinsam genutzte Bibliothek einzubinden und Eingabefelder bereitzustellen, die den Standardeinstellungen der Bibliothek entsprechen. Bei der Initialisierung ruft es `DTL::SetParameters` auf, um die Benutzereingaben in die globalen Variablen des Terminals zu übertragen, erstellt das visuelle Bedienfeld, führt eine erste Aktualisierung durch, um den Cache zu füllen, und startet einen 1-Sekunden-Timer, um fortlaufende Aktualisierungen zu steuern.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Propagate user inputs into globals
   DTL::SetParameters(InpDailyTradeLimit,InpDayStartTime,InpAmberRemainingTrades);

   //--- Build UI and prime data
   CreatePanel();
   DTL::Refresh();
   prevState=DTL::GetState();

   //--- Start timer for updates
   EventSetTimer(1);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

  • CreatePanel()

CreatePanel erstellt ein kompaktes, in sich geschlossenes Panel im Chart. Zunächst werden alle vorhandenen Objekte entfernt, die das Präfix des Panels tragen, um Unübersichtlichkeit zu vermeiden; anschließend werden ein Hintergrundrechteck und vier Beschriftungen gezeichnet: TITLE, L1, L2 und L3. Das Layout des Panels ist übersichtlich und gut lesbar gestaltet, wobei die Farben so gewählt wurden, dass sie den Handelsstatus widerspiegeln.

//+------------------------------------------------------------------+
//| Create the entire dashboard panel                                |
//+------------------------------------------------------------------+
void CreatePanel()
  {
   DeleteByPrefix(PREFIX);

   ObjectCreate(0,PREFIX+"BG",OBJ_RECTANGLE_LABEL,0,0,0);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_CORNER,InpCorner);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_XDISTANCE,InpX);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_YDISTANCE,InpY);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_XSIZE,InpWidth);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_YSIZE,InpHeight);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_BACK,false);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_SELECTABLE,false);

   string labels[4]={"TITLE","L1","L2","L3"};
   int y[4]={8,32,52,78};

   for(int i=0; i<4; i++)
     {
      ObjectCreate(0,PREFIX+labels[i],OBJ_LABEL,0,0,0);
      ObjectSetInteger(0,PREFIX+labels[i],OBJPROP_CORNER,InpCorner);
      ObjectSetInteger(0,PREFIX+labels[i],OBJPROP_XDISTANCE,InpX+10);
      ObjectSetInteger(0,PREFIX+labels[i],OBJPROP_YDISTANCE,InpY+y[i]);
      ObjectSetInteger(0,PREFIX+labels[i],OBJPROP_COLOR,clrWhite);
      ObjectSetInteger(0,PREFIX+labels[i],OBJPROP_SELECTABLE,false);
     }

   ObjectSetInteger(0,PREFIX+"TITLE",OBJPROP_FONTSIZE,11);
   ObjectSetString(0,PREFIX+"TITLE",OBJPROP_TEXT,"DAILY TRADE LIMIT");

   ObjectSetInteger(0,PREFIX+"L1",OBJPROP_FONTSIZE,10);
   ObjectSetInteger(0,PREFIX+"L2",OBJPROP_FONTSIZE,10);
   ObjectSetInteger(0,PREFIX+"L3",OBJPROP_FONTSIZE,10);
  }
//+------------------------------------------------------------------+

  • UpdatePanel()

UpdatePanel liest mithilfe von TradesToday, GetParamLimit, Remaining und GetState Echtzeitdaten aus der Bibliothek ein und aktualisiert die Textzeilen des Panels entsprechend. Außerdem wird die Hintergrundfarbe je nach Status auf Grün (STATE_ALLOWED), Orange (STATE_CAUTION) bzw. Kastanienbraun (STATE_LIMIT) angepasst, wodurch der aktuelle Status auf einen Blick erkennbar ist.

//+------------------------------------------------------------------+
//| Refresh the panel's content with the latest state                |
//+------------------------------------------------------------------+
void UpdatePanel()
  {
   int trades=DTL::TradesToday();
   int limit=DTL::GetParamLimit();
   int remaining=DTL::Remaining();
   DTL::ENUM_DTL_STATE state=DTL::GetState();

   ObjectSetString(0,PREFIX+"L1",OBJPROP_TEXT,
                   StringFormat("Trades today: %d / %d",trades,limit));
   ObjectSetString(0,PREFIX+"L2",OBJPROP_TEXT,
                   StringFormat("Remaining today: %d",remaining));
   ObjectSetString(0,PREFIX+"L3",OBJPROP_TEXT,
                   StringFormat("STATUS: %s",DTL::StateToString(state)));

   color bg=clrNONE;
   switch(state)
     {
      case DTL::STATE_ALLOWED:
         bg=clrDarkGreen;
         break;
      case DTL::STATE_CAUTION:
         bg=clrOrange;
         break;
      case DTL::STATE_LIMIT:
         bg=clrMaroon;
         break;
     }
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_BGCOLOR,bg);
  }
//+------------------------------------------------------------------+

  • Umgang mit Timern (OnTimer)

OnTimer fungiert als Herzstück des Dashboards. Es ruft zunächst Refresh auf, um den internen Zustand zu aktualisieren, und anschließend UpdatePanel, um die Anzeige zu aktualisieren. Wenn sich der Status geändert hat und Benachrichtigungen aktiviert sind, werden eine Terminal-Benachrichtigung sowie eine Push-Benachrichtigung (sofern aktiviert) ausgegeben und optional ein Ton abgespielt, um auf die Änderung aufmerksam zu machen.

//+------------------------------------------------------------------+
//| Timer event – updates the panel and triggers alerts              |
//+------------------------------------------------------------------+
void OnTimer()
  {
   bool changed=DTL::Refresh();
   UpdatePanel();

   if(changed && InpShowAlerts)
     {
      string msg;
      switch(DTL::GetState())
        {
         case DTL::STATE_ALLOWED:
            msg="Trading allowed: within daily trade limit.";
            break;
         case DTL::STATE_CAUTION:
            msg="Caution: approaching daily trade limit.";
            break;
         case DTL::STATE_LIMIT:
            msg="Trading limit reached: no trades remaining today.";
            break;
        }
      Alert(msg);
      if(InpUsePushNotifications)
         SendNotification(msg);
      if(InpPlaySound)
         PlaySound(InpSoundFile);
     }
  }
//+------------------------------------------------------------------+

C) DailyTradeLimitEnforcer.mq5 – Der Enforcement-EA

  • Initialisierung

Der Enforcer enthält die gemeinsam genutzte Bibliothek und ruft mithilfe von ForceRefresh den aktuellen Status ab. Ein kleines internes Flag (s_limitJustReached) hilft dabei, den Übergang in den Grenzzustand zu erkennen, sodass die Bereinigung genau dann erfolgen kann, wenn die Grenze aktiv wird. Es wird ein 1-Sekunden-Timer gestartet, um den Status regelmäßig erneut zu überprüfen und eine Bereinigung durchzuführen, falls der Grenzwert gerade erreicht wurde.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   Print("DailyTradeLimitEnforcer started. AutoTrading must be enabled.");
   EventSetTimer(1);
   DTL::ForceRefresh(); // initial read
   s_limitJustReached=(DTL::GetState()==DTL::STATE_LIMIT);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
   Print("DailyTradeLimitEnforcer stopped.");
  }
//+------------------------------------------------------------------+

  • Timer-Routine (OnTimer)

OnTimer ruft ForceRefresh auf, um sicherzustellen, dass es über aktuelle Informationen verfügt. Wenn der aktuelle Zustand STATE_LIMIT ist und das Limit gerade erreicht wurde (Übergang durch das Flag erkannt), werden alle ausstehenden Orders über CancelAllPendingOrders storniert, wodurch verhindert wird, dass aus Orders, die vor Inkrafttreten des Limits platziert wurden, neue Trades entstehen. Das Flag wird aktualisiert, um den aktuellen Übergangsstatus widerzuspiegeln.

//+------------------------------------------------------------------+
//| Timer – checks for limit state and cancels pending orders        |
//+------------------------------------------------------------------+
void OnTimer()
  {
   DTL::ForceRefresh();
   bool nowLimited=(DTL::GetState()==DTL::STATE_LIMIT);

   //--- If we just entered the limit, cancel all pending orders
   if(nowLimited && !s_limitJustReached)
     {
      Print("Daily limit reached – cancelling all pending orders.");
      CancelAllPendingOrders();
     }

   s_limitJustReached=nowLimited;
  }
//+------------------------------------------------------------------+

  • Reaktion auf eine Handelstransaktion (OnTradeTransaction)

OnTradeTransaction reagiert sofort auf neue Aktivitäten. Wenn nach dem Erzwingen einer Aktualisierung der Handel weiterhin zulässig ist, kehrt die Funktion ohne weitere Aktion zurück. Wird das Limit erreicht, werden zwei spezielle Fälle behandelt:

  1. Eine neu hinzugefügte ausstehende Order: Der Enforcer storniert sie sofort, um zu verhindern, dass daraus ein Handel wird.
  2. Ein neu eröffneter Trade: Dabei wird die zugehörige Position ermittelt und mit einer Marktorder in die entgegengesetzte Richtung geschlossen.

Dieser Ansatz verhindert ein zusätzliches Risiko, ohne die Positionen zu beeinträchtigen, die vor Erreichen des Limits eröffnet wurden.

//+------------------------------------------------------------------+
//| Trade transaction handler – blocks any disallowed trade          |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
   //--- Ensure we have latest state
   DTL::ForceRefresh();

   //--- If still allowed, nothing to do
   if(DTL::IsTradingAllowed())
      return;

   //--- Case: new pending order added
   if(trans.type==TRADE_TRANSACTION_ORDER_ADD)
     {
      ulong orderTicket=trans.order;
      if(orderTicket==0)
         return;

      if(!OrderSelect(orderTicket))
        {
         Print("Failed to select order ",orderTicket);
         return;
        }

      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
         return; // market order – will be handled if filled

      MqlTradeRequest req={};
      MqlTradeResult  res={};
      req.action   =TRADE_ACTION_REMOVE;
      req.order    =orderTicket;
      req.comment  ="Daily limit reached – order cancelled immediately";

      if(OrderSend(req,res))
         Print("Immediately cancelled pending order ",orderTicket);
      else
         Print("Immediate cancel failed for order ",orderTicket,": ",res.comment);
     }

   //--- Case: new deal added (opening trade)
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
     {
      ulong dealTicket=trans.deal;
      if(dealTicket==0)
         return;

      if(HistoryDealGetInteger(dealTicket,DEAL_ENTRY)!=DEAL_ENTRY_IN)
         return;

      ulong posTicket=HistoryDealGetInteger(dealTicket,DEAL_POSITION_ID);
      if(posTicket==0)
         return;

      if(!PositionSelectByTicket(posTicket))
        {
         Print("Position ",posTicket," no longer open.");
         return;
        }

      //--- Close newly opened position
      MqlTradeRequest req={};
      MqlTradeResult  res={};
      req.action    =TRADE_ACTION_DEAL;
      req.position  =posTicket;
      req.symbol    =PositionGetString(POSITION_SYMBOL);
      req.volume    =PositionGetDouble(POSITION_VOLUME);
      req.deviation =5;
      req.comment   ="Daily limit reached – new position closed immediately";

      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
        {
         req.price=SymbolInfoDouble(req.symbol,SYMBOL_BID);
         req.type=ORDER_TYPE_SELL;
        }
      else
        {
         req.price=SymbolInfoDouble(req.symbol,SYMBOL_ASK);
         req.type=ORDER_TYPE_BUY;
        }

      if(OrderSend(req,res))
         Print("Immediately closed new position ",posTicket);
      else
         Print("Immediate close failed for position ",posTicket,": ",res.comment);
     }
  }
//+------------------------------------------------------------------+

  • Hilfsfunktion (CancelAllPendingOrders)

CancelAllPendingOrders durchläuft alle Orders und entfernt dabei die ausstehenden (Nicht-Markt-)Orders. Marktorders werden ignoriert, da sie sich durch die normale Kursentwicklung auswirken und nicht als vorab platzierte Aufträge behandelt werden.

//+------------------------------------------------------------------+
//| Cancel all pending orders (market orders are ignored)            |
//+------------------------------------------------------------------+
void CancelAllPendingOrders()
  {
   for(int i=OrdersTotal()-1; i>=0; i--)
     {
      ulong ticket=OrderGetTicket(i);
      if(ticket==0)
         continue;

      if(!OrderSelect(ticket))
        {
         Print("Failed to select order ",ticket);
         continue;
        }

      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
         continue; // market order – ignore

      MqlTradeRequest req={};
      MqlTradeResult  res={};
      req.action   =TRADE_ACTION_REMOVE;
      req.order    =ticket;
      req.comment  ="Daily limit reached – order cancelled";

      if(OrderSend(req,res))
         Print("Cancelled pending order ",ticket);
      else
         Print("Failed to cancel order ",ticket,": ",res.comment);
     }
  }
//+------------------------------------------------------------------+

Nachdem nun alle drei Komponenten vollständig implementiert sind – eine gemeinsam genutzte Bibliothek mit der zentralen Disziplin- bzw. Kontrolllogik, ein visuelles Dashboard zur Echtzeit-Überwachung und ein eigenständiger EA, der weitere Handelsgeschäfte aktiv blockiert, sobald das Limit erreicht ist –, ist das System fertiggestellt. Die Kommunikation über globale Terminalvariablen stellt sicher, dass jede Komponente mit derselben Konfiguration und der aktuellsten Handelsstatistik arbeitet.

Der modulare Aufbau bietet Flexibilität: Das Dashboard und der Enforcer können gemeinsam für umfassenden Schutz genutzt werden, oder die Bibliothek kann zur freiwilligen Einhaltung in benutzerdefinierte EAs integriert werden, während der Enforcer ein zuverlässiges Sicherheitsnetz bietet.

Erstellen der Dateien in MetaEditor

Um das System auf Ihrer eigenen Plattform zu implementieren, führen Sie die folgenden Schritte aus, um die drei erforderlichen Dateien im MetaEditor zu erstellen:

Erstellen Sie die gemeinsam genutzte Bibliothek (.mqh)

  • Klicken Sie im MetaEditor auf „Neu“ → wählen Sie „Datei einbinden“ (.mqh).
  • Benennen Sie die Datei „DailyTradeLimit.mqh“ und speichern Sie sie im Ordner „MQL5\Include\“.
  • Kopieren Sie den vollständigen Code für die gemeinsam genutzte Bibliothek, fügen Sie ihn in diese Datei ein und speichern Sie sie. (Include-Dateien werden von anderen Programmen referenziert und müssen nicht separat kompiliert werden.)

Erstellen Sie den Dashboard-Indikator (.mq5)

  • Klicken Sie auf „Neu“ → wählen Sie „Benutzerdefinierter Indikator“ aus.
  • Benennen Sie die Datei „DailyTradeLimitDashboard.mq5“ und speichern Sie sie im Ordner „MQL5\Indicators\“.
  • Übernehmen Sie im Assistenten die Standardeinstellungen (es ist nicht erforderlich, Indikatorpuffer manuell hinzuzufügen – der Code kümmert sich bereits darum).
  • Ersetzen Sie den generierten Code durch den bereitgestellten vollständigen Dashboard-Code und kompilieren Sie anschließend (Strg+F7).

Erstellen Sie den Enforcer-EA (.mq5)

  • Klicken Sie auf „Neu“ → wählen Sie „Expert Advisor“ aus.
  • Benennen Sie die Datei „DailyTradeLimitEnforcer.mq5“ und speichern Sie sie im Ordner „MQL5\Experts\“.
  • Ersetzen Sie den Vorlagencode durch den vollständigen Enforcer-EA-Code und kompilieren Sie anschließend.

Sobald alle Dateien erfolgreich erstellt wurden, können Sie das Dashboard an einen beliebigen Chart anhängen (über „Navigator“ → „Indikatoren“) und den Enforcer-EA an einen beliebigen Chart (über „Navigator“ → „Expert Advisors“). Stellen Sie sicher, dass der Algo-Handel im Terminal aktiviert ist, damit der Enforcer funktioniert. Im nächsten Abschnitt werden wir das System unter realen Marktbedingungen testen und dabei seine Genauigkeit, Reaktionsfähigkeit und vor allem seine Fähigkeit überprüfen, das tägliche Handelslimit ausnahmslos durchzusetzen.



Tests

Das gesamte System zur Begrenzung des täglichen Handelslimits – bestehend aus der Shared Library, dem visuellen Dashboard und dem Enforcer-EA – wurde in der MetaTrader-5-Umgebung unter Live-Chart-Bedingungen getestet, um seine Genauigkeit, Reaktionsgeschwindigkeit und vor allem seine Fähigkeit zu überprüfen, neue Trades aktiv zu verhindern, sobald das festgelegte Limit erreicht ist.

Die Tests konzentrierten sich auf vier Kernbereiche: die genaue Zählung der Trades über das gesamte Konto hinweg, Statuswechsel und Aktualisierungen des Dashboards in Echtzeit, die sofortige Reaktion des Enforcers auf Handelsaktivitäten nach Überschreitung des Limits sowie das korrekte Verhalten beim Übergang zwischen den Handelstagen. Jede Komponente wurde einzeln auf ihre interne Korrektheit überprüft und anschließend als Teil des vollständig integrierten Systems getestet, um eine konsistente und zuverlässige Funktionsweise unter realistischen Bedingungen sicherzustellen.

Übersicht über die Testumgebung

Das System wurde unter den folgenden Bedingungen evaluiert:

Kategorie Spezifikationen
Plattform
MetaTrader 5
Kontotyp
Demo-Konto
Ausführungstyp
Manuelle Orderplatzierung
Aktive Komponenten
1. Gemeinsam genutzte Bibliothek (DailyTradeLimit.mqh)

2. Dashboard-Indikator (DailyTradeLimitDashboard.mq5)
3. Enforcer EA (DailyTradeLimitEnforcer.mq5)

Ziel
Validierung der Zählgenauigkeit, der Zustandsübergänge und des Durchsetzungsverhaltens unter realen Chartbedingungen.

Der Test wurde bewusst mit manuell platzierten Trades durchgeführt, um zu bestätigen, dass die Durchsetzung allgemein gilt – nicht nur für automatisierte Systeme.

Phase 1 – Ausgangszustand und fortschreitender Übergang

Abbildung 3: Ausgangszustand und fortschreitender Übergang

Das erste Diagramm zeigt den Beginn der Handelssitzung.

Beobachtungen:

  • Trades today: 0 / 5
  • Remaining trades: 5
  • Status: TRADING ALLOWED
  • Farbe des Dashboards: Grün

Dies bestätigt:

  1. Der Handelstag wurde korrekt initialisiert.
  2. Der Trade-Zähler startete bei null
  3. Das System hat den Status korrekt als STATE_ALLOWED klassifiziert.
  4. Die globalen Variablen wurden korrekt gelesen und synchronisiert.

Da die Trades manuell ausgeführt wurden, durchlief das System die folgenden Phasen:

Zulässiger Zustand

Solange die Anzahl der Trades deutlich unter dem Schwellenwert blieb, wurde der Handel ohne Einschränkungen fortgesetzt.

Warnstatus

Als sich die Anzahl der verbleibenden Trades dem konfigurierten gelben Schwellenwert näherte, wechselte das System in den Status Vorsicht. Die Farbe des Dashboards änderte sich entsprechend, und der Status wurde auf „STATE_CAUTION“ aktualisiert, wodurch bereits vor der Anwendung einer Einschränkung ein deutliches visuelles Signal gegeben wurde. Dieses Verhalten bestätigt, dass das System die Annäherung an das Tageslimit korrekt erkennt, den gelben Schwellenwert genau auswertet, die Anzeige in Echtzeit aktualisiert und den Handelsstatus unmittelbar nach jedem Handelsereignis neu berechnet.

Übergang bei Erreichen des Limits

Als der fünfte Handel ausgeführt wurde, sank die verbleibende Handelsanzahl auf null, und das System wechselte in den Zustand STATE_LIMIT. Das Dashboard färbte sich sofort kastanienbraun/rot, und der Status änderte sich in „HANDELSGRENZE ERREICHT“, was deutlich signalisierte, dass die Tagesgrenze erreicht worden war. Dieser Übergang bestätigt die korrekte Zählung der Trades durch die historische Überprüfung, die korrekte Festlegung des Beginns des Handelstages, die sofortige Neuklassifizierung des Status bei Überschreitung eines Limits sowie die vollständige Synchronisation zwischen dem Dashboard und der Logik der gemeinsam genutzten Bibliothek.

Sobald das Tageslimit erreicht ist, wird der Sperrmechanismus aktiviert und verhindert, dass weitere Trades ausgeführt werden. Auch wenn möglicherweise noch weitere Handelsversuche unternommen werden, greift das System sofort ein, um diese zu stornieren oder zu schließen. Das Dashboard wird weiterhin in Echtzeit aktualisiert und zeigt die Gesamtzahl der Handelsversuche im Vergleich zum vordefinierten Schwellenwert an. Der Status bleibt jedoch rot, was deutlich anzeigt, dass der Handel gesperrt ist und die Durchsetzung aktiv erfolgt.

Phase 2 – Aktive Durchsetzung nach Limitverletzung

Das zweite Diagramm veranschaulicht die entscheidende Durchsetzungsphase.

Abbildung 4: Aktive Durchsetzung nach Limitverletzung

Trotz manueller Versuche, weitere Geschäfte zu tätigen, nachdem das Tageslimit erreicht worden war, setzte das System die Beschränkung sofort durch. Neu platzierte ausstehende Orders wurden storniert, und alle neu eröffneten Positionen wurden unverzüglich geschlossen, während Positionen, die vor der Auslösung des Limits eröffnet worden waren, davon unberührt blieben. Dies bestätigt, dass der Enforcer-EA Handelsereignisse über „OnTradeTransaction“ korrekt erkannt, den Handelsstatus vor der Ausführung einer Aktion mit „ForceRefresh()“ aktualisiert, den Übergang in den Limit-Status anhand seines internen Flags genau identifiziert und die Bereinigungslogik genau zum erforderlichen Zeitpunkt ausgeführt hat. Zwischen dem Handelsversuch und der Durchsetzung trat keine erkennbare Verzögerung auf, wodurch sichergestellt wurde, dass ein zusätzliches Risiko vermieden wurde, ohne bereits bestehende Positionen zu beeinträchtigen.

Der Test bestätigt, dass alle Komponenten als einheitliches System nahtlos zusammenarbeiteten. Die gemeinsam genutzte Bibliothek zählte die Trades für das gesamte Konto präzise und stellte sicher, dass kein Symbol und keine manuelle Ausführung bei der Zählung unberücksichtigt blieb. Die Anzeige auf dem Dashboard zeigte Statusänderungen in Echtzeit an und wurde sofort aktualisiert, sobald Handelsaktivitäten stattfanden. Der Enforcer EA reagierte sofort und konsistent, sobald die Limitbedingung erfüllt war, und zeigte damit ein zuverlässiges Durchsetzungsverhalten. Die globalen Terminal-Variablen sorgten für eine erfolgreiche Synchronisierung der Konfiguration über alle Komponenten hinweg und gewährleisten so eine einheitliche, maßgebliche Datenquelle. Wichtig ist, dass manuelle Handelsversuche die Durchsetzungslogik nicht umgehen konnten und das System sich auch nach mehreren Versuchen, das Tageslimit zu überschreiten, weiterhin konsistent verhielt.

Aus dieser Live-Demo-Evaluierung lassen sich mehrere wichtige Schlussfolgerungen ziehen. Die Handelszählung bleibt über alle Symbole und Ausführungsarten hinweg genau. Unmittelbar nach Handelsereignissen finden Zustandsübergänge statt, was die korrekte Neuberechnungslogik bestätigt. Die Vorsichtsstufe fungiert praktisch als Frühwarnmechanismus, bevor Beschränkungen in Kraft treten. Sobald das Tageslimit erreicht ist, erfolgt die Durchsetzung automatisch und bedingungslos. Während der Tests wurden keine Race-Conditions oder zeitlichen Unstimmigkeiten festgestellt, und insgesamt zeigte das System unter Live-Chart-Bedingungen eine stabile und zuverlässige Leistung.


Schlussfolgerung

In diesem Artikel wurde ein tägliches Handelslimit als eine Reihe technischer Spezifikationen formalisiert und als Kontrollmechanismus auf Kontoebene in MT5 implementiert. Das Ziel bestand nicht darin, Warnmeldungen anzuzeigen, sondern sicherzustellen, dass eine vordefinierte Höchstzahl von Einstiegen pro Handelstag unter klar definierten Bedingungen nicht überschritten werden kann.

Das fertige Paket besteht aus drei aufeinander abgestimmten Komponenten:

  • DailyTradeLimit.mqh – eine gemeinsam genutzte Bibliothek, die die Sitzungszeitmessung, die Zählung von DEAL_ENTRY_IN und die Zustandsverwaltung zentralisiert.
  • DailyTradeLimitDashboard.mq5 – eine Konfigurations- und Überwachungsebene, die eine Visualisierung des Status in Echtzeit sowie Warnmeldungen bietet.
  • DailyTradeLimitEnforcer.mq5 – ein eigenständiger Expert Advisor, der für Eingriffe und die Durchsetzung zuständig ist.

Das System zählt die bestätigten DEAL_ENTRY_IN-Deals pro Handelstag anhand einer konfigurierbaren Sitzungsstartzeit. Der Geltungsbereich umfasst das gesamte Handelskonto, über alle Symbole hinweg, einschließlich manueller Trades und Expert Advisors von Drittanbietern. Sobald der festgelegte Schwellenwert erreicht ist, werden neu platzierte ausstehende Orders gelöscht und alle Positionen, die nach Erreichen dieses Schwellenwerts eröffnet wurden, sofort geschlossen. Positionen, die vor Erreichen des Limits eröffnet wurden, bleiben davon unberührt.

Die Validierung erfolgte anhand strukturierter Live-Testszenarien: symbolübergreifende Ausführung, gleichzeitige EA-Aktivitäten, wiederholte manuelle Einstiegsversuche nach Erreichen des Limits sowie Überprüfung des Sitzungs-Resets. In jedem Fall blieb die Zählung der Transaktionen konsistent, die Zustandsübergänge erfolgten sofort, und das Limit konnte nicht überschritten werden.

Das Endergebnis ist eine klar definierte und überprüfbare Invariante: Bei der vorgegebenen Definition eines Einstiegs und eines Handelstages werden auf dem Konto nicht mehr als die konfigurierte Anzahl täglicher Einstiegsgeschäfte ausgeführt.

Spätere Erweiterungen können zusätzliche Einschränkungen wie Gewinnziele oder Drawdown-Obergrenzen beinhalten, wobei derselbe hier festgelegte, spezifikationsgesteuerte und modulare Entwurfsansatz verfolgt wird.

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

Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Einführung in MQL5 (Teil 41): Leitfaden für Anfänger zur Dateiverwaltung in MQL5 (III) Einführung in MQL5 (Teil 41): Leitfaden für Anfänger zur Dateiverwaltung in MQL5 (III)
Erfahren Sie, wie Sie eine CSV-Datei in MQL5 einlesen und deren Handelsdaten in dynamischen Arrays organisieren. Dieser Artikel zeigt Schritt für Schritt, wie man Dateielemente zählt, alle Daten in einem einzigen Array speichert und jede Spalte in eigene Arrays aufteilt, wodurch die Grundlage für fortgeschrittene Analysen und die Visualisierung der Handelsperformanz geschaffen wird.
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.
Statistische Arbitrage anhand kointegrierter Aktien (Abschluss): Datenanalyse mit einer spezialisierten Datenbank Statistische Arbitrage anhand kointegrierter Aktien (Abschluss): Datenanalyse mit einer spezialisierten Datenbank
Der Artikel zeigt, wie man SQLite (OLTP) mit DuckDB (OLAP) für die Datenverarbeitung im Bereich der statistischen Arbitrage kombinieren kann. Die spaltenorientierte Engine von DuckDB, ASOF JOIN und die Array-Funktionen beschleunigen Kernaufgaben wie die zeitliche Zuordnung von Kursen zu Trades sowie RWEC, wobei bei größeren Eingabedaten im Vergleich zu SQLite Geschwindigkeitssteigerungen von 2- bis 23-fach gemessen wurden. Das ermöglicht einfachere Abfragen und schnellere Analysen, während die Handelsausführung weiterhin in SQLite erfolgt.