Biblioteca para criação simples e rápida de programas para MetaTrader (Parte XXVI): trabalho com ordens de negociação pendentes - primeira implementação (abertura de posições)
Sumário
- Ideia
- Número mágico como repositório de dados
- Classe ordem pendente, primeira implementação de ordens
- Teste
- O que vem agora?
Ideia
Em vários artigos, expressei o conceito de ordem de negociação pendente.
Hoje vamos analisar o que é, por que precisamos
dela, o que nos trará e começaremos a tratar da implementação de ordens pendentes.
Ao receber e processar erros do servidor de negociação, às vezes precisamos esperar um pouco e repetir a ordem. Para aguardar, no caso mais
simples, podemos simplesmente executar a função Sleep() com o número
necessário de milissegundos e repetir a solicitação. Em muitos programas, isso é suficiente. Mas aqui temos um algo negativo, isto é,
enquanto aguardamos, nosso programa para e aguarda até que a pausa seja concluída e, em seguida, continua sua lógica. Tudo isso acontece no
método de negociação, enquanto todas as outras funções do programa resultam inacessíveis.
Para contornar esse momento
desagradável, podemos criar uma cópia da ordem de negociação cujo envio ao servidor causa um erro que exige uma segunda ordem após a espera,
colocar essa ordem na lista de ordens de negociação e sair com calma do método de negociação. Assim, evitamos que o programa necessite "ficar
suspenso" num estado de espera dentro do método de negociação e propomos trabalhar ainda mais de acordo com a lógica estabelecida. Além
disso, a classe de negociação examina constantemente a lista de ordens pendentes e, quando chega a hora de executá-la (o tempo de espera
expira), a biblioteca chama independentemente o método de negociação necessário com a ordem de negociação necessária. Em seguida, tudo
acontece na mesma sequência - se recebermos um erro novamente, sairemos novamente do método de negociação antes da próxima ordem de
negociação aparecer. Se a ordem for executada sem erros e enfileirada no servidor, essa ordem pendente será excluída da lista de ordens de
negociação.
Esta é uma das variantes de implementação para ordens pendentes, variante essa que nos permite não interromper a execução do programa
enquanto aguardamos, sobretudo, muito.
Outra variantes de implementação para ordens pendentes é e a utilização de ordens StopLimit
em MQL4. Afinal, o que é uma ordem StopLimit? É uma ordem pendente dupla que contém dois preços, o preço de uma ordem Stop e o preço de uma ordem
Limit. Uma ordem limite pendente é fixada quando o preço atinge o nível definido para a ordem Stop. Assim, com a ajuda da ordem pendente,
empregamos facilmente a lógica da ordem StopLimit: simplesmente criamos uma ordem pendente para definir uma ordem Limit e, no momento em
que a ordem Limit é fixada, nos parâmetros da ordem pendente escrevemos o preço ao qual precisamos enviá-la. Assim que o preço atingir o valor
fixado, ao servidor será enviada uma solicitação para colocar uma ordem Limit. Esta lógica repete a das ordens StopLimit.
Outra variante para usar ordens pendentes é enviar automaticamente ordens de negociação para abrir posições a mercado quando o preço atingir
um nível especificado ou quando determinado tempo for atingido, ou ambos.
Em geral, há muito espaço para imaginar como automatizar o
envio de ordens de negociação sob determinadas condições.
Número mágico como repositório de dados
Ao criar uma nova ordem pendente, para que o programa possa saber exatamente o que a ordem é realizada de acordo com a ordem pendente em questão
precisamos marcá-la de alguma forma, em outras palavras, precisamos identificar e associar exclusivamente a ordem ou posição a uma ordem
pendente específica. Além disso, esta ligação não deve ser perdida em situações de emergência.
Pensei muito nas diferentes
possibilidades de organizar essa ligação e cheguei a um único pensamento: no valor do número mágico da ordem/posição é necessário
registrar o identificador da ordem pendente. Este é o único parâmetro que permanece inalterado e que está inicialmente na ordem. Todos os
outros métodos não são confiáveis (armazenamento no comentário da ordem/posição) ou exigem grandes despesas para criação e manutenção
(armazenamento em arquivos). Também não considero variáveis globais do terminal, pois elas podem não conseguirem se registrar na mídia em
caso de emergência e, portanto, também não dão total confiança quanto à relevância das informações.
Sendo assim, vejo apenas uma saída: armazenar dados no valor do número mágico. Neste ponto, é importante lembrar que anteriormente
aos objetos-ordens adicionávamos um identificador de grupo que permitia agrupar ordens/posições num grupo comum, o que podia ser
útil para vários, por exemplo, EAs gradadores. Podemos adicionar esse identificador ao objeto-ordem somente após seu aparecimento
físico no servidor. Em situações de emergência, ele será perdido. Certamente, mas para frente, está planejado o armazenamento de todas as
coleções juntamente com seus objetos no disco rígido. Mas, agora podemos adicionar, juntamente com o identificador da ordem pendente, os
identificadores dos grupos ao número mágico da ordem.
E podemos armazenar vários identificadores ao mesmo tempo no valor do número
mágico:
- identificador do magic (aquele especificado nos parâmetros de entrada do EA)
- identificador do primeiro grupo (com números de subgrupos de 0 a 15, zero indica que não tem associação ao grupo)
- identificador do segundo grupo (com números de subgrupos de 0 a 15, zero indica que não tem associação ao grupo)
- identificador de ordem pendente (com números possíveis de 0 a 255, zero indica que não tem identificador)
Assim, teremos a oportunidade de definir o número do primeiro e do segundo grupos de ordens. Cada grupo de pedidos pode ter até quinze subgrupos. O que isso nos dará? Bem, digamos que temos 20 ordens/posições que queremos coletar num subgrupo segundo algum critério. Para eles no primeiro grupo definimos como 1 o número de subgrupo. Também temos 20 ordens/posições que queremos coletar por outro critério em outro subgrupo do mesmo primeiro grupo. Damos a eles o número de subgrupo 2. Aos terceiros vinte ordens/posições atribuímos o subgrupo 3. Agora, temos três grupos de ordens/posições, que podemos processar com facilidade tudo simultaneamente com nosso manipulador para cada um dos subgrupos do primeiro grupo. Se precisarmos de dois dos três grupos para processar/analisar de alguma forma, mas com um manipulador completamente diferente, sem perder sua participação em grupos já estabelecidos, podemos identificá-los no segundo grupo (até quinze subgrupos). Isso nos dá maior flexibilidade na combinação de ordens/posições em grupos diferentes do que com apenas um grupo, embora com mais números de subgrupos.
Bem, vemos que planejamos muito, mas onde está o problema? O problema é que o tamanho do valor inteiro no qual é armazenado o número mágico para MQL4 é de apenas 4 bytes (int). Por isso, aqui temos que sacrificar a magnitude do valor do número mágico, que podemos definir em nossos programas. Para MQL5, o tamanho do número mágico é definido como ulong, e aí podemos definir números grandes e armazenar muitas mais informações. Mas tudo se resume à compatibilidade... Isso quer dizer que é necessário sacrificar alguma coisa. É preciso sacrificar o tamanho do valor do número mágico, para isso, teremos apenas dois bytes (ushort) e os dois bytes liberados serão alocados para os identificadores de dois grupos (uchar) e o identificador da ordem pendente (uchar).
A tabela mostra a estrutura do número mágico e a localização dos dados no valor uint do número mágico:
------------------------------------------------------------------------- | bit |31 24|23 20|19 16|15 8|7 0| ------------------------------------------------------------------------- | byte | 3 | 2 | 1 | 0 | ------------------------------------------------------------------------- | type | uchar | uchar | ushort | ------------------------------------------------------------------------- | descr | request id | id2 | id1 | magic | -------------------------------------------------------------------------
Como pode ser visto na tabela,
- o valor do número mágico tem dois bytes de tamanho e é armazenado nos bytes finais 0 e 1 de um número uint (bytes 0 — 15), o que corresponde ao tipo ushort. É esse tipo de número mágico que teremos que usar em nossos programas com valores de 0 a 65535.
- Em seguida, segundo o inicial, o byte 2 do número uint serve para
armazenar dois identificador de grupo, e tem um tamanho uchar
(bits 16 — 23).
O identificador do primeiro grupo é armazenado nos quatro bits finais do número uchar (bits 16 — 19), а
o identificador do segundo grupo é armazenado nos quatro bits iniciais desse número uchar (bits 20 — 23).
Assim, podemos salvar num valor uchar de um byte dois grupos cuja quantidade de números pode ser de zero (sem grupo) a 15 (o valor máximo que podemos armazenar em quatro bits). - No terceiro e último byte do número uint vamos armazenar o valor uchar do identificador da ordem pendente (bits 24 — 31), que pode ter valores de zero (sem identificador) a 255. Isso significa que, no total, podemos ter simultaneamente até 255 ordens pendentes ativas.
Para realizar o armazenamento de dados no valor da propriedade "número mágico" da ordem, modificaremos a classe da ordem abstrata e as
classes de eventos.
Mas primeiro no arquivo Defines.mqh
inserimos as novas propriedades de número inteiro do objeto de ordem abstrata:
//+------------------------------------------------------------------+ //| Order, deal, position integer properties | //+------------------------------------------------------------------+ enum ENUM_ORDER_PROP_INTEGER { ORDER_PROP_TICKET = 0, // Order ticket ORDER_PROP_MAGIC, // Real order magic number ORDER_PROP_TIME_OPEN, // Open time in milliseconds (MQL5 Deal time) ORDER_PROP_TIME_CLOSE, // Close time in milliseconds (MQL5 Execution or removal time - ORDER_TIME_DONE) ORDER_PROP_TIME_EXP, // Order expiration date (for pending orders) ORDER_PROP_STATUS, // Order status (from the ENUM_ORDER_STATUS enumeration) ORDER_PROP_TYPE, // Order/deal type ORDER_PROP_REASON, // Deal/order/position reason or source ORDER_PROP_STATE, // Order status (from the ENUM_ORDER_STATE enumeration) ORDER_PROP_POSITION_ID, // Position ID ORDER_PROP_POSITION_BY_ID, // Opposite position ID ORDER_PROP_DEAL_ORDER_TICKET, // Ticket of the order that triggered a deal ORDER_PROP_DEAL_ENTRY, // Deal direction – IN, OUT or IN/OUT ORDER_PROP_TIME_UPDATE, // Position change time in milliseconds ORDER_PROP_TICKET_FROM, // Parent order ticket ORDER_PROP_TICKET_TO, // Derived order ticket ORDER_PROP_PROFIT_PT, // Profit in points ORDER_PROP_CLOSE_BY_SL, // Flag of closing by StopLoss ORDER_PROP_CLOSE_BY_TP, // Flag of closing by TakeProfit ORDER_PROP_MAGIC_ID, // Order's "magic number" ID ORDER_PROP_GROUP_ID1, // First order/position group ID ORDER_PROP_GROUP_ID2, // Second order/position group ID ORDER_PROP_PEND_REQ_ID, // Pending request ID ORDER_PROP_DIRECTION, // Type by direction (Buy, Sell) }; #define ORDER_PROP_INTEGER_TOTAL (24) // Total number of integer properties #define ORDER_PROP_INTEGER_SKIP (0) // Number of order properties not used in sorting //+------------------------------------------------------------------+
Estas propriedades armazenam os identificadores mencionados anteriormente que serão armazenados no valor do número mágico. Como
adicionamos três novas propriedades e alteramos uma delas, na substituição de macro
indicando o número total de propriedades inteiras, alteramos seu número de 21 para 24.
Também à enumeração de critérios para classificar ordens e transações adicionamos a classificação destas propriedades:
//+------------------------------------------------------------------+ //| Possible criteria of sorting orders and deals | //+------------------------------------------------------------------+ #define FIRST_ORD_DBL_PROP (ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP) #define FIRST_ORD_STR_PROP (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP) enum ENUM_SORT_ORDERS_MODE { //--- Sort by integer properties SORT_BY_ORDER_TICKET = 0, // Sort by an order ticket SORT_BY_ORDER_MAGIC, // Sort by an order magic number SORT_BY_ORDER_TIME_OPEN, // Sort by an order open time in milliseconds SORT_BY_ORDER_TIME_CLOSE, // Sort by an order close time in milliseconds SORT_BY_ORDER_TIME_EXP, // Sort by an order expiration date SORT_BY_ORDER_STATUS, // Sort by an order status (market order/pending order/deal/balance and credit operation) SORT_BY_ORDER_TYPE, // Sort by an order type SORT_BY_ORDER_REASON, // Sort by a deal/order/position reason/source SORT_BY_ORDER_STATE, // Sort by an order status SORT_BY_ORDER_POSITION_ID, // Sort by a position ID SORT_BY_ORDER_POSITION_BY_ID, // Sort by an opposite position ID SORT_BY_ORDER_DEAL_ORDER, // Sort by the order a deal is based on SORT_BY_ORDER_DEAL_ENTRY, // Sort by a deal direction – IN, OUT or IN/OUT SORT_BY_ORDER_TIME_UPDATE, // Sort by position change time in seconds SORT_BY_ORDER_TICKET_FROM, // Sort by a parent order ticket SORT_BY_ORDER_TICKET_TO, // Sort by a derived order ticket SORT_BY_ORDER_PROFIT_PT, // Sort by order profit in points SORT_BY_ORDER_CLOSE_BY_SL, // Sort by the flag of closing an order by StopLoss SORT_BY_ORDER_CLOSE_BY_TP, // Sort by the flag of closing an order by TakeProfit SORT_BY_ORDER_MAGIC_ID, // Sort by an order/position "magic number" ID SORT_BY_ORDER_GROUP_ID1, // Sort by the first order/position group ID SORT_BY_ORDER_GROUP_ID2, // Sort by the second order/position group ID SORT_BY_ORDER_PEND_REQ_ID, // Sort by a pending request ID SORT_BY_ORDER_DIRECTION, // Sort by direction (Buy, Sell) //--- Sort by real properties SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP, // Sort by open price SORT_BY_ORDER_PRICE_CLOSE, // Sort by close price SORT_BY_ORDER_SL, // Sort by StopLoss price SORT_BY_ORDER_TP, // Sort by TakeProfit price SORT_BY_ORDER_PROFIT, // Sort by profit SORT_BY_ORDER_COMMISSION, // Sort by commission SORT_BY_ORDER_SWAP, // Sort by swap SORT_BY_ORDER_VOLUME, // Sort by volume SORT_BY_ORDER_VOLUME_CURRENT, // Sort by unexecuted volume SORT_BY_ORDER_PROFIT_FULL, // Sort by profit+commission+swap SORT_BY_ORDER_PRICE_STOP_LIMIT, // Sort by Limit order when StopLimit order is activated //--- Sort by string properties SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP, // Sort by symbol SORT_BY_ORDER_COMMENT, // Sort by comment SORT_BY_ORDER_COMMENT_EXT, // Sort by custom comment SORT_BY_ORDER_EXT_ID // Sort by order ID in an external trading system }; //+------------------------------------------------------------------+
Para exibir corretamente as propriedades da ordem no log,
ao arquivo Datas.mqh adicionamos os índices
das novas propriedades e os índices das mensagens correspondentes:
MSG_ORD_PROFIT_PT, // Profit in points MSG_ORD_MAGIC_ID, // Magic number ID MSG_ORD_GROUP_ID1, // First group ID MSG_ORD_GROUP_ID2, // Second group ID MSG_ORD_PEND_REQ_ID, // Pending request ID MSG_ORD_PRICE_OPEN, // Open price {"Прибыль в пунктах","Profit in points"}, {"Идентификатор магического номера","Magic number's identifier"}, {"Идентификатор первой группы","First group's identifier"}, {"Идентификатор второй группы","Second group's identifier"}, {"Идентификатор отложенного запроса","Pending request's identifier"}, {"Цена открытия","Price open"},
À classe de ordem abstrata - no arquivo Order.mqh - adicionamos as alterações necessárias.
Na seção privada da classe inserimos quatro métodos que do valor da propriedade da ordem extraem o "número mágico" e que retornam o identificador
do número mágico (o magic especificado nas configurações do programa), os identificadores
do grupo 1 e do grupo 2 e o ID
da ordem pendente:
//+------------------------------------------------------------------+ //| Abstract order class | //+------------------------------------------------------------------+ class COrder : public CObject { private: ulong m_ticket; // Selected order/deal ticket (MQL5) long m_long_prop[ORDER_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[ORDER_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[ORDER_PROP_STRING_TOTAL]; // String properties //--- Return the index of the array the order's (1) double and (2) string properties are located at int IndexProp(ENUM_ORDER_PROP_DOUBLE property) const { return(int)property-ORDER_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_ORDER_PROP_STRING property) const { return(int)property-ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_DOUBLE_TOTAL; } //--- Data location in the magic number int value //----------------------------------------------------------- // bit 32|31 24|23 16|15 8|7 0| //----------------------------------------------------------- // byte | 3 | 2 | 1 | 0 | //----------------------------------------------------------- // data | uchar | uchar | ushort | //----------------------------------------------------------- // descr |pend req id| id2 | id1 | magic | //----------------------------------------------------------- //--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value ushort GetMagicID(void) const { return ushort(this.Magic() & 0xFFFF); } uchar GetGroupID1(void) const { return uchar(this.Magic()>>16) & 0x0F; } uchar GetGroupID2(void) const { return uchar((this.Magic()>>16) & 0xF0)>>4; } uchar GetPendReqID(void) const { return uchar(this.Magic()>>24) & 0xFF; } public: //--- Default constructor COrder(void){;}
Para a partir do valor unit do número mágico retornar o valor ushort do magic definido nas configurações, basta aplicar a máscara (0xFFFF), que no valor do número uint deixa apenas os dois bytes finais inalterados, enquanto os dois bytes iniciais do número uint são preenchidos com zeros. No entanto, ao converter um tipo uint em ushort, os dois bytes iniciais são automaticamente descartados.
Para extrair o identificador do primeiro grupo, primeiro precisamos deslocar 16 bits para a direita o valor da propriedade número mágico (para que o valor uchar dos identificadores de grupo caia no byte zero do número uint) e, em seguida, ao número resultante devemos aplicar a máscara 0x0F, que deixará apenas os quatro bits finais do valor recebido quando alterado o valor. A conversão de um tipo uint num tipo uchar fará com que sejam descartados todos os bytes iniciais do número, deixando apenas um byte final mascarado. Assim, obtemos um valor de quatro bits de 0 a 15.
A extração do identificador do segundo grupo é um pouco diferente da do identificador do primeiro grupo, pois, neste caso, o valor que precisamos está nos quatro bits iniciais do valor uchar. Por isso, primeiro fazemos exatamente o mesmo que ao extrair o identificador do primeiro grupo, isto é, deslocamos 16 bits para a direita o valor da propriedade número mágico (para que o valor uchar dos identificadores de grupo caia no byte zero do número uint) e ao número resultante aplicamos a máscara 0x0F, que deixará apenas os quatro bits iniciais do valor recebido quando alterado o valor e, em seguida, deslocamos outros quatro bits para a direita o valor recebido, para que os bits iniciais que armazenam o número do identificador levem a valores de 0 a 15.
Para extrair o identificador da ordem pendente, precisamos
deslocar 24 bits para a direita o byte inicial do número uint, para que o valor uchar de um byte caia no byte zero do número uint e, em seguida, a
ele aplicar a máscara 0xFF (o que não é necessário, pois ao converter o número uint no tipo uchar, apenas permanece um byte final)
Ao bloco contendo os métodos de acesso simplificado às propriedades do objeto da ordem abstrata adicionamos
os métodos que retornam quatro novas propriedades:
//+------------------------------------------------------------------+ //| Methods of a simplified access to the order object properties | //+------------------------------------------------------------------+ //--- Return (1) ticket, (2) parent order ticket, (3) derived order ticket, (4) magic number, (5) order reason, //--- (6) position ID, (7) opposite position ID, (8) first group ID, (9) second group ID, //--- (10) pending request ID, (11) magic number ID, (12) type, (13) flag of closing by StopLoss, //--- (14) flag of closing by TakeProfit (15) open time, (16) close time, //--- (17) order expiration date, (18) state, (19) status, (20) type by direction long Ticket(void) const { return this.GetProperty(ORDER_PROP_TICKET); } long TicketFrom(void) const { return this.GetProperty(ORDER_PROP_TICKET_FROM); } long TicketTo(void) const { return this.GetProperty(ORDER_PROP_TICKET_TO); } long Magic(void) const { return this.GetProperty(ORDER_PROP_MAGIC); } long Reason(void) const { return this.GetProperty(ORDER_PROP_REASON); } long PositionID(void) const { return this.GetProperty(ORDER_PROP_POSITION_ID); } long PositionByID(void) const { return this.GetProperty(ORDER_PROP_POSITION_BY_ID); } long MagicID(void) const { return this.GetProperty(ORDER_PROP_MAGIC_ID); } long GroupID1(void) const { return this.GetProperty(ORDER_PROP_GROUP_ID1); } long GroupID2(void) const { return this.GetProperty(ORDER_PROP_GROUP_ID2); } long PendReqID(void) const { return this.GetProperty(ORDER_PROP_PEND_REQ_ID); } long TypeOrder(void) const { return this.GetProperty(ORDER_PROP_TYPE); } bool IsCloseByStopLoss(void) const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL); } bool IsCloseByTakeProfit(void) const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP); } long TimeOpen(void) const { return this.GetProperty(ORDER_PROP_TIME_OPEN); } long TimeClose(void) const { return this.GetProperty(ORDER_PROP_TIME_CLOSE); } datetime TimeExpiration(void) const { return (datetime)this.GetProperty(ORDER_PROP_TIME_EXP); } ENUM_ORDER_STATE State(void) const { return (ENUM_ORDER_STATE)this.GetProperty(ORDER_PROP_STATE); } ENUM_ORDER_STATUS Status(void) const { return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS); } ENUM_ORDER_TYPE TypeByDirection(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION);
Aqui também adicionamos três métodos para definir nas propriedades da ordem
abstrata das novas propriedades:
//--- Get the full order profit double ProfitFull(void) const { return this.Profit()+this.Comission()+this.Swap(); } //--- Get order profit in points int ProfitInPoints(void) const; //--- Set (1) the first group ID, (2) the second group ID, (3) the pending request ID, (4) custom comment void SetGroupID1(const long group_id) { this.SetProperty(ORDER_PROP_GROUP_ID1,group_id); } void SetGroupID2(const long group_id) { this.SetProperty(ORDER_PROP_GROUP_ID2,group_id); } void SetPendReqID(const long req_id) { this.SetProperty(ORDER_PROP_PEND_REQ_ID,req_id); } void SetCommentExt(const string comment_ext) { this.SetProperty(ORDER_PROP_COMMENT_EXT,comment_ext); } //+------------------------------------------------------------------
No construtor de classe particular preenchemos os novos campos de propriedades
da ordem abstrata com valores de identificadores usando os métodos acima:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket) { //--- Save integer properties this.m_ticket=ticket; this.m_long_prop[ORDER_PROP_STATUS] = order_status; this.m_long_prop[ORDER_PROP_MAGIC] = this.OrderMagicNumber(); this.m_long_prop[ORDER_PROP_TICKET] = this.OrderTicket(); this.m_long_prop[ORDER_PROP_TIME_EXP] = this.OrderExpiration(); this.m_long_prop[ORDER_PROP_TYPE] = this.OrderType(); this.m_long_prop[ORDER_PROP_STATE] = this.OrderState(); this.m_long_prop[ORDER_PROP_DIRECTION] = this.OrderTypeByDirection(); this.m_long_prop[ORDER_PROP_POSITION_ID] = this.OrderPositionID(); this.m_long_prop[ORDER_PROP_REASON] = this.OrderReason(); this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET] = this.DealOrderTicket(); this.m_long_prop[ORDER_PROP_DEAL_ENTRY] = this.DealEntry(); this.m_long_prop[ORDER_PROP_POSITION_BY_ID] = this.OrderPositionByID(); this.m_long_prop[ORDER_PROP_TIME_OPEN] = this.OrderOpenTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_CLOSE] = this.OrderCloseTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_UPDATE] = this.PositionTimeUpdateMSC(); //--- Save real properties this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)] = this.OrderOpenPrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)] = this.OrderClosePrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)] = this.OrderProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)] = this.OrderCommission(); this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)] = this.OrderSwap(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)] = this.OrderVolume(); this.m_double_prop[this.IndexProp(ORDER_PROP_SL)] = this.OrderStopLoss(); this.m_double_prop[this.IndexProp(ORDER_PROP_TP)] = this.OrderTakeProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)] = this.OrderVolumeCurrent(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)] = this.OrderPriceStopLimit(); //--- Save string properties this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)] = this.OrderSymbol(); this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)] = this.OrderComment(); this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)] = this.OrderExternalID(); //--- Save additional integer properties this.m_long_prop[ORDER_PROP_PROFIT_PT] = this.ProfitInPoints(); this.m_long_prop[ORDER_PROP_TICKET_FROM] = this.OrderTicketFrom(); this.m_long_prop[ORDER_PROP_TICKET_TO] = this.OrderTicketTo(); this.m_long_prop[ORDER_PROP_CLOSE_BY_SL] = this.OrderCloseByStopLoss(); this.m_long_prop[ORDER_PROP_CLOSE_BY_TP] = this.OrderCloseByTakeProfit(); this.m_long_prop[ORDER_PROP_MAGIC_ID] = this.GetMagicID(); this.m_long_prop[ORDER_PROP_GROUP_ID1] = this.GetGroupID1(); this.m_long_prop[ORDER_PROP_GROUP_ID2] = this.GetGroupID2(); this.m_long_prop[ORDER_PROP_PEND_REQ_ID] = this.GetPendReqID(); //--- Save additional real properties this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)] = this.ProfitFull(); //--- Save additional string properties this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT_EXT)] = ""; } //+------------------------------------------------------------------+
Ao método que retorna descrições de propriedades inteiras adicionamos a exibição da descrição de todas as novas propriedades adicionadas da ordem abstrata:
//+------------------------------------------------------------------+ //| Return description of an order's integer property | //+------------------------------------------------------------------+ string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property) { return ( //--- General properties property==ORDER_PROP_MAGIC ? CMessage::Text(MSG_ORD_MAGIC)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET ? CMessage::Text(MSG_ORD_TICKET)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET_FROM ? CMessage::Text(MSG_ORD_TICKET_FROM)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET_TO ? CMessage::Text(MSG_ORD_TICKET_TO)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TIME_EXP ? CMessage::Text(MSG_ORD_TIME_EXP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : (this.GetProperty(property)==0 ? CMessage::Text(MSG_LIB_PROP_NOT_SET)+": "+CMessage::Text(MSG_LIB_PROP_NOT_SET) : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)) ) : property==ORDER_PROP_TYPE ? CMessage::Text(MSG_ORD_TYPE)+": "+this.TypeDescription() : property==ORDER_PROP_DIRECTION ? CMessage::Text(MSG_ORD_TYPE_BY_DIRECTION)+": "+this.DirectionDescription() : property==ORDER_PROP_REASON ? CMessage::Text(MSG_ORD_REASON)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetReasonDescription(this.GetProperty(property)) ) : property==ORDER_PROP_POSITION_ID ? CMessage::Text(MSG_ORD_POSITION_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_DEAL_ORDER_TICKET ? CMessage::Text(MSG_ORD_DEAL_ORDER_TICKET)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_DEAL_ENTRY ? CMessage::Text(MSG_ORD_DEAL_ENTRY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetEntryDescription(this.GetProperty(property)) ) : property==ORDER_PROP_POSITION_BY_ID ? CMessage::Text(MSG_ORD_POSITION_BY_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_TIME_OPEN ? CMessage::Text(MSG_ORD_TIME_OPEN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" ) : property==ORDER_PROP_TIME_CLOSE ? CMessage::Text(MSG_ORD_TIME_CLOSE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" ) : property==ORDER_PROP_TIME_UPDATE ? CMessage::Text(MSG_ORD_TIME_UPDATE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(property)!=0 ? TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" : "0") ) : property==ORDER_PROP_STATE ? CMessage::Text(MSG_ORD_STATE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": \""+this.StateDescription()+"\"" ) : //--- Additional property property==ORDER_PROP_STATUS ? CMessage::Text(MSG_ORD_STATUS)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": \""+this.StatusDescription()+"\"" ) : property==ORDER_PROP_PROFIT_PT ? ( this.Status()==ORDER_STATUS_MARKET_PENDING ? CMessage::Text(MSG_ORD_DISTANCE_PT) : CMessage::Text(MSG_ORD_PROFIT_PT) )+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_CLOSE_BY_SL ? CMessage::Text(MSG_LIB_PROP_CLOSE_BY_SL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) ) : property==ORDER_PROP_CLOSE_BY_TP ? CMessage::Text(MSG_LIB_PROP_CLOSE_BY_TP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) ) : property==ORDER_PROP_MAGIC_ID ? CMessage::Text(MSG_ORD_MAGIC_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_GROUP_ID1 ? CMessage::Text(MSG_ORD_GROUP_ID1)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_GROUP_ID2 ? CMessage::Text(MSG_ORD_GROUP_ID2)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_PEND_REQ_ID ? CMessage::Text(MSG_ORD_PEND_REQ_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : "" ); } //+------------------------------------------------------------------+
Assim concluímos a classe da ordem abstrata. Agora precisamos fazer alterações nas classes de eventos.
Na classe abstrata do evento no arquivo Event.mqh na seção protegida da classe inserimos
métodos que retornam novos identificadores:
protected: ENUM_TRADE_EVENT m_trade_event; // Trading event bool m_is_hedge; // Hedge account flag long m_chart_id; // Control program chart ID int m_digits; // Symbol's Digits() int m_digits_acc; // Number of decimal places for the account currency long m_long_prop[EVENT_PROP_INTEGER_TOTAL]; // Event integer properties double m_double_prop[EVENT_PROP_DOUBLE_TOTAL]; // Event real properties string m_string_prop[EVENT_PROP_STRING_TOTAL]; // Event string properties //--- return the flag presence in the trading event bool IsPresentEventFlag(const int event_code) const { return (this.m_event_code & event_code)==event_code; } //--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value ushort GetMagicID(void) const { return ushort(this.Magic() & 0xFFFF); } uchar GetGroupID1(void) const { return uchar(this.Magic()>>16) & 0x0F; } uchar GetGroupID2(void) const { return uchar((this.Magic()>>16) & 0xF0)>>4; } uchar GetPendReqID(void) const { return uchar(this.Magic()>>24) & 0xFF; } //--- Protected parametric constructor CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);
Os métodos são semelhantes aos métodos da classe de ordem abstrata considerados acima.
Agora às cinco classes descendentes da classe de evento abstrato nos arquivos EventModify.mqh, EventOrderPlaced.mqh, EventOrderRemoved.mqh, EventPositionClose.mqh e EventPositionOpen.mqh em seus métodos de breve descrição do evento em vez de uma linha
//+------------------------------------------------------------------+ //| Create and return a short event message | //+------------------------------------------------------------------+ string CEventModify::EventsMessage(void) { //--- (1) header, (2) magic number string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic() : ""); string text="";
para cada classe inserimos estas linhas:
//+------------------------------------------------------------------+ //| Create and return a short event message | //+------------------------------------------------------------------+ string CEventModify::EventsMessage(void) { //--- (1) header, (2) magic number string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string magic_id=((this.GetPendReqID()>0 || this.GetGroupID1()>0 || this.GetGroupID2()>0) ? " ("+(string)this.GetMagicID()+")" : ""); string group_id1=(this.GetGroupID1()>0 ? ", G1: "+(string)this.GetGroupID1() : ""); string group_id2=(this.GetGroupID2()>0 ? ", G2: "+(string)this.GetGroupID2() : ""); string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic()+magic_id+group_id1+group_id2 : ""); string text="";
Como ao armazenar muitos dados num valor do número mágico, ao exibir este número no log, será exibido um valor totalmente diferente, em vez de o definido nas configurações do programa — afinal o valor do número mágico é armazenado apenas nos dois bytes finais, enquanto nos dois iniciais, os identificadores de grupo e de ordem pendente. Por essa razão, se ao valor do número mágico da ordem forem adicionados identificadores (ou pelo menos um), aos valores exibidos no log serão adicionadas as descrições de cada identificador individual.
Fizemos todas as alterações necessárias para armazenar dados no valor do número mágico. Agora, trataremos diretamente da classe da ordem
pendente e da primeira implementação da criação de ordens pendentes em caso de erros de abertura de posição.
Classe ordem pendente, primeira implementação de ordens
No arquivo da classe de negociação Trading.mqh, diretamente antes do corpo da classe de negociação CTrading inserimos a nova classe, que descreve o objeto-ordem pendente:
//+------------------------------------------------------------------+ //| Pending request object class | //+------------------------------------------------------------------+ class CPendingReq : public CObject { private: MqlTradeRequest m_request; // Trade request structure uchar m_id; // Trading request ID int m_retcode; // Result a request is based on double m_price_create; // Price at the moment of a request generation ulong m_time_create; // Request generation time ulong m_time_activate; // Next attempt activation time ulong m_waiting_msc; // Waiting time between requests uchar m_current_attempt; // Current attempt index uchar m_total_attempts; // Number of attempts //--- Copy trading request data void CopyRequest(const MqlTradeRequest &request) { this.m_request=request; } //--- Compare CPendingReq objects by IDs virtual int Compare(const CObject *node,const int mode=0) const; public: //--- Return (1) the request structure, (2) the price at the moemnt of the request generation, //--- (3) request generation time, (4) current attempt time, //--- (5) waiting time between requests, (6) current attempt index, //--- (7) number of attempts, (8) request ID MqlTradeRequest MqlRequest(void) const { return this.m_request; } double PriceCreate(void) const { return this.m_price_create; } ulong TimeCreate(void) const { return this.m_time_create; } ulong TimeActivate(void) const { return this.m_time_activate; } ulong WaitingMSC(void) const { return this.m_waiting_msc; } uchar CurrentAttempt(void) const { return this.m_current_attempt; } uchar TotalAttempts(void) const { return this.m_total_attempts; } uchar ID(void) const { return this.m_id; } //--- Set (1) the price when creating a request, (2) request creation time, //--- (3) current attempt time, (4) waiting time between requests, //--- (5) current attempt index, (6) number of attempts, (7) request ID void SetPriceCreate(const double price) { this.m_price_create=price; } void SetTimeCreate(const ulong time) { this.m_time_create=time; } void SetTimeActivate(const ulong time) { this.m_time_activate=time; } void SetWaitingMSC(const ulong miliseconds) { this.m_waiting_msc=miliseconds; } void SetCurrentAttempt(const uchar number) { this.m_current_attempt=number; } void SetTotalAttempts(const uchar number) { this.m_total_attempts=number; } void SetID(const uchar id) { this.m_id=id; } //--- Constructors CPendingReq(void){;} CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode); }; //+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CPendingReq::CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode) : m_price_create(price), m_time_create(time), m_id(id), m_retcode(retcode) { this.CopyRequest(request); } //+------------------------------------------------------------------+ //| Compare CPendingReq objects by IDs | //+------------------------------------------------------------------+ int CPendingReq::Compare(const CObject *node,const int mode=0) const { const CPendingReq *compared_req=node; return(this.ID()>compared_req.ID() ? 1 : this.ID()<compared_req.ID() ? -1 : 0); return 0; } //+------------------------------------------------------------------+
Acho que esta classe é tão simples que não faz sentido descrever as linhas comentadas, pois tudo fica claro graças ao nome dos métodos e das variáveis-membro da classe. No entanto, penso que vale a pena explicar como devem funcionar este objeto, os métodos e a funcionalidade da classe de negociação associados a ele.
Ao receber um erro do servidor, queremos criar uma segunda solicitação para o servidor e sair do método de negociação. Em seguida, essa
solicitação recém-criada - quando o tempo limite expirar - é enviada novamente ao servidor. No caso de recebimento repetido de erro,
precisamos criar uma ordem pendente de acordo com a lógica em questão. Mas ela já é criada quando é recebido o primeiro erro do servidor. Por
esse motivo, no magic da ordem de negociação entrante, é verificado se existe identificador de ordem pendente, e, se houver, significa que
esta ordem já foi criada e que atualmente é enviada outra tentativa ao servidor, e não é necessário criar uma nova solicitação. Se no magic da
ordem de negociação não houver um identificador, será criada uma nova ordem pendente com o primeiro identificador mínimo livre e o método de
negociação será encerrado para permitir ao programa fazer outras ações.
No temporizador da classe de negociação, é examinada a lista
de solicitações de negociação constantemente e, se o tempo de espera da próxima ordem expirar, o método de negociação correspondente à
ordem será chamado a partir do temporizador. Ao verificar cada próxima ordem da lista de ordens pendentes, é verificada a presença da
posição ou ordem correspondente na lista de ordens e posições de mercado e, se houver uma ordem ou posição com o identificador atual,
significa que a ordem pendente cumpre sua função e é removida da lista de ordens.
Resumidamente, a lógica é a seguinte. Continuamos com a sua implementação.
Já inserimos a classe no arquivo Trading.mqh, agora em sua seção privada
declaramos
o método para localizar e retornar o primeiro identificador menor da ordem pendente:
//--- Look for the first free pending request ID int GetFreeID(void); public: //--- Constructor CTrading();
Fora do corpo da classe, escrevemos sua implementação:
//+------------------------------------------------------------------+ //| Look for the first free pending request ID | //+------------------------------------------------------------------+ int CTrading::GetFreeID(void) { int id=WRONG_VALUE; CPendingReq *element=new CPendingReq(); if(element==NULL) return 0; for(int i=1;i<256;i++) { element.SetID((uchar)i); this.m_list_request.Sort(); if(this.m_list_request.Search(element)==WRONG_VALUE) { id=i; break; } } delete element; return id; } //+------------------------------------------------------------------+
Podemos ter 255 ordens pendentes independentes no total. Cada uma delas possuindo suas próprias propriedades, seu tempo de espera entre tentativas de negociação e a partir daqui seu próprio período de existência do objeto/ordem pendente. Devido a isso, pode acontecer que, para o identificador de ordem, o número, por exemplo, 255, já esteja sendo usado, enquanto o identificador 0 ou 1, ou qualquer um menor, já esteja livre e possa ser usado em novas ordens de negociação. Este método é usado para procurar o número mais baixo de identificador livre.
Primeiro, é criado o objeto temporário da classe da ordem pendente
e é gerado um identificador com valor -1. Esse valor nos informará
que não há identificadores livres, quer dizer, notificará que todos os 255 estão ocupados e o valor
0 será retornado se a criação de um objeto temporário falhar. Em seguida, num
ciclo de possíveis números de identificadores ( de 1 a 255), verificamos na lista de ordens pendentes se o objeto de ordem contendo um
identificador igual ao valor atual do índice de loop existe. Para fazer isso, primeiro ao
objeto temporário atribuímos um identificador igual ao número do índice do ciclo, à
lista atribuímos um sinalizador de lista classificada, e simplesmente na
lista procuramos um objeto-ordem contendo esse identificador, isto é, um igual ao objeto temporário, ao qual o índice de ciclo é
atribuído como um identificador. Se na lista não for encontrado um objeto assim,
ao valor retornado do método atribuímos um tamanho de índice de ciclo e
interrompemos o ciclo.
No final do ciclo o objeto-ordem
temporária é excluído e retornamos valor de identificador,
que pode ser -1, ou de 1 a 255.
Na seção pública da classe declaramos o método para criar uma ordem pendente,
e inserimos os métodos para definir
e retornar valores de identificadores de/para valor da propriedade de
ordem/posição do "número mágico":
//--- Create a pending request bool CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj); //--- Data location in the magic number int value //----------------------------------------------------------- // bit 32|31 24|23 16|15 8|7 0| //----------------------------------------------------------- // byte | 3 | 2 | 1 | 0 | //----------------------------------------------------------- // data | uchar | uchar | ushort | //----------------------------------------------------------- // descr |pend req id| id2 | id1 | magic | //----------------------------------------------------------- //--- Set the ID of the (1) first group, (2) second group, (3) pending request to the magic number value void SetGroupID1(const uchar group,uint &magic) { magic &=0xFFF0FFFF; magic |= uint(ConvToXX(group,0)<<16); } void SetGroupID2(const uchar group,uint &magic) { magic &=0xFF0FFFFF; magic |= uint(ConvToXX(group,1)<<16); } void SetPendReqID(const uchar id,uint &magic) { magic &=0x00FFFFFF; magic |= (uint)id<<24; } //--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones) uchar ConvToXX(const uchar number,const uchar index) const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index)));} //--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value ushort GetMagicID(const uint magic) const { return ushort(magic & 0xFFFF); } uchar GetGroupID1(const uint magic) const { return uchar(magic>>16) & 0x0F; } uchar GetGroupID2(const uint magic) const { return uchar((magic>>16) & 0xF0)>>4; } uchar GetPendReqID(const uint magic) const { return uchar(magic>>24) & 0xFF; }
Nós consideramos acima os métodos de retorno de valores. Aqui
eles são idênticos. Vejamos os métodos para configurar
identificadores.
Como temos dois identificadores de grupo armazenados em apenas um byte e o valor numérico do identificador pode ser apenas de 0 a 15 (4 bytes),
para definir o identificador do segundo grupo precisamos deslocar seu valor 4 bits para a esquerda — para armazená-lo nos quatro bits
iniciais de um número de um byte. Para isso, foi criado o método ConvToXX(),
que, dependendo do índice do grupo (0 ou 1), pode quer deslocar o número transferido a ele (de 0 a 15) 4 bits para a esquerda (segundo grupo,
índice 1) quer não deslocar (primeiro grupo, índice 0).
Para definir o valor do identificador do primeiro grupo, primeiro precisamos zerar os quatro bits finais do byte no qual armazenaremos o valor
do identificador. Isso pode ser feito aplicando uma máscara ao valor do magic, na qual, para cada meio byte (4 bits), use o valor F.
Em
outras palavras, para aqueles bits em que o valor deve permanecer inalterado, sobrepomos o valor hexadecimal do número decimal 15 (F) e,
para os bits que precisam ser apagados, sobrepomos um zero. Assim, a máscara sobreposta ao valor do número mágico será: 0x FFF0FFFF.
Aqui:
- FFFF — deixamos o identificador do magic (o magic definido nas configurações do EA),
- F0 — apagamos (0) os quatro bits finais no byte que armazena os identificadores de grupo, ficam os iniciais (F), pois neles é armazenado o identificador do segundo grupo,
- FF — deixamos o valor do identificador da ordem pendente
Em seguida, no byte preparado para armazenar os identificadores de grupo colocamos o número do grupo, obtido a partir do método ConvToXX() com índice 0, e deslocado 16 bits para a esquerda — para que o número caia no byte necessário, onde são armazenados os identificadores de grupo.
Para definir o valor do identificador do segundo grupo, zeramos os quatro bits iniciais do byte nos quais armazenaremos o valor do
identificador. Fazemos isso sobrepondo ao valor do magic a máscara 0x FF0FFFFF.
Aqui:
- FFFF — deixamos o identificador do magic (o magic definido nas configurações do EA),
- 0F — apagamos (0) os quatro bits iniciais no byte que armazena os identificadores de grupo, ficam os finais (F), pois neles é armazenado o identificador do primeiro grupo,
- FF — deixamos o valor do identificador da ordem pendente
Em seguida, no byte preparado para armazenar os identificadores de grupo colocamos o número do grupo, obtido a partir do método ConvToXX() com índice 1, e deslocado 16 bits para a esquerda — para que o número caia no byte necessário, onde são armazenados os identificadores de grupo.
Para definir o valor do identificador da ordem pendente, zeramos o valor do byte em que armazenaremos o valor do identificador. Fazemos isso
sobrepondo ao valor do magic a máscara 0x 00FFFFFF.
Aqui:
- FFFF — deixamos o identificador do magic (o magic definido nas configurações do EA),
- FF — deixamos os valores dos identificadores de grupo,
- 00 — apagamos o valor do identificador da ordem
pendente
Em seguida, no byte preparado para armazenar o identificador da ordem pendente colocamos o valor uchar do identificador, deslocado 24 bits para a esquerda — para que o número obtido caia no byte necessário, onde será armazenado o identificador da ordem pendente.
Fora do corpo da classe, escrevemos a implementação do método para criação do objeto-ordem pendente:
//+------------------------------------------------------------------+ //| Create a pending request | //+------------------------------------------------------------------+ bool CTrading::CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj) { //--- Create a new pending request object CPendingReq *req_obj=new CPendingReq(id,symbol_obj.Bid(),symbol_obj.Time(),request,retcode); if(req_obj==NULL) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ)); return false; } //--- If failed to add the request to the list, display the appropriate message, //--- remove the created object and return 'false' if(!this.m_list_request.Add(req_obj)) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ)); delete req_obj; return false; } //--- Filled in the fields of a successfully created object by the values passed to the method req_obj.SetTimeActivate(symbol_obj.Time()+wait); req_obj.SetWaitingMSC(wait); req_obj.SetCurrentAttempt(0); req_obj.SetTotalAttempts(attempts); return true; } //+------------------------------------------------------------------+
O método é simples — é criado um novo objeto-ordem, à lista é adicionada uma ordem pendente, são preenchidos os campo do objeto com os valores transferidos ao método (o tempo de ativação da ordem é calculado como hora de criação da ordem + tempo de espera) e é retornado true. Em caso de erro, é retornado false.
No temporizador da classe de negociação, que criamos no artigo anterior, escrevemos a lógica para trabalhar com ordens pendentes:
//+------------------------------------------------------------------+ //| Timer | //+------------------------------------------------------------------+ void CTrading::OnTimer(void) { //--- In a loop by the list of pending requests int total=this.m_list_request.Total(); for(int i=total-1;i>WRONG_VALUE;i--) { //--- receive the next request object CPendingReq *req_obj=this.m_list_request.At(i); if(req_obj==NULL) continue; //--- if the current attempt exceeds the defined number of trading attempts, //--- remove the current request object and move on to the next one if(req_obj.CurrentAttempt()>req_obj.TotalAttempts() || req_obj.CurrentAttempt()>=UCHAR_MAX) { this.m_list_request.Delete(i); continue; } //--- get the request structure and the symbol object a trading operation should be performed for MqlTradeRequest request=req_obj.MqlRequest(); CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(request.symbol); if(symbol_obj==NULL || !symbol_obj.RefreshRates()) continue; //--- Set the request activation time in the request object req_obj.SetTimeActivate(req_obj.TimeCreate()+req_obj.WaitingMSC()*(req_obj.CurrentAttempt()+1)); //--- If the current time is less than the request activation time, //--- this is not the request time - move on to the next request in the list if(symbol_obj.Time()<req_obj.TimeActivate()) continue; //--- Set the attempt number in the request object req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()+1)); //--- Get the pending request ID uchar id=this.GetPendReqID((uint)request.magic); //--- Get the list of orders/positions containing the order/position with the pending request ID CArrayObj *list=this.m_market.GetList(ORDER_PROP_PEND_REQ_ID,id,EQUAL); if(::CheckPointer(list)==POINTER_INVALID) continue; //--- Depending on the type of action performed in the trading request switch(request.action) { //--- Open a position case TRADE_ACTION_DEAL : //--- if there is no position/order with the obtained pending request ID (the list is empty), send a trading request if(list.Total()==0) { this.OpenPosition((ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation); } //--- if a position/order with the obtained pending request ID is already present (the list is empty), the request has been handled and should be removed else this.m_list_request.Delete(i); break; //--- default: break; } } } //+------------------------------------------------------------------+
A lógica de funcionamento é descrita em detalhes nos comentários do código. A única coisa que pode ser ressaltada é o cálculo do tempo de
ativação da ordem de negociação seguinte. O tempo é calculado como o "tempo de espera do objeto-ordem" + tempo de espera em milissegundos *
número da próxima tentativa. Assim, vinculamos o tempo da ordem à hora da criação da primeira ordem e o número da tentativa — quanto maior o
número da tentativa, mais tempo deve passar entre a criação do objeto e a sua ativação. Este tempo aumenta discretamente: ao esperar 10
segundos, a primeira tentativa deve ocorrer depois de 10 segundos, a segunda depois de 20 segundos, a terceira depois de 30, etc. Assim, o
intervalo entre tentativas regulares de negociação será sempre menor que o tempo de espera especificado entre elas.
No método que retorna o processamento de erros, para o bloco que retorna o método de processamento "esperar" deslocamos o código de erro de falta de conexão com o servidor de negociação . Anteriormente, este código era processado como "criar uma ordem de negociação pendente":
//+------------------------------------------------------------------+ //| Return the error handling method | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(const uint result_code) { switch(result_code) { #ifdef __MQL4__ //--- Malfunctional trade operation case 9 : //--- Account disabled case 64 : //--- Invalid account number case 65 : return ERROR_CODE_PROCESSING_METHOD_DISABLE; //--- No error but result is unknown case 1 : //--- General error case 2 : //--- Old client terminal version case 5 : //--- Not enough rights case 7 : //--- Market closed case 132 : //--- Trading disabled case 133 : //--- Order is locked and being processed case 139 : //--- Buy only case 140 : //--- The number of open and pending orders has reached the limit set by the broker case 148 : //--- Attempt to open an opposite order if hedging is disabled case 149 : //--- Attempt to close a position on a symbol contradicts the FIFO rule case 150 : return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- Invalid trading request parameters case 3 : //--- Invalid price case 129 : //--- Invalid stop levels case 130 : //--- Invalid volume case 131 : //--- Not enough money to perform the operation case 134 : //--- Expirations are denied by broker case 147 : return ERROR_CODE_PROCESSING_METHOD_CORRECT; //--- Trade server is busy case 4 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- No connection to the trade server case 6 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Too frequent requests case 8 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- No price case 136 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Broker is busy case 137 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Too many requests case 141 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Modification denied because the order is too close to market case 145 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Trade context is busy case 146 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)1000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Trade timeout case 128 : //--- Price has changed case 135 : //--- New prices case 138 : return ERROR_CODE_PROCESSING_METHOD_REFRESH; //--- MQL5 #else //--- Auto trading disabled by the server case 10026 : return ERROR_CODE_PROCESSING_METHOD_DISABLE; //--- Request canceled by a trader case 10007 : //--- Request expired case 10012 : //--- Trading disabled case 10017 : //--- Market closed case 10018 : //--- Order status changed case 10023 : //--- Request unchanged case 10025 : //--- Request blocked for handling case 10028 : //--- Transaction is allowed for live accounts only case 10032 : //--- The maximum number of pending orders is reached case 10033 : //--- Reached the maximum order and position volume for this symbol case 10034 : //--- Invalid or prohibited order type case 10035 : //--- Position with the specified ID already closed case 10036 : //--- A close order is already present for a specified position case 10039 : //--- The maximum number of open positions is reached case 10040 : //--- Request to activate a pending order is rejected, the order is canceled case 10041 : //--- Request is rejected, because the rule "Only long positions are allowed" is set for the symbol case 10042 : //--- Request is rejected, because the rule "Only short positions are allowed" is set for the symbol case 10043 : //--- Request is rejected, because the rule "Only closing of existing positions is allowed" is set for the symbol case 10044 : //--- Request is rejected, because the rule "Only closing of existing positions by FIFO rule is allowed" is set for the symbol case 10045 : return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- Requote case 10004 : //--- Request rejected case 10006 : //--- Prices changed case 10020 : return ERROR_CODE_PROCESSING_METHOD_REFRESH; //--- Invalid request case 10013 : //--- Invalid request volume case 10014 : //--- Invalid request price case 10015 : //--- Invalid request stop levels case 10016 : //--- Insufficient funds for request execution case 10019 : //--- Invalid order expiration in a request case 10022 : //--- The specified type of order execution by balance is not supported case 10030 : //--- Closed volume exceeds the current position volume case 10038 : return ERROR_CODE_PROCESSING_METHOD_CORRECT; //--- No quotes to handle the request case 10021 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT; //--- Too frequent requests case 10024 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- An order or a position is frozen case 10029 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT; //--- No connection to the trade server case 10031 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)20000; // ERROR_CODE_PROCESSING_METHOD_WAIT; //--- Request handling error case 10011 : //--- Auto trading disabled by the client terminal case 10027 : return ERROR_CODE_PROCESSING_METHOD_PENDING; //--- Order placed case 10008 : //--- Request executed case 10009 : //--- Request executed partially case 10010 : #endif //--- "OK" default: break; } return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
Porque é assim? Primeiro, para testar a criação de ordens pendentes com espera, devemos retornar uma espera de 20 segundos entre ordens, segundo, porque assim é mais fácil várias tentativas de negociação com espera de conexão com servidor de negociação. De qualquer forma, esta é a primeira opção de teste para processar ordens pendentes e, em seguida, será desenvolvida e alterada.
Como hoje estamos testando apenas a ideia, criaremos uma ordem pendente apenas para abrir uma posição e somente quando um erro for recebido do servidor de negociação. Por enquanto, ao verificar se as ordens de negociação estão corretas, não criaremos ordens pendentes, e deixaremos a espera dentro do método de abertura de posição.
Ao método de abertura de posição adicionamos o bloco de criação de ordem pendente:
//+------------------------------------------------------------------+ //| Open a position | //+------------------------------------------------------------------+ template<typename SL,typename TP> bool CTrading::OpenPosition(const ENUM_POSITION_TYPE type, const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX) { //--- Set the trading request result as 'true' and the error flag as "no errors" bool res=true; this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)type; ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type; //--- Get a symbol object by a symbol name. If failed to get CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); return false; } //--- get a trading object from a symbol object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); return false; } //--- Set the prices //--- If failed to set - write the "internal error" flag, set the error code in the return structure, //--- display the message in the journal and return 'false' if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj)) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; trade_obj.SetResultRetcode(10021); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(10021)); // No quotes to process the request return false; } //--- Write the volume to the request structure this.m_request.volume=volume; //--- Get the method of handling errors from the CheckErrors() method while checking for errors in the request parameters double pr=(type==POSITION_TYPE_BUY ? symbol_obj.Ask() : symbol_obj.Bid()); ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,pr,action,order_type,symbol_obj,trade_obj,DFUN,0,this.m_request.sl,this.m_request.tp); //--- In case of trading limitations, funds insufficiency, //--- if there are limitations by StopLevel or FreezeLevel ... if(method!=ERROR_CODE_PROCESSING_METHOD_OK) { //--- If trading is completely disabled, set the error code to the return structure, //--- display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE) { trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "abort trading operation" - set the last error code to the return structure, //--- display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { int code=this.m_list_errors.At(this.m_list_errors.Total()-1); if(code!=NULL) { trade_obj.SetResultRetcode(code); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); } if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "waiting" - set the last error code to the return structure and display the message in the journal if(method==ERROR_CODE_PROCESSING_METHOD_WAIT) { int code=this.m_list_errors.At(this.m_list_errors.Total()-1); if(code!=NULL) { trade_obj.SetResultRetcode(code); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); } if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); //--- Instead of creating a pending request, we temporarily wait the required time period (the CheckErrors() method result is returned) ::Sleep(method); //--- after waiting, update all data symbol_obj.Refresh(); } //--- If the check result is "create a pending request", do nothing temporarily if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); } } //--- In the loop by the number of attempts for(int i=0;i<this.m_total_try;i++) { //--- Send the request res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation); //--- If the request is executed successfully or the asynchronous order sending mode is set, play the success sound //--- set for a symbol trading object for this type of trading operation and return 'true' if(res || trade_obj.IsAsyncMode()) { if(this.IsUseSounds()) trade_obj.PlaySoundSuccess(action,order_type); return true; } //--- If the request is not successful, play the error sound set for a symbol trading object for this type of trading operation else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRY_N),string(i+1),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode())); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); //--- Get the error handling method method=this.ResultProccessingMethod(trade_obj.GetResultRetcode()); //--- If "Disable trading for the EA" is received as a result of sending a request, enable the disabling flag and end the attempt loop if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE) { this.SetTradingDisableFlag(true); break; } //--- If "Exit the trading method" is received as a result of sending a request, end the attempt loop if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { break; } //--- If "Correct the parameters and repeat" is received as a result of sending a request - //--- correct the parameters and start the next iteration if(method==ERROR_CODE_PROCESSING_METHOD_CORRECT) { this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj,trade_obj); continue; } //--- If "Update data and repeat" is received as a result of sending a request - //--- update data and start the next iteration if(method==ERROR_CODE_PROCESSING_METHOD_REFRESH) { symbol_obj.Refresh(); continue; } //--- If "Wait and repeat" or "Create a pending request" is received as a result of sending a request, //--- create a pending request and end the loop if(method>ERROR_CODE_PROCESSING_METHOD_REFRESH) { //--- If the trading request magic number, has no pending request ID if(this.GetPendReqID((uint)magic)==0) { //--- Waiting time in milliseconds: //--- for the "Wait and repeat" handling method, the waiting value corresponds to the 'method' value, //--- for the "Create a pending request" handling method - till there is a zero waiting time ulong wait=(method>ERROR_CODE_PROCESSING_METHOD_PENDING ? method : 0); //--- Look for the least of the possible IDs. If failed to find //--- or in case of an error while updating the current symbol data, return 'false' int id=this.GetFreeID(); if(id<1 || !symbol_obj.RefreshRates()) return false; //--- Write the request ID to the magic number, while a symbol name is set in the request structure //--- Set position and trading operation types (the remaining structure fields are already filled in) uint mn=(magic==ULONG_MAX ? (uint)trade_obj.GetMagic() : (uint)magic); this.SetPendReqID((uchar)id,mn); this.m_request.magic=mn; this.m_request.symbol=symbol_obj.Name(); this.m_request.action=TRADE_ACTION_DEAL; this.m_request.type=order_type; //--- Pass the number of trading attempts minus one to the pending request, //--- since there already has been one failed attempt uchar attempts=(this.m_total_try-1 < 1 ? 1 : this.m_total_try-1); this.CreatePendingRequest((uchar)id,attempts,wait,this.m_request,trade_obj.GetResultRetcode(),symbol_obj); break; } } } } //--- Return the result of sending a trading request in a symbol trading object return res; } //+------------------------------------------------------------------+
Aqui tudo está descrito em detalhes nos comentários do código, e espero que tudo esteja suficientemente claro para que seja possível estudar independente o assunto. De qualquer forma, todas as questões podem ser discutidas nos comentários do artigo.
Realizamos pequenas alterações no arquivo Engine.mqh da classe do objeto base da biblioteca CEngine.
No bloco de métodos para definir propriedades de objetos de negociação, escrevemos método
para definir o número de tentativas de negociação:
//--- Set the following for the trading classes: //--- (1) correct filling policy, (2) filling policy, //--- (3) correct order expiration type, (4) order expiration type, //--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date, //--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) number of trading attempts void TradingSetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL); void TradingSetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL); void TradingSetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL); void TradingSetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL); void TradingSetMagic(const uint magic,const string symbol_name=NULL); void TradingSetComment(const string comment,const string symbol_name=NULL); void TradingSetDeviation(const ulong deviation,const string symbol_name=NULL); void TradingSetVolume(const double volume=0,const string symbol_name=NULL); void TradingSetExpiration(const datetime expiration=0,const string symbol_name=NULL); void TradingSetAsyncMode(const bool async_mode=false,const string symbol_name=NULL); void TradingSetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol_name=NULL); void TradingSetTotalTry(const uchar attempts) { this.m_trading.SetTotalTry(attempts); } //--- Set standard sounds (symbol==NULL) for a symbol trading object, (symbol!=NULL) for trading objects of all symbols
O método simplesmente chama o método da classe de negociação com o mesmo nome.
Na seção privada da classe, inserimos método para converter valores de
identificador de grupo em valores uchar,
e na seção pública declaramos
o método para criar e retornar o valor de magic composto:
//--- Constructor/destructor CEngine(); ~CEngine(); private: //--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones) uchar ConvToXX(const uchar number,const uchar index) const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index))); } public: //--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID uint SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0); }; //+------------------------------------------------------------------+
Já consideramos acima o método de conversão. O método para criar um magic composto é usado para combinar os valores do número mágico, do
primeiro e do segundo grupos e do identificador da ordem pendente num único número mágico, atribuído à ordem ao ser enviada ao servidor.
Fora
do corpo da classe, escrevemos sua implementação:
//+------------------------------------------------------------------+ //| Create and return the composite magic number | //| from the specified magic number value, | //| first and seconf group IDs and | //| the pending request ID | //+------------------------------------------------------------------+ uint CEngine::SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0) { uint magic=magic_id; this.m_trading.SetGroupID1(group_id1,magic); this.m_trading.SetGroupID2(group_id2,magic); this.m_trading.SetPendReqID(pending_req_id,magic); return magic; } //+------------------------------------------------------------------+
Ao método são transferidos todos os identificadores e, com a ajuda dos métodos descritos anteriormente da classe de negociação para definir identificadores, são adicionados ao valor do número mágico, retornado pelo método.
Em geral, é tudo o que precisamos fazer como parte deste artigo para verificar o ideia proposta.
Para testar a criação e o processamento de ordem pendente, precisamos simular uma situação de erro que exija uma segunda ordem após a espera. Se você se lembra, fizemos o processamento do erro "não há conexão com o servidor de negociação" como uma espera de 20 segundos. Por padrão, definimos cinco tentativas de negociação. Portanto, precisamos apenas iniciar o EA, desconectar a Internet (desconectar do servidor de negociação) e tentar abrir uma posição (botão Buy ou Sell no painel de negociação do EA de teste). Após receber o erro, teremos 20 * 5 = 100 segundos para ligar novamente a Internet e ver como o EA processa a ordem pendente criada. Após decorridos 100 segundos, que é o tempo para realizar cinco tentativas, a ordem pendente deve ser removida automaticamente da lista de ordens (após a restauração da conexão com o servidor, pois o tempo pode ser obtido apenas quando a conexão é estabelecida). Até o momento, este recurso não tem sido implementado, pois, em primeiro lugar, trata-se apenas de um teste de ordem pendente e, em segundo lugar, a funcionalidade deve ser desenvolvida e exige alterações, só depois disso é que serão realizadas as possibilidades restantes. Em outras palavras, após a restauração da conexão com o servidor de negociação, o EA começará a enviar ordens de negociação registradas no objeto-ordem. Após a primeira tentativa repetida, deve ser aberta a posição e a ordem pendente de objeto é removida da lista de ordens.
Juntamente com a ordem pendente, implementamos o armazenamento de vários identificadores no valor do número mágico. Para testar a definição destes identificadores no magic da ordem enviada, faremos uma seleção aleatória dos números do primeiro e do segundo subgrupos nos grupos 1 e 2 e os escreveremos na propriedade de ordem "número mágico". Ao abrir uma posição, no log, veremos o valor real do magic da posição aberta ou da ordem efetuada, bem como o identificador do magic especificado nas configurações (entre parênteses após o valor verdadeiro do número mágico) e os identificadores dos subgrupos no primeiro e no segundo grupos (indicados como G1 e G2 )
Teste
Para testar ordens pendentes, pegaremos o EA do artigo anterior e o salvaremos na nova pasta \MQL5\Experts\TestDoEasy\Part26\ com o novo nome TestDoEasyPart26.mq5.
Nos parâmetros de entrada do EA mudamos o tipo de magic de ulong
para ushort—
agora o tamanho máximo para o magic não pode exceder dois bytes (65535) e também adicionamos outra variável — número
de tentativas de negociação:
//--- input variables input ushort InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 150; // StopLoss in points input uint InpTakeProfit = 150; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpSlippage = 5; // Slippage in points input uint InpSpreadMultiplier = 1; // Spread multiplier for adjusting stop-orders by StopLevel input uchar InpTotalAttempts = 5; // Number of trading attempts sinput double InpWithdrawal = 10; // Withdrawal funds (in tester) sinput uint InpButtShiftX = 40; // Buttons X shift sinput uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; // Mode of used symbols list sinput string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; // List of used symbols (comma - separator) sinput bool InpUseSounds = true; // Use sounds //--- global variables
Nas variáveis globais alteramos o tipo de variável magic_number de ulong
para ushort, e adicionamos duas
variáveis para armazenar valores de grupo:
//--- global variables CEngine engine; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ushort magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; bool trailing_on; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; int used_symbols_mode; string used_symbols; string array_used_symbols[]; bool testing; uchar group1; uchar group2; //+------------------------------------------------------------------+
No manipulador OnInit() inicializamos aas variáveis de grupo
e configuramos o estado inicial
para gerar números pseudo-aleatórios:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; testing=engine.IsTester(); for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; //--- Initialize random group numbers group1=0; group2=0; srand(GetTickCount()); //--- Initialize DoEasy library OnInitDoEasy();
Na função de inicialização da biblioteca definimos o magic por padrão
para todos os objetos de negociação e o número de tentativas de negociação:
//+------------------------------------------------------------------+ //| Initializing DoEasy library | //+------------------------------------------------------------------+ void OnInitDoEasy() { //--- Check if working with the full list is selected used_symbols_mode=InpModeUsedSymbols; if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL) { int total=SymbolsTotal(false); string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов."; string en_n="\nNumber of symbols on server "+(string)total+".\nMaximum number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols."; string caption=TextByLanguage("Внимание!","Attention!"); string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\""; string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\""; string message=TextByLanguage(ru,en); int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); int mb_res=MessageBox(message,caption,flags); switch(mb_res) { case IDNO : used_symbols_mode=SYMBOLS_MODE_CURRENT; break; default: break; } } //--- Fill in the array of used symbols used_symbols=InpUsedSymbols; CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols); //--- Set the type of the used symbol list in the symbol collection engine.SetUsedSymbols(array_used_symbols); //--- Displaying the selected mode of working with the symbol object collection Print(engine.ModeSymbolsListDescription(),TextByLanguage(". Number of used symbols: ",". Number of symbols used: "),engine.GetSymbolsCollectionTotal()); //--- Create resource text files engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","Falling coin 1"),sound_array_coin_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Falling coins"),sound_array_coin_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Coins"),sound_array_coin_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","Falling coin 2"),sound_array_coin_04); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Button click 1"),sound_array_click_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Button click 2"),sound_array_click_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Button click 3"),sound_array_click_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","Cash machine"),sound_array_cash_machine_01); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red); //--- Pass all existing collections to the trading class engine.TradingOnInit(); //--- Set the default magic number for all used symbols engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number)); //--- Set synchronous passing of orders for all used symbols engine.TradingSetAsyncMode(false); //--- Set the number of trading attempts in case of an error engine.TradingSetTotalTry(InpTotalAttempts); //--- Set standard sounds for trading objects of all used symbols engine.SetSoundsStandart(); //--- Set the general flag of using sounds engine.SetUseSounds(InpUseSounds); //--- Set the spread multiplier for symbol trading objects in the symbol collection engine.SetSpreadMultiplier(InpSpreadMultiplier); //--- Set controlled values for symbols //--- Get the list of all collection symbols CArrayObj *list=engine.GetListAllUsedSymbols(); if(list!=NULL && list.Total()!=0) { //--- In a loop by the list, set the necessary values for tracked symbol properties //--- By default, the LONG_MAX value is set to all properties, which means "Do not track this property" //--- It can be enabled or disabled (by setting the value less than LONG_MAX or vice versa - set the LONG_MAX value) at any time and anywhere in the program /* for(int i=0;i<list.Total();i++) { CSymbol* symbol=list.At(i); if(symbol==NULL) continue; //--- Set control of the symbol price increase by 100 points symbol.SetControlBidInc(100000*symbol.Point()); //--- Set control of the symbol price decrease by 100 points symbol.SetControlBidDec(100000*symbol.Point()); //--- Set control of the symbol spread increase by 40 points symbol.SetControlSpreadInc(400); //--- Set control of the symbol spread decrease by 40 points symbol.SetControlSpreadDec(400); //--- Set control of the current spread by the value of 40 points symbol.SetControlSpreadLevel(400); } */ } //--- Set controlled values for the current account CAccount* account=engine.GetAccountCurrent(); if(account!=NULL) { //--- Set control of the profit increase to 10 account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0); //--- Set control of the funds increase to 15 account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0); //--- Set profit control level to 20 account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0); } } //+------------------------------------------------------------------+
Para testar o magic com valores aleatórios de identificadores de grupo, introduzimos a variável booleana comp_magic
igual a true e que indica o uso de magic composto nas funções de abertura de
posições/colocação de ordens pendentes. Em vez de usar uma variável magic_number, inserimos a nova variável magic
que armazena o valor do magic, dependendo do valor da variável comp_magic.
Ao definir o valor em magic (magic permanente definido nas configurações ou um magic composto que consiste no magic definido + valores
aleatórios dos identificadores dos grupos 1 e 2), verificaremos o valor comp_magic, e se for true,
usaremos o magic composto, já se for false ,
utilizaremos o definido nas configurações.
Faremos alterações na função para processar os botões do painel de negociação do EA PressButtonEvents():
//+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { bool comp_magic=true; // Temporary variable selecting the composite magic number with random group IDs string comment=""; //--- Convert button name into its string ID string button=StringSubstr(button_name,StringLen(prefix)); //--- Random group 1 and 2 numbers within the range of 0 - 15 group1=(uchar)Rand(); group2=(uchar)Rand(); uint magic=(comp_magic ? engine.SetCompositeMagicNumber(magic_number,group1,group2) : magic_number); //--- If the button is pressed if(ButtonState(button_name)) { //--- If the BUTT_BUY button is pressed: Open Buy position if(button==EnumToString(BUTT_BUY)) { //--- Open Buy position engine.OpenBuy(lot,Symbol(),magic,stoploss,takeprofit); // No comment - the default comment is to be set } //--- If the BUTT_BUY_LIMIT button is pressed: Place BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Set BuyLimit order engine.PlaceBuyLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyLimit","Pending BuyLimit order")); } //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop else if(button==EnumToString(BUTT_BUY_STOP)) { //--- Set BuyStop order engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStop","Pending BuyStop order")); } //--- If the BUTT_BUY_STOP_LIMIT button is pressed: Set BuyStopLimit else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- Set BuyStopLimit order engine.PlaceBuyStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStopLimit","Pending BuyStopLimit order")); } //--- If the BUTT_SELL button is pressed: Open Sell position else if(button==EnumToString(BUTT_SELL)) { //--- Open Sell position engine.OpenSell(lot,Symbol(),magic,stoploss,takeprofit); // No comment - the default comment is to be set } //--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- Set SellLimit order engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending SellLimit order")); } //--- If the BUTT_SELL_STOP button is pressed: Set SellStop else if(button==EnumToString(BUTT_SELL_STOP)) { //--- Set SellStop order engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStop","Pending SellStop order")); } //--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- Set SellStopLimit order engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStopLimit","Pending SellStopLimit order")); } //--- If the BUTT_CLOSE_BUY button is pressed: Close Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY))
Em todas as linhas de chamada para os métodos de negociação da biblioteca, substituímos a variável magic_number pela variável magic.
Para definir um valor aleatório para identificadores de grupo, neles inserimos o valor retornado pela função Rand(), onde são definidos por padrão os valores mínimo e máximo do intervalo, no qual a função retornará um número pseudo-aleatório:
//+------------------------------------------------------------------+ //| A random value within the range | //+------------------------------------------------------------------+ uint Rand(const uint min=0, const uint max=15) { return (rand() % (max+1-min))+min; } //+------------------------------------------------------------------+
Compilamos e iniciamos o EA. Desligamos a Internet de qualquer forma e aguardamos tal ícone no canto inferior direito do terminal:
Após desconectar a Internet e clicar no botão Sell, o servidor de negociação retorna um erro e no log são exibidas as seguintes entradas:
2019.11.26 15:34:48.661 CTrading::OpenPosition<uint,uint>: Invalid request: 2019.11.26 15:34:48.661 No connection with the trade server 2019.11.26 15:34:48.661 Correction of trade request parameters ... 2019.11.26 15:34:48.661 Trading attempt #1. Error: No connection with the trade server
Após receber desse erro, a biblioteca cria uma ordem pendente com os parâmetros existentes no momento da tentativa sem êxito de abrir uma
posição curta.
A ordem pendente também contém o número de tentativas e um tempo de espera de 20 segundos.
Em seguida, conectamos a Internet, restaurando assim a comunicação com o servidor de negociação:
Assim que a conexão é restaurada, a biblioteca começa a processar a ordem pendente, enviando-a para o servidor. Como resultado, temos uma posição aberta com entradas no log:
2019.11.26 15:35:00.853 CTrading::OpenPosition<double,double>: Invalid request: 2019.11.26 15:35:00.853 Trading is prohibited for the current account 2019.11.26 15:35:00.853 Correction of trade request parameters ... 2019.11.26 15:35:00.853 Trading operation aborted 2019.11.26 15:35:01.192 CTrading::OpenPosition<double,double>: Invalid request: 2019.11.26 15:35:01.192 Trading is prohibited for the current account 2019.11.26 15:35:01.192 Correction of trade request parameters ... 2019.11.26 15:35:01.192 Trading operation aborted 2019.11.26 15:35:01.942 - Position is open: 2019.11.26 10:35:01.660 - 2019.11.26 15:35:01.942 EURUSD Opened 0.10 Sell #486405595 [0.10 Market-order Sell #486405595] at price 1.10126, sl 1.10285, tp 1.09985, Magic number 17629307 (123), G1: 13 2019.11.26 15:35:01.942 OnDoEasyEvent: Position is open
Como se pode ver no log, após reconectar ao servidor de negociação, a
permissão de negociação para a conta atual não foi ativada imediatamente.
Mas, seja como for, a ordem pendente fez o que devia fazer...
Também no log vemos o número mágico real 17629307, atrás dele entre parênteses vemos o magic definido nas configurações do EA (123), além de uma entrada G1: 13, que nos diz que o identificador do primeiro grupo é 13 e o identificador do segundo grupo não existe, pois seu valor acabou sendo zero, portanto, não foi gerao o segundo registro com os identificadores do segundo grupo G2: XX
Lembre-se:
Chamo bastante a atenção para o fato de que o resultado de uma classe de negociação com as ordens pendentes descritas neste artigo e o EA de teste anexado ao artigo não deve, em caso algum, ser usado para negociação real!Este artigo, seus materiais e o resultado são apenas uma verificação de teste da ideia de ordens pendentes e, nesta apresentação, não são um produto acabado para uso em contas reais, mas, sim, para uso somente em modo de demonstração ou no testador.
O que vem agora?
Nos artigos a seguir, continuamos a desenvolver a classe de ordem pendente.
Abaixo estão anexados todos os arquivos da versão atual da biblioteca e os arquivos do EA de teste. Você pode baixá-los e testar tudo sozinho.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.
Artigos desta série:
Parte 1. Conceito, gerenciamento de dados e primeiros resultados
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ções de eventos de negociação. Envio de eventos para o programa
Parte 6. Eventos da conta netting
Parte
7. Eventos de ativação da ordem stoplimit, preparação da funcionalidade para os eventos de modificação de ordens e posições
Parte
8. Eventos de modificação de ordens e posições
Parte 9. Compatibilidade com a MQL4 —
preparação dos dados
Parte 10. Compatibilidade com a MQL4 — eventos de abertura de
posição e ativação de ordens pendentes
Parte 11. Compatibilidade com a MQL4 —
eventos de encerramento de posição
Parte 12. Implementação da classe de objeto
"conta" e da coleção de objetos da conta
Parte 13. Eventos do objeto conta
Parte 14. O objeto símbolo
Parte
15. Coleção de objetos-símbolos
Parte 16. Eventos de coleção de símbolos
Parte
17. Interatividade de objetos de biblioteca
Parte 18. Interatividade do
objeto-conta e quaisquer de outros objetos da biblioteca
Parte 19. Classe de
mensagens de biblioteca
Parte 20. Criação e armazenamento de recursos de
programas
Parte 21 Торговые классы — Базовый кроссплатформенный торговый объект
Parte 22. Торговые классы — Основной торговый класс, контроль ограничений
Parte 23. Торговые классы — Основной торговый класс, контроль допустимых параметров
Parte 24. Торговые классы — Основной торговый класс, автоматическая коррекция ошибочных
параметров
Часть 25. Торговые классы — Основной торговый класс, обработка
ошибок, возвращаемых торговым сервером
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/7394
- 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