Développer un Expert Advisor de trading à partir de zéro (Partie 21) : Nouveau système d'ordres (IV)
Introduction
Dans l'article précédent, Développer un Expert Advisor de trading à partir de zéro (Partie 20), nous avons examiné les principaux changements à apporter pour obtenir le système visuel des ordres. Cependant, les étapes suivantes nécessitent plus d'explications. J’ai donc décidé de diviser l'article en plusieurs parties. Nous allons terminer ici les principales modifications. Il y en aura plusieurs, mais elles sont toutes nécessaires. L'ensemble du travail sera très intéressant. Mais le travail ne sera pas encore terminé. Il reste encore quelque chose à faire pour vraiment terminer le système. Quoi qu'il en soit, à la fin de cet article, le système disposera de presque toutes les fonctionnalités nécessaires.
Passons directement à sa mise en œuvre.
1. Implémentation
Ajoutons tout d’abord un bouton Fermer (CLOSE) ou Annuler (CANCEL) pour l’ordre. La classe responsable des boutons est la suivante.
1.1. Classe C_Object_BtnBitMap
Cette classe est responsable de la prise en charge des boutons bitmap sur le graphique.
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Object_Base.mqh" //+------------------------------------------------------------------+ #define def_BtnClose "Images\\NanoEA-SIMD\\Btn_Close.bmp" //+------------------------------------------------------------------+ #resource "\\" + def_BtnClose //+------------------------------------------------------------------+ class C_Object_BtnBitMap : public C_Object_Base { public : //+------------------------------------------------------------------+ void Create(string szObjectName, string szResource1, string szResource2 = NULL) { C_Object_Base::Create(szObjectName, OBJ_BITMAP_LABEL); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 0, "::" + szResource1); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 1, "::" + (szResource2 == NULL ? szResource1 : szResource2)); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, false); }; //+------------------------------------------------------------------+ bool GetStateButton(string szObjectName) const { return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE); } //+------------------------------------------------------------------+ };
En écrivant le code de cette classe, j'ai réalisé que la classe de positionnement pouvait être déplacée dans la classe C_Object_Base. La classe C_Object_BackGround dans son ensemble permettrait d'éliminer ce code puisqu'il appartiendrait à une classe inférieure. C'est ce que l'on appelle la réutilisation du code. Cette approche implique moins de programmation, et augmente les performances. Mais surtout le code devient plus stable car les modifications sont vérifiées plus fréquemment.
Pour ajouter un bouton CLOSE, nous allons procéder comme suit :
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Object_TradeLine.mqh" #include "C_Object_BtnBitMap.mqh" //+------------------------------------------------------------------+ class C_ObjectsTrade { // ... Class code ... }
Puis :
enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE};
Et l'étape suivante :
C_Object_BackGround m_BackGround;
C_Object_TradeLine m_TradeLine;
C_Object_BtnBitMap m_BtnClose;
Et l'étape suivante :
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2; string sz0; // ... Internal function code ... switch (it) { case IT_TAKE: case IT_STOP: m_BackGround.Size(sz0, 92, 22); break; case IT_PENDING: m_BackGround.Size(sz0, 110, 22); break; } m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose); }
Et l'étape suivante :
#define macroDelete(A) { \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE)); \ } inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it) else { macroDelete(IT_PENDING); macroDelete(IT_RESULT); macroDelete(IT_TAKE); macroDelete(IT_STOP); } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); } #undef macroDelete
Et enfin, la dernière étape :
#define macroSetAxleY(A) { \ m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \ m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y); \ m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y); \ } #define macroSetAxleX(A, B) { \ m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \ m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B); \ m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3);\ } inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy) { // ... Internal code... } #undef macroSetAxleX #undef macroSetAxleY
Lorsque vous exécutez ce système, vous obtenez le résultat suivant :
Mais ce bouton ne fonctionne toujours pas, bien que MetaTrader 5 génère un événement pour qu’il soit traité par l'Expert Advisor. Nous n'avons pas encore mis en œuvre cette fonctionnalité. Nous y reviendrons un peu plus loin dans cet article.
1.2. La classe C_Object_Edit
Le système serait inutile s'il ne pouvait pas informer le trader des valeurs en cours d’échanges. Pour cela, nous disposons de la classe C_Object_Edit. La classe devra subir quelques modifications par la suite pour accroître sa fonctionnalité. Mais pour l'instant, nous la laisserons telle quelle : elle informera le trader de ce qui se passe. Pour mettre cela en œuvre, nous devons ajouter quelques lignes de code dans la classe. Le premier extrait avec le nouveau code :
void Create(string szObjectName, color cor, int InfoValue) { C_Object_Base::Create(szObjectName, OBJ_EDIT); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, "Lucida Console"); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, 10); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_CENTER); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clrBlack); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, clrBlack); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true); SetTextValue(szObjectName, InfoValue, cor); }
Le code mis en évidence empêche les valeurs d'être modifiées par le trader. Mais comme je l'ai dit, cela changera à l'avenir. Cela nécessitera d'autres changements qui ne sont pas importants pour l'instant.
La fonction suivante permet d'afficher le texte. Attention à un détail quand même :
void SetTextValue(string szObjectName, int InfoValue, color cor = clrNONE) { color clr; clr = (cor != clrNONE ? cor : (InfoValue < 0 ? def_ColorNegative : def_ColoPositive)); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, IntegerToString(InfoValue < 0 ? -(InfoValue) : InfoValue)); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, clr); }
Le code sélectionné affichera la couleur de l'arrière-plan du texte en fonction de la valeur saisie. Il est important de le faire ici, car nous ne voulons pas essayer constamment de deviner si une valeur est négative ou positive, ou passer du temps à essayer de déterminer si le texte contient une valeur négative. Il est très pratique de regarder et de comprendre immédiatement si la valeur est positive ou négative. Voici ce que fait le code : vous pouvez maintenant déterminer instantanément si la valeur est négative ou positive, en fonction de la couleur. Mais il existe une condition qui exige que la couleur ne soit pas définie au préalable. Cela sera également utile plus tard.
Nous avons ensuite la dernière fonction de cette classe :
long GetTextValue(string szObjectName) const { return (StringToInteger(ObjectGetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT)) * (ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR) == def_ColorNegative ? -1 : 1)); };
Vous remarquerez que lorsque nous représentons des valeurs, elles sont toujours positives en raison de leur formatage. Mais lorsque nous vérifions le contenu d'un objet, nous devons disposer d'informations correctes. C'est ici que le code mis en évidence est utilisé. L'information sur la couleur est utilisée : si la couleur indique que la valeur est négative, elle sera corrigée pour fournir à l'EA l'information correcte. Si la couleur indique une valeur positive, la valeur sera simplement enregistrée.
Les définitions des couleurs se trouvent dans la classe elle-même. Ces couleurs peuvent être modifiées si vous souhaitez définir d'autres couleurs ultérieurement. Mais veillez à utiliser des couleurs différentes pour que la fonction précédente fonctionne correctement, faute de quoi l'EA obtiendra des valeurs ambiguës. L'EA peut donc considérer les valeurs négatives comme positives, ce qui pose des problèmes dans l'ensemble de l'analyse effectuée par l'EA.
1.3. La classe C_Object_Label
C'est la dernière classe dont nous avons besoin pour le moment. En fait, je pensais ne pas créer cette classe au début, car ses actions sont similaires à celles de la classe C_Object_BtnBitMap. Mais comme je voulais pouvoir ajouter des informations textuelles indépendamment de la classe C_Object_Edit, j'ai décidé de la créer ici.
Son code est très simple :
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Object_Edit.mqh" //+------------------------------------------------------------------+ class C_Object_Label : public C_Object_Edit { public : //+------------------------------------------------------------------+ void Create(string szObjectName, string Font = "Lucida Console", string szTxt = "", int FontSize = 10, color cor = clrBlack) { C_Object_Base::Create(szObjectName, OBJ_LABEL); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, Font); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, szTxt); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, FontSize); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor); }; //+------------------------------------------------------------------+ };
Nous n'avons besoin de rien d'autre dans cette classe, car tout le reste du travail a déjà été mis en œuvre par des objets de classe inférieure.
Comme vous pouvez le constater, la POO est un outil très puissant. Plus nous organisons le code en classes, moins nous avons besoin de programmer des classes similaires.
Il y a toutefois un petit changement que nous devons mettre en œuvre. En expérimentant, j'ai remarqué qu'il était très difficile d'interpréter les données du panneau. J'ai donc modifié les données comme suit :
Il est ainsi plus facile d'afficher des valeurs importantes. Voici à quoi ressemblera l'objet final : la partie supérieure indique le nombre de contrats ou le facteur de levier de la position ouverte, et la partie inférieure indique le résultat de la position.
Pour cela, nous devons modifier la classe C_Object_Base, qui est responsable du positionnement des objets. Les changements sont mis en évidence dans le code ci-dessous :
virtual void PositionAxleY(string szObjectName, int Y, int iArrow = 0) { int desl = (int)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, (iArrow == 0 ? Y - (int)(desl / 2) : (iArrow == 1 ? Y : Y - desl))); };
Nous pouvons passer ensuite à l'étape suivante : modifier la classe C_ObjectsTrade.
1.4. La classe C_ObjectsTrade
Terminons maintenant de dessiner les objets nécessaires. Nous pourrons alors vraiment obtenir le résultat souhaité sur le graphique : nous aurons toutes les informations présentées et tous les objets qui y sont liés. Ce n'est pas difficile, nous allons analyser les actions étape par étape. Si vous comprenez comment procéder, vous pourrez ajouter n’importe quelle autre information que vous souhaitez en suivant simplement les instructions et tout ira bien. La première chose à faire est de définir les nouveaux événements auxquels les objets doivent répondre. Ils sont mis en évidence dans le code ci-dessous :
enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE, EV_EDIT, EV_VOLUME, EV_MOVE};
Ajoutons maintenant les objets nécessaires. Les nouveaux objets sont également mis en évidence dans le code ci-dessous :
C_Object_BackGround m_BackGround; C_Object_TradeLine m_TradeLine; C_Object_BtnBitMap m_BtnClose; C_Object_Edit m_EditInfo, m_InfoVol; C_Object_Label m_BtnMove;
Nous créons ensuite des objets et nous définissons leur apparence à l'écran. Veuillez noter que les objets doivent être créés dans l'ordre dans lequel ils doivent apparaître : d'abord l'objet d'arrière-plan, puis l'objet suivant à placer sur l'arrière-plan et ainsi de suite, jusqu'à ce que tous les objets soient créés. Si vous le faites dans le mauvais ordre et que l'un des objets est caché, il suffit de modifier sa position ici. Voici maintenant le code correspondant :
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2, cor3; string sz0; int infoValue; switch (it) { case IT_TAKE : infoValue = m_BaseFinance.FinanceTake; cor1 = clrForestGreen; cor2 = clrDarkGreen; cor3 = clrNONE; break; case IT_STOP : infoValue = - m_BaseFinance.FinanceStop; cor1 = clrFireBrick; cor2 = clrMaroon; cor3 = clrNONE; break; case IT_PENDING: infoValue = m_BaseFinance.Leverange; cor1 = clrCornflowerBlue; cor2 = clrDarkGoldenrod; cor3 = clrLightBlue; break; case IT_RESULT : default: infoValue = m_BaseFinance.Leverange; cor1 = clrDarkBlue; cor2 = clrDarkBlue; cor3 = clrSilver; break; } m_TradeLine.Create(MountName(ticket, it, EV_LINE), cor2); if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE)); m_BackGround.Create(sz0 = MountName(ticket, it, EV_GROUND), cor1); switch (it) { case IT_TAKE: case IT_STOP: case IT_PENDING: m_BackGround.Size(sz0, 92, 22); break; case IT_RESULT: m_BackGround.Size(sz0, 84, 34); break; } m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose); m_EditInfo.Create(sz0 = MountName(ticket, it, EV_EDIT), cor3, infoValue); m_EditInfo.Size(sz0, 60, 14); if (it != IT_RESULT) m_BtnMove.Create(MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2); else { m_InfoVol.Create(sz0 = MountName(ticket, it, EV_VOLUME), clrNONE, infoValue); m_InfoVol.Size(sz0, 60, 14); } }
Toutes les lignes surlignées sont les ajouts au code effectués depuis la dernière version présentée dans l'article précédent. Nous pouvons maintenant écrire le code de la fonction d’après.
#define macroDelete(A) { \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT)); \ if (A != IT_RESULT) \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE)); \ else \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_VOLUME)); \ } inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it) else { macroDelete(IT_PENDING); macroDelete(IT_RESULT); macroDelete(IT_TAKE); macroDelete(IT_STOP); } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); } #undef macroDelete
Notez l'avantage de l'utilisation de la macro : j'ai dû ajouter uniquement les parties en surbrillance afin de pouvoir supprimer tous les objets du panneau. Nous utilisons maintenant 6 objets dans 4 panneaux. Une mise en œuvre différente nécessiterait trop de travail et présenterait donc une forte probabilité d'erreurs. Terminons la fonction de positionnement :
#define macroSetAxleY(A) { \ m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \ m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y); \ m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y); \ m_EditInfo.PositionAxleY(MountName(ticket, A, EV_EDIT), y, (A == IT_RESULT ? -1 : 0)); \ if (A != IT_RESULT) \ m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE), y); \ else \ m_InfoVol.PositionAxleY(MountName(ticket, A, EV_VOLUME), y, 1); \ } #define macroSetAxleX(A, B) { \ m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \ m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B); \ m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3); \ m_EditInfo.PositionAxleX(MountName(ticket, A, EV_EDIT), B + 21); \ if (A != IT_RESULT) \ m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE), B + 80); \ else \ m_InfoVol.PositionAxleX(MountName(ticket, A, EV_VOLUME), B + 21); \ } inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy) { double ad; int x, y; ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y); macroSetAxleY(it); macroSetAxleX(it, m_PositionMinimalAlxeX); if (Leverange == 0) return; if (it == IT_PENDING) { ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal()); ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))), x, y); macroSetAxleY(IT_TAKE); macroSetAxleX(IT_TAKE, m_PositionMinimalAlxeX + 110); ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)), x, y); macroSetAxleY(IT_STOP); macroSetAxleX(IT_STOP, m_PositionMinimalAlxeX + 220); } } #undef macroSetAxleX #undef macroSetAxleY
Là encore, très peu de code a été ajouté. Mais la fonction peut fonctionner avec tous les éléments car elle les positionne correctement grâce à l'utilisation de macros. Après avoir compilé l'EA, nous obtenons à ce stade le résultat suivant :
Bien que tout soit beau, les contrôles ne fonctionnent toujours pas : nous devons implémenter des événements pour chacun des objets. Sans les événements, cette interface est presque inutile, puisque la seule chose qu'elle fera sera de remplacer les lignes utilisées à l'origine.
2. Comment résoudre le problème ?
Si c'était simple, tout le monde pourrait le faire. Mais nous avons toujours des problèmes à résoudre, et cela fait partie du processus de développement. Je pourrais me contenter de fournir la solution au lieu de vous montrer comment résoudre le problème. Mais je veux que ces articles vous motivent à faire face aux problèmes et à apprendre à programmer. Il y aura donc quelque chose d'intéressant dans cette section.
2.1. Ajustement au fur et à mesure de la mise à jour du graphique
C'est le premier problème que nous rencontrons. Ce problème est dû au fait que les positions des objets ne sont pas mises à jour en même temps que le graphique. Pour le comprendre, regardez le gif ci-dessous :
Ce genre de choses peut être exaspérant, mais la solution est très simple : MetaTrader 5 génère lui-même un événement notifiant que le graphique doit être mis à jour. Nous devons donc capturer l'événement et mettre à jour notre système d’ordres.
La modification doit être capturée lors de l'appel de l'événement CHARTEVENT_CHART_CHANGE. La mise à jour est plus facile si vous utilisez la fonction UpdatePosition, qui est présente dans le code EA. Il suffit d'ajouter une seule ligne à notre code, dans C_OrderView, comme indiqué ci-dessous :
void DispatchMessage(int id, long lparam, double dparam, string sparam) { // ... Code .... switch (id) { case CHARTEVENT_CHART_CHANGE: SetPositionMinimalAxleX(); UpdatePosition(); break; // ... The rest of the code...
Cette solution simple présente un problème : si vous avez de nombreux ordres pour un instrument, cela peut prendre un certain temps, ce qui peut bloquer l'EA lors de la mise à jour avant qu'il ne puisse passer à la suite du traitement. Il existe d'autres solutions plus complexes pour accélérer le traitement. Mais cette solution est tout à fait suffisante pour ce système. Le résultat est le suivant :
Tout semble correct, n'est-ce pas ? Mais il y a une erreur. Il est difficile de s'en rendre compte tant que nous n'avons pas testé le système. Mais il y a bien une faille dans ce système qui, même après avoir été corrigée, ne va nulle part.
2.2. La sélection automatique des éléments dans l’EA
Si vous regardez le graphique ci-dessus, vous remarquerez que la ligne d'arrêt est sélectionnée, bien que nous ne l'ayons pas sélectionnée. C'est ce que fait l'EA à chaque fois. En fonction des paramètres définis lors de la création du tableau de bord, il se peut que le EA sélectionne le Take Profit ou la ligne de position, et ainsi de suite, à chaque fois que le graphique est déplacé.
Vous pouvez devenir fou en essayant de comprendre ce qui se passe, mais la solution est encore plus simple que la précédente : il suffit d'ajouter une ligne au même code, et l'EA arrêtera automatiquement de sélectionner une ligne. La solution est mise en évidence dans le code ci-dessous :
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2, cor3; string sz0; double infoValue; // ... Internal code... Select(NULL); }
Ces choses se produiront toujours au cours du développement. Je veux parler des bugs, ceux qui sont faciles à repérer et ceux qui sont moins évidents, que nous ne remarquons parfois pas au premier abord. Quoi qu'il en soit, cela peut arriver. L'apprentissage de la programmation est donc un processus continu. Parfois, vous pouvez résoudre ces problèmes vous-même et les partager avec tout le monde pour les aider à résoudre le même problème. Essayez de le faire. Personnellement, je le fais de manière systématique, car c'est ainsi que j'ai appris à programmer. De la même manière, vous pouvez apprendre à créer des programmes. En général, l'obtention d'un code source et sa modification font partie de votre apprentissage. Vous devriez le faire avec un code qui marche, de sorte qu'il est plus simple de comprendre comment il a été construit. Et cela porte souvent ses fruits, car nous apprenons beaucoup en comprenant comment chaque programmeur a réussi à résoudre un problème spécifique.
Mais c'était juste pour vous motiver à étudier. Passons à quelque chose de très important.
3. Gestion des événements
Nous allons construire un système qui donnera le résultat d'une position. Cela remplacera la zone correspondante dans Chart Trade. Mais je laisserai Chart Trade tel quel, car il indique le résultat total des positions. Si un compte prend en charge la couverture, la valeur sera différente de celle spécifiée dans le système d'ordres, car l'une est une valeur locale et l'autre représente la valeur totale. Il n'y aura pas de différence pour les autres types de comptes. Si vous le souhaitez, vous pouvez donc supprimer le système de résultats de Chart Trade.
3.1. Afficher le résultat de la position
Ceux qui voient le code pour la première fois peuvent être perdus, ne sachant pas où chercher l'information, et pourraient créer d'autres opérations pour faire quelque chose que le code original fait déjà. C'est ce qui conduit généralement à de nombreux problèmes : nous pouvons générer du code supplémentaire qui génère des bugs supplémentaires que le code d'origine n'avait pas. Cela n'est pas conforme à la règle de REUTILISATION, selon laquelle vous ne devez programmer que lorsque c'est vraiment nécessaire. En sachant comment fonctionne MetaTrader 5, et en sachant comment l'EA fonctionne déjà, vous devriez donc pouvoir trouver l'endroit où le résultat affiché dans Chart Trade est généré. Car si le résultat des positions est donné, nous devons l'utiliser. Prêtez attention au code ci-dessous :
void OnTick() { Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]); TimesAndTrade.Update(); }
Nous passons ensuite au point mis en évidence. Le code source est présenté ci-dessous :
inline double CheckPosition(void) { double Res = 0, sl, profit, bid, ask; ulong ticket; bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID); ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK); for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol()) { ticket = PositionGetInteger(POSITION_TICKET); sl = PositionGetDouble(POSITION_SL); if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { if (ask < sl) ClosePosition(ticket); }else { if ((bid > sl) && (sl > 0)) ClosePosition(ticket); } Res += profit; } return Res; };
On pourrait se dire : mais comment vais-je prendre les données et les appliquer au panneau, s'il n'y a aucun moyen de savoir à quels objets je dois me référer ? Il peut sembler que les objets aient été créés en vrac, sans aucun critère ni soin... Mais ce n'est pas vrai. Si vous pensez ou imaginez les choses de cette manière, il serait préférable de commencer à en apprendre un peu plus sur le fonctionnement réel de la plateforme MetaTrader 5. Il est vrai que je n'ai pas créé de liste, de tableau ou de structure référençant les objets créés, mais c'était voulu parce que je sais que ça marche. Je vais vous montrer que cela fonctionne vraiment : vous pouvez faire référence à un certain objet sans utiliser de structure pour stocker les objets qui figureront sur le graphique. Ce qui est vraiment nécessaire est de modéliser correctement le nom de l'objet. C'est tout.
Question : Comment les noms des objets ont-ils été modélisés ?
Réponse :
1 - En-tête | Cette séquence permet de distinguer l'objet utilisé dans le système d’ordres de tous les autres objets. |
2 - Séparateur | Indique que d'autres informations vont suivre |
3 - Indication du type | Distingue entre le Take Profit et le Stop Loss. |
4 - Séparateur | Même chose que pour le point 2. |
5 - Ticket de l’ordre ou de la position | Rappelle le numéro du ticket de l’ordre : relie les données de l’ordre OCO et fait la distinction entre les ordres. |
6 - Séparateur | Même chose que pour le point 2. |
7 - Indication de l'événement | Distinction entre les objets d'un même panneau |
C'est-à-dire que la modélisation est tout, même s'il semble à ceux qui ne programment pas réellement que nous créons quelque chose de répétitif, en fait nous créons quelque chose d'unique : chacun des objets est unique et peut être référencé par une simple règle. Cette règle est créée par le code suivant :
inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev) { return StringFormat("%s%c%c%c%d%c%c", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)ev); }
Par conséquent, si vous indiquez au code précédent le numéro de ticket de l’ordre, l'indicateur et l'événement auxquels vous souhaitez accéder, vous obtiendrez le nom d'un objet particulier. Cela permet de manipuler ses attributs. Sachant qu'il s'agit d'une première étape, nous devons encore prendre une décision : comment rendre cette manipulation sûre, sans causer de dégâts dans le code et sans le transformer en un code ressemblant à Frankenstein ?
Voici ce qu'il faut faire. Accédons à la classe C_ObjectsTrade et ajoutons le code suivant :
inline void SetResult(ulong ticket, double dVolume, double dResult) { m_InfoVol.SetTextValue(MountName(ticket, IT_RESULT, EV_VOLUME), (dVolume / Terminal.GetVolumeMinimal()), def_ColorVolumeResult); m_EditInfo.SetTextValue(MountName(ticket, IT_RESULT, EV_EDIT), dResult); }
Passons maintenant à la classe C_Router et ajoutons le code mis en évidence :
inline double CheckPosition(void) { double Res = 0, sl, profit, bid, ask; ulong ticket; bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID); ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK); for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol()) { ticket = PositionGetInteger(POSITION_TICKET); SetResult(ticket, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT)); sl = PositionGetDouble(POSITION_SL); if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { if (ask < sl) ClosePosition(ticket); }else { if ((bid > sl) && (sl > 0)) ClosePosition(ticket); } Res += profit; } return Res; };
Cela résout l'un des problèmes. Mais nous avons encore d'autres problèmes non résolus.
3.2. Indiquer le volume de l'ordre en attente
Résolvons maintenant le problème du volume indiqué dans un ordre en attente. Pour ce faire, nous devons créer une nouvelle fonction dans la classe C_ObjectsTrade.
inline void SetVolumePendent(ulong ticket, double dVolume) { m_EditInfo.SetTextValue(MountName(ticket, IT_PENDING, EV_EDIT), dVolume / Terminal.GetVolumeMinimal(), def_ColorVolumeEdit); }
Une fois cela fait, nous utilisons la fonction UpdatePosition de la classe C_Router et la mise à jour s'effectue sans problème :
void UpdatePosition(int iAdjust = -1) { // ... Internal code .... for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { price = OrderGetDouble(ORDER_PRICE_OPEN); take = OrderGetDouble(ORDER_TP); stop = OrderGetDouble(ORDER_SL); bTest = CheckLimits(price); vol = OrderGetDouble(ORDER_VOLUME_CURRENT); // ... Internal code... CreateIndicatorTrade(ul, price, IT_PENDING); SetVolumePendent(ul, vol); CreateIndicatorTrade(ul, take, IT_TAKE); CreateIndicatorTrade(ul, stop, IT_STOP); } };
Le problème est ainsi résolu. Nous devons maintenant résoudre le problème des valeurs indiquées comme Take et Stop, car ces valeurs ne sont plus vraies après que nous ayons placé l'ordre sur le graphique.
3.3. Événement du clic sur le bouton de fermeture du panneau
Le seul moyen sûr de supprimer un ordre ou l'un de ses niveaux d'arrêt est le bouton Fermer (CLOSE) situé dans le coin de chacune des valeurs. Mais nous ici avons un événement mal mis en œuvre. Réglons ce problème.
L'événement de clic doit en fait être implémenté dans la classe C_OrderView. Remplaçons l'ancien système par le code mis en évidence :
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price, pp, pt, ps; eIndicatorTrade it; eEventType ev; switch (id) { // ... Internal code... case CHARTEVENT_OBJECT_CLICK: if (GetInfosOrder(sparam, ticket, price, it, ev)) { switch (ev) { case EV_CLOSE: if (OrderSelect(ticket)) switch (it) { case IT_PENDING: RemoveOrderPendent(ticket); break; case IT_TAKE: ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL)); break; case IT_STOP: ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0); break; } if (PositionSelectByTicket(ticket)) switch (it) { case IT_RESULT: ClosePosition(ticket); break; case IT_TAKE: ModifyPosition(ticket, 0, PositionGetDouble(POSITION_SL)); break; case IT_STOP: ModifyPosition(ticket, PositionGetDouble(POSITION_TP), 0); break; } break; // ... Rest of the code...
Il y a encore une autre chose à ajouter dans cette classe. Que se passerait-il si le trader supprimait accidentellement un objet qui donne les données de la position ? Pour éviter cela, le code suivant a été ajouté au système :
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price, pp, pt, ps; eIndicatorTrade it; eEventType ev; switch (id) { case CHART_EVENT_OBJECT_DELETE: case CHARTEVENT_CHART_CHANGE: SetPositionMinimalAxleX(); UpdatePosition(); break; // ... Rest of the code...
Ainsi, si l'opérateur supprime quelque chose qu'il ne devrait pas, l'EA remplacera rapidement l'objet supprimé.
La vidéo suivante montre le fonctionnement actuel du système. Il y a également eu des changements que je n'ai pas abordés dans l'article, car il s'agissait de changements mineurs qui n'auraient pas eu d'incidence sur mon explication.
Conclusion
Bien que le système semble complet et que vous souhaitiez l'utiliser, je dois vous avertir qu'il n'est pas encore terminé. Cet article est censé vous montrer comment vous pouvez ajouter et modifier des éléments afin d'obtenir un système d’ordres beaucoup plus pratique et facile à utiliser. Mais il manque encore le système responsable du déplacement des positions et c'est ce qui rendra l'EA très instructif, pratique et intuitif à utiliser. Mais nous laisserons cela pour le prochain article.
La pièce jointe contient le système tel qu'il est au stade actuel de développement.
Traduit du portugais par MetaQuotes Ltd.
Article original : https://www.mql5.com/pt/articles/10499
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation