English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
preview
Développer un Expert Advisor de trading à partir de zéro (partie 14) : Ajout du Volume Au Prix (II)

Développer un Expert Advisor de trading à partir de zéro (partie 14) : Ajout du Volume Au Prix (II)

MetaTrader 5Systèmes de trading | 27 janvier 2023, 13:56
1 090 0
Daniel Jose
Daniel Jose

Introduction

Notre EA dispose déjà de certaines ressources ajoutées dans nos articles précédents et qui aident au trading. Cependant, cet EA présente quelques problèmes de visualisation et de redimensionnement. Ils n'interfèrent pas avec les trades, mais à certains moments, cela entraîne un désordre à l'écran jusqu'à ce que vous le forciez à se rafraîchir. Il manque encore également certains éléments qui nous fourniraient des informations précieuses. Ce sont des choses spécifiques, mais dont l'information peut être nécessaire.

Commençons donc à mettre en œuvre ces nouvelles améliorations. Ce nouvel article peut apporter de nouvelles idées et des méthodes de présentation des informations. Et il peut aussi aider à corriger les défauts mineurs des projets.


Planification et mise en œuvre d'une nouvelle fonction dans Volume Au Prix

1. Planification

Il y a une chose curieuse à propos du trading. Nous constatons souvent que le marché s'accumule dans certaines régions de prix. Et lorsque les stops se déclenchent, que ce soit à l'achat ou à la vente, on assiste à un mouvement rapide du prix. Ce mouvement peut être observé dans Times & Trade. Nous avons examiné cette question dans les articles Times & Trade (I) et Times & Trade (II) précédents. Dans ces articles, nous avons vu comment créer un autre système graphique pour lire et analyser un flux d'ordres exécutés. Si vous regardez de plus près, vous remarquerez qu'à certains moments, le prix a tendance à revenir dans la région d'accumulation, qu'il ne veut pas quitter à ce moment-là. Mais lorsque nous observons l'indicateur Volume au Prix (Volume At Price), il est difficile de déterminer à quelle date le prix est resté inchangé dans cette région spécifique. Cet indicateur a été implémenté dans l'article "Ajout du Volume Au Prix (I)". Avec lui, nous pouvons analyser des mouvements relativement récents en changeant simplement le point de départ de l'analyse, ce qui se fait en ajustant la valeur de l'objet indiqué dans la figure ci-dessous :

Mais cela n'est pas pratique, car nous sommes liés à l'échelle de temps principale. C'est-à-dire que si vous avez un graphique de 60 minutes, vous ne pourrez pas analyser les mouvements de prix en dessous de cette échelle de temps. Vous devez passer à une échelle de temps inférieure pour pouvoir ajuster le point d'analyse. Mais lorsqu'ils traders des contrats à terme, la plupart des traders utilisent en fait des échelles de temps inférieures, telles que 5, 10 ou 30 minutes, de sorte que l'ajustement du point de départ de l'analyse ne pose aucun problème. Mais comme je l'ai expliqué précédemment, il arrive que le prix sorte de l'accumulation parce que les stops se sont déclenchés. Et un tel retour se produit généralement en moins de 5 minutes. Dans ce cas, un chandelier avec une longue ombre supérieure ou inférieure apparaît sur le graphique. Dans ces cas, le Price Action nous indique que ce qui s'est passé est un sondage du marché. Ce type de mouvement peut être vu ci-dessous sur les chandeliers indiqués par des flèches :

   

Déclenchement typique d'un mouvement de test des acheteurs ou du déclenchement des stops à découvert

 

Déclenchement typique d'un mouvement de test des vendeurs ou du déclenchement des stops des acheteurs

Ce type de mouvement se produit fréquemment, et l'analyse du volume généré dans chacune des fourchettes de prix est très importante car elle permet de comprendre si le marché est en train de tester un niveau ou si la tendance est réellement en train de s'inverser. Mais il est impossible de le faire correctement, ou plutôt rapidement, en utilisant l'indicateur de Volumes proposé précédemment.

Mais nous pouvons apporter une petite modification à la classe d'objet de l’indicateur pour avoir une idée plus claire de ce qui se passe. Cela apparaîtra comme la trace d'un trade qui a eu lieu dans la période de temps donnée.


2. Implémentation

