
Manuale MQL5: Ordini ОСО
Introduzione
Questo articolo si concentra sulla gestione di questo tipo di coppia di ordini come OCO. Questo meccanismo è implementato in alcuni terminali di trading in concorrenza con MetaTrader 5. Perseguo due obiettivi attraverso l'esempio della creazione di un EA con un pannello per l'elaborazione degli ordini OCO. Il primo è la descrizione delle caratteristiche della libreria standard, l’altro è la volontà di estendere il set di strumenti di un trader.
1. Essenza degli ordini OCO
Gli ordini OCO (one-cancels-the-other order, in italiano un-ordine-annulla-l'altro) rappresentano una coppia di due ordini in sospeso.
Sono collegati dalla funzione di cancellazione reciproca: se il primo si attiva, il secondo dovrebbe essere rimosso e viceversa.
Fig. 1 Coppia di ordini OCO
La Fig.1 mostra un semplice schema di interdipendenza dell'ordine. Riflette una definizione essenziale: una coppia esiste finché esistono entrambi gli ordini. In termini di logica, qualsiasi ordine della coppia è una condizione essenziale ma non sufficiente per l'esistenza della coppia.
Alcune fonti dicono che la coppia deve avere un ordine limite e un ordine stop, inoltre gli ordini devono avere una direzione (comprare o vendere). A mio avviso, tale restrizione non può aiutare nella creazione di strategie di trading flessibili. Suggerisco che vari ordini OCO vengano analizzati nella coppia e, soprattutto, cercheremo di programmare questa coppia.
2. Coppia di ordini di programmazione
A mio avviso, il set di strumenti ООP è adatto per la programmazione di attività connesse al controllo degli ordini OCO nel miglior modo possibile.
Le sezioni seguenti sono dedicate a nuovi tipi di dati che serviranno al nostro scopo. La classe CiOcoObject viene prima di tutto.
2.1. CiOcoObject Classe
Quindi, abbiamo bisogno di trovare qualche oggetto software responsabile del controllo su due ordini interconnessi.
Tradizionalmente, creiamo un nuovo oggetto sulla base della classe astratta CObject.
Questa nuova classe può avere l'aspetto seguente:
//+------------------------------------------------------------------+ //| 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;}; };
Ogni coppia di ordini OCO avrà il proprio identificatore. Il suo valore è impostato per mezzo del generatore di numeri casuali (oggetto della classe CRandom).
I metodi di inizializzazione e deistizializzazione delle coppie sono di preoccupazione nel contesto dell'interfaccia. Il primo crea (inizializza) la coppia, e il secondo la rimuove (la deinitializza).
Il metodoCiOcoObject::Init() accetta come argomento la matrice di strutture di tipo SOrderProperties. Questo tipo di struttura rappresenta le proprietà dell'ordine nella coppia, cioè l'ordine OCO.
2.2 Struttura SOrderProperties
Consideriamo i campi della suddetta struttura.
//+------------------------------------------------------------------+ //| 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 }
Quindi, per far funzionare il metodo di inizializzazione, dovremmo in precedenza riempire l'array di strutture costituito da due elementi. In parole semplici, dobbiamo spiegare al programma quali ordini eseguirà.
L'enumerazione di ENUM_PENDING_ORDER_TYPE tipo viene utilizzata nella struttura:
//+------------------------------------------------------------------+ //| 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 };
In generale, ha lo stesso aspetto dell'ENUM _ORDER_TYPE enumerazione standard, ma consente di selezionare solo gli ordini in sospeso o, più esattamente, i tipi di tali ordini.
Protegge dagli errori durante la selezione del tipo di ordine corrispondente nei parametri di input (Fig.2).
Fig. 2. Il campo "Tipo" con un elenco a discesa dei tipi di ordine disponibili
Se tuttavia utilizziamo ENUM _ORDER_TYPE enumerazione standard, potremmo impostare un tipo di ordine di mercato (ORDER_TYPE_BUY o ORDER_TYPE_SELL), che non è richiesto in quanto abbiamo a che fare solo con ordini in sospeso.
2.3. Inizializzazione della coppia
Come notato sopra, il metodo CiOcoObject::Init() è impegnato nell'inizializzazione della coppia di ordini.
Infatti posiziona la coppia di ordini stessa e registra il successo o il fallimento dell'emergere di nuove coppie. Devo dire che questo è un metodo attivo, in quanto esegue operazioni di trading da solo. Possiamo anche creare un metodo passivo. Si collegherà semplicemente a una coppia di ordini in sospeso già attivi che sono stati effettuati in modo indipendente.
Non fornirò un codice dell'intero metodo. Ma vorrei notare che è importante calcolare tutti i prezzi (apertura, stop, profitto, limite), quindi il metodo di classe commerciale CTrade::OrderOpen() può eseguire un ordine commerciale. A tal fine dovremmo considerare due cose: la direzione dell'ordine (acquisto o vendita) e la posizione di un prezzo di esecuzione dell'ordine rispetto a un prezzo corrente (sopra o sotto).
Questo metodo chiama un paio di metodi privati: BaseOrderType() e PendingType(). Il primo definisce la direzione dell'ordine, il secondo determina il tipo di ordine in sospeso.
Se l'ordine viene effettuato, il relativo ticket viene registrato nella matrice m_order_tickets[].
Ho usato un semplice script Init_OCO.mq5 per testare questo metodo.
#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!"); }
Qui puoi impostare varie proprietà degli ordini futuri della coppia. MetaTrader 5 ha sei diversi tipi di ordini in sospeso.
In questo contesto, ci possono essere 15 varianti (combinazioni) di coppie (a condizione che ci siano ordini diversi nella coppia).
C(k;N) = C(2,6) = 15
Tutte le varianti sono state testate con l'aiuto dello script. Farò un esempio per la coppia Buy Stop - Buy Stop Limit.
I tipi di ordini devono essere specificati nei parametri di script (Fig.3).
Fig. 3. Coppia di ordini "Buy Stop" con ordine "Buy Stop Limit"
Le seguenti informazioni appariranno nel registro "Esperti":
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
Ma non possiamo lavorare con gli ordini OCO al massimo con l'aiuto della sceneggiatura senza ricorrere al looping.
2.4. Deinitializzazione della coppia
Questo metodo è responsabile del controllo sulla coppia di ordini. La coppia "morirà" quando un ordine lascia l'elenco degli ordini attivi.
Suppongo che questo metodo dovrebbe essere inserito nei gestori OnTrade() o OnTradeTransaction() del codice EA. In tal modo, l'EA sarà in grado di elaborare l'attivazione di qualsiasi ordine di coppia senza indugio.
//+------------------------------------------------------------------+ //| 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; }
Vorrei menzionare un dettaglio. Il flag di inizializzazione della coppia viene controllato nel corpo del metodo class. Il tentativo di controllare gli ordini non verrà effettuato se il flag viene cancellato. Questo approccio impedisce di eliminare un ordine attivo quando non ne è stato ancora inserito un altro.
Aggiungiamo funzionalità allo script in cui sono stati effettuati un paio di ordini. A tale scopo, creeremo Control_OCO_EA.mq5 test EA.
In generale, l'EA differirà dallo script solo dal blocco di gestione degli eventi Trade() nel suo codice:
//+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade() { //--- OCO pair deinitialization if(myOco.Deinit()) { Print("No more order pair!"); //--- clear pair CiOcoObject new_oco; myOco=new_oco; } }
Il video mostra il lavoro di entrambi i programmi nel terminale MetaTrader 5.
Tuttavia, entrambi i programmi di test hanno punti deboli.
Il primo programma (script) può solo creare attivamente la coppia, ma poi perde il controllo su di essa.
Il secondo programma (Expert Advisor) controlla però la coppia, ma non può creare ripetutamente altre coppie dopo la creazione della prima. Per rendere il programma di ordini OCO (script) completo, dobbiamo espandere il suo set di strumenti dandogli la possibilità di effettuare ordini. Lo faremo nella prossima sezione.
3. Controllo di EA
Creiamo il pannello di gestione degli ordini OCO sul grafico per posizionare e impostare i parametri degli ordini di coppia.
Farà parte dell'EA di controllo (Fig.4). Il codice sorgente si trova in Panel_OCO_EA.mq5.
Fig. 4. Pannello per la creazione di ordini OCO: stato iniziale
Dovremmo selezionare un tipo di ordine futuro e compilare i campi per inserire la coppia di ordini OCO.
Quindi l'etichetta sull'unico pulsante del pannello verrà modificata (proprietà text, Fig.5).
Fig. 5. Pannello per la creazione di ordini OCO: nuova coppia
Le seguenti classi della Libreria Standard sono state utilizzate per costruire il nostro Pannello:
- CAppDialog è la finestra di dialogo principale dell'applicazione;
- CPanel è un'etichetta rettangolare;
- CLabel è un'etichetta di testo;
- CComboBox è un campo con un elenco a discesa;
- CEdit è un campo di input;
- CButton è un pulsante.
Naturalmente, i metodi di classe dei genitori sono stati chiamati automaticamente.
Ora passiamo al codice. Va detto che la parte della Libreria Standard che è stata dedicata alla creazione di pannelli di indicazione e dialoghi è piuttosto grande.
Ad esempio, se vuoi catturare un evento di chiusura dell'elenco a discesa, dovrai approfondire la pila di chiamate (Fig. 6).
Fig. 6. Pila di chiamate
Uno sviluppatore imposta macro e una notazione nel file %MQL5\Include\Controls\Defines.mqh per eventi specifici.
Ho creato ON_OCO evento personalizzato per creare la coppia OCO.
#define ON_OCO (101) // OCO pair creation event
I parametri degli ordini futuri vengono compilati e la coppia viene generata nel corpo del gestore 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(); } }
Il codice del gestore non è piccolo. Vorrei porre l'accento su diversi blocchi.
La prima gestione di tutti gli eventi del grafico viene data alla finestra di dialogo principale.
Di seguito sono i blocchi di vari eventi che gestiscono:
- Modifica degli elenchi a discesa per la definizione di un tipo di ordine;
- Modifica dei campi di input per riempire le proprietà degli ordini;
- Fare clic sul pulsante per ON_OCO generazione di eventi;
- ON_OCO risposta all'evento: creazione di coppie di ordini.
L'EA non verifica la correttezza della compilazione dei campi del pannello. Questo è il motivo per cui dobbiamo controllare i valori da soli, altrimenti l'EA mostrerà l'errore di inserimento degli ordini OCO.
La necessità di rimuovere la coppia e chiudere l'ordine rimanente viene verificata nel corpo del gestore OnTrade().
Conclusione
Ho cercato di dimostrare le ricchezze delle classi Standard Library che possono essere utilizzate per l'adempimento di alcuni compiti specifici.
In particolare, avevamo a che fare con un problema di gestione degli ordini OCO. Spero che il codice dell'EA con Panel per la gestione degli ordini OCO sia un punto di partenza per la creazione di coppie di ordini più complicate.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/1582





- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso