English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
preview
Développer un Expert Advisor de trading à partir de zéro (Partie 25) : Assurer la robustesse du système (II)

Développer un Expert Advisor de trading à partir de zéro (Partie 25) : Assurer la robustesse du système (II)

MetaTrader 5Exemples | 12 février 2024, 12:41
1 011 0
Daniel Jose
Daniel Jose

Introduction

Dans l'article précédent Assurer la robustesse du système (I), nous avons vu comment modifier certaines parties de l'EA pour rendre le système plus fiable et plus robuste.

Ce n'était qu'une introduction à ce que nous allons faire dans cet article. Oubliez tout ce que vous saviez, ce que vous aviez planifié ou ce que vous souhaitiez. Le plus difficile ici sera de pouvoir séparer les choses. Depuis le début de cette série, l'EA a évolué presque constamment : nous avons ajouté, modifié et même supprimé certaines choses. Cette fois, nous irons encore plus loin.

Contrairement à ce que cela peut paraître, il y a un problème de taille : une évaluation environnementale bien conçue ne contient pas et ne contiendra aucun type d’indicateur. Elle se contentera d'observer et de veiller à ce que les positions d'ordre indiquées soient respectées. L’évaluation environnementale parfaite n’est essentiellement qu’un assistant qui fournit un véritable aperçu de l’évolution du prix. Il ne regarde pas les indicateurs, mais uniquement les positions ou les ordres qui figurent sur le graphique.

Vous pourriez penser que je dis des bêtises et que je ne sais pas de quoi je parle. Mais avez-vous déjà pensé pourquoi MetaTrader 5 propose différentes classes pour différentes choses ? Pourquoi la plateforme propose-t-elle des indicateurs, des services, des scripts et des Experts Advisors séparément et non dans un seul bloc ? Donc...

C'est le point. Si les choses sont séparées, c’est précisément parce qu’il est préférable de les travailler séparément.

Les indicateurs sont utilisés dans un but général, quel qu'il soit. C'est bien si la conception des indicateurs est bien pensée afin de ne pas nuire aux performances globales, je veux dire ne pas nuire à la plateforme MetaTrader 5, mais aux autres indicateurs. Ils peuvent effectuer des tâches en parallèle de manière très efficace parce qu’ils s’exécutent sur des threads différents.

Les services aident de manières différentes. Par exemple, dans les articles Accès aux données sur le Web (II) et Accès aux données sur le Web (III) de cette série, nous avons utilisé des services pour accéder aux données d'une manière très intéressante. En fait, nous pourrions le faire directement dans l’EA. Mais ce n’est pas la méthode la plus adaptée, comme je l’ai déjà expliqué les autres articles.

Les scripts nous aident d'une manière tout à fait unique : ils n'existent que pendant un certain temps, font quelque chose de très spécifique, puis disparaissent du graphique. Ou alors, ils peuvent aussi y rester jusqu'à ce que nous modifiions certains paramètres du graphique comme, par exemple, la période.

Cela limite un peu les possibilités, mais cela fait partie de ce qu'il faut accepter tel quel. Les Expert Advisors, ou EA, au contraire, sont spécifiques au travail avec un système de trading. Bien que nous puissions ajouter des fonctions et des codes qui ne font pas partie du système de trading dans les EA, cela n'est pas recommandé dans les systèmes hautes performances ou haute fiabilité. La raison en est que tout ce qui ne fait pas partie du système de trading ne devrait pas figurer dans l’EA : les choses doivent être placées aux bons endroits et traitées correctement.

La première chose à faire pour améliorer la fiabilité est donc de supprimer absolument tout le code qui ne fait pas partie du système de trading et de transformer ces éléments en indicateurs ou quelque chose du genre. La seule chose qui restera dans le code de l’EA, ce sont les parties chargées de gérer, d'analyser et de traiter les ordres ou les positions. Toutes les autres choses seront supprimées.

Alors, c’est parti.


2.0. Implémentation

2.0.1. Suppression de l'arrière-plan de l’EA

Bien que cela ne nuise pas à l'EA et ne pose aucun problème, certaines personnes souhaitent parfois que leur écran soit vide et que seuls certains éléments y soient affichés. Nous allons donc supprimer cette partie de l’EA et la transformer en indicateur. C’est très simple à mettre en œuvre. Nous ne toucherons à aucune des classes, mais nous créerons le code suivant :

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
//+------------------------------------------------------------------+
input string                    user10 = "Wallpaper_01";        //Used BitMap
input char                      user11 = 60;                    //Transparency (from 0 to 100)
input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;   //Background image type
//+------------------------------------------------------------------+
C_Terminal      Terminal;
C_WallPaper WallPaper;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "WallPaper");
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
        break;
        }
        ChartRedraw();
}
//+------------------------------------------------------------------+

Comme vous pouvez le constater, tout est naturel et compréhensible. Nous avons simplement supprimé le code de l'EA et nous l'avons converti en un indicateur qui peut être ajouté au graphique. Et tout changement, qu'il s'agisse de l'arrière-plan, du niveau de transparence, ou même de sa suppression du graphique, n'aura aucun effet sur les actions de l'EA.

Nous allons maintenance commencer à supprimer les éléments qui provoquent réellement une dégradation des performances de l’EA. Ce sont ces choses qui fonctionnent de temps en temps, ou alors à chaque mouvement de prix, et qui peuvent donc parfois provoquer un ralentissement de l'EA l'empêchant de faire son véritable travail : surveiller ce qui se passe avec les ordres ou les positions sur le graphique.


2.0.2. Conversion du Volume Au Prix (Volume At Price) en indicateur