La première chose à faire est d'analyser le temps de suivi que vous souhaitez définir, que ce soit 1, 7, 19, 30, 45 ou 60 minutes. Indépendamment de cela, nous recommandons d'utiliser des valeurs suffisamment multiples pour que le système de suivi puisse être réellement utile. Pour des raisons pratiques, nous allons l'implémenter en utilisant un suivi de 30 minutes. Nous allons le définir dans la ligne de code suivante :

#define def_MaxTrailMinutes     30

Mais pourquoi 30 minutes ? En fait, le suivi sera effectué toutes les minutes, mais la durée maximale du suivi sera de 30 minutes. C'est-à-dire que vous aurez toujours un suivi sur 30 minutes, par exemple, mais lorsque le suivi passe à 31 minutes, la première minute de trading ne sera plus affichée. Comment cela est-il mis en œuvre ? Ce système utilise un système de capture comme illustré ci-dessous :

inline void SetMatrix(MqlTick &tick)
{
        int pos;
                                
        if ((tick.last == 0) || ((tick.flags & (TICK_FLAG_BUY | TICK_FLAG_SELL)) == (TICK_FLAG_BUY | TICK_FLAG_SELL))) return;
        pos = (int) ((tick.last - m_Infos.FirstPrice) / Terminal.GetPointPerTick()) * 2;
        pos = (pos >= 0 ? pos : (pos * -1) - 1);
        if ((tick.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY) m_InfoAllVaP[pos].nVolBuy += tick.volume; else
        if ((tick.flags & TICK_FLAG_SELL) == TICK_FLAG_SELL) m_InfoAllVaP[pos].nVolSell += tick.volume;
        m_InfoAllVaP[pos].nVolDif = (long)(m_InfoAllVaP[pos].nVolBuy - m_InfoAllVaP[pos].nVolSell);
        m_InfoAllVaP[pos].nVolTotal = m_InfoAllVaP[pos].nVolBuy + m_InfoAllVaP[pos].nVolSell;
        m_Infos.MaxVolume = (m_Infos.MaxVolume > m_InfoAllVaP[pos].nVolTotal ? m_Infos.MaxVolume : m_InfoAllVaP[pos].nVolTotal);
        m_Infos.CountInfos = (m_Infos.CountInfos == 0 ? 1 : (m_Infos.CountInfos > pos ? m_Infos.CountInfos : pos));
        m_Infos.Momentum = macroGetMin(tick.time);
        m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);
        if (m_Infos.memMomentum != m_Infos.Momentum)
        {
                for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++) m_TrailG30[m_Infos.Momentum].nVolume[c0] = 0;
                m_Infos.memMomentum = m_Infos.Momentum;
        }
        m_TrailG30[m_Infos.Momentum].nVolume[pos] += tick.volume;
}

Les lignes en surbrillance sont celles qui ont été ajoutées au code source de la classe d'objets : elles mettent en œuvre la capture de la trace du volume. Les lignes ci-dessous garantissent que le suivi sera effectué comme on le veut.

m_Infos.Momentum = macroGetMin(tick.time);
m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);

Le système de capture de traces est maintenant prêt. Nous devons maintenant prendre une nouvelle décision. Rappelez-vous que la trace est capturée toutes les minutes. Cela peut être présenté de manière à ce que nous puissions voir le volume à chaque intervalle de prix en 1 minute. Vous pouvez également envisager de faire quelque chose comme ci-dessous :

 

Les teintes claires représentent des volumes plus récents, ce qui peut être une bonne idée...

Bien que cela semble effectivement être une bonne idée, lorsque le volume est faible ou que le mouvement est très rapide, même avec un volume expressif, il peut en fait ne pas être visible car il sera tracé en s'ajustant au volume maximum qui a été trouvé jusqu'à présent. Vous pourriez donc vouloir tracer un peu différemment pour résoudre ce problème :

Chaque couleur dans la trace du volume représente une période spécifique.

Cela peut aider à analyser des bandes très étroites de volume, et corrige aussi les problèmes occasionnels observés dans le premier cas. Mais nous avons toujours un problème d'ajustement qui se produit lorsque le volume n'est pas aussi expressif par rapport au volume global à un autre moment. Les couleurs pour chaque période doivent également être choisies avec soin afin que l'analyse ne soit pas confuse lors de transactions très actives.

