English Русский Deutsch 日本語 한국어 Italiano Türkçe
preview
Architecture événementielle en MQL5 : Comment transformer un Expert Advisor en un système de trading à part entière

Architecture événementielle en MQL5 : Comment transformer un Expert Advisor en un système de trading à part entière

MetaTrader 5Exemples |
52 6
MetaQuotes
MetaQuotes

Introduction

Lors du développement d'un Expert Advisor en MQL5, beaucoup de personnes privilégient la solution la plus évidente : elles placent toute la logique dans la méthode OnTick. Il est effectivement plus facile de commencer de cette manière. Cependant, cette approche a un coût caché. À mesure que le projet se développe, les règles de trading, la vérification des conditions, le traitement des ordres, la mise à jour des données, l'interface, les calculs et la journalisation sont tous combinés dans un seul gestionnaire. De ce fait, le code évolue jusqu'à atteindre un état où il n'est plus programmable et où il ne tient plus que par hasard. Tout changement à un endroit commence à affecter des parties complètement différentes du système. Vous modifiez le panneau visuel – et soudain, la logique de trading cesse de fonctionner. Vous modifiez le filtre d'entrée - une erreur apparaît lors de la vérification en arrière-plan. Un tel Expert Advisor se transforme rapidement en un monolithe fragile, où la complexité grandit plus vite que la confiance du développeur.

MetaTrader 5 est bien plus qu'un simple flux de cotations. Son fonctionnement repose sur des événements. Le terminal reçoit continuellement des ticks, des signaux de minuterie, des actions des utilisateurs, des changements d'état de trading et des événements de profondeur de marché. Ces messages doivent être traités séparément. Pour ce faire, MQL5 propose différents gestionnaires, chacun ayant son propre domaine de responsabilité. OnTick est responsable des mises à jour du marché. OnTimer — pour les tâches périodiques et en arrière-plan. OnChartEvent — pour la réaction à l'interface graphique et aux actions de l'utilisateur. Lorsque la logique est répartie en fonction de sa finalité, le code cesse d'être encombré. Cela commence à ressembler à un système d'ingénierie bien structuré, où chaque module remplit sa fonction sans interférer avec celle de ses voisins.

Cette conception est particulièrement importante lorsque l'Expert Advisor dépasse le cadre d'un seul symbole et commence à exécuter plusieurs fonctions simultanément. Nous devons surveiller le marché, maintenir l’interface, répondre aux appuis sur les boutons, synchroniser l'état interne, transmettre des signaux entre les composants et parfois même gérer plusieurs instruments. À ce stade, l'architecture événementielle cesse de ressembler à une belle théorie. Cela devient une nécessité pratique. Si nous déplaçons le travail en arrière-plan vers OnTimer, et que la réponse aux actions de l'utilisateur est déplacée vers OnChartEvent, le circuit de trading principal est libéré de toute charge inutile. Cela rend le comportement du système plus prévisible et simplifie considérablement la maintenance.

Les événements et services personnalisés jouent un rôle distinct dans une telle architecture. Les événements personnalisés nous permettent d'organiser un bus de messages interne entre les modules, et les services nous permettent de déplacer la logique auxiliaire en dehors d'un graphique spécifique. Il ne s'agit plus seulement d'un Expert Advisor, mais d'un système de composants capables d'échanger des commandes et des signaux sans tout mélanger en une seule fonction. Cela permet de concevoir des solutions au niveau des applications de trading avec des panneaux de contrôle, des analyses en arrière-plan, des échanges inter-modules et une séparation claire des rôles.

De plus, l'approche événementielle améliore considérablement la testabilité. Lorsque la logique est répartie entre plusieurs gestionnaires, il est plus facile de la tester par parties. Nous pouvons analyser séparément la réponse au minuteur, les événements d’interface utilisateur et les transactions de trading. C'est particulièrement important dans les tâches où une erreur peut coûter du temps et de l'argent. Plus la structure est solide, plus il est facile de contrôler le comportement du système et de trouver rapidement les causes des défaillances.

Dans cet article, nous verrons comment passer du modèle « tout dans OnTick » à une architecture événementielle plus mature. Examinons le rôle des gestionnaires prédéfinis et des événements personnalisés, ainsi que celui des services qui ne sont pas liés à un graphique. Examinons de plus près les erreurs typiques qui compromettent l'architecture avant même que le travail ne commence réellement. L'idée principale est simple : lorsque MQL5 est utilisé conformément à sa finalité, il nous permet de construire non seulement des robots de trading, mais aussi de véritables systèmes applicatifs.

Événements MQL5



Événements prédéfinis

