Développer un Expert Advisor à partir de zéro (partie 11) : Système d’ordre croisés

Daniel Jose | 28 novembre, 2022

Introduction

Il existe un type d'actifs qui rend la vie des traders très difficile : les contrats à terme. Mais pourquoi ? Lorsque le contrat d'un instrument financier expire, un nouveau contrat est créé que nous pourrons ensuite négocier. En fait, à l'expiration d’un contrat, nous devons terminer toute analyse, tout enregistrer en tant que modèle et importer ce modèle dans un nouveau contrat pour poursuivre l'analyse. C'est une chose courante pour quiconque trade ce type d'actif. Mais même les contrats à terme ont une certaine histoire. Et en l’utilisant, nous pouvons analyser le contrat sur une base continue.

Les traders professionnels aiment analyser les informations passées, auquel cas un deuxième graphique est nécessaire. Il n'est maintenant plus nécessaire d'avoir le deuxième graphique si nous utilisons les bons outils. L'un de ces outils est l'utilisation du système d'ordres croisés.


Plan

Dans le premier article de cette série, nous avons déjà mentionné ce type d’ordres, mais nous n'avons pas abordé la mise en œuvre. Nous nous étions concentrés sur d'autres éléments, car nous étions en train de créer un système complet pouvant fonctionner sur la plateforme MetaTrader 5. Cette fois-ci, nous allons montrer comment implémenter cette fonctionnalité.

Pour comprendre les raisons de la création de cette fonctionnalité, regardez les 2 images suivantes :

           

L'image de gauche est un contrat à terme (future) typique. Il s'agit ici du MINI DOLLAR FUTURE, qui a commencé il y a quelques jours, comme on peut le voir sur le graphique. Le graphique de droite montre le même contrat et contient des données supplémentaires représentant les valeurs des contrats expirés, de sorte que le graphique de droite est un graphique historique. Le graphique de droite est plus adapté à l'analyse des niveaux de support et de résistance. Mais un problème se pose pour le trading. Voici :

          

Comme vous pouvez le voir, le symbole tradé est spécifié dans CHART TRADE. Même si nous utilisons l'historique, CHART TRADE dit que nous pouvons envoyer un ordre - cela est visible dans la barre d'outils. Dans l'image de gauche, le graphique a un ordre créé pour le contrat en cours. Mais dans l'image de droite, l’ordre ne peut être vu que dans la boîte de message, alors qu'il n'y a rien de visible sur le graphique.

On pourrait penser qu'il ne s'agit que d'un problème d'affichage, mais non, car tout est beaucoup plus compliqué. C'est ce dont nous allons parler dans cet article.

IMPORTANT ! Nous allons voir ici comment créer des règles pour pouvoir utiliser les données historiques. Dans notre cas, ces règles seront axées sur l’utilisation du Mini Dollar (WDO) et du Mini Index (WIN), négociés à la bourse brésilienne (B3). Une compréhension correcte vous permettra d'adapter les règles à tout type de contrat à terme, à partir de n'importe quelle place boursière dans le monde.

Le système ne se limite pas à un actif ou à un autre. Il s'agit d'adapter les bonnes parties du code. Si cela est fait correctement, nous aurons alors un Expert Advisor avec lequel nous n'aurons pas à nous soucier de savoir si un contrat d'actif approche de l'expiration et quel sera le prochain contrat - l'EA le fera pour nous, en remplaçant le contrat actuel par le prochain selon les besoins.


Comprendre les règles du jeu

Les contrats à terme WDO (mini dollar), WIN (mini index), DOL (dollar future) et IND (index future) suivent des règles très spécifiques concernant la maturité et la spécification des contrats. Voyons d'abord comment connaître la date d'expiration d’un contrat :


Faites attention aux informations surlignées : le bleu indique la date d'expiration du contrat et le rouge indique la date à laquelle le contrat cessera d'exister, après quoi il ne sera plus négociable. C’est très important de le savoir.

La durée du contrat est spécifiée dans le contrat lui-même, mais aucun nom n'y est spécifié. Heureusement, nous pouvons facilement trouver le nom en fonction des règles qui sont strictes et qui sont utilisées sur tout les marchés. Dans le cas des contrats à terme sur dollar et sur indice, nous avons :

