English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
preview
Développer un Expert Advisor de trading à partir de zéro (Partie 26) : En route vers le futur (1)

Développer un Expert Advisor de trading à partir de zéro (Partie 26) : En route vers le futur (1)

MetaTrader 5Exemples | 5 avril 2024, 08:58
211 0
Daniel Jose
Daniel Jose

Introduction

Malgré les corrections de code et les améliorations présentées dans les articles Partie 24 et Partie 25 de la série "Développer un Expert Advisor de trading à partir de zéro" dans lesquels nous avons vu comment augmenter la robustesse du système, il reste encore quelques détails. Mais ce n’est pas parce qu’ils sont moins pertinents. Au contraire, ils sont vraiment importants.

Nous nous posons maintenant quelques questions liées à la manière dont nous voulons travailler et aux choses que nous faisons pendant notre journée de trading. De nombreux traders passent simplement un ordre à un certain prix et ne le déplacent pas à partir de ce point. Quoi qu’il arrive, ils supposeront qu’il s’agit du point d’entrée idéal et ne modifieront pas l’ordre. Ils peuvent déplacer les niveaux d'arrêt ou même supprimer les niveaux d'arrêt, mais ils ne modifient pas le point d'entrée.

Par conséquent, les failles restantes du code n’affecteront pas le fonctionnement réel des traders. Ils peuvent même se rendre compte que le système d’ordres contient des failles (par exemple celles que nous allons corriger dans cet article). Mais ceux qui aiment courir après le prix, essayant quand même d’entrer dans une transaction, mais ne veulent pas entrer sur le marché, seront témoins de nombreuses erreurs dans le système. Certains d'entre eux peuvent interférer et rendre les transactions dangereuses (c'est un euphémisme), tandis que d'autres gagneront, laissant ces traders impuissants face au marché.


2.0. Implémentation

Pour commencer notre voyage dans cet article, commençons par corriger une faille qui fait de l'EA un CRUSHER d'argent réel. Encore une fois, si vous ne changez pas constamment le point d’entrée, ce problème ne vous affectera pas. Mais je vous recommande de penser à mettre à jour votre code, juste au cas où. Même si le correctif est déjà implémenté dans le code joint, vous pourriez penser que cela nuira à l'EA car certaines performances sont perdues, ce qui est vrai. Cependant, qu’est-ce qui est mieux : perdre un peu de performance, ou risquer de perdre de l’argent sur une mauvaise entrée ?


2.0.1. Erreur de point d'entrée

Cette erreur est la première chose que nous allons corriger, même si elles doivent toutes être corrigées d'une manière ou d'une autre. Cependant, celle-ci est de loin la plus catastrophique de toutes. Cela se produit lorsque nous plaçons une entrée en attente, disons BUY STOP, et que nous déplaçons le point d'entrée de sorte que l'ordre soit désormais du type BUY LIMIT. Il ne semble y avoir aucun problème ici, mais cet échec est assez catastrophique. L'EA, au stade actuel de développement, ne sera pas en mesure d'effectuer le changement de la bonne manière. En fait, de nombreux EA souhaitent effectuer cette modification, et si cela se produit, vous verrez des informations sur le graphique, mais le serveur aura d'autres informations. Le système ne sera correctement mis à jour que lorsque la position sera ouverte. Jusque-là, les données entre ce que l'EA affiche sur le graphique et ce qui se trouve sur le serveur seront incohérentes.

Dans certains cas, nous n’avons que cette incohérence, mais dans d’autres cas, le problème sera un désastre complet. Pour le comprendre, lisez attentivement l’article.

Pour éliminer cette erreur, nous disposons d’une solution qui peut passer par différents chemins avant d’être appliquée. Mais le principe de fonctionnement sera toujours le même : supprimer l'ordre du carnet d'ordres, le déplacer vers une nouvelle position, changer le type d'ordre et le remettre dans le carnet d'ordres. C’est ce qui devrait être fait, mais la manière dont cela sera fait dépendra de la mise en œuvre spécifique.

Nous allons donc mettre en œuvre la solution la plus basique. Mais comme elle n’est pas idéale, nous devrons faire face à certains problèmes.

La solution est de modifier la fonction ci-dessous en ajoutant les lignes en surbrillance.