Les événements prédéfinis en MQL5 constituent le cadre sur lequel repose toute la logique du programme. Il ne s'agit pas simplement d'un ensemble de fonctions de gestion, mais d'un modèle de réponse aux changements de l'environnement strictement défini. Et plus tôt le développeur cessera de les percevoir comme un ajout à OnTick, plus vite le code commencera à acquérir une architecture.

Le cycle de vie de tout programme commence par OnInit et se termine par OnDeinit. Ce sont en quelque sorte des points d'entrée et de sortie. OnInit pose les bases : les minuteurs sont définis via EventSetTimer, les objets graphiques sont créés, les structures internes et les composants externes sont initialisés. Il est également opportun de mettre en place des mécanismes de service et de préparer l'environnement ici.

int OnInit()
  {
   EventSetTimer(30); // poll every 30 seconds
   return(INIT_SUCCEEDED);
  }

OnDeinit, à son tour, requiert de la discipline. Tout ce qui a été créé doit être libéré correctement : les objets sont supprimés, le minuteur est désactivé via EventKillTimer, les ressources sont fermées. Ignorer cette symétrie est une erreur classique qui provoque la pollution de l'environnement d'exécution et des dysfonctionnements subtils.

void OnDeinit(const int reason)
  {
   EventKillTimer(); // disable the timer when unloading
  }

OnTick est traditionnellement considéré comme le cœur de l'EA. Ce n’est que partiellement vrai. Son objectif est de réagir aux fluctuations du marché d'un symbole spécifique. C'est ici que vous calculez les signaux, vérifiez les conditions d'entrée et gérez les positions. Toutefois, le point fondamental est que la présence de OnTick n'est pas obligatoire. Si la logique est déplacée vers d'autres événements, l'Expert Advisor peut fonctionner sans elle. De plus, dans une architecture mature, OnTick devient souvent un routeur léger plutôt que le centre de l'univers.

OnTimer est un outil que beaucoup sous-estiment. Le terminal génère ces événements à un intervalle spécifié, ce qui permet de séparer les tâches d'arrière-plan des tâches liées au marché. Collecte de statistiques, mise à jour des caches, interrogation de plusieurs symboles, recalcul des indicateurs — tout cela s'intègre logiquement dans le minuteur. Cette approche soulage OnTick et stabilise le comportement du système, notamment lors des périodes de forte volatilité. N'oubliez pas une règle simple mais importante : le minuteur doit non seulement être défini dans OnInit, mais aussi nécessairement supprimé dans OnDeinit.

OnChartEvent ouvre la porte au monde des applications interactives. Il s'agit d'un gestionnaire d'événements d'interface graphique : clics de souris, frappes au clavier, interactions avec des objets et, surtout, événements personnalisés. Les panneaux de contrôle, les boutons et les sélecteurs de mode sont mis en œuvre par son intermédiaire. C'est ici que la réponse aux signaux externes provenant d'autres programmes est mise en œuvre. Dans le contexte d'une architecture événementielle, il ne s'agit plus d'un simple gestionnaire d'interface utilisateur, mais d'un canal de communication à part entière.

OnTradeTransaction est un point de contrôle du statut de trading. Contrairement aux approches simplifiées où seul le résultat d'une opération est vérifié, la structure complète de MqlTradeTransaction est disponible ici. Cela permet un suivi précis du cycle de vie des ordres : de l’envoi à l’exécution et aux modifications ultérieures. Ce niveau de détail est particulièrement important dans les scénarios complexes de gestion de position, où non seulement l'objectif mais aussi le chemin pour y parvenir sont importants.

OnBookEvent est moins fréquemment utilisé, mais il est irremplaçable dans certaines tâches. Il réagit aux changements de profondeur du marché et est activé via MarketBookAdd. Nous en sommes déjà au territoire de stratégies plus subtiles, où non seulement le prix est important, mais aussi la structure de liquidité. Dans les approches à haute fréquence ou lors de l'analyse des mouvements de micro-marché, ce gestionnaire devient une source clé de signaux.

L'essentiel à retenir est simple : chaque type de programme en MQL5 reçoit son propre ensemble d'événements, et c'est grâce à eux que son comportement se définit. Tenter d'ignorer ce modèle et de tout réduire à un seul gestionnaire conduit inévitablement à la complexité. Au contraire, une répartition adéquate de la logique entre les événements crée un système clair, prévisible et évolutif — le fondement même sans lequel il est impossible d'aller de l'avant.



Événements personnalisés

Les événements personnalisés en MQL5 sont l'un des outils les plus pratiques pour construire une architecture événementielle. Grâce à leur aide, le programme n'est plus autonome. Il acquiert la capacité de transmettre des signaux entre ses parties aussi naturellement que les nœuds d'un même système échangent des messages de service. Ceci est réalisé par la fonction EventChartCustom, qui nous permet d'envoyer par programmation notre propre événement à un graphique spécifié et de le gérer ensuite dans OnChartEvent. Il s'agit d'un bus de messagerie interne. Il est particulièrement précieux lorsqu'un seul programme doit répondre au marché, coordonner le fonctionnement de l'interface, des modules de service et de la logique de trading.