Les 3ères lettres indiquent le type de contrat :

Code Contrat
WIN Contrat à terme sur indice Mini Ibovespa 
IND Contrat à terme sur indice Ibovespa
WDO Contrat à terme mini dollar
DOL Contrat à terme sur le dollar

Ce code est suivi d'une lettre indiquant le mois d'expiration du contrat :

Mois d'expiration Lettre représentant WDO et DOL Lettre représentant WIN et IND 
Janvier F
 
Février  G  G
Mars  H  
Avril  J  J
Mai  K  
Juin  M  M
Juillet  N  
Août  Q  Q
Septembre  U  
Octobre  V  V
Novembre  X  
Décembre  Z  Z

Ceux-ci sont suivis de deux chiffres représentant l'année d'expiration du contrat. Par exemple, un contrat à terme en dollars expirant en avril 2022 est indiqué par DOLJ22. C'est le contrat qui peut être échangé jusqu'au début du mois de mai. Lorsque le mois de mai commencera, le contrat sera expiré. Étant donné que la règle diffère légèrement pour WIN et IND, le contrat expire en fait le mercredi le plus proche du 15 du mois indiqué. Donc, la règle est plus compliquée qu’il ne paraît, mais l'EA peut le gérer, et il fournira ainsi toujours le bon contrat.


Implémentation

Notre EA a déjà les points nécessaires pour recevoir les règles. Ici, nous devrons implémenter certains paramètres concernant le système d'envoi des ordres. C’est parti ! Ajoutez tout d’abord le code suivant à l'objet de classe C_Terminal :

void CurrentSymbol(void)
{
        MqlDateTime mdt1;
        string sz0, sz1;
        datetime dt = TimeLocal();
            
        sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);                           
        if ((sz0 != "WDO") && (sz0 != "DOL") && (sz0 != "WIN") && (sz0 != "IND")) return;
        sz1 = ((sz0 == "WDO") || (sz0 == "DOL") ? "FGHJKMNQUVXZ" : "GJMQVZ");
        TimeToStruct(TimeLocal(), mdt1);
        for (int i0 = 0, i1 = mdt1.year - 2000;;)
        {
                m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1);
                if (i0 < StringLen(sz1)) i0++; else
                {
                        i0 = 0;
                        i1++;
                }
                if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break;
        }
}

Ce code utilise les règles que nous avons vues ci-dessus pour générer le nom correct de l'actif. Pour nous assurer que nous utilisons toujours le contrat actuel, nous allons implémenter une vérification (indiquée dans la ligne en surbrillance). C'est-à-dire que l'actif doit être valide pour la plateforme, et l'EA utilisera le nom généré. Si vous souhaitez travailler avec d'autres contrats à terme, vous devez adapter le code précédent afin que le nom soit généré correctement, car le nom peut varier d'un cas à l'autre. Mais le code ne se limite pas uniquement aux actifs qui y sont liés : il peut être aussi utilisé pour refléter tout type de contrat à terme, tant que vous utilisez une règle correcte.

Vient ensuite la partie avec les détails de l’ordre. Si vous utilisez le système à ce stade de développement, vous pourrez constater le comportement suivant :


Vous pouvez déjà avoir le mode ordre croisé, mais il n'est pas encore complètement implémenté : il n'y a aucune indication de l'ordre sur le graphique. Ce n'est pas aussi difficile à mettre en œuvre que vous pouvez l'imaginer, car nous devons indiquer les ordres à l'aide de lignes horizontales. Mais ce n'est pas tout non plus. Lorsque nous utilisons des ordres croisés, nous manquons certaines choses fournies par MetaTrader 5. Nous devons donc implémenter la logique manquante afin que le système d'ordres puisse fonctionner de manière sûre, stable et fiable. Sinon, l'utilisation d'ordres croisés pourrait causer des problèmes.

De ce point de vue, cela ne semble pas être si simple. Et ce n'est pas simple, puisqu'il va falloir créer toute la logique que la plateforme MetaTrader propose à l'origine. La première chose à faire est donc d'oublier le système interne de MetaTrader - il ne sera pas disponible à partir du moment où nous commencerons à utiliser le système d'ordres croisés.