Nous utiliserons donc ici un modèle plus simple, qui peut à nouveau être ajusté pour analyser les mouvements de différentes périodes. Mais gardez à l'esprit les problèmes mentionnés ci-dessus. C'est à vous de décider. La trace s'affiche maintenant comme suit :

Nous avons ici une trace pure. Lorsque cela se produit, nous devons analyser à la fois le Times & Trade et le Price Action pour comprendre ce qui se passe.

La seule fonction qui doit être modifiée pour changer l'affichage du volume est la fonction ci-dessous :

void Redraw(void)
{
        uint            x, y, y1, p;
        double  reason = (double) (m_Infos.MaxVolume > m_WidthMax ? (m_WidthMax / (m_Infos.MaxVolume * 1.0)) : 1.0);
        double  desl = Terminal.GetPointPerTick() / 2.0;
        ulong           uValue;
                                
        Erase();
        p = m_WidthMax - 8;
        for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++)
        {
                if (m_InfoAllVaP[c0].nVolTotal == 0) continue;
                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, m_Infos.FirstPrice + (Terminal.GetPointPerTick() * (((c0 & 1) == 1 ? -(c0 + 1) : c0) / 2)) + desl, x, y);
                y1 = y + Terminal.GetHeightBar();
                FillRectangle(p + 2, y, p + 8, y1, macroColorRGBA(m_InfoAllVaP[c0].nVolDif > 0 ? m_Infos.ColorBuy : m_Infos.ColorSell, m_Infos.Transparency));
                FillRectangle((int)(p - (m_InfoAllVaP[c0].nVolTotal * reason)), y, p, y1, macroColorRGBA(m_Infos.ColorBars, m_Infos.Transparency));
                uValue = 0;
                for (int c1 = 0; c1 < def_MaxTrailMinutes; c1++) uValue += m_TrailG30[c1].nVolume[c0];
                FillRectangle((int) (p - (uValue * reason)), y, p, y1, macroColorRGBA(clrRoyalBlue, m_Infos.Transparency));
        }
        C_Canvas::Update();
};

Et pour être plus précis, seul le code mis en évidence doit être modifié. Vous pouvez jouer avec jusqu'à ce que vous obteniez le résultat que vous souhaitez. Rien d'autre dans la classe ne doit être modifié. Après avoir compilé le programme et l'avoir exécuté sur le graphique, vous verrez quelque chose comme ceci :



Résoudre le problème de rendu

Bien que le code ne présente pas de problèmes particuliers, il y a un petit défaut lors du redimensionnement d'un graphique : lorsqu'un graphique maximisé est redimensionné à une taille plus petite, puis à nouveau maximisé, certains objets semblent perdus, ne se comportent pas comme prévu, et se positionnent à de mauvais endroits. Mais il n'y a pas grand chose à corriger. Le problème se trouve dans le code ci-dessous (nous l'avons utilisé dans des articles précédents).

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        ChartRedraw();
}

Il y a une modification très simple. Mais il est possible que vous pouvez pensiez "je ne vois rien - le code est correct". À première vue, je ne vois rien d'anormal non plus. Et le code reste avec une erreur d'exécution. Mais en ajoutant quelques fonctionnalités supplémentaires, j'ai remarqué le problème, qui est exactement celui que j'ai décrit ci-dessus. Pour le résoudre, le code doit être modifié comme suit :

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}

Cela peut paraître idiot, mais pour le comprendre, il suffit de voir l'ensemble du code de la fonction et la partie mise en évidence. Maintenant que le système est réparé, nous pouvons passer à l'étape suivante.


Ajout de ressources supplémentaires

La fonction que nous allons ajouter maintenant est très simple et beaucoup ne verront peut-être pas de raison de l'implémenter. Mais elle sera très utile lorsque vous travaillerez avec des ordres, notamment en terme de positionnement, de déplacement ou simplement de l'observation de l'indicateur Volume Au Prix (Volume At Price).

La première chose à faire est de modifier la classe dont fera partie le code de modification de la ligne de prix. Ce code provient de la classe C_OrderView et entre dans la classe C_Terminal. Mais pour cela, il y a quelques petites modifications, puisqu'il commence à travailler avec les variables de la classe elle-même. Voici à quoi ressemblera le nouveau code :