Le principal avantage de cette approche est qu'elle permet de répartir les responsabilités entre les modules. Un élément peut être dédié à l'analyse de marché. L'autre – l'affichage de l'état du panneau. Le troisième – l’exécution des opérations de trading. Le quatrième assure les calculs en arrière-plan. Si nous essayons de relier tout cela par des appels directs et des variables globales partagées, le code commencera rapidement à se désagréger. La maintenance s'apparentera à la réparation d'un mécanisme où chaque engrenage est lié à tous les autres. Les événements personnalisés évitent cette confusion : le module n’interfère pas avec la logique interne d’un tiers, mais envoie simplement un signal clair à l’endroit où il doit être reçu.

Il convient de noter séparément la capacité de transmission d'un événement ciblé. Dans EventChartCustom, nous pouvons spécifier non seulement le graphique actuel, mais aussi l'ID du graphique d'une autre fenêtre. Cela ouvre la voie à la communication entre différentes instances d'Expert Advisor, d'indicateurs et de services. Ce mécanisme est particulièrement utile dans les systèmes multi-symboles et multi-graphiques. Un composant collecte ou génère des informations, et l'autre les reçoit et effectue une action. Dans ce cas, l'événement personnalisé devient le lien qui unit des éléments disparates en une architecture unique.

De plus, l'idée même d'échange d'événements est particulièrement évidente au stade de l'initialisation. Parfois, nous devons surcharger OnInit avec des actions préparatoires : création d’objets internes, chargement de données, création de caches, configuration de l’interface, lancement de services. Si tout cela est fait immédiatement lors de l'initialisation, la méthode devient lourde et commence à ralentir le démarrage du programme. Et si l'initialisation prend trop de temps, il y a un risque de dépasser la limite de temps ou d'obtenir un démarrage lent et laborieux. Il est beaucoup plus logique de ne conserver que le minimum de préparation dans OnInit et de créer immédiatement un événement personnalisé qui déplacera la partie nécessitant beaucoup de travail vers un gestionnaire séparé. Le programme démarre alors rapidement et la préparation principale se déroule en mode événementiel.

Ceci est également important car, en MQL5, le fonctionnement à long terme d'une fonction bloque la gestion d'autres événements. L'appel successif de plusieurs petites fonctions ne résout pas le problème : tant que la chaîne n'est pas complète, rien ne peut intervenir dans le déroulement des événements. Cela est particulièrement visible dans les programmes dotés d'une interface utilisateur. L'utilisateur interagit avec le panneau et attend une réponse, mais le programme ne répond pas car il exécute un long script. On a l'impression d'un blocage, alors qu'en réalité le système n'a tout simplement pas le temps de transférer le contrôle au gestionnaire d'événements. C’est précisément pourquoi le modèle événementiel est architecturalement nécessaire ici. Il permet de déléguer l'orchestration à un gestionnaire d'événements qui exécute les étapes séquentiellement sans bloquer l'interface ni rendre le programme non réactif.

Cette flexibilité a un inconvénient : les événements personnalisés ne peuvent pas être envoyés sans système. Il faut définir un protocole clair : à quoi correspond chaque identifiant, où le code du message est transmis et comment vérifier qu’un événement relève bien de l’application. En règle générale, des plages d’identifiants personnalisées sont définies à cet effet ; utilisez des étiquettes claires dans les paramètres de chaîne et assurez-vous de vérifier que l'événement est bien destiné à ce module. Autrement, nous pourrions facilement recevoir un signal sans rapport qui perturberait la logique et l'architecture.

Il existe une autre règle souvent sous-estimée : ne créez pas d’événements trop souvent, sauf en cas de réel besoin. La file d'attente des événements du terminal n'apprécie pas les bruits inutiles. Si les événements sont envoyés trop fréquemment, la file d'attente des messages est surchargée et le traitement du signal se dégrade. Il est préférable de ne transmettre que les états significatifs : la disponibilité des données, l’apparition d’un signal, une commande de mise à jour de l’interface, la confirmation d’exécution d’une action. Les événements fonctionneront alors comme de courts messages structurés, plutôt que comme un flux chaotique de télégrammes.

C’est pourquoi les événements personnalisés sont particulièrement précieux dans un système mature. Ils connectent les modules sans couplage rigide, permettent de construire des protocoles d'interaction clairs et transforment MQL5 en un environnement pour un développement événementiel à part entière.



Services

En MQL5, un service est un service terminal exécuté en arrière-plan. Pas de graphique, pas de liaison de symboles, pas d'attente de ticks. Après son lancement, il fonctionne tout seul, comme un service discret qui n’a pas besoin qu’on lui rappelle sa tâche.

Toute la logique réside dans la fonction OnStart. Le plus souvent, un cycle est organisé en interne : vérification de l’état, exécution de la tâche, pause via la fonction Sleep suivie de l’étape suivante. Ce rythme est simple et fiable. Cela ne nécessite pas d'événements de marché et ne dépend pas de l'activité des graphiques. Le service fonctionne à son propre rythme.

Les avantages pratiques deviennent rapidement évidents. Si vous avez besoin de télécharger régulièrement des données à partir d'une source externe, ce service peut s'en charger. Besoin de générer des ticks pour un symbole personnalisé ? Ce service peut également le faire. Besoin d'envoyer des signaux ou de mettre à jour l'état du système toutes les quelques secondes ? Encore une fois, c'est le rôle de ce service. Tout ce qui se répète, tout ce qui ne devrait pas dépendre des fluctuations du marché, devrait logiquement être confié à ce service.

Il y a un autre détail qui rend ce service particulièrement pratique. Si vous ne l'arrêtez pas manuellement, il redémarrera automatiquement au prochain démarrage du terminal. Cela en fait un outil pour les opérations de routine : vérification de l’état au démarrage, préparation des données, synchronisation – tout cela peut être fait sans intervention de l’utilisateur. Une fois configuré, le service fonctionne tous les jours comme une horloge.

En architecture, cela paraît très sain. L'Expert Advisor sur le graphique réagit au marché. Parallèlement, l'interface est orientée utilisateur. En attendant, le service effectue un travail discret et régulier en arrière-plan. Sans tracas, sans surcharge de OnTick. Cette répartition des rôles soulage le système des contraintes inutiles et le rend plus stable. Lorsque chaque élément remplit sa fonction, l'ensemble de la structure fonctionne de manière nettement plus propre.



Communication entre les programmes

Lorsqu'une solution de trading comprend plusieurs composants, celle-ci a besoin d'un mécanisme de messagerie. Un seul module peut collecter des données. Le second les montre sur le panneau. Le troisième prend une décision de trading. Le quatrième gère la logique en arrière-plan. Et si chacun d'eux se met à vivre dans son propre univers, le résultat ne sera pas une architecture, mais un enchevêtrement de drapeaux, de variables globales et de vérifications aléatoires. C’est pourquoi la structure événementielle fonctionne particulièrement bien ici.

La forme la plus propre de l'architecture événementielle est celle où les indicateurs deviennent la source des signaux, non pas de manière conventionnelle (lecture d’un buffer d’indicateur pour en extraire une valeur), mais en tant que générateurs d'événements à part entière.

L'idée est relativement simple. Chaque indicateur est enveloppé dans un adaptateur léger. En interne, il ne fait qu'une seule chose : il surveille l'apparition d'un signal et, si la condition est remplie, il déclenche un événement personnalisé. Et c’est tout. Pas de sollicitations constantes de l'extérieur, pas de bruit inutile.

int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
  {
   if(prev_calculated==rates_total)
     return prev_calculated;
//---
   if(BarsCalculated(handle) < rates_total)
      return(prev_calculated);
   vector<double> sig, main;
   if(!main.CopyIndicatorBuffer(handle, 0, 1, 2) ||
      !sig.CopyIndicatorBuffer(handle, 1, 1, 2))
      return(prev_calculated);
   if(sig[0] > main[0] && sig[1] <= main[1])
     {
      if(!EventChartCustom(ChartID(), BuyID, MagicNumber, main[0], IndComment))
         return(prev_calculated);
     }
   if(sig[0] < main[0] && sig[1] >= main[1])
     {
      if(!EventChartCustom(ChartID(), SellID, MagicNumber, main[0], IndComment))
         return(prev_calculated);
     }
//--- return value of prev_calculated for the next call
   return(rates_total);
  }

Prenons comme exemple deux indicateurs classiques : le MACD et le RSI. Chacun d'eux travaille indépendamment sur son propre timeframe. Ils ne gèrent qu'une nouvelle barre. Le MACD vérifie l'intersection de l'histogramme et de la ligne de signal. Le RSI vérifie le franchissement des niveaux de sur-achat et de survente. Lorsqu'un signal se produit, ils génèrent des événements personnalisés dont les codes sont définis à l’avance. C’est un point fondamental : l’indicateur ne stocke pas le signal dans un buffer, il enregistre un événement.

L'Expert Advisor entre alors en jeu, mais dans un rôle complètement différent. Il n'interroge pas les indicateurs à chaque tick. Il ne parcourt pas l'historique, ne synchronise pas les buffers, et n'essaie pas de deviner s'il y avait un signal. Il se contente d'écouter les événements dans OnChartEvent.

void OnChartEvent(const int32_t id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(lparam != MagicNumber)
      return;
   switch(id - CHARTEVENT_CUSTOM)
     {
      case MACD1Buy:
         indSignals[0] = 1;
         break;
      case MACD1Sell:
         indSignals[0] = -1;
         break;
      case MACD2Buy:
         indSignals[1] = 1;
         CloseSellSignal = true;
         CloseBuySignal = false;
         break;
      case MACD2Sell:
         indSignals[1] = -1;
         CloseBuySignal = true;
         CloseSellSignal = false;
         break;
      case RSI1CrossOverBoughtDown:
         indSignals[2] = -1;
         break;
      case RSI1CrossOverSoldUp:
         indSignals[2] = 1;
         break;
      case RSI2CrossOverBoughtDown:
         indSignals[3] = -1;
         CloseBuySignal = true;
         CloseSellSignal = false;
         break;
      case RSI2CrossOverSoldUp:
         indSignals[3] = 1;
         CloseSellSignal = true;
         CloseBuySignal = false;
         break;
     }
   BuySignal = true;
   SellSignal = true;
   for(uint i = 0; i < indSignals.Size() && (BuySignal || SellSignal); i++)
     {
      BuySignal = BuySignal && (indSignals[i] == 1);
      SellSignal = SellSignal && (indSignals[i] == -1);
     }
  }

L'Expert Advisor enregistre les événements entrants. S’il ne reçoit rien, il ne fait rien.

Cela modifie radicalement la nature de la charge. Dans la structure classique avec interrogation des indicateurs, la même chose se produit à chaque tick : demande de buffers, vérification des valeurs. Il faut parfois parcourir l’historique pour ne pas rater le signal. Cela devient particulièrement difficile avec plusieurs indicateurs et des périodes différentes. Il y a toujours un décalage temporel entre les signaux, et nous devons constamment revenir en arrière pour le capter.

Le modèle événementiel élimine tout simplement ces efforts superflus. Chaque signal est un événement déjà prêt. Il n'est pas nécessaire de le rechercher, de le recalculer ou de le reconstruire rétroactivement. Il arrive au moment même où le signal apparaît.

Le résultat est un diagramme clair et rapide :

  • l'indicateur n'est responsable que de son propre calcul et de son propre signal ;
  • un événement enregistre le moment du changement d'état ;
  • l'Expert Advisor agrège les signaux et prend une décision.

Cela est particulièrement évident dans les stratégies multi-indicateurs. Par exemple, un signal arrive de M5, l’autre de M30. Dans le modèle classique, il est nécessaire de synchroniser les données, de tenir compte des décalages et de consulter l'historique. Ici, tout est plus simple : chaque indicateur s’est manifesté au bon moment, l'Expert Advisor a sauvegardé l'état et a attendu que toutes les conditions soient réunies.

En termes de performance, cela représente un avantage notable. Il n'y a pas de références constantes aux indicateurs, pas de vérifications à vide à chaque tick. OnTick devient simple et rapide, ce qui signifie que la réaction au marché est plus rapide. En matière de trading, il ne s'agit pas seulement d'une théorie, mais d'un avantage tout à fait concret : moins de délais, une logique plus claire, une exécution plus précise.

void OnTick()
  {
//---
   if(BuySignal)
     {
      cSymbol.Refresh();
      cSymbol.RefreshRates();
      if(!cTrade.Buy(InpLot, cSymbol.Name(), cSymbol.Ask(),
                     cSymbol.Ask() - SL * cSymbol.Point(),
                     cSymbol.Ask() + TP * cSymbol.Point(), "Event Example"))
        {
         PrintFormat("Error open Buy position: %d", GetLastError());
         return;
        }
      BuySignal = false;
      ArrayFill(indSignals, 0, indSignals.Size(), 0);
     }
//---
   if(SellSignal)
     {
      cSymbol.Refresh();
      cSymbol.RefreshRates();
      if(!cTrade.Sell(InpLot, cSymbol.Name(), cSymbol.Bid(),
                      cSymbol.Bid() + SL * cSymbol.Point(),
                      cSymbol.Bid() - TP * cSymbol.Point(), "Event Example"))
        {
         PrintFormat("Error open Sell position: %d", GetLastError());
         return;
        }
      SellSignal = false;
      ArrayFill(indSignals, 0, indSignals.Size(), 0);
     }
//---
   if(CloseBuySignal)
     {
      if(cPosition.SelectByMagic(cSymbol.Name(), MagicNumber) &&
         cPosition.PositionType() == POSITION_TYPE_BUY)
        {
         if(!cTrade.PositionClose(cPosition.Ticket()))
           {
            PrintFormat("Error close Buy position: %d", GetLastError());
            return;
           }
        }
     }
//---
   if(CloseSellSignal)
     {
      if(cPosition.SelectByMagic(cSymbol.Name(), MagicNumber) &&
         cPosition.PositionType() == POSITION_TYPE_SELL)
        {
         if(!cTrade.PositionClose(cPosition.Ticket()))
           {
            PrintFormat("Error close Sell position: %d", GetLastError());
            return;
           }
        }
     }
//---
  }

Ce qui est tout aussi important, c'est que le code devienne plus transparent. Le signal n'est plus une valeur tampon nécessitant une interprétation, mais se transforme en un événement clair : survenu – enregistré – traité. C’est précisément le cas lorsque l’architecture améliore directement à la fois la lisibilité et le comportement du système.

L'exécution de l’Expert Advisor dans le Testeur de Stratégie MetaTrader 5 a donné un résultat intéressant. Nous disposons d'un système aux caractéristiques bien définies : il génère des revenus tout en maintenant les risques dans des limites raisonnables.

Résultats des testsRésultats des tests

Avec un dépôt de 1.000 USD, le résultat a été de 2.427,52 USD, soit +143% en 3 ans. Le résultat est encourageant, étant donné que le drawdown reste modéré. La courbe d’équité semble bien dessinée : la croissance est progressive, il y a des replis, mais sans baisses prolongées. Le drawdown maximal est d'environ 12 à 13%, alors que le Facteur de Récupération est d'environ 4,6 — le système est capable de se remettre des pertes sans y rester bloqué. C'est un signe important de durabilité.

Le mécanisme de profit fonctionne de manière assez classique. Au total, 18 transactions, avec un taux de réussite de 50%. Mais le profit moyen est presque 4 fois supérieur à la perte moyenne. Cela a permis au Profit Factor d'atteindre le niveau de 3,9. Cette stratégie génère des profits non pas grâce à la fréquence des signaux, mais grâce à leur qualité et au maintien des positions. Mais c'est là que réside une limite. Le faible nombre de transactions rend les statistiques fragiles.

La courbe d’équité est relativement lisse, sans aucune discontinuité chaotique. Cela confirme indirectement que les signaux ne sont pas aléatoires.

Et voici un point architectural important. Le modèle événementiel élimine l'interrogation constante des indicateurs. L'Expert Advisor ne réagit qu'au signal. Moins d'opérations inutiles signifie une réponse plus rapide. Dans le Testeur de Stratégie, ce n'est qu'une nuance, mais en conditions réelles de trading, cela devient un avantage.

Le résultat est simple. Nous avons ici un modèle de signal présentant un bon ratio risque/récompense et une architecture bien conçue. Mais pour l'instant, il s'agit d'un prototype. Des tests hors échantillon et des statistiques plus poussées sont nécessaires.



Erreurs typiques

Lorsqu'une architecture devient événementielle, le code devient plus propre et plus rapide. Mais en même temps, la nature des erreurs change également. Elles ne restent plus à la surface, mais se manifestent dans les liens entre les gestionnaires et dans la discipline du travail avec les ressources.

Le premier problème, et le plus urgent, est toujours le même système OnTick surchargé et vieillissant. Même après s'être familiarisé avec le modèle événementiel, la tentation est grande de laisser une partie de la logique en place, juste au cas où. Le résultat est un système hybride : il y a des minuteurs et des événements, mais l’essentiel du travail est toujours effectué par une seule méthode. Ce compromis nous ramène rapidement à la case départ : un code lourd et mal entretenu. L'expérience montre que si nous avons déjà réparti les rôles, nous devons aller jusqu'au bout.

La couche d'erreurs suivante est liée à la gestion des ressources. Le modèle événementiel implique davantage d'entités : minuteurs, objets, abonnements, structures auxiliaires. Et si elles ne sont pas gérées avec soin, le système commence à faire du bruit. Un EventKillTimer oublié, des éléments graphiques non supprimés, des variables globales laissées en place — tout cela ne casse pas immédiatement le programme, mais réduit progressivement sa prévisibilité. Il en résulte une situation classique : l'erreur apparaît ailleurs que là où elle a été commise.

void OnDeinit(const int reason)
  {
//---
   for(uint i = 0; i < handles.Size(); i++)
      if(handles[i] != INVALID_HANDLE)
         IndicatorRelease(handles[i]);
  }

La nature même de ces événements exige une attention particulière. Les gestionnaires sont exécutés séquentiellement, et une opération longue au sein d'un événement bloque les autres. Cela se remarque particulièrement au niveau de l'interface : l'utilisateur interagit avec le panneau, mais le programme ne répond pas. Il est occupé à effectuer des calculs. Là encore, l'idée clé de l'architecture est évidente : découper l'exécution en étapes et ne pas maintenir le thread occupé plus longtemps que nécessaire.

Lorsque plusieurs sources d'événements apparaissent dans un système (indicateurs, panneau, service), la discipline d'identification devient primordiale. Si nous n’introduisons pas de conventions claires pour les noms d’objets et les codes d’événements, la confusion s’installe : les signaux se chevauchent, les gestionnaires réagissent à des choses inappropriées. Une règle simple avec des préfixes et des plages d’identifiants semble presque triviale, mais c'est ce qui permet de maintenir le système en ordre lorsqu'il commence à se développer.

#define  MACD1Buy                   1
#define  MACD1Sell                  2
#define  MACD2Buy                   3
#define  MACD2Sell                  4
#define  RSI1CrossOverBoughtUp      5
#define  RSI1CrossOverBoughtDown    6
#define  RSI1CrossOverSoldUp        7
#define  RSI1CrossOverSoldDown      8
#define  RSI2CrossOverBoughtUp      9
#define  RSI2CrossOverBoughtDown    10
#define  RSI2CrossOverSoldUp        11
#define  RSI2CrossOverSoldDown      12

Enfin, le moment le plus délicat est le début du programme. Dans un modèle événementiel, les événements peuvent survenir presque immédiatement, avant même que l'initialisation ne soit complètement terminée. Si les structures ne sont pas prêtes à ce stade, il est facile de se retrouver à accéder à des données vides ou à un état incorrect. Par conséquent, l'initialisation doit soit être strictement terminée avant le début du traitement, soit — ce qui est souvent plus pratique — être décomposée en étapes avec un contrôle de préparation via les mêmes événements personnalisés.

Il apparaît donc clairement que les événements eux-mêmes ne rendent pas un système fiable. Ils fournissent uniquement des outils. La fiabilité apparaît lorsque la rigueur de l'ingénierie est intégrée à ces outils. Et si elle existe, l'architecture événementielle cesse d'être une simple technique pratique ; elle devient la base d'un système de trading stable et prévisible.



Conclusion

Lorsque la logique du programme est construite sur des événements, la plateforme cesse d'être un simple environnement pour les Expert Advisors et devient la base de solutions applicatives complètes. Dans ce modèle, la programmation ressemble déjà au développement d'une application de bureau classique : une interface avec des boutons et des panneaux apparaît, des services en arrière-plan sont lancés, et des gestionnaires distincts sont créés pour différents types d'événements. Chaque chose est à sa place, et c'est pourquoi le système est nettement plus stable en matière de maintenance.

L’approche événementielle est particulièrement précieuse lorsqu’un Expert Advisor classique ne peut plus assumer le rôle d’un fichier unique en toutes circonstances. Il permet de créer des assistants multi-symboles, des panneaux de contrôle flexibles et des contrôleurs complexes qui réagissent aux changements sémantiques d'état. Il est plus facile d'ajouter de nouvelles fonctionnalités à cette architecture : il suffit d'introduire un autre événement ou un autre gestionnaire sans avoir à retravailler l'intégralité du code.

C’est là le principal avantage de l’architecture événementielle : elle assure l’ordre sans sacrifier la flexibilité. Une séparation adéquate des gestionnaires, des services et des canaux de communication permettent de créer des systèmes de trading fiables, évolutifs et pleinement aboutis. C’est là que MQL5 dépasse le cadre traditionnel de l’Expert Advisor pour devenir un environnement destiné à des applications de trading complexes, où comptent non seulement la réaction au marché, mais aussi une organisation technique mature de l’ensemble du programme.


Programmes utilisés dans l'article

# Nom Type Description
1 EventExample.mq5 Expert Advisor Expert Advisor de gestion des événements
2 EventMACD.mq5 Indicateur Indicateur MACD avec génération d'événements lors du croisement des lignes
3 EventRSI.mq5 Indicateur Indicateur RSI avec génération d'événements lors du franchissement de niveaux.

Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/22383

Fichiers joints |
MQL5.zip (5.12 KB)
Derniers commentaires | Aller à la discussion (6)
fxsaber
fxsaber | 5 mai 2026 à 15:00
//+------------------------------------------------------------------+
//| Fonction de trading                                                   |
//+------------------------------------------------------------------+
void OnTrade()
  {
   Sleep(0);
//---
  }
//+------------------------------------------------------------------+
//| Fonction TradeTransaction                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   Sleep(0);
//---
  }
Pourquoi ?
fxsaber
fxsaber | 5 mai 2026 à 15:02
            if(!cTrade.PositionClose(cPosition.Ticket()))
              {
               PrintFormat("Error close Sell position: %d", GetLastError());
               return;
              }
Il semblerait que cTrade contienne une instruction de sortie en cas d'erreur.
fxsaber
fxsaber | 5 mai 2026 à 15:33
fxsaber #:
Travailler dans un conseiller technique avec des indicateurs sans tampon : cela fera sans doute l'affaire comme exemple.


Dans cet exemple, la mise à jour des données du symbole est effectuée de manière stricte (il serait préférable d'utiliser MQL_TESTER).

void OnTick()
  {
//---
   if(BuySignal)
     {
      cSymbol.Refresh();
      cSymbol.RefreshRates();


Mais aucune vérification n’est effectuée pour s’assurer que les valeurs calculées à partir des événements de signal et de tick sont bien à jour. Et c’est là un véritable problème.


On peut l'atténuer grâce à des appels asynchrones à OrderSend, mais pas la résoudre. C'est pourquoi, même dans un tel exemple, il faut en plus transmettre, dans l'événement ChartEvent, les données du tick au cours duquel l'événement a été généré.

Chacha Ian Maroa
Chacha Ian Maroa | 11 mai 2026 à 12:46
Super, le type de service des programmes MQL5 est vraiment sous-estimé.
Picazzo Research and Development
Picazzo Research and Development | 11 mai 2026 à 14:27
C'est un excellent exemple, merci.
Comment Échanger des Données : Une DLL pour MQL5 en 10 minutes Comment Échanger des Données : Une DLL pour MQL5 en 10 minutes
Maintenant, peu de développeurs se rappellent de la façon d'écrire une DLL simple et des caractéristiques spéciales des différentes liaisons système. À l'aide de plusieurs exemples, je vais tenter de montrer l'ensemble du processus de création de la DLL simple en 10 minutes, ainsi que de discuter de certains détails techniques de notre implémentation de liaison. Je vais montrer étape par étape le processus de la création de DLL dans Visual Studio avec des exemples d'échange de différents types de variables (nombres, tableaux, chaînes, etc.). En outre, je vais vous expliquer comment protéger votre terminal client des plantages dans les DLL personnalisées.
MetaTrader 5 : créez un marché adapté à votre stratégie — barres Renko, Range et Volume Egal, instruments synthétiques et tests de résistance sur symboles personnalisés MetaTrader 5 : créez un marché adapté à votre stratégie — barres Renko, Range et Volume Egal, instruments synthétiques et tests de résistance sur symboles personnalisés
Dans cet article, nous démontrons comment utiliser l'API des symboles personnalisés de MetaTrader 5 pour transformer votre terminal en un outil de construction des données permettant de générer des graphiques Renko, Range et Volume Egal indépendants du temps et d'assembler des instruments synthétiques. Nous analyserons l'agrégation des ticks et la modification de l'historique pour les tests de résistance (élargissement du spread, modifications du niveau de stop) en tenant compte des limitations de la plateforme. Vous pourrez également vous entraîner à utiliser CiCustomSymbol et le routage des ordres vers un symbole réel via le wrapper CustomOrder grâce à des fragments de code prêts à l'emploi.
L'Histogramme des prix (Profile du Marché) et son implémentation  en MQL5 L'Histogramme des prix (Profile du Marché) et son implémentation en MQL5
Le Profile du Marché a été élaboré par le brillant penseur Peter Steidlmayer. Il a suggéré l’utilisation de la représentation alternative de l'information sur les mouvements de marché « horizontaux » et « verticaux » qui conduit à un ensemble de modèles complètement différent. Il a assumé qu'il existe une impulsion sous-jacente du marché ou un modèle fondamental appelé cycle d'équilibre et de déséquilibre. Dans cet article, j’examinerai l'Histogramme des Prix - un modèle simplifié de profil de marché, et décrirai son implémentation dans MQL5.
Du CPU au GPU en MQL5 : un framework OpenCL pratique pour accélérer la recherche, l’optimisation et la détection de motifs Du CPU au GPU en MQL5 : un framework OpenCL pratique pour accélérer la recherche, l’optimisation et la détection de motifs
Découvrez comment construire une approche pratique de migration des calculs du processeur (CPU) vers le processeur graphique (GPU) en MQL5 en utilisant OpenCL. Nous nous concentrerons sur l'initialisation du contexte, l'organisation des buffers, le traitement par lots de grande taille, le lancement du noyau et la minimisation des échanges de données. Les erreurs courantes et les moyens de les éliminer seront également examinés. Un exemple utilisant des figures de chandeliers japonais illustre l'intérêt pratique de cette approche.