English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
preview
Développer un Expert Advisor à partir de zéro (partie 8) : Un saut conceptuel

Développer un Expert Advisor à partir de zéro (partie 8) : Un saut conceptuel

MetaTrader 5Exemples | 21 septembre 2022, 09:19
664 0
Daniel Jose
Daniel Jose

Introduction

Parfois, lors du développement de certains projets, nous trouvons de nouvelles fonctionnalités idéales et possibles qui pourraient être utiles et apporter une grande amélioration au système que nous créons. Mais la question se pose : quelle est la manière la plus simple d'implémenter une nouvelle fonctionnalité ?

Mais le problème est qu'il faut parfois oublier tout ce qui a déjà été développé et repartir de zéro. Ca peut être démotivant. Au fil du temps, après plus de 20 ans de programmation en C++, j'ai développé une certaine ligne de pensée. Nous développons des concepts qui nous aident à planifier les choses et à apporter des changements avec un minimum d'effort. Mais parfois les choses peuvent changer et devenir beaucoup plus complexes que prévues.

Jusqu'à présent, nous avons construit l'EA de manière à ce qu'il puisse recevoir du nouveau code sans perdre sa fonctionnalité actuelle : nous avons simplement créé et ajouté des classes. Nous devons maintenant faire un pas en arrière, puis faire deux pas en avant. Ce retour en arrière nous permettra d'introduire d’abord une nouvelle fonctionnalité. Ce sera une classe de fenêtre avec des informations basées sur des modèles. Ca sera la première partie ici. Nous allons radicalement changer le code, tout en gardant toutes les fonctionnalités dont nous disposons pour le moment. Et dans la deuxième partie nous nous occuperons de l'IDE.


Plan

Notre Expert Advisor est actuellement structuré en une classe d'objet. On peut la représenter selon le schéma ci-dessous :

Le système fonctionne actuellement bien et il est très stable. Mais nous devons maintenant restructurer l'EA comme indiqué ci-dessous. Vous remarquerez peut-être qu'il existe une classe supplémentaire, et que les positions de C_TemplateChart et C_SubWindow ont changé.


Quel est le but de cette restructuration ? Le problème est que la manière dont les fenêtres flottantes ont été implémentées n'est pas adaptée aux fenêtres contenant des données d'actifs. Ces changements sont donc nécessaires. Mais ce changement n'est pas seulement esthétique en termes de structure. Il nécessite également une modification extrême du code. Il sera donc très différent du code précédent.

Allons-y !


Mise en œuvre de l’implémentation

1. Modifications du code de l'Expert Advisor

Le premier grand changement commence dans le fichier d'initialisation de l’EA. Voici le code :

input group "Window Indicators"
input string                    user01 = "";                    //Subwindow indicators
input group "WallPaper"
input string                    user10 = "Wallpaper_01";        //Used BitMap
input char                      user11 = 60;                    //Transparency (from 0 to 100)
input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;   //Chart background type
input group "Chart Trader"
input int                       user20   = 1;                   //Leverage factor
input int                       user21   = 100;                 //Take Profit (financial)
input int                       user22   = 75;                  //Stop Loss (financial)
input color                     user23   = clrBlue;             //Price line color
input color                     user24   = clrForestGreen;      //Take Profit line color
input color                     user25   = clrFireBrick;        //Stop line color
input bool                      user26   = true;                //Day Trade?
input group "Volume At Price"
input color                     user30  = clrBlack;             //Bar color
input char                      user31  = 20;                   //Transparency (from 0 to 100 )
//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_WallPaper     WallPaper;
C_VolumeAtPrice VolumeAtPrice;
//+------------------------------------------------------------------+
int OnInit()
{
        static string   memSzUser01 = "";
        
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);
        if (memSzUser01 != user01)
        {
                Chart.ClearTemplateChart();
                Chart.AddThese(memSzUser01 = user01);
        }
        Chart.InitilizeChartTrade(user20, user21, user22, user23, user24, user25, user26);
        VolumeAtPrice.Init(user24, user25, user30, user31);
        
   OnTrade();
        EventSetTimer(1);
   
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+