double AdjustPrice(const double arg)
{
        double v0, v1;
                                
        if(m_Infos.TypeSymbol == OTHER) return arg;
        v0 = (m_Infos.TypeSymbol == WDO ? round(arg * 10.0) : round(arg));
        v1 = fmod(round(v0), 5.0);
        v0 -= ((v1 != 0) || (v1 != 5) ? v1 : 0);
        return (m_Infos.TypeSymbol == WDO ? v0 / 10.0 : v0);
};

Avec cela, nous pouvons créer une nouvelle classe dans l’EA : la classe C_Mouse. Un objet de cette classe sera en charge et à la base des événements de la souris. Alors voyons comment cela se passe à ce stade du développement. Mais regardons d'abord la structure de classe actuelle de notre Expert Advisor, montrée dans la figure ci-dessous :

Pour mettre en œuvre le prochain système, il est nécessaire d'introduire une nouvelle structure.

Compte tenu de la structure décrite ci-dessus, décomposons le code de la classe d'objets C_Mouse, en commençant par la déclaration des variables :

class C_Mouse
{
        private  :
                struct st00
                {
                color   cor01,
                        cor02,
                        cor03;
                string  szNameObjH,
                        szNameObjV,
                        szNameObjT,
                        szNameObjI,
                        szNameObjB;
                }m_Infos;
                struct st01
                {
                        int      X,
                                 Y;
                        datetime dt;
                        double   price;
                        uint     ButtonsStatus;
                }Position;

Étant donné qu'il y a peu de besoins à ce stade du développement, passons au point suivant qui mérite notre attention :

~C_Mouse()
{
// ... Internal code ...
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, true);
}

Ce code rétablit le réticule CHART_CROSSHAIR_TOOL et désactive l'utilisation des événements de la souris par le graphique. Ceci signifie que MT5 ne doit plus se soucier de l'envoi de cs événements au graphique car ils seront gérés par la plateforme elle-même.

Nous avons également 2 fonctions très courantes utilisées lorsque nous allons contrôler la souris :

inline void Show(void)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_COLOR, m_Infos.cor01);
}
//+------------------------------------------------------------------+
inline void Hide(void)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, clrNONE);
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, 0, 0);
}

Ce qui est intéressant, c'est que la souris ne disparaît pas réellement. Seuls les objets que nous créons disparaissent de l'écran, et lorsque nous "allumons" la souris, seule la ligne de prix sera réellement visible. Cela peut sembler curieux, mais cela a son utilité dans certains points spécifiques de l'EA. L'un de ces points est l'objet de classe C_OrderView dans la partie mise en évidence dans le code ci-dessous :

inline void MoveTo(uint Key)
{
        static double local = 0;
        int w = 0;
        datetime dt;
        bool bEClick, bKeyBuy, bKeySell;
        double take = 0, stop = 0, price;
                                
        bEClick  = (Key & 0x01) == 0x01;    //Left click
        bKeyBuy  = (Key & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (Key & 0x08) == 0x08;    //CTRL pressed
        Mouse.GetPositionDP(dt, price);
        if (bKeyBuy != bKeySell) Mouse.Hide(); else Mouse.Show();
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1)));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1)));
        if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade); else local = 0;
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE));
};

Faites attention à la ligne juste au-dessus de la partie surlignée :

Mouse.GetPositionDP(dt, price);

Cette ligne va capturer la valeur de la position de la souris. Voici le code qui retournera ces valeurs :

inline void GetPositionDP(datetime &dt, double &price)
{
        dt = Position.dt;
        price = Position.price;
}

Mais ce n'est pas tout. Dans certains cas, nous avons besoin des coordonnées cartésiennes du graphique en terme de position à l'écran. Il existe une autre fonction qui permet d'obtenir les bonnes valeurs. Le voici :

inline void GetPositionXY(int &X, int &Y)
{
        X = Position.X;
        Y = Position.Y;
}

Pour en revenir à la classe C_OrderView, nous devons revenir sur un point intéressant :

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eHLineTrade     hl;
                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        MoveTo(Mouse.GetButtonStatus());
                        break;

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

}

La fonction MoveTo est visible un peu plus tôt dans cette fonction. Elle fait également partie de la classe C_OrderView. Mais la fonction la plus importante est Mouse.GetButtonsStatus. Cette fonction renvoie l'état des boutons et des touches associés aux événements de la souris.