void SetPriceSelection(double price)
{
        char Pending;
                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

Même si cette solution résout partiellement le problème, elle ne le résout pas complètement. Par exemple, pour les ordres BUY STOP et SELL STOP, le problème est résolu en ajoutant ces lignes simples. Mais pour BUY LIMIT et STOP LIMIT, le serveur remplira immédiatement l’ordre une fois que nous aurons cliqué pour modifier le point d'entrée. Ce qui est pire ici, c'est que nous entrons dans une position perdante. Dans le cas où l'ordre est configuré comme un ordre vide (avec des niveaux de profit ou de perte) et que le point du Stop Loss est en dehors des limites de prix, alors, en plus du fait que le serveur exécutera immédiatement l'ordre, il le fermera également immédiatement, ce qui signifiera un désastre complet pour notre compte de trading. C'est pourquoi les systèmes de trading sont si difficiles à développer. Nous effectuons plusieurs tests sur un compte de démo et si tout semble fonctionner nous passons à un compte réel. A ce moment-là nous commençons à perdre de l'argent sans savoir ce qui se passe réellement.

Je le répète encore une fois : l'erreur N'AFFECTE PAS le cas où un point d'entrée est placé une seule fois et n'est jamais modifié. Le problème se produit lorsque le trader déplace ce point.

En fait, les ordres STOP fonctionnent bien. Nous devons maintenant résoudre le problème des ordre en attente de type LIMIT. Bien que ce problème puisse sembler facile à résoudre, il y a une chose à comprendre : il n’existe AUCUNE solution parfaite, et la solution qui fonctionne le mieux pour le développeur du système n’est peut-être pas celle qui fonctionne pour vous..

Je vais montrer ici l'une des solutions possibles à ce problème. La solution sera implémentée dans la fonction indiquée ci-dessus. Voici son nouveau code :

void SetPriceSelection(double price)
{
        char Pending;
        double last;
        long orderType;
                                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        orderType = OrderGetInteger(ORDER_TYPE);
                        if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                        {
                                last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
                                {
                                        RemoveOrderPendent(m_Selection.ticket);
                                        RemoveIndicator(m_Selection.ticket);
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                                        break;
                                }
                        }
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

Cela se fait comme suit : lorsque l'on va changer le point d'entrée d'un ordre en attente, on vérifie si l'ordre dans le carnet d'ordres (Depth of Market) est de type STOP LIMIT ou BUY LIMIT. Si ce n’est pas le cas, le flux d’exécution continuera vers un autre point du code. Si c’est le cas, nous effectuons une capture immédiate du prix actuel de l'actif et nous utiliserons les critères suivants : pour un ordre d'achat, nous utilisons la valeur ASK actuelle. Et nous prendrons la valeur BID pour les ordres de vente. Ceci remplace l'ancienne méthode utilisant la DERNIÈRE valeur. Mais comme elle n'est pas utilisée sur certains marchés, nous ne l'utiliserons pas comme référence. Vérifiez ensuite si l'ordre présent dans le carnet d'ordres est invalidé ou s'il est seulement modifié.

Si l’ordre est toujours valable, le système ignorera le code de validation et se dirigera vers la partie où l’ordre sera modifié. Mais si l’ordre dans le Depth of Market n’est pas valide, le système exécutera le code suivant :

RemoveOrderPendent(m_Selection.ticket);
RemoveIndicator(m_Selection.ticket);
CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
break;

Mais le code ci-dessus ne modifiera que les ordres SELL LIMIT et BUY LIMIT respectivement en SELL STOP et BUY STOP. Que se passe-t-il si nous voulons restaurer ces types à ceux d'origine ou simplement empêcher un tel changement ?

Si nous ne voulons pas que le système change le type de l'ordre exécuté, il suffit de remplacer le fragment en surbrillance par le code suivant :

if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
{
        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
        {
                RemoveOrderPendent(m_Selection.ticket);
                RemoveIndicator(m_Selection.ticket);
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                m_TradeLine.SpotLight();
                break;
        }
}

Ce code empêchera la modification du type de l’ordre. Vous pouvez modifier le moment auquel un ordre en attente sera exécuté, mais vous ne pouvez pas modifier un ordre LIMIT en ordre STOP ou vice versa. Maintenant, si vous souhaitez continuer à rechercher le prix et forcer l'entrée à un certain moment, utilisez le code ci-dessous. C'est le code qui sera utilisé dans l'EA.

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, m_Selection.pr,  m_Selection.tp, m_Selection.sl, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorFloat);
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

Note importante : Soyez prudent lorsque vous travaillez avec ce code en raison de la macro ForceNewType. Cette macro contient un break qui, une fois exécuté, fera sortir le code du bloc 'case'. Vous devez donc être extrêmement prudent lorsque vous modifiez ce bloc.

Le système n'aura plus d'erreur lors du déplacement du point d'entrée, mais nous avons d'autres problèmes à résoudre. Je vous ai montré comment corriger le problème en modifiant ou en gardant le même type d’ordre — vous choisissez celle qui vous convient le mieux. N'oubliez pas que chacune de ces solutions a ses avantages et ses inconvénients. Mais je n'entrerai pas dans les détails. Je montre seulement comment corriger et mettre en œuvre le système.

Le résultat de ces changements peut être vu dans la vidéo suivante :



2.0.2. Préparer l'avenir

Le changement ci-dessus résout le problème, mais il y a quelque chose de plus à faire. Je vais vous montrer ici le début de ce changement. En ce qui concerne le système d’ordres de l'EA, il reste encore beaucoup à améliorer. Peu de changements sont nécessaires, et je souhaite les expliquer afin que vous puissiez choisir la voie qui vous convient le mieux, car chaque trader a sa propre façon d'agir sur le marché. Je ne veux pas que vous vous sentiez obligé d'utiliser le système que je vais vous montrer. Au lieu de cela, je souhaite créer une base permettant à chacun de développer un EA personnalisé.

Passons donc au fait suivant : à partir de la partie 18, j'ai montré comment développer un système d'ordres facile à utiliser pour ceux qui tradent un actif particulier. Mais dans la partie 20, le système d’ordres a reçu des éléments visuels, car à un moment donné, Chart Trade deviendra inutile pour le trading, puisque tout sera indiqué par le système d’ordres lui-même. Vous pourrez donc tout modifier et configurer directement sur le graphique. Pour en arriver là, nous devons commencer quelque part. Et c’est maintenant.

Que diriez-vous de modifier le volume négocié directement dans l'ordre, sans avoir à supprimer l'ordre du graphique, de modifier le volume dans Chart Trade puis de replacer l'ordre sur le graphique ? Intéressant, n'est-ce pas ? Nous allons implémenter cette fonctionnalité dès maintenant. Cela aide beaucoup dans plusieurs scénarios. Mais vous devez apprendre et comprendre comment utiliser le système car vous ne le trouverez sur aucune autre plateforme. Pour être honnête, je n’ai jamais vu un EA doté d’une telle fonctionnalité. Voyons ce que vous pouvez faire pour avoir cette fonctionnalité dans n'importe quel EA.

Tout d’abord, définissez un nouvel indice d’indicateur.

#define def_IndicatorFloat      3

Lorsqu'un ordre en attente reçoit cette valeur sous forme de ticket, elle peut être traitée d'une manière complètement différente. Tout ce qui existait auparavant restera dans le système d’ordres, et nous ajouterons simplement un nouvel index.1

Après cela, nous ajouterons un nouvel objet au système :

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose,
                        m_BtnCheck;
C_Object_Edit           m_EditInfo1,
                        m_EditInfo2;
C_Object_Label          m_BtnMove;

Cet objet permettra toujours certaines choses pendant que l’ordre est en attente.

Passons maintenant à la classe C_Object_BitMap pour la modifier. Ajoutez ces quelques définitions :

#define def_BtnClose            "Images\\NanoEA-SIMD\\Btn_Close.bmp"
#define def_BtnCheckEnabled     "Images\\NanoEA-SIMD\\CheckBoxEnabled.bmp"
#define def_BtnCheckDisabled    "Images\\NanoEA-SIMD\\CheckBoxDisabled.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
#resource "\\" + def_BtnCheckEnabled
#resource "\\" + def_BtnCheckDisabled

Nous devons savoir ce qui se passe dans cette classe. Alors, ajoutez maintenant les fonctions suivantes :

bool GetStateButton(string szObjectName) const
{
        return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
}
//+------------------------------------------------------------------+
inline void SetStateButton(string szObjectName, bool bState)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, bState);
}

GetStateButton renvoie l'état du bouton. MetaTrader 5 change l'état. Nous n'avons donc pas besoin d'implémenter d'étapes supplémentaires, mais seulement de savoir si la valeur du bouton est True ou False. Mais il peut arriver que l’état ne reflète pas ce que nous souhaitons. Utilisez ensuite SetStateButton pour définir l’état afin qu’il reflète l’état réel tel que vu par le serveur de trading et par l’EA.

Une autre modification simple concerne la classe C_Object_Edit :

inline void SetOnlyRead(string szObjectName, bool OnlyRead)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, OnlyRead);
}

Elle indique si la valeur peut être modifiée ou non. Nous souhaitons pouvoir modifier les volumes d'ordres directement sur le graphique, sans utiliser Chart Trade. Tout ordre en attente créé sera toujours en lecture seule, mais nous créerons un système qui changera ça.

Revenons donc à C_IndicatorTradeView et implémentons quelques modifications supplémentaires. Nous allons créer une nouvelle fonction pour le système. C'est la suivante :

#define macroSwapAtFloat(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorFloat, A, B));
                bool PendingAtFloat(ulong ticket)
                        {
                                eIndicatorTrade it;
                                
                                if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;
                                macroSwapAtFloat(IT_PENDING, EV_CHECK);
                                for (char c0 = 0; c0 < 3; c0++)
                                {
                                        switch(c0)
                                        {
                                                case 0: it = IT_PENDING;        break;
                                                case 1: it = IT_STOP;           break;
                                                case 2: it = IT_TAKE;           break;
                                                default:
                                                        return false;
                                        }
                                        macroSwapAtFloat(it, EV_CLOSE);
                                        macroSwapAtFloat(it, EV_MOVE);
                                        macroSwapAtFloat(it, EV_EDIT);
                                        macroSwapAtFloat(it, EV_GROUND);
                                        macroSwapAtFloat(it, EV_LINE);
                                        m_EditInfo1.SetOnlyRead(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT), false);
                                }
                                return true;
                        }
#undef macroSwapAtFloat

Lorsque cette fonction est appelée, tous les objets indicateurs sont renommés, c'est à dire que la valeur pointant vers le ticket de l’ordre sera remplacée par une autre valeur. Dans ce cas, il s’agit de l’indicateur dont nous avons parlé au début de ce sujet. Il nous reste encore une question. Je n'utilise aucune structure pour maintenir la liste des objets indicateurs, je le fais d'une manière différente. De cette façon, nous laissons MetaTrader 5 s'occuper de cette liste pour nous. Mais à cause de cela, je ne peux pas créer d’ordres flottants illimités car nous serons limités à un seul ordre flottant. Cela peut être vérifié en utilisant la ligne suivante :

if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;

La vérification ici est simple : si la ligne indicatrice est située quelque part, la macro renverra une valeur différente de 0. On sait donc qu'il existe déjà un indicateur utilisant le ticket réservé. Cela sera important plus tard, pour que l'EA restaure les données de l'indicateur pour lequel la demande est refusée. MetaTrader 5 change automatiquement l'état de l'objet Bitmap. Nous devons donc informer l'appelant de l'échec.

Le prochain changement requis concerne la fonction qui crée les indicateurs :

#define macroCreateIndicator(A, B, C, D)        {                                                                               \
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                        \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                                     \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : (A == IT_PENDING ? 108 : 92)), (A == IT_RESULT ? 34 : 22));       \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                                   \
                m_EditInfo1.Size(sz0, 60, 14);                                                                                  \
                if (A != IT_RESULT)     {                                                                                       \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);            \
                        m_BtnMove.Size(sz0, 21, 23);                                                                            \
                                        }else                   {                                                               \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);                   \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                                       \
                                                }
                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING:
                                                macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit);
                                                m_BtnCheck.Create(ticket, sz0 = macroMountName(ticket, it, EV_CHECK), def_BtnCheckEnabled, def_BtnCheckDisabled);
                                                m_BtnCheck.SetStateButton(sz0, true);
                                                break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