Désormais, le ticket de l’ordre dictera les règles. Mais cela aura des conséquences négatives. L'un des points les plus négatifs est que nous ne savons pas combien d’ordres sont placés sur un graphique. Limiter leur nombre serait certainement déplaisant pour le trader. Par conséquent, nous devons faire quelque chose pour permettre au trader d'utiliser le système de la même manière, comme cela se fait normalement avec MetaTrader. C'est le 1er problème à résoudre.


Classe C_HLineTrade

Pour résoudre ce problème, nous allons créer une nouvelle classe C_HLineTrade, qui remplacera le système d'affichage des ordres sur le graphique fourni par MetaTrader 5. Commençons donc par la déclaration de la classe :

class C_HLineTrade
{
#define def_NameHLineTrade "*HLTSMD*"
        protected:
                enum eHLineTrade {HL_PRICE, HL_STOP, HL_TAKE};
        private :
                color   m_corPrice,
                        m_corStop,
                        m_corTake;
                string  m_SelectObj;

Notez que quelques éléments sont définis ici car ils seront fréquemment utilisés dans le code. Soyez donc très attentif aux changements ultérieurs car il y aura beaucoup de changements. Nous déclarons ensuite le constructeur et le destructeur de la classe :
C_HLineTrade() : m_SelectObj("")
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        RemoveAllsLines();
};
//+------------------------------------------------------------------+  
~C_HLineTrade()
{
        RemoveAllsLines();
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, true);
};

Le constructeur empêchera les lignes d'origine d'être visibles, et le destructeur les remettra sur le graphique. Les deux fonctions ont une utilité commune :

void RemoveAllsLines(void)
{
        string sz0;
        int i0 = StringLen(def_NameHLineTrade);
                                
        for (int c0 = ObjectsTotal(Terminal.Get_ID(), -1, -1); c0 >= 0; c0--)
        {
                sz0 = ObjectName(Terminal.Get_ID(), c0, -1, -1);
                if (StringSubstr(sz0, 0, i0) == def_NameHLineTrade) ObjectDelete(Terminal.Get_ID(), sz0);
        }
}

La ligne en surbrillance vérifie si l'objet (dans ce cas, il s'agit d'une ligne horizontale) est l'un des objets utilisés par la classe. Si c'est le cas, il supprimera l'objet. Notez que nous ne savons pas combien d'objets nous avons. Le système vérifiera objet par objet, en essayant de nettoyer tout ce qui a été créé par la classe. La prochaine fonction de cette classe est illustrée ci-dessous :

inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select)
{
        string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1;
                                
        if (price <= 0)
        {
                ObjectDelete(Terminal.Get_ID(), sz0);
                return;
        }
        if (!ObjectGetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, 0, sz1))
        {
                ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (hl == HL_PRICE ? m_corPrice : (hl == HL_STOP ? m_corStop : m_corTake)));
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_WIDTH, 1);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_STYLE, STYLE_DASHDOT);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTABLE, select);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_BACK, true);
                ObjectSetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, (string)ticket + " "+StringSubstr(EnumToString(hl), 3, 10));
        }
        ObjectSetDouble(Terminal.Get_ID(), sz0, OBJPROP_PRICE, price);
}
Pour cette fonction, peu importe combien d'objets seront créés et si l'objet existe au moment où il est appelé. Il garantit que la ligne est créée et qu’elle est placée au bon endroit. Cette ligne créée remplace celle utilisée à l'origine dans MetaTrader.

Notre objectif est de les rendre fonctionnelles plutôt que belles. C'est pourquoi les lignes ne sont pas sélectionnées lors de leur création (vous pouvez modifier ce comportement si nécessaire). J'utilise personnellement le système de messagerie MetaTrader 5 pour positionner les lignes. Pour pouvoir les déplacer, vous devrez donc l'indiquer explicitement. Pour indiquer quelle ligne est ajustée, il existe une autre fonction :

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (m_SelectObj != "") ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        m_SelectObj = sparam;
                };
        }
}
Cette fonction utilise la sélection d'une ligne. Si une autre ligne est sélectionnée, la sélection précédente sera annulée. C'est tout simple. La fonction ne manipulera que les lignes réellement gérées par la classe et aucune autre. Une autre fonction importante de cette classe est la suivante :
bool GetNewInfosOrder(const string &sparam, ulong &ticket, double &price, eHLineTrade &hl)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                hl = (eHLineTrade) StringToInteger(StringSubstr(sparam, i0, 1));
                ticket = (ulong)StringToInteger(StringSubstr(sparam, i0 + 1, StringLen(sparam)));
                price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE);
                return true;
        }
        return false;
}
Cette fonction est peut-être la plus importante de cette classe : puisque nous ne savons pas combien de lignes il y a sur le graphique, nous devons savoir quelle ligne l'utilisateur est en train de manipuler. C’est exactement ce que fait cette fonction : elle indique au système quelle ligne est manipulée.