Même si cela ne semble pas être le cas, le système Volume At Price prend du temps, ce qui est souvent critique pour un EA. Surtout dans les moments de forte volatilité où les prix fluctuent énormément sans grande direction. C'est à ces moments-là que l'EA a besoin de tous les cycles machine disponibles pour accomplir sa tâche. Il serait dommage de rater une bonne opportunité parce qu’un indicateur décide de prendre le relais. Alors, supprimons-le de l'EA et transformons-le en un véritable indicateur en créant le code ci-dessous :

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
//+------------------------------------------------------------------+
input color             user0   = clrBlack;                     //Bar color
input   char            user1   = 20;                                   //Transparency (from 0 to 100 )
input color     user2 = clrForestGreen; //Buying
input color     user3 = clrFireBrick;   //Selling
//+------------------------------------------------------------------+
C_Terminal                      Terminal;
C_VolumeAtPrice VolumeAtPrice;
//+------------------------------------------------------------------+
int OnInit()
{
        Terminal.Init();
        VolumeAtPrice.Init(user2, user3, user0, user1);
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        VolumeAtPrice.Update();
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

C’était la partie la plus simple. Nous avons supprimé le code de l'EA et nous l'avons placé dans l'indicateur. Si vous souhaitez remettre le code dans l'EA, il vous suffira de copier le code de l'indicateur et de le remettre dans l'EA.

Nous avons donc commencé par quelque chose de simple. Mais les choses deviennent maintenant plus compliquées : nous allons supprimer Times & Trade de l’EA.


2.0.3. Transformer Times & Trade en indicateur

Ce n’est pas si simple si l’on vise à créer du code qui peut être utilisé à la fois dans une évaluation environnementale et dans un indicateur. Étant un indicateur qui fonctionne dans une sous-fenêtre, il pourrait semblait que le convertir en indicateur serait facile. Mais ce n’est pas si facile car il fonctionne dans une sous-fenêtre. Le principal problème est que si nous faisons comme dans les cas précédents, nous aurons alors le résultat suivant dans la fenêtre de l'indicateur :

Il n'est pas recommandé de placer de tels éléments dans la fenêtre de l'indicateur. Cela pourrait induire l'utilisateur en erreur s'il souhaite supprimer l'indicateur de l'écran. Il faudrait donc procéder autrement. A la fin, même si cela peut paraître assez déroutant alors que ce ne sont qu’un simple ensemble de directives et quelques modifications, nous obtiendrons le résultat suivant dans la fenêtre d'indicateur :

C’est exactement ce à quoi l’utilisateur s’attend, et pas le désordre visible dans l’image précédente.

Vous trouverez ci-dessous le code complet de l’indicateur Times & Trade :

#property copyright "Daniel Jose"
#property version   "1.00"
#property indicator_separate_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
//+------------------------------------------------------------------+
C_Terminal        Terminal;
C_TimesAndTrade   TimesAndTrade;
//+------------------------------------------------------------------+
input int     user1 = 2;      //Scale
//+------------------------------------------------------------------+
bool isConnecting = false;
int SubWin;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "Times & Trade");
        SubWin = ChartWindowFind();
        Terminal.Init();
        TimesAndTrade.Init(user1);
        EventSetTimer(1);
                
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        if (isConnecting)
                TimesAndTrade.Update();
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        if (TimesAndTrade.Connect())
        {
                isConnecting = true;
                EventKillTimer();
        }
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        TimesAndTrade.Resize();
        break;
        }
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

Le code semble similaire à celui utilisé dans l'EA, à l'exception de la ligne en surbrillance qui n'est pas présente dans le code de l’EA. Alors, quel est le problème ? Ou est-ce qu’il n'y en a pas ? En fait, il y a bien un problème : le code n'est pas exactement le même. La différence n'est pas dans l'indicateur ou dans le code de l’EA, mais dans le code de la classe. Mais avant d’examiner la différence, réfléchissons aux questions suivantes : comment pouvons-nous dire au compilateur ce qu’il doit compiler ou pas ? Peut-être que lors de la programmation, vous ne vous en inquiétez pas du tout, peut-être que vous créez simplement du code et que si quelque chose ne vous plaît pas, vous le supprimez simplement.

Les programmeurs expérimentés ont une règle : supprimer quelque chose uniquement lorsque cela ne fonctionne définitivement pas, sinon conserver les fragments même s'ils ne sont pas réellement compilés. Mais comment faire cela dans un code linéaire, quand on veut que les fonctions écrites fonctionnent toujours ? Voici la question : Savez-vous comment indiquer au compilateur ce qu’il faut compiler et ce qu’il ne faut pas compiler ? Si la réponse est « Non », ce n’est pas grave. Personnellement, je ne savais pas comment faire quand j'ai commencé. Mais ça aide beaucoup. Alors, découvrons comment faire.

Certains langages ont des directives de compilation, qui sont également appelées des directives de préprocesseur, selon l'auteur. Mais l’idée est la même : indiquer au compilateur quoi compiler et comment faire la compilation. Il existe un type de directive très spécifique pouvant être utilisé pour isoler intentionnellement du code pour tester des éléments spécifiques. Ce sont des directives de compilation conditionnelle . Lorsqu'elles sont utilisés correctement, elles nous permettent de compiler le même code de différentes manières. C’est exactement ce qui est fait ici avec l’exemple du Times & Trade. Nous choisissons qui sera chargé de générer la compilation conditionnelle : l'EA ou l'indicateur. Après avoir défini ce paramètre, créez la directive #define puis utilisez la directive conditionnelle #ifdef #else #endif pour indiquer au compilateur comment le code sera compilé.

Cela peut être difficile à comprendre, alors voyons comment cela fonctionne.

Dans le code de l’EA, définissez et ajoutez les lignes mises en évidence ci-dessous :

#define def_INTEGRATION_WITH_EA
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#ifdef def_INTEGRATION_WITH_EA
        #include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
#endif
//+------------------------------------------------------------------+

Voici ce qui suit se produit : Si vous souhaitez compiler un EA avec des classes dans des fichiers MQH, laissez la directive #ifdefine def_INTEGRATION_WITH_EA qui est définie dans l'Expert Advisor. L'EA contiendra ainsi toutes les classes que nous suivons et insérons dans les indicateurs. Si vous souhaitez supprimer les indicateurs, il n'est pas nécessaire de supprimer le code, il suffit de commenter la définition. Cela peut être fait simplement en convertissant la ligne où la directive est déclarée en ligne de commentaire. Le compilateur ne verra pas la directive, elle sera donnée comme inexistante ; comme elle n'existe pas, chaque fois que la directive conditionnelle #ifdef def_INTEGRATION_WITH_EA est trouvée, elle sera complètement ignorée, et le code entre elle et la partie #endif dans l'exemple ci-dessus ne sera pas compilé.

C'est l'idée que nous implémentons dans la classe C_TimesAndTrade. Voici à quoi ressemble la nouvelle classe. Je ne montrerai qu'un seul point pour attirer votre attention :

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Canvas.mqh>
#ifdef def_INTEGRATION_WITH_EA

#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh>

class C_TimesAndTrade : private C_FnSubWin

#else

class C_TimesAndTrade

#endif
{
//+------------------------------------------------------------------+
#define def_SizeBuff 2048
#define macro_Limits(A) (A & 0xFF)
#define def_MaxInfos 257
#define def_ObjectName "TimesAndTrade"
//+------------------------------------------------------------------+
        private :
                string  m_szCustomSymbol;

// ... The rest of the class code....

}

Le code peut sembler étrange à quiconque n'utilise pas de directives de compilation. La directive def_INTEGRATION_WITH_EA est déclarée dans l'EA. Ensuite, voila ce qui se produit. Lorsque le compilateur génère du code objet à partir de ce fichier, il vérifiera si le fichier en cours de compilation est un EA et a une directive déclarée. Dans ce cas, il générera du code objet avec le code situé entre les directives conditionnelles #ifdef def_INTEGRATION_WITH_EA et #else. Habituellement, nous utilisons dans ce cas la directive #else. Dans le cas où un autre fichier est compilé, par exemple l'indicateur dont la directive def_INTEGRATION_WITH_EA n'est pas définie, tout ce qui se trouve entre les directives #else et #endif sera compilé. C'est comme ça que ça marche.

Lors de la compilation d'un EA ou d'un indicateur, regardez l'intégralité du code de la classe C_TimesAndTrade afin de comprendre chacun de ces tests et le fonctionnement général. Le compilateur MQL5 effectuera ainsi tous les réglages, ce qui nous fera gagner du temps et des efforts liés à la nécessité de conserver 2 fichiers différents.


2.0.4. Rendre l'EA plus agile

Comme mentionné auparavant, l'EA ne devrait fonctionner qu'avec le système d’ordres. Jusqu’à présent, il possédait des caractéristiques qui sont désormais devenues des indicateurs. La raison en est quelque chose de très personnel, qui a à voir avec les éléments impliqués dans les calculs que l'EA doit effectuer. Mais ce système de calcul a été modifié et déplacé vers une autre méthode. Grâce à cela, j'ai remarqué que le système d’ordres était impacté par certaines choses que l'EA faisait au lieu de s'occuper des ordres. Le pire des problèmes est survenu lors de l'événement OnTick :

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, TradeView.SecureChannelPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
#ifdef def_INTEGRATION_WITH_EA
        TimesAndTrade.Update();
#endif 
}

Cette directive conditionnelle est maintenant ajoutée à cet évènement pour que ceux qui ne tradent pas pendant les périodes de forte volatilité puissent, s'ils le souhaitent, disposer d'un EA avec tous les indicateurs d'origine. Mais avant de penser que c'est une bonne idée, permettez-moi de vous rappeler comment fonctionne la fonctionnalité de mise à jour de Times & Trade.

inline void Update(void)
{
        MqlTick Tick[];
        MqlRates Rates[def_SizeBuff];
        int i0, p1, p2 = 0;
        int iflag;
        long lg1;
        static int nSwap = 0;
        static long lTime = 0;

        if (m_ConnectionStatus < 3) return;
        if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0)
        {

// ... The rest of the code...

        }
}

Le code ci-dessus fait partie de la fonction de mise à jour présente dans la classe C_TimesAndTrade. Le problème est dans la partie mise en évidence. Chaque fois qu'elle est exécutée, une demande est envoyée au serveur pour obtenir tous les tickets de transaction effectués depuis un certain moment, ce qui, soit dit en passant, n'est pas si problématique. Le problème est que, de temps en temps, cet appel coïncide avec 2 autres événements.

Le premier, et le plus évident, événement est le grand nombre de transactions qui peuvent avoir lieu, ce qui fait que la fonction OnTick reçoit un grand nombre d'appels. En plus de devoir exécuter le code ci-dessus présent dans la classe C_TimesAndTrade, cette fonction va résoudre un autre problème : appeler la fonction SecureChannelPosition présente dans la classe C_IndicatorTradeView. C'est donc un autre petit problème, mais ce n'est pas tout. J'ai déjà dit que, de temps en temps, malgré la faible volatilité, nous aurons une coïncidence de 2 événements, dont le premier était cet événement.

Le second se trouve dans l'événement OnTime qui a déjà été mis à jour et qui se présente comme suit :

#ifdef def_INTEGRATION_WITH_EA
void OnTimer()
{
        VolumeAtPrice.Update();
        TimesAndTrade.Connect();
}
#endif 

Si vous envisagez d'utiliser l'EA tel qu'il a été conçu, étant donné également qu'il reçoit encore plus de code, il peut parfois y avoir des problèmes en raison d'événements coïncidents. Lorsque cela se produit, l'EA restera (même pendant une seule seconde) à faire des choses qui ne sont pas liées au système d’ordres.

Contrairement à la fonction trouvée dans C_TimesAndTrade, cette fonction est présente dans la classe C_VolumeAtPrice et peut vraiment nuire aux performances de l'EA lors de la gestion des ordres. Voici pourquoi cela se produit :

inline virtual void Update(void)
{
        MqlTick Tick[];
        int i1, p1;

        if (macroCheckUsing == false) return;
        if ((i1 = CopyTicksRange(Terminal.GetSymbol(), Tick, COPY_TICKS_TRADE, m_Infos.memTimeTick)) > 0)
        {
                if (m_Infos.CountInfos == 0)
                {
                        macroSetInteger(OBJPROP_TIME, m_Infos.StartTime = macroRemoveSec(Tick[0].time));
                        m_Infos.FirstPrice = Tick[0].last;
                }                                               
                for (p1 = 0; (p1 < i1) && (Tick[p1].time_msc == m_Infos.memTimeTick); p1++);
                for (int c0 = p1; c0 < i1; c0++) SetMatrix(Tick[c0]);
                if (p1 == i1) return;
                m_Infos.memTimeTick = Tick[i1 - 1].time_msc;
                m_Infos.CurrentTime = macroRemoveSec(Tick[i1 - 1].time);
                Redraw();
        };      
};

La raison réside dans les parties en surbrillance, mais la pire d'entre elles est l’appel à REDRAW. Cela nuit grandement aux performances de l'EA car à chaque tick reçu avec un volume AU-DESSUS de la valeur spécifiée, la totalité du Volume Au Prix est supprimée de l'écran, recalculée et remise en place. Cela se produit toutes les secondes environ. Cela peut coïncider avec d'autres choses, c'est pourquoi tous les indicateurs sont supprimés de l'EA. Bien que je les ai laissés pour que vous puissiez les utiliser directement dans l'EA, je ne recommande toujours pas de le faire pour les raisons expliquées précédemment.

Ces changements étaient nécessaires. Mais il en est une autre, plus emblématique, et qu’il faut faire. Cette fois le changement concerne l'événement OnTradeTransaction. L'utilisation de cet événement est une tentative de rendre le système aussi flexible que possible. Beaucoup de ceux qui programment des EA pour l'exécution des ordres utilisent l'événement OnTrade, où ils vérifient quels ordres sont ou non sur le serveur, ou quelles positions sont encore ouvertes. Je ne dis pas qu'ils le font mal. C'est juste que ce n'est pas très efficace puisque le serveur nous informe de ce qui se passe. Mais le gros problème de l’événement OnTrade est le fait que nous devons continuer à vérifier inutilement. Si nous utilisons l'événement OnTradeTransaction, nous aurons un système au moins plus efficace en termes d'analyse de mouvement. Mais ce n’est pas l’objectif ici. Chacun utilise la méthode qui correspond le mieux à ses critères.

Lors du développement de cet EA, j'ai décidé de n'utiliser aucune structure de stockage. Et donc de ne pas limiter le nombre d’ordres ou de positions avec lesquels on peut travailler. Mais cela complique tellement la situation qu'une alternative à l'événement OnTrade est nécessaire. On peut la trouver en utilisant l'événement OnTradeTransaction.

Cet événement est très difficile à mettre en œuvre. C'est probablement pourquoi il n'est pas utilisé par beaucoup de monde. Mais je n'avais pas le choix. Soit ça marche, soit ça ne marche pas, sinon les choses seraient compliquées. Mais dans la version précédente, le code de cet événement était très inefficace. Vous pouvez le voir ci-dessous :

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        ulong ticket;
        
        if (trans.symbol == Terminal.GetSymbol()) switch (trans.type)
        {
                case TRADE_TRANSACTION_DEAL_ADD:
                case TRADE_TRANSACTION_ORDER_ADD:
                        ticket = trans.order;
                        ticket = (ticket == 0 ? trans.position : ticket);
                        TradeView.IndicatorInfosAdd(ticket);
                        TradeView.UpdateInfosIndicators(0, ticket, trans.price, trans.price_tp, trans.price_sl, trans.volume, (trans.position > 0 ? trans.deal_type == DEAL_TYPE_BUY : def_IsBuy(trans.order_type)));
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                         if (trans.order != trans.position) TradeView.RemoveIndicator(trans.order);
                         else TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                         if (!PositionSelectByTicket(trans.position)) TradeView.RemoveIndicator(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        TradeView.UpdateInfosIndicators(0, trans.order, trans.price, trans.price_tp, trans.price_sl, trans.volume, def_IsBuy(trans.order_type));
                        break;
                case TRADE_TRANSACTION_POSITION:
                        TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                        break;
        }
                
#undef def_IsBuy
}

Bien que le code ci-dessus fonctionne, il est pour le moins HORRIBLE. Le nombre d'appels inutiles générés par le code ci-dessus est insensé. Rien ne peut améliorer l'EA en termes de stabilité et de fiabilité si le code ci-dessus ne peut pas être corrigé.

