
Le MQL5 Cookbook : Ordres ОСО
Introduction
Cet article se concentre sur le traitement d’un type de paire d’ordres tel que les OCO. Ce mécanisme est implémenté dans certains terminaux de trading en concurrence avec MetaTrader 5. Je poursuis deux objectifs à travers l’exemple de la création d’un EA avec un panneau pour le traitement des ordres OCO. D’une part, je souhaite décrire les caractéristiques de la bibliothèque standard, d’autre part, je voudrais étendre l’ensemble des outils d’un trader.
1. Essence des ordres OCO
Les ordres OCO (une ordre annule l’autre) représentent une paire de deux ordres en attente.
Ils sont reliés par une fonction d’annulation mutuelle : si le premier se déclenche, le second doit être supprimé, et vice versa.
Fig. 1 Paire d’ordres OCO
La Fig.1 montre un schéma simple d’interdépendance d’ordre. Elle reflète une définition essentielle : une paire existe tant que les deux ordres existent. En termes de logique, tout ordre de la paire est une condition essentielle mais non suffisante pour l’existence de la paire.
Certaines sources disent que la paire doit avoir un ordre à cours limité et un ordre stop, de plus les ordres doivent avoir une direction (achat ou vente). À mon avis, une telle restriction ne peut pas aider à créer des stratégies de trading flexibles. Je suggère que divers ordres OCO soient analysés dans la paire, et surtout nous essaierons de programmer cette paire.
2. Programmation de paire d’ordres
Dans ma pensée, l’ensemble d’outils de POO est adapté à la programmation de tâches liées au contrôle des ordres OCO de la meilleure façon possible.
Les sections suivantes sont consacrées aux nouveaux types de données qui serviront notre objectif. La classeCiOcoObject vient en premier.
2.1. Classe CiOcoObject
Nous devons donc trouver un objet logiciel responsable du contrôle de deux ordres interconnectés.
Traditionnellement, créons un nouvel objet sur la base de la classe abstraite CObject.
Cette nouvelle classe peut paraître comme suit :
//+------------------------------------------------------------------+ //| Class CiOcoObject | //| Purpose: a class for OCO orders | //+------------------------------------------------------------------+ class CiOcoObject : public CObject { //--- === Data members === --- private: //--- tickets of pair ulong m_order_tickets[2]; //--- initialization flag bool m_is_init; //--- id uint m_id; //--- === Methods === --- public: //--- constructor/destructor void CiOcoObject(void){m_is_init=false;}; void ~CiOcoObject(void){}; //--- copy constructor void CiOcoObject(const CiOcoObject &_src_oco); //--- assignment operator void operator=(const CiOcoObject &_src_oco); //--- initialization/deinitialization bool Init(const SOrderProperties &_orders[],const uint _bunch_cnt=1); bool Deinit(void); //--- get id uint Id(void) const {return m_id;}; private: //--- types of orders ENUM_ORDER_TYPE BaseOrderType(const ENUM_ORDER_TYPE _ord_type); ENUM_BASE_PENDING_TYPE PendingType(const ENUM_PENDING_ORDER_TYPE _pend_type); //--- set id void Id(const uint _id){m_id=_id;}; };
Chaque paire d’ordres OCO aura son propre identifiant. Sa valeur est définie au moyen du générateur de nombres aléatoires (objet de la classe CRandom ).
Les méthodes d’initialisation et de désinitialisation des paires sont préoccupantes dans le contexte de l’interface. Le premier crée (initialise) la paire, et le second la supprime (désinitialise).
La méthodeCiOcoObject::Init() accepte un tableau de structures de type SOrderProperties comme argument. Ce type de structure représente les propriétés de l’ordre dans la paire, c’est-à-dire l’ordre OCO.
2.2 Structure de SOrderProperties
Considérons les champs de la structure susmentionnée.
//+------------------------------------------------------------------+ //| Order properties structure | //+------------------------------------------------------------------+ struct SOrderProperties { double volume; // order volume string symbol; // symbol ENUM_PENDING_ORDER_TYPE order_type; // order type uint price_offset; // offset for execution price, points uint limit_offset; // offset for limit price, points uint sl; // stop loss, points uint tp; // take profit, points ENUM_ORDER_TYPE_TIME type_time; // expiration type datetime expiration; // expiration string comment; // comment }
Donc, pour que la méthode d’initialisation fonctionne, nous devons précédemment remplir le tableau de structures composé de deux éléments. En termes simples, nous devons expliquer au programme les ordres qu’il passera.
L’énumération du type ENUM_PENDING_ORDER_TYPE est utilisée dans la structure :
//+------------------------------------------------------------------+ //| Pending order type | //+------------------------------------------------------------------+ enum ENUM_PENDING_ORDER_TYPE { PENDING_ORDER_TYPE_BUY_LIMIT=2, // Buy Limit PENDING_ORDER_TYPE_SELL_LIMIT=3, // Sell Limit PENDING_ORDER_TYPE_BUY_STOP=4, // Buy Stop PENDING_ORDER_TYPE_SELL_STOP=5, // Sell Stop PENDING_ORDER_TYPE_BUY_STOP_LIMIT=6, // Buy Stop Limit PENDING_ORDER_TYPE_SELL_STOP_LIMIT=7, // Sell Stop Limit };
D’une manière générale, elle ressemble à l’énumération standard ENUM _ORDER_TYPE, mais elle permet de sélectionner uniquement les ordres en attente ou, plus vraiment, les types de ces ordres.
Elle protège contre les erreurs lors de la sélection du type d’ordre correspondant dans les paramètres d’entrée (Fig.2).
Fig. 2. Le champ « Type » avec une liste déroulante des types d’ordres disponibles
Toutefois, si nous utilisons l’énumération standard ENUM _ORDER_TYPE , nous pourrions définir un type d’ordre boursier (ORDER_TYPE_BUY ou ORDER_TYPE_SELL), ce qui n’est pas nécessaire car nous traitons uniquement des ordres en attente.
2.3. Initialisation de la paire
Comme indiqué ci-dessus, la méthode CiOcoObject::Init() est engagée dans l’initialisation de la paire d’ordres.
En fait, elle place la paire d’ordre elle-même et enregistre le succès ou l’échec de l’émergence d’une nouvelle paire. Je dois dire qu’il s’agit d’une méthode active, car elle effectue des opérations de trading par elle-même. Nous pouvons également créer une méthode passive. Elle se connectera simplement à une paire d’ordres en attente déjà actifs qui ont été passés indépendamment.
Je ne fournirai pas de code de l’ensemble de la méthode. Mais je voudrais noter qu’il est important de calculer tous les prix (ouverture, arrêt, profit, limite), afin que la méthode de classe de trade CTrade::OrderOpen() puisse effectuer un ordre de trade. À cette fin, nous devrions considérer deux choses: la direction de l’ordre (achat ou vente) et la position d’un prix d’exécution de l’ordre par rapport à un prix actuel (supérieur ou inférieur).
Cette méthode appelle quelques méthodes privées : BaseOrderType() et PendingType(). La première définit la direction de l’ordre, la seconde détermine le type d’ordre en attente.
Si l’ordre est passé, son ticket est enregistré dans le tableau m_order_tickets[].
J’ai utilisé un script Init_OCO.mq5 simple pour tester cette méthode.
#property script_show_inputs //--- #include "CiOcoObject.mqh" //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ sinput string Info_order1="+===--Order 1--====+"; // +===--Order 1--====+ input ENUM_PENDING_ORDER_TYPE InpOrder1Type=PENDING_ORDER_TYPE_SELL_LIMIT; // Type input double InpOrder1Volume=0.02; // Volume input uint InpOrder1PriceOffset=125; // Offset for execution price, points input uint InpOrder1LimitOffset=50; // Offset for limit price, points input uint InpOrder1SL=250; // Stop loss, points input uint InpOrder1TP=455; // Profit, points input string InpOrder1Comment="OCO Order 1"; // Comment //--- sinput string Info_order2="+===--Order 2--====+"; // +===--Order 2--====+ input ENUM_PENDING_ORDER_TYPE InpOrder2Type=PENDING_ORDER_TYPE_SELL_STOP; // Type input double InpOrder2Volume=0.04; // Volume input uint InpOrder2PriceOffset=125; // Offset for execution price, points input uint InpOrder2LimitOffset=50; // Offset for limit price, points input uint InpOrder2SL=275; // Stop loss, points input uint InpOrder2TP=300; // Profit, points input string InpOrder2Comment="OCO Order 2"; // Comment //--- globals CiOcoObject myOco; SOrderProperties gOrdersProps[2]; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- property of the 1st order gOrdersProps[0].order_type=InpOrder1Type; gOrdersProps[0].volume=InpOrder1Volume; gOrdersProps[0].price_offset=InpOrder1PriceOffset; gOrdersProps[0].limit_offset=InpOrder1LimitOffset; gOrdersProps[0].sl=InpOrder1SL; gOrdersProps[0].tp=InpOrder1TP; gOrdersProps[0].comment=InpOrder1Comment; //--- property of the 2nd order gOrdersProps[1].order_type=InpOrder2Type; gOrdersProps[1].volume=InpOrder2Volume; gOrdersProps[1].price_offset=InpOrder2PriceOffset; gOrdersProps[1].limit_offset=InpOrder2LimitOffset; gOrdersProps[1].sl=InpOrder2SL; gOrdersProps[1].tp=InpOrder2TP; gOrdersProps[1].comment=InpOrder2Comment; //--- initialization of pair if(myOco.Init(gOrdersProps)) PrintFormat("Id of new OCO pair: %I32u",myOco.Id()); else Print("Error when placing OCO pair!"); }
Ici, vous pouvez définir diverses propriétés des ordres futurs de la paire. MetaTrader 5 a six différents types d’ordres en attente.
Dans ce contexte, il peut y avoir 15 variantes (combinaisons) de paires (à condition qu’il y ait différents ordres dans la paire).
C(k,N) = C(2,6) = 15
Toutes les variantes ont été testées à l’aide du script. Je vais donner un exemple pour la paire Buy Stop - Buy Stop Limit.
Les types d’ordres doivent être spécifiés dans les paramètres de script (Fig.3).
Fig. 3. Paire d’ordres « Buy Stop » avec ordre « Buy Stop Limit »
Les informations suivantes apparaîtront dans le registre « Experts »:
QO 0 17:17:41.020 Init_OCO (GBPUSD.e,M15) Code of request result: 10009 JD 0 17:17:41.036 Init_OCO (GBPUSD.e,M15) New order ticket: 24190813 QL 0 17:17:41.286 Init_OCO (GBPUSD.e,M15) Code of request result: 10009 JH 0 17:17:41.286 Init_OCO (GBPUSD.e,M15) New order ticket: 24190814 MM 0 17:17:41.379 Init_OCO (GBPUSD.e,M15) Id of new OCO pair: 3782950319
Mais nous ne pouvons pas travailler avec les ordres OCO au maximum à l’aide du script sans recourir à la boucle.
2.4. Désinitialisation de la paire
Cette méthode est responsable du contrôle de la paire d’ordres. La paire « meurt » lorsqu’un ordre quitte la liste des ordres actifs.
Je suppose que cette méthode devrait être placée dans les gestionnaires OnTrade() ou OnTradeTransaction() du code de l’EA. De cette manière, l’EA sera en mesure de traiter l’activation de toute ordre de la paire sans délai.
//+------------------------------------------------------------------+ //| Deinitialization of pair | //+------------------------------------------------------------------+ bool CiOcoObject::Deinit(void) { //--- if pair is initialized if(this.m_is_init) { //--- check your orders for(int ord_idx=0;ord_idx<ArraySize(this.m_order_tickets);ord_idx++) { //--- current pair order ulong curr_ord_ticket=this.m_order_tickets[ord_idx]; //--- another pair order int other_ord_idx=!ord_idx; ulong other_ord_ticket=this.m_order_tickets[other_ord_idx]; //--- COrderInfo order_obj; //--- if there is no current order if(!order_obj.Select(curr_ord_ticket)) { PrintFormat("Order #%d is not found in active orders list.",curr_ord_ticket); //--- attempt to delete another order if(order_obj.Select(other_ord_ticket)) { CTrade trade_obj; //--- if(trade_obj.OrderDelete(other_ord_ticket)) return true; } } } } //--- return false; }
J’aimerais mentionner un détail. L’indicateur d’initialisation de paire est vérifié dans le corps de la méthode de classe. Toute tentative de vérification des ordres ne sera pas effectuée si l’indicateur est effacé. Cette approche empêche la suppression d’un ordre actif lorsqu’un autre n’a pas encore été passé.
Ajoutons des fonctionnalités au script où quelques ordres ont été passés. À cette fin, nous allons créer Control_OCO_EA.mq5 EA de test.
D’une manière générale, l’EA ne diffère du script que par le bloc de gestion des événements Trade() dans son code :
//+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade() { //--- OCO pair deinitialization if(myOco.Deinit()) { Print("No more order pair!"); //--- clear pair CiOcoObject new_oco; myOco=new_oco; } }
La vidéo montre le travail des deux programmes dans le terminal MetaTrader 5.
Cependant, les deux programmes de test ont des faiblesses.
Le premier programme (script) ne peut que créer activement la paire, mais il perd ensuite le contrôle sur elle.
Le deuxième programme (Expert Advisor) contrôle la paire, mais il ne peut pas créer à plusieurs reprises d’autres paires après la création de la première. Pour rendre le programme d’ordre OCO (script) complet, nous devons élargir son ensemble d’outils avec la possibilité de passer des ordres. Nous le ferons dans la section suivante.
3. Contrôle de l’EA
Laissez-nous créer le panneau de gestion des ordres OCO sur le graphique pour placer et définir les paramètres des ordres de la paire.
Il fera partie de l’EA de contrôle (Fig.4). Le code source se trouve dans Panel_OCO_EA.mq5.
Fig. 4. Panneau de création des ordres OCO : état initial
Nous devons sélectionner un type d’ordre future et remplir les champs pour passer la paire d’ordres OCO.
Ensuite, l’étiquette sur le seul bouton du panneau sera modifiée (propriété de texte, Fig.5).
Fig. 5. Panneau de création des ordres OCO : nouvelle paire
Les classes suivantes de la bibliothèque standard ont été utilisées pour construire notre panneau :
- CAppDialog est la boîte de dialogue principale de l’application ;
- CPanel est une étiquette rectangulaire ;
- CLabel est une étiquette de texte ;
- CComboBox est un champ avec une liste déroulante ;
- CEdit est un champ de saisie ;
- CButton est un bouton.
Bien sûr, les méthodes de classe parent ont été appelées automatiquement.
Maintenant, nous passons au code. Il faut dire que la partie de la bibliothèque standard qui a été consacrée à la création de panneaux d’indication et de dialogues est assez grande.
Par exemple, si vous souhaitez attraper un événement de fermeture de la liste déroulante, vous devrez plonger profondément dans la pile d’appels (Fig. 6).
Fig. 6. Pile d’appels
Un développeur définit des macros et une notation dans le fichier %MQL5\Include\Controls\Defines.mqh pour des événements spécifiques.
J’ai créé l’événement personnalisé ON_OCO pour créer la paire OCO.
#define ON_OCO (101) // OCO pair creation event
Les paramètres des ordres futures sont remplis et la paire est générée dans le corps du gestionnaire OnChartEvent().
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- handling all chart events by main dialog myDialog.ChartEvent(id,lparam,dparam,sparam); //--- drop-down list handling if(id==CHARTEVENT_CUSTOM+ON_CHANGE) { //--- if it is Panel list if(!StringCompare(StringSubstr(sparam,0,7),"myCombo")) { static ENUM_PENDING_ORDER_TYPE prev_vals[2]; //--- list index int combo_idx=(int)StringToInteger(StringSubstr(sparam,7,1))-1; ENUM_PENDING_ORDER_TYPE curr_val=(ENUM_PENDING_ORDER_TYPE)(myCombos[combo_idx].Value()+2); //--- remember order type change if(prev_vals[combo_idx]!=curr_val) { prev_vals[combo_idx]=curr_val; gOrdersProps[combo_idx].order_type=curr_val; } } } //--- handling input fields else if(id==CHARTEVENT_OBJECT_ENDEDIT) { //--- if it is Panel's input field if(!StringCompare(StringSubstr(sparam,0,6),"myEdit")) { //--- find object for(int idx=0;idx<ArraySize(myEdits);idx++) { string curr_edit_obj_name=myEdits[idx].Name(); long curr_edit_obj_id=myEdits[idx].Id(); //--- if names coincide if(!StringCompare(sparam,curr_edit_obj_name)) { //--- get current value of field double value=StringToDouble(myEdits[idx].Text()); //--- define gOrdersProps[] array index int order_num=(idx<gEditsHalfLen)?0:1; //--- define gOrdersProps structure field number int jdx=idx; if(order_num) jdx=idx-gEditsHalfLen; //--- fill up gOrdersProps structure field switch(jdx) { case 0: // volume { gOrdersProps[order_num].volume=value; break; } case 1: // execution { gOrdersProps[order_num].price_offset=(uint)value; break; } case 2: // limit { gOrdersProps[order_num].limit_offset=(uint)value; break; } case 3: // stop { gOrdersProps[order_num].sl=(uint)value; break; } case 4: // profit { gOrdersProps[order_num].tp=(uint)value; break; } } } } //--- OCO pair creation flag bool is_to_fire_oco=true; //--- check structure filling for(int idx=0;idx<ArraySize(gOrdersProps);idx++) { //--- if order type is set if(gOrdersProps[idx].order_type!=WRONG_VALUE) //--- if volume is set if(gOrdersProps[idx].volume!=WRONG_VALUE) //--- if offset for execution price is set if(gOrdersProps[idx].price_offset!=(uint)WRONG_VALUE) //--- if offset for limit price is set if(gOrdersProps[idx].limit_offset!=(uint)WRONG_VALUE) //--- if stop loss is set if(gOrdersProps[idx].sl!=(uint)WRONG_VALUE) //--- if take profit is set if(gOrdersProps[idx].tp!=(uint)WRONG_VALUE) continue; //--- clear OCO pair creation flag is_to_fire_oco=false; break; } //--- create OCO pair? if(is_to_fire_oco) { //--- complete comment fields for(int ord_idx=0;ord_idx<ArraySize(gOrdersProps);ord_idx++) gOrdersProps[ord_idx].comment=StringFormat("OCO Order %d",ord_idx+1); //--- change button properties myButton.Text("New pair"); myButton.Color(clrDarkBlue); myButton.ColorBackground(clrLightBlue); //--- respond to user actions myButton.Enable(); } } } //--- handling click on button else if(id==CHARTEVENT_OBJECT_CLICK) { //--- if it is OCO pair creation button if(!StringCompare(StringSubstr(sparam,0,6),"myFire")) //--- if to respond to user actions if(myButton.IsEnabled()) { //--- generate OCO pair creation event EventChartCustom(0,ON_OCO,0,0.0,"OCO_fire"); Print("Command to create new bunch has been received."); } } //--- handling new pair initialization command else if(id==CHARTEVENT_CUSTOM+ON_OCO) { //--- OCO pair initialization if(gOco.Init(gOrdersProps,gOcoList.Total()+1)) { PrintFormat("Id of new OCO pair: %I32u",gOco.Id()); //--- copy pair CiOcoObject *ptr_new_oco=new CiOcoObject(gOco); if(CheckPointer(ptr_new_oco)==POINTER_DYNAMIC) { //--- add to list int node_idx=gOcoList.Add(ptr_new_oco); if(node_idx>-1) PrintFormat("Total number of bunch: %d",gOcoList.Total()); else PrintFormat("Error when adding OCO pair %I32u to list!",gOco.Id()); } } else Print("OCO-orders placing error!"); //--- clear properties Reset(); } }
Le code du gestionnaire n’est pas petit. Je voudrais mettre l’accent sur plusieurs blocs.
La première gestion de tous les événements de graphique est donnée à la boîte de dialogue principale.
Viennent ensuite les blocs de gestion de divers événements :
- Modification des listes déroulantes pour définir un type d’ordre ;
- Modification des champs de saisie pour remplir les propriétés des ordres ;
- Cliquez sur le bouton pour la génération d’événements ON_OCO ;
- ON_OCO réponse à l’événement : création d’une paire d’ordres.
L’EA ne vérifie pas l’exactitude du remplissage des champs du panneau. C’est pourquoi nous devons vérifier les valeurs par nous-mêmes, sinon l’EA affichera une erreur de passation des ordres OCO.
La nécessité de supprimer la paire et de fermer l’ordre restant est vérifiée dans le corps du gestionnaire OnTrade().
Conclusion
J’ai essayé de démontrer les richesses des classes de la bibliothèque standard qui peuvent être utilisées pour l’accomplissement de certaines tâches spécifiques.
En particulier, nous étions confrontés à un problème de traitement des ordres OCO. J’espère que le code de l’EA avec panneau de gestion des ordres OCO sera un point de départ pour la création de paires d’ordres plus compliquées.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/1582





- 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