Mais ce n'est qu'une petite partie de ce que nous avons à faire. Le système est encore loin d'être utilisable. Passons donc à l'étape suivante : nous allons ajouter et modifier les fonctions de la classe C_Router, responsable du routage des commandes. Cette classe hérite de la fonctionnalité que nous venons de créer dans la classe C_HLineTrade. Voici le code :

#include "C_HLineTrade.mqh"
//+------------------------------------------------------------------+
class C_Router : public C_HLineTrade



Nouvelle classe C_Router

La classe source C_Router avait une limitation permettant de n'avoir qu’un seul ordre ouvert. Pour lever cette limite, nous devons apporter des modifications importantes à la classe C_Router.

Le premier changement concerne la fonction de mise à jour de classe :

void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal() - 1;
        o = OrdersTotal() - 1;
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true);
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};

Cette fonction collectait auparavant des données sur la seule position ouverte et les enregistrait. La fonction affichera maintenant toutes les positions ouvertes et les ordres en attente sur le graphique. C'est un remplacement du système fourni par MetaTrader. Comme ce sont des choses importantes, il est nécessaire de comprendre comment cela fonctionne. Car en cas d’échec, cela affectera l'ensemble du système d'ordres croisés. Donc, avant de trader sur un compte réel, testons ce système sur un compte de démo. Ce genre de système doit être correctement testé jusqu'à ce que nous soyons absolument sûrs que tout fonctionne comme prévu. Nous devons tout d’abord configurer le système car il fonctionne un peu différemment du fonctionnement de MetaTrader 5.

Regardez les lignes surlignées et répondez honnêtement : Est-ce que vous comprenez clairement ce qu'elles font ? La raison pour laquelle ces deux lignes de code sont ici sera clarifiée plus tard, lorsque nous parlerons de la classe C_OrderView plus loin dans cet article. Sans ces deux lignes, le code est très instable et fonctionne bizarrement. Quant au reste du code, il est assez simple : il crée chacune des lignes via l'objet de classe C_HLineTrade. Dans ce cas, nous n'avons qu'une seule ligne qui ne peut pas être sélectionnée. Ceci est facilement indiqué, comme on peut le voir dans le code ci-dessous:

SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);

En d'autres termes, le système est devenu très simple. La fonction est appelée par l'EA lors de l’événement OnTrade :

C_TemplateChart Chart;

// ... Expert Advisor code ...

void OnTrade()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
        Chart.UpdatePosition();
}

// ... The rest of the Expert Advisor code ...

Le code en surbrillance permettra la mise à jour des ordres à l'écran. Notez que nous utilisons le graphique C_TemplateChart pour cela car la structure des classes dans le système a changé. La nouvelle structure est illustrée ci-dessous :

Cette structure permet un flux dirigé de messages dans la direction de l’EA. Chaque fois que vous avez des doutes sur la façon dont le flux de messages entre dans une classe particulière, jetez un œil à ce graphique d'héritage des classes. La seule classe considérée comme publique est la classe d'objets C_Terminal. Toutes les autres sont gérées par héritage entre classes, et absolument aucune variable n'est publique dans ce système.

Maintenant, comme le système n'analyse pas qu'un seul ordre, il faut comprendre autre chose : Comment comprendre le résultat des opérations ? Pourquoi c'est important ? Lorsque vous n'avez qu'une seule position ouverte, le système peut facilement tout comprendre. Mais à mesure que le nombre de positions ouvertes augmente, vous devez comprendre ce qui se passe. Voici la fonction qui fournit toutes ces informations :

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

Il y a peu de changements. Jetez un œil au code de la fonction en surbrillance :

