Guia Prático MQL5: Ordens ОСО
Introdução
Este artigo foca em como lidar com o tipos de pares de ordens tal como o OCO. Esse mecanismo é implementado em alguns terminais de negociação concorrentes do MetaTrader 5. Eu busquei dois objetivos através do exemplo de criação de um EA com um painel para o processamento de ordens OCO. Por um lado, eu gostaria de descrever as características da Biblioteca Padrão, por outro lado, eu gostaria de estender o conjunto de ferramentas do trader.
1. Essência da Ordem OCO
As ordens OCO (ordem uma-cancela-a-outra) representam um par de duas ordens pendentes.
Elas estão ligadas por uma função de cancelamento mútuo: se a primeira disparar, a segunda é removida, e vice-versa.
Fig. 1 Par de ordens OCO
Fig.1 mostra um esquema simples da interdependência da ordem. Ela reflete uma definição essencial: o par existirá enquanto ambas as ordens existirem. Em termos de lógica, qualquer [uma] ordem do par é uma condição essencial, mas não suficiente para a existência do par.
Algumas fontes dizem que o par deve ter uma ordem limite e uma ordem de stop, além disso as ordens devem ter uma direção (de compra ou venda). Em minha cabeça, tal restrição não pode ajudar na criação de estratégias de negociação flexíveis. Eu sugiro que várias ordens OCO devem ser analisadas no par, e mais importante ainda, nós vamos tentar programar este par.
2. Programação do Par de Ordens
Em meu pensamento, o conjunto de ferramentas POO se adequa da melhor maneira possível para as tarefas de programação relacionadas ao controle sobre as ordens OCO.
As seções seguintes são dedicadas aos novos tipos de dados que servirão para o nosso propósito. A classe CiOcoObject vem em primeiro lugar.
2.1. Classe CiOcoObject
Então, nós precisamos inventar algum objeto do programa responsável pelo controle das duas ordens interligadas.
Tradicionalmente, vamos criar um novo objeto com base na classe abstrata CObject.
Esta nova classe será da seguinte maneira:
//+------------------------------------------------------------------+ //| Classe CiOcoObject | //| Objetivo: uma classe para ordens OCO | //+------------------------------------------------------------------+ class CiOcoObject : public CObject { //--- === Os membros de dados === --- private: //--- Tickets do par ulong m_order_tickets[2]; //--- Flag de inicialização bool m_is_init; //--- Id uint m_id; //--- === Métodos === --- public: //--- Construtor/destrutor void CiOcoObject(void){m_is_init=false;}; void ~CiOcoObject(void){}; //--- Construtor de cópia void CiOcoObject(const CiOcoObject &_src_oco); //--- Operador de atribuição void operator=(const CiOcoObject &_src_oco); //--- Inicialização/desinicialização bool Init(const SOrderProperties &_orders[],const uint _bunch_cnt=1); bool Deinit(void); //--- Obter o id uint Id(void) const {return m_id;}; private: //--- Tipos de ordens ENUM_ORDER_TYPE BaseOrderType(const ENUM_ORDER_TYPE _ord_type); ENUM_BASE_PENDING_TYPE PendingType(const ENUM_PENDING_ORDER_TYPE _pend_type); //--- Definir o id void Id(const uint _id){m_id=_id;}; };
Cada par de ordens OCO vai ter o seu próprio identificador. O seu valor é ajustado por meio do gerador de números aleatórios (objeto da classe Crandom).
Métodos de inicialização e desinicialização do par são de preocupação no contexto da interface. A primeira cria (inicializa) o par, e a segunda remove (desinicializa) ele.
O método CiOcoObject::Init() aceita arrays de estruturas do tipo SOrderProperties como argumento. Este tipo de estrutura representa as propriedades da ordem do par, ou seja, ordem OCO.
2.2 Estrutura SOrderProperties
Vamos considerar os campos da estrutura acima mencionada.
//+------------------------------------------------------------------+ //| Estrutura de propriedades da ordem | //+------------------------------------------------------------------+ struct SOrderProperties { double volume; // volume da ordem string symbol; // Símbolo ENUM_PENDING_ORDER_TYPE order_type; // Tipo de ordem uint price_offset; // desvio para o preço de execução, em pontos uint limit_offset; // desvio para o preço limite, em pontos uint sl; // stop loss, em pontos uint tp; // take profit, em pontos ENUM_ORDER_TYPE_TIME type_time; // Tipo de expiração datetime expiration; // Expiração string comment; // comentário }
Assim, para realizar o método de trabalho de inicialização nós devemos preencher previamente o array de estruturas compostas por dois elementos. Em palavras simples, é preciso explicar ao programa quais ordens que ele estará colocando.
A enumeração do tipo ENUM_PENDING_ORDER_TYPE é usada na estrutura:
//+------------------------------------------------------------------+ //| Tipo da ordem pendente | //+------------------------------------------------------------------+ 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 };
De um modo geral, ele parece o mesmo que a enumeração padrão ENUM _ORDER_TYPE, mas ele permite selecionar apenas as ordens pendentes, ou, mais verdadeiramente, os tipos de tais ordens.
Ele protege de erros ao selecionar o tipo de ordem correspondente nos parâmetros de entrada (Fig.2).
Fig. 2. O campo "Type" com uma lista suspensa dos tipos de ordens disponíveis
Se, no entanto, usarmos a enumeração padão ENUM _ORDER_TYPE, então poderíamos definir o tipo de uma ordem a mercado (ORDER_TYPE_BUY ou ORDER_TYPE_SELL), que não é necessária já que estamos lidando apenas com ordens pendentes.
2.3. Inicialização do Par
Como observado acima, o método CiOcoObject::Init() está envolvido com a inicialização do par da ordem
Na verdade, ele mesmo coloca o par de ordens e registra se o par foi colocado com sucesso ou houve alguma falha. Devo dizer que este é um método ativo, já que ele executa as operações de negociação por si só. Nós podemos criar um método passivo também. Ela simplesmente se conecta a um par de ordens pendentes já ativos, que tenham sido colocados de forma independente.
Eu não irei fornecer o código de todo o método. Mas eu gostaria de salientar que é importante calcular todos os preços (abertura, stop, lucro, limite), de modo que o método da classe de negociação CTrade::OrderOpen() possa executar uma ordem de negociação. Para esse fim, devemos considerar duas coisas: a direção da ordem (compra ou venda) e a posição do preço de execução da ordem em relação ao preço atual (acima ou abaixo).
Este método chama alguns métodos privados: BaseOrderType() e PendingType(). A primeira define a direção da ordem, a segunda determina o tipo da ordem pendente.
Se a ordem foi colocada, o seu ticket é registrado no array m_order_tickets[].
Eu usei um script simples Init_OCO.mq5 para testar este método.
#property script_show_inputs //--- #include "CiOcoObject.mqh" //+------------------------------------------------------------------+ //| Entradas | //+------------------------------------------------------------------+ sinput string Info_order1="+===--Order 1--====+"; // +===--Ordem 1--====+ input ENUM_PENDING_ORDER_TYPE InpOrder1Type=PENDING_ORDER_TYPE_SELL_LIMIT; // Type input double InpOrder1Volume=0.02; // Volume input uint InpOrder1PriceOffset=125; // Desvio para o preço de execução, em pontos input uint InpOrder1LimitOffset=50; // Desvio para o preço limite, em pontos input uint InpOrder1SL=250; // Stop loss, em pontos input uint InpOrder1TP=455; // Lucro, em pontos input string InpOrder1Comment="OCO Order 1"; // Comentários //--- sinput string Info_order2="+===--Order 2--====+"; // +===--Ordem 2--====+ input ENUM_PENDING_ORDER_TYPE InpOrder2Type=PENDING_ORDER_TYPE_SELL_STOP; // Tipo input double InpOrder2Volume=0.04; // Volume input uint InpOrder2PriceOffset=125; // Desvio do preço de execução, em pontos input uint InpOrder2LimitOffset=50; // Desvio do preço limite, em pontos input uint InpOrder2SL=275; // Stop loss, em pontos input uint InpOrder2TP=300; // Lucro, em pontos input string InpOrder2Comment="OCO Order 2"; // Comentário //--- Globais CiOcoObject myOco; SOrderProperties gOrdersProps[2]; //+------------------------------------------------------------------+ //| Script da função de início do programa | //+------------------------------------------------------------------+ void OnStart() { //--- Propriedade da 1ª ordem 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; //--- Propriedade da 2ª ordem 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; //--- Inicialização do par if(myOco.Init(gOrdersProps)) PrintFormat("Id do novo par OCO: %I32u",myOco.Id()); else Print("Erro ao colocar o par OCO!"); }
Aqui você pode definir várias propriedades dos futuros pares de ordens. O MetaTrader 5 tem seis diferentes tipos de ordens pendentes.
Com este contexto, pode haver 15 variantes (combinações) de pares (desde que existam diferentes ordens no par).
C(k,N) = C(2,6) = 15
Todas as variantes foram testadas com o auxílio do script. Vou dar um exemplo para o par Buy Stop - Buy Stop Limit.
Os tipos de ordens devem ser especificados nos parâmetros do script (Fig.3).
Fig. 3. Par de ordens "Buy Stop" com "Buy Limit Stop"
A seguinte informação aparece no registo "Experts":
QO 0 17:17:41.020 Init_OCO (GBPUSD.e,M15) Código do resultado da solicitação: 10009 JD 0 17:17:41.036 Init_OCO (GBPUSD.e,M15) Ticket da nova ordem: 24190813 QL 0 17:17:41.286 Init_OCO (GBPUSD.e,M15) Código do resultado da solicitação: 10009 JH 0 17:17:41.286 Init_OCO (GBPUSD.e,M15) Ticket da nova ordem: 24190814 MM 0 17:17:41.379 Init_OCO (GBPUSD.e,M15) Id do novo par OCO: 3782950319
Mas não podemos trabalhar com as ordens OCO ao máximo com a ajuda do script sem recorrer ao looping.
2.4. Desinicialização do Par
Este método é responsável pelo controle do par de ordens. O par irá "morrer" quando qualquer ordem sair da lista de pedidos ativos.
Eu suponho que este método deve ser colocado nos manipuladores OnTrade() ou OnTradeTransaction() do código do EA. Em tal forma, o EA será capaz de processar a ativação de qualquer par de ordens, sem atraso.
//+------------------------------------------------------------------+ //| Desinicialização do par | //+------------------------------------------------------------------+ bool CiOcoObject::Deinit(void) { //--- Se o par é inicializado if(this.m_is_init) { //--- Verificação das suas ordens for(int ord_idx=0;ord_idx<ArraySize(this.m_order_tickets);ord_idx++) { //--- Ordem do par atual ulong curr_ord_ticket=this.m_order_tickets[ord_idx]; //--- Outro par de ordens int other_ord_idx=!ord_idx; ulong other_ord_ticket=this.m_order_tickets[other_ord_idx]; //--- COrderInfo order_obj; //--- Se não houver ordem atual if(!order_obj.Select(curr_ord_ticket)) { PrintFormat("Ordem #%d não foi encontrada na lista de ordens ativas.",curr_ord_ticket); //--- Tentativa de excluir uma outra ordem if(order_obj.Select(other_ord_ticket)) { CTrade trade_obj; //--- if(trade_obj.OrderDelete(other_ord_ticket)) return true; } } } } //--- return false; }
Eu gostaria de mencionar um detalhe. O flag de inicialização do par é verificado no corpo do método da classe. Uma tentativa de verificar as ordens não será feita se a flag estiver desmarcada. Esta abordagem evita a exclusão de uma ordem ativa quando outra não tenha sido colocado ainda.
Vamos adicionar esta funcionalidade ao script onde um par de ordens foi colocado. Para este efeito, criaremos o EA de teste Control_OCO_EA.mq5.
De um modo geral o EA vai diferir do script só pelo bloco de manipulação do evento Trade() em seu código:
//+------------------------------------------------------------------+ //| Função Trade | //+------------------------------------------------------------------+ void OnTrade() { //--- Desinicialização do par OCO if(myOco.Deinit()) { Print("Não há mais par de ordens!"); //--- Limpar o Par CiOcoObject new_oco; myOco=new_oco; } }
O vídeo mostra o trabalho de ambos os programas no terminal MetaTrader 5.
No entanto ambos os programas de teste têm pontos fracos.
O primeiro programa (script) só pode criar ativamente o par, mas depois ele perde o controle sobre ela.
O segundo programa (Expert Advisor), embora ele controle o par, ele não pode criar outros pares repetidamente após a criação da primeira. Para fazer o programa completo de ordem OCO (script), precisamos expandir seu conjunto de ferramentas com a oportunidade de colocar ordens. Faremos isso na próxima seção.
3. EA de Controle
Vamos criar o Painel de Gerenciamento de Ordens OCO no gráfico para colocar e ajustar os parâmetros do par de ordens.
Ele será uma parte do EA de controle (Fig.4). O código fonte está localizado em Panel_OCO_EA.mq5.
Fig. 4. Painel para a criação das ordens OCO: estado inicial
Devemos selecionar o tipo da futura ordem e preencher os campos para colocar o par de ordens OCO.
Em seguida, o rótulo no único botão do painel será alterado (propriedade de texto, Fig.5).
Fig. 5. Painel para a criação das ordens OCO: novo par
As seguintes classes da Biblioteca Padrão foram utilizadas para construir o nosso Painel:
- CAppDialog é o diálogo do aplicativo principal;
- CPanel é um rótulo de retângulo;
- CLabel é um rótulo de texto;
- CComboBox é um campo com uma lista suspensa;
- CEdit é um campo de entrada;
- CButton é um botão.
Claro, os métodos da classe pai foram chamados automaticamente.
Agora vamos para o código. Tem que ser dito que a parte da Biblioteca Padrão que tem se dedicado para a criação dos painéis de indicação e os diálogos é bem grande.
Por exemplo, se você quiser pegar um evento de encerramento da lista suspensa, você terá que mergulhar profundamente na pilha de chamadas (Fig. 6).
Fig. 6. Pilha de Chamadas
O desenvolvedor define os macros e a notação no arquivo %MQL5\Include\Controls\Defines.mqh para os eventos específicos.
Eu criei o evento personalizado ON_OCO para criar o par de OCO.
#define ON_OCO (101) // evento de criação do par OCO
Os parâmetros das ordens futuras são preenchidas e o par é gerado no corpo do manipulador OnChartEvent().
//+------------------------------------------------------------------+ //| Função ChartEvent | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Manipulador de todos os eventos do gráfico através do diálogo principal myDialog.ChartEvent(id,lparam,dparam,sparam); //--- Manipulador da lista suspensa if(id==CHARTEVENT_CUSTOM+ON_CHANGE) { //--- Se é o Painel lista if(!StringCompare(StringSubstr(sparam,0,7),"myCombo")) { static ENUM_PENDING_ORDER_TYPE prev_vals[2]; //--- Índice da lista 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); //--- Lembrar a alteração do tipo da ordem if(prev_vals[combo_idx]!=curr_val) { prev_vals[combo_idx]=curr_val; gOrdersProps[combo_idx].order_type=curr_val; } } } //--- Manipulador dos campos de entrada else if(id==CHARTEVENT_OBJECT_ENDEDIT) { //--- Se é o campo de entrada do Painel if(!StringCompare(StringSubstr(sparam,0,6),"myEdit")) { //--- Buscar objeto for(int idx=0;idx<ArraySize(myEdits);idx++) { string curr_edit_obj_name=myEdits[idx].Name(); long curr_edit_obj_id=myEdits[idx].Id(); //--- Se os nomes coincidem if(!StringCompare(sparam,curr_edit_obj_name)) { //--- Obter o valor atual do campo double value=StringToDouble(myEdits[idx].Text()); //--- Define o índice do array gOrdersProps[] int order_num=(idx<gEditsHalfLen)?0:1; //--- Define o número do campo da estrutura gOrdersProps int jdx=idx; if(order_num) jdx=idx-gEditsHalfLen; //--- Preenche o campo de estrutura gOrdersProps switch(jdx) { case 0: // volume { gOrdersProps[order_num].volume=value; break; } case 1: // Execução { gOrdersProps[order_num].price_offset=(uint)value; break; } case 2: // limite { gOrdersProps[order_num].limit_offset=(uint)value; break; } case 3: // stop { gOrdersProps[order_num].sl=(uint)value; break; } case 4: // lucro { gOrdersProps[order_num].tp=(uint)value; break; } } } } //--- Criação do flag do par OCO bool is_to_fire_oco=true; //--- Verificação da estrutura de preenchimento for(int idx=0;idx<ArraySize(gOrdersProps);idx++) { //--- Se o tipo da ordem está definido if(gOrdersProps[idx].order_type!=WRONG_VALUE) //--- Se o volume está definido if(gOrdersProps[idx].volume!=WRONG_VALUE) //--- Se o desvio para o preço de execução está definido if(gOrdersProps[idx].price_offset!=(uint)WRONG_VALUE) //--- Se o desvio para o preço limite está definido if(gOrdersProps[idx].limit_offset!=(uint)WRONG_VALUE) //--- Se o stop loss está definido if(gOrdersProps[idx].sl!=(uint)WRONG_VALUE) //--- Se o take profit está definido if(gOrdersProps[idx].tp!=(uint)WRONG_VALUE) continue; //--- limpa o flag de criação do par OCO is_to_fire_oco=false; break; } //--- Cria o par OCO? if(is_to_fire_oco) { //--- Completa os campos de comentários for(int ord_idx=0;ord_idx<ArraySize(gOrdersProps);ord_idx++) gOrdersProps[ord_idx].comment=StringFormat("OCO Order %d",ord_idx+1); //--- Altera as propriedades do botão myButton.Text("New pair"); myButton.Color(clrDarkBlue); myButton.ColorBackground(clrLightBlue); //--- Responder às ações do usuário myButton.Enable(); } } } //--- Manipulador do clique no botão else if(id==CHARTEVENT_OBJECT_CLICK) { //--- Se é o botão de criação do par OCO if(!StringCompare(StringSubstr(sparam,0,6),"myFire")) //--- Se é para responder às ações do usuário if(myButton.IsEnabled()) { //--- Gerar o evento de criação do par OCO EventChartCustom(0,ON_OCO,0,0.0,"OCO_fire"); Print("Foi recebido o comando para criar um novo grupo."); } } //--- Manipulador do comando de inicialização do novo par else if(id==CHARTEVENT_CUSTOM+ON_OCO) { //--- Inicialização do par OCO if(gOco.Init(gOrdersProps,gOcoList.Total()+1)) { PrintFormat("Id do novo par OCO: %I32u",gOco.Id()); //--- Cópia do par CiOcoObject *ptr_new_oco=new CiOcoObject(gOco); if(CheckPointer(ptr_new_oco)==POINTER_DYNAMIC) { //--- Adicionar a lista int node_idx=gOcoList.Add(ptr_new_oco); if(node_idx>-1) PrintFormat("Número total do grupo: %d",gOcoList.Total()); else PrintFormat("Erro ao adicionar o par OCO %I32u na lista!",gOco.Id()); } } else Print("Erro na colocação das ordens OCO!"); //--- Limpa as propriedades Reset(); } }
O código do manipulador não é pequeno. Eu gostaria de dar ênfase a vários blocos.
O primeiro manipulador de todos os eventos do gráfico é dado ao diálogo principal.
Em seguida, estão os blocos de vários eventos de manipulação:
- Alterar as listas suspensas para definir um tipo de ordem;
- Edição dos campos de entrada para preencher as propriedades das ordens;
- Clique no botão para geração de eventos ON_OCO;
- Resposta de evento ON_OCO: ordem de criação do par.
O EA não verifica se o preenchimento dos campos do painel está correto. É por isso que temos de verificar os valores por nós mesmos, caso contrário, o EA irá mostrar a colocação de ordens OCO com erro.
A necessidade de remover o par e fechar a ordem remanescente é verificada no corpo do manipulador OnTrade().
Conclusão
Eu tentei demonstrar as riquezas das classes da Biblioteca Padrão que podem ser utilizadas para a realização de algumas tarefas específicas.
Particularmente, nós estamos lidando com um problema de manipulação de ordens OCO. Eu espero que o código do EA com o Painel para manipulação de ordens OCO será um ponto de partida para a criação dos pares de ordem mais complicados.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/1582
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso