
Das MQL5-Kochbuch: ОСО-Orders
Einleitung
Dieser Beitrag beschäftigt sich mit dem Umgang einem derartigen Order-Paar wie OCO. Dieser Mechanismus ist in einigen Handelsterminals implementiert, die mit MetaTrader 5, im Wettbewerb stehen. Anhand des Beispiels der Erzeugung eines EAs mit einem Panel zur Verarbeitung von OCO-Orders verfolge ich zwei Ziele: ich möchte einerseits die Merkmale der Standard Library beschreiben, und andererseits das Toolset eines Händlers erweitern.
1. Das Wesentliche der OCO-Orders
OCO-Order (eine Order storniert die andere) stellen ein Paar zweier pending Orders dar.
Sie sind mittels einer gegenseitigen Stornierungsfunktion aneinander geknüpft - wird die erste ausgelöst, bleibt die zweite davon unberührt und umgekehrt.
Abb. 1 Paar an OCO-Orders
Abb. 1 zeigt ein einfaches Schema der gegenseitigen Abhängigkeit von Orders. Damit wird eine grundlegende Definition klar: ein Paar existiert nur so lange, wie es beide Orders gibt. Unter logischen Gesichtspunkten ist jede [eine] Order des Paares eine wesentliche, aber nicht ausreichende Bedingung für das Vorhandensein des Paares.
Einige Quellen führen an, dass das Paar eine Limit-Order und eine Stop-Order haben muss, und dass Orders darüber hinaus eine Richtung besitzen müssen (entweder Kaufen oder Verkaufen). Meiner Ansicht nach helfen solche Einschränkungen bei der Erzeugung flexibler Handelsstrategien nicht. Ich schlage daher vor, dass unterschiedliche OCO-Orders im Paar analysiert werden, und dass wir, und das ist am wichtigsten, dieses Paar zu programmieren versuchen.
2. Programmierung eines Order-Paares
Meiner Meinung nach ist das Objekt-orientierte Programmierungs-Toolset für Programmierungsaufgaben in Zusammenhang mit der Kontrolle über OCO-Orders die bestmögliche Alternative.
Die folgenden Abschnitte beschäftigen sich mit neuen Datentypen, die unserem Ziel hier dienen. Die CiOcoObject-Klasse kommt zuerst.
2.1 CiOcoObject-Klasse
Also müssen wir irgendein Software-Objekt finden, das für die Kontrolle über zwei miteinander verbundene Orders zuständig ist.
Herkömmlicherweise erzeugen wir dazu ein neues Objekt auf Basis der abstrakten CObject-Klasse.
Diese neue Klasse kann folgendermaßen aussehen:
//+------------------------------------------------------------------+ //| 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;}; };
Jedes Paar an OCO-Orders besitzt seinen eigenen Identifikator und sein Wert wird mit Hilfe des Generators von zufälligen Zahlen festgesetzt (Objekt der CRandom-Klasse).
Im Kontext einer Schnittstelle sind die Methoden der Initialisierung und De-Initialisierung des Paares ein Problem. Die erste erzeugt (initialisiert) das Paar und die zweite entfernt (de-initialisiert) es.
Die CiOcoObject::Init()-Methode akzeptiert Strukturen-Arrays vom Typ SOrderProperties als Begründung. Dieser Strukturtyp gibt die Eigenschaften der Order im Paar wieder - also eine OCO-Order.
2.2 SOrderProperties-Struktur
Betrachten wir uns nun die Felder der gerade angesprochenen Struktur.
//+------------------------------------------------------------------+ //| 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 }
Damit die Initialisierungsmethode funktioniert, sollten wir das Strukturen-Array, das aus zwei Elementen besteht, zuvor füllen. Einfacher gesagt: Wir müssen dem Programm erklären, welche Orders es platzieren wird.
Die Aufzählung vom Typ ENUM_PENDING_ORDER_TYPE wird in der Struktur verwendet:
//+------------------------------------------------------------------+ //| 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 };
Generell gesagt, sieht sie genauso aus wie die ENUM _ORDER_TYPE Standard-Aufzählung, erlaubt aber, nur pending Orders auszuwählen, oder präziser - Typen solcher Orders.
Sie schützt bei der Auswahl des entsprechenden Ordertyps in den Eingabeparametern vor Fehlern (Abb. 2).
Abb. 2 Das "Typ"-Feld mit einer Dropdown-Liste der verfügbaren Order-Typen
Wenn wir allerdings die ENUM _ORDER_TYPE Standard-Aufzählung verwenden, könnten wir einen Typ einer Marktorder einrichten (ORDER_TYPE_BUY oder ORDER_TYPE_SELL), der nicht nötig ist, das wir uns ja nur mit pending Orders beschäftigen.
2.3 Initialisierung eines Paares
Wie oben erwähnt, beschäftigt sich die CiOcoObject::Init()-Methode mit der Initialisierung eines Order-Paares und
platziert in der Tat das Order-Paar selbst und vermerkt den Erfolg oder Misserfolg des Auftauchens eines neuen Paares. Hier muss ich darauf hinweisen, dass dies eine aktive Methode ist und Handelsabläufe selbst ausführt. Wir können aber auch eine passive Methode erzeugen. Dazu muss man nur ein Paar bereits aktiver pending Orders miteinander verbinden, die unabhängig voneinander platziert wurden.
Den Code für die gesamte Methode spare ich mir, doch möchte ich anmerken, dass es wichtig ist, alle Kurse zu berechnen (Eröffnung, Stop, Profit, Limit), damit die CTrade::OrderOpen() Handelsklassen-Methode eine Handelsorder ausführen kann. Zu diesem Zweck sollten man sich zwei Dinge überlegen: 1) Richtung der Order (Kaufen oder Verkaufen) und 2) Position des Ausführungskurses einer Order in Relation zum aktuellen Kurs (darüber oder darunter).
Diese Methode ruft einige private Methoden auf: BaseOrderType() und PendingType(). Die erste definiert die Richtung der Order; die zweite legt den Typ der pending Order fest.
Wird die Order platziert, wird ihr Ticket im Array m_order_tickets[] aufgezeichnet.
Um diese Methode zu testen, habe ich ein einfaches Init_OCO.mq5 Script verwendet.
#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!"); }
Hier kann man die verschiedenen Eigenschaften zukünftiger Orders des Paares einrichten. MetaTrader 5 verfügt über sechs verschiedene Typen von pending Orders.
In diesem Kontext kann es also 15 Varianten (Kombinationen) von Paaren geben (vorausgesetzt, die Orders in dem Paar sind auch unterschiedlich).
C(k,N) = C(2,6) = 15
Alle Varianten wurden mit Hilfe des Scripts getestet. Ich zeige Ihnen hier ein Beispiel für das Buy Stop - Buy Stop Limit Paar.
Die Order-Typen sollten in den Script-Parametern spezifiziert werden (Abb. 3).
Abb. 3. Paar einer "Buy Stop" Order mit "Buy Stop Limit" Order
Im "Experts" Register erscheint die folgende Information:
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
Doch wir können mit Hilfe des Scripts mit OCO-Orders nicht optimal und maximal arbeiten, ohne auf Schleifen zurückgreifen zu müssen.
2.4 De-Initialisierung eines Paares
Diese Methode ist für die Kontrolle über das Order-Paar verantwortlich. Sobald irgendeine Order die Liste der aktiven Orders verlässt, wird dieses Paar zu existieren aufhören.
Ich nehme an, dass diese Methode in die OnTrade() oder OnTradeTransaction() Handler des EA-Codes platziert werden sollte. Denn nur so kann der EA die Aktivierung jeder Paar-Order ohne Zeitverzögerung verarbeiten.
//+------------------------------------------------------------------+ //| 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; }
Lassen Sie mich hier auf ein Detail hinweisen: Die Flagge für Paar-Initialisierung wird im Korpus der Klassenmethode angekreuzt. Wird diese Flagge wieder "geleert", findet kein Versuch mehr statt, die Orders zu prüfen. Mit diesem Ansatz wird das Löschen einer aktiven Order vermieden, wenn die andere Order noch gar nicht platziert worden ist.
Fügen wir dem Script, wo einige Orders platziert wurden, nun Funktionalität hinzu. Dazu erzeugen wir den Control_OCO_EA.mq5 Test-EA.
Generell gesagt, unterscheidet sich dieser EA vom Script nur durch den Trade() Block in seinem Code zum Umgang mit Ereignissen:
//+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade() { //--- OCO pair deinitialization if(myOco.Deinit()) { Print("No more order pair!"); //--- clear pair CiOcoObject new_oco; myOco=new_oco; } }
Das Video zeigt die Arbeit beider Programme im MetaTrader 5 Terminal.
Beide Testprogramme haben jedoch auch ihre Schwächen.
Das erste Programm (Script) kann das Paar nur aktiv erzeugen, verliert jedoch dann Kontrolle darüber.
Das zweite Programm (Expert Advisor) kontrolliert zwar das Paar, kann jedoch keine weiteren anderen Paare nach Erzeugung des ersten anlegen. Damit ein OCO-Orderprogramm (Script) voll funktionsfähig wird, müssen wir sein Toolset um die Möglichkeit der Platzierung von Orders erweitern. Darum geht es im nächsten Abschnitt.
3 Der kontrollierende EA
Erzeugen wir also ein Panel zur OCO-Verwaltung auf dem Chart und zwar zur Platzierung und Einrichtung von Parametern von Paar-Orders.
Dieses Panel wird Teil des kontrollierenden EAs (Abb. 4). Der Quellcode befindet sich in Panel_OCO_EA.mq5.
Abb. 4 Panel zur Erzeugung einer OCO-Order: Ursprungsstadium
Wir sollten einen Typ einer zukünftigen Order wählen und alle entsprechenden Felder zur Platzierung des Paares an OCO-Orders ausfüllen
Dann wird die Bezeichnung der einzigen Schaltfläche auf dem Panel geändert (Texteigenschaft, Abb. 5).
Abb. 5 Panel zur Erzeugung einer OCO-Order: neues Paar
Die folgenden Klassen der Standard Library wurden zum Bau unseres Panels benutzt:
- CAppDialog ist der hauptsächliche Anwendungsdialog;
- CPanel ist eine rechteckige Bezeichnung;
- CLabel ist eine Textbezeichnung;
- CComboBox ist ein Feld mit der Dropdown-Liste;
- CEdit ist ein Eingabefeld;
- CButton ist eine Schaltfläche.
Übergeordnete Klassen-Methoden wurden natürlich automatisch aufgerufen.
Jetzt kommen wir zum Code. Hier muss gesagt werden, dass der Teil der Standard Library, der für die Erzeugung der Hinweis-Panels und Dialoge extra zugewiesen wurde, ziemlich umfangreich ist.
Wenn man z.B. ein Schließungselement der Dropdown-Liste erfassen will, muss man sich tief in den Haufen der Aufrufe hineinbegeben (Abb. 6).
Abb. 6 Haufen der Aufrufe
Ein Entwickler setzt für spezifische Ereignisse in der %MQL5\Include\Controls\Defines.mqh-Datei Makros und einen Vermerk.
Ich habe für die Erzeugung des OCO-Paares ein ON_OCO individuell angepasstes Ereignis angelegt.
#define ON_OCO (101) // OCO pair creation event
Die Parameter für zukünftige Orders werden gefüllt und das Paar wird im OnChartEvent() Handler-Korpus generiert.
//+------------------------------------------------------------------+ //| 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(); } }
Der Handler-Code ist dabei gar nicht klein. Ich möchte Ihre Aufmerksamkeit auf verschiedene Blöcke lenken.
Dem Hauptdialog ist der erste Umgang mit allen Chart-Ereignissen übertragen.
Dann folgen die Blöcke unterschiedlicher Umgänge mit Ereignissen:
- Sich ändernde Dropdown-Listen zur Definition eines Order-Typs;
- Bearbeitung der Eingabefelder zum Eintragen der Order-Eigenschaften;
- Anklick-Schaltfläche zur ON_OCO Ereignis-Generierung;
- ON_OCO Ereignis-Antwort: Erzeugung eines Order-Paares.
Der EA bestätigt das exakte Füllen der Panel-Felder nicht, und deshalb müssen wir die Werte selbst überprüfen, denn sonst liefert uns der EA eine Fehlermeldung bei der Platzierung von OCO-Orders.
Die Notwendigkeit der Entfernung des Paares und des Schließens der restlichen Order wird im OnTrade() Handler-Korpus geprüft.
Fazit
Ich habe hier die Reichhaltigkeit der Standard Library-Klassen zu zeigen versucht, die zur Erfüllung einiger ganz bestimmter Aufgaben verwendet werden können.
Genauer gesagt, ging es hier um die Lösung eines Problems im Umgang mit OCO-Orders. Ich hoffe, dass der Code des EAs mit seinem Panel zum Umgang mit OCO-Orders einen geeigneten Ausgangspunkt zur Erzeugung weitaus komplizierterer Order-Paare darstellt.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/1582





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.