MQL5 Trading Toolkit (Parte 4): Desenvolvendo uma Biblioteca EX5 de Gerenciamento de Histórico
Introdução
Nesta envolvente série de artigos, desenvolvemos duas bibliotecas EX5 abrangentes: PositionsManager.ex5, que processa e gerencia posições, e PendingOrdersManager.ex5, que lida com ordens pendentes. Junto a elas, criamos exemplos práticos — incluindo alguns com interfaces gráficas — para demonstrar a implementação dessas bibliotecas de forma eficaz.
Neste artigo, apresentaremos outra biblioteca EX5 essencial, projetada para recuperar e processar o histórico de ordens concluídas, negociações e transações de posição. Além disso, desenvolveremos módulos analíticos para gerar relatórios de negociação que avaliem o desempenho de sistemas de trading, Expert Advisors ou símbolos específicos com base em diferentes critérios flexíveis.
Este artigo serve como um guia prático para desenvolvedores iniciantes de MQL5 que possam considerar desafiador trabalhar com posições, ordens e históricos de negociações. Também será um recurso valioso para qualquer programador MQL5 que busque uma biblioteca para simplificar e aprimorar a eficiência no tratamento de históricos de negociação.
Para começar, abordaremos algumas perguntas críticas que muitos programadores MQL5 — especialmente os que estão iniciando no processamento de históricos de negociações no MetaTrader 5 — costumam achar difíceis de compreender.
Qual é o ciclo de vida de uma transação de negociação em MQL5?
Em MQL5, o ciclo de vida de uma transação de negociação começa com a execução de uma ordem. Essa ordem pode ser classificada em dois tipos principais: uma ordem de mercado direta ou uma ordem pendente.
Entrada de Ordem de Mercado Direta
Uma ordem de mercado direta é uma solicitação em tempo real para comprar ou vender um ativo ao preço atual de mercado (Ask ou Bid). Anteriormente, abordamos como processar essas ordens nos primeiro e segundo artigos enquanto desenvolvíamos a biblioteca Positions Manager.

Uma ordem de mercado direta é executada imediatamente, tornando-a ideal para estratégias de negociação manuais e automatizadas. Uma vez executada, a ordem se transforma em uma posição aberta ativa e recebe um número de tíquete exclusivo e um identificador de posição (POSITION_ID) separado, que é mais confiável para rastrear e gerenciar as várias etapas da posição ao longo de seu ciclo de vida.
Entrada de Ordem Pendente
Uma ordem pendente (BUY STOP, BUY LIMIT, SELL STOP, SELL LIMIT, BUY STOP LIMIT e SELL STOP LIMIT), por outro lado, é uma ordem atrasada que é acionada quando um determinado nível de preço é alcançado. Um guia detalhado sobre o processamento desses tipos de ordens é apresentado no terceiro artigo desta série, onde desenvolvemos a biblioteca Pending Orders Manager.

Até que o preço de mercado se alinhe com o preço de disparo pré-definido da ordem pendente, a ordem permanece inativa. Uma vez acionada, ela se converte em uma ordem de mercado e é executada, recebendo um número de tíquete e um identificador de posição (POSITION_ID) semelhantes aos de uma ordem de mercado direta.
Como o status de uma posição pode mudar durante sua vida útil?
Ao longo da vida de uma posição, seu status pode mudar devido a diversos fatores:
- Fechamento Parcial: Se uma parte da posição for encerrada, uma negociação de saída (out) correspondente é registrada no histórico de negociações.
- Reversão de Posição: Uma reversão de posição, como uma transação "close by", também é registrada como uma negociação de saída.
- Fechamento Total: Quando toda a posição é encerrada, seja manualmente ou automaticamente por meio de um evento de Take Profit, Stop Loss ou Stop Out devido a chamada de margem, uma negociação de saída final é registrada no histórico de negociações.
Compreender o ciclo de vida de uma operação de negociação em MQL5 é essencial. Toda negociação começa como uma ordem enviada ao servidor de negociações — seja um pedido para abrir uma ordem pendente, executar uma ordem de mercado direta de compra ou venda, ou fechar parcialmente uma posição existente. Independentemente do tipo, todas as operações de negociação são primeiramente registradas como ordens.
Se a ordem for executada com sucesso, ela avança para a próxima etapa e é salva no banco de dados de histórico como uma negociação (deal). Usando as diversas propriedades e funções disponíveis para ordens e negociações, é possível rastrear cada negociação até sua ordem de origem e vinculá-la à posição correspondente. Isso cria um caminho claro e sistemático do ciclo de vida de uma negociação.
Essa abordagem de “rastro de migalhas” permite acompanhar a origem, o progresso e o resultado de cada operação ou transação no ambiente MQL5. Ela fornece uma trilha de auditoria detalhada, incluindo a ordem que iniciou a transação, o momento exato da execução, quaisquer modificações feitas ao longo do processo e o resultado final da negociação (posição). Esse rastreamento não apenas aumenta a transparência, mas também capacita o programador MQL5 a desenvolver algoritmos para analisar estratégias de negociação, identificar áreas de melhoria e otimizar o desempenho.
Em MQL5, uma posição representa uma negociação ativa em andamento (posição) que você mantém atualmente no mercado. Ela está em um estado aberto que reflete uma compra ou venda de um determinado símbolo. Uma negociação, por outro lado, refere-se a uma transação concluída — quando uma posição foi totalmente encerrada. Posições abertas e ordens pendentes ativas são exibidas na janela Toolbox do MetaTrader 5, na guia "Trade".

Posições fechadas (negociações), juntamente com ordens e deals, são mostradas na guia "History" da janela Toolbox.

Para acessar o histórico completo de posições, você pode usar as opções do menu da plataforma e selecionar o item "Positions". O histórico de ordens e negociações também pode ser acessado usando as mesmas opções de menu.

A distinção entre posições e negociações pode ser confusa para programadores iniciantes em MQL5, especialmente ao usar as funções padrão de histórico da plataforma. Este artigo, juntamente com o código detalhado da biblioteca que estamos prestes a criar, fornecerá uma compreensão clara de como as posições e negociações são categorizadas e rastreadas em MQL5. Se você estiver com pouco tempo e precisar de uma biblioteca de histórico pronta para uso, basta seguir a documentação completa no próximo artigo sobre como implementá-la diretamente em seu projeto.
Criar o Arquivo de Código-Fonte da Biblioteca History Manager (.mq5)
Para começar, abra o seu MetaEditor IDE e acesse o MQL Wizard selecionando 'New' no menu. No assistente, escolha criar um novo arquivo de código-fonte Library, que chamaremos de HistoryManager.mq5. Este arquivo será a base de nossas funções principais, dedicadas ao gerenciamento e à análise do histórico de negociações das contas. Ao criar a nova biblioteca HistoryManager.mq5, salve-a na pasta Libraries\Toolkit que estabelecemos no primeiro artigo. Ao armazenar este novo arquivo no mesmo diretório das bibliotecas EX5 Positions Manager e Pending Orders Manager, mantemos uma estrutura organizacional clara e consistente para o projeto. Essa abordagem facilitará a localização e o gerenciamento de cada componente conforme nosso toolkit se expande.