inline double CheckPosition(void)
{
        double Res = 0, last, sl;
        ulong ticket;
                        
        last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ticket = PositionGetInteger(POSITION_TICKET);
                Res += PositionGetDouble(POSITION_PROFIT);
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (last < sl) ClosePosition(ticket);
                }else
                {
                        if ((last > sl) && (sl > 0)) ClosePosition(ticket);
                }
        }
        return Res;
};

La fonction se compose de 3 parties : la partie jaune récupère le résultat des positions ouvertes, et les parties vertes vérifient la position au cas où le stop loss est manqué en raison d'une forte volatilité, auquel cas il est nécessaire de fermer la position dès que possible. Cette fonction ne renvoie pas que le résultat d'une position unique, sauf si vous ouvrez une position pour un actif spécifique.

Il existe également d'autres fonctions qui aident le système à continuer de fonctionner lorsque nous utilisons le modèle d'ordres croisés. Jetez-y un œil dans le code ci-dessous :

bool ModifyOrderPendent(const ulong Ticket, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        if (Ticket == 0) return false;
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_MODIFY;
        TradeRequest.order      = Ticket;
        TradeRequest.price      = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.type_time  = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.expiration = 0;
        return OrderSend(TradeRequest, TradeResult);
};
//+------------------------------------------------------------------+
bool ModifyPosition(const ulong Ticket, const double Take, const double Stop)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        if (!PositionSelectByTicket(Ticket)) return false;
        TradeRequest.action     = TRADE_ACTION_SLTP;
        TradeRequest.position   = Ticket;
        TradeRequest.symbol     = PositionGetString(POSITION_SYMBOL);
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        return OrderSend(TradeRequest, TradeResult);
};
La première est responsable de la modification de l'ordre encore ouvert, et la seconde modifie la position ouverte. Bien qu'elles semblent être identiques, elle ne le sont pas. Il existe une autre fonction importante pour le système :
bool RemoveOrderPendent(ulong Ticket)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_REMOVE;
        TradeRequest.order      = Ticket;       
        return OrderSend(TradeRequest, TradeResult);
};

Avec cette dernière fonction nous terminons la classe C_Router. Avons-nous implémenté le système de base qui couvre les fonctionnalités normalement supportées dans MetaTrader ? Non, à cause du système d'ordres croisés, nous ne pouvons plus compter sur ce support. Mais le système n'est pas encore complet. Nous devons ajouter autre chose pour que le système fonctionne vraiment. Pour le moment, s'il y a un ordre, il ressemblera à ci-dessous. C’est nécessaire pour pouvoir passer à l'étape suivante.



Regardez bien l'image ci-dessus. La boîte de message affiche l’ordre ouvert et l’actif correspondant. L'actif négocié est indiqué dans l’interface de CHART TRADE. Notez que c'est bien le même actif qui est indiqué dans la boîte de message. Vérifions maintenant l'actif affiché sur le graphique. Le nom peut être vérifié dans l'en-tête de la fenêtre du graphique. Mais c'est complètement différent : ce n'est pas un actif sur le graphique, c'est l'historique du mini-index? Ce qui signifie que maintenant nous n'utilisons pas le système interne de MetaTrader 5, mais le système d'ordres croisés décrit dans cet article. Nous n'avons maintenant que la fonctionnalité qui permet de montrer où se trouve l’ordre. Mais ce n'est pas suffisant, car nous voulons avoir un système entièrement fonctionnel qui permettrait le fonctionnement via le système des ordres croisés. Il nous faut donc autre chose. L'événement lié au déplacement des ordres sera quant à lui implémenté dans une autre classe.


Nouvelle fonctionnalité dans la classe C_OrderView

