Biblioteca para desenvolvimento fácil e rápido de programas MetaTrader (parte VII): Eventos de ativação da ordem StopLimit, preparação da funcionalidade para os eventos de modificação de ordens e posições
Artyom Trishkin | 14 agosto, 2019
Conteúdo
Conceito
Nas partes anteriores dedicadas à biblioteca multi-plataforma para a MetaTrader 5 e MetaTrader 4, nós desenvolvemos ferramentas para a criação de funções de caso de usuário, permitindo o acesso rápido de programas até quaisquer dados de ordem e posições em contas hedging e netting. Estas são as funções para o monitoramento de eventos que ocorrem nas ordens e posições — colocação, remoção e ativação das ordens pendentes, bem como a abertura e encerramento de posições.
No entanto, a funcionalidade para o monitoramento da ativação das ordens StopLimit, que já foram colocadas e a modificação das ordens e posições de mercado ainda não estão implementadas.
Neste artigo, nós implementaremos o monitoramento da ordem StopLimit que leva ao evento de colocação de uma ordem Limit.
A biblioteca monitorará esses eventos e enviará as mensagens necessárias para o programa, para que os eventos possam ser usados posteriormente.
Implementação
Ao testar a ativação das ordens StopLimit, eu notei que esse evento não é refletido no histórico da conta, significando que ele não pode ser simplesmente obtido do histórico da conta "do jeito que está". Portanto, nós precisamos monitorar o estado das ordens existentes até o momento em que ele é alterado (no nosso caso, isso significa alterar o tipo da ordem colocada com o mesmo ticket).
Eu vou abordar a implementação do monitoramento da ativação das ordem StopLimit a partir de uma perspectiva prática. Além de desenvolver a funcionalidade necessária, eu deixarei ela monitorar outros eventos por meio das alterações das ordens e posições existentes (alterando o preço das ordens pendentes existentes, seus níveis de StopLoss e TakeProfit, bem como os mesmos níveis pertencentes as posições em aberto).
A lógica da funcionalidade preparada é a seguinte:
Nós temos acesso à lista completa de todas as ordens e posições ativas na conta. A lista também nos permite obter o estado atual de cada uma das propriedades do objeto. Para monitorar as alterações das propriedades monitoradas, nós precisamos ter uma lista adicional contendo o estado "passado" das propriedades, que inicialmente será igual ao atual.
Ao comparar as propriedades dos objetos dessas duas listas, uma propriedade é considerada alterada assim que for detectado uma diferença em qualquer uma das propriedades monitoradas. Nesse caso, um objeto "alterado" é criado imediatamente. Tanto as propriedades do passado quanto a alterada são gravadas nela e o objeto é colocado na nova lista — "a lista de objetos alterados".
Essa lista deve ser tratada na classe que monitora os eventos da conta.
Naturalmente, nós podemos enviar um evento imediatamente após a detecção das alterações nas propriedades do objeto, mas podemos ter uma situação em que vários objetos são alterados em um tick. Se nós lidarmos com as alterações imediatamente, nós podemos lidar com a alteração apenas do último objeto do pacote, o que é inaceitável. Isso significa que nós devemos criar a lista de todos os objetos alterados e verificar o tamanho da lista na classe do manipulador de eventos. Cada objeto alterado da lista de objetos alterados é tratado em um loop. Isso nos impede de perder algumas das alterações ocorridas simultaneamente nas propriedades de ordem e posição.
Ao criar uma coleção de ordens e posições de mercado na terceira parte da descrição da biblioteca, nós decidimos atualizar a lista e armazenar a soma hash atual e anterior calculada como sendo a alteração do horário do ticket+posição em milissegundos e o volume. Isso nos permite acompanhar constantemente o estado atual de ordens e posições. No entanto, para monitorar as alterações nas propriedades de ordem e posição, esses dados são insuficientes para o cálculo da soma de hash.
- Nós precisamos considerar esse preço para levar em conta as alterações nos preços da ordem
- Nós precisamos considerar também esses preços para levar em consideração as alterações de preço do StopLoss e do TakeProfit.
Isso significa que nós adicionamos esses três preços à soma hash, mas cada um dos preços é convertido em um número do tipo ulong de sete dígitos pela remoção de um ponto decimal e aumentando a capacidade numérica por uma única ordem (para considerar as cotações de seis dígitos). Por exemplo, se o preço for 1.12345, o valor da soma hash é 1123450.
Vamos começar a implementação.
Adicione as enumerações com as flags possível de posição e as opções de alteração da ordem junto com as próprias opções que devem ser monitoradas para o arquivo Define.mqh:
//+------------------------------------------------------------------+ //| List of flags of possible order and position change options | //+------------------------------------------------------------------+ enum ENUM_CHANGE_TYPE_FLAGS { CHANGE_TYPE_FLAG_NO_CHANGE = 0, // No changes CHANGE_TYPE_FLAG_TYPE = 1, // Order type change CHANGE_TYPE_FLAG_PRICE = 2, // Price change CHANGE_TYPE_FLAG_STOP = 4, // StopLoss change CHANGE_TYPE_FLAG_TAKE = 8, // TakeProfit change CHANGE_TYPE_FLAG_ORDER = 16 // Order properties change flag }; //+------------------------------------------------------------------+ //| Possible order and position change options | //+------------------------------------------------------------------+ enum ENUM_CHANGE_TYPE { CHANGE_TYPE_NO_CHANGE, // No changes CHANGE_TYPE_ORDER_TYPE, // Order type change CHANGE_TYPE_ORDER_PRICE, // Order price change CHANGE_TYPE_ORDER_PRICE_STOP_LOSS, // Order and StopLoss price change CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT, // Order and TakeProfit price change CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT, // Order, StopLoss and TakeProfit price change CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT, // StopLoss and TakeProfit change CHANGE_TYPE_ORDER_STOP_LOSS, // Order's StopLoss change CHANGE_TYPE_ORDER_TAKE_PROFIT, // Order's TakeProfit change CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT, // Change position's StopLoss and TakeProfit CHANGE_TYPE_POSITION_STOP_LOSS, // Change position's StopLoss CHANGE_TYPE_POSITION_TAKE_PROFIT, // Change position's TakeProfit }; //+------------------------------------------------------------------+
Quanto as flags de possíveis opções de alteração da propriedade de ordem e posição:
- a flag de alteração do tipo da ordem é definida ao ativar uma ordem StopLimit,
- a flag de alteração do preço é colocada quando há a alteração do preço da ordem pendente,
- as flags de alteração do stop loss e take profit são auto-explicativas,
- a flag de ordem é usada para identificar uma alteração da propriedade da ordem (não a posição)
A enumeração de todas as opções possíveis de alteração da ordem e posição apresenta todas as opções que nós devemos rastrear no futuro. Neste artigo, nós vamos implementar o monitoramento de apenas um evento de ativação da ordem StopLimit (CHANGE_TYPE_ORDER_TYPE).
Adicionar oito novos eventos (a ser enviado ao programa durante a identificação) para a enumeração ENUM_TRADE_EVENT da lista de possíveis eventos de negociação da conta:
//+------------------------------------------------------------------+ //| List of possible trading events on the account | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT { TRADE_EVENT_NO_EVENT = 0, // No trading event TRADE_EVENT_PENDING_ORDER_PLASED, // Pending order placed TRADE_EVENT_PENDING_ORDER_REMOVED, // Pending order removed //--- enumeration members matching the ENUM_DEAL_TYPE enumeration members //--- (constant order below should not be changed, no constants should be added/deleted) TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT, // Accruing credit (3) TRADE_EVENT_ACCOUNT_CHARGE, // Additional charges TRADE_EVENT_ACCOUNT_CORRECTION, // Correcting entry TRADE_EVENT_ACCOUNT_BONUS, // Accruing bonuses TRADE_EVENT_ACCOUNT_COMISSION, // Additional commissions TRADE_EVENT_ACCOUNT_COMISSION_DAILY, // Commission charged at the end of a trading day TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY, // Commission charged at the end of a trading month TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY, // Agent commission charged at the end of a trading day TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY, // Agent commission charged at the end of a month TRADE_EVENT_ACCOUNT_INTEREST, // Accrued interest on free funds TRADE_EVENT_BUY_CANCELLED, // Canceled buy deal TRADE_EVENT_SELL_CANCELLED, // Canceled sell deal TRADE_EVENT_DIVIDENT, // Accruing dividends TRADE_EVENT_DIVIDENT_FRANKED, // Accruing franked dividends TRADE_EVENT_TAX = DEAL_TAX, // Tax //--- constants related to the DEAL_TYPE_BALANCE deal type from the DEAL_TYPE_BALANCE enumeration TRADE_EVENT_ACCOUNT_BALANCE_REFILL = DEAL_TAX+1, // Replenishing account balance TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL = DEAL_TAX+2, // Withdrawing funds from an account //--- Remaining possible trading events //--- (constant order below can be changed, constants can be added/deleted) TRADE_EVENT_PENDING_ORDER_ACTIVATED = DEAL_TAX+3, // Pending order activated by price TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL, // Pending order partially activated by price TRADE_EVENT_POSITION_OPENED, // Position opened TRADE_EVENT_POSITION_OPENED_PARTIAL, // Position opened partially TRADE_EVENT_POSITION_CLOSED, // Position closed TRADE_EVENT_POSITION_CLOSED_BY_POS, // Position closed partially TRADE_EVENT_POSITION_CLOSED_BY_SL, // Position closed by StopLoss TRADE_EVENT_POSITION_CLOSED_BY_TP, // Position closed by TakeProfit TRADE_EVENT_POSITION_REVERSED_BY_MARKET, // Position reversal by a new deal (netting) TRADE_EVENT_POSITION_REVERSED_BY_PENDING, // Position reversal by activating a pending order (netting) TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL, // Position reversal by partial market order execution (netting) TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL, // Position reversal by partial pending order activation (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET, // Added volume to a position by a new deal (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL, // Added volume to a position by partial activation of an order (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING, // Added volume to a position by activating a pending order (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL, // Added volume to a position by partial activation of a pending order (netting) TRADE_EVENT_POSITION_CLOSED_PARTIAL, // Position closed partially TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS, // Position closed partially by an opposite one TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL, // Position closed partially by StopLoss TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP, // Position closed partially by TakeProfit TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER, // StopLimit order activation TRADE_EVENT_MODIFY_ORDER_PRICE, // Changing order price TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS, // Changing order and StopLoss price TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT, // Changing order and TakeProfit price TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT, // Changing order, StopLoss and TakeProfit price TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT, // Changing order's StopLoss and TakeProfit price TRADE_EVENT_MODIFY_POSITION_STOP_LOSS, // Changing position StopLoss TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT, // Changing position TakeProfit };
Finalmente, adicionamos a nova constante descrevendo a ativação da ordem StopLimit à lista ENUM_EVENT_REASON de enumerações do motivo do evento:
//+------------------------------------------------------------------+ //| Event reason | //+------------------------------------------------------------------+ enum ENUM_EVENT_REASON { EVENT_REASON_REVERSE, // Position reversal (netting) EVENT_REASON_REVERSE_PARTIALLY, // Position reversal by partial request execution (netting) EVENT_REASON_REVERSE_BY_PENDING, // Position reversal by pending order activation (netting) EVENT_REASON_REVERSE_BY_PENDING_PARTIALLY, // Position reversal in case of a pending order partial execution (netting) //--- All constants related to a position reversal should be located in the above list EVENT_REASON_ACTIVATED_PENDING, // Pending order activation EVENT_REASON_ACTIVATED_PENDING_PARTIALLY, // Pending order partial activation EVENT_REASON_STOPLIMIT_TRIGGERED, // StopLimit order activation EVENT_REASON_CANCEL, // Cancelation EVENT_REASON_EXPIRED, // Order expiration EVENT_REASON_DONE, // Request executed in full EVENT_REASON_DONE_PARTIALLY, // Request executed partially EVENT_REASON_VOLUME_ADD, // Add volume to a position (netting) EVENT_REASON_VOLUME_ADD_PARTIALLY, // Add volume to a position by a partial request execution (netting) EVENT_REASON_VOLUME_ADD_BY_PENDING, // Add volume to a position when a pending order is activated (netting) EVENT_REASON_VOLUME_ADD_BY_PENDING_PARTIALLY, // Add volume to a position when a pending order is partially executed (netting) EVENT_REASON_DONE_SL, // Closing by StopLoss EVENT_REASON_DONE_SL_PARTIALLY, // Partial closing by StopLoss EVENT_REASON_DONE_TP, // Closing by TakeProfit EVENT_REASON_DONE_TP_PARTIALLY, // Partial closing by TakeProfit EVENT_REASON_DONE_BY_POS, // Closing by an opposite position EVENT_REASON_DONE_PARTIALLY_BY_POS, // Partial closing by an opposite position EVENT_REASON_DONE_BY_POS_PARTIALLY, // Closing an opposite position by a partial volume EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY, // Partial closing of an opposite position by a partial volume //--- Constants related to DEAL_TYPE_BALANCE deal type from the ENUM_DEAL_TYPE enumeration EVENT_REASON_BALANCE_REFILL, // Refilling the balance EVENT_REASON_BALANCE_WITHDRAWAL, // Withdrawing funds from the account //--- List of constants is relevant to TRADE_EVENT_ACCOUNT_CREDIT from the ENUM_TRADE_EVENT enumeration and shifted to +13 relative to ENUM_DEAL_TYPE (EVENT_REASON_ACCOUNT_CREDIT-3) EVENT_REASON_ACCOUNT_CREDIT, // Accruing credit EVENT_REASON_ACCOUNT_CHARGE, // Additional charges EVENT_REASON_ACCOUNT_CORRECTION, // Correcting entry EVENT_REASON_ACCOUNT_BONUS, // Accruing bonuses EVENT_REASON_ACCOUNT_COMISSION, // Additional commissions EVENT_REASON_ACCOUNT_COMISSION_DAILY, // Commission charged at the end of a trading day EVENT_REASON_ACCOUNT_COMISSION_MONTHLY, // Commission charged at the end of a trading month EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY, // Agent commission charged at the end of a trading day EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY, // Agent commission charged at the end of a month EVENT_REASON_ACCOUNT_INTEREST, // Accruing interest on free funds EVENT_REASON_BUY_CANCELLED, // Canceled buy deal EVENT_REASON_SELL_CANCELLED, // Canceled sell deal EVENT_REASON_DIVIDENT, // Accruing dividends EVENT_REASON_DIVIDENT_FRANKED, // Accruing franked dividends EVENT_REASON_TAX // Tax }; #define REASON_EVENT_SHIFT (EVENT_REASON_ACCOUNT_CREDIT-3)
Nós fizemos todas as alterações no arquivo Defines.mqh.
Como nós decidimos criar e armazenar a lista de controle das ordens, essa lista deve armazenar os objetos com um conjunto minimamente suficiente de propriedades para definir o momento de alteração de um dos objetos de ordem e posição de mercado.
Vamos criar a classe objeto de controle da ordem.
Criamos a nova classe OrderControl.mqh na pasta da biblioteca Collections. Definimos a classe da biblioteca padrão CObject como base e incluímos os arquivos necessários para o funcionamento da classe:
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: public: COrderControl(); ~COrderControl(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ COrderControl::COrderControl() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ COrderControl::~COrderControl() { } //+------------------------------------------------------------------+
Declaramos diretamente todas as variáveis e métodos necessários na seção privada da classe:
private: ENUM_CHANGE_TYPE m_changed_type; // Order change type MqlTick m_tick; // Tick structure string m_symbol; // Symbol ulong m_position_id; // Position ID ulong m_ticket; // Order ticket long m_magic; // Magic number ulong m_type_order; // Order type ulong m_type_order_prev; // Previous order type double m_price; // Order price double m_price_prev; // Previous order price double m_stop; // StopLoss price double m_stop_prev; // Previous StopLoss price double m_take; // TakeProfit price double m_take_prev; // Previous TakeProfit price double m_volume; // Order volume datetime m_time; // Order placement time datetime m_time_prev; // Order previous placement time int m_change_code; // Order change code //--- return the presence of the property change flag bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Return the order parameters change type void CalculateChangedType(void);
Todas as variáveis membro da turma possuem descrições claras. Eu deveria fazer um esclarecimento sobre a variável que armazena a estrutura do tick: quando uma ordem StopLimit é ativada, nós precisamos salvar o horário da ativação. A hora deve ser definida em milissegundos, porém, a função TimeCurrent() retorna o tempo sem os milissegundos. Para obter o horário do último tick que uma ordem foi ativada com os milissegundos, nós usaremos a função padrão SymbolInfoTick() preenchendo a estrutura de ticks com os dados, incluindo o tempo do tick em milissegundos.
O código de alteração da ordem é composta pelas flags que nós descrevemos na enumeração ENUM_CHANGE_TYPE_FLAGS e depende das alterações ocorridas na propriedade da ordem. O método privado CalculateChangedType() descrito abaixo verifica as flags e cria o código de modificação da ordem.
Na seção pública da classe, organizamos os métodos para receber e gravar os dados sobre o estado anterior e atual das propriedades da ordem de controle. o método definindo o tipo de alteração ocorrido na propriedade da ordem , o método definindo o novo estado de uma ordem alterada, o método retornando o tipo de alteração ocorrida e o método verificando a alteração das propriedades da ordem, bem como o que define e retorna o tipo de alteração ocorrido. O método é chamado da classe de coleções de ordens e posições de mercado para detectar a alteração das ordens e posições ativas.
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| Order and position control class | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: ENUM_CHANGE_TYPE m_changed_type; // Order change type MqlTick m_tick; // Tick structure string m_symbol; // Symbol ulong m_position_id; // Position ID ulong m_ticket; // Order ticket long m_magic; // Magic number ulong m_type_order; // Order type ulong m_type_order_prev; // Previous order type double m_price; // Order price double m_price_prev; // Previous order price double m_stop; // StopLoss price double m_stop_prev; // Previous StopLoss price double m_take; // TakeProfit price double m_take_prev; // Previous TakeProfit price double m_volume; // Order volume datetime m_time; // Order placement time datetime m_time_prev; // Order previous placement time int m_change_code; // Order change code //--- return the presence of the property change flag bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Calculate the order parameters change type void CalculateChangedType(void); public: //--- Set the (1,2) current and previous type (2,3) current and previous price, (4,5) current and previous StopLoss, //--- (6,7) current and previous TakeProfit, (8,9) current and previous placement time, (10) volume void SetTypeOrder(const ulong type) { this.m_type_order=type; } void SetTypeOrderPrev(const ulong type) { this.m_type_order_prev=type; } void SetPrice(const double price) { this.m_price=price; } void SetPricePrev(const double price) { this.m_price_prev=price; } void SetStopLoss(const double stop_loss) { this.m_stop=stop_loss; } void SetStopLossPrev(const double stop_loss) { this.m_stop_prev=stop_loss; } void SetTakeProfit(const double take_profit) { this.m_take=take_profit; } void SetTakeProfitPrev(const double take_profit) { this.m_take_prev=take_profit; } void SetTime(const datetime time) { this.m_time=time; } void SetTimePrev(const datetime time) { this.m_time_prev=time; } void SetVolume(const double volume) { this.m_volume=volume; } //--- Set (1) change type, (2) new current status void SetChangedType(const ENUM_CHANGE_TYPE type) { this.m_changed_type=type; } void SetNewState(COrder* order); //--- Check and set order parameters change flags and return the change type ENUM_CHANGE_TYPE ChangeControl(COrder* compared_order); //--- Return (1,2,3,4) position ID, ticket, magic and symbol, (5,6) current and previous type (7,8) current and previous price, //--- (9,10) current and previous StopLoss, (11,12) current and previous TakeProfit, (13,14) current and previous placement time, (15) volume ulong PositionID(void) const { return this.m_position_id; } ulong Ticket(void) const { return this.m_ticket; } long Magic(void) const { return this.m_magic; } string Symbol(void) const { return this.m_symbol; } ulong TypeOrder(void) const { return this.m_type_order; } ulong TypeOrderPrev(void) const { return this.m_type_order_prev; } double Price(void) const { return this.m_price; } double PricePrev(void) const { return this.m_price_prev; } double StopLoss(void) const { return this.m_stop; } double StopLossPrev(void) const { return this.m_stop_prev; } double TakeProfit(void) const { return this.m_take; } double TakeProfitPrev(void) const { return this.m_take_prev; } ulong Time(void) const { return this.m_time; } ulong TimePrev(void) const { return this.m_time_prev; } double Volume(void) const { return this.m_volume; } //--- Return the change type ENUM_CHANGE_TYPE GetChangeType(void) const { return this.m_changed_type; } //--- Constructor COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) : m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE), m_changed_type(CHANGE_TYPE_NO_CHANGE), m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;} }; //+------------------------------------------------------------------+
O construtor da classe recebe o ID da posição, o ticket, o número mágico e a ordem/posição do símbolo. Na sua lista de inicialização, redefinimos de forma imediata as flags de alteração da ordem e o tipo da alteração, bem como a gravação de dados da ordem/posição obtidos nos parâmetros passados para as variáveis membro da classe correspondentes.
Implementamos os métodos declarados fora do corpo da classe.
O método privado que calcula o tipo de alteração do parâmetro da ordem/posição:
//+------------------------------------------------------------------+ //| Calculate order parameters change type | //+------------------------------------------------------------------+ void COrderControl::CalculateChangedType(void) { this.m_changed_type= ( //--- If the order flag is set this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? ( //--- If StopLimit order is activated this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE) ? CHANGE_TYPE_ORDER_TYPE : //--- If an order price is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE) ? ( //--- If StopLoss and TakeProfit are modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : //--- If StopLoss modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS : //--- Only order price is modified CHANGE_TYPE_ORDER_PRICE ) : //--- Price is not modified //--- If StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : //--- If StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ) : //--- Position //--- If position's StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT : //--- If position's TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : //--- If position's StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ); } //+------------------------------------------------------------------+
O método grava o tipo da alteração ocorrida da enumeração declarada anteriormente ENUM_CHANGE_TYPE para a variável membro de classe m_changed_type, dependendo da presença das flags dentro da variável m_change_code.
Todas as ações relacionadas à verificação das flags são descritas nos comentários das linhas dos métodos listados e devem ser fáceis de entender.
O método privado verifica a presença da flag dentro da variável m_change_code
bool IsPresentChangeFlag(const int change_flag const { return (this.m_change_code & change_flag)==change_flag }
O método recebe a flag marcada. Sua presença dentro de m_change_code é verificada através da operação AND bit a bit, retornando o resultado booleano da comparação (operação bit a bit entre o código e os valores da flag) com o valor da flag marcada.
O método retornando um novo estado relevante das propriedades de ordem/posição:
//+------------------------------------------------------------------+ //| Set the new relevant status | //+------------------------------------------------------------------+ void COrderControl::SetNewState(COrder* order) { if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick)) return; //--- New type this.SetTypeOrderPrev(this.TypeOrder()); this.SetTypeOrder(order.TypeOrder()); //--- New price this.SetPricePrev(this.Price()); this.SetPrice(order.PriceOpen()); //--- New StopLoss this.SetStopLossPrev(this.StopLoss()); this.SetStopLoss(order.StopLoss()); //--- New TakeProfit this.SetTakeProfitPrev(this.TakeProfit()); this.SetTakeProfit(order.TakeProfit()); //--- New time this.SetTimePrev(this.Time()); this.SetTime(this.m_tick.time_msc); } //+------------------------------------------------------------------+
O ponteiro para a ordem/posição, na qual ocorreu uma alteração de uma das propriedades, é passado para o método.
Assim que uma alteração de uma das propriedades de ordem/posição é detectada, nós precisamos salvar o novo estado para as verificações adicionais, o método salva primeiro o estado de sua propriedade atual como sendo a anterior e grava o valor da propriedade da ordem passada para o método como sendo o seu estado atual.
Ao salvar o horário de um evento ocorrido, use a função padrão SymbolInfoTick() para receber o horário do tick em milissegundos.
O método principal chamado da classe CMarketCollection, responsável pela definição das alterações ocorridas:
//+------------------------------------------------------------------+ //| Check and set order parameters change flags | //+------------------------------------------------------------------+ ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order) { this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE; if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket) return CHANGE_TYPE_NO_CHANGE; if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING) this.m_change_code+=CHANGE_TYPE_FLAG_ORDER; if(compared_order.TypeOrder()!=this.m_type_order) this.m_change_code+=CHANGE_TYPE_FLAG_TYPE; if(compared_order.PriceOpen()!=this.m_price) this.m_change_code+=CHANGE_TYPE_FLAG_PRICE; if(compared_order.StopLoss()!=this.m_stop) this.m_change_code+=CHANGE_TYPE_FLAG_STOP; if(compared_order.TakeProfit()!=this.m_take) this.m_change_code+=CHANGE_TYPE_FLAG_TAKE; this.CalculateChangedType(); return this.GetChangeType(); } //+------------------------------------------------------------------+
O método recebe o ponteiro para a ordem/posição verificada e inicializa o código de alteração. Se um objeto vazio de uma ordem comparada for passada ou o seu ticket não for igual ao ticket da ordem de controle atual, é retornado o código de ausência de alteração.
Em seguida, verificamos todas as propriedades monitoradas de ordens de controle e verificadas. Se uma incompatibilidade for encontrada, será adicionado ao código de alteração a flag necessária descrevendo essa alteração.
Em seguida, o tipo de alteração é calculado pelo código de alteração totalmente formado no método CalculateChangedType() e é retornado para o programa de chamada usando o método GetChangeType().
A listagem completa da classe de ordem de controle:
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| Order and position control class | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: ENUM_CHANGE_TYPE m_changed_type; // Order change type MqlTick m_tick; // Tick structure string m_symbol; // Symbol ulong m_position_id; // Position ID ulong m_ticket; // Order ticket long m_magic; // Magic number ulong m_type_order; // Order type ulong m_type_order_prev; // Previous order type double m_price; // Order price double m_price_prev; // Previous order price double m_stop; // StopLoss price double m_stop_prev; // Previous StopLoss price double m_take; // TakeProfit price double m_take_prev; // Previous TakeProfit price double m_volume; // Order volume datetime m_time; // Order placement time datetime m_time_prev; // Order previous placement time int m_change_code; // Order change code //--- return the presence of the property change flag bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Calculate the order parameters change type void CalculateChangedType(void); public: //--- Set the (1,2) current and previous type (2,3) current and previous price, (4,5) current and previous StopLoss, //--- (6,7) current and previous TakeProfit, (8,9) current and previous placement time, (10) volume void SetTypeOrder(const ulong type) { this.m_type_order=type; } void SetTypeOrderPrev(const ulong type) { this.m_type_order_prev=type; } void SetPrice(const double price) { this.m_price=price; } void SetPricePrev(const double price) { this.m_price_prev=price; } void SetStopLoss(const double stop_loss) { this.m_stop=stop_loss; } void SetStopLossPrev(const double stop_loss) { this.m_stop_prev=stop_loss; } void SetTakeProfit(const double take_profit) { this.m_take=take_profit; } void SetTakeProfitPrev(const double take_profit) { this.m_take_prev=take_profit; } void SetTime(const datetime time) { this.m_time=time; } void SetTimePrev(const datetime time) { this.m_time_prev=time; } void SetVolume(const double volume) { this.m_volume=volume; } //--- Set (1) change type, (2) new current status void SetChangedType(const ENUM_CHANGE_TYPE type) { this.m_changed_type=type; } void SetNewState(COrder* order); //--- Check and set order parameters change flags and return the change type ENUM_CHANGE_TYPE ChangeControl(COrder* compared_order); //--- Return (1,2,3,4) position ID, ticket, magic and symbol, (5,6) current and previous type (7,8) current and previous price, //--- (9,10) current and previous StopLoss, (11,12) current and previous TakeProfit, (13,14) current and previous placement time, (15) volume ulong PositionID(void) const { return this.m_position_id; } ulong Ticket(void) const { return this.m_ticket; } long Magic(void) const { return this.m_magic; } string Symbol(void) const { return this.m_symbol; } ulong TypeOrder(void) const { return this.m_type_order; } ulong TypeOrderPrev(void) const { return this.m_type_order_prev; } double Price(void) const { return this.m_price; } double PricePrev(void) const { return this.m_price_prev; } double StopLoss(void) const { return this.m_stop; } double StopLossPrev(void) const { return this.m_stop_prev; } double TakeProfit(void) const { return this.m_take; } double TakeProfitPrev(void) const { return this.m_take_prev; } ulong Time(void) const { return this.m_time; } ulong TimePrev(void) const { return this.m_time_prev; } double Volume(void) const { return this.m_volume; } //--- Return the change type ENUM_CHANGE_TYPE GetChangeType(void) const { return this.m_changed_type; } //--- Constructor COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) : m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE), m_changed_type(CHANGE_TYPE_NO_CHANGE), m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;} }; //+------------------------------------------------------------------+ //| Check and set the order parameters change flags | //+------------------------------------------------------------------+ ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order) { this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE; if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket) return CHANGE_TYPE_NO_CHANGE; if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING) this.m_change_code+=CHANGE_TYPE_FLAG_ORDER; if(compared_order.TypeOrder()!=this.m_type_order) this.m_change_code+=CHANGE_TYPE_FLAG_TYPE; if(compared_order.PriceOpen()!=this.m_price) this.m_change_code+=CHANGE_TYPE_FLAG_PRICE; if(compared_order.StopLoss()!=this.m_stop) this.m_change_code+=CHANGE_TYPE_FLAG_STOP; if(compared_order.TakeProfit()!=this.m_take) this.m_change_code+=CHANGE_TYPE_FLAG_TAKE; this.CalculateChangedType(); return this.GetChangeType(); } //+------------------------------------------------------------------+ //| Calculate the order parameters change type | //+------------------------------------------------------------------+ void COrderControl::CalculateChangedType(void) { this.m_changed_type= ( //--- If the order flag is set this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? ( //--- If StopLimit order is activated this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE) ? CHANGE_TYPE_ORDER_TYPE : //--- If an order price is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE) ? ( //--- If StopLoss and TakeProfit are modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : //--- If StopLoss modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS : //--- Only order price is modified CHANGE_TYPE_ORDER_PRICE ) : //--- Price is not modified //--- If StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : //--- If StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ) : //--- Position //--- If position's StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT : //--- If position's TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : //--- If position's StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ); } //+------------------------------------------------------------------+ //| Set the new relevant status | //+------------------------------------------------------------------+ void COrderControl::SetNewState(COrder* order) { if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick)) return; //--- New type this.SetTypeOrderPrev(this.TypeOrder()); this.SetTypeOrder(order.TypeOrder()); //--- New price this.SetPricePrev(this.Price()); this.SetPrice(order.PriceOpen()); //--- New StopLoss this.SetStopLossPrev(this.StopLoss()); this.SetStopLoss(order.StopLoss()); //--- New TakeProfit this.SetTakeProfitPrev(this.TakeProfit()); this.SetTakeProfit(order.TakeProfit()); //--- New time this.SetTimePrev(this.Time()); this.SetTime(this.m_tick.time_msc); } //+------------------------------------------------------------------+
Vamos melhorar a classe de coleções de ordens e posições de mercado CMarketCollection.
Nós precisamos acompanhar as alterações das propriedade ocorridas nas ordens e posições ativas. Como nós recebemos todas as ordens e posições de mercado nesta classe, seria razoável verificar também a sua modificação.
Incluímos o arquivo da classe da ordem de controle. Na seção privada da classe, declaramos a lista para armazenar as ordens e posições de controle, a lista para armazenar as ordens e posições alteradas, a variável membro de classe para armazenar o tipo de alteração da ordem e a variável para armazenar a taxa para converter o preço na soma hash.
Além disso, declaramos os métodos privados:
o método para converter as propriedades da ordem em uma soma hash, o método para adicionar um ordem ou uma posição à lista de ordens pendentes e posições na conta, o método para criação e adição de uma ordem de controle à lista de ordens de controle e o método para criar e adicionar uma ordem alterada à lista de ordens alteradas, o método para remover uma ordem da lista de ordens de controle por um ticket e um ID da posição, o método retornando o índice da ordem de controle na lista de ordens de controle por um ticket e ID da posição e o manipulador de um evento de alteração de ordem/posição existente.
Na seção pública da classe, declaramos o método retornando a lista de ordens alteradas criada.
//+------------------------------------------------------------------+ //| MarketCollection.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Orders\MarketOrder.mqh" #include "..\Objects\Orders\MarketPending.mqh" #include "..\Objects\Orders\MarketPosition.mqh" #include "OrderControl.mqh" //+------------------------------------------------------------------+ //| Collection of market orders and positions | //+------------------------------------------------------------------+ class CMarketCollection : public CListObj { private: struct MqlDataCollection { ulong hash_sum_acc; // Hash sum of all orders and positions on the account int total_market; // Number of market orders on the account int total_pending; // Number of pending orders on the account int total_positions; // Number of positions on the account double total_volumes; // Total volume of orders and positions on the account }; MqlDataCollection m_struct_curr_market; // Current data on market orders and positions on the account MqlDataCollection m_struct_prev_market; // Previous data on market orders and positions on the account CListObj m_list_all_orders; // List of pending orders and positions on the account CArrayObj m_list_control; // List of control orders CArrayObj m_list_changed; // List of changed orders COrder m_order_instance; // Order object for searching by property ENUM_CHANGE_TYPE m_change_type; // Order change type bool m_is_trade_event; // Trading event flag bool m_is_change_volume; // Total volume change flag double m_change_volume_value; // Total volume change value ulong m_k_pow; // Ratio for converting the price into a hash sum int m_new_market_orders; // Number of new market orders int m_new_positions; // Number of new positions int m_new_pendings; // Number of new pending orders //--- Save the current values of the account data status as previous ones void SavePrevValues(void) { this.m_struct_prev_market=this.m_struct_curr_market; } //--- Convert order data into a hash sum value ulong ConvertToHS(COrder* order) const; //--- Add an order or a position to the list of pending orders and positions on an account and sets the data on market orders and positions on the account bool AddToListMarket(COrder* order); //--- (1) Create and add a control order to the list of control orders, (2) a control order to the list of changed control orders bool AddToListControl(COrder* order); bool AddToListChanges(COrderControl* order_control); //--- Remove an order by a ticket or a position ID from the list of control orders bool DeleteOrderFromListControl(const ulong ticket,const ulong id); //--- Return the control order index in the list by a position ticket and ID int IndexControlOrder(const ulong ticket,const ulong id); //--- Handler of an existing order/position change event void OnChangeEvent(COrder* order,const int index); public: //--- Return the list of (1) all pending orders and open positions, (2) modified orders and positions CArrayObj* GetList(void) { return &this.m_list_all_orders; } CArrayObj* GetListChanges(void) { return &this.m_list_changed; } //--- Return the list of orders and positions with an open time from begin_time to end_time CArrayObj* GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Return the list of orders and positions by selected (1) double, (2) integer and (3) string property fitting a compared condition CArrayObj* GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } //--- Return the number of (1) new market order, (2) new pending orders, (3) new positions, (4) occurred trading event flag, (5) changed volume int NewMarketOrders(void) const { return this.m_new_market_orders; } int NewPendingOrders(void) const { return this.m_new_pendings; } int NewPositions(void) const { return this.m_new_positions; } bool IsTradeEvent(void) const { return this.m_is_trade_event; } double ChangedVolumeValue(void) const { return this.m_change_volume_value; } //--- Constructor CMarketCollection(void); //--- Update the list of pending orders and positions void Refresh(void); }; //+------------------------------------------------------------------+
Adicionamos ao construtor da classe a limpeza e ordenação da lista de ordens de controle e a lista de ordens alteradas, assim como o cálculo da razão que define a soma hash para o construtor de classe:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0) { this.m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN); this.m_list_all_orders.Clear(); ::ZeroMemory(this.m_struct_prev_market); this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE; this.m_list_all_orders.Type(COLLECTION_MARKET_ID); this.m_list_control.Clear(); this.m_list_control.Sort(); this.m_list_changed.Clear(); this.m_list_changed.Sort(); this.m_k_pow=(ulong)pow(10,6); } //+------------------------------------------------------------------+
The method of converting order properties into the number for calculating the hash sum:
//+---------------------------------------------------------------------+ //| Convert the order price and its type into a number for the hash sum | //+---------------------------------------------------------------------+ ulong CMarketCollection::ConvertToHS(COrder *order) const { if(order==NULL) return 0; ulong price=ulong(order.PriceOpen()*this.m_k_pow); ulong stop=ulong(order.StopLoss()*this.m_k_pow); ulong take=ulong(order.TakeProfit()*this.m_k_pow); ulong type=order.TypeOrder(); ulong ticket=order.Ticket(); return price+stop+take+type+ticket; } //+------------------------------------------------------------------+
O método recebe um ponteiro para a ordem cujos dados devem ser convertidos em um número. Em seguida, as propriedades do tipo double da ordem são convertidas em um número para a soma hash por uma simples multiplicação pela razão previamente calculada no construtor da classe, todos os valores das propriedades são somadas e retornadas como um número ulong.
As alterações afetaram a adição de objetos à lista de ordens e posições. Agora, essas sequências do mesmo tipo estão localizadas no método único AddToListMarket(). Após declarar um objeto de ordem na lista de ordens e posições, a presença da mesma ordem é verificada na lista de ordens de controle. Se tal ordem estiver ausente, é criado um objeto de ordem de controle e adicionado à lista de ordens de controle usando o método AddToListControl(). Se a ordem de controle estiver presente, é chamado o método OnChangeEvent() para comparar as propriedades da ordem atual com a ordem de controle.
Todas as ações executadas são descritas nas linhas de comentários destacadas no texto da listagem dos métodos.
//+------------------------------------------------------------------+ //| Update the list of orders | //+------------------------------------------------------------------+ void CMarketCollection::Refresh(void) { ::ZeroMemory(this.m_struct_curr_market); this.m_is_trade_event=false; this.m_is_change_volume=false; this.m_new_pendings=0; this.m_new_positions=0; this.m_change_volume_value=0; this.m_list_all_orders.Clear(); #ifdef __MQL4__ int total=::OrdersTotal(); for(int i=0; i<total; i++) { if(!::OrderSelect(i,SELECT_BY_POS)) continue; long ticket=::OrderTicket(); //--- Get the control order index by a position ticket and ID int index=this.IndexControlOrder(ticket); ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType(); if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL) { CMarketPosition *position=new CMarketPosition(ticket); if(position==NULL) continue; //--- Add a position object to the list of market orders and positions if(!this.AddToListMarket(position)) continue; //--- If there is no order in the list of control orders and positions, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket()); } } //--- If the order is already present in the list of control orders, check it for changed properties if(index>WRONG_VALUE) { this.OnChangeEvent(position,index); } } else { CMarketPending *order=new CMarketPending(ticket); if(order==NULL) continue; //--- Add a pending order object to the list of market orders and positions if(!this.AddToListMarket(order)) continue; //--- If there is no order in the list of control orders and positions, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket()); } } //--- If the order is already present in the list of control orders, check it for changed properties if(index>WRONG_VALUE) { this.OnChangeEvent(order,index); } } } //--- MQ5 #else //--- Positions int total_positions=::PositionsTotal(); for(int i=0; i<total_positions; i++) { ulong ticket=::PositionGetTicket(i); if(ticket==0) continue; CMarketPosition *position=new CMarketPosition(ticket); if(position==NULL) continue; //--- Add a position object to the list of market orders and positions if(!this.AddToListMarket(position)) continue; //--- Get the control order index by a position ticket and ID int index=this.IndexControlOrder(ticket,position.PositionID()); //--- If the order is not present in the list of control orders, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(position)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольую позицию ","Failed to add control position "),position.TypeDescription()," #",position.Ticket()); } } //--- If the order is already present in the list of control orders, check it for changed properties else if(index>WRONG_VALUE) { this.OnChangeEvent(position,index); } } //--- Orders int total_orders=::OrdersTotal(); for(int i=0; i<total_orders; i++) { ulong ticket=::OrderGetTicket(i); if(ticket==0) continue; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE); //--- Market order if(type<ORDER_TYPE_BUY_LIMIT) { CMarketOrder *order=new CMarketOrder(ticket); if(order==NULL) continue; //--- Add a market order object to the list of market orders and positions if(!this.AddToListMarket(order)) continue; } //--- Pending order else { CMarketPending *order=new CMarketPending(ticket); if(order==NULL) continue; //--- Add a pending order object to the list of market orders and positions if(!this.AddToListMarket(order)) continue; //--- Get the control order index by a position ticket and ID int index=this.IndexControlOrder(ticket,order.PositionID()); //--- If the order is not present in the control order list, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket()); } } //--- If the order is already in the control order list, check it for changed properties else if(index>WRONG_VALUE) { this.OnChangeEvent(order,index); } } } #endif //--- First launch if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE) { this.SavePrevValues(); } //--- Se a soma hash de todas as ordens e posições mudou if(this.m_struct_curr_market.hash_sum_acc!=this.m_struct_prev_market.hash_sum_acc) { this.m_new_market_orders=this.m_struct_curr_market.total_market-this.m_struct_prev_market.total_market; this.m_new_pendings=this.m_struct_curr_market.total_pending-this.m_struct_prev_market.total_pending; this.m_new_positions=this.m_struct_curr_market.total_positions-this.m_struct_prev_market.total_positions; this.m_change_volume_value=::NormalizeDouble(this.m_struct_curr_market.total_volumes-this.m_struct_prev_market.total_volumes,4); this.m_is_change_volume=(this.m_change_volume_value!=0 ? true : false); this.m_is_trade_event=true; this.SavePrevValues(); } } //+------------------------------------------------------------------+
O método que adiciona as ordens e posições à lista de coleção de ordens e posições de mercado:
//+--------------------------------------------------------------------------------+ //| Add an order or a position to the list of orders and positions on the account | //+--------------------------------------------------------------------------------+ bool CMarketCollection::AddToListMarket(COrder *order) { if(order==NULL) return false; ENUM_ORDER_STATUS status=order.Status(); if(this.m_list_all_orders.InsertSort(order)) { if(status==ORDER_STATUS_MARKET_POSITION) { this.m_struct_curr_market.hash_sum_acc+=order.GetProperty(ORDER_PROP_TIME_UPDATE_MSC)+this.ConvertToHS(order); this.m_struct_curr_market.total_volumes+=order.Volume(); this.m_struct_curr_market.total_positions++; return true; } if(status==ORDER_STATUS_MARKET_PENDING) { this.m_struct_curr_market.hash_sum_acc+=this.ConvertToHS(order); this.m_struct_curr_market.total_volumes+=order.Volume(); this.m_struct_curr_market.total_pending++; return true; } } else { ::Print(DFUN,order.TypeDescription()," #",order.Ticket()," ",TextByLanguage("не удалось добавить в список","failed to add to list")); delete order; } return false; } //+------------------------------------------------------------------+
O ponteiro para a ordem adicionada à lista de coleção é passado para o método. Depois de adicionar uma ordem à lista de coleção, os dados da estrutura, que armazena o estado atual de ordens e posições de mercado para uma verificação subsequente e que define as alterações no número de ordens e posições, são alteradas dependendo do estado da ordem.
- Se esta é uma posição, o horário de alteração da posição e o valor calculado para a soma hash são adicionados à soma hash geral e o número total da posição é aumentado.
- Se esta é uma ordem pendente, o valor calculado para a soma hash é adicionado à soma hash geral e o número total de ordens pendentes é aumentado.
O método para criar uma ordem de controle e adicioná-la à lista de ordens de controle:
//+------------------------------------------------------------------+ //| Create and add an order to the list of control orders | //+------------------------------------------------------------------+ bool CMarketCollection::AddToListControl(COrder *order) { if(order==NULL) return false; COrderControl* order_control=new COrderControl(order.PositionID(),order.Ticket(),order.Magic(),order.Symbol()); if(order_control==NULL) return false; order_control.SetTime(order.TimeOpenMSC()); order_control.SetTimePrev(order.TimeOpenMSC()); order_control.SetVolume(order.Volume()); order_control.SetTime(order.TimeOpenMSC()); order_control.SetTypeOrder(order.TypeOrder()); order_control.SetTypeOrderPrev(order.TypeOrder()); order_control.SetPrice(order.PriceOpen()); order_control.SetPricePrev(order.PriceOpen()); order_control.SetStopLoss(order.StopLoss()); order_control.SetStopLossPrev(order.StopLoss()); order_control.SetTakeProfit(order.TakeProfit()); order_control.SetTakeProfitPrev(order.TakeProfit()); if(!this.m_list_control.Add(order_control)) { delete order_control; return false; } return true; } //+------------------------------------------------------------------+
O ponteiro para uma ordem e posição de mercado é passado para o método. Se um objeto inválido for passado, ele retorna false.
Uma nova ordem de controle é então criada, para que o seu construtor receba imediatamente o ID da posição, o ticket, o número mágico e o símbolo de um objeto de ordem, que foi passada para o método. Todos os dados necessários para identificar as modificações da ordem/posição são então preenchidos.
Se a adição de uma nova ordem de controle à lista de ordens de controle falhar, a ordem será removida e será retornado 'false'.
Como nós sempre adicionamos novas ordens e posições à lista de ordens e posições de controle, isso pode se tornar muito volumoso após um longo tempo de funcionamento. Ordens e posições não vivem para sempre, e suas cópias de controle não devem ser permanentemente armazenadas na lista ocupando memória sem nenhum motivo. Para remover ordens de controle desnecessárias da lista, use o método DeleteOrderFromListControl(), que remove uma ordem de controle da lista de ordens de controle pelo ticket e ID da posição.
Por enquanto, o método apenas foi declarado, mas não implementado. A implementação será feita após a preparação de toda a funcionalidade para monitorar as modificações da ordem e posição.
O método que retorna o índice da ordem de controle na lista de ordens de controle pelo ticket e ID da posição:
//+------------------------------------------------------------------+ //| Return an order index by a ticket in the list of control orders | //+------------------------------------------------------------------+ int CMarketCollection::IndexControlOrder(const ulong ticket,const ulong id) { int total=this.m_list_control.Total(); for(int i=0;i<total;i++) { COrderControl* order=this.m_list_control.At(i); if(order==NULL) continue; if(order.PositionID()==id && order.Ticket()==ticket) return i; } return WRONG_VALUE; } //+------------------------------------------------------------------+
O método recebe um ticket da ordem/posição e um ID da posição. Uma ordem de controle com o ticket e ID correspondentes é buscada ao longo de todas as ordens de controle através de um loop, e o seu índice é retornado na lista de ordens de controle. Se a ordem não for encontrada, -1 é retornado.
Método do manipulador de eventos para alterar uma ordem/posição existente:
//+------------------------------------------------------------------+ //| Handler of changing an existing order/position | //+------------------------------------------------------------------+ void CMarketCollection::OnChangeEvent(COrder* order,const int index) { COrderControl* order_control=this.m_list_control.At(index); if(order_control!=NULL) { this.m_change_type=order_control.ChangeControl(order); ENUM_CHANGE_TYPE change_type=(order.Status()==ORDER_STATUS_MARKET_POSITION ? CHANGE_TYPE_ORDER_TAKE_PROFIT : CHANGE_TYPE_NO_CHANGE); if(this.m_change_type>change_type) { order_control.SetNewState(order); if(!this.AddToListChanges(order_control)) { ::Print(DFUN,TextByLanguage("Не удалось добавить модифицированный ордер в список изменённых ордеров","Could not add modified order to list of modified orders")); } } } } //+------------------------------------------------------------------+
O método recebe o ponteiro para a ordem verificada e o índice da ordem de controle apropriada na lista de ordens de controle.
Obtemos a ordem de controle da lista pelo seu índice e verificamos as alterações nas propriedades da ordem de controle correspondentes às propriedades da ordem de controle verificada usando o método ChangeControl(). O método recebe o ponteiro para a ordem de controle. Se a diferença for encontrada, o método retornará o tipo de alteração que foi gravada na variável membro de classe m_change_type.
Em seguida, verificamos o estado da ordem verificada e definimos o valor acima na qual a alteração foi considerada como ocorrida.. Para a posição, este valor deve exceder a constante CHANGE_TYPE_ORDER_TAKE_PROFIT da enumeração ENUM_CHANGE_TYPE, pois todos os valores iguais ou inferiores a essa constante estão relacionados apenas a uma ordem pendente. Para uma ordem pendente, o valor deve exceder a constante CHANGE_TYPE_NO_CHANGE.
Se a variável obtida m_change_type exceder aquela especificada, a modificação é detectada. Neste caso, o estado atual da ordem de controle é salvo para uma verificação subsequente e uma cópia da ordem de controle é colocada na lista de ordens alteradas para a manipulação subsequente da lista na classe CEventsCollection.
O método para criar uma ordem de controle alterada e adicioná-la à lista de ordens alteradas:
//+------------------------------------------------------------------+ //|Create and add a control order to the list of changed orders | //+------------------------------------------------------------------+ bool CMarketCollection::AddToListChanges(COrderControl* order_control) { if(order_control==NULL) return false; COrderControl* order_changed=new COrderControl(order_control.PositionID(),order_control.Ticket(),order_control.Magic(),order_control.Symbol()); if(order_changed==NULL) return false; order_changed.SetTime(order_control.Time()); order_changed.SetTimePrev(order_control.TimePrev()); order_changed.SetVolume(order_control.Volume()); order_changed.SetTypeOrder(order_control.TypeOrder()); order_changed.SetTypeOrderPrev(order_control.TypeOrderPrev()); order_changed.SetPrice(order_control.Price()); order_changed.SetPricePrev(order_control.PricePrev()); order_changed.SetStopLoss(order_control.StopLoss()); order_changed.SetStopLossPrev(order_control.StopLossPrev()); order_changed.SetTakeProfit(order_control.TakeProfit()); order_changed.SetTakeProfitPrev(order_control.TakeProfitPrev()); order_changed.SetChangedType(order_control.GetChangeType()); if(!this.m_list_changed.Add(order_changed)) { delete order_changed; return false; } return true; } //+------------------------------------------------------------------+
O método recebe o ponteiro para a ordem de controle modificada. A cópia da ordem deve ser colocada na lista de ordens e posições de controle alteradas.
Em seguida, uma nova ordem de controle é criada. Ela recebe imediatamente o ID da posição, o ticket, o número mágico e o símbolo correspondente aos da ordem de controle alterada.
Depois disso, as propriedades da ordem de controle alterada são simplesmente copiadas para as propriedades do objeto recém-criado elemento por elemento.
Finalmente, colocamos a cópia recém-criada de uma ordem de controle alterada na lista de ordens alteradas.
Se a ordem recém-criada não puder ser colocada na lista, o objeto da ordem recém-criado é removido e é retornado false.
Nós concluímos a implementação das alterações na classe CMarketCollection. Agora vamos passar para a classe CEventsCollection.
A classe de coleção de eventos CEventsCollection deve apresentar os eventos de manipulação, nos quais a lista de ordens alteradas, que foi criada na classe de coleção de ordem e posição de mercado, não está vazia. Isso significa que ele contém as ordens e posições alteradas que devem ser manipuladas (criamos um novo evento e enviamos uma mensagem apropriada para o programa que realizou a chamada).
Vamos adicionar a definição dos dois métodos à seção privada da classe, além do método já existente: o novo método sobrecarregado de criação de um novo evento e o método para lidar com a alteração de uma ordem/posição existente, enquanto o método Refresh() recebe a capacidade de passar a lista de ordens alteradas ao método:
//+------------------------------------------------------------------+ //| EventsCollection.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Orders\Order.mqh" #include "..\Objects\Events\EventBalanceOperation.mqh" #include "..\Objects\Events\EventOrderPlaced.mqh" #include "..\Objects\Events\EventOrderRemoved.mqh" #include "..\Objects\Events\EventPositionOpen.mqh" #include "..\Objects\Events\EventPositionClose.mqh" //+------------------------------------------------------------------+ //| Collection of account events | //+------------------------------------------------------------------+ class CEventsCollection : public CListObj { private: CListObj m_list_events; // List of events bool m_is_hedge; // Hedge account flag long m_chart_id; // Control program chart ID int m_trade_event_code; // Trading event code ENUM_TRADE_EVENT m_trade_event; // Account trading event CEvent m_event_instance; // Event object for searching by property //--- Create a trading event depending on the (1) order status and (2) change type void CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market); void CreateNewEvent(COrderControl* order); //--- Create an event for a (1) hedging account, (2) netting account void NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); void NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); //--- Select and return the list of market pending orders CArrayObj* GetListMarketPendings(CArrayObj* list); //--- Select from the list and return the list of historical (1) removed pending orders, (2) deals, (3) all closing orders CArrayObj* GetListHistoryPendings(CArrayObj* list); CArrayObj* GetListDeals(CArrayObj* list); CArrayObj* GetListCloseByOrders(CArrayObj* list); //--- Return the list of (1) all position orders by its ID, (2) all deal positions by its ID, //--- (3) all market entry deals by position ID, (4) all market exit deals by position ID, //--- (5) all position reversal deals by position ID CArrayObj* GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsInOutByPosID(CArrayObj* list,const ulong position_id); //--- Return the total volume of all deals (1) IN, (2) OUT of the position by its ID double SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id); double SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id); //--- Return the (1) first, (2) last and (3) closing order from the list of all position orders, //--- (4) an order by ticket, (5) market position by ID, //--- (6) the last and (7) penultimate InOut deal by position ID COrder* GetFirstOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetLastOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetCloseByOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetHistoryOrderByTicket(CArrayObj* list,const ulong order_ticket); COrder* GetPositionByID(CArrayObj* list,const ulong position_id); //--- Return the flag of the event object presence in the event list bool IsPresentEventInList(CEvent* compared_event); //--- Handler of an existing order/position change void OnChangeEvent(CArrayObj* list_changes,CArrayObj* list_history,CArrayObj* list_market,const int index); public: //--- Select events from the collection with time within the range from begin_time to end_time CArrayObj *GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Return the full event collection list "as is" CArrayObj *GetList(void) { return &this.m_list_events; } //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } //--- Update the list of events void Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals); //--- Set the control program chart ID void SetChartID(const long id) { this.m_chart_id=id; } //--- Return the last trading event on the account ENUM_TRADE_EVENT GetLastTradeEvent(void) const { return this.m_trade_event; } //--- Reset the last trading event void ResetLastTradeEvent(void) { this.m_trade_event=TRADE_EVENT_NO_EVENT; } //--- Constructor CEventsCollection(void); }; //+------------------------------------------------------------------+
Vamos implementar os novos métodos fora do corpo da classe.
O método sobrecarregado para criar um evento de modificação de ordem/posição:
//+------------------------------------------------------------------+ //| Create a trading event depending on the order change type | //+------------------------------------------------------------------+ void CEventsCollection::CreateNewEvent(COrderControl* order) { CEvent* event=NULL; //--- Pending StopLimit order placed if(order.GetChangeType()==CHANGE_TYPE_ORDER_TYPE) { this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_PLASED; event=new CEventOrderPlased(this.m_trade_event_code,order.Ticket()); } //--- if(event!=NULL) { event.SetProperty(EVENT_PROP_TIME_EVENT,order.Time()); // Event time event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_STOPLIMIT_TRIGGERED); // Event reason (from the ENUM_EVENT_REASON enumeration) event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrderPrev()); // Type of the order that triggered an event event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket()); // Ticket of the order that triggered an event event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder()); // Event order type event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket()); // Event order ticket event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder()); // Position first order type event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket()); // Position first order ticket event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID()); // Position ID event.SetProperty(EVENT_PROP_POSITION_BY_ID,0); // Opposite position ID event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0); // Opposite position magic number event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrderPrev()); // Position order type before changing the direction event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket()); // Position order ticket before changing direction event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder()); // Current position order type event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket()); // Current position order ticket event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic()); // Order magic number event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimePrev()); // First position order time event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PricePrev()); // Event price event.SetProperty(EVENT_PROP_PRICE_OPEN,order.Price()); // Order open price event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.Price()); // Order close price event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss()); // StopLoss order price event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit()); // TakeProfit order price event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume()); // Requested order volume event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,0); // Executed order volume event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.Volume()); // Remaining (unexecuted) order volume event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,0); // Executed position volume event.SetProperty(EVENT_PROP_PROFIT,0); // Profit event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol()); // Order symbol event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.Symbol()); // Opposite position symbol //--- Set the control program chart ID, decode the event code and set the event type event.SetChartID(this.m_chart_id); event.SetTypeEvent(); //--- Add the event object if it is not in the list if(!this.IsPresentEventInList(event)) { this.m_list_events.InsertSort(event); //--- Send a message about the event and set the value of the last trading event event.SendEvent(); this.m_trade_event=event.TradeEvent(); } //--- If the event is already present in the list, remove a new event object and display a debugging message else { ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list.")); delete event; } } } //+------------------------------------------------------------------+
Eu descrevi o novo método de criação de eventos na quinta parte da descrição da biblioteca ao criar uma coleção de eventos.
Este método é quase idêntico. A única diferença é o tipo da ordem, cujo ponteiro é passado ao método.
Verifica-se o tipo de alteração que ocorreu na ordem no início do método e o código de alteração é definido na variável membro de classe m_trade_event_code de acordo com o tipo da alteração.
Em seguida, é criado o evento correspondente ao tipo de alteração, suas propriedades são preenchidas de acordo com o tipo da alteração, o evento é colocado na lista de eventos e é enviado para o programa de controle.
O método melhorado para atualizar a lista de eventos:
//+------------------------------------------------------------------+ //| Update the event list | //+------------------------------------------------------------------+ void CEventsCollection::Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals) { //--- Exit if the lists are empty if(list_history==NULL || list_market==NULL) return; //--- If the event is in the market environment if(is_market_event) { //--- if the order properties were changed int total_changes=list_changes.Total(); if(total_changes>0) { for(int i=total_changes-1;i>=0;i--) { this.OnChangeEvent(list_changes,i); } } //--- if the number of placed pending orders increased if(new_market_pendings>0) { //--- Receive the list of the newly placed pending orders CArrayObj* list=this.GetListMarketPendings(list_market); if(list!=NULL) { //--- Sort the new list by order placement time list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Take the number of orders equal to the number of newly placed ones from the end of the list in a loop (the last N events) int total=list.Total(), n=new_market_pendings; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive an order from the list, if this is a pending order, set a trading event COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING) this.CreateNewEvent(order,list_history,list_market); } } } } //--- If the event is in the account history if(is_history_event) { //--- If the number of historical orders increased if(new_history_orders>0) { //--- Receive the list of removed pending orders only CArrayObj* list=this.GetListHistoryPendings(list_history); if(list!=NULL) { //--- Sort the new list by order removal time list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC); //--- Take the number of orders equal to the number of newly removed ones from the end of the list in a loop (the last N events) int total=list.Total(), n=new_history_orders; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive an order from the list. If this is a removed pending order without a position ID, //--- this is an order removal - set a trading event COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0) this.CreateNewEvent(order,list_history,list_market); } } } //--- If the number of deals increased if(new_deals>0) { //--- Receive the list of deals only CArrayObj* list=this.GetListDeals(list_history); if(list!=NULL) { //--- Sort the new list by deal time list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Take the number of deals equal to the number of new ones from the end of the list in a loop (the last N events) int total=list.Total(), n=new_deals; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive a deal from the list and set a trading event COrder* order=list.At(i); if(order!=NULL) this.CreateNewEvent(order,list_history,list_market); } } } } } //+------------------------------------------------------------------+
Este método também foi considerado na quinta parte da descrição da biblioteca ao criar uma coleção de eventos. A diferença desse método está no bloco de código para manipular os eventos de modificação no caso em que o tamanho da lista de ordens alteradas não for zero. Cada ordem alterada da lista é tratada no método do manipulador de eventos de mudança de ordens em um loop:
//+------------------------------------------------------------------+ //| The handler of an existing order/position change event | //+------------------------------------------------------------------+ void CEventsCollection::OnChangeEvent(CArrayObj* list_changes,const int index) { COrderControl* order_changed=list_changes.Detach(index); if(order_changed!=NULL) { if(order_changed.GetChangeType()==CHANGE_TYPE_ORDER_TYPE) { this.CreateNewEvent(order_changed); } delete order_changed; } } //+------------------------------------------------------------------+
Ao manipular a lista de ordens alteradas, nós precisamos obter uma ordem modificada da lista e remover o objeto da ordem e o seu ponteiro correspondente da lista após o término da manipulação, para evitar manipular o mesmo evento várias vezes.
Felizmente, quando se trabalha com uma array dinâmico de ponteiros de objeto CArrayObj, a biblioteca padrão fornece o método Detach() que recebe o elemento da posição especificada e o remove do array. Em outras palavras, nós recebemos o ponteiro para o objeto armazenado no array pelo índice e removemos esse ponteiro do array. Se o tipo de alteração for CHANGE_TYPE_ORDER_TYPE (alteração do tipo de ordem — acionando uma ordem pendente StopLimit e transformando-a em uma ordem Limit), criamos um novo evento — de ativação da ordem StopLimit. Depois que o objeto é manipulado pelo ponteiro obtido usando o método Detach(), o ponteiro (que não é mais necessário) é simplesmente removido.
Isso conclui a melhoria da classe CEventsCollection.
Para que todas as alterações entrem em vigor, a lista de ordens alteradas da classe de coleção de ordens e posições de mercado deve ser recebida e o seu tamanho deve ser escrito no objeto principal da biblioteca — na classe CEngine (o método TradeEventsControl()). Quando o método de atualização do evento Refresh() da classe de coleção de eventos é chamado, o tamanho da lista de ordens alteradas deve ser verificado adicionalmente, enquanto a lista de ordens modificadas deve ser passada para o método Refresh() da coleção de eventos para manipulação:
//+------------------------------------------------------------------+ //| Check trading events | //+------------------------------------------------------------------+ void CEngine::TradeEventsControl(void) { //--- Initialize the trading events code and flag this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- Update the lists this.m_market.Refresh(); this.m_history.Refresh(); //--- First launch actions if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Check the changes in the market status and account history this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- If there is any event, send the lists, the flags and the number of new orders and deals to the event collection, and update it int change_total=0; CArrayObj* list_changes=this.m_market.GetListChanges(); if(list_changes!=NULL) change_total=list_changes.Total(); if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0) { this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes, this.m_is_history_trade_event,this.m_is_market_trade_event, this.m_history.NewOrders(),this.m_market.NewPendingOrders(), this.m_market.NewMarketOrders(),this.m_history.NewDeals()); //--- Receive the last account trading event this.m_acc_trade_event=this.m_events.GetLastTradeEvent(); } } //+------------------------------------------------------------------+
Como a ativação de uma ordem StopLimit leva a uma ordem Limit, nós "qualificaremos" esse evento como uma ordem pendente, enquanto o motivo do evento é a ativação de uma ordem StopLimit EVENT_REASON_STOPLIMIT_TRIGGERED. Nós já definimos sua constante na enumeração ENUM_EVENT_REASON do arquivo Defines.mqh.
Vamos melhorar a classe EventOrderPlased para exibir o programa de eventos no diário e enviá-lo ao programa de controle:
Basta adicionar o manipulador do motivo do evento EVENT_REASON_STOPLIMIT_TRIGGERED.
//+------------------------------------------------------------------+ //| EventOrderPlased.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Event.mqh" //+------------------------------------------------------------------+ //| Placing a pending order event | //+------------------------------------------------------------------+ class CEventOrderPlased : public CEvent { public: //--- Constructor CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {} //--- Supported order properties (1) real, (2) integer virtual bool SupportProperty(ENUM_EVENT_PROP_INTEGER property); virtual bool SupportProperty(ENUM_EVENT_PROP_DOUBLE property); //--- (1) Display a brief message about the event in the journal, (2) Send the event to the chart virtual void PrintShort(void); virtual void SendEvent(void); }; //+------------------------------------------------------------------+ //| Return 'true' if the event supports the passed | //| integer property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_INTEGER property) { if(property==EVENT_PROP_TYPE_DEAL_EVENT || property==EVENT_PROP_TICKET_DEAL_EVENT || property==EVENT_PROP_TYPE_ORDER_POSITION || property==EVENT_PROP_TICKET_ORDER_POSITION || property==EVENT_PROP_POSITION_ID || property==EVENT_PROP_POSITION_BY_ID || property==EVENT_PROP_TIME_ORDER_POSITION ) return false; return true; } //+------------------------------------------------------------------+ //| Return 'true' if the event supports the passed | //| real property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_DOUBLE property) { if(property==EVENT_PROP_PRICE_CLOSE || property==EVENT_PROP_PROFIT ) return false; return true; } //+------------------------------------------------------------------+ //| Display a brief message about the event in the journal | //+------------------------------------------------------------------+ void CEventOrderPlased::PrintShort(void) { int digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS); string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),digits) : ""); string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),digits) : ""); string vol=::DoubleToString(this.VolumeOrderInitial(),DigitsLots(this.Symbol())); string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : ""); string type=this.TypeOrderFirstDescription()+" #"+(string)this.TicketOrderEvent(); string event=TextByLanguage(" Установлен "," Placed "); string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),digits); string txt=head+this.Symbol()+event+vol+" "+type+price+sl+tp+magic; //--- If StopLimit order is activated if(this.Reason()==EVENT_REASON_STOPLIMIT_TRIGGERED) { head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimeEvent())+" -\n"; event=TextByLanguage(" Сработал "," Triggered "); type= ( OrderTypeDescription(this.TypeOrderPosPrevious())+" #"+(string)this.TicketOrderEvent()+ TextByLanguage(" по цене "," at price ")+DoubleToString(this.PriceEvent(),digits)+" -->\n"+ vol+" "+OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderEvent()+ TextByLanguage(" на цену "," on price ")+DoubleToString(this.PriceOpen(),digits) ); txt=head+this.Symbol()+event+"("+TimeMSCtoString(this.TimePosition())+") "+vol+" "+type+sl+tp+magic; } ::Print(txt); } //+------------------------------------------------------------------+ //| Send the event to the chart | //+------------------------------------------------------------------+ void CEventOrderPlased::SendEvent(void) { this.PrintShort(); ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol()); } //+------------------------------------------------------------------+
Aqui tudo é muito fácil de entender. Não faz sentido insistir em ações simples.
Isso conclui a melhoria da biblioteca para a monitoração da ativação de uma ordem StopLimit.
Teste
Para testar as melhorias implementadas, nós usaremos o EA do artigo anterior. Basta renomear o EA TestDoEasyPart06.mq5 da pasta \MQL5\Experts\TestDoEasy\Part06 para TestDoEasyPart07.mq5 e salvá-lo na nova subpasta \MQL5\Experts\TestDoEasy\Part07.
Compile o EA, inicie-o no testador, coloque uma ordem StopLimit e aguarde a sua ativação:
Qual é o próximo?
A funcionalidade implementada no artigo inclui a capacidade de adicionar rapidamente o monitoramento de outros eventos: modificação de propriedades de ordem pendentes — seus níveis de preço, StopLoss e TakeProfit, bem como a modificação dos níveis de StopLoss e TakeProfit das posições. Nós vamos considerar essas tarefas no próximo artigo.
Todos os arquivos da versão atual da biblioteca estão anexados abaixo, juntamente com os arquivos do EA de teste para você testar e fazer o download.
Deixe suas perguntas, comentários e sugestões nos comentários.
Artigos anteriores da série:
Parte 1. Conceito, gerenciamento de dados.
Parte 2. Coleção do histórico de ordens e negócios.
Parte 3 Coleção de ordens e posições de mercado, busca e ordenação.
Parte 4 Eventos de negociação. Conceito.
Parte 5. Classes e coleção de eventos de negociação. Envio de eventos para o programa.
Parte 6. Eventos da conta netting.