![Développer un Expert Advisor de trading à partir de zéro (Partie 23) : Nouveau système d'ordres (VI)](https://c.mql5.com/2/49/Developing_a_trading_Expert_Advisor_002_600x314.jpg)
Développer un Expert Advisor de trading à partir de zéro (Partie 23) : Nouveau système d'ordres (VI)
Introduction
Dans l'article précédent Développement d'un Expert Advisor de trading à partir de zéro (Partie 22), nous avons développé un système pour déplacer les ordres en attente et les niveaux d'arrêt des positions. Bien que cette méthode soit relativement sûre (parce qu'elle reflète ce qui se trouve sur le serveur de trading), ce n'est pas la meilleure façon de faire évoluer les niveaux rapidement.
Le problème est que chaque fois que nous modifions quelque chose à l'aide de la souris, cet événement est envoyé au serveur, et nous devons alors attendre une réponse. Le problème est lié au fait que l'événement est envoyé à chaque tick. C'est-à-dire que si nous devions déplacer un niveau de plusieurs ticks à la fois, nous devrions passer par toutes les valeurs intermédiaires, ce qui rend l'ensemble du processus très lent. C'est pourquoi nous apportons des modifications au code afin de le rendre plus flexible et de changer les niveaux plus rapidement.
1.0. Plan
Pour mettre en œuvre les changements, nous devons faire une chose très simple : Ne pas informer le serveur de toutes les modifications, ne communiquer au serveur que les modifications nécessaires. Le simple fait de procéder ainsi permet déjà de tout faire fonctionner correctement, même si nous ne sommes pas absolument sûrs que tout se passe exactement comme nous le faisons.
Voyons maintenant où nous devons modifier le code. Nous utilisons une fonction distincte présentée ci-dessous :
#define macroGetPrice(A) StringToDouble(ObjectGetString(Terminal.Get_ID(), MountName(ticket, A, EV_LINE), OBJPROP_TOOLTIP)) void MoveSelection(double price, uint keys) { static string memStr = NULL; static ulong ticket = 0; static eIndicatorTrade it; eEventType ev; double tp, sl, pr; bool isPending; string sz0 = m_TradeLine.GetObjectSelected(); if (sz0 != NULL) { if (memStr != sz0) GetIndicatorInfos(memStr = sz0, ticket, pr, it, ev); isPending = OrderSelect(ticket); switch (it) { case IT_TAKE: if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), price, macroGetPrice(IT_STOP)); else ModifyPosition(ticket, price, macroGetPrice(IT_STOP)); break; case IT_STOP: if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), macroGetPrice(IT_TAKE), price); else ModifyPosition(ticket, macroGetPrice(IT_TAKE), price); break; case IT_PENDING: pr = macroGetPrice(IT_PENDING); tp = macroGetPrice(IT_TAKE); sl = macroGetPrice(IT_STOP); ModifyOrderPendent(ticket, price, (tp == 0 ? 0 : price + tp - pr), (sl == 0 ? 0 : price + sl - pr)); break; } }; } #undef macroGetPrice
En plus des modifications à l'intérieur de la fonction, nous devrons également mettre en œuvre certains changements liés aux événements de la souris. C'est sur ce point que nous allons nous concentrer en premier lieu.
Les segments mis en évidence doivent être remplacés par quelque chose d'autre pour représenter les nouvelles positions qui seront utilisées. Mais tous les changements doivent être compréhensibles pour l'utilisateur.
J'ai trouvé un moyen efficace de rendre tout cela facile à comprendre et en même temps fonctionnel, sans apporter de grands changements à l'ensemble de la structure du code. Il s'agit de créer une étiquette d'indication fantôme qui ne sera visible qu'en cas de besoin. Heureusement, MetaTrader 5 offre un moyen très simple de le faire. Cet article sera donc très facile à comprendre pour ceux qui sont déjà familiarisés avec le contenu des articles précédents de cette série.
2.0. Implémentation
Pour mettre en œuvre une étiquette fantôme, nous la créerons simplement avec une étiquette réelle. Il s'agit de l'ombre exacte d'une étiquette réelle jusqu'à ce que nous travaillions avec les prix de la manière décrite dans l'article précédent. À ce stade, l'étiquette fantôme apparaît sur le graphique au fur et à mesure que l'étiquette réelle se déplace. Cela vous permettra de comparer facilement ce qui se passe et de comprendre s'il y a lieu ou non d'apporter des changements.
2.0.1. Création d'une étiquette d'indication de fantôme
Tous les changements sont mis en œuvre dans la classe C_IndicatorTradeView. Commençons par définir 3 nouvelles directives :
#define def_IndicatorGhost "G" #define def_IndicatorReal "R" #define def_IndicatorGhostColor clrDimGray
Cela permettra à MetaTrader 5 de fonctionner pour nous. La règle est la suivante : nous créons d'abord une étiquette fantôme, puis une étiquette réelle. MetaTrader 5 prendra ainsi soin de ne pas afficher l'étiquette fantôme jusqu'au moment où nous aurons vraiment besoin de la voir. Comme cela est fait par MetaTrader 5 lui-même, nous économiserons beaucoup sur la programmation.
Détail important : si vous voulez changer la couleur du fantôme, il suffit de changer la couleur spécifiée dans la partie sélectionnée.
L'étape suivante consiste donc à modifier la fonction qui permet de générer des noms uniques.
inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev, bool isGhost = false) { return StringFormat("%s%c%c%c%llu%c%c%c%s", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)(isGhost ? ev + 32 : ev), def_SeparatorInfo, (isGhost ? def_IndicatorGhost : def_IndicatorReal)); }
Les parties surlignées ont été ajoutées ou modifiées par rapport à la version précédente. Nous pouvons laisser MetaTrader 5 générer des noms uniques. Il n'y a donc pas lieu de s'inquiéter à ce sujet. Notez que j'ai ajouté des valeurs aux événements. J'ai fait cela pour empêcher le fantôme de recevoir des événements et d'essayer de prendre le contrôle.
L'étape suivante est évidente : nous devons créer l'étiquette elle-même :
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2, cor3; string sz0, sz1; switch (it) { case IT_TAKE : cor1 = clrForestGreen; cor2 = clrDarkGreen; cor3 = clrNONE; break; case IT_STOP : cor1 = clrFireBrick; cor2 = clrMaroon; cor3 = clrNONE; break; case IT_PENDING: cor1 = clrCornflowerBlue; cor2 = clrDarkGoldenrod; cor3 = def_ColorVolumeEdit; break; case IT_RESULT : default: cor1 = clrDarkBlue; cor2 = clrDarkBlue; cor3 = def_ColorVolumeResult; break; } m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE, true), def_IndicatorGhostColor); m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE), cor2); if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE)); if (it != IT_RESULT) m_BackGround.Create(ticket, sz0 = MountName(ticket, it, EV_GROUND, true), def_IndicatorGhostColor); m_BackGround.Create(ticket, sz1 = MountName(ticket, it, EV_GROUND), cor1); switch (it) { case IT_TAKE: case IT_STOP: case IT_PENDING: m_BackGround.Size(sz0, 92, 22); m_BackGround.Size(sz1, 92, 22); break; case IT_RESULT: m_BackGround.Size(sz1, 84, 34); break; } m_BtnClose.Create(ticket, MountName(ticket, it, EV_CLOSE), def_BtnClose); m_EditInfo1.Create(ticket, sz0 = MountName(ticket, it, EV_EDIT, true), def_IndicatorGhostColor, 0.0); m_EditInfo1.Create(ticket, sz1 = MountName(ticket, it, EV_EDIT), cor3, 0.0); m_EditInfo1.Size(sz0, 60, 14); m_EditInfo1.Size(sz1, 60, 14); if (it != IT_RESULT) { m_BtnMove.Create(ticket, sz0 = MountName(ticket, it, EV_MOVE, true), "Wingdings", "u", 17, def_IndicatorGhostColor); m_BtnMove.Create(ticket, sz1 = MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2); m_BtnMove.Size(sz1, 21, 21); }else { m_EditInfo2.Create(ticket, sz1 = MountName(ticket, it, EV_PROFIT), clrNONE, 0.0); m_EditInfo2.Size(sz1, 60, 14); } }
Toutes les lignes surlignées créent des fantômes. Une chose peut paraître étrange : pourquoi ne reproduisons-nous pas tous les éléments ? En effet, le fantôme n'est pas une copie exacte de l'étiquette réelle mais seulement son ombre. Il n'est donc pas nécessaire de créer tous les éléments. L'étiquette réelle est celle qui définira ce qui doit se passer. L’étiquette fantôme ne sert que de référence à ce que le serveur de trading verra.
Vient maintenant la partie qui nécessite une étude plus détaillée, où MetaTrader 5 va vraiment travailler dur. On pourrait penser qu'arranger les objets au bon endroit demande beaucoup de travail. Mais regardez ce qui a changé dans le code source.
#define macroSetAxleY(A, B) { \ m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND, B), y); \ m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE, B), y); \ m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE, B), y); \ m_EditInfo1.PositionAxleY(MountName(ticket, A, EV_EDIT, B), y, (A == IT_RESULT ? -1 : 0)); \ m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE, B), (A == IT_RESULT ? 9999 : y)); \ m_EditInfo2.PositionAxleY(MountName(ticket, A, EV_PROFIT, B), (A == IT_RESULT ? y : 9999), 1); \ } #define macroSetAxleX(A, B, C) { \ m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND, C), B); \ m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE, C), B); \ m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE, C), B + 3); \ m_EditInfo1.PositionAxleX(MountName(ticket, A, EV_EDIT, C), B + 21); \ m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE, C), B + 80); \ m_EditInfo2.PositionAxleX(MountName(ticket, A, EV_PROFIT, C), B + 21); \ } //+------------------------------------------------------------------+ inline void ReDrawAllsIndicator(void) { int max = ObjectsTotal(Terminal.Get_ID(), -1, -1); ulong ticket; double price; eIndicatorTrade it; eEventType ev; for (int c0 = 0; c0 <= max; c0++) if (GetIndicatorInfos(ObjectName(Terminal.Get_ID(), c0, -1, -1), ticket, price, it, ev)) PositionAxlePrice(ticket, it, price); } //+------------------------------------------------------------------+ inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price) { int x, y, desl; ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y); ObjectSetString(Terminal.Get_ID(), MountName(ticket, it, EV_LINE), OBJPROP_TOOLTIP, DoubleToString(price)); macroSetAxleY(it, true); macroSetAxleY(it, false); switch (it) { case IT_TAKE: desl = 110; break; case IT_STOP: desl = 220; break; default: desl = 0; } macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), true); macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), false); } #undef macroSetAxleX #undef macroSetAxleY
Est-ce que c'est tout ce qu’il y a à faire ? Faut-il simplement modifier les macros ? Il est vrai qu'il n'est pas nécessaire de recréer l'ensemble du code, nous ne faisons que l'adapter. Tout ce que nous avons à faire est d'indiquer à MetaTrader 5 le nom de l'objet que nous manipulons. Et MetaTrader 5 fera le reste pour nous. Il n'est pas nécessaire de créer une série de fonctions pour cela. Il suffit d'ajouter les parties surlignées.
Une autre fonction à modifier ici est illustrée ci-dessous :
void SetTextValue(ulong ticket, eIndicatorTrade it, double value0, double value1 = 0.0, double priceOpen = 0.0) { double finance; switch (it) { case IT_RESULT : PositionAxlePrice(ticket, it, priceOpen); PositionAxlePrice(ticket, IT_PENDING, 0); m_EditInfo2.SetTextValue(MountName(ticket, it, EV_PROFIT), value1); case IT_PENDING: value0 = value0 / Terminal.GetVolumeMinimal(); m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit); m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor); break; case IT_TAKE : case IT_STOP : finance = (value1 / Terminal.GetAdjustToTrade()) * value0; m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance); m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor); break; } }
Ici, nous avons ajouté les segments sélectionnés. C'est de cette façon que notre fantôme est sélectionné. Il reflète fidèlement ce qui se passe avec la vraie étiquette. En fait, il la reflète même trop bien. A tel point que nous devrons mettre en œuvre quelques éléments supplémentaires pour qu'il fonctionne correctement. Le code n'est pas faux, mais le fantôme est trop étroitement lié à l'étiquette réelle, ce que nous devrions en fait éviter.
La dernière fonction à modifier est illustrée ci-dessous :
inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL) { #define macroDestroy(A, B) { \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND, B)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE, B)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE, B)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT, B)); \ if (A != IT_RESULT) ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE, B)); \ else ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_PROFIT, B)); \ } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT)) { macroDestroy(IT_RESULT, true); macroDestroy(IT_RESULT, false); macroDestroy(IT_PENDING, true); macroDestroy(IT_PENDING, false); macroDestroy(IT_TAKE, true); macroDestroy(IT_TAKE, false); macroDestroy(IT_STOP, true); macroDestroy(IT_STOP, false); } else { macroDestroy(it, true); macroDestroy(it, false); } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); #undef macroDestroy }
Les fragments mis en évidence ont changé, il n'y a rien de spécial ici.
2.0.2. Séparer le fantôme de la réalité
Les modifications apportées dans la section précédente créent un fantôme. Mais nous avons un problème : il est trop proche de l'objet réel. Il s'agit d'une mesure qui, à première vue, sera très difficile à mettre en œuvre. Mais lorsque nous examinons le code, nous pouvons constater que nous avons déjà une solution. Mais cette solution n'est pas au bon endroit. Nous devons changer l'endroit où se trouve la solution et la rendre plus visible dans toute la classe.
La solution est présentée dans le code suivant :
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price, tp, sl; bool isBuy, bKeyBuy, bKeySell, bEClick; long info; datetime dt; uint mKeys; eIndicatorTrade it; eEventType ev; static bool bMounting = false, bIsDT = false, bIsMove = false; static double leverange = 0, valueTp = 0, valueSl = 0, memLocal = 0; switch (id) { case CHARTEVENT_MOUSE_MOVE: // ... The rest of the code...
La solution correspond au code mis en évidence. Mais comment est-ce possible ? Rappelez-vous que lorsque vous placez un ordre en attente, le système se charge de créer et de manipuler les données de sorte qu'au final, vous avez une représentation des étiquettes sur le graphique indiquant l'endroit où l'ordre sera placé. C’est ce qui est fait avec le code suivant :
case CHARTEVENT_MOUSE_MOVE: Mouse.GetPositionDP(dt, price); mKeys = Mouse.GetButtonStatus(); bEClick = (mKeys & 0x01) == 0x01; //left mouse click bKeyBuy = (mKeys & 0x04) == 0x04; //SHIFT pressed bKeySell = (mKeys & 0x08) == 0x08; //CTRL pressed if (bKeyBuy != bKeySell) { if (!bMounting) { Mouse.Hide(); bIsDT = Chart.GetBaseFinance(leverange, valueTp, valueSl); valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / leverange); valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / leverange); m_TradeLine.SpotLight(MountName(def_IndicatorTicket0, IT_PENDING, EV_LINE)); bMounting = true; } tp = price + (bKeyBuy ? valueTp : (-valueTp)); sl = price + (bKeyBuy ? (-valueSl) : valueSl); UpdateInfosIndicators(0, def_IndicatorTicket0, price, tp, sl, leverange, bKeyBuy); if ((bEClick) && (memLocal == 0)) CreateOrderPendent(leverange, bKeyBuy, memLocal = price, tp, sl, bIsDT); }else if (bMounting) { UpdateInfosIndicators(0, def_IndicatorTicket0, 0, 0, 0, 0, false); Mouse.Show(); memLocal = 0; bMounting = false; //... Rest of the code...
Selon que vous appuyez sur la touche SHIFT pour acheter ou sur la touche CTRL pour vendre, le système créera une représentation de l'ordre en attente. Il sera affiché directement sur le graphique. Les valeurs à utiliser comme Take Profit ou comme Stop Loss sont capturées dans Chart Trade. Vous déplacez ensuite la représentation sur le point où vous souhaitez placer l'ordre. Puis un clic avec le bouton gauche de la souris indique au système qu'un ordre doit être placé à cet endroit. Dès que la souris se déplace à nouveau, l'étiquette utilisée pour représenter l'ordre est supprimée et l'indication de l'ordre est laissée.
Jusqu'à présent, il n'y avait rien de spécial. Mais si vous regardez dans le code, vous verrez ce qui suit :
// ... CHARTEVENT_MOUSE_MOVE code.... }else if ((!bMounting) && (bKeyBuy == bKeySell)) { if (bEClick) { bIsMove = false; m_TradeLine.SpotLight(); } MoveSelection(price, mKeys); } break; // ... The rest of the code....
Lorsque nous ne créons pas d'ordre en attente et que les touches sont relâchées, nous envoyons ainsi la position du prix de la souris à une étiquette qui peut être sélectionnée. Cela est fait dans la ligne surlignée. Dès que nous cliquons sur le bouton gauche de la souris, nous mettons fin à ce transfert. L'indicateur est désélectionné et cela modifie l'état d'une variable qui est actuellement locale, bIsMove. Mais nous allons changer cela. L'état de cette variable n'est modifié que par un autre événement à l'intérieur de la fonction DispatchMessage. Cet événement se présente de cette façon :
// ... Code .... case EV_MOVE: if (bIsMove) { m_TradeLine.SpotLight(); bIsMove = false; }else { m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE)); bIsMove = true; } break;
Ce code modifiera l'état de la variable bIsMove et modifiera en même temps l'étiquette pour indiquer si elle est sélectionnée ou non.
Ainsi, si nous rendons cette variable visible dans toute la classe, nous pouvons séparer le fantôme de l'étiquette réelle. Il sera alors possible de manipuler soit uniquement le réel, soit uniquement le fantôme - cela dépend de ce que vous trouvez le plus intéressant. Nous changerons ici l'étiquette réelle. Le fantôme montrera ce que le serveur de trading voit.
De cette manière, nous n'avons pas besoin de toucher au code, mais seulement d'ajuster quelques détails. Une fois que le clic gauche a été effectué et qu'un objet a été déplacé, un ordre de modification d'un ordre en attente ou d'un niveau d'arrêt sera envoyé.
Voyons comment cela se passe en pratique. Créons tout d’abord une variable privée :
bool m_bIsMovingSelect;
Cela reflète ce que j'ai expliqué plus haut. Mais elle doit être initialisée :
C_IndicatorTradeView() : m_bIsMovingSelect(false) {}
Nous allons maintenant dans DispatchMessage et nous utilisons cette variable à la place de bIsMove :
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; // ... Internal code... switch (id) { case CHARTEVENT_MOUSE_MOVE: // ... Internal code... }else if ((!bMounting) && (bKeyBuy == bKeySell)) { if (bEClick) { m_bIsMovingSelect = false; m_TradeLine.SpotLight(); } MoveSelection(price, mKeys); } break; // ... Internal code... case CHARTEVENT_OBJECT_CLICK: if (GetIndicatorInfos(sparam, ticket, price, it, ev)) switch (ev) { // ... Internal code.... case EV_MOVE: if (m_bIsMovingSelect) { m_TradeLine.SpotLight(); m_bIsMovingSelect = false; }else { m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE)); m_bIsMovingSelect = true; } break; } break; } }
Les modifications sont mises en évidence. Toute la classe sait maintenant si nous travaillons ou non avec une étiquette choisie par l'utilisateur. Nous pouvons ainsi séparer le fantôme de l'étiquette réelle et obtenir une représentation plus précise.
2.0.3. Ne déplacer que ce qui est important
Dans les deux sections précédentes, nous avons créé et corrigé le système afin d'avoir une étiquette d'indication des fantômes. Nous devons maintenant déplacer les composants. Pour ce faire, nous devons effectuer quelques travaux. La première étape consiste à créer une structure pour stocker les différentes informations dont nous avons besoin pour éviter les appels excessifs entre les fonctions. La structure est présentée ci-dessous :
struct st00 { eIndicatorTrade it; bool bIsMovingSelect, bIsBuy; ulong ticket; double vol, pr, tp, sl; }m_InfoSelection;
Le segment mis en évidence existait auparavant dans le code, mais il fait désormais partie de la structure. Ne vous inquiétez pas. La raison pour laquelle la structure comporte ces éléments apparaîtra plus clairement au fur et à mesure que nous avancerons.
Nous examinerons ici les fonctions qui ont été modifiées et qui nécessitent une explication. La première est SetTextValue :
void SetTextValue(ulong ticket, eIndicatorTrade it, double value0, double value1 = 0.0, double priceOpen = 0.0) { double finance; switch (it) { case IT_RESULT : PositionAxlePrice(ticket, it, priceOpen); PositionAxlePrice(ticket, IT_PENDING, 0); m_EditInfo2.SetTextValue(MountName(ticket, it, EV_PROFIT), value1); case IT_PENDING: value0 = value0 / Terminal.GetVolumeMinimal(); m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit); if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor); break; case IT_TAKE : case IT_STOP : finance = (value1 / Terminal.GetAdjustToTrade()) * value0; m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance); if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor); break; } }
Lorsque nous déplaçons quelque chose, nous ne voulons pas que le fantôme suive les nouvelles données. Nous voulons qu'il reste immobile, montrant où se trouvait l'étiquette. Pour cela, il suffit d'ajouter les points mis en évidence. Ainsi, le fantôme reste immobile alors que l'étiquette se déplace librement. Nous n'aurons pas ici le mouvement lui-même, mais l'indication des valeurs qui se trouvent sur le serveur.
Il est suivi du code de mouvement indiqué ci-dessous :
void MoveSelection(double price) { double tp, sl; if (!m_InfoSelection.bIsMovingSelect) return; switch (m_InfoSelection.it) { case IT_TAKE: UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy); break; case IT_STOP: UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price, m_InfoSelection.vol, m_InfoSelection.bIsBuy); break; case IT_PENDING: tp = (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr); sl = (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr); UpdateInfosIndicators(0, m_InfoSelection.ticket, price, tp, sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy); break; } }
Veuillez prêter attention à la partie surlignée. Elle ajuste le mouvement pour que le système indique correctement les données. Cela peut sembler étrange, mais la fonction UpdateINfosIndicators a une autre solution. Si nous ne le faisons pas maintenant, nous aurons des problèmes plus tard. D'autres fonctions sont assez simples et ne nécessitent pas d'explication.
void SetPriceSelection(double price) { bool isPending; if (!m_InfoSelection.bIsMovingSelect) return; isPending = OrderSelect(m_InfoSelection.ticket); m_InfoSelection.bIsMovingSelect = false; m_TradeLine.SpotLight(); switch (m_InfoSelection.it) { case IT_TAKE: if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl); else ModifyPosition(m_InfoSelection.ticket, price, m_InfoSelection.sl); break; case IT_STOP: if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price); else ModifyPosition(m_InfoSelection.ticket, m_InfoSelection.tp, price); break; case IT_PENDING: ModifyOrderPendent(m_InfoSelection.ticket, price, (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr), (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr)); break; } }
La fonction ci-dessus informe le serveur de ce qui se passe et des nouvelles données. Veuillez noter que certains indicateurs doivent être sélectionnés dans les lignes en surbrillance, sinon la demande au serveur ne sera pas effectuée.
La dernière fonction recommandée est présentée ci-dessous :
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price; bool bKeyBuy, bKeySell, bEClick; datetime dt; uint mKeys; char cRet; eIndicatorTrade it; eEventType ev; static bool bMounting = false, bIsDT = false; static double valueTp = 0, valueSl = 0, memLocal = 0; switch (id) { case CHARTEVENT_MOUSE_MOVE: Mouse.GetPositionDP(dt, price); mKeys = Mouse.GetButtonStatus(); bEClick = (mKeys & 0x01) == 0x01; //Left mouse click bKeyBuy = (mKeys & 0x04) == 0x04; //Pressed SHIFT bKeySell = (mKeys & 0x08) == 0x08; //Pressed CTRL if (bKeyBuy != bKeySell) { if (!bMounting) { Mouse.Hide(); bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl); valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol); valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol); m_TradeLine.SpotLight(MountName(def_IndicatorTicket0, IT_PENDING, EV_LINE)); m_InfoSelection.it = IT_PENDING; m_InfoSelection.ticket = def_IndicatorTicket0; m_InfoSelection.bIsMovingSelect = true; m_InfoSelection.pr = price; bMounting = true; } m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp)); m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl); m_InfoSelection.bIsBuy = bKeyBuy; MoveSelection(price); if ((bEClick) && (memLocal == 0)) { MoveSelection(0); m_InfoSelection.bIsMovingSelect = false; CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price, price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT); } }else if (bMounting) { MoveSelection(0); m_InfoSelection.bIsMovingSelect = false; Mouse.Show(); memLocal = 0; bMounting = false; }else if ((!bMounting) && (bKeyBuy == bKeySell)) { if (bEClick) SetPriceSelection(price); else MoveSelection(price); } break; case CHARTEVENT_OBJECT_DELETE: if (GetIndicatorInfos(sparam, ticket, price, it, ev)) { CreateIndicatorTrade(ticket, it); GetInfosTradeServer(ticket); m_InfoSelection.bIsMovingSelect = false; UpdateInfosIndicators(0, ticket, m_InfoSelection.pr, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy); } break; case CHARTEVENT_CHART_CHANGE: ChartSetInteger(ChartID(), CHART_SHOW_OBJECT_DESCR, false); ReDrawAllsIndicator(); break; case CHARTEVENT_OBJECT_CLICK: if (GetIndicatorInfos(sparam, ticket, price, it, ev)) switch (ev) { case EV_CLOSE: 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_InfoSelection.bIsMovingSelect = true; SetPriceSelection(0); break; } break; case EV_MOVE: if (m_InfoSelection.bIsMovingSelect) { m_TradeLine.SpotLight(); m_InfoSelection.bIsMovingSelect = false; }else { m_InfoSelection.ticket = ticket; m_InfoSelection.it = it; if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0)) m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE)); } break; } break; } }
Il y a plusieurs choses à noter à propos de cette fonction. Mais prêtez attention à la différence avec la version précédente : nous réutilisons beaucoup plus de code ici. Ainsi, si le code comporte une erreur à un autre endroit, nous pouvons rapidement la remarquer et la corriger. Auparavant, ce code de fonction était quelque peu instable, avec des corrections et des défauts dans d'autres parties. L'un des principaux points était que lorsque nous placions un ordre en attente, il existait un système distinct pour les indicateurs mobiles. Nous disposons désormais d'un système unique, le même que celui qui est utilisé pour placer les ordres sur le graphique. Il est également utilisé pour déplacer des objets, c'est-à-dire que le même événement CHARTEVENT_MOUSE_MOVE est désormais utilisé à la fois pour placer un ordre en attente et pour déplacer des objets. Cela peut sembler anodin, mais cela permet de rendre visibles toutes les modifications apportées au code. Et si nous avons un problème, il apparaîtra tant que nous utiliserons l'événement de la souris.
Conclusion
Veuillez regarder la vidéo ci-dessous pour avoir une idée plus claire de ce qui se passe avec les changements apportés. Vous verrez qu'il ne nous manque plus que quelques détails pour que l'EA soit totalement complet en termes de système d’ordres.
Traduit du portugais par MetaQuotes Ltd.
Article original : https://www.mql5.com/pt/articles/10563
![Comment utiliser les modèles ONNX dans MQL5](https://c.mql5.com/2/52/onnx_models_avatar.png)
![Développer un Expert Advisor de trading à partir de zéro (Partie 22) : Nouveau système d’ordres (V)](https://c.mql5.com/2/47/development__5.png)
![Algorithmes d'optimisation de la population : Algorithme des Lucioles (Firefly Algorithm - FA)](https://c.mql5.com/2/51/firefly_algorithm_avatar.png)
![Apprenez à concevoir un système de trading basé sur l’Awesome Oscillator](https://c.mql5.com/2/48/why-and-how__9.png)
![MQL5 - Langage des stratégies de trading intégré au terminal client MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- 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