LA fonction Mouse.GetButtonStatus est présentée ci-dessous :

inline uint GetButtonStatus(void) const
{
        return Position.ButtonsStatus;
}

Il ne s'agit que d'une ligne qui renvoie une variable contenant les valeurs enregistrées depuis le dernier événement de la souris. Voyons maintenant le code qui enregistre cette valeur. Mais d'abord, regardons le code d'initialisation de la souris. Il doit indiquer à l'EA que nous voulons initialiser la souris et qu'à partir de maintenant, l'EA gérera diverses choses liées à la souris. Voici le code responsable de cette opération :

// ... Other things ....

input group "Mouse"
input color     user50 = clrBlack;      //Price line
input color     user51 = clrDarkGreen;  //Positive move
input color     user52 = clrMaroon;     //Negative move
//+------------------------------------------------------------------+

// ... General information ...

//+------------------------------------------------------------------+
int OnInit()
{
        static string   memSzUser01 = "";
        
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);
        Mouse.Init(user50, user51, user52);

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

Nous devons donc définir 3 couleurs que le système utilisera. Ces couleurs doivent être choisies de manière à ce que les données soient claires et visibles sur le graphique. Jetez un coup d'œil au code de Souris.Init pour comprendre mieux. Le voici :

void Init(color c1, color c2, color c3)
{
        m_Infos.cor01 = c1;
        m_Infos.cor02 = c2;
        m_Infos.cor03 = c3;
        if (m_Infos.szNameObjH != NULL) return;
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);
        m_Infos.szNameObjH = "H" + (string)MathRand();
        m_Infos.szNameObjV = "V" + (string)MathRand();
        m_Infos.szNameObjT = "T" + (string)MathRand();
        m_Infos.szNameObjB = "B" + (string)MathRand();
        m_Infos.szNameObjI = "I" + (string)MathRand();
//---
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjH, OBJ_HLINE, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjV, OBJ_VLINE, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjT, OBJ_TREND, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjB, OBJ_BITMAP, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjI, OBJ_TEXT, 0, 0, 0);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TOOLTIP, "\n");
//---
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_WIDTH, 2);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_BMPFILE, "::" + def_Fillet);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_FONT, "Lucida Console");
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_FONTSIZE, 10);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_BACK, false);
        Hide();
        Show();
}

Il n'y a rien de spécial dans ce code. Nous créons simplement des objets qui seront utilisés par la classe. Mais la partie surlignée peut prêter à confusion : si vous la cherchez dans la classe, vous ne trouverez aucun endroit où elle est déclarée. Elle est en fait déclarée dans le code du fichier de l’EA avec les déclarations d'autres ressources. Je regrouperai tout cela plus tard dans un autre fichier, mais pour l'instant, cela reste ainsi. Du coup, si vous regardez le code de l'EA, vous trouverez les lignes suivantes :

#define def_Resource    "Resources\\SubSupport.ex5"
#define def_Fillet      "Resources\\Fillet.bmp"
//+------------------------------------------------------------------+
#resource def_Resource
#resource def_Fillet

La ligne indique la ressource mise en évidence dans le code d'initialisation de la souris.

Nous avons finalement atteint le haut de cette classe, qui est appelée avec le code suivant :

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Mouse.DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}

La ligne mise en évidence dans le code de l’EA appellera le code suivant :

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        int     w = 0;
        uint    key;
        static int b1 = 0;
        static double memPrice = 0;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Position.X = (int)lparam;
                        Position.Y = (int)dparam;
                        ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);
                        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
                        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjV, 0, Position.dt, 0);
                        key = (uint) sparam;
                        if ((key & 0x10) == 0x10)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, m_Infos.cor01);
                                b1 = 1;
                        }
                        if (((key & 0x01) == 0x01) && (b1 == 1))
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, m_Infos.cor01);
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 0, Position.dt, memPrice = Position.price);
                                b1 = 2;
                        }
                        if (((key & 0x01) == 0x01) && (b1 == 2))
                        {
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 1, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
                                ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjI, 0, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
                        }
                        if (((key & 0x01) != 0x01) && (b1 == 2))
                        {
                                b1 = 0;
                                ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
                                Hide();
                                Show();
                        }
                        Position.ButtonsStatus = (b1 == 0 ? key : 0);
                        break;
        }
}