Aqui está como o nosso novo arquivo de código-fonte HistoryManager.mq5 fica. Comece excluindo os comentários "My function" localizados abaixo das diretivas property. As diretivas de propriedade copyright e link no seu arquivo podem diferir, mas isso não afetará o comportamento nem o desempenho do código. Você pode personalizar as diretivas copyright e link com as informações que desejar, mas certifique-se de que a diretiva library permaneça inalterada.
//+------------------------------------------------------------------+ //| HistoryManager.mq5 | //| Copyright 2024, Wanateki Solutions Ltd. | //| https://www.wanateki.com | //+------------------------------------------------------------------+ #property library #property copyright "Copyright 2024, Wanateki Solutions Ltd." #property link "https://www.wanateki.com" #property version "1.00" //+------------------------------------------------------------------+ //| My function | //+------------------------------------------------------------------+ // int MyCalculator(int value,int value2) export // { // return(value+value2); // } //+------------------------------------------------------------------+
Estruturas de Dados, Diretivas de Pré-processador e Variáveis Globais
Em nossa nova biblioteca HistoryManager.mq5, começaremos definindo os seguintes componentes:
- Diretivas de Pré-processador: Ajudarão na ordenação e consulta de vários tipos de histórico de negociações.
- Estruturas de Dados: Armazenarão dados históricos de ordens, negociações, posições e ordens pendentes.
- Arrays Dinâmicos de Estruturas Globais: Conterão todos os dados históricos relevantes na biblioteca.
Definir esses componentes no escopo global garante que sejam acessíveis em toda a biblioteca e possam ser utilizados por todos os diferentes módulos ou funções dentro dela.
Diretivas de Pré-processador
Como nossa biblioteca de gerenciamento de histórico lidará com vários tipos de solicitações, é essencial projetá-la de modo que recupere apenas os dados históricos específicos necessários para cada tipo de requisição. Essa abordagem modular e direcionada aprimorará o desempenho da biblioteca, mantendo a flexibilidade para diferentes casos de uso.
Para alcançar isso, definiremos constantes inteiras que servirão como identificadores para tipos específicos de dados históricos. Essas constantes permitirão que a biblioteca busque apenas os dados necessários, garantindo consumo mínimo de recursos e processamento mais rápido.
Organizaremos os dados de histórico em cinco categorias principais:
- Histórico de Ordens.
- Histórico de Negociações (Deals).
- Histórico de Posições.
- Histórico de Ordens Pendentes.
- Todos os Dados de Histórico.
Usando essas constantes, as funções dentro da biblioteca poderão especificar o tipo de histórico que desejam processar. A função principal de busca de histórico consultará e retornará apenas os dados solicitados, economizando tempo e recursos computacionais. Comecemos definindo essas constantes inteiras logo abaixo das últimas diretivas #property no código.
#define GET_ORDERS_HISTORY_DATA 1001 #define GET_DEALS_HISTORY_DATA 1002 #define GET_POSITIONS_HISTORY_DATA 1003 #define GET_PENDING_ORDERS_HISTORY_DATA 1004 #define GET_ALL_HISTORY_DATA 1005
Estruturas de Dados
Nossa biblioteca EX5 armazenará diversos dados históricos em estruturas de dados declaradas globalmente. Essas estruturas manterão de forma eficiente o histórico de negociações, ordens, posições e ordens pendentes sempre que forem consultadas.
//- Data structure to store deal properties struct DealData { ulong ticket; ulong magic; ENUM_DEAL_ENTRY entry; ENUM_DEAL_TYPE type; ENUM_DEAL_REASON reason; ulong positionId; ulong order; string symbol; string comment; double volume; double price; datetime time; double tpPrice; double slPrice; double commission; double swap; double profit; }; //- Data structure to store order properties struct OrderData { datetime timeSetup; datetime timeDone; datetime expirationTime; ulong ticket; ulong magic; ENUM_ORDER_REASON reason; ENUM_ORDER_TYPE type; ENUM_ORDER_TYPE_FILLING typeFilling; ENUM_ORDER_STATE state; ENUM_ORDER_TYPE_TIME typeTime; ulong positionId; ulong positionById; string symbol; string comment; double volumeInitial; double priceOpen; double priceStopLimit; double tpPrice; double slPrice; }; //- Data structure to store closed position/trade properties struct PositionData { ENUM_POSITION_TYPE type; ulong ticket; ENUM_ORDER_TYPE initiatingOrderType; ulong positionId; bool initiatedByPendingOrder; ulong openingOrderTicket; ulong openingDealTicket; ulong closingDealTicket; string symbol; double volume; double openPrice; double closePrice; datetime openTime; datetime closeTime; long duration; double commission; double swap; double profit; double tpPrice; double slPrice; int tpPips; int slPips; int pipProfit; double netProfit; ulong magic; string comment; }; //- Data structure to store executed or canceled pending order properties struct PendingOrderData { string symbol; ENUM_ORDER_TYPE type; ENUM_ORDER_STATE state; double priceOpen; double tpPrice; double slPrice; int tpPips; int slPips; ulong positionId; ulong ticket; datetime timeSetup; datetime expirationTime; datetime timeDone; ENUM_ORDER_TYPE_TIME typeTime; ulong magic; ENUM_ORDER_REASON reason; ENUM_ORDER_TYPE_FILLING typeFilling; string comment; double volumeInitial; double priceStopLimit; };
Arrays Dinâmicos de Estruturas Globais
As declarações finais no escopo global consistirão nos arrays dinâmicos das estruturas definidas anteriormente. Esses arrays servirão como o principal armazenamento para todos os dados centrais gerenciados pela biblioteca.
OrderData orderInfo[]; DealData dealInfo[]; PositionData positionInfo[]; PendingOrderData pendingOrderInfo[];
Função Get History Data
A função GetHistoryDataFunction() servirá como o núcleo de nossa biblioteca EX5, formando a espinha dorsal de sua funcionalidade. A maioria das outras funções na biblioteca dependerá dela para recuperar o histórico de negociações com base nos períodos e tipos de histórico especificados. Como essa função é destinada apenas para uso interno, ela não será definida como exportável.
Essa função é projetada para buscar os dados históricos solicitados para um determinado período e tipo de histórico. É uma função do tipo bool, o que significa que retornará true se o histórico for recuperado com sucesso e false se a operação falhar.
A função GetHistoryDataFunction() aceita três parâmetros de entrada:
- Duas variáveis datetime, fromDateTime e toDateTime, que especificam o início e o fim do período desejado.
- Um número inteiro sem sinal, dataToGet, que corresponde a uma das constantes predefinidas no início do arquivo.
Combinando essas entradas, a função pode consultar e processar de forma eficiente os dados de histórico necessários. Comecemos definindo a função.
bool GetHistoryData(datetime fromDateTime, datetime toDateTime, uint dataToGet) { return(true); //-- Our function's code will go here }
A primeira tarefa da função será verificar se o intervalo de datas fornecido é válido. Como o tipo de dado datetime em MQL5 é essencialmente um número inteiro long que representa o tempo no formato Unix epoch (ou seja, o número de segundos decorridos desde 1º de janeiro de 1970, 00:00:00 UTC), podemos comparar esses valores diretamente para garantir sua validade. Também é importante observar que, ao solicitar dados de histórico em MQL5, o tempo é baseado no horário do servidor de negociação, e não no horário local da sua máquina.
Para validar o intervalo de datas, verificaremos se o valor de fromDateTime é menor que o valor de toDateTime. Se fromDateTime for maior ou igual a toDateTime, isso indica um período inválido, pois a data inicial não pode ser posterior nem igual à data final. Se o período fornecido falhar na validação, retornaremos false e encerraremos a função.
if(fromDateTime >= toDateTime) { //- Invalid time period selected Print("Invalid time period provided. Can't load history!"); return(false); }
Uma vez verificadas as datas e o período, redefiniremos o cache de erros do MQL5 para garantir códigos de erro precisos caso ocorram problemas. Em seguida, chamaremos a função HistorySelect() dentro de uma estrutura if-else, passando os valores datetime validados para recuperar o histórico de deals e orders para o período especificado. Como HistorySelect() retorna um boolean, ela retornará true se encontrar histórico para processar com sucesso, ou false se ocorrer um erro ou falhar ao recuperar os dados.
ResetLastError(); if(HistorySelect(fromDateTime, toDateTime)) //- History selected ok { //-- Code to process the history data will go here } else //- History selecting failed { Print("Selecting the history failed. Error code = ", GetLastError()); return(false); }
Dentro da parte else da estrutura if-else, adicionamos um código para registrar uma mensagem indicando que a seleção do histórico falhou, junto com o código de erro, antes de sair da função e retornar um valor boolean de false. Na parte if, usaremos uma estrutura switch para chamar as funções apropriadas de processamento dos dados históricos carregados, com base no valor de dataToGet.
switch(dataToGet) { case GET_DEALS_HISTORY_DATA: //- Get and save only the deals history data SaveDealsData(); break; case GET_ORDERS_HISTORY_DATA: //- Get and save only the orders history data SaveOrdersData(); break; case GET_POSITIONS_HISTORY_DATA: //- Get and save only the positions history data SaveDealsData(); //- Needed to generate the positions history data SaveOrdersData(); //- Needed to generate the positions history data SavePositionsData(); break; case GET_PENDING_ORDERS_HISTORY_DATA: //- Get and save only the pending orders history data SaveOrdersData(); //- Needed to generate the pending orders history data SavePendingOrdersData(); break; case GET_ALL_HISTORY_DATA: //- Get and save all the history data SaveDealsData(); SaveOrdersData(); SavePositionsData(); SavePendingOrdersData(); break; default: //-- Unknown entry Print("-----------------------------------------------------------------------------------------"); Print(__FUNCTION__, ": Can't fetch the historical data you need."); Print("*** Please specify the historical data you need in the (dataToGet) parameter."); break; }
Aqui está a função completa GetHistoryDataFunction() com todos os trechos de código incluídos.
bool GetHistoryData(datetime fromDateTime, datetime toDateTime, uint dataToGet) { //- Check if the provided period of dates are valid if(fromDateTime >= toDateTime) { //- Invalid time period selected Print("Invalid time period provided. Can't load history!"); return(false); } //- Reset last error and get the history ResetLastError(); if(HistorySelect(fromDateTime, toDateTime)) //- History selected ok { //- Get the history data switch(dataToGet) { case GET_DEALS_HISTORY_DATA: //- Get and save only the deals history data SaveDealsData(); break; case GET_ORDERS_HISTORY_DATA: //- Get and save only the orders history data SaveOrdersData(); break; case GET_POSITIONS_HISTORY_DATA: //- Get and save only the positions history data SaveDealsData(); //- Needed to generate the positions history data SaveOrdersData(); //- Needed to generate the positions history data SavePositionsData(); break; case GET_PENDING_ORDERS_HISTORY_DATA: //- Get and save only the pending orders history data SaveOrdersData(); //- Needed to generate the pending orders history data SavePendingOrdersData(); break; case GET_ALL_HISTORY_DATA: //- Get and save all the history data SaveDealsData(); SaveOrdersData(); SavePositionsData(); SavePendingOrdersData(); break; default: //-- Unknown entry Print("-----------------------------------------------------------------------------------------"); Print(__FUNCTION__, ": Can't fetch the historical data you need."); Print("*** Please specify the historical data you need in the (dataToGet) parameter."); break; } } else { Print(__FUNCTION__, ": Selecting the history failed. Error code = ", GetLastError()); return(false); } return(true); }
Se você salvar e tentar compilar nosso arquivo de código-fonte neste estágio, encontrará vários erros e avisos de compilação. Isso ocorre porque muitas das funções referenciadas no código ainda não foram criadas. Como ainda estamos nas etapas iniciais do desenvolvimento da nossa biblioteca EX5, assim que todas as funções ausentes forem implementadas, o arquivo da biblioteca EX5 será compilado sem erros ou avisos.
Função Save Deals Data
A função SaveDealsData() será responsável por recuperar e salvar todo o histórico de negociações (deals) atualmente disponível no cache de histórico de negociações, para os diferentes períodos que serão solicitados por diversas funções da biblioteca. Ela não retornará nenhum dado e não será definida como exportável, pois é chamada internamente dentro da biblioteca — especificamente a partir da função GetHistoryData(). Essa função utilizará as funções padrão do MQL5 HistoryDealGet... para buscar as várias propriedades das negociações e armazená-las no array dinâmico de estrutura dealInfo.
Primeiro, vamos começar criando a definição ou assinatura da função.
void SaveDealsData() { //-- Our function's code will go here }
Como SaveDealsData() é chamada dentro da função GetHistoryData(), não há necessidade de chamar HistorySelect() novamente antes de processar o histórico de negociações. O primeiro passo na função SaveDealsData() será verificar se há algum histórico de negociações para processar. Faremos isso usando a função HistoryDealsTotal(), que retorna o número total de negociações disponíveis no cache de histórico. Para eficiência, criaremos uma variável inteira chamada totalDeals para armazenar o total de negociações no histórico e uma variável do tipo unsigned long chamada dealTicket para armazenar os identificadores (tickets) das negociações.
int totalDeals = HistoryDealsTotal(); ulong dealTicket;
Se nenhuma negociação estiver disponível ou encontrada (totalDeals é 0 ou menos), registraremos uma mensagem indicando isso e encerraremos a função antecipadamente, a fim de evitar processamento desnecessário.
if(totalDeals > 0) { //-- Code to process deal goes here } else { Print(__FUNCTION__, ": No deals available to be processed, totalDeals = ", totalDeals); }
Se houver histórico de negociações, o próximo passo será preparar um array para armazenar os dados obtidos. Usaremos o array dinâmico dealInfo para essa tarefa e começaremos redimensionando seu tamanho para corresponder ao número total de negociações usando a função ArrayResize(), garantindo que tenha capacidade suficiente para armazenar todas as propriedades relevantes das negociações.
ArrayResize(dealInfo, totalDeals); Em seguida, iteraremos pelas negociações em ordem inversa, começando pela mais recente, usando um loop “for”. Para cada negociação, utilizaremos a função HistoryDealGetTicket() para recuperar o tíquete exclusivo associado à negociação. Se a recuperação do tíquete for bem-sucedida, buscaremos e salvaremos as diversas propriedades da negociação. Armazenaremos cada propriedade em seu respectivo campo dentro do array dealInfo, no índice correspondente à iteração atual do loop.
Se a função HistoryDealGetTicket() falhar ao recuperar um tíquete válido para alguma negociação, registraremos uma mensagem de erro incluindo o código do erro para fins de depuração. Isso garantirá transparência no caso de surgirem problemas inesperados durante o processo de recuperação das propriedades.
for(int x = totalDeals - 1; x >= 0; x--) { ResetLastError(); dealTicket = HistoryDealGetTicket(x); if(dealTicket > 0) { //- Deal ticket selected ok, we can now save the deals properties dealInfo[x].ticket = dealTicket; dealInfo[x].entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY); dealInfo[x].type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(dealTicket, DEAL_TYPE); dealInfo[x].magic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC); dealInfo[x].positionId = HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID); dealInfo[x].order = HistoryDealGetInteger(dealTicket, DEAL_ORDER); dealInfo[x].symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL); dealInfo[x].comment = HistoryDealGetString(dealTicket, DEAL_COMMENT); dealInfo[x].volume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME); dealInfo[x].price = HistoryDealGetDouble(dealTicket, DEAL_PRICE); dealInfo[x].time = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME); dealInfo[x].tpPrice = HistoryDealGetDouble(dealTicket, DEAL_TP); dealInfo[x].slPrice = HistoryDealGetDouble(dealTicket, DEAL_SL); dealInfo[x].commission = HistoryDealGetDouble(dealTicket, DEAL_COMMISSION); dealInfo[x].swap = HistoryDealGetDouble(dealTicket, DEAL_SWAP); dealInfo[x].reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON); dealInfo[x].profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT); } else { Print( __FUNCTION__, " HistoryDealGetTicket(", x, ") failed. (dealTicket = ", dealTicket, ") *** Error Code: ", GetLastError() ); } }
Aqui está a função completa SaveDealsData() com todos os trechos de código incluídos.
void SaveDealsData() { //- Get the number of loaded history deals int totalDeals = HistoryDealsTotal(); ulong dealTicket; //- //- Check if we have any deals to be worked on if(totalDeals > 0) { //- Resize the dynamic array that stores the deals ArrayResize(dealInfo, totalDeals); //- Let us loop through the deals and save them one by one for(int x = totalDeals - 1; x >= 0; x--) { ResetLastError(); dealTicket = HistoryDealGetTicket(x); if(dealTicket > 0) { //- Deal ticket selected ok, we can now save the deals properties dealInfo[x].ticket = dealTicket; dealInfo[x].entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY); dealInfo[x].type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(dealTicket, DEAL_TYPE); dealInfo[x].magic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC); dealInfo[x].positionId = HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID); dealInfo[x].order = HistoryDealGetInteger(dealTicket, DEAL_ORDER); dealInfo[x].symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL); dealInfo[x].comment = HistoryDealGetString(dealTicket, DEAL_COMMENT); dealInfo[x].volume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME); dealInfo[x].price = HistoryDealGetDouble(dealTicket, DEAL_PRICE); dealInfo[x].time = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME); dealInfo[x].tpPrice = HistoryDealGetDouble(dealTicket, DEAL_TP); dealInfo[x].slPrice = HistoryDealGetDouble(dealTicket, DEAL_SL); dealInfo[x].commission = HistoryDealGetDouble(dealTicket, DEAL_COMMISSION); dealInfo[x].swap = HistoryDealGetDouble(dealTicket, DEAL_SWAP); dealInfo[x].reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON); dealInfo[x].profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT); } else { Print( __FUNCTION__, " HistoryDealGetTicket(", x, ") failed. (dealTicket = ", dealTicket, ") *** Error Code: ", GetLastError() ); } } } else { Print(__FUNCTION__, ": No deals available to be processed, totalDeals = ", totalDeals); } }
Função Print Deals History
A função PrintDealsHistory() foi projetada para recuperar e exibir os dados históricos de negociações (deals) para um período especificado. Essa função será útil em situações nas quais você precisa examinar um intervalo de dados de negociações dentro de um determinado período de tempo. Ela não retorna nenhum dado, mas envia as informações das negociações para o log do MetaTrader 5 para revisão. Essa função pode ser chamada externamente para fornecer aos usuários uma visão das negociações passadas, utilizando a função GetHistoryData() para buscar os dados relevantes.
Começamos definindo a função PrintDealsHistory(). A função requer dois parâmetros: fromDateTime e toDateTime, que representam o início e o fim do período que queremos pesquisar. A função buscará as negociações executadas dentro desse intervalo de tempo. Observe que a função é marcada como export, o que significa que ela pode ser chamada a partir de outros programas ou bibliotecas, tornando-se prontamente disponível para uso externo.
void PrintDealsHistory(datetime fromDateTime, datetime toDateTime) export { //-- Our function's code will go here }
Em seguida, chamamos a função GetHistoryData(), passando os parâmetros fromDateTime, toDateTime e uma constante adicional GET_DEALS_HISTORY_DATA. Isso informa à função que deve buscar os dados de negociações relevantes entre os horários de início e fim especificados. Essa chamada garante que as informações das negociações do período desejado sejam obtidas e armazenadas no array dealInfo.
GetHistoryData(fromDateTime, toDateTime, GET_DEALS_HISTORY_DATA);
Depois que os dados das negociações são buscados, precisamos verificar se há informações disponíveis. Usamos a função ArraySize() para obter o número total de negociações armazenadas no array dealInfo. Se nenhuma negociação for encontrada (ou seja, totalDeals é 0), registramos uma mensagem para informar o usuário e encerramos a função. Caso não haja negociações a serem exibidas, a função é finalizada antecipadamente, economizando tempo e evitando operações desnecessárias.
int totalDeals = ArraySize(dealInfo); if(totalDeals <= 0) { Print(""); Print(__FUNCTION__, ": No deals history found for the specified period."); return; //-- Exit the function }
Se os dados de negociações forem encontrados, prosseguimos para imprimir os detalhes. O primeiro passo é exibir uma mensagem de resumo mostrando o número total de negociações encontradas e o intervalo de datas em que foram executadas.
Print(""); Print(__FUNCTION__, "-------------------------------------------------------------------------------"); Print( "Found a total of ", totalDeals, " deals executed between (", fromDateTime, ") and (", toDateTime, ")." );
Em seguida, usamos um loop for para iterar sobre todas as negociações no array dealInfo. Para cada negociação, imprimimos os detalhes relevantes, como símbolo, número do tíquete, ID da posição, tipo de entrada, preço, níveis de stop loss (SL) e take profit (TP), swap, comissão, lucro e outros. Os detalhes de cada negociação são exibidos de forma organizada e com rótulos descritivos, facilitando a compreensão do histórico de transações pelo usuário.
for(int r = 0; r < totalDeals; r++) { Print("---------------------------------------------------------------------------------------------------"); Print("Deal #", (r + 1)); Print("Symbol: ", dealInfo[r].symbol); Print("Time Executed: ", dealInfo[r].time); Print("Ticket: ", dealInfo[r].ticket); Print("Position ID: ", dealInfo[r].positionId); Print("Order Ticket: ", dealInfo[r].order); Print("Type: ", EnumToString(dealInfo[r].type)); Print("Entry: ", EnumToString(dealInfo[r].entry)); Print("Reason: ", EnumToString(dealInfo[r].reason)); Print("Volume: ", dealInfo[r].volume); Print("Price: ", dealInfo[r].price); Print("SL Price: ", dealInfo[r].slPrice); Print("TP Price: ", dealInfo[r].tpPrice); Print("Swap: ", dealInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY)); Print("Commission: ", dealInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY)); Print("Profit: ", dealInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY)); Print("Comment: ", dealInfo[r].comment); Print("Magic: ", dealInfo[r].magic); Print(""); }
Aqui está a função completa PrintDealsHistory() com todos os trechos de código integrados.
void PrintDealsHistory(datetime fromDateTime, datetime toDateTime) export { //- Get and save the deals history for the specified period GetHistoryData(fromDateTime, toDateTime, GET_DEALS_HISTORY_DATA); int totalDeals = ArraySize(dealInfo); if(totalDeals <= 0) { Print(""); Print(__FUNCTION__, ": No deals history found for the specified period."); return; //-- Exit the function } Print(""); Print(__FUNCTION__, "-------------------------------------------------------------------------------"); Print( "Found a total of ", totalDeals, " deals executed between (", fromDateTime, ") and (", toDateTime, ")." ); for(int r = 0; r < totalDeals; r++) { Print("---------------------------------------------------------------------------------------------------"); Print("Deal #", (r + 1)); Print("Symbol: ", dealInfo[r].symbol); Print("Time Executed: ", dealInfo[r].time); Print("Ticket: ", dealInfo[r].ticket); Print("Position ID: ", dealInfo[r].positionId); Print("Order Ticket: ", dealInfo[r].order); Print("Type: ", EnumToString(dealInfo[r].type)); Print("Entry: ", EnumToString(dealInfo[r].entry)); Print("Reason: ", EnumToString(dealInfo[r].reason)); Print("Volume: ", dealInfo[r].volume); Print("Price: ", dealInfo[r].price); Print("SL Price: ", dealInfo[r].slPrice); Print("TP Price: ", dealInfo[r].tpPrice); Print("Swap: ", dealInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY)); Print("Commission: ", dealInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY)); Print("Profit: ", dealInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY)); Print("Comment: ", dealInfo[r].comment); Print("Magic: ", dealInfo[r].magic); Print(""); } }
Função Save Orders Data
A função SaveOrdersData() será responsável por recuperar e armazenar os dados históricos de ordens disponíveis no cache de histórico de negociações. Essa função processa as ordens uma a uma, extrai suas principais propriedades utilizando as funções HistoryOrderGet... do MQL5 e as armazena em um array dinâmico chamado orderInfo. Esse array será então usado por outras partes da biblioteca para analisar e manipular os dados conforme necessário. A função não retornará nenhum dado, não será definida como exportável (já que é usada internamente dentro da biblioteca), tratará erros de forma adequada e registrará quaisquer problemas para fins de depuração.
Comecemos definindo a assinatura da função.
void SaveOrdersData() { //-- Our function's code will go here }
Em seguida, determinamos quantas ordens históricas estão disponíveis. Isso é feito usando a função HistoryOrdersTotal(), que retorna o número total de ordens históricas no cache. O resultado é armazenado em uma variável chamada totalOrdersHistory. Além disso, declaramos uma variável unsigned long chamada orderTicket para armazenar o tíquete de cada ordem conforme elas são processadas.
int totalOrdersHistory = HistoryOrdersTotal(); ulong orderTicket;
Se não houver ordens históricas (totalOrdersHistory <= 0), a função registra uma mensagem indicando isso e encerra antecipadamente para evitar processamento desnecessário.
if(totalOrdersHistory > 0) { //-- Code to process orders goes here } else { Print(__FUNCTION__, ": No order history available to be processed, totalOrdersHistory = ", totalOrdersHistory); return; }
Quando houver ordens históricas disponíveis, preparamos o array orderInfo para armazenar os dados obtidos. Isso é feito redimensionando o array com a função ArrayResize() para corresponder ao número total de ordens históricas.
ArrayResize(orderInfo, totalOrdersHistory); Iteramos pelas ordens em ordem inversa (começando pela mais recente) usando um loop “for”. Para cada ordem: Primeiro, recuperamos o tíquete da ordem usando a função HistoryOrderGetTicket(). Se a recuperação do tíquete for bem-sucedida, extraímos as várias propriedades da ordem utilizando as funções HistoryOrderGet... e as armazenamos nos campos correspondentes do array orderInfo. Se a recuperação do tíquete falhar, a função registra uma mensagem de erro junto com o código do erro para depuração.
for(int x = totalOrdersHistory - 1; x >= 0; x--) { ResetLastError(); orderTicket = HistoryOrderGetTicket(x); if(orderTicket > 0) { //- Order ticket selected ok, we can now save the order properties orderInfo[x].ticket = orderTicket; orderInfo[x].timeSetup = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_SETUP); orderInfo[x].timeDone = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_DONE); orderInfo[x].expirationTime = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_EXPIRATION); orderInfo[x].typeTime = (ENUM_ORDER_TYPE_TIME)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_TIME); orderInfo[x].magic = HistoryOrderGetInteger(orderTicket, ORDER_MAGIC); orderInfo[x].reason = (ENUM_ORDER_REASON)HistoryOrderGetInteger(orderTicket, ORDER_REASON); orderInfo[x].type = (ENUM_ORDER_TYPE)HistoryOrderGetInteger(orderTicket, ORDER_TYPE); orderInfo[x].state = (ENUM_ORDER_STATE)HistoryOrderGetInteger(orderTicket, ORDER_STATE); orderInfo[x].typeFilling = (ENUM_ORDER_TYPE_FILLING)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_FILLING); orderInfo[x].positionId = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_ID); orderInfo[x].positionById = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_BY_ID); orderInfo[x].symbol = HistoryOrderGetString(orderTicket, ORDER_SYMBOL); orderInfo[x].comment = HistoryOrderGetString(orderTicket, ORDER_COMMENT); orderInfo[x].volumeInitial = HistoryOrderGetDouble(orderTicket, ORDER_VOLUME_INITIAL); orderInfo[x].priceOpen = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_OPEN); orderInfo[x].priceStopLimit = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_STOPLIMIT); orderInfo[x].tpPrice = HistoryOrderGetDouble(orderTicket, ORDER_TP); orderInfo[x].slPrice = HistoryOrderGetDouble(orderTicket, ORDER_SL); } else { Print( __FUNCTION__, " HistoryOrderGetTicket(", x, ") failed. (orderTicket = ", orderTicket, ") *** Error Code: ", GetLastError() ); } }
Após processar todas as ordens, a função é encerrada corretamente. Aqui está a implementação completa da função SaveOrdersData(), com todos os trechos de código incluídos.
void SaveOrdersData() { //- Get the number of loaded history orders int totalOrdersHistory = HistoryOrdersTotal(); ulong orderTicket; //- //- Check if we have any orders in the history to be worked on if(totalOrdersHistory > 0) { //- Resize the dynamic array that stores the history orders ArrayResize(orderInfo, totalOrdersHistory); //- Let us loop through the order history and save them one by one for(int x = totalOrdersHistory - 1; x >= 0; x--) { ResetLastError(); orderTicket = HistoryOrderGetTicket(x); if(orderTicket > 0) { //- Order ticket selected ok, we can now save the order properties orderInfo[x].ticket = orderTicket; orderInfo[x].timeSetup = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_SETUP); orderInfo[x].timeDone = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_DONE); orderInfo[x].expirationTime = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_EXPIRATION); orderInfo[x].typeTime = (ENUM_ORDER_TYPE_TIME)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_TIME); orderInfo[x].magic = HistoryOrderGetInteger(orderTicket, ORDER_MAGIC); orderInfo[x].reason = (ENUM_ORDER_REASON)HistoryOrderGetInteger(orderTicket, ORDER_REASON); orderInfo[x].type = (ENUM_ORDER_TYPE)HistoryOrderGetInteger(orderTicket, ORDER_TYPE); orderInfo[x].state = (ENUM_ORDER_STATE)HistoryOrderGetInteger(orderTicket, ORDER_STATE); orderInfo[x].typeFilling = (ENUM_ORDER_TYPE_FILLING)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_FILLING); orderInfo[x].positionId = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_ID); orderInfo[x].positionById = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_BY_ID); orderInfo[x].symbol = HistoryOrderGetString(orderTicket, ORDER_SYMBOL); orderInfo[x].comment = HistoryOrderGetString(orderTicket, ORDER_COMMENT); orderInfo[x].volumeInitial = HistoryOrderGetDouble(orderTicket, ORDER_VOLUME_INITIAL); orderInfo[x].priceOpen = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_OPEN); orderInfo[x].priceStopLimit = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_STOPLIMIT); orderInfo[x].tpPrice = HistoryOrderGetDouble(orderTicket, ORDER_TP); orderInfo[x].slPrice = HistoryOrderGetDouble(orderTicket, ORDER_SL); } else { Print( __FUNCTION__, " HistoryOrderGetTicket(", x, ") failed. (orderTicket = ", orderTicket, ") *** Error Code: ", GetLastError() ); } } } else { Print(__FUNCTION__, ": No order history available to be processed, totalOrdersHistory = ", totalOrdersHistory); } }
Função de Histórico de Pedidos de Impressão
A função PrintOrdersHistory() fornece um recurso essencial para exibir os detalhes do histórico de ordens dentro de um período especificado. Ela consulta os dados previamente salvos no array orderInfo e imprime todas as informações relevantes das ordens. Esta função é definida como export, pois foi projetada para ser acessível a módulos externos ou aplicativos MQL5 que utilizem esta biblioteca. Ela segue uma abordagem semelhante à função PrintDealsHistory(). Aqui está a implementação completa da função PrintOrdersHistory(), com comentários explicativos para ajudar você a entender melhor como cada parte do código funciona.
void PrintOrdersHistory(datetime fromDateTime, datetime toDateTime) export { //- Get and save the orders history for the specified period GetHistoryData(fromDateTime, toDateTime, GET_ORDERS_HISTORY_DATA); int totalOrders = ArraySize(orderInfo); if(totalOrders <= 0) { Print(""); Print(__FUNCTION__, ": No orders history found for the specified period."); return; //-- Exit the function } Print(""); Print(__FUNCTION__, "-------------------------------------------------------------------------------"); Print( "Found a total of ", totalOrders, " orders filled or cancelled between (", fromDateTime, ") and (", toDateTime, ")." ); for(int r = 0; r < totalOrders; r++) { Print("---------------------------------------------------------------------------------------------------"); Print("Order #", (r + 1)); Print("Symbol: ", orderInfo[r].symbol); Print("Time Setup: ", orderInfo[r].timeSetup); Print("Type: ", EnumToString(orderInfo[r].type)); Print("Ticket: ", orderInfo[r].ticket); Print("Position ID: ", orderInfo[r].positionId); Print("State: ", EnumToString(orderInfo[r].state)); Print("Type Filling: ", EnumToString(orderInfo[r].typeFilling)); Print("Type Time: ", EnumToString(orderInfo[r].typeTime)); Print("Reason: ", EnumToString(orderInfo[r].reason)); Print("Volume Initial: ", orderInfo[r].volumeInitial); Print("Price Open: ", orderInfo[r].priceOpen); Print("Price Stop Limit: ", orderInfo[r].priceStopLimit); Print("SL Price: ", orderInfo[r].slPrice); Print("TP Price: ", orderInfo[r].tpPrice); Print("Time Done: ", orderInfo[r].timeDone); Print("Expiration Time: ", orderInfo[r].expirationTime); Print("Comment: ", orderInfo[r].comment); Print("Magic: ", orderInfo[r].magic); Print(""); } }
Função Salvar Dados de Posições
A função SavePositionsData() organiza o histórico de negociações e ordens para reconstruir o ciclo de vida de cada posição, desempenhando um papel central na criação do histórico de posições ao sintetizar informações dos dados disponíveis. Na documentação do MQL5, você notará que não há funções padrão (como HistoryPositionSelect() ou HistoryPositionsTotal()) para acessar diretamente dados de posição histórica. Portanto, precisamos criar uma função personalizada que combine dados de pedidos e negócios, usando o ID da posição como a chave de conexão para vincular negócios aos seus pedidos de origem.
Começaremos examinando os deals para identificar todos os exit deals, que indicam que uma posição foi encerrada. A partir daí, rastrearemos o entry deal correspondente para coletar informações sobre a abertura da posição. Por fim, usaremos o histórico de ordens para complementar o histórico da posição com informações adicionais, como o tipo de ordem original ou se a posição foi iniciada por uma ordem pendente. Esse processo passo a passo garantirá que o ciclo de vida de cada posição — da abertura ao fechamento — seja reconstruído com precisão, fornecendo um registro completo e claro.
Comecemos definindo a assinatura da função. Como essa função será usada apenas internamente pelos módulos principais da biblioteca EX5, ela não será exportável.
void SavePositionsData() { //-- Our function's code will go here }
Em seguida, calcularemos o número total de deals no array dealInfo, que contém todos os dados de negociações. Depois disso, redimensionaremos o array positionInfo, que será usado para armazenar todos os dados históricos das posições, preparando-o para acomodar o número esperado de posições.
int totalDealInfo = ArraySize(dealInfo); ArrayResize(positionInfo, totalDealInfo); int totalPositionsFound = 0, posIndex = 0;
Se não houver deals disponíveis no array dealInfo (ou seja, totalDealInfo == 0), encerramos a função imediatamente, já que não há dados para processar.
if(totalDealInfo == 0) { return; }
Depois, percorremos os deals em ordem inversa (começando pelo mais recente) para garantir que possamos mapear os exit deals para seus entry deals correspondentes. Verificamos se o deal atual é um exit deal avaliando sua propriedade de entrada. (dealInfo[x].entry == DEAL_ENTRY_OUT). É fundamental começar pelos exit deals, pois isso confirma que a posição foi encerrada e não está mais ativa. Queremos registrar apenas posições fechadas, não posições em aberto.
for(int x = totalDealInfo - 1; x >= 0; x--) { if(dealInfo[x].entry == DEAL_ENTRY_OUT) { // Process exit deal } }
Se um exit deal for encontrado, buscamos seu entry deal correspondente, combinando o POSITION_ID. Quando um entry deal é localizado, começamos a salvar suas informações relevantes no array positionInfo.
for(int k = ArraySize(dealInfo) - 1; k >= 0; k--) { if(dealInfo[k].positionId == positionId) { if(dealInfo[k].entry == DEAL_ENTRY_IN) { exitDealFound = true; totalPositionsFound++; posIndex = totalPositionsFound - 1; // Save the entry deal data positionInfo[posIndex].openingDealTicket = dealInfo[k].ticket; positionInfo[posIndex].openTime = dealInfo[k].time; positionInfo[posIndex].openPrice = dealInfo[k].price; positionInfo[posIndex].volume = dealInfo[k].volume; positionInfo[posIndex].magic = dealInfo[k].magic; positionInfo[posIndex].comment = dealInfo[k].comment; } } }
Depois que o exit deal for associado ao entry deal, salvamos as propriedades do exit deal, como preço de fechamento, horário de fechamento, lucro, swap e comissão. Também calculamos a duração da operação e o lucro líquido, considerando swap e comissão.
if(exitDealFound) { if(dealInfo[x].type == DEAL_TYPE_BUY) { positionInfo[posIndex].type = POSITION_TYPE_SELL; } else { positionInfo[posIndex].type = POSITION_TYPE_BUY; } positionInfo[posIndex].positionId = dealInfo[x].positionId; positionInfo[posIndex].symbol = dealInfo[x].symbol; positionInfo[posIndex].profit = dealInfo[x].profit; positionInfo[posIndex].closingDealTicket = dealInfo[x].ticket; positionInfo[posIndex].closePrice = dealInfo[x].price; positionInfo[posIndex].closeTime = dealInfo[x].time; positionInfo[posIndex].swap = dealInfo[x].swap; positionInfo[posIndex].commission = dealInfo[x].commission; positionInfo[posIndex].duration = MathAbs((long)positionInfo[posIndex].closeTime - (long)positionInfo[posIndex].openTime); positionInfo[posIndex].netProfit = positionInfo[posIndex].profit + positionInfo[posIndex].swap - positionInfo[posIndex].commission; }
Para cada posição, calculamos os valores de pip para stop loss (SL) e take profit (TP), dependendo se a posição é buy ou sell. Usamos o valor de symbol's point para determinar o número de pips.
if(positionInfo[posIndex].type == POSITION_TYPE_BUY) { // Calculate TP and SL pip values for buy position if(positionInfo[posIndex].tpPrice > 0) { double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].tpPips = int((positionInfo[posIndex].tpPrice - positionInfo[posIndex].openPrice) / symbolPoint); } if(positionInfo[posIndex].slPrice > 0) { double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].slPips = int((positionInfo[posIndex].openPrice - positionInfo[posIndex].slPrice) / symbolPoint); } // Calculate pip profit for buy position double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].pipProfit = int((positionInfo[posIndex].closePrice - positionInfo[posIndex].openPrice) / symbolPoint); } else { // Calculate TP and SL pip values for sell position if(positionInfo[posIndex].tpPrice > 0) { double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].tpPips = int((positionInfo[posIndex].openPrice - positionInfo[posIndex].tpPrice) / symbolPoint); } if(positionInfo[posIndex].slPrice > 0) { double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].slPips = int((positionInfo[posIndex].slPrice - positionInfo[posIndex].openPrice) / symbolPoint); } // Calculate pip profit for sell position double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].pipProfit = int((positionInfo[posIndex].openPrice - positionInfo[posIndex].closePrice) / symbolPoint); }
Por fim, percorremos o array orderInfo para encontrar a ordem que iniciou a posição. Comparamos o POSITION_ID e garantimos que a ordem esteja no estado ORDER_STATE_FILLED. Quando ela for encontrada, armazenamos o ticket e o tipo da ordem, o que ajudará a determinar se a posição foi iniciada por uma ordem pendente ou por uma entrada direta de mercado.
for(int k = 0; k < ArraySize(orderInfo); k++) { if( orderInfo[k].positionId == positionInfo[posIndex].positionId && orderInfo[k].state == ORDER_STATE_FILLED ) { positionInfo[posIndex].openingOrderTicket = orderInfo[k].ticket; positionInfo[posIndex].ticket = positionInfo[posIndex].openingOrderTicket; //- Determine if the position was initiated by a pending order or direct market entry switch(orderInfo[k].type) { case ORDER_TYPE_BUY_LIMIT: case ORDER_TYPE_BUY_STOP: case ORDER_TYPE_SELL_LIMIT: case ORDER_TYPE_SELL_STOP: case ORDER_TYPE_BUY_STOP_LIMIT: case ORDER_TYPE_SELL_STOP_LIMIT: positionInfo[posIndex].initiatedByPendingOrder = true; positionInfo[posIndex].initiatingOrderType = orderInfo[k].type; break; default: positionInfo[posIndex].initiatedByPendingOrder = false; positionInfo[posIndex].initiatingOrderType = orderInfo[k].type; break; } break; //- Exit the orderInfo loop once the required data is found } }
Finalmente, para limpar o array positionInfo, ajustamos seu tamanho para remover elementos vazios ou não utilizados após o processamento de todas as posições.
ArrayResize(positionInfo, totalPositionsFound); Aqui está a implementação completa da função SavePositionsData(), com todos os trechos de código incluídos.
void SavePositionsData() { //- Since every transaction is recorded as a deal, we will begin by scanning the deals and link them //- to different orders and generate the positions data using the POSITION_ID as the primary and foreign key int totalDealInfo = ArraySize(dealInfo); ArrayResize(positionInfo, totalDealInfo); //- Resize the position array to match the deals array int totalPositionsFound = 0, posIndex = 0; if(totalDealInfo == 0) //- Check if we have any deal history available for processing { return; //- No deal data to process found, we can't go on. exit the function } //- Let us loop through the deals array for(int x = totalDealInfo - 1; x >= 0; x--) { //- First we check if it is an exit deal to close a position if(dealInfo[x].entry == DEAL_ENTRY_OUT) { //- We begin by saving the position id ulong positionId = dealInfo[x].positionId; bool exitDealFound = false; //- Now we check if we have an exit deal from this position and save it's properties for(int k = ArraySize(dealInfo) - 1; k >= 0; k--) { if(dealInfo[k].positionId == positionId) { if(dealInfo[k].entry == DEAL_ENTRY_IN) { exitDealFound = true; totalPositionsFound++; posIndex = totalPositionsFound - 1; positionInfo[posIndex].openingDealTicket = dealInfo[k].ticket; positionInfo[posIndex].openTime = dealInfo[k].time; positionInfo[posIndex].openPrice = dealInfo[k].price; positionInfo[posIndex].volume = dealInfo[k].volume; positionInfo[posIndex].magic = dealInfo[k].magic; positionInfo[posIndex].comment = dealInfo[k].comment; } } } if(exitDealFound) //- Continue saving the exit deal data { //- Save the position type if(dealInfo[x].type == DEAL_TYPE_BUY) { //- If the exit deal is a buy, then the position was a sell trade positionInfo[posIndex].type = POSITION_TYPE_SELL; } else { //- If the exit deal is a sell, then the position was a buy trade positionInfo[posIndex].type = POSITION_TYPE_BUY; } positionInfo[posIndex].positionId = dealInfo[x].positionId; positionInfo[posIndex].symbol = dealInfo[x].symbol; positionInfo[posIndex].profit = dealInfo[x].profit; positionInfo[posIndex].closingDealTicket = dealInfo[x].ticket; positionInfo[posIndex].closePrice = dealInfo[x].price; positionInfo[posIndex].closeTime = dealInfo[x].time; positionInfo[posIndex].swap = dealInfo[x].swap; positionInfo[posIndex].commission = dealInfo[x].commission; positionInfo[posIndex].tpPrice = dealInfo[x].tpPrice; positionInfo[posIndex].tpPips = 0; positionInfo[posIndex].slPrice = dealInfo[x].slPrice; positionInfo[posIndex].slPips = 0; //- Calculate the trade duration in seconds positionInfo[posIndex].duration = MathAbs((long)positionInfo[posIndex].closeTime - (long)positionInfo[posIndex].openTime); //- Calculate the net profit after swap and commission positionInfo[posIndex].netProfit = positionInfo[posIndex].profit + positionInfo[posIndex].swap - positionInfo[posIndex].commission; //- Get pip values for the position if(positionInfo[posIndex].type == POSITION_TYPE_BUY) //- Buy position { //- Get sl and tp pip values if(positionInfo[posIndex].tpPrice > 0) { double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].tpPips = int((positionInfo[posIndex].tpPrice - positionInfo[posIndex].openPrice) / symbolPoint); } if(positionInfo[posIndex].slPrice > 0) { double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].slPips = int((positionInfo[posIndex].openPrice - positionInfo[posIndex].slPrice) / symbolPoint); } //- Get the buy profit in pip value double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].pipProfit = int((positionInfo[posIndex].closePrice - positionInfo[posIndex].openPrice) / symbolPoint); } else //- Sell position { //- Get sl and tp pip values if(positionInfo[posIndex].tpPrice > 0) { double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].tpPips = int((positionInfo[posIndex].openPrice - positionInfo[posIndex].tpPrice) / symbolPoint); } if(positionInfo[posIndex].slPrice > 0) { double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].slPips = int((positionInfo[posIndex].slPrice - positionInfo[posIndex].openPrice) / symbolPoint); } //- Get the sell profit in pip value double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT); positionInfo[posIndex].pipProfit = int((positionInfo[posIndex].openPrice - positionInfo[posIndex].closePrice) / symbolPoint); } //- Now we scan and get the opening order ticket in the orderInfo array for(int k = 0; k < ArraySize(orderInfo); k++) //- Search from the oldest to newest order { if( orderInfo[k].positionId == positionInfo[posIndex].positionId && orderInfo[k].state == ORDER_STATE_FILLED ) { //- Save the order ticket that intiated the position positionInfo[posIndex].openingOrderTicket = orderInfo[k].ticket; positionInfo[posIndex].ticket = positionInfo[posIndex].openingOrderTicket; //- Determine if the position was initiated by a pending order or direct market entry switch(orderInfo[k].type) { //- Pending order entry case ORDER_TYPE_BUY_LIMIT: case ORDER_TYPE_BUY_STOP: case ORDER_TYPE_SELL_LIMIT: case ORDER_TYPE_SELL_STOP: case ORDER_TYPE_BUY_STOP_LIMIT: case ORDER_TYPE_SELL_STOP_LIMIT: positionInfo[posIndex].initiatedByPendingOrder = true; positionInfo[posIndex].initiatingOrderType = orderInfo[k].type; break; //- Direct market entry default: positionInfo[posIndex].initiatedByPendingOrder = false; positionInfo[posIndex].initiatingOrderType = orderInfo[k].type; break; } break; //--- We have everything we need, exit the orderInfo loop } } } } else //--- Position id not found { continue;//- skip to the next iteration } } //- Resize the positionInfo array and delete all the indexes that have zero values ArrayResize(positionInfo, totalPositionsFound); }
Função de Histórico de Posições de Impressão
A função PrintPositionsHistory() foi projetada para exibir um histórico detalhado de posições encerradas dentro de um período específico. Ela acessa os dados previamente salvos no array positionInfo e imprime informações relevantes para cada posição. Essa função é exportável, tornando-a acessível para módulos externos ou aplicativos MQL5 que utilizem esta biblioteca. Sua implementação segue uma estrutura semelhante às outras funções de impressão que já desenvolvemos. Aqui está a implementação completa, com comentários detalhados para maior clareza.
void PrintPositionsHistory(datetime fromDateTime, datetime toDateTime) export { //- Get and save the deals, orders, positions history for the specified period GetHistoryData(fromDateTime, toDateTime, GET_POSITIONS_HISTORY_DATA); int totalPositionsClosed = ArraySize(positionInfo); if(totalPositionsClosed <= 0) { Print(""); Print(__FUNCTION__, ": No position history found for the specified period."); return; //- Exit the function } Print(""); Print(__FUNCTION__, "-------------------------------------------------------------------------------"); Print( "Found a total of ", totalPositionsClosed, " positions closed between (", fromDateTime, ") and (", toDateTime, ")." ); for(int r = 0; r < totalPositionsClosed; r++) { Print("---------------------------------------------------------------------------------------------------"); Print("Position #", (r + 1)); Print("Symbol: ", positionInfo[r].symbol); Print("Time Open: ", positionInfo[r].openTime); Print("Ticket: ", positionInfo[r].ticket); Print("Type: ", EnumToString(positionInfo[r].type)); Print("Volume: ", positionInfo[r].volume); Print("0pen Price: ", positionInfo[r].openPrice); Print("SL Price: ", positionInfo[r].slPrice, " (slPips: ", positionInfo[r].slPips, ")"); Print("TP Price: ", positionInfo[r].tpPrice, " (tpPips: ", positionInfo[r].tpPips, ")"); Print("Close Price: ", positionInfo[r].closePrice); Print("Close Time: ", positionInfo[r].closeTime); Print("Trade Duration: ", positionInfo[r].duration); Print("Swap: ", positionInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY)); Print("Commission: ", positionInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY)); Print("Profit: ", positionInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY)); Print("Net profit: ", DoubleToString(positionInfo[r].netProfit, 2), " ", AccountInfoString(ACCOUNT_CURRENCY)); Print("pipProfit: ", positionInfo[r].pipProfit); Print("Initiating Order Type: ", EnumToString(positionInfo[r].initiatingOrderType)); Print("Initiated By Pending Order: ", positionInfo[r].initiatedByPendingOrder); Print("Comment: ", positionInfo[r].comment); Print("Magic: ", positionInfo[r].magic); Print(""); } }
Função Salvar Dados de Pedidos Pendentes
A função SavePendingOrdersData() processa os dados do histórico de ordens para gerar e salvar o histórico de ordens pendentes. Essa função basicamente filtra as ordens pendentes do histórico de ordens, armazena detalhes essenciais e calcula valores específicos, como o número de pips para os níveis de take profit (TP) e stop loss (SL). Ela desempenha um papel crucial no rastreamento do ciclo de vida das ordens pendentes, ajudando a gerar um histórico preciso e enriquecendo o sistema com dados sobre como cada ordem pendente foi estruturada e executada.
Atualmente, o MQL5 não possui funções padrão como HistoryPendingOrderSelect() ou HistoryPendingOrdersTotal() para acessar diretamente dados históricos de ordens pendentes. Como resultado, precisamos criar uma função personalizada para analisar o histórico de ordens e montar uma fonte de dados contendo todas as ordens pendentes preenchidas ou canceladas dentro de um determinado período histórico.
Comecemos definindo a assinatura da função. Como essa função será usada apenas internamente pelos módulos principais da biblioteca EX5, ela não será exportável.
void SavePendingOrdersData() { //-- Function's code will go here }
Em seguida, calculamos o número total de ordens no array orderInfo, que contém os detalhes de todas as ordens. Redimensionaremos o array pendingOrderInfo para comportar inicialmente o total de ordens, garantindo espaço suficiente para armazenar apenas as ordens pendentes filtradas.
int totalOrderInfo = ArraySize(orderInfo); ArrayResize(pendingOrderInfo, totalOrderInfo); int totalPendingOrdersFound = 0, pendingIndex = 0;
Se não houver ordens para processar (ou seja, totalOrderInfo == 0), saímos imediatamente da função, pois não há dados de ordens pendentes para tratar.
if(totalOrderInfo == 0) { return; }
Agora, percorremos as ordens em ordem inversa, garantindo que analisemos primeiro as mais recentes. Dentro do loop, verificamos se a ordem atual é uma ordem pendente avaliando seu tipo. O histórico salvo incluirá ordens pendentes (como buy limit, sell stop, etc.) que foram executadas (filled) e transformadas em posições, ou canceladas sem se tornarem posições.
for(int x = totalOrderInfo - 1; x >= 0; x--) { if( orderInfo[x].type == ORDER_TYPE_BUY_LIMIT || orderInfo[x].type == ORDER_TYPE_BUY_STOP || orderInfo[x].type == ORDER_TYPE_SELL_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP || orderInfo[x].type == ORDER_TYPE_BUY_STOP_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP_LIMIT ) { totalPendingOrdersFound++; pendingIndex = totalPendingOrdersFound - 1; //-- Save the pending order properties into the pendingOrderInfo array }
Se a ordem for uma ordem pendente, salvamos suas propriedades (por exemplo, tipo, estado, ID da posição, tíquete, símbolo, horário e outras) no array pendingOrderInfo.
pendingOrderInfo[pendingIndex].type = orderInfo[x].type; pendingOrderInfo[pendingIndex].state = orderInfo[x].state; pendingOrderInfo[pendingIndex].positionId = orderInfo[x].positionId; pendingOrderInfo[pendingIndex].ticket = orderInfo[x].ticket; pendingOrderInfo[pendingIndex].symbol = orderInfo[x].symbol; pendingOrderInfo[pendingIndex].timeSetup = orderInfo[x].timeSetup; pendingOrderInfo[pendingIndex].expirationTime = orderInfo[x].expirationTime; pendingOrderInfo[pendingIndex].timeDone = orderInfo[x].timeDone; pendingOrderInfo[pendingIndex].typeTime = orderInfo[x].typeTime; pendingOrderInfo[pendingIndex].priceOpen = orderInfo[x].priceOpen; pendingOrderInfo[pendingIndex].tpPrice = orderInfo[x].tpPrice; pendingOrderInfo[pendingIndex].slPrice = orderInfo[x].slPrice;
Em seguida, calculamos o número de pips para os níveis de take profit (TP) e stop loss (SL), caso estejam definidos. Para isso, usamos o valor de ponto do símbolo para determinar o número de pips.
if(pendingOrderInfo[pendingIndex].tpPrice > 0) { double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT); pendingOrderInfo[pendingIndex].tpPips = (int)MathAbs((pendingOrderInfo[pendingIndex].tpPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint); } if(pendingOrderInfo[pendingIndex].slPrice > 0) { double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT); pendingOrderInfo[pendingIndex].slPips = (int)MathAbs((pendingOrderInfo[pendingIndex].slPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint); }
Também salvamos propriedades adicionais, como o magic number, motivo, tipo de preenchimento (filling type), comentário, volume inicial e o preço de stop limit.
pendingOrderInfo[pendingIndex].magic = orderInfo[x].magic; pendingOrderInfo[pendingIndex].reason = orderInfo[x].reason; pendingOrderInfo[pendingIndex].typeFilling = orderInfo[x].typeFilling; pendingOrderInfo[pendingIndex].comment = orderInfo[x].comment; pendingOrderInfo[pendingIndex].volumeInitial = orderInfo[x].volumeInitial; pendingOrderInfo[pendingIndex].priceStopLimit = orderInfo[x].priceStopLimit;
Após processar todas as ordens, redimensionamos o array pendingOrderInfo para remover itens vazios ou não utilizados, garantindo que o array contenha somente os dados relevantes das ordens pendentes.
ArrayResize(pendingOrderInfo, totalPendingOrdersFound); Aqui está a implementação completa da função SavePendingOrdersData() com todos os trechos de código incluídos.
void SavePendingOrdersData() { //- Let us begin by scanning the orders and link them to different deals int totalOrderInfo = ArraySize(orderInfo); ArrayResize(pendingOrderInfo, totalOrderInfo); int totalPendingOrdersFound = 0, pendingIndex = 0; if(totalOrderInfo == 0) { return; //- No order data to process found, we can't go on. exit the function } for(int x = totalOrderInfo - 1; x >= 0; x--) { //- Check if it is a pending order and save its properties if( orderInfo[x].type == ORDER_TYPE_BUY_LIMIT || orderInfo[x].type == ORDER_TYPE_BUY_STOP || orderInfo[x].type == ORDER_TYPE_SELL_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP || orderInfo[x].type == ORDER_TYPE_BUY_STOP_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP_LIMIT ) { totalPendingOrdersFound++; pendingIndex = totalPendingOrdersFound - 1; pendingOrderInfo[pendingIndex].type = orderInfo[x].type; pendingOrderInfo[pendingIndex].state = orderInfo[x].state; pendingOrderInfo[pendingIndex].positionId = orderInfo[x].positionId; pendingOrderInfo[pendingIndex].ticket = orderInfo[x].ticket; pendingOrderInfo[pendingIndex].symbol = orderInfo[x].symbol; pendingOrderInfo[pendingIndex].timeSetup = orderInfo[x].timeSetup; pendingOrderInfo[pendingIndex].expirationTime = orderInfo[x].expirationTime; pendingOrderInfo[pendingIndex].timeDone = orderInfo[x].timeDone; pendingOrderInfo[pendingIndex].typeTime = orderInfo[x].typeTime; pendingOrderInfo[pendingIndex].priceOpen = orderInfo[x].priceOpen; pendingOrderInfo[pendingIndex].tpPrice = orderInfo[x].tpPrice; pendingOrderInfo[pendingIndex].slPrice = orderInfo[x].slPrice; if(pendingOrderInfo[pendingIndex].tpPrice > 0) { double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT); pendingOrderInfo[pendingIndex].tpPips = (int)MathAbs((pendingOrderInfo[pendingIndex].tpPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint); } if(pendingOrderInfo[pendingIndex].slPrice > 0) { double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT); pendingOrderInfo[pendingIndex].slPips = (int)MathAbs((pendingOrderInfo[pendingIndex].slPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint); } pendingOrderInfo[pendingIndex].magic = orderInfo[x].magic; pendingOrderInfo[pendingIndex].reason = orderInfo[x].reason; pendingOrderInfo[pendingIndex].typeFilling = orderInfo[x].typeFilling; pendingOrderInfo[pendingIndex].comment = orderInfo[x].comment; pendingOrderInfo[pendingIndex].volumeInitial = orderInfo[x].volumeInitial; pendingOrderInfo[pendingIndex].priceStopLimit = orderInfo[x].priceStopLimit; } } //--Resize the pendingOrderInfo array and delete all the indexes that have zero values ArrayResize(pendingOrderInfo, totalPendingOrdersFound); }
Função de impressão do histórico de pedidos pendentes
A função PrintPendingOrdersHistory() foi criada para exibir um histórico detalhado das ordens pendentes executadas ou canceladas dentro de um período específico. Ela acessa os dados previamente armazenados no array pendingOrderInfo e imprime informações importantes sobre cada ordem pendente. Essa função é exportável, permitindo que módulos externos ou aplicações MQL5 que utilizem esta biblioteca EX5 possam acessá-la. Sua implementação segue uma estrutura semelhante às outras funções de impressão que já desenvolvemos. Aqui está a implementação completa, com comentários detalhados para facilitar o entendimento.
void PrintPendingOrdersHistory(datetime fromDateTime, datetime toDateTime) export { //- Get and save the pending orders history for the specified period GetHistoryData(fromDateTime, toDateTime, GET_PENDING_ORDERS_HISTORY_DATA); int totalPendingOrders = ArraySize(pendingOrderInfo); if(totalPendingOrders <= 0) { Print(""); Print(__FUNCTION__, ": No pending orders history found for the specified period."); return; //- Exit the function } Print(""); Print(__FUNCTION__, "-------------------------------------------------------------------------------"); Print( "Found a total of ", totalPendingOrders, " pending orders filled or cancelled between (", fromDateTime, ") and (", toDateTime, ")." ); for(int r = 0; r < totalPendingOrders; r++) { Print("---------------------------------------------------------------------------------------------------"); Print("Pending Order #", (r + 1)); Print("Symbol: ", pendingOrderInfo[r].symbol); Print("Time Setup: ", pendingOrderInfo[r].timeSetup); Print("Type: ", EnumToString(pendingOrderInfo[r].type)); Print("Ticket: ", pendingOrderInfo[r].ticket); Print("State: ", EnumToString(pendingOrderInfo[r].state)); Print("Time Done: ", pendingOrderInfo[r].timeDone); Print("Volume Initial: ", pendingOrderInfo[r].volumeInitial); Print("Price Open: ", pendingOrderInfo[r].priceOpen); Print("SL Price: ", pendingOrderInfo[r].slPrice, " (slPips: ", pendingOrderInfo[r].slPips, ")"); Print("TP Price: ", pendingOrderInfo[r].tpPrice, " (slPips: ", pendingOrderInfo[r].slPips, ")"); Print("Expiration Time: ", pendingOrderInfo[r].expirationTime); Print("Position ID: ", pendingOrderInfo[r].positionId); Print("Price Stop Limit: ", pendingOrderInfo[r].priceStopLimit); Print("Type Filling: ", EnumToString(pendingOrderInfo[r].typeFilling)); Print("Type Time: ", EnumToString(pendingOrderInfo[r].typeTime)); Print("Reason: ", EnumToString(pendingOrderInfo[r].reason)); Print("Comment: ", pendingOrderInfo[r].comment); Print("Magic: ", pendingOrderInfo[r].magic); Print(""); } }
Conclusão
Neste artigo, exploramos como usar MQL5 para recuperar dados de histórico de transações de ordens e deals. Você aprendeu como usar esses dados para gerar o histórico de posições fechadas e ordens pendentes, com uma trilha de auditoria que rastreia o ciclo de vida de cada posição encerrada. Isso inclui sua origem, como foi encerrada e outros detalhes relevantes como lucro líquido, lucro em pips, valor por pip para stop loss e take profit, duração da operação e mais.
Também desenvolvemos as funções principais da biblioteca History Manager EX5, permitindo consultar, salvar e categorizar diferentes tipos de dados históricos. Essas funções fundamentais fazem parte do núcleo da biblioteca, responsável pela lógica interna. No entanto, ainda há mais trabalho pela frente. Grande parte das funções criadas aqui são preparatórias, preparando o terreno para uma biblioteca mais orientada ao usuário.
No próximo artigo, expandiremos a biblioteca History Manager EX5 adicionando funções exportáveis projetadas para classificar e analisar dados históricos conforme necessidades comuns do usuário. Por exemplo, será possível obter propriedades das posições mais recentemente fechadas, analisar as últimas ordens pendentes executadas ou canceladas, verificar a última posição fechada para um símbolo específico, calcular o lucro fechado do dia atual e determinar lucros semanais em pips, entre outras funcionalidades.
Além disso, incluiremos módulos avançados de ordenação e análise para gerar relatórios detalhados — semelhantes aos produzidos pelo MetaTrader 5 Strategy Tester. Esses relatórios analisarão históricos reais, oferecendo informações sobre o desempenho de um Expert Advisor ou estratégia de trading. Também será possível filtrar e classificar estes dados programaticamente por símbolos ou magic numbers.
Para facilitar a implementação, forneceremos documentação completa da biblioteca History Manager EX5, junto com exemplos práticos. Esses exemplos demonstrarão como integrar a biblioteca ao seu projeto e realizar análises eficazes. Também incluiremos exemplos simples de Expert Advisors e tutoriais passo a passo para ajudar você a otimizar suas estratégias e aproveitar ao máximo os recursos da biblioteca.
Você encontrará o arquivo fonte HistoryManager.mq5 anexado ao final deste artigo. Obrigado por acompanhar, e desejo muito sucesso na sua jornada de trading e programação em MQL5!
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16528
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Criando um Painel de Administração de Trading em MQL5 (Parte VIII): Painel de Análises
Simulação de mercado: Position View (XV)
Do básico ao intermediário: Classes (III)
Como publicar código no CodeBase: Guia prático
- 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