Notez que maintenant nous n'avons plus qu'une seule variable qui indique quels modèles seront chargés. Le reste du code reste identique, à l'exception de la partie en surbrillance. Il n'est peut-être pas tout à fait clair de ce à quoi sert ce code en surbrillance dans la partie d'initialisation de l’EA. Lorsque nous chargeons l'EA sur un graphique, cela crée certaines choses. Dans le cadre d'une utilisation normale, elles peuvent être modifiées. Il était inutile d'ajouter le code en surbrillance auparavant, car tout était destiné à fonctionner ensemble et les modifications n'affectaient pas le comportement ou l'apparence de l'EA. Mais lorsque nous ajoutons des indicateurs flottants, un nouveau comportement apparaît : à chaque fois que nous modifions la période du graphique, l'EA redémarre et les fenêtres reviennent à leur état d'origine. Si vous ne les détruisez pas, des éléments inutiles s'accumuleront sur le graphique. Si vous les détruisez de manière incorrecte, ils seront reconstruits à leur emplacement d'origine, ce qui est également un gros inconvénient. Si l'utilisateur ne modifie pas les modèles requis, le code en surbrillance empêchera la destruction incorrecte des fenêtres flottantes. Mais si des modifications sont apportées aux modèles, l'EA redémarrera normalement. C'est très simple mais aussi extrêmement efficace.

La prochaine chose à laquelle il faut faire attention est liée au système de messagerie interne. Auparavant, il avait une variable supplémentaire. Mais nous l'avons supprimée. Le code ressemble donc maintenant à ceci :

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, NanoEA.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
}


Le système utilise désormais le système d'échange de messages MQL5 plus efficacement. L'envoi de messages est très similaire à la fonction OnChartEvent elle-même. Cela permet de transmettre des paramètres facilement à la classe d'objets afin que chaque classe puisse traiter les messages d'événement générés par le système MetaTrader 5 de la manière que l’on désire. De cette façon, nous isolons davantage chaque classe d'objets. L'EA peut prendre ainsi des formes diverses pour chaque type d'utilisateur, avec moins d'effort.


2. Modifications du code de prise en charge des sous-fenêtres

Jusqu'à présent, le code de la sous-fenêtre était très simple. Mais il avait un problème : pour une raison ou une autre, l'EA ne pouvait pas supprimer la sous-fenêtre créée. Lorsque l'Expert Advisor est rouvert, une nouvelle sous-fenêtre est créée. A cause de cela, le contrôle du système a été perdu. Mais c’est très facile à réparer. Jetez d’abord un coup d’œil au fragment du fichier de support ci-dessous :

int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "SubWinSupport");
        
        return INIT_SUCCEEDED;
}

La ligne en surbrillance va créer un alias pour le fichier de support. L'EA verra cet alias de l’EA et vérifiera si le système de sous-fenêtre est chargé ou non. Ce sera fait quel que soit le nom du fichier car l'EA ne vérifie que l'alias. Le même type de code sera utilisé plus tard pour prendre en charge d'autres éléments dans notre EA. Je n'entrerai pas trop dans les détails pour l’instant. Mais je le ferai plus tard dans un autre article. J'expliquerai comment profiter du code mis en évidence.

Maintenant que ceci est fait, regardons le code qui charge et crée des sous-fenêtres. Il est écrit ci-dessous :

void Init(void)
{
        int i0;
        if ((i0 = ChartWindowFind(Terminal.Get_ID(), def_Indicador)) == -1)
                ChartIndicatorAdd(Terminal.Get_ID(), i0 = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_Resource));
        m_IdSubWinEA = i0;
}


Comme vous pouvez le voir, c'est beaucoup plus simple. Mais ce code n'est pas public, on y accède par un autre code qui lui est public :

inline int GetIdSubWinEA(void)
{
        if (m_IdSubWinEA < 0) Init();
        return m_IdSubWinEA;
}


Mais pourquoi est-il implémenté de cette façon ? Il peut arriver que l'EA n'utilise aucun indicateur dans la sous-fenêtre. Lorsque le système s'en rend compte, il supprime la sous-fenêtre du graphique et ne la crée que si nécessaire. Mais cette décision n'est pas prise par le code de l’EA, mais par la classe C_TemplateChart.


3. Nouvelle classe C_TemplateChart

Jetez un œil à l’animation suivante :

Vous pouvez voir que nous avons maintenant une ligne verticale qui indique où nous analysons. Ces lignes sont toutes indépendantes les unes des autres. Elles n’existaient pas auparavant, ce qui rendait difficile l'analyse de certains points de l'indicateur en fonction du graphique. C'est l'une des différentes améliorations incluses dans la classe C_TemplateChart. Regardons le code à l'intérieur de la classe pour comprendre également les autres changements.

Les variables sont déclarées dans le code suivant :