Pour cette raison, j'ai fait quelques petites choses sur un compte de démo pour essayer de trouver un modèle dans les messages, ce qui est en fait assez difficile. Je n'ai pas trouvé de modèle. Mais j'ai trouvé quelque chose qui évitait la folie des appels inutiles générés, rendant le code stable, fiable et en même temps suffisamment flexible pour pouvoir trader à tout moment sur le marché. Bien sûr, il reste encore quelques petits bugs à corriger. Mais le code est très bon :

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
        if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
        {
                case TRADE_ACTION_PENDING:
                        TradeView.IndicatorAdd(request.order);
                        break;
                case TRADE_ACTION_SLTP:
                        TradeView.UpdateIndicators(request.position, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
                case TRADE_ACTION_DEAL:
                        TradeView.RemoveIndicator(request.position);
                        break;
                case TRADE_ACTION_REMOVE:
                        TradeView.RemoveIndicator(request.order);
                        break;
                case TRADE_ACTION_MODIFY:
                        TradeView.UpdateIndicators(request.order, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
        }
                        
#undef def_IsBuy
}

N'essayez pas de comprendre ce qui se passe tout de suite. Profitez simplement de la beauté de cette fonctionnalité. C'est presque la perfection vivante. Je dis cela, non pas parce que c'est moi qui l’ai fait, mais à cause de son degré de robustesse et d'agilité.

Même si cela peut paraître compliqué, il y a 2 vérifications dans ce code. Ils sont mis en évidence ci-dessous pour mieux expliquer ce qui se passe.

if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
{

//... inner code ...

}

La ligne surlignée en VERT vérifiera à chaque fois qu'une transaction se produit dans l'historique pour voir si son actif est le même que l'actif observé par l'EA. Si c’est le cas, la classe C_IndicatorTradeView recevra une commande pour supprimer l'indicateur du graphique. Cela peut se produire dans 2 cas : lorsqu'un ordre devient une position et lorsqu'une position se clôture. Veuillez noter que j'utilise uniquement le mode NETTING et non le monde HEDGING. Ainsi, quoi qu’il arrive, l’indicateur sera supprimé du graphique.

On pourrait se demander : Si la position est fermée, ce n'est pas grave. Mais que se passe-t-il si l’ordre devient une position : serai-je impuissant ? Non. Mais le problème est résolu, non pas dans l’erreur mais dans la classe C_IndicatorTradeView. Nous l'examinerons dans la section suivante de l'article.

La ligne surlignée en rouge, en revanche, réduit énormément la quantité de messages inutiles transmis à la classe C_IndicatorTradeView. Cela se fait en vérifiant la réponse renvoyée par le serveur. Nous devons donc obtenir une confirmation en émettant la demande avec le même nom de l'actif que l'EA suit. Ce n'est qu'alors qu'une nouvelle série d'appels sera envoyée à la classe C_IndicatorTradeView.

C'est tout ce que je peux dire sur ce système. Mais l’histoire n’est pas encore terminée. Nous avons beaucoup de travail devant nous, et à partir de maintenant nous nous concentrerons uniquement sur la classe C_IndicatorTradeView. Nous allons commencer maintenant par quelques changements qui doivent être apportés.


2.0.5. Réduire le nombre d'objets créés par C_IndicatorTradeView

Dans l'article Développer un Expert Advisor de trading à partir de zéro (Partie 23), j'ai introduit un concept plutôt abstrait mais très intéressant de déplacement des ordres ou des niveaux d'arrêt. Le concept était d'utiliser des positions fantômes ou ombres. Elles définissent et affichent sur le graphique ce que le serveur de trading voit, et sont utilisées jusqu'à ce que le mouvement réel se produise. Ce modèle a un petit problème : il ajoute des objets à gérer par MetaTrader 5. Mais les objets ajoutés ne sont pas nécessaires dans la plupart des cas. Donc MetaTrader 5 obtient une liste d'objets qui est souvent pleine de choses inutiles ou rarement utilisées.

Mais nous ne voulons pas que l'EA crée constamment des objets ou qu'il conserve des objets inutiles dans la liste, car cela dégrade les performances de l'EA. Puisque nous utilisons MetaTrader 5 pour gérer les ordres, nous devons éliminer les objets inutiles qui interfèrent avec l'ensemble du système.

Mais il existe pourtant une solution très simple. Enfin, ce n'est pas si simple. Nous apporterons d'autres modifications à la classe C_IndicatorTradeView pour l'améliorer. Nous garderons les fantômes à l'écran et nous utiliserons une méthode très curieuse et peu utilisée.

Ce sera amusant et intéressant.

Nous allons tout d’abord modifier la structure de sélection. Cela ressemblera maintenant à ceci :

struct st00
{
        eIndicatorTrade it;
        bool            bIsBuy,
			bIsDayTrade;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl;
}m_Selection;

Je ne vous dirai pas exactement ce qui a changé – vous devriez pouvoir le comprendre par vous-même. Mais les changements ont simplifié certains points de la logique de codage.

Notre indicateur fantôme aura maintenant désormais son propre index :

#define def_IndicatorGhost      2

Pour cette raison, la modélisation du nom a également changé :

#define macroMountName(ticket, it, ev) StringFormat("%s%c%llu%c%c%c%c", def_NameObjectsTrade, def_SeparatorInfo,\
                                                                        ticket, def_SeparatorInfo,              \
                                                                        (char)it, def_SeparatorInfo,            \
                                                                        (char)(ticket <= def_IndicatorGhost ? ev + 32 : ev))

Cela semble être une petite chose, mais cela va bientôt beaucoup changer. Continuons.