Bien que la classe d'objets C_OrderView puisse faire certaines choses, elle ne peut pas encore gérer les ordre ouverts et les ordres en attente. Mais en y ajoutant un système de messagerie, nous avons plus de possibilités pour l'utiliser. C'est le seul ajout que nous ferons à cette classe pour l'instant. Le code complet de la fonction est indiqué ci-dessous :

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((int)lparam, (int)dparam, (uint)sparam);
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                if (OrderSelect(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        RemoveOrderPendent(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        ClosePosition(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyPosition(ticket, OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyPosition(ticket, 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        C_HLineTrade::Select(sparam);
                        break;
                case CHARTEVENT_OBJECT_DRAG:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                price = AdjustPrice(price);
                                if (OrderSelect(ticket)) switch(hl)
                                {
                                        case HL_PRICE:
                                                pp = price - OrderGetDouble(ORDER_PRICE_OPEN);
                                                pt = OrderGetDouble(ORDER_TP);
                                                ps = OrderGetDouble(ORDER_SL);
                                                if (!ModifyOrderPendent(ticket, price, (pt > 0 ? pt + pp : 0), (ps > 0 ? ps + pp : 0))) UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), price)) UpdatePosition();
                                                break;
                                        case HL_TAKE:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), price, OrderGetDouble(ORDER_SL))) UpdatePosition();
                                                break;
                                }
                                if (PositionSelectByTicket(ticket)) switch (hl)
                                {
                                        case HL_PRICE:
                                                UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                ModifyPosition(ticket, PositionGetDouble(POSITION_TP), price);
                                                break;
                                        case HL_TAKE:
                                                ModifyPosition(ticket, price, PositionGetDouble(POSITION_SL));
                                                break;
                                }
                        };
                break;
        }
}

Ce code complète le système des ordres croisés. Nos capacités ont augmenté afin que nous puissions faire presque la même chose que ce qui était possible sans le système d'ordres croisés. La fonction devrait être assez claire. Mais il existe un type d'événement qui n'est pas très courant : CHARTEVENT_OBJECT_DELETE. Lorsque l'utilisateur supprime une ligne, cela se répercute sur le graphique et dans le système d’ordre. Vous devez donc être très prudent lorsque vous commencez à supprimer des lignes du graphique. Nous n'avons pas à nous inquiéter lorsque nous supprimons l'EA du graphique, car les ordres resteront inchangés, comme le montre l'animation suivante :


Mais si l'EA est sur le graphique, nous devons être très prudents lors de la suppression des lignes du graphique, et en particulier celles qui sont masquées dans la liste des objets. Vous pouvez voir sinon ci-dessous ce qui se passe dans le système d’ordres lorsque nous supprimons les lignes créées par le système d’ordres croisés.

Voyons maintenant ce qu'il advient de l’ordre lorsque l'on fait glisser les lignes de prix. N'oubliez pas : la ligne tracée doit être sélectionnée. Si elle n'est pas sélectionné, il sera impossible de la déplacer. Le changement de prix se produira lorsque la ligne sera publiée sur le graphique. Avant cela, le prix restera dans la même position qu'avant.


Modifions le code afin de savoir si une ligne est sélectionnée ou non. Les changements sont mis en évidence ci-dessous.

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                
        if (m_SelectObj != "")
        {
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_WIDTH, 1);
        }
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_WIDTH, 2);
                        m_SelectObj = sparam;
                };
        }
}

Le résultat de cette modification de code est visible ci-dessous.


Conclusion

J'ai montré ici comment créer un système d'ordres croisés dans MetaTrader. J'espère que ce système sera utile à tous ceux qui liront cet article. Mais n'oubliez : avant de commencer à trader sur un compte réel avec ce système, vous devez le tester aussi minutieusement que possible dans de nombreux scénarios de marché différents. Car bien que ce système soit implémenté dans la plateforme MetaTrader, il n'y a pas de support côté plateforme en terme de gestion des erreurs. Donc si elles arrivent par hasard, il faudra agir vite pour ne pas subir de grosses pertes. Mais en le testant dans différents scénarios, vous pouvez découvrir d'où viennent les problèmes : dans les mouvements, le nombre maximal d'ordres que votre ordinateur peut gérer, l'écart maximal autorisé pour le système d'analyse, le niveau de volatilité autorisé pour les ordres ouverts. Plus le nombre d'ordres ouverts est grand et plus il y a d'informations à analyser, plus il est probable que quelque chose de grave se produise. En effet, chaque ordre est analysé à chaque tick reçu par le système, ce qui peut poser problème lorsque de nombreux ordres sont ouverts en même temps.

Je recommande de ne pas faire confiance à ce système tant que vous ne l'avez pas testé sur un compte de démonstration et avec de nombreux scénarios. Même si le code semble très bien, il ne comporte aucune analyse d'erreur.

Je vous joins ici le code de tous les Expert Advisors.