Développer un Expert Advisor de trading à partir de zéro (partie 18) : Nouveau système d’ordres (I)
Introduction
Depuis que nous avons commencé à documenter cet EA dans cette série à partir du tout premier article Développer un Expert Advisor en trading à partir de zéro, il a subi divers changements et améliorations tout en conservant le même modèle de système d'ordres sur le graphique. Il est simple et fonctionnel. Mais il ne convient pas au trading réel dans de nombreuses situations.
Nous pourrions évidemment ajouter certaines choses au système d'origine afin d'avoir des informations sur les positions ouvertes et les ordres en attente. Mais cela transformerait notre code en une espèce de Frankenstein qui empêcherait toute amélioration. Même avec votre propre méthodologie, il serait perdu avec le temps car le code devient trop gros et trop compliqué.
Nous devons donc créer un système complètement différent en termes de modèle d’ordres. Le code doit être facile à comprendre, tout en fournissant toutes les informations dont nous avons besoin pour travailler en toute sécurité.
Note Importante
Le système que je décris dans cet article est conçu pour les comptes de compensation (netting) ACCOUNT_MARGIN_MODE_RETAIL_NETTING, ce qui permet de n'avoir une seule position ouverte par symbole. Si vous utilisez un compte de couverture (hedging) ACCOUNT_MARGIN_MODE_RETAIL_HEDGING, l'article n'apportera rien à votre EA car les ordres n'interféreront pas les uns avec les autres, et vous pouvez donc en avoir autant que vous voulez. Vous pouvez donc simplement réinitialiser et supprimer toutes les modifications du code final.
Le mode ACCOUNT_MARGIN_MODE_RETAIL_HEDGING est le plus souvent utilisé lorsque vous souhaitez qu'un EA s'exécute automatiquement, ouvrant et fermant des positions sans votre participation, et que vous voulez continuer à trader manuellement en parallèle. Encore plus si vous tradez le même actif que votre EA. Avec la couverture (hedging), les activités de votre EA n'affecteront aucune de vos transactions. Vous aurez donc des positions indépendantes.
Pour cette raison, je mettrai en évidence toutes les parties de code qui sont ajoutées ou modifiées. Nous mettrons en œuvre tous les changements et ajouts lentement et progressivement. Donc si vous avez besoin de supprimer quoi que ce soit de votre code, vous pourrez facilement trouver les bonnes parties de code.
Bien que les modifications puissent être supprimées, il y a une partie où je vérifie le système. Même si vous enregistrez les modifications que nous examinerons ici, vous pouvez utiliser cet EA sur n'importe quel type de compte de trading, NETTING et HEDGING, car l'Expert Advisor vérifiera le modèle particulier et s'ajustera donc en conséquence.
1.0. Plan
La toute première chose à faire est de comprendre ce qu'il advient des ordres ajoutés au système et exécutés au fur et à mesure qu'ils atteignent les prix demandés. De nombreux traders ne le savent pas, ou plutôt, n'y ont jamais pensé et n'ont donc effectué aucun test pour comprendre cette idée et ensuite mettre en place un système adéquat mais fiable.
Pour comprendre ce qui se passe réellement, analysons un exemple simple : vous avez une position ouverte, disons un Achat, pour un certain actif avec un volume initial de 1 lot. Vous passez ensuite un nouvel ordre d'achat de 2 lots à un prix légèrement supérieur. Jusqu'ici tout va bien, il n’y a rien de spécial. Mais dès que ces 2 lots seront achetés, il se passera quelque chose, et c'est là que réside le problème.
Une fois ces 2 lots achetés, vous aurez 3 lots. Mais le système mettra à jour votre prix initial et le fixera au prix moyen. Cela semble clair, et tout le monde le comprend. Mais que se passe-t-il pour stopper les pertes et prendre les niveaux de profit de la nouvelle position ?
De nombreux traders ne connaissent pas la réponse, mais ils feraient mieux d'y réfléchir. Si vous utilisez un système d'ordre OCO (One Cancel The Other) dans toutes les transactions, vous pouvez remarquer quelque chose d'intéressant chaque fois que vous ouvrez ou fermez une position sur une partie du volume total échangé.
Dans l'article sur les Ordres Croisés (Cross Orders), j'ai présenté un moyen de placer le Take Profit et le Stop Loss directement sur le graphique, sans utiliser le système standard MetaTrader 5. Cette méthode est presque identique au système MetaTrader 5, puisque sa fonctionnalité est très proche de celle de la plateforme. Mais après avoir effectué quelques tests, j'ai constaté que lorsque nous avons un ordre OCO ouvert et un ordre en attente en parallèle, l’ordre OCO est également capturé par le système de trading car le prix a atteint la valeur spécifiée dans l’ordre. En plus d'un nouveau prix moyen, nous avons également un changement dans les valeurs de Take Profit et de Stop Loss : elles sont remplacées par celles spécifiées dans le dernier ordre OCO capturé. Selon la façon dont il est configuré, l'EA le fermera donc immédiatement dès que le système de trading signalera de nouvelles valeurs de Take Profit et de Stop Loss.
Cela se produit à cause du test suivant implémenté dans l'EA :
inline double CheckPosition(void) { double Res = 0, last, sl; ulong ticket; last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST); for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol()) { ticket = PositionGetInteger(POSITION_TICKET); Res += PositionGetDouble(POSITION_PROFIT); sl = PositionGetDouble(POSITION_SL); if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { if (last < sl) ClosePosition(ticket); }else { if ((last > sl) && (sl > 0)) ClosePosition(ticket); } } return Res; };
Les lignes en surbrillance effectuent cette vérification et clôturent un ordre au cas où le prix quitte le canal formé par les valeurs de Take Profit et de Stop Loss.
Il est important de le savoir car il ne s’agit pas d’une erreur de l’EA, d'un problème ou d'un défaut du système de trading. Le vrai problème est que la plupart du temps, nous n'accordons pas l'attention voulue à ce qui se passe et nous ignorons ce genre de détail jusqu'à ce qu'il ne soit plus possible de fermer les yeux dessus.
Si vous tradez sans que le prix soit moyenné, vous ne verrez pas ce genre de chose se produire. Mais il existe un très large éventail d'opérations où un prix moyen est approprié, voire même nécessaire. Dans ces cas précis, si vous utilisez le système sans connaître les détails ci-dessus, vous pouvez quitter la position plus tôt que vous ne le souhaitez, même si vous avez précédemment modifié l'ordre OCO de la position ouverte en conséquence. Dès qu'un ordre OCO en attente est capturé, les valeurs de seuil ( chaque fois que je parle de seuil, je me réfère au Take Profit précédent et au Stop Loss précédent) seront remplacées par celles spécifiées dans l'ordre OCO en attente récemment capturé.
Il existe un moyen de le corriger ou de l'éviter : n'utilisez pas d'ordres OCO, du moins lorsque vous avez déjà une position ouverte. Tous les autres ordres passés dans le système doivent être de type simple, sans définir de Take Profit et de Stop Loss.
C'est tout. Mais lorsqu'un EA est sur le graphique, il est là pour nous aider, pour nous faciliter la vie. Cela n'aurait aucun sens de programmer un Expert Advisor pour ne pas l'utiliser plus tard.
2.0. Mise en place d'un nouveau système
Nous devons apporter de petites modifications au code pour implémenter le système et nous assurer qu'il fonctionne comme prévu : l'EA devrait nous aider et nous aider à éviter les erreurs.
Ce n'est pas très difficile, mais ces changements garantissent que nous ne courrons jamais le risque de voir un ordre OCO arriver à un moment indésirable, provoquant une réelle confusion dans nos opérations.
Commençons donc par les modifications suivantes :
2.0.1. Modification de la classe C_Router
La classe C_Router est responsable de l'analyse et de l'envoi des ordres. Ajoutons-y une variable privée. Lorsqu'une position ouverte est trouvée pour l'actif utilisé par l'EA, cette variable stockera des informations. Chaque fois que l'EA veut savoir s'il y a un ordre ouvert, il nous le dira.
Cette implémentation est illustrée dans le code ci-dessous. Notez que ce code sera encore modifié dans l'article. Je veux juste montrer tous les changements étape par étape, pour que vous compreniez à quoi ressemble réellement le processus de modification.
//+------------------------------------------------------------------+ inline bool ExistPosition(void) const { return m_bContainsPosition; } //+------------------------------------------------------------------+ void UpdatePosition(void) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true); } for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } }; //+------------------------------------------------------------------+
Les lignes en surbrillance montrent les ajouts nécessaires pour exécuter toutes les vérifications qui seront ajoutées ultérieurement à certains endroits du code de l’EA.
D'une certaine manière, nous pourrions implémenter des tests et faire des ajustements juste dans la classe C_Router. Mais cela ne suffirait pas. Je l'expliquerai plus tard. Pour l'instant, continuons avec les modifications. Après avoir ajouté le test ci-dessus, ajoutons maintenant un constructeur pour initialiser correctement cette nouvelle variable.
C_Router() : m_bContainsPosition(false) {}
Editons maintenant la fonction qui place les ordres en attente comme suit :
ulong CreateOrderPendent(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true) { double last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST); ZeroMemory(TradeRequest); ZeroMemory(TradeResult); TradeRequest.action = TRADE_ACTION_PENDING; TradeRequest.symbol = Terminal.GetSymbol(); TradeRequest.volume = Volume; TradeRequest.type = (IsBuy ? (last >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : (last < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP)); TradeRequest.price = NormalizeDouble(Price, Terminal.GetDigits()); TradeRequest.sl = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits()); TradeRequest.tp = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits()); TradeRequest.type_time = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC); TradeRequest.stoplimit = 0; TradeRequest.expiration = 0; TradeRequest.type_filling = ORDER_FILLING_RETURN; TradeRequest.deviation = 1000; TradeRequest.comment = "Order Generated by Experts Advisor."; if (!Send()) return 0; return TradeResult.order; };
Les parties en surbrillance sont les changements à mettre en œuvre.
Revenons maintenant au code et effectuons une nouvelle modification. N'oubliez pas qu'il est appelé par la fonction OnTrade et qu'il est appelé à chaque fois que les ordres sont modifiés. Voici le code :
void UpdatePosition(void) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true); } for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { if (m_bContainsPosition) { ModifyOrderPendent(ul, OrderGetDouble(ORDER_PRICE_OPEN), 0, 0); (OrderSelect(ul) ? 0 : 0); } SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } };
Nous devons maintenant nous assurer que l'utilisateur ne convertit pas un simple ordre en attente en un ordre OCO en attente lorsqu'il y a déjà une position ouverte, c'est-à-dire si l'utilisateur ouvre la boîte à outils et essaie de modifier le Take Profit ou le Stop Loss. Lorsque l'utilisateur essaie de le faire, le serveur de trading nous en informera via la fonction OnTrade. Ainsi, l'EA sera immédiatement informé et annulera le changement effectué par l'utilisateur, garantissant la fiabilité du système.
Mais il reste une chose qui doit également être modifiée : les ordres au marché. Il s'agit d'un changement très simple car il ne nécessite aucune modification liée aux vérifications. Le code complet de la fonction est donné ci-dessous :
ulong ExecuteOrderInMarket(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true) { ZeroMemory(TradeRequest); ZeroMemory(TradeResult); TradeRequest.action = TRADE_ACTION_DEAL; TradeRequest.symbol = Terminal.GetSymbol(); TradeRequest.volume = Volume; TradeRequest.type = (IsBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); TradeRequest.price = NormalizeDouble(Price, Terminal.GetDigits()); TradeRequest.sl = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits()); TradeRequest.tp = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits()); TradeRequest.type_time = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC); TradeRequest.stoplimit = 0; TradeRequest.expiration = 0; TradeRequest.type_filling = ORDER_FILLING_RETURN; TradeRequest.deviation = 1000; TradeRequest.comment = "[ Order Market ] Generated by Experts Advisor."; if (!Send()) return 0; return TradeResult.order; };
Bien que cela puisse sembler étrange, ces changements offrent déjà un niveau de sécurité suffisant (disons un niveau acceptable). Cela permet de ne pas manquer un ordre OCO, un ordre en attente ou un ordre au marché alors qu'il existe déjà une position ouverte sur le même actif que l'EA. L'EA s'occupera ainsi réellement du système de soumission des ordres.
Eh bien, on dirait que tout est trop beau et trop merveilleux pour être vrai, n'est-ce pas ? Vous pourriez penser que cela vous garantira déjà une bonne marge de sécurité. Mais ce n'est pas tout à fait le cas. Ces modifications garantissent qu'un ordre OCO ne reste pas en attente ou n'entre pas sur le marché lorsque nous avons une position ouverte. Mais il y a un défaut fatal dans ces modifications. S'il n'est pas correctement corrigé, ce défaut peut vous donner un énorme casse-tête et une perte énorme. Cela pourrait casser votre compte ou la position clôturée par le courtier faute de marge.
Notez qu'il n'y a pas de vérification pour savoir si un ordre en attente se trouve ou non dans les limites d'une position ouverte. C’est très dangereux car dans l'état actuel du système, lorsque vous ajoutez un ordre OCO en attente en dehors des limites de position ouverte, l'EA ne permet pas que cet ordre soit de type OCO. En d'autres termes : l'ordre n'aura pas de limites; Cet ordre sera un ordre sans Take Profit ni Stop Loss. Donc lorsque vous clôturerez une position et saisirez cet ordre en attente, vous devrez ajuster ces niveaux le plus rapidement possible. Si vous oubliez de le faire, vous risquez d'avoir une position ouverte sans limite.
Pour définir les niveaux, vous devez ouvrir la fenêtre de message, ouvrir l’ordre et modifier les valeurs des niveaux. Ce problème sera bientôt résolu. Réglons maintenant le problème actuel.
Nous devons changer la façon dont l'EA fonctionne avec les ordres en attente : si l'utilisateur veut créer un ordre sans les niveaux, l'EA le traitera comme quelque chose de normal. Mais si l'utilisateur finit par créer un ordre à cours limité, l'EA devra ajuster l'ordre en conséquence : fixer des limites si l'ordre est placé en dehors de la position ouverte, ou supprimer les limites d'un ordre en attente si l'ordre est déclenché.
2.0.2. Travailler dans les limites
La première chose que nous allons faire est de créer des limites de vérification. C'est très simple à faire. Cela demande toutefois beaucoup d'attention. Il y a 2 cas possibles (qui peuvent être étendus à plus de cas). Pour comprendre quoi faire, ces 2 cas suffisent.
Le premier cas est illustré ci-dessus. Un ordre en attente est en dehors des limites (la limite dans ce cas est une zone dans le gradient, c'est-à-dire que nous avons une position, soit d’achat soit de vente, et nous avons une limite supérieure). Lorsque le prix atteindra ou dépassera cette limite, la position sera fermée. Dans ce cas, l’ordre en attente peut être configuré par l'utilisateur en tant qu’ordre OCO et l'EA doit accepter la manière dont l’ordre est configuré par l'utilisateur, qu'il s'agisse d'un ordre simple ou d'un ordre OCO — l'EA ne doit pas interférer dans ce cas.
Le deuxième cas est présenté ci-dessous. Ici, l’ordre en attente se trouve dans la zone délimitée par la position ouverte. L'EA doit ici supprimer les limites qui peuvent être configurées par l'utilisateur.
Faites bien attention : quelque soit la limite, que nous achetions ou que nous vendions, si l’ordre entre dans cette zone, l'EA doit supprimer les valeurs limites de l’ordre en attente. Mais s'il quitte cette zone, l'EA doit laisser l’ordre tel qu'il est configuré par l'utilisateur.
Après l’avoir défini, nous devons créer certaines variables :
class C_Router : public C_HLineTrade { protected: MqlTradeRequest TradeRequest; MqlTradeResult TradeResult; private : bool m_bContainsPosition; struct st00 { double TakeProfit, StopLoss; bool IsBuy; }m_Limits; // ... Rest of the code
Nous avons maintenant un moyen de vérifier les limites au moment du déclenchement de l’ordre OnTrade. Nous devons modifier la fonction de mise à jour de la classe C_Router encore une fois :
// Rest of the code.... //+------------------------------------------------------------------+ #define macro_MAX(A, B) (A > B ? A : B) #define macro_MIN(A, B) (A < B ? A : B) void UpdatePosition(void) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; double price; bool bTest; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; m_Limits.StopLoss = -1; m_Limits.TakeProfit = -1; m_Limits.IsBuy = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY; } for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { price = OrderGetDouble(ORDER_PRICE_OPEN); if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else { bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price))); } if ((m_bContainsPosition) && (bTest)) { ModifyOrderPendent(ul, price, 0, 0); (OrderSelect(ul) ? 0 : 0); } SetLineOrder(ul, price, HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } }; #undef macro_MAX #undef macro_MIN //+------------------------------------------------------------------+ // ... The rest of the code...
La classe gérera désormais les positions en attente afin de différencier lorsqu'elles se trouvent dans une zone qui ne peut pas avoir d'ordres en attente OCO, ou en dehors de celle-ci. Notez que cette fonction sera appelée à chaque changement de statut dans le système d’ordres. Elle commencera tout d’abord par initialiser correctement les variables.
m_Limits.StopLoss = -1; m_Limits.TakeProfit = -1; m_Limits.IsBuy = false;
Nous vérifierons ensuite s'il y a ou non une position ouverte. Cela peut être fait à tout moment. Dès que nous aurons une position ouverte, cela délimitera la zone où il ne sera pas possible d'avoir des ordres OCO en attente.
SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
Nous pouvons maintenant vérifier chacun des ordres en attente pour savoir s’ils se trouvent ou non dans la zone limite. Une remarque importante : ici, nous devons savoir si l'ordre est de type Achat ou Vente, car nous pouvons ne pas avoir de Take Profit mais avoir un Stop Loss. Il est donc nécessaire dans ce cas de connaître le type de la position. Pour le comprendre, regardez les figures ci-dessous :
S'il s'agit d'une position de vente, le Stop Loss marque la limite supérieure...
S'il s'agit d'une position d’Achat, le Stop Loss représente la limite inférieure.
En d'autres termes, dans certains cas, les ordres en attente peuvent être de type OCO s'ils sont placés au maximum. Dans les autres cas, ils doivent être placés en dessous du minimum. Mais il y a également le cas où les ordres en attente peuvent également être de type OCO, comme indiqué ci-dessous :
Ordres en attente en dehors des limites, ce qui est un cas typique.
Pour le vérifier, nous utilisons le fragment de code suivant :
price = OrderGetDouble(ORDER_PRICE_OPEN); if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else { bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price))); }
Il vérifiera s'il y a des positions ouvertes. Dans le cas contraire, l'EA devra respecter les paramètres de l’ordre spécifiés par l'utilisateur. Si une position est trouvée, la vérification sera effectuée dans l'ordre suivant :
- Si nous sommes Short, alors le prix auquel un ordre en attente sera placé doit être supérieur à la valeur du Stop Loss de la position ouverte ;
- Si nous sommes Long, le prix auquel un ordre en attente sera placé doit être inférieur à la valeur du Stop Loss de la position ouverte ;
- Si le système accepte toujours l’ordre en tant qu’ordre de type OCO, nous ferons un dernier test pour voir si l’ordre est hors position.
Une fois cela fait, nous serons sûrs que l’ordre en attente peut être laissé tel que l'utilisateur l'a configuré ou non. Il y a toutefois un dernier ajout avant de passer à l'étape suivante. C'est la dernière vérification, que j'avais mentionné au début de l'article. Elle se trouve dans le fragment de code ci-dessous :
void UpdatePosition(void) { // ... Internal code... for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY; } if (AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) m_bContainsPosition = false; for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { price = OrderGetDouble(ORDER_PRICE_OPEN); if (m_bContainsPosition) { if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else { bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price))); } if (bTest) { ModifyOrderPendent(ul, price, 0, 0); (OrderSelect(ul) ? 0 : 0); } } SetLineOrder(ul, price, HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } };
Dans la partie en surbrillance, nous vérifions si le type de compte est HEDGING. Si c'est le cas, même si la variable indiquait que nous devions travailler avec des limites, cela indique ici que nous n'en avons finalement pas besoin. Par conséquent, l'EA ignorera toutes les restrictions qui pourraient survenir et traitera les ordres comme ils ont été configurés par l'utilisateur. Il s'agit d'une vérification très simple, mais elle doit être effectuée à ce stade pour s'assurer que l'ensemble de notre système fonctionnera correctement.
2.1.0. Réglage du système de positionnement
Bien que la plupart des problèmes aient été résolus en ajustant la classe d'objets C_Router, nous n'avons toujours pas de système correct. Il y a un autre problème à résoudre : corriger le système de positionnement via la souris, ce qui est une autre étape tout aussi importante. Cela a plusieurs implications, puisque nous devons définir comment le système présent dans la classe C_OrderView doit fonctionner.
La grande question, sur la façon dont vous voulez réellement fonctionner, est de savoir si la classe C_OrderView créera ou non des limites pour les ordres en attente lorsqu'ils quittent les limites d'une position ouverte.
Bien qu'il soit tentant de le faire à chaque fois, il y a des éléments à prendre en compte lors de la prise de cette décision. Mais allons-y petit à petit. Fondamentalement, le seul véritable changement que nous devrons réellement apporter à la classe C_OrderView est indiqué dans le code ci-dessous :
inline void MoveTo(uint Key) { static double local = 0; datetime dt; bool bEClick, bKeyBuy, bKeySell, bCheck; double take = 0, stop = 0, price; bEClick = (Key & 0x01) == 0x01; //Left click bKeyBuy = (Key & 0x04) == 0x04; //SHIFT pressed bKeySell = (Key & 0x08) == 0x08; //CTRL pressed Mouse.GetPositionDP(dt, price); if (bKeyBuy != bKeySell) { Mouse.Hide(); bCheck = CheckLimits(price); } else Mouse.Show(); ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0)); ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = (bCheck ? 0 : price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1)))); ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = (bCheck ? 0 : price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1)))); if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade); local = (local != price ? 0 : local); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE)); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE)); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE)); };
Est-ce que c'est tout ce qu’il y a à faire ? Oui, c'est tout ce que nous devons faire. Tout le reste de la logique est à l'intérieur de la classe C_Router. Ce qui n'a pas été modifié est exécuté par le propre système de messagerie de MetaTrader 5, car lorsqu'il y a un changement dans la liste des ordres (en attente ou positions), la fonction OnTrade est appelée. Lorsque cela se produit, la fonction de mise à jour à partir de la classe C_Router est appelée et elle effectuera les ajustements nécessaires. Mais il y a un morceau de code dans cette fonction qui peut vous rendre fou en cherchant où il se trouve. Il à l'intérieur de la classe C_Router :
#define macro_MAX(A, B) (A > B ? A : B) #define macro_MIN(A, B) (A < B ? A : B) inline bool CheckLimits(const double price) { bool bTest = false; if ((!m_bContainsPosition) || ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1))) return bTest; bTest = ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)); if (m_Limits.TakeProfit == 0) { bTest = (bTest ? bTest : (!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); } return bTest; }; #undef macro_MAX #undef macro_MIN
Ce code se trouvait dans la fonction de mise à jour de la classe C_Router. Il a été retiré de là et remplacé par un appel...
2.2.0. Limiter ou ne pas limiter, telle est la question
Notre travail est presque terminé. Mais la dernière question reste à résoudre, qui est peut-être la plus difficile à l'heure actuelle. Si vous avez suivi et compris le contenu jusqu'à présent, vous avez peut-être remarqué que le système fonctionne très bien. Mais chaque fois qu'un ordre en attente configuré en OCO entre dans les limites d'une position ouverte, l'ordre perd les limites qui lui ont été configurées. . Cela arrivera toujours.
Mais si par hasard le trader change les limites de positions ouvertes, ou si un ordre qui était un OCO et qui est maintenant un ordre simple dépasse ces limites, ce sera toujours un ordre simple. Nous avons donc un problème potentiel.
Un autre gros détail : comment doit se comporter l'EA ? Doit-il notifier au trader qu'un ordre simple vient d'apparaître sur l'actif ? Ou devrait-il simplement fixer des limites pour l’ordre et le transformer en OCO ?
Cette question est extrêmement pertinente si vous voulez vraiment que l'EA vous aide dans le trading. Il serait bon que l'EA émette un avertissement pour nous prévenir de ce qu’il s'est passé. Mais si vous êtes placé sur un actif, dans une période de forte volatilité, il est bon que l'EA crée automatiquement une limite pour l’ordre afin qu'il ne soit pas trop éloigné, ce qui pourrait causer de gros dégâts avant même que nous réalisions ce qui se passe.
Pour résoudre ce problème, le système a donc subi une dernière modification. Mais comme je l'ai expliqué ci-dessus, vous devriez sérieusement réfléchir à la manière de traiter réellement ce problème. Voici comment j'ai implémenté une solution possible.
J'ai tout d’abord ajouté une nouvelle variable pour informer l'EA quelle procédure sera effectuée. La voici :
// ... Code ... input group "Chart Trader" input int user20 = 1; //Leverage factor input int user21 = 100; //Take Profit (financial) input int user22 = 75; //Stop Loss (financial) input color user23 = clrBlue; //Price line color input color user24 = clrForestGreen; //Take Profit line color input color user25 = clrFireBrick; //Stop Loss line color input bool user26 = true; //Day Trade? input bool user27 = true; //Always set loose order limits // ... Rest of the code... void OnTrade() { Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]); OrderView.UpdatePosition(user27); } // ... Rest of the code...
Nous devons maintenant revenir à la classe C_Router et lui ajouter 3 nouvelles fonctions. Les voici également :
//+------------------------------------------------------------------+ void SetFinance(const int Contracts, const int Take, const int Stop) { m_Limits.Contract = Contracts; m_Limits.FinanceTake = Take; m_Limits.FinanceStop = Stop; } //+------------------------------------------------------------------+ inline double GetDisplacementTake(const bool IsBuy, const double Vol) const { return (Terminal.AdjustPrice(m_Limits.FinanceTake * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? 1 : -1)); } //+------------------------------------------------------------------+ inline double GetDisplacementStop(const bool IsBuy, const double Vol) const { return (Terminal.AdjustPrice(m_Limits.FinanceStop * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? -1 : 1)); } //+------------------------------------------------------------------+
Le code conservera les valeurs renseignées dans le Chart Trader comme on peut le voir dans l'image suivante. Mais li corrigera également proportionnellement la valeur que nous devrions utiliser comme limites dans les ordres en attente OCO.
Autrement dit, nous avons déjà l’endroit où obtenir les valeurs que nous allons utiliser pour que l'EA puisse configurer un ordre OCO minimum lorsqu'un ordre en attente est déclenché. Mais comme vous vous en doutez, nous devons apporter une nouvelle modification au code de mise à jour de la classe C_Router. Le changement est illustré ci-dessous :
void UpdatePosition(bool bAdjust) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; long info; double price, stop, take, vol; bool bIsBuy, bTest; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; m_Limits.StopLoss = -1; m_Limits.TakeProfit = -1; m_Limits.IsBuy = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, take = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, stop = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY; m_Limits.TakeProfit = (m_Limits.TakeProfit < 0 ? take : (m_Limits.IsBuy ? (m_Limits.TakeProfit > take ? m_Limits.TakeProfit : take) : (take > m_Limits.TakeProfit ? m_Limits.TakeProfit : take))); m_Limits.StopLoss = (m_Limits.StopLoss < 0 ? stop : (m_Limits.IsBuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop))); } if ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) m_bContainsPosition = false; 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); if ((take == 0) && (stop == 0) && (bAdjust) && (!bTest)) { info = OrderGetInteger(ORDER_TYPE); vol = OrderGetDouble(ORDER_VOLUME_CURRENT); bIsBuy = ((info == ORDER_TYPE_BUY_LIMIT) || (info == ORDER_TYPE_BUY_STOP) || (info == ORDER_TYPE_BUY_STOP_LIMIT) || (info == ORDER_TYPE_BUY)); take = price + GetDisplacementTake(bIsBuy, vol); stop = price + GetDisplacementStop(bIsBuy, vol); ModifyOrderPendent(ul, price, take, stop); } if ((take != 0) && (stop != 0) && (bTest)) ModifyOrderPendent(ul, price, take = 0, stop = 0); SetLineOrder(ul, price, HL_PRICE, true); SetLineOrder(ul, take, HL_TAKE, true); SetLineOrder(ul, stop, HL_STOP, true); } };
Les lignes en surbrillance vérifieront si l’ordre est libre et si l'EA doit intervenir. Si l'EA intervient, le calcul sera effectué sur la base de la valeur financière présentée dans le Chart Trader et sur la base du volume d’ordres en attente. L’ordre en attente simple recevra alors des limites calculées en fonction des informations collectées. L'EA créera des lignes pour informer que les limites seront créées, transformant ainsi l’ordre en attente simple en un ordre OCO en attente.
Conclusion
Malgré toutes les tentatives pour tester le système et voir s'il reconnaît le compte comme un compte couverture, je n'ai pas réussi à ce stade. L'EA a toujours signalé que le compte était en mode de compensation, même si la plateforme MetaTrader 5 a signalé que le compte était couvert. Par conséquent, vous devez être prudent. Bien que cela fonctionne comme nous le souhaitions, les ordres en attente sont ajustés même sur un compte de couverture comme s'il s'agissait d'un compte de compensation...
La vidéo montre clairement tout ce qui est décrit ci-dessus. Comme vous pouvez le voir, le système est très intéressant à utiliser.
Traduit du portugais par MetaQuotes Ltd.
Article original : https://www.mql5.com/pt/articles/10462
- Applications de trading gratuites
- VPS Forex gratuit pendant 24 heures
- 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
Un nouvel article Développer un Expert Advisor de trading à partir de zéro (partie 18) : Nouveau système d’ordres (I) a été publié :
Auteur : Daniel Jose