Veuillez noter que le code ci-dessus n'est pas le code complet implémenté. Il prend en charge et résout uniquement les principales tâches de l'EA jusqu'au stade actuel du développement. Pour le comprendre, faites attention à une chose dans le code d'initialisation de la souris. Il comporte la ligne suivante :

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

Cette ligne empêche le réticule de s'afficher lorsque l'on clique sur le bouton du milieu de la souris. Mais pourquoi empêchons-nous l’affichage du réticule ? Pour le comprendre, regardons le gif suivant :

C'est le graphique WDO, il bouge de 0,5 en 0,5. Mais lorsque nous essayons de faire une analyse, nous n'avons pas beaucoup de précision, alors que dans certains cas, il est important d'avoir une certaine précision pour mener l'analyse. Le réticule de MetaTrader 5 n'est pas adéquat dans certains cas spécifiques. Dans ce cas, nous devons recourir à un nouveau système et nous devons donc forcer MetaTrader 5 à cesser de créer des réticules lorsque l'EA est en cours d'exécution. A sa place, nous créons notre propre réticule pour effectuer l'analyse. Cela nous permet d'ajouter des données et des valeurs plus pertinentes pour nous, et de les présenter de la manière qui nous semble la plus appropriée. Ceci est visible dans la figure ci-dessous. Elle montre le résultat de l'utilisation du système de modélisation des données avec l'EA en cours d'exécution.


Comme vous pouvez le constater, les valeurs spécifiées correspondent aux valeurs exactes du mouvement. Nous disposons également d'une indication visuelle : si la valeur est positive, l'indication devient verte, et si elle est négative, alors l'indication devient rouge. Une trace est créée et les points de départ et d'arrivée deviennent également visibles. Mais, comme je l'ai déjà mentionné, le système n'est pas encore complet. Vous pouvez toujours y apporter des améliorations si vous le souhaitez ou si vous en avez besoin. En attendant la sortie d'une nouvelle version de la classe C_Mouse, vous pouvez améliorer cette version et disposer de plus de données. Mais pour ce faire, vous devez comprendre comment tout fonctionne. C'est pourquoi nous allons examiner de plus près le code de gestion des messages de la classe C_Mouse. 


Comprendre le code DispathMessage de la classe C_Mouse

Le code commence par capturer et ajuster les valeurs des variables de la position de la souris. Cela fait dans le code ci-dessous :

Position.X = (int)lparam;
Position.Y = (int)dparam;
ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);

Les valeurs de la position sont rapportées par la plateforme MetaTrader 5. Mais les valeurs proviennent en fait du système d'exploitation et sont en coordonnées écran, c'est-à-dire X et Y. Nous devons les convertir en coordonnées graphiques avec la fonction ChartXYToTimePrice, disponible en MQL5, qui nous simplifie grandement la vie.

Une fois que cela est fait, nous pouvons déplacer les lignes de prix et de temps.

ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjV, 0, Position.dt, 0);

Mais la ligne de temps est initialement invisible, nous ne pouvons donc pas la voir sur le graphique. Après cela, nous pouvons capturer l'état de la souris

key = (uint) sparam;

Jusqu'à présent, tout va bien. Nous allons maintenant vérifier si le bouton du milieu est appuyé. Si c'est le cas, alors la ligne de temps devient visible sur le graphique. Voici le code correspondant :

if ((key & 0x10) == 0x10)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, m_Infos.cor01);
        b1 = 1;
}

Dans ce but, nous utilisons une variable statique pour stocker cet événement. Ainsi à partir de maintenant, aucun autre événement ne sera accepté et géré par l'EA. Il ne s'occupera que de l'étude que nous voulons faire sur le graphique. Mais en fait, l'étude ne commence que lorsque nous appuyons sur le bouton gauche de la souris, c'est-à-dire que j'utilise le même mode de travail que celui déjà connu par tous les utilisateurs de la plateforme MetaTrader 5. C'est le moyen le plus approprié, car l'utilisateur risque de ne pas utiliser le système s’il doit apprendre une nouvelle façon de faire des lignes de recherche. L'EA attend alors ce clic gauche, fait avec le code suivant :

if (((key & 0x01) == 0x01) && (b1 == 1))
{
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, m_Infos.cor01);
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 0, Position.dt, memPrice = Position.price);
        b1 = 2;
}