Toutes les pièces mises en évidence ont été ajoutées pour prendre en charge notre nouveau système. En gros, nous créons ici une case à cocher qui sera toujours définie sur ’true’, ce qui signifie que l’ordre sera immédiatement placé dans le carnet d’ordres. Je n'ai pas souhaité modifier cette manière de trader. Mais ce n'est pas le simple fait de changer la valeur de la case à cocher de 'true' à 'false' qui empêchera que les ordres soient passés directement. Ce changement nécessiterait d'autres changements encore plus profonds. Et le problème est qu'à un moment donné, vous pourriez venir passer un ordre et oublier de cocher la case. Le point d’entrée serait manqué et on pourrait penser que l’EA est défectueux, alors qu’en réalité tout cela est dû à un oubli. Pour éviter cela, par défaut les ordres en attente iront donc directement dans le carnet d’ordres. Vous devrez donc changer explicitement leur statut.

La prochaine fonction très importante est présentée ci-dessous :

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        switch(m_Selection.it)
                                        {
                                                case IT_STOP   : m_Selection.sl = price; break;
                                                case IT_TAKE   : m_Selection.tp = price; break;
                                                case IT_PENDING:
                                                        m_Selection.sl = def_AdjustValue(m_Selection.sl);
                                                        m_Selection.tp = def_AdjustValue(m_Selection.tp);
                                                        m_Selection.pr = price;
                                                        break;
                                        }
                                        m_Selection.ticket = 0;
                                        m_TradeLine.SpotLight();
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

Les parties de code mises en surbrillance font une chose intéressante : elles mettent uniquement à jour les valeurs qui seront utilisées dans le sélecteur, mais ces valeurs sont en réalité stockées dans l'indicateur lui-même. Il peut également arriver que nous déplacions le système de manière plus générale. Nous avons donc besoin que ces valeurs soient spécifiées dans le sélecteur afin que la fonction qui effectue les calculs de position spécifie les valeurs correctes.

Il y a quelque chose dans cette fonction qui n'a peut-être pas de sens. Elle est responsable de la création et de la modification des données d'un ordre en attente. Mais si vous la regardez, vous ne verrez aucun point où l’ordre en attente sera renvoyé dans le carnet d’ordres. Vous pouvez déplacer, modifier et ajuster le volume de la valeur de l'ordre directement sur le graphique, mais vous ne pourrez pas voir comment il reviendra sur le graphique.

C'est un fait. L'ensemble du système de modification et de création d’ordres en attente est implémenté dans la fonction ci-dessus. Curieusement, cette fonction ne remet pas l’ordre dans le carnet d’ordres simplement parce que nous le souhaitons, mais parce qu'elle fait réellement une demande, comme indiqué ci-dessous. Pour ne pas compliquer, je montrerai uniquement la partie responsable d'une demande de passation d’ordre dans le Depth of Market.

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

// ... Internal code...

        case CHARTEVENT_OBJECT_CLICK:
                if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                {
                        case EV_CLOSE:
                                if (ticket == def_IndicatorFloat) RemoveIndicator(def_IndicatorFloat, it);
                                else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
                                {
                        case IT_PENDING:
                        case IT_RESULT:
                                if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                                break;
                        case IT_TAKE:
                        case IT_STOP:
                                m_Selection.ticket = ticket;
                                m_Selection.it = it;
                                SetPriceSelection(0);
                        break;
                }
                break;
        case EV_MOVE:
                if (ticket == def_IndicatorFloat)
                {
                        m_Selection.ticket = ticket;
                        m_Selection.it = it;
                }else   CreateGhostIndicator(ticket, it);
                break;
        case EV_CHECK:
                if (ticket != def_IndicatorFloat)
                {
                        if (PendingAtFloat(ticket)) RemoveOrderPendent(ticket);
                        else m_BtnCheck.SetStateButton(macroMountName(ticket, IT_PENDING, EV_CHECK), true);
                } else
                {
                        m_Selection.ticket = def_IndicatorTicket0;
                        m_Selection.it = IT_PENDING;
                        m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
                        m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
                        m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
                        m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
                        m_Selection.bIsDayTrade = true;
                        m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
                        SetPriceSelection(m_Selection.pr);
                        RemoveIndicator(def_IndicatorFloat);
                }

// ... Rest of the code...

Voyez comment le système se construit : nous programmons de moins en moins à mesure que le système grandit.

Le code surligné a quelque chose à voir avec l'indicateur que nous avons créé au début du sujet. Bien que tout semble bien fonctionner, nous avons certaines choses qui seront modifiées plus tard car lorsque l'ordre flottant reviendra dans le carnet d'ordres : il aura l'inconvénient d'être un ordre de day trading, il sera donc clôturé en fin de journée. Il sera modifié ultérieurement, mais vous devez en être conscient. Maintenant, vous pourriez être confus par tout cela et ne pas comprendre comment l'ordre en attente entre et sort réellement du carnet d'ordres, lorsque nous cliquons sur la case à cocher. Regardez le schéma ci-dessous :

Assurez-vous que tous les appels proviennent du même endroit. Nous avons supprimé un ordre de la Profondeur du Marché (Depth of Market), mais il restera sur le graphique. Toutes les manipulations sont effectuées comme indiqué dans les articles précédents. Mais si vous essayez de trouver une heure précise à laquelle l'ordre reviendra dans le Market Depth, vous risquez de vous perdre un peu dans le code. Maintenant, si vous regardez le diagramme, vous pouvez voir que l'appel provient de la fonction DispatchMessage, car c'est le seul endroit qui appelle la fonction SetPriceSelection. Mais si nous regardons la fonction SetPriceSelection, il n'y a aucune référence à la création d'un ordre avec l'index utilisé dans le système flottant. Mais faites attention à une chose. Nous avons la création d’ordre à l’index 0, et c'est exactement ce que nous utilisons. Nous modifions le ticket de l’ordre et nous informons qu'il s'agira du ticket d'index 0 — de cette façon l’ordre sera créé. Consultez le code ci-dessous pour comprendre comment cela fonctionne.

m_Selection.ticket = def_IndicatorTicket0;
m_Selection.it = IT_PENDING;
m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
m_Selection.bIsDayTrade = true;
m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
SetPriceSelection(m_Selection.pr);
RemoveIndicator(def_IndicatorFloat);

Le code est parfait sauf pour la ligne en surbrillance. Il n'existe actuellement aucun moyen de résoudre ce problème. Cela sera fait dans le prochain article, puisque nous devrons apporter quelques modifications à la classe elle-même.

La vidéo ci-dessous montre le résultat des changements. Faites attention à la manière dont le volume est modifié et à la manière dont un nouvel ordre est envoyé au point spécifié. L'EA est désormais beaucoup plus facile à utiliser.



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

Développer un Expert Advisor de trading à partir de zéro (Partie 27) : Vers le futur (II) Développer un Expert Advisor de trading à partir de zéro (Partie 27) : Vers le futur (II)
Passons à un système d’ordres plus complet directement sur le graphique. Dans cet article, je vais montrer un moyen de corriger le système d’ordres, ou plutôt de le rendre plus intuitif.
Apprendre à concevoir un système de trading basé sur les Fractales Apprendre à concevoir un système de trading basé sur les Fractales
Voici un nouvel article de notre série sur la façon de concevoir un système de trading basé sur les indicateurs techniques les plus populaires. Nous apprendrons un nouvel indicateur, l'indicateur Fractals, et nous apprendrons comment concevoir un système de trading basé sur celui-ci pour être exécuté dans le terminal MetaTrader 5.
Développer un Expert Advisor de trading à partir de zéro (Partie 28) : Vers l'avenir (III) Développer un Expert Advisor de trading à partir de zéro (Partie 28) : Vers l'avenir (III)
Il reste une tâche pour laquelle notre système d’ordres n'est pas à la hauteur, mais nous allons ENFIN la résoudre. MetaTrader 5 fournit un système de tickets qui permet de créer et de corriger les valeurs des ordres. L'idée est d'avoir un Expert Advisor qui rendrait le même système de tickets plus rapide et plus efficace.
Algorithmes d'optimisation de la population : Optimisation de la Lutte contre les Mauvaises Herbes Invasives (Invasive Weed Optimization, IWO) Algorithmes d'optimisation de la population : Optimisation de la Lutte contre les Mauvaises Herbes Invasives (Invasive Weed Optimization, IWO)
L'étonnante capacité des mauvaises herbes à survivre dans une grande variété de conditions est devenue l'idée d'un puissant algorithme d'optimisation. L'IWO est l'un des meilleurs algorithmes parmi ceux qui ont été examinés précédemment.