Désormais, les macros de position de prix sont toujours directes, il n'y a plus de duplications, donc notre code ressemble maintenant à ceci :

#define macroSetLinePrice(ticket, it, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE, price)
#define macroGetLinePrice(ticket, it) ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE)

Ces changements nous ont obligés à créer 2 autres fonctions. La première est le remplacement de la fonction qui crée les indicateurs eux-mêmes. Cela a littéralement montré clairement ce qui différenciait réellement un indicateur d’un autre. La voici :

#define macroCreateIndicator(A, B, C, D)        {                                                                       \       
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                             \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : 92), (A == IT_RESULT ? 34 : 22));                         \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                           \
                m_EditInfo1.Size(sz0, 60, 14);                                                                          \
                if (A != IT_RESULT)     {                                                                               \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);    \
                        m_BtnMove.Size(sz0, 21, 23);                                                                    \
                                        }else                   {                                                       \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);           \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                               \
                                                }

                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING : macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit); break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

Vous avez peut-être remarqué que j'aime utiliser des directives de prétraitement dans mon code. Je le fais presque tout le temps. Et comme vous pouvez le constater, il est désormais assez simple de différencier les indicateurs. Si vous souhaitez donner à l'indicateur les couleurs souhaitées, modifiez ce code. Comme ils sont tous presque identiques, grâce aux macros, nous pouvons les faire fonctionner de la même manière et avoir les mêmes éléments. Il s’agit d’une réutilisation ultime du code.

Il existe une autre fonction dont le nom est très similaire à celui-ci. Mais cela fait quelque chose de différent, et j'en parlerai en détail à la fin.

La fonction IndicatorAdd a été modifiée : nous y avons supprimé certains fragments.

inline void IndicatorAdd(ulong ticket)
                        {
                                char ret;
                                
                                if (ticket == def_IndicatorTicket0) ret = -1; else
                                {
                                        if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_PENDING, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                                        if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_RESULT, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                                        if ((ret = GetInfosTradeServer(ticket)) == 0) return;
                                }
                                switch (ret)
                                {
                                        case  1:
                                                CreateIndicatorTrade(ticket, IT_RESULT);
                                                PositionAxlePrice(ticket, IT_RESULT, m_InfoSelection.pr);
                                                break;
                                        case -1:
                                                CreateIndicatorTrade(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_InfoSelection.pr);
                                                break;
                                }
                                ChartRedraw();
                                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
				UpdateIndicators(ticket, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        } 

L'un des fragments supprimés est remplacé par celui en surbrillance. Cela signifie-t-il que les indicateurs d'ordre en attente et 0 ne seront plus créés ? Ils sont toujours créés mais dans un endroit différent. Il y a donc une autre fonction à venir.

La voici : la fonction qui crée des indicateurs d’ordres en attente et l'indicateur 0. Le code de UpdateIndicators est le suivant :

#define macroUpdate(A, B) { if (B > 0) {                                                                \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                        } else RemoveIndicator(ticket, A); }
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetLinePrice(ticket, IT_RESULT);
                                        if ((pr == 0) && (macroGetLinePrice(ticket, IT_PENDING) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                if (m_Selection.tp > 0) macroUpdate(IT_TAKE, tp);
                                if (m_Selection.sl > 0) macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

La fonction a une vérification très intéressante, mise en évidence dans le code. Cela aidera à créer des indicateurs fantômes, de sorte que la fonction IndicatorAdd ne pourra plus créer d'indicateurs d'ordres en attente et d'indicateur 0. Mais cette simple vérification ne suffit pas à créer un indicateur fantôme.

La fonction DispatchMessage inclut désormais quelques détails. Ce sont de petits changements, mais ils nous facilitent grandement la vie. Je vais montrer les parties qui ont été changées :

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{

// ... Code ....

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

// ... Code ....
                        }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;

// ... Code ...

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:

// ... Code ...

                                        break;
                                case EV_MOVE:
                                        CreateGhostIndicator(ticket, it);
                                        break;
                        }
                break;
        }
}

CHARTEVENT_MOUSE_MOVE a une partie modifiée. Ce code vérifiera si nous travaillons avec le fantôme. S'il s'agit d'un fantôme, le fragment est bloqué. Mais si ce n’est pas le cas, le mouvement est possible (à condition que l’indicateur lui-même puisse bouger).

Dès que l'on clique sur la nouvelle position de l'indicateur, le fantôme et tous ses composants seront supprimés de la liste des objets. Je pense que cela est clair. Faites maintenant attention au point mis en surbrillance : il s'agit de l'appel à la fonction CreateGhostndicator . Nous discuterons de ce code dans la section suivante.


2.0.6. Comment fonctionne CreateGhostIndicator

CreateGhostIndicator semble être une fonction étrange. Regardons son code ci-dessous :

CreateGhostIndicator

#define macroSwapName(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorGhost, A, B));
                void CreateGhostIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                if (GetInfosTradeServer(m_Selection.ticket = ticket) != 0)
                                {
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                        macroSwapName(it, EV_LINE);
                                        macroSwapName(it, EV_GROUND);
                                        macroSwapName(it, EV_MOVE);
                                        macroSwapName(it, EV_EDIT);
                                        macroSwapName(it, EV_CLOSE);
                                        m_TradeLine.SetColor(macroMountName(def_IndicatorGhost, it, EV_LINE), def_IndicatorGhostColor);
                                        m_BackGround.SetColor(macroMountName(def_IndicatorGhost, it, EV_GROUND), def_IndicatorGhostColor);
                                        m_BtnMove.SetColor(macroMountName(def_IndicatorGhost, it, EV_MOVE), def_IndicatorGhostColor);
                                        ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));
                                        m_TradeLine.SpotLight();
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                                        m_Selection.it = it;
                                }else m_Selection.ticket = 0;
                        }