lorsqu'un clic se produit, le système de déplacement du graphique est verrouillé. Nous affichons ensuite une ligne de tendance indiquant les points d'analyse. Ensuite, le système passe à l'étape suivante, avec une nouvelle valeur en b1. C'est maintenant la partie où vous pouvez ajouter plus d'informations ou mettre ce vous désirez. Ici, je ne fais que démontrer le système, mais n'hésitez pas à mettre ce que vous voulez. Cette opération doit être effectuée comme ci-dessous :

if (((key & 0x01) == 0x01) && (b1 == 2))
{
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 1, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjI, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
}

Faites bien attention à la ligne en surbrillance, car c'est là que la valeur que vous voyez sur l'écran du graphique est affichée et calculée. Vous pouvez aussi y ajouter d'autres informations utiles. Le fonctionnement de cette partie entraîne le calcul et la présentation des données pendant que l'on appuie sur le bouton gauche. Il s'agit donc du même comportement que le comportement par défaut dans MetaTrader 5, mais les valeurs seront ajustées et modélisées en fonction de vos désirs et besoins.

Nous devons maintenant faire un autre test, montré ci-dessous :

if (((key & 0x01) != 0x01) && (b1 == 2))
{
        b1 = 0;
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
        Hide();
        Show();
}

Après avoir relâché le bouton gauche de la souris, le graphique est libéré et peut être déplacé. Tous les éléments qui ont été utilisés pour créer l'analyse sont cachés, et seule la ligne de prix redevient visible. Et nous avons enfin la dernière partie du code, qui est montrée ci-dessous :

Position.ButtonsStatus = (b1 == 0 ? key : 0);

Si aucune étude n'est appelée, alors l'état du bouton de la souris est stocké et peut être utilisé ailleurs dans l'EA. Mais si une étude a été appelée, une valeur NULL est utilisée comme données d'état. Il ne sera donc pas possible de créer des ordres, ou de changer leur position.

Dans la vidéo ci-dessous, vous pouvez voir comment cette trace fonctionne réellement et comment elle ajuste le volume sur l'écran. L'indicateur est d'une grande aide et il sera très utile si vous apprenez à l'utiliser correctement. Avec le Times & Trade, ils forment ensemble un outil d'analyse à double bruit, qui est l'une des méthodes de trading les plus avancées du marché.




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

Fichiers joints |
EA_-_Mouse.zip (5986.31 KB)
Développer un Expert Advisor à partir de zéro (partie 15) : Accéder aux données du web (I) Développer un Expert Advisor à partir de zéro (partie 15) : Accéder aux données du web (I)
Comment accéder aux données disponibles en ligne via MetaTrader 5 ? Il existe un grand nombre de sites et d'endroits sur le web qui contiennent une grande quantité d'informations. Ce que vous devez savoir, c'est où chercher et comment utiliser au mieux ces informations.
Apprenez à concevoir un système de trading basé sur les Volumes Apprenez à concevoir un système de trading basé sur les Volumes
Voici un nouvel article de notre série sur l'apprentissage de la conception de systèmes de trading basés sur les indicateurs techniques les plus populaires. Cette fois-ci, notre article sera consacré à l'indicateur Volumes. Le concept de volume est l'un des facteurs les plus importants dans le trading sur les marchés financiers. Nous devons donc y prêter attention. Dans cet article, nous allons apprendre à concevoir un système de trading simple grâce à l'indicateur des Volumes.
Matrices et vecteurs en MQL5 Matrices et vecteurs en MQL5
En utilisant les types de données spéciaux "matrix" et "vector", il est possible de créer un code très proche de la notation mathématique. Avec ces méthodes, vous pouvez éviter de créer des boucles imbriquées ou de faire attention à l'indexation correcte des tableaux dans les calculs. Par conséquent, l'utilisation des méthodes matricielles et des méthodes vectorielles augmente la fiabilité et la rapidité du développement de programmes complexes.
Apprenez à concevoir un système de trading basé sur le MFI Apprenez à concevoir un système de trading basé sur le MFI
Le nouvel article de notre série sur la conception d'un système de trading basé sur les indicateurs techniques les plus populaires est basé sur un nouvel indicateur technique : le Money Flow Index (MFI). Nous l'apprendrons en détail et développerons un système de trading simple en MQL5 pour l'exécuter dans MetaTrader 5.