class C_TemplateChart : public C_SubWindow
{
#define def_MaxTemplates                8
#define def_NameTemplateRAD     "IDE"
//+------------------------------------------------------------------+
        private :
//+------------------------------------------------------------------+
        enum eParameter {TEMPLATE = 0, PERIOD, SCALE, WIDTH, HEIGHT};
//+------------------------------------------------------------------+
                struct st
                {
                        string          szObjName,
                                        szSymbol,
                                        szTemplate,
                                        szVLine;
                        int             width,
                                        scale;
                        ENUM_TIMEFRAMES timeframe;
                        long            handle;
                }m_Info[def_MaxTemplates];
                int     m_Counter,
                        m_CPre,
                        m_Aggregate;
                struct st00
                {
                        int     counter;
                        string  Param[HEIGHT + 1];
                }m_Params;


La première chose à laquelle il faut faire attention est que la classe C_TemplateChart étend la classe C_SubWindow. Cette partie de code ne semble pas spéciale, mais faites attention à la partie surlignée : elle pointe vers un système interne d'analyse de données avec lequel il est possible de créer et de présenter de manière appropriée des indicateurs demandés par l'utilisateur. Le système de description de la manière dont l'utilisateur indiquera les choses commence maintenant à être standardisé. Même si cela semble déroutant, cela deviendra plus clair avec le temps. Pour expliquer le nouveau format, il est nécessaire d'analyser le morceau de code suivant, qui est chargé d'analyser la demande de l'utilisateur :

int GetCommand(int iArg, const string szArg)
{
        for (int c0 = TEMPLATE; c0 <= HEIGHT; c0++) m_Params.Param[c0] = "";
        m_Params.counter = 0;
        for (int c1 = iArg, c2 = 0; szArg[iArg] != 0x00; iArg++) switch (szArg[iArg])
        {
                case ')':
                case ';':
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        for (; (szArg[iArg] != 0x00) && (szArg[iArg] != ';'); iArg++);
                        return iArg + 1;
                case ' ':
                        c2 += (c1 == iArg ? 0 : 1);
                        c1 = (c1 == iArg ? iArg + 1 : c1);
                        break;
                case '(':
                case ',':
                        c2 = (m_Params.counter == SCALE ? (c2 >= 1 ? 1 : c2) : c2);
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        c2 = 0;
                        c1 = iArg + 1;
                        break;
                default:
                        c2++;
                        break;
        }
        return -1;
}


Il faut tout d’abord effacer toutes les données qui se trouvaient auparavant dans la structure. Puis analyser et recevoir les paramètres un par un s'il y en a. Ils ne sont pas obligatoires, mais ils indiquent comment les choses seront affichées à l'utilisateur. Ce système est auto-extensible. Donc, si vous souhaitez ajouter plus d'informations, il vous suffit de le spécifier dans l'énumération eParameter . Le système a pour l’instant 5 paramètres. Ils doivent être spécifiés dans le même ordre qu'ils sont indiqués dans l'énumération eParameters . Cette énumération est mise en surbrillance dans la partie de déclaration des variables. Les paramètres (dans le bon ordre) et leurs explications sont indiqués ci-dessous.

Paramètre Résultat
1. MODÈLE ou ACTIF Spécifie le modèle ou l'actif à afficher
2. PÉRIODE Si spécifiée, elle définira la période de l’indicateur, tout comme il était utilisé auparavant
3. ÉCHELLE Si spécifié, l'indicateur sera lié à une échelle fixe.
4. LARGEUR S'il est spécifié, le paramètre définira la largeur de la fenêtre de l’indicateur.
5. HAUTEUR Ce nouveau paramètre sera abordé plus loin dans cet article. Il indique l'utilisation d'une fenêtre flottante.

La seule structure qui ne bénéficiera pas du paramètre 5 pour le moment est IDE. Mais cela sera corrigé. Dans le prochain article, je montrerai comment bénéficier de l'IDE. Cet article se concentrera donc sur d'autres systèmes.

Supposons maintenant que, pour une raison quelconque, nous souhaitions permettre à l'utilisateur de contrôler la couleur de la ligne verticale de l'indicateur. Il n'est pas nécessaire d'apporter des modifications au code d'analyse des paramètres. Effectuez simplement la modification comme suit :

class C_TemplateChart : public C_SubWindow
{
#define def_MaxTemplates        8
#define def_NameTemplateRAD     "IDE"
//+------------------------------------------------------------------+
        private :
//+------------------------------------------------------------------+
        enum eParameter {TEMPLATE = 0, COLOR_VLINE, PERIOD, SCALE, WIDTH, HEIGHT};
//+------------------------------------------------------------------+

// ... Internal code ....

                struct st00
                {
                        int     counter;
                        string  Param[HEIGHT + 1];
                }m_Params;


Le système reconnaîtra automatiquement qu'il peut y avoir 6 paramètres dans un appel. Mais maintenant, un nouveau problème est survenu. Bien que le code GetCommand ci-dessus fonctionne correctement, il contient un bug. Souvent, nous ne voyons pas ce genre de bug lorsque nous créons le système que nous utiliserons nous-mêmes. Mais lorsque le système est mis à la disposition d'autres personnes, l'erreur devient évidente et peut laisser certains programmeurs moins expérimentés dans l'incertitude quant à la manière de résoudre ce problème. C'est pourquoi la Programmation Orientée Objet est si appréciée : elle permet de créer le modèle le plus approprié à utiliser dans les programmes les plus intéressants. L'un des prérequis de la POO est de s'assurer que les données et les variables de classes sont initialisées correctement. Pour le garantir, il est important de tout tester. Bien que le code de la fonction GetCommand semble correct, il contient un bug : il ne vérifie pas la limite maximale des paramètres. Si le modèle d'origine n'accepte que 5 paramètres, que se passe-t-il si l'utilisateur en définit 6 ? C'est quelque chose qu’il vaut mieux éviter : il ne faut pas supposer que tout fonctionnera, mais garantir que tout fonctionnera. Le code doit donc être corrigé comme indiqué ci-dessous (les corrections sont mises en surbrillance) :

int GetCommand(int iArg, const string szArg)
{
        for (int c0 = TEMPLATE; c0 <= HEIGHT; c0++) m_Params.Param[c0] = "";
        m_Params.counter = 0;
        for (int c1 = iArg, c2 = 0; szArg[iArg] != 0x00; iArg++) switch (szArg[iArg])
        {
                case ')':
                case ';':
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        for (; (szArg[iArg] != 0x00) && (szArg[iArg] != ';'); iArg++);
                        return iArg + 1;
                case ' ':
                        c2 += (c1 == iArg ? 0 : 1);
                        c1 = (c1 == iArg ? iArg + 1 : c1);
                        break;
                case '(':
                case ',':
                        if (m_Params.counter == HEIGHT) return StringLen(szArg) + 1;
                        c2 = (m_Params.counter == SCALE ? (c2 >= 1 ? 1 : c2) : c2);
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        c2 = 0;
                        c1 = iArg + 1;
                        break;
                default:
                        c2++;
                        break;
        }
        return -1;
}

En ajoutant simplement une seule ligne, nous empêcherons le système de générer un résultat inattendu. Car si le dernier paramètre attendu est HEIGHT, mais que ce n'est pas le dernier, cela signifie logiquement que quelque chose ne va pas. Donc le système ignore tout ce qui est déclaré par la suite et évite ainsi les problèmes.

Si vous ne comprenez pas comment le système reconnaît les modèles et les paramètres, voici la syntaxe :

Paramètre_00 (Paramètre_01, Paramètre_02, Paramètre_03, Paramètre_04)

Avec Parameter_00 qui spécifie le modèle à utiliser, le reste est séparé par des virgules ( , ) et indique les valeurs définies dans l'énumération eParameter . Si nous voulons modifier uniquement Parameter_03, nous pouvons laisser le reste vide, comme indiqué dans l’image ci-dessous. Dans cette figure, je montre que le système fonctionne comme l'utilisateur le désire.

       

Notez que nous avons une indication standardisée, très similaire aux appels de fonction, ce qui peut sembler déroutant. Voici ce qu’il se passe réellement : nous spécifions le modèle RSI, puis nous ne spécifions ni la période ni l'échelle. Ces valeurs sont laissées vides, afin que le système suive le graphique principal. Mais nous spécifions la largeur et la hauteur, pour que le système affiche le RSI dans une fenêtre flottante. Dans le modèle ADX, nous indiquons uniquement la largeur afin que le système l'affiche avec la largeur donnée dans la sous-fenêtre. L'indicateur Stoch prendra toute la sous-fenêtre restante, partageant l'espace avec l’ADX. Mais si l'utilisateur veut changer quelque chose, ce n’est pas difficile. Voyez ce qui se passe lorsque nous spécifions la hauteur pour l'ADX.

Le système changera immédiatement la façon dont l'ADX est présenté : il le placera dans une fenêtre flottante, tout en laissant la sous-fenêtre entière à la fenêtre Stoch. Chaque fenêtre flottante sera complètement indépendante de l'autre. Mais cela va au-delà de ce que l'on peut voir :

La sous-fenêtre a été supprimée car elle n'est plus nécessaire. Mais quelle fonction gère tout cela ? Je vous le montré ci-dessous - beaucoup de choses intéressantes peuvent être faites avec seulement quelques modifications :

void AddTemplate(void)
{
        ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;
        string sz0 = m_Params.Param[PERIOD];
        int w, h, i;
        bool bIsSymbol;

        if (sz0 == "1M") timeframe = PERIOD_M1; else
        if (sz0 == "2M") timeframe = PERIOD_M2; else
        if (sz0 == "3M") timeframe = PERIOD_M3; else
        if (sz0 == "4M") timeframe = PERIOD_M4; else
        if (sz0 == "5M") timeframe = PERIOD_M5; else
        if (sz0 == "6M") timeframe = PERIOD_M6; else
        if (sz0 == "10M") timeframe = PERIOD_M10; else
        if (sz0 == "12M") timeframe = PERIOD_M12; else
        if (sz0 == "15M") timeframe = PERIOD_M15; else
        if (sz0 == "20M") timeframe = PERIOD_M20; else
        if (sz0 == "30M") timeframe = PERIOD_M30; else
        if (sz0 == "1H") timeframe = PERIOD_H1; else
        if (sz0 == "2H") timeframe = PERIOD_H2; else
        if (sz0 == "3H") timeframe = PERIOD_H3; else
        if (sz0 == "4H") timeframe = PERIOD_H4; else
        if (sz0 == "6H") timeframe = PERIOD_H6; else
        if (sz0 == "8H") timeframe = PERIOD_H8; else
        if (sz0 == "12H") timeframe = PERIOD_H12; else
        if (sz0 == "1D") timeframe = PERIOD_D1; else
        if (sz0 == "1S") timeframe = PERIOD_W1; else
        if (sz0 == "1MES") timeframe = PERIOD_MN1;
        if ((m_Counter >= def_MaxTemplates) || (m_Params.Param[TEMPLATE] == "")) return;
        bIsSymbol = SymbolSelect(m_Params.Param[TEMPLATE], true);
        w = (m_Params.Param[WIDTH] != "" ? (int)StringToInteger(m_Params.Param[WIDTH]) : 0);
        h = (m_Params.Param[HEIGHT] != "" ? (int)StringToInteger(m_Params.Param[HEIGHT]) : 0);
        i = (m_Params.Param[SCALE] != "" ? (int)StringToInteger(m_Params.Param[SCALE]) : -1);
        i = (i > 5 || i < 0 ? -1 : i);
        if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else
        {
                SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w);
                if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl");
                if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD)
                {
                        C_Chart_IDE::Create(GetIdSubWinEA());
                        m_Info[m_Counter - 1].szVLine = "";
                }else
                {
                        m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
                        ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0);
                        ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack);
                }
                ChartRedraw(m_Info[m_Counter - 1].handle);
        }
}

Les parties en surbrillance montrent la manière dont les données seront présentées à l'écran. Le code ne diffère pas beaucoup de celui qui existait avant. Mais ces tests garantissent que le système se comportera comme on le souhaite. Tout cela ne nécessite pas jusqu’à présent une vraie restructuration du code. Mais quand on regarde la fonction responsable du redimensionnement de la sous-fenêtre, l'image change.

void Resize(void)
{
#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, A, B)
        int x0, x1, y;
        if (!ExistSubWin()) return;
        x0 = 0;
        y = (int)(ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS, GetIdSubWinEA()));
        x1 = (int)((Terminal.GetWidth() - m_Aggregate) / (m_Counter > 0 ? (m_CPre == m_Counter ? m_Counter : (m_Counter - m_CPre)) : 1));
        for (char c0 = 0; c0 < m_Counter; x0 += (m_Info[c0].width > 0 ? m_Info[c0].width : x1), c0++)
        {
                macro_SetInteger(OBJPROP_XDISTANCE, x0);
                macro_SetInteger(OBJPROP_XSIZE, (m_Info[c0].width > 0 ? m_Info[c0].width : x1));
                macro_SetInteger(OBJPROP_YSIZE, y);
                if (m_Info[c0].szTemplate == "IDE") C_Chart_IDE::Resize(x0);
        }
        ChartRedraw();