#undef macroSwapName

Il est très intéressant que rien ne soit créé dans cette fonction. Pourtant, si l'EA est compilé et exécuté, il créera des fantômes qui afficheront l'état de l’ordre sur le serveur. Pour comprendre cela, regardez la vidéo suivante. Il s'agit d'une démonstration de la façon dont le système fonctionne dans la réalité.



Des indicateurs fantômes sont en réalité créés sur le graphique. Mais comment cela se produit-il réellement ? Comment avons-nous réussi à créer des indicateurs sans les créer quelque part dans le code ?

Ce sont des fantômes. Vous ne les verrez pas réellement se créer. Cela ne sert à rien de lire le code en essayant de trouver la ligne qui dit : "ICI... J'ai trouvé... des indicateurs fantômes sont créés ici..." La vérité est qu’ils sont simplement déjà sur la carte. Mais ils ne sont affichés nulle part jusqu’à ce que nous commencions à manipuler l’ordre ou la position – alors seulement ils deviennent visibles. Comment est-ce possible ?

Pour le comprendre, considérons le fil d'exécution de l'EA.

Après l'initialisation de l'EA, nous voyons le thread d'exécution suivant :

Thread 1

init_ea<<<< Thread d'initialisation du système

La zone orange fait partie de l'EA, et la zone verte fait partie de la classe C_IndicatorTradeView. Voyez ce qui se passe avant que l'indicateur ne soit créé et affiché à l'écran. Les flèches noires représentent le chemin classique pour les ordres en attente et les positions ouvertes ; la flèche bleue est le chemin des positions, et les flèches violettes montrent le chemin emprunté par les ordres en attente pour créer leurs indicateurs. Bien sûr, il y a des éléments à l'intérieur des fonctions qui dirigent le thread d'une manière ou d'une autre. Mais le diagramme ici est destiné à montrer comment tout fonctionne en termes généraux.

Le schéma précédent n'est utilisé qu'une seule fois et uniquement lors du démarrage du système. Désormais, chaque fois que nous allons placer un ordre en attente sur le graphique, nous aurons 2 threads d'exécution différents : le premier est chargé de créer l'indicateur 0 et d'essayer de passer l'ordre sur le graphique. Ceci est illustré dans la figure ci-dessous :

Thread 2

     <<<< Thread d'initialisation de l'indicateur 0

Attention, ce n'est pas vraiment la classe qui va créer l'ordre qui apparaît sur le graphique. Elle essaiera seulement de le faire. Si tout se passe bien, la fonction SetPriceSelection sera exécutée avec succès, et un nouveau thread sera créé, qui présentera l'ordre sur le graphique. Nous obtiendrons ainsi le fil suivant. Il passera en fait l’ordre à l'endroit indiqué par le serveur de trading. Il ne sert donc à rien d'attendre que l’ordre aboutisse réellement à l'endroit que nous avons initialement spécifié. Si la volatilité amène le serveur à exécuter l’ordre à un moment différent de celui que nous avons indiqué, l'EA corrigera cela et présentera l’ordre au bon endroit. Il vous suffira ainsi d’analyser si les conditions sont adaptées à votre modèle de trading.

Thread 3

     <<< Thread du placement d’un ordre en attente

Il s'agit simplement de la partie chargée de placer l’ordre sur le graphique. Je parle ici d'un ordre complet, c'est-à-dire qu'il aura un point d'entrée, un take profit et un stop loss. Mais comment sera le thread si l’un des ordres limites, qu’il s’agisse du take profit ou du stop loss, est supprimé de l’ordre ? Ces threads n’y répondent pas. En fait, le thread sera assez différent, mais les éléments seront quasiment les mêmes. Voyons ci-dessous à quoi ressemblera le flux si vous cliquez sur le bouton pour clôturer l'un des ordres limites.

Cela peut paraître étrange.

Thread 4

     <<< Supprimer un ordre ou des niveaux d'arrêt

Nous avons 2 threads côte à côte. Celui marqué d'une flèche violette sera exécuté en premier. Dès son exécution, l'événement OnTradeTransaction capturera la réponse du serveur et déclenchera la suppression de l'indicateur de l'écran par le système. Il n'y a qu'une seule différence entre la suppression d'ordres stop et la clôture d'une position ou d'un ordre : dans ces cas, la fonction SetPriceSelection ne sera pas exécutée, mais le flux d'événements OnTradeTransaction restera.

Tout cela est merveilleux, mais cela ne répond toujours pas à la question de savoir comment apparaissent les fantômes.

Pour comprendre comment les fantômes sont créés, nous devons savoir comment se déroule le fil d’exécution : comment l’EA passe un ordre en attente ou comment se produit dans la pratique la création de l’indicateur 0. Ce flux est illustré dans la figure ci - dessus. Si vous comprenez les fils d’exécution (threads), il vous sera plus facile de comprendre les fantômes.

Voyons enfin comment se créent les fantômes. Regardez à nouveau la fonction CreateGhostIndicator. Elle ne crée rien, mais manipule simplement certaines données. Mais pourquoi ? Parce que si nous essayons de créer un objet, il sera superposé aux objets existants et dessiné par-dessus. Les objets requis seront ainsi masqués. Il y a 2 solutions pour ce problème. La première est de créer un ensemble en-dessous à tous les autres. Il sera créé avant tout autre objet représentant des ordres. Mais cette solution pose un problème. Nous aurons beaucoup d’objets inutiles. Et nous modifions tout le code pour éviter cela. La deuxième solution consiste à créer un fantôme, puis à supprimer le pointeur que nous manipulons, puis à le recréer. Aucune de ces solutions n’est très pratique et elles sont toutes deux assez coûteuses.

En étudiant la documentation, j'ai trouvé une information qui a retenu mon attention : la fonction ObjectSetString permet de manipuler la propriété d'un objet qui n'a pas de sens à première vue : OBJPROP_NAME. J'ai été intrigué par la raison pour laquelle cela est autorisé. Cela n'a aucun sens. Si l'objet a déjà été créé, à quoi bon changer son nom ?

Le point est le suivant. Lorsque nous renommons un objet, l'ancien objet cesse d'exister et prend un nouveau nom. Après avoir été renommé, l'objet prend la place de l'objet original, de sorte que l'EA peut créer l'objet original sans problème, et le fantôme peut apparaître et disparaître sans effets secondaires pour les graphiques et sans laisser de traces. Le seul objet qui doit être supprimé est le bouton de fermeture de l'indicateur. Cela se fait avec cette ligne :

ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));

Il y a un détail mineur ici. En regardant la documentation de la fonction ObjectSetString, nous voyons un avertissement concernant son fonctionnement :

Lors du renommage d'un objet graphique, 2 événements sont générés simultanément. Ces événements peuvent être traités dans l'EA ou dans l'indicateur à l'aide de la fonction OnChartEvent()  :

  • évènement de suppression d'un objet avec un ancien nom
  • événement de création d'un objet avec un nouveau nom

Il est important d'en tenir compte car nous ne voulons pas que l'objet que nous sommes sur le point de renommer apparaisse simplement si nous n'y sommes pas prêts. Nous ajoutons donc une chose supplémentaire avant et après le changement de nom :

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);

// ... Secure code...

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);

Tout ce qui se trouve à l'intérieur du code ne déclenchera pas d'événements de création et de suppression d'objets. Nous avons maintenant le code complet où les fantômes apparaîtront et nous aurons le bon comportement.

Peut-être n'est-il pas encore clair comment le code crée réellement des fantômes en renommant simplement l'indicateur. Je vais vous laisser ici. Pour vous aider un peu, je vais vous montrer à quoi ressemble le thread des fantômes. Ceci est montré dans l’image ci-dessous :

Thread 5

    <<<< Thread fantôme

Notez qu'il s'agit d'un clone presque parfait du thread 2. Vous pouvez donc déjà vous amuser en sachant comment les fantômes sont créés et détruits, mais sans réellement écrire de code de création.


Conclusion

En tant qu'auteur, j'ai trouvé cet article assez intéressant et même passionnant. Nous avons dû changer beaucoup le code de l’EA. Mais tout cela est pour le mieux. Il reste encore quelques éléments et mesures à prendre pour le rendre encore plus fiable. Mais les changements déjà mis en œuvre bénéficieront grandement à l’ensemble du système. Je tiens à souligner qu'un programme bien conçu passe généralement par certaines étapes qui ont été mises en œuvre ici : étudier la documentation, analyser les threads d'exécution, comparer le système pour voir s'il est surchargé aux moments critiques, et surtout, rester calme pour ne pas transformer le code en un véritable monstre. Il est très important d'éviter de transformer notre code en une copie de Frankenstein. Cela n'améliorera pas le code, et rendra seulement les améliorations futures et surtout les corrections plus difficiles.

Un gros câlin à tous ceux qui suivent cette série. J'espère vous voir dans le prochain article, car nous n'avons pas encore terminé et il reste encore beaucoup à faire.



Traduit du portugais par MetaQuotes Ltd.
Article original : https://www.mql5.com/pt/articles/10606

Algorithmes d'optimisation de la population : Optimisation de la Lutte contre les Mauvaises Herbes Invasives (Invasive Weed Optimization, IWO) Algorithmes d'optimisation de la population : Optimisation de la Lutte contre les Mauvaises Herbes Invasives (Invasive Weed Optimization, IWO)
L'étonnante capacité des mauvaises herbes à survivre dans une grande variété de conditions est devenue l'idée d'un puissant algorithme d'optimisation. L'IWO est l'un des meilleurs algorithmes parmi ceux qui ont été examinés précédemment.
Évaluation des modèles ONNX à l'aide de mesures de régression Évaluation des modèles ONNX à l'aide de mesures de régression
La régression consiste à prédire une valeur réelle à partir d'un exemple non étiqueté. Les mesures dites de régression sont utilisées pour évaluer la précision des prédictions des modèles de régression.
Apprendre à concevoir un système de trading basé sur les Fractales Apprendre à concevoir un système de trading basé sur les Fractales
Voici un nouvel article de notre série sur la façon de concevoir un système de trading basé sur les indicateurs techniques les plus populaires. Nous apprendrons un nouvel indicateur, l'indicateur Fractals, et nous apprendrons comment concevoir un système de trading basé sur celui-ci pour être exécuté dans le terminal MetaTrader 5.
Développer un Expert Advisor de trading à partir de zéro (Partie 24) : Assurer la robustesse du système (I) Développer un Expert Advisor de trading à partir de zéro (Partie 24) : Assurer la robustesse du système (I)
Dans cet article, nous allons rendre le système plus fiable afin d’en garantir une utilisation robuste et sûre. L'un des moyens d'obtenir la robustesse souhaitée est d'essayer de réutiliser le code autant que possible afin qu'il soit constamment testé dans différents cas. Mais ce n'est qu'un moyen parmi d'autres. Une autre solution consiste la POO.