#undef macro_SetInteger
}

La ligne en surbrillance empêche le système d'être construit sur l'ancienne base. Cette ligne vérifiera s'il y a une sous-fenêtre ouverte et maintenue par l'EA .Si ce n'est pas le cas, elle revient et la fonction ne fait rien d'autre. Mais si une sous-fenêtre existe, tous les éléments qu'elle contient doivent être redimensionnés selon les besoins. Juste à cause de ce test, le système a été complètement remanié.

Ci-dessous une autre fonction qui a également été modifiée :

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        int mx, my;
        datetime dt;
        double p;

        C_Chart_IDE::DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        mx = (int)lparam;
                        my = (int)dparam;
                        ChartXYToTimePrice(Terminal.Get_ID(), mx, my, my, dt, p);
                        for (int c0 = 0; c0 < m_Counter; c0++)  if (m_Info[c0].szVLine != "")
                        {
                                ObjectMove(m_Info[c0].handle, m_Info[c0].szVLine, 0, dt, 0);
                                ChartRedraw(m_Info[c0].handle);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        Resize();
                        for (int c0 = 0; c0 < m_Counter; c0++)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, OBJPROP_PERIOD, m_Info[c0].timeframe);
                                ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, OBJPROP_CHART_SCALE,(m_Info[c0].scale < 0 ? ChartGetInteger(Terminal.Get_ID(),CHART_SCALE):m_Info[c0].scale));
                        }
                        break;
        }
}


La partie en surbrillance mérite vraiment toute notre attention. Qu'est ce que ce code fait ? Ce code affiche la ligne verticale au bon endroit et dans le bon modèle. Le reste du code maintient et ajuste simplement les modèles au fur et à mesure que le graphique change.

Il y a plusieurs avantages à le faire ici dans la classe d'objets plutôt que dans l'EA à l'intérieur du système OnChartEvent. Le plus important est que chaque classe puisse gérer l'événement que MetaTrader 5 envoie à l'EA. Au lieu de tout centraliser dans une seule fonction, nous laissons chaque classe faire son travail. Si nous ne voulons pas utiliser la classe dans l'EA, nous la supprimons simplement sans aucun effet de bord sur le reste du code.

La programmation est magnifique, n'est-ce pas ? J'adore programmer...

Avant de passer au point suivant de cet article, je voudrais commenter brièvement les valeurs utilisables dans les paramètres 1 et 2. Les valeurs suivantes peuvent être affectées au Paramètre 1 : 1M, 2M, 3M, 4M, 5M, 6M, 10M, 12M, 15M, 20M, 30M, 1H, 2H, 3H, 4H, 6H, 8H, 12H, 1D, 1S, 1MONTH. Ces valeurs ne sont pas des valeurs aléatoires : elles proviennent de l'énumération ENUM_TIMEFRAMEet répètent exactement ce que vous feriez si vous utilisiez un graphique standard. Le paramètre 2 peut prendre une valeur entre 0 et 5, où 0 est l'échelle la plus éloignée et 5 la plus proche. Pour plus de détails, vous pouvez voir CHART_SCALE.


3.4 Support des fenêtres flottantes

Nous allons maintenant comprendre comment les fenêtres flottantes sont créées et maintenues. C’est important de le comprendre pour profiter pleinement du système. La classe d'objet responsable de cela s'appelle C_ChartFloating. On pourrait se poser une question : pourquoi ne pas utiliser la classe Control de la bibliothèque standard MQL5 ? La raison en est simple. La classe de contrôle nous permet de créer et de maintenir une fenêtre avec une fonctionnalité très similaire à celle du système d'exploitation de la machine. Mais elle est trop vaste pour notre objectif. Nous avons besoin de quelque chose de beaucoup plus simple. Utiliser la classe Control pour faire ce que nous voulons reviendrait à utiliser un bazooka pour tuer une mouche. C'est pourquoi nous utilisons la classe C_ChartFloating. Elle contient le minimum d'éléments nécessaires pour prendre en charge les fenêtres flottantes, tout en nous permettant de les contrôler.

La classe elle-même n'a pas besoin de beaucoup d'explications. La seule chose que nous faisons est de créer 4 objets graphiques. Mais parmi ses fonctions internes, il y en existe deux qui méritent une attention particulière. Commençons par la fonction qui crée la fenêtre :

bool AddIndicator(string sz0, int x = 0, int y = -1, int w = 300, int h = 200, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1)
{
        m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS);
        m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS);
        if (m_MaxCounter >= def_MaxFloating) return false;
        y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);
        CreateBarTitle();
        CreateCaption(sz0);
        CreateBtnMaxMin();
        CreateRegion(TimeFrame, Scale);
        m_Win[m_MaxCounter].handle = ObjectGetInteger(Terminal.Get_ID(), m_Win[m_MaxCounter].szRegionChart, OBJPROP_CHART_ID);
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, sz0 + ".tpl");   
        m_Win[m_MaxCounter].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
        ObjectCreate(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJ_VLINE, 0, 0, 0);
        ObjectSetInteger(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJPROP_COLOR, clrBlack);
        m_Win[m_MaxCounter].PosX = -1;
        m_Win[m_MaxCounter].PosY = -1;
        m_Win[m_MaxCounter].PosX_Minimized = m_Win[m_MaxCounter].PosX_Maximized = x;
        m_Win[m_MaxCounter].PosY_Minimized = m_Win[m_MaxCounter].PosY_Maximized = y;
        SetDimension(w, h, true, m_MaxCounter);
        SetPosition(x, y, m_MaxCounter);
        ChartRedraw(m_Win[m_MaxCounter].handle);
        m_MaxCounter++;
        return true;
}

Ce code crée tout le code nécessaire pour que vous puissiez appliquer le modèle sur l'objet CHART . Elle est implémentée dans la partie de code en surbrillance. Notez que le seul paramètre vraiment requis pour appeler cette fonction est le nom du modèle. Toutes les autres valeurs sont pré-initialisées. Mais rien ne vous empêche de spécifier celles qui sont nécessaires. Pour chaque nouvelle fenêtre créée, la suivante sera légèrement décalée afin qu'elle ne chevauche pas les autres fenêtres. C’est ce qui est indiqué dans la ligne suivante :

y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);


L’autre fonction intéressante de cette classe gère les messages. Voici le code :

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        int mx, my;
        datetime dt;
        double p;
        static int six = -1, siy = -1, sic = -1;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        mx = (int)lparam;
                        my = (int)dparam;
                        if ((((int)sparam) & 1) == 1)
                        {
                                if (sic == -1)  for (int c0 = m_MaxCounter - 1; (sic < 0) && (c0 >= 0); c0--)
                                        sic = (((mx > m_Win[c0].PosX) && (mx < (m_Win[c0].PosX + m_Win[c0].Width)) && (my > m_Win[c0].PosY) && (my < (m_Win[c0].PosY + def_SizeBarCaption))) ? c0 : -1);
                                if (sic >= 0)
                                {
                                        if (six < 0) ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
                                        six = (six < 0 ? mx - m_Win[sic].PosX : six);
                                        siy = (siy < 0 ? my - m_Win[sic].PosY : siy);
                                        SetPosition(mx - six, my - siy, sic);
                                }
                        }else
                        {
                                if (six > 0) ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
                                six = siy = sic = -1;
                        }
                        ChartXYToTimePrice(Terminal.Get_ID(), mx, my, my, dt, p);
                        for (int c0 = 0; c0 < m_MaxCounter; c0++)
                                ObjectMove(m_Win[c0].handle, m_Win[c0].szVLine, 0, dt, 0);
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        for (int c0 = 0; c0 < m_MaxCounter; c0++) if (sparam == m_Win[c0].szBtnMaxMin)
                        {
                                SwapMaxMin((bool)ObjectGetInteger(Terminal.Get_ID(), m_Win[c0].szBtnMaxMin, OBJPROP_STATE), c0);
                                break;
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        for(int c0 = 0; c0 < m_MaxCounter; c0++)
                        {
                               ObjectSetInteger(Terminal.Get_ID(), m_Win[c0].szRegionChart, OBJPROP_PERIOD, m_Win[c0].TimeFrame);
                               ObjectSetInteger(Terminal.Get_ID(), m_Win[c0].szRegionChart, OBJPROP_CHART_SCALE,(m_Win[c0].Scale < 0 ? ChartGetInteger(Terminal.Get_ID(),CHART_SCALE):m_Win[c0].Scale));
                        }
                        m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS);
                        m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS);
                        break;
        }
        for (int c0 = 0; c0 < m_MaxCounter; c0++)
                ChartRedraw(m_Win[c0].handle);
}


Cette fonction implémente toute la gestion des événements pris en charge par la classe C_ChartFloating. Peu importe le nombre de fenêtres présentes, elle les gérera toutes de la même manière. Si nous faisions cela à l'intérieur de la fonction OnChartEvent de l'Expert Advisor, la fonction serait extrêmement complexe et instable. En implémentant la fonctionnalité ici, dans la classe objet, nous garantissons l'intégrité du code. Donc, si nous n'avons pas besoin d'utiliser des fenêtres flottantes, il nous suffit de supprimer le fichier de la classe et des points depuis lesquels il est accessible. Cette implémentation vous permet de rendre le code beaucoup plus rapide et facile à lire.

Il y a une autre partie intéressante dans le code ci-dessus. Il est mis en surbrillance. Son code interne est le suivant :

void SwapMaxMin(const bool IsMax, const int c0)
{
        m_Win[c0].IsMaximized = IsMax;
        SetDimension((m_Win[c0].IsMaximized ? m_Win[c0].MaxWidth : 100), (m_Win[c0].IsMaximized ? m_Win[c0].MaxHeight : 0), false, c0);
        SetPosition((m_Win[c0].IsMaximized ? m_Win[c0].PosX_Maximized : m_Win[c0].PosX_Minimized), (m_Win[c0].IsMaximized ? m_Win[c0].PosY_Maximized : m_Win[c0].PosY_Minimized), c0);
}


A quoi sert ce code ? Cela vous semble trop déroutant ? Pour comprendre, regardez attentivement l'animation ci-dessous.


Lorsqu'une fenêtre flottante est créée, elle possède un point d'ancrage initial, spécifié par le programmeur ou par le système de positionnement de la classe elle-même. Ce point d'ancrage est le même pour la fenêtre maximisée et pour la fenêtre réduite. Ces valeurs ne sont pas fixes : l'utilisateur peut facilement modifier ces points.

Supposons que vous ayez besoin d'un certain emplacement sur un graphique vierge. Vous pouvez déplacer la fenêtre agrandie vers un endroit facile et rapide à lire, puis réduire cette même fenêtre et la déplacer vers un autre endroit, par exemple, vers le coin de l'écran. Le système s'en souviendra. Lorsque vous maximiserez la fenêtre, il sautera au dernier point d'ancrage où il se trouvait avant la réduction. Il en va de même pour la situation inverse, lorsque la fenêtre est minimisée.


Conclusion

C'est tout pour l’instant. Dans le prochain article, nous étendrons cette fonctionnalité à la classe de support de l’IDE.


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

Fichiers joints |
EA.zip (3616.46 KB)
Développer un Expert Advisor à partir de zéro (partie 9) : Un saut conceptuel (2) Développer un Expert Advisor à partir de zéro (partie 9) : Un saut conceptuel (2)
Dans cet article, nous allons placer Chart Trade dans une fenêtre flottante. Dans la partie précédente, nous avions créé un système de base qui permettait l'utilisation de modèles dans une fenêtre flottante.
Développer un Expert Advisor à partir de zéro (partie 7) : Ajout du Volume au Prix (I) Développer un Expert Advisor à partir de zéro (partie 7) : Ajout du Volume au Prix (I)
Il s'agit de l'un des indicateurs les plus puissants actuellement. Tout trader essayant d'avoir un certain degré de confiance doit avoir cet indicateur sur son graphique. Le plus souvent, l'indicateur est utilisé par ceux qui préfèrent "lire les bandes" lorsqu'ils tradent. Cet indicateur peut également être utilisé par ceux qui n'utilisent que l'action des prix dans leurs transactions.
Apprenez à concevoir un système de trading basé sur Le SAR Parabolique Apprenez à concevoir un système de trading basé sur Le SAR Parabolique
Dans cet article, nous poursuivons notre série sur la conception d'un système de trading utilisant des indicateurs les plus populaires. Dans cet article, nous allons découvrir en détail l'indicateur SAR Parabolique, puis comment nous pouvons concevoir un système de trading à utiliser dans MetaTrader 5 en utilisant quelques stratégies simples.
Science des Données et Apprentissage Automatique (partie 03) : Matrices de Régression Science des Données et Apprentissage Automatique (partie 03) : Matrices de Régression
Cette fois-ci, nos modèles sont faits avec des matrices. Ceci permet une certaine flexibilité tout en nous permettant de faire des modèles puissants pouvant gérer non seulement cinq variables indépendantes mais aussi de nombreuses variables (tant que nous restons dans les limites de calcul d'un ordinateur). Cet article va être une lecture intéressante, c'est certain.