Gráfico de saldo multissímbolo no MetaTrader 5
Anatoli Kazharski | 7 maio, 2018
Sumário
- Introdução
- Desenvolvimento da interface gráfica
- EA multissímbolo para testes
- Salvando dados num arquivo
- Extraindo dados de um arquivo
- Exibindo dados em gráficos
- Apresentando o resultado
- Gráfico de saldo multissímbolo durante a negociação e testes
- Visualizamos relatórios do serviço Sinais
- Fim do artigo
Introdução
Num dos artigos anteriores, examinamos a visualização de gráficos de saldo multissímbolos. Porém, desde então, apareceram muitas bibliotecas MQL que permitem implementar completamente isso no terminal MetaTrader 5, sem usar programas de terceiros.
Neste artigo, mostrarei um aplicativo de exemplo com uma interface gráfica em que será mostrado um gráfico multissímbolo da saldo e rebaixamento do depósito, a partir dos resultados do último teste. No final do teste do EA, o histórico de trades será salvo num arquivo. Esses dados podem ser lidos e exibidos em gráficos, posteriormente.
Além disso, o artigo apresenta uma versão do EA em que o gráfico de saldo multissímbolo é exibido e atualizado, na interface gráfica, durante a negociação, bem como durante o teste, no modo de visualização.
Desenvolvimento da interface gráfica
O artigo Visualizemos a otimização de uma estratégia de negociação no MetaTrader 5 mostrou em detalhes como conectar e usar a biblioteca EasyAndFast, e, além disso, como implementá-la, a fim de criar uma interface gráfica para um aplicativo MQL próprio. Por conseguinte, aqui abordaremos imediatamente o tema da interface gráfica sobre o tópico em consideração.
Vamos listar os elementos que serão usados na interface gráfica.
- Formulário para controles.
- Botão para atualizar os gráficos com os resultados do último teste.
- Gráfico para exibir o saldo multissímbolo.
- Gráfico para exibir os rebaixamentos do depósito.
- A barra de status para exibir informações de resumo adicionais.
A listagem de código abaixo mostra as declarações de método para criar esses elementos. A implementação dos métodos é feita de forma separada no arquivo anexado.
//+------------------------------------------------------------------+ //| Classe para criar o aplicativo | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Janela CWindow m_window1; //--- Barra de Status CStatusBar m_status_bar; //--- Gráficos CGraph m_graph1; CGraph m_graph2; //--- Botões CButton m_update_graph; //--- public: //--- Cria uma interface gráfica bool CreateGUI(void); //--- private: //--- Forma bool CreateWindow(const string text); //--- Barra de Status bool CreateStatusBar(const int x_gap,const int y_gap); //--- Gráficos bool CreateGraph1(const int x_gap,const int y_gap); bool CreateGraph2(const int x_gap,const int y_gap); //--- Botões bool CreateUpdateGraph(const int x_gap,const int y_gap,const string text); }; //+------------------------------------------------------------------+ //| Métodos para criar controles | //+------------------------------------------------------------------+ #include "CreateGUI.mqh" //+------------------------------------------------------------------+
O principal método de criar o GUI será como se segue:
//+------------------------------------------------------------------+ //| Cria a interface gráfica do usuário | //+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Criação do formulário para controles if(!CreateWindow("Expert panel")) return(false); //--- Criação de controles if(!CreateStatusBar(1,23)) return(false); if(!CreateGraph1(1,50)) return(false); if(!CreateGraph2(1,159)) return(false); if(!CreateUpdateGraph(7,25,"Update data")) return(false); //--- Conclusão da criação da GUI CWndEvents::CompletedGUI(); return(true); }
Como resultado disso, se você agora compilar o EA e carregá-lo no gráfico, no terminal, o resultado atual ficará assim:
Fig. 1. Interface gráfica do EA.
Em seguida, tomamos conta do registro de dados num arquivo, após o teste.
EA multissímbolo para testes
Para testes, pegamos no EA MACD Sample da entrega padrão, mas vamos torná-lo multissímbolo. O esquema multissímbolo utilizado nesta versão é impreciso. Nos mesmos parâmetros, o resultado será diferente dependendo do símbolo em que será realizado o teste (selecionado nas configurações do testador). Por isso, este EA se destina apenas a testes e apresentação dos resultados obtidos no âmbito do tópico apresentado.
Nas atualizações mais próximas do terminal MetaTrader 5, serão apresentadas novas possibilidades para a criação de EAs multissímbolos. Então, será possível pensar em criar uma versão final e universal para EAs desse tipo. Se você precisa urgentemente de um esquema multissímbolo rápido e preciso, pode experimentar a opção que foi proposta no fórum.
Nos parâmetros externos, adicionamos mais um parâmetro de string, para especificar os símbolo em que será realizado o teste:
//--- Parâmetros externos sinput string Symbols ="EURUSD,USDJPY,GBPUSD,EURCHF"; // Symbols input double InpLots =0.1; // Lots input int InpTakeProfit =167; // Take Profit (in pips) input int InpTrailingStop =97; // Trailing Stop Level (in pips) input int InpMACDOpenLevel =16; // MACD open level (in pips) input int InpMACDCloseLevel =19; // MACD close level (in pips) input int InpMATrendPeriod =14; // MA trend period
Os símbolos devem ser especificados separados por uma vírgula. Na classe do programa (CProgram), são implementados métodos para leitura deste parâmetro, bem como para verificar e definir os símbolos, na observação do mercado, daqueles que estão na lista no servidor. Como alternativa, você pode especificar símbolos para negociação por meio de uma lista preparada num arquivo, conforme mostrado no artigo Guia prático do MQL5: desenvolvendo um consultor especialista multimoeda com um número ilimitado de parâmetros. Além disso, você pode, no arquivo, criar várias listas opcionais. Você pode ver esse exemplo no artigo Guia prático do MQL5: reduzindo o efeito de sobreajuste e lidando com a falta de cotações. Você pode encontrar muitas outras maneiras de selecionar símbolos e suas listas usando a interface gráfica. Num dos artigos a seguir, mostrarei essa abordagem.
Antes de validar os símbolos na lista comum, é preciso salvá-los numa matriz. Em seguida, passamos esta matriz (source_array[]) para o método CProgram::CheckTradeSymbols(). Aqui, no primeiro ciclo, passamos esses símbolo para um parâmetro externo, e depois, no segundo ciclo, verificamos se existe este símbolo na lista no servidor da corretora. Se existir, devemos adicioná-lo à janela "Observação do mercado" e à matriz de símbolos verificados.
No final do método, se nenhum símbolo for encontrado, somente será usado o símbolo atual em que está instalado o EA.
class CProgram : public CWndEvents { private: //--- Verifica os símbolos para negociação na matriz passada e retorna uma matriz de símbolos disponíveis void CheckTradeSymbols(string &source_array[],string &checked_array[]); }; //+------------------------------------------------------------------+ //--- Verifica os símbolos para negociação na matriz passada e | //| retorna uma matriz de símbolos disponíveis | //+------------------------------------------------------------------+ void CProgram::CheckTradeSymbols(string &source_array[],string &checked_array[]) { int symbols_total =::SymbolsTotal(false); int size_source_array =::ArraySize(source_array); //--- Procuramos os símbolos especificados na lista geral for(int i=0; i<size_source_array; i++) { for(int s=0; s<symbols_total; s++) { //--- Obtemos o nome do símbolo atual na lista geral string symbol_name=::SymbolName(s,false); //--- Se coincidir if(symbol_name==source_array[i]) { //--- Definimos o símbolo na observação do mercado ::SymbolSelect(symbol_name,true); //--- Adicionamos à matriz de símbolos confirmados int size_array=::ArraySize(checked_array); ::ArrayResize(checked_array,size_array+1); checked_array[size_array]=symbol_name; break; } } } //--- Se nenhum símbolo for encontrado, usamos apenas o símbolo atual if(::ArraySize(checked_array)<1) { ::ArrayResize(checked_array,1); checked_array[0]=_Symbol; } }
Para ler o parâmetro externo de string em que são especificados os símbolos, é usado o método CProgram::CheckSymbols(). Aqui, a string é dividida numa matriz pelo separador ','. Nas linhas resultantes, são cortados os espaços em branco em ambos os lados. Depois disso, a matriz é enviada para verificação ao método CProgram::CheckTradeSymbols() que consideramos acima.
class CProgram : public CWndEvents { private: //--- Verifica e seleciona para a matriz os símbolos para negociação a partir da string int CheckSymbols(const string symbols_enum); }; //+-----------------------------------------------------------------------------------+ //| Verifica e seleciona na matriz os símbolos para negociação a partir da string | //+-----------------------------------------------------------------------------------+ int CProgram::CheckSymbols(const string symbols_enum) { if(symbols_enum!="") ::Print(__FUNCTION__," > input trade symbols: ",symbols_enum); //--- Obtemos os símbolos a partir da strings string symbols[]; ushort u_sep=::StringGetCharacter(",",0); ::StringSplit(symbols_enum,u_sep,symbols); //--- Cortamos os espaços em branco de ambos os lados int elements_total=::ArraySize(symbols); for(int e=0; e<elements_total; e++) { ::StringTrimLeft(symbols[e]); ::StringTrimRight(symbols[e]); } //--- Verificamos os símbolos ::ArrayFree(m_symbols); CheckTradeSymbols(symbols,m_symbols); //--- Retornamos o número de símbolos para negociação return(::ArraySize(m_symbols)); }
O arquivo com a classe de estratégia de negociação é anexado ao arquivo com a classe de aplicativo, e é criada uma matriz dinâmica de tipo CStrategy.
#include "Strategy.mqh" //+------------------------------------------------------------------+ //| Classe para criar o aplicativo | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Matriz de estratégias CStrategy m_strategy[]; };
Durante a inicialização do programa, é, precisamente, aqui que, a partir de um parâmetro externo, obtemos a matriz de símbolos e seu número. Em seguida, pelo número de caracteres, definimos o tamanho da matriz de estratégias e inicializamos todas as instâncias das estratégias, passado em cada uma delas o nome de símbolo.
class CProgram : public CWndEvents { private: //--- Total de símbolos int m_symbols_total; }; //+------------------------------------------------------------------+ //| Inicialização | //+------------------------------------------------------------------+ bool CProgram::OnInitEvent(void) { //--- Obtemos os símbolos para a negociação m_symbols_total=CheckSymbols(Symbols); //--- Tamanho da matriz da estratégia de negociação ::ArrayResize(m_strategy,m_symbols_total); //--- Inicialização for(int i=0; i<m_symbols_total; i++) { if(!m_strategy[i].OnInitEvent(m_symbols[i])) return(false); } //--- Inicialização bem-sucedida return(true); }
Em seguida, consideramos salvar os dados do último teste num arquivo.
Salvando dados num arquivo
Salvamos os dados do último teste na pasta de dados compartilhados do terminal. Assim, o arquivo estará acessível a partir de qualquer terminal MetaTrader 5. No construtor, de uma vez, definimos o nome da pasta e o nome do arquivo:
class CProgram : public CWndEvents { private: //--- Caminho para o arquivo com os resultados do último teste string m_last_test_report_path; }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CProgram::CProgram(void) : m_symbols_total(0) { //--- Caminho para o arquivo com os resultados do último teste m_last_test_report_path=::MQLInfoString(MQL_PROGRAM_NAME)+"\\LastTest.csv"; }
Examinamos o método CProgram::CreateSymbolBalanceReport(), que nos ajudará a salvar no arquivo. Para trabalhar neste método (e também no outro que vamos considerar mais adiante), precisaremos de matrizes de saldos de símbolos.
//--- Matrizes para os saldos de todos os símbolos struct CReportBalance { double m_data[]; }; //+------------------------------------------------------------------+ //| Classe para criar o aplicativo | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Matriz dos saldos de todos os símbolos CReportBalance m_symbol_balance[]; //--- private: //--- Cria um relatório de teste para trades no formato CSV void CreateSymbolBalanceReport(void); }; //+------------------------------------------------------------------+ //| Cria um relatório de teste para os trades no formato CSV | //+------------------------------------------------------------------+ void CProgram::CreateSymbolBalanceReport(void) { ... }
No início do método, abrimos o arquivo para trabalhar na pasta compartilhada dos terminais (FILE_COMMON):
... //--- Criamos o arquivo para salvar os dados na pasta compartilhada do terminal int file_handle=::FileOpen(m_last_test_report_path,FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON); //--- Se o identificador for válido (o arquivo for criado/aberto) if(file_handle==INVALID_HANDLE) { ::Print(__FUNCTION__," > Error creating file: ",::GetLastError()); return; } ...
Algumas variáveis auxiliares serão necessárias para formar alguns indicadores do relatório. Salvaremos no arquivo todo o histórico de trades com os dados listados abaixo:
- Hora do trade
- Símbolo
- Tipo
- Direção
- Volume
- Preço
- Swap
- Resultado (lucro/perda)
- Rebaixamento
- Saldo. Nesta coluna, aparecerá o saldo total, e, nas seguintes, os saldos de símbolos envolvidos no teste
Aqui formamos a primeira linha com os cabeçalhos desses dados:
... double max_drawdown =0.0; // Rebaixamento máximo double balance =0.0; // Saldo string delimeter =","; // Separador string string_to_write =""; // Para formar uma linha para gravação //--- Formamos a linha de cabeçalho string headers="TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,PRICE,SWAP($),PROFIT($),DRAWDOWN(%),BALANCE"; ...
Se mais de um símbolo participar do teste, a linha com os cabeçalhos deve ser complementada com seus nomes. Depois disso, os cabeçalhos (a primeira linha) podem ser salvos no arquivo.
... //--- Se mais de um símbolo participar, complementamos a linha de cabeçalhos int symbols_total=::ArraySize(m_symbols); if(symbols_total>1) { for(int s=0; s<symbols_total; s++) ::StringAdd(headers,delimeter+m_symbols[s]); } //--- Escrevemos os cabeçalhos do relatório ::FileWrite(file_handle,headers); ...
Em seguida, obtemos tanto o histórico de trades completo quanto seu número e, em seguida, definimos os tamanhos das matrizes:
... //--- Obtemos todo o histórico ::HistorySelect(0,LONG_MAX); //--- Conhecemos o número de trades int deals_total=::HistoryDealsTotal(); //--- Definimos o tamanho da matriz de saldos pelo número de símbolos ::ArrayResize(m_symbol_balance,symbols_total); //--- Definimos o tamanho das matrizes de trades para cada símbolo for(int s=0; s<symbols_total; s++) ::ArrayResize(m_symbol_balance[s].m_data,deals_total); ...
No ciclo principal, percorremos todo o histórico e formamos as linhas para gravar no arquivo. Ao calcular os lucros, também somamos o swap e a comissão. Se for verificado que os símbolos são mais de um, no segundo ciclo vamos por cada um deles e formamos o saldo para cada símbolo.
Escrevemos os dados num arquivo, linha por linha. No final do método, o arquivo é fechado.... //--- Passamos pelo ciclo e registramos os dados for(int i=0; i<deals_total; i++) { //--- Obtemos o boleto do trade if(!m_deal_info.SelectByIndex(i)) continue; //--- Ficamos sabendo o número de caracteres no preço int digits=(int)::SymbolInfoInteger(m_deal_info.Symbol(),SYMBOL_DIGITS); //--- Calculamos o saldo total balance+=m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission(); //--- Formamos uma linha para salvar usando concatenação ::StringConcatenate(string_to_write, ::TimeToString(m_deal_info.Time(),TIME_DATE|TIME_MINUTES),delimeter, m_deal_info.Symbol(),delimeter, m_deal_info.TypeDescription(),delimeter, m_deal_info.EntryDescription(),delimeter, ::DoubleToString(m_deal_info.Volume(),2),delimeter, ::DoubleToString(m_deal_info.Price(),digits),delimeter, ::DoubleToString(m_deal_info.Swap(),2),delimeter, ::DoubleToString(m_deal_info.Profit(),2),delimeter, MaxDrawdownToString(i,balance,max_drawdown),delimeter, ::DoubleToString(balance,2)); //--- Se mais de um símbolo estiver envolvido, escrevemos os valores do seu saldo if(symbols_total>1) { //--- Passamos por todos os símbolos for(int s=0; s<symbols_total; s++) { //--- Se os símbolos corresponderem e o resultado do trade for diferente de zero if(m_deal_info.Symbol()==m_symbols[s] && m_deal_info.Profit()!=0) //--- Exibimos o trade, no saldo, com este símbolo. Consideramos o swap e a comissão m_symbol_balance[s].m_data[i]=m_symbol_balance[s].m_data[i-1]+m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission(); //--- Caso contrário, escrevemos o valor anterior else { // --- Se o tipo de trade for "Cálculo do saldo " (a primeira transação), para todos os símbolos, o saldo será o mesmo if(m_deal_info.DealType()==DEAL_TYPE_BALANCE) m_symbol_balance[s].m_data[i]=balance; //--- Caso contrário, escrevemos o valor anterior no índice atual else m_symbol_balance[s].m_data[i]=m_symbol_balance[s].m_data[i-1]; } //--- Adicionamos à linha o saldo do símbolo ::StringAdd(string_to_write,delimeter+::DoubleToString(m_symbol_balance[s].m_data[i],2)); } } //--- Escrevemos a linha gerada ::FileWrite(file_handle,string_to_write); //--- Zeramento obrigatório para a próxima linha string_to_write=""; } //--- Fechamos o arquivo ::FileClose(file_handle); ...
No processo de geração de linhas (veja o código acima), a fim de salvar no arquivo para calcular o rebaixamento total do saldo, é usado o método CProgram::MaxDrawdownToString(). Quando esse método é chamado pela primeira vez, o rebaixamento é zero, e, como um máximo/mínimo local, iremos nos lembrar do valor do saldo atual. Nas seguintes chamadas de método, no caso em que o saldo é maior que na memória, consideramos o rebaixamento de acordo com os valores anteriores e atualizamos o máximo local. Caso contrário, atualizamos o mínimo local e retornamos um valor nulo (uma linha em branco).
class CProgram : public CWndEvents { private: //--- Retorna o rebaixamento máximo do máximo local string MaxDrawdownToString(const int deal_number,const double balance,double &max_drawdown); }; //+------------------------------------------------------------------+ //| Retorna o rebaixamento máximo do máximo local | //+------------------------------------------------------------------+ string CProgram::MaxDrawdownToString(const int deal_number,const double balance,double &max_drawdown) { //--- Linha para exibição no relatório string str=""; //--- Para calcular o máximo local e o rebaixamento static double max=0.0; static double min=0.0; //--- Se o primeiro trade if(deal_number==0) { //--- Ainda não houver rebaixamento max_drawdown=0.0; //--- Definimos o ponto inicial como um máximo local max=balance; min=balance; } else { //--- Se o saldo atual for maior que na memória if(balance>max) { //--- Calculamos o rebaixamento de acordo com os valores anteriores max_drawdown=100-((min/max)*100); //--- Atualizamos o máximo local max=balance; min=balance; } else { //--- Retornamos o valor zero do rebaixamento e atualizamos o mínimo max_drawdown=0.0; min=fmin(min,balance); } } //--- Definimos a linha do relatório str=(max_drawdown==0)? "" : ::DoubleToString(max_drawdown,2); return(str); }
A estrutura do arquivo permite abri-lo no Excel. A imagem abaixo apresenta como é mostrado:
Fig. 2. Estrutura do arquivo de relatório no Excel.
Como resultado disso, é preciso fazer, no final do teste, a chamada do método CProgram::CreateSymbolBalanceReport(), para salvar o relatório, após o teste:
//+------------------------------------------------------------------+ //| Evento de final de teste | //+------------------------------------------------------------------+ double CProgram::OnTesterEvent(void) { //--- Salvamos o relatório somente após o teste if(::MQLInfoInteger(MQL_TESTER) && !::MQLInfoInteger(MQL_OPTIMIZATION) && !::MQLInfoInteger(MQL_VISUAL_MODE) && !::MQLInfoInteger(MQL_FRAME_MODE)) { //--- Geração do relatório e gravação em arquivos CreateSymbolBalanceReport(); } //--- return(0.0); }
Em seguida, consideramos a leitura dos dados do relatório.
Extraindo dados de um arquivo
Após tudo o que foi implementado acima, agora, todas as verificações do EA, no Testador de Estratégias, terminarão com um registro do relatório no arquivo. Em seguida, consideraremos os métodos pelos quais serão lidos os dados desse relatório. Primeiro de tudo, é preciso ler o arquivo e colocar seu conteúdo numa matriz, para que seja conveniente trabalhar com ele. Para isso, usamos o método CProgram::ReadFileToArray(). Aqui, abrimos o arquivo, no qual, no final do teste do EA, foi registrado o histórico de trades. No ciclo, lemos o arquivo, até a última linha, e preenchemos a matriz com os dados de origem.
class CProgram : public CWndEvents { private: //--- Matriz para os dados do arquivo string m_source_data[]; //--- private: //--- Leitura do arquivo para a matriz passada bool ReadFileToArray(const int file_handle); }; //+------------------------------------------------------------------+ //| Leitura do arquivo na matriz passada | //+------------------------------------------------------------------+ bool CProgram::ReadFileToArray(const int file_handle) { //--- Abrimos o arquivo int file_handle=::FileOpen(m_last_test_report_path,FILE_READ|FILE_ANSI|FILE_COMMON); //--- Sair se o arquivo não abrir if(file_handle==INVALID_HANDLE) return(false); //--- Liberamos a matriz ::ArrayFree(m_source_data); //--- Lemos o arquivo numa matriz while(!::FileIsEnding(file_handle)) { int size=::ArraySize(m_source_data); ::ArrayResize(m_source_data,size+1,RESERVE); m_source_data[size]=::FileReadString(file_handle); } //--- Fechar o arquivo ::FileClose(file_handle); return(true); }
Será necessário o método auxiliar CProgram::GetStartIndex() para definir o índice da coluna denominada BALANCE. Como argumentos, você precisa passar para ele uma linha de cabeçalho, na qual será realizada a pesquisa do nome da coluna e da matriz dinâmica para os elementos da linha dividida pelo separador ','.
class CProgram : public CWndEvents { private: //--- Índice inicial de saldos no relatório bool GetBalanceIndex(const string headers); }; //+-------------------------------------------------------------------------------+ //| Definimos o índice a partir do qual é necessário iniciar a cópia de dados | //+-------------------------------------------------------------------------------+ bool CProgram::GetBalanceIndex(const string headers) { //--- Obtemos os elementos da linha pelo separador string str_elements[]; ushort u_sep=::StringGetCharacter(",",0); ::StringSplit(headers,u_sep,str_elements); //--- Procuramos uma coluna chamada 'BALANCE' int elements_total=::ArraySize(str_elements); for(int e=elements_total-1; e>=0; e--) { string str=str_elements[e]; ::StringToUpper(str); //--- Se encontrarmos a coluna com o cabeçalho desejado if(str=="BALANCE") { m_balance_index=e; break; } } //--- Exibir a mensagem, se não for encontrada a coluna denominada 'BALANCE' if(m_balance_index==WRONG_VALUE) { ::Print(__FUNCTION__," > In the report file there is no heading \'BALANCE\' ! "); return(false); } //--- Se for bem-sucedido return(true); }
No eixo X, ambos os gráficos exibirão os números dos trades. O intervalo de datas será mostrado como informação adicional no rodapé do saldo. Para determinar a data inicial e final do histórico de trades, é implementado o método CProgram::GetDateRange(). Duas variáveis de string são passadas para ele, por referência, para a data inicial e final do histórico de trades.
class CProgram : public CWndEvents { private: //--- Intervalo de datas void GetDateRange(string &from_date,string &to_date); }; //+------------------------------------------------------------------+ //| Obtemos as datas inicial e final do intervalo de teste | //+------------------------------------------------------------------+ void CProgram::GetDateRange(string &from_date,string &to_date) { //--- Sair, se a linha for inferior a 3 int strings_total=::ArraySize(m_source_data); if(strings_total<3) return; //--- Obtemos a datas inicial e final do relatório string str_elements[]; ushort u_sep=::StringGetCharacter(",",0); //--- ::StringSplit(m_source_data[1],u_sep,str_elements); from_date=str_elements[0]; ::StringSplit(m_source_data[strings_total-1],u_sep,str_elements); to_date=str_elements[0]; }
Para obter os dados dos dados de saldos e rebaixamentos, são usados os métodos CProgram::GetReportDataToArray() e CProgram::AddDrawDown(). O segundo é chamado no primeiro, e seu código é muito breve (veja a listagem abaixo). Aqui, o índice de transação e o valor do rebaixamento são transferidos. Eles são colocados em matrizes correspondentes cujos valores serão exibidos no gráfico. Na matriz m_dd_y[] armazenamos o valor do rebaixamento, enquanto na matriz m_dd_x[] - o índice em que deve ser exibido esse valor. Assim, nos índices onde não há valores, nada será exibido nos gráficos (valores vazios).
class CProgram : public CWndEvents { private: //--- Rebaixamento do saldo geral double m_dd_x[]; double m_dd_y[]; //--- private: //--- Adiciona o rebaixamento na matriz void AddDrawDown(const int index,const double drawdown); }; //+------------------------------------------------------------------+ //| Adiciona o rebaixamento na matriz | //+------------------------------------------------------------------+ void CProgram::AddDrawDown(const int index,const double drawdown) { int size=::ArraySize(m_dd_y); ::ArrayResize(m_dd_y,size+1,RESERVE); ::ArrayResize(m_dd_x,size+1,RESERVE); m_dd_y[size] =drawdown; m_dd_x[size] =(double)index; }
No método CProgram::GetReportDataToArray(), primeiro são definidos os tamanhos das matrizes e a quantidade de séries, para o gráfico de saldos. Logo, inicializamos a matriz de cabeçalhos. Em seguida, no ciclo, os elementos de linha são extraídos, linha por linha, pelo separador, e os dados são colocados em matrizes de rebaixamento e saldo.
class CProgram : public CWndEvents { private: //--- Obtém os dados dos símbolos a partir do relatório int GetReportDataToArray(string &headers[]); }; //+------------------------------------------------------------------+ //| Recebemos os dados dos símbolos a partir do relatório | //+------------------------------------------------------------------+ int CProgram::GetReportDataToArray(string &headers[]) { //--- Obtemos os elementos da linha de cabeçalho string str_elements[]; ushort u_sep=::StringGetCharacter(",",0); ::StringSplit(m_source_data[0],u_sep,str_elements); //--- Dimensões de matriz int strings_total =::ArraySize(m_source_data); int elements_total =::ArraySize(str_elements); //--- Liberamos as matrizes ::ArrayFree(m_dd_y); ::ArrayFree(m_dd_x); //--- Obtemos o número de séries int curves_total=elements_total-m_balance_index; curves_total=(curves_total<3)? 1 : curves_total; //--- Definimos o tamanho da matriz de acordo com o número de séries ::ArrayResize(headers,curves_total); ::ArrayResize(m_symbol_balance,curves_total); //--- Definimos o tamanho da série for(int i=0; i<curves_total; i++) ::ArrayResize(m_symbol_balance[i].m_data,strings_total,RESERVE); //--- Se houver vários símbolos (obtemos os cabeçalhos) if(curves_total>2) { for(int i=0,e=m_balance_index; e<elements_total; e++,i++) headers[i]=str_elements[e]; } else headers[0]=str_elements[m_balance_index]; //---Obtemos os dados for(int i=1; i<strings_total; i++) { ::StringSplit(m_source_data[i],u_sep,str_elements); //--- Coletamos os dados nas matrizes if(str_elements[m_balance_index-1]!="") AddDrawDown(i,double(str_elements[m_balance_index-1])); //--- Se houver vários símbolos if(curves_total>2) for(int b=0,e=m_balance_index; e<elements_total; e++,b++) m_symbol_balance[b].m_data[i]=double(str_elements[e]); else m_symbol_balance[0].m_data[i]=double(str_elements[m_balance_index]); } //--- Primeiro valor da série for(int i=0; i<curves_total; i++) m_symbol_balance[i].m_data[0]=(strings_total<2)? 0 : m_symbol_balance[i].m_data[1]; //--- Retornar o número de séries return(curves_total); }
Na próxima seção, veremos como exibir os dados nos gráficos.
Exibindo dados em gráficos
A chamada de métodos auxiliares, que abordamos na seção anterior, ocorrerá no início do método, para atualização do gráfico de saldo - CProgram::UpdateBalanceGraph(). Logo, as séries atuais são excluídas do gráfico, porque a quantidade símbolos participando no último teste pode mudar. Então, no ciclo, pelo número atual de símbolos definidos no método CProgram::GetReportDataToArray(), acrescentamos as novas séries de dados de saldo e, ao mesmo tempo, definimos os valores mínimo e máximo ao longo do eixo Y.
Aqui decoramos, nos campos da classe, o tamanho da série e a marca de escala ao longo do eixo X. Esses valores também serão necessários para formatar o gráfico de rebaixamento. Para o eixo Y, são ajustados recuos, para os extremos do gráfico, iguais a 5%. Como resultado, todos esses valores se aplicam ao gráfico de saldo, enquanto o gráfico é atualizado para mostrar as alterações mais recentes.
class CProgram : public CWndEvents { private: //--- Total de dados na série double m_data_total; //--- Marca de escala X double m_default_step; //--- private: //--- Atualiza os dados no gráfico de saldo void UpdateBalanceGraph(void); }; //+------------------------------------------------------------------+ //| Atualizar o gráfico de saldo | //+------------------------------------------------------------------+ void CProgram::UpdateBalanceGraph(void) { //--- Obtemos os dados do intervalo de teste string from_date=NULL,to_date=NULL; GetDateRange(from_date,to_date); //--- Definimos o índice a partir do qual é necessário começar a copiar dados if(!GetBalanceIndex(m_source_data[0])) return; //--- Obtemos os dados dos símbolos a partir do relatório string headers[]; int curves_total=GetReportDataToArray(headers); //--- Atualizamos todas as séries do gráfico com novos dados CColorGenerator m_generator; CGraphic *graph=m_graph1.GetGraphicPointer(); //--- Limpar o gráfico int total=graph.CurvesTotal(); for(int i=total-1; i>=0; i--) graph.CurveRemoveByIndex(i); //--- Máximo e mínimo do gráfico double y_max=0.0,y_min=m_symbol_balance[0].m_data[0]; //--- Adicionamos dados for(int i=0; i<curves_total; i++) { //--- Definimos o máximo/mínimo ao longo do eixo Y y_max=::fmax(y_max,m_symbol_balance[i].m_data[::ArrayMaximum(m_symbol_balance[i].m_data)]); y_min=::fmin(y_min,m_symbol_balance[i].m_data[::ArrayMinimum(m_symbol_balance[i].m_data)]); //--- Adicionamos uma série ao gráfico CCurve *curve=graph.CurveAdd(m_symbol_balance[i].m_data,m_generator.Next(),CURVE_LINES,headers[i]); } //--- Número de valores e espaçamento de grade do eixo X m_data_total =::ArraySize(m_symbol_balance[0].m_data)-1; m_default_step =(m_data_total<10)? 1 : ::MathFloor(m_data_total/5.0); //--- Intervalo e recuos double range =::fabs(y_max-y_min); double offset =range*0.05; //--- Cor para a primeira série graph.CurveGetByIndex(0).Color(::ColorToARGB(clrCornflowerBlue)); //--- Propriedades do eixo horizontal CAxis *x_axis=graph.XAxis(); x_axis.AutoScale(false); x_axis.Min(0); x_axis.Max(m_data_total); x_axis.MaxGrace(0); x_axis.MinGrace(0); x_axis.DefaultStep(m_default_step); x_axis.Name(from_date+" - "+to_date); //--- Propriedades do eixo vertical CAxis *y_axis=graph.YAxis(); y_axis.AutoScale(false); y_axis.Min(y_min-offset); y_axis.Max(y_max+offset); y_axis.MaxGrace(0); y_axis.MinGrace(0); y_axis.DefaultStep(range/10.0); //--- Atualizar gráfico graph.CurvePlotAll(); graph.Update(); }
Para atualização do gráfico de rebaixamentos, usa-se o método CProgram::UpdateDrawdownGraph(). Como os dados são calculados no método CProgram::UpdateBalanceGraph(), eles precisam serem aplicados ao gráfico e atualizá-lo.
class CProgram : public CWndEvents { private: //--- Atualiza os dados no gráfico de rebaixamento void UpdateDrawdownGraph(void); }; //+------------------------------------------------------------------+ //| Atualizar o gráfico de rebaixamento | //+------------------------------------------------------------------+ void CProgram::UpdateDrawdownGraph(void) { //--- Atualizamos o gráfico de rebaixamento CGraphic *graph=m_graph2.GetGraphicPointer(); CCurve *curve=graph.CurveGetByIndex(0); curve.Update(m_dd_x,m_dd_y); curve.PointsFill(false); curve.PointsSize(6); curve.PointsType(POINT_CIRCLE); //--- Propriedades do eixo horizontal CAxis *x_axis=graph.XAxis(); x_axis.AutoScale(false); x_axis.Min(0); x_axis.Max(m_data_total); x_axis.MaxGrace(0); x_axis.MinGrace(0); x_axis.DefaultStep(m_default_step); //--- Atualizar gráfico graph.CalculateMaxMinValues(); graph.CurvePlotAll(); graph.Update(); }
A chamada dos métodos CProgram::UpdateBalanceGraph() e CProgram::UpdateDrawdownGraph() é realizada no método CProgram::UpdateGraphs(). Antes de chamar esses métodos, primeiro, é chamado o método CProgram::ReadFileToArray(). Ele recebe os dados do arquivo com os resultados do último teste do EA.
class CProgram : public CWndEvents { private: //--- Atualiza os dados nos gráficos de resultados do último teste void UpdateGraphs(void); }; //+------------------------------------------------------------------+ //| Atualizar gráficos | //+------------------------------------------------------------------+ void CProgram::UpdateGraphs(void) { //--- Preenchemos a matriz de dados a partir do arquivo if(!ReadFileToArray()) { ::Print(__FUNCTION__," > Could not open the test results file!"); return; } //--- Atualizar o gráfico de saldos e rebaixamentos UpdateBalanceGraph(); UpdateDrawdownGraph(); }
Apresentando o resultado
Para exibir os resultados do último teste nos gráficos da interface, é preciso pressionar apenas um botão. O evento dessa ação é processado no método CProgram::OnEvent():
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Evento de pressionado de botões if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- Pressionado do botão 'Update data' if(lparam==m_update_graph.Id()) { //--- Atualizar gráficos UpdateGraphs(); return; } //--- return; } }
Se o EA já tiver sido testado antes de clicar no botão, veremos algo assim:
Fig. 3. Resultado do último teste do EA.
Por conseguinte, se o EA é carregado no gráfico, você pode, conferindo os resultados de muitos testes, após otimizar os parâmetros, ver imediatamente as mudanças no gráfico de saldo multissímbolo.
Gráfico de saldo multissímbolo durante a negociação e testes
Agora, consideremos a segunda versão do EA, quer dizer, quando o gráfico multissímbolo do saldo é desenhado e atualizado durante o processo de negociação.
A interface gráfica permanece quase igual à da versão descrita acima. A única diferença é que, em vez de um botão para atualização, haverá um calendário suspenso com o qual você pode especificar data a partir de qual exibir os resultados da negociação nos gráficos.
No método OnTrade(), vamos conferir, na chegada do evento, a alteração no histórico. O método CProgram::IsLastDealTicket() é usado para verificar que o histórico foi carregado com um novo trade. Nele obtemos o histórico a partir do tempo salvo na memória, após a última chamada. Em seguida, verificamos as boletas do último trade e a boleta armazenada na memória. Se as boletas forem diferentes, atualizamos, na memória, a boleta e a hora do último trade para a próxima verificação, e retornamos true, a fim de indicar que o histórico mudou.
class CProgram : public CWndEvents { private: //--- Hora e boleta do último trae verificado datetime m_last_deal_time; ulong m_last_deal_ticket; //--- private: //--- Verificação de novo trade bool IsLastDealTicket(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CProgram::CProgram(void) : m_last_deal_time(NULL), m_last_deal_ticket(WRONG_VALUE) { } //+------------------------------------------------------------------+ //| Retorna o evento do último trade no novo símbolo especificado | //+------------------------------------------------------------------+ bool CProgram::IsLastDealTicket(void) { //--- Sair se o histórico não for recebido if(!::HistorySelect(m_last_deal_time,LONG_MAX)) return(false); //--- Obtemos o número de trades na lista int total_deals=::HistoryDealsTotal(); /--- Percorremos todos os trades na lista recebida, desde a última transação até a primeira for(int i=total_deals-1; i>=0; i--) { //--- Obtemos o boleto do trade ulong deal_ticket=::HistoryDealGetTicket(i); //--- Se as boletas forem iguais, sairemos if(deal_ticket==m_last_deal_ticket) return(false); //--- Se as boletas não forem iguais, informamos sobre isso else { datetime deal_time=(datetime)::HistoryDealGetInteger(deal_ticket,DEAL_TIME); //--- Lembramos a hora a boleta do último trade m_last_deal_time =deal_time; m_last_deal_ticket =deal_ticket; return(true); } } //--- Boletas de outro símbolo return(false); }
Antes de percorrer o histórico de trades e preencher as matrizes com dados, é preciso determinar quais e quantos símbolos estão no histórico. Isso é necessário para definir o tamanho das matrizes. Para fazer isso, usa-se o método CProgram::GetHistorySymbols(). Antes de chamá-lo, deve-se selecionar o histórico no intervalo desejado. Em seguida, adicionamos à lina os símbolos que encontramos no histórico. Para garantir que os símbolos na linha não se repitam, verificamos a presença da substring especificada. Depois disso adicionamos os símbolos encontrados no histórico à matriz e retornamos o número de símbolos.
class CProgram : public CWndEvents { private: //--- Matriz de símbolos a partir do histórico string m_symbols_name[]; //--- private: //--- Obtemos os símbolos a partir do histórico da conta e retornamos seu número int GetHistorySymbols(void); }; //+----------------------------------------------------------------------+ //| Obtemos os símbolos a partir do histórico e retornamos seu número | //+----------------------------------------------------------------------+ int CProgram::GetHistorySymbols(void) { string check_symbols=""; //--- Passamos, pela primeira vez, no ciclo e obtemos os símbolos de negociação int deals_total=::HistoryDealsTotal(); for(int i=0; i<deals_total; i++) { //--- Obtemos o boleto do trade if(!m_deal_info.SelectByIndex(i)) continue; //--- Se houver um nome de símbolo if(m_deal_info.Symbol()=="") continue; //--- Se não existir essa linha, adicionamo-la if(::StringFind(check_symbols,m_deal_info.Symbol(),0)==-1) ::StringAdd(check_symbols,(check_symbols=="")? m_deal_info.Symbol() : ","+m_deal_info.Symbol()); } //--- Obtemos os elementos da linha pelo separador ushort u_sep=::StringGetCharacter(",",0); int symbols_total=::StringSplit(check_symbols,u_sep,m_symbols_name); //--- Retornamos o número de símbolos return(symbols_total); }
Para obter o saldo de multissímbolo, é preciso chamar o métodoCProgram::GetHistorySymbolsBalance():
class CProgram : public CWndEvents { private: //--- Obtém o saldo geral e os saldos de todos os símbolos separadamente void GetHistorySymbolsBalance(void); }; //+----------------------------------------------------------------------------+ //| Obtém o saldo geral e os saldos de todos os símbolos, separadamente | //+----------------------------------------------------------------------------+ void CProgram::GetHistorySymbolsBalance(void) { ... }
Aqui, no início, é preciso obter o saldo inicial da conta. Obtemos o histórico desde o primeiro trade que será este saldo inicial. Assumimos que é possível especificar no calendário a data a partir da qual você deseja mostrar o resultado da negociação. Por isso, escolhemos o histórico novamente. Em seguida, por meio do método CProgram::GetHistorySymbols(), obtemos os símbolos no histórico selecionado e sua quantidade, e, depois, definimos o tamanho para a matriz. Para exibir o intervalo de resultados do histórico, determinamos as datas de início e final.
... //--- Tamanho inicial do depósito ::HistorySelect(0,LONG_MAX); double balance=(m_deal_info.SelectByIndex(0))? m_deal_info.Profit() : 0; //--- Obtemos o histórico a partir da data especificada ::HistorySelect(m_from_trade.SelectedDate(),LONG_MAX); //--- Obtemos o número de símbolos int symbols_total=GetHistorySymbols(); //--- Liberamos as matrizes ::ArrayFree(m_dd_x); ::ArrayFree(m_dd_y); //--- Definimos o tamanho da matriz de saldo de acordo com o número de símbolos + 1 para o saldo total ::ArrayResize(m_symbols_balance,(symbols_total>1)? symbols_total+1 : 1); //--- Definimos o tamanho das matrizes de trades para cada símbolo int deals_total=::HistoryDealsTotal(); for(int s=0; s<=symbols_total; s++) { if(symbols_total<2 && s>0) break; //--- ::ArrayResize(m_symbols_balance[s].m_data,deals_total); ::ArrayInitialize(m_symbols_balance[s].m_data,0); } //--- Número de saldos int balances_total=::ArraySize(m_symbols_balance); //--- Começo e fim do histórico m_begin_date =(m_deal_info.SelectByIndex(0))? m_deal_info.Time() : m_from_trade.SelectedDate(); m_end_date =(m_deal_info.SelectByIndex(deals_total-1))? m_deal_info.Time() : ::TimeCurrent(); ...
No próximo ciclo, são calculados os saldos dos símbolos e os rebaixamentos. Os dados recebidos são colocados em matrizes. Para o cálculo de rebaixamento, neste caso, também são usado os métodos descritos nas seções anteriores.
... //--- Rebaixamento máximo double max_drawdown=0.0; //--- Escrevemos as matrizes de saldo na matriz transferida for(int i=0; i<deals_total; i++) { //--- Obtemos o boleto do trade if(!m_deal_info.SelectByIndex(i)) continue; //--- Inicialização no primeiro trade if(i==0 && m_deal_info.DealType()==DEAL_TYPE_BALANCE) balance=0; //--- A partir da data especificada if(m_deal_info.Time()>=m_from_trade.SelectedDate()) { //--- Calculamos o saldo total balance+=m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission(); m_symbols_balance[0].m_data[i]=balance; //--- Calculamos o rebaixamento if(MaxDrawdownToString(i,balance,max_drawdown)!="") AddDrawDown(i,max_drawdown); } //--- Se mais de um símbolo estiver envolvido, escrevemos os valores do seu saldo if(symbols_total<2) continue; //--- Apenas a partir da data selecionada if(m_deal_info.Time()<m_from_trade.SelectedDate()) continue; //--- Passamos por todos os símbolos for(int s=1; s<balances_total; s++) { int prev_i=i-1; // --- Se o tipo de trade for "Cálculo do saldo " (a primeira transação) ... if(prev_i<0 || m_deal_info.DealType()==DEAL_TYPE_BALANCE) { //--- ... para todos os símbolos, o saldo é o mesmo m_symbols_balance[s].m_data[i]=balance; continue; } //--- Se os símbolos forem iguais e o resutado do trade for diferente de zero if(m_deal_info.Symbol()==m_symbols_name[s-1] && m_deal_info.Profit()!=0) { //--- Exibimos o trade, no saldo, com este símbolo. Vamos levar em conta o swap e a comissão. m_symbols_balance[s].m_data[i]=m_symbols_balance[s].m_data[prev_i]+m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission(); } //--- Caso contrário, escrevemos o valor anterior else m_symbols_balance[s].m_data[i]=m_symbols_balance[s].m_data[prev_i]; } } ...
Os dados são adicionados aos gráficos e atualizados usando os métodos CProgram::UpdateBalanceGraph() e CProgram::UpdateDrawdownGraph(). Seu código é quase o mesmo da primeira versão do EA discutido nas seções anteriores, por isso passapmos para a parte em que são chamados.
Em primeiro lugar, esses métodos são chamados ao criar uma interface gráfica, para que o usuário veja imediatamente o resultado da negociação. Após isso, os gráficos são atualizados ao ocorrerem eventos de negociação no método OnTrade().
class CProgram : public CWndEvents { private: //--- Inicialização de gráficos void UpdateBalanceGraph(const bool update=false); void UpdateDrawdownGraph(void); }; //+------------------------------------------------------------------+ //| Evento de operação de negociação | //+------------------------------------------------------------------+ void CProgram::OnTradeEvent(void) { //--- Atualização de gráficos de saldo e rebaixamento UpdateBalanceGraph(); UpdateDrawdownGraph(); }
Além disso, o usuário na interface gráfica pode especificar a data a partir da qual deseja construir os gráficos de saldo. Para forçar a atualização do gráfico sem verificar a última boleta do trade, para o método CProgram::UpdateBalanceGraph() você precisa transferir o valor true.
Evento de alteração de data no calendário (ON_CHANGE_DATE) é processado assim:
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Eventos escolha de data no calendário if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE) { if(lparam==m_from_trade.Id()) { UpdateBalanceGraph(true); UpdateDrawdownGraph(); m_from_trade.ChangeComboBoxCalendarState(); } //--- return; } }
Veja como funciona no testador no modo de visualização:
Fig. 4. Resultado no testador, no modo de visualização.
Visualizamos relatórios do serviço Sinais
Como outro complemento que pode ser útil para os usuários, criaremos um EA com o qual será possível visualizar os resultados da negociação a partir dos relatórios no serviço Sinais.
Vá para a página do sinal de seu interesse e selecione a guia Histórico de transações:
Fig. 5. Histórico de trades do sinal.
O link para baixar o arquivo CSV com histórico de transações pode ser encontrado na parte inferior desta lista:
Fig. 6 Exportação do histórico de trades para um arquivo CSV.
Esses arquivos, para a implementação do EA, devem ser colocados no diretório do terminal, no caminho \MQL5\Files. Adicionamos um parâmetro externo ao Expert Advisor. Ele especificará o nome do arquivo de relatório cujos dados devem ser visualizados nos gráficos.
//+------------------------------------------------------------------+ //| Program.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ //--- Parâmetros externos input string PathToFile=""; // Path to file ...
Fig. 7. Parâmetro externo para especificar o arquivo de relatório.
Na interface gráfica desta versão do EA, haverá apenas dois gráficos. Durante o carregamento do EA no gráfico, no terminal, ele tenta abrir o arquivo especificado nas configurações. Se esse arquivo não for encontrado, o programa exibirá uma mensagem no Diário. O conjunto de métodos aqui é aproximadamente o mesmo que nas versões acima. Em alguns lugares existem pequenas diferenças, mas basicamente o princípio é o mesmo. Consideramos apenas os métodos em que a abordagem tenha mudado significativamente.
Assim, o arquivo é lido e linhas são movidas para a matriz, para os dados de origem. Agora, é preciso distribuir esses dados numa matriz bidimensional, como é feito nas tabelas. Isso é necessário para a classificação conveniente de dados no horário de abertura dos trades, desde o início até o final. Para isso, precisamos de uma matriz separada de matrizes.
//--- Matrizes para dados a partir do arquivo struct CReportTable { string m_rows[]; }; //+------------------------------------------------------------------+ //| Classe para criar o aplicativo | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Tabela do relatório CReportTable m_columns[]; //--- Número de linhas e colunas uint m_rows_total; uint m_columns_total; }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CProgram::CProgram(void) : m_rows_total(0), m_columns_total(0) { ... }
Para classificar a matriz de matrizes, você precisa dos seguintes métodos:
class CProgram : public CWndEvents { private: //--- Método de classificação rápida void QuickSort(uint beg,uint end,uint column); //--- Verificação da condição de classificação bool CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction); //--- Alterar os valores nas células especificadas, em lugares void Swap(uint r1,uint r2); };
Todos esses métodos foram discutidos em detalhes num dos artigos anteriores.
Todas as operações básicas são realizadas no método CProgram::GetData(). Iremos considerá-lo mais detalhadamente.
class CProgram : public CWndEvents { private: //--- Obtemos os dados em matrizes int GetData(void); }; //+------------------------------------------------------------------+ //| Recebemos os dados dos símbolos a partir do relatório | //+------------------------------------------------------------------+ int CProgram::GetData(void) { ... }
Primeiro, definimos o número de linhas e elementos da linha pelo separador ';'. Em seguida, obtemos, numa matriz separada, os nomes de símbolos que estão no relatório e seu número. Depois, temos que preparar as matrizes e preenchê-las com os dados do relatório.
... //--- Obtemos os elementos da linha de cabeçalho string str_elements[]; ushort u_sep=::StringGetCharacter(";",0); ::StringSplit(m_source_data[0],u_sep,str_elements); //--- Número de linas e elementos de linha int strings_total =::ArraySize(m_source_data); int elements_total =::ArraySize(str_elements); //--- Obtemos os símbolos if((m_symbols_total=GetHistorySymbols())==WRONG_VALUE) return; //--- Liberamos as matrizes ::ArrayFree(m_dd_y); ::ArrayFree(m_dd_x); //--- Tamanho da série de dados ::ArrayResize(m_columns,elements_total); for(int i=0; i<elements_total; i++) ::ArrayResize(m_columns[i].m_rows,strings_total-1); //--- Preenchemos as matrizes com dados do arquivo for(int r=0; r<strings_total-1; r++) { ::StringSplit(m_source_data[r+1],u_sep,str_elements); for(int c=0; c<elements_total; c++) m_columns[c].m_rows[r]=str_elements[c]; } ...
Tudo está pronto para ordenar os dados. Aqui, é necessário definir o tamanho das matrizes de saldos de símbolos, antes de preenchê-las:
... //--- Número de linhas e colunas m_rows_total =strings_total-1; m_columns_total =elements_total; //--- Classificamos pela hora na primeira coluna QuickSort(0,m_rows_total-1,0); //--- Tamanho da série ::ArrayResize(m_symbol_balance,m_symbols_total); for(int i=0; i<m_symbols_total; i++) ::ArrayResize(m_symbol_balance[i].m_data,m_rows_total); ...
Em seguida, primeiro, preenchemos a matriz geral de saldo e rebaixamentos. Todos os trades relacionados ao reabastecimento do depósito serão omitidos.
... //--- Saldo e rebaixamento máximo double balance =0.0; double max_drawdown =0.0; //--- Obtemos os dados do salgo total for(uint i=0; i<m_rows_total; i++) { //--- Saldo inicial if(i==0) { balance+=(double)m_columns[elements_total-1].m_rows[i]; m_symbol_balance[0].m_data[i]=balance; } else { //--- Ignoramos o reabastecimento if(m_columns[1].m_rows[i]=="Balance") m_symbol_balance[0].m_data[i]=m_symbol_balance[0].m_data[i-1]; else { balance+=(double)m_columns[elements_total-1].m_rows[i]+(double)m_columns[elements_total-2].m_rows[i]+(double)m_columns[elements_total-3].m_rows[i]; m_symbol_balance[0].m_data[i]=balance; } } //--- Calculamos o rebaixamento if(MaxDrawdownToString(i,balance,max_drawdown)!="") AddDrawDown(i,max_drawdown); } ...
Em seguida, preenchemos as matrizes de saldos para cada símbolo individual.
... //--- Obtemos os dados dos saldos dos símbolos for(int s=1; s<m_symbols_total; s++) { //--- Saldo inicial balance=m_symbol_balance[0].m_data[0]; m_symbol_balance[s].m_data[0]=balance; //--- for(uint r=0; r<m_rows_total; r++) { //--- Se símbolos não corresponderem, então o valor anterior if(m_symbols_name[s]!=m_columns[m_symbol_index].m_rows[r]) { if(r>0) m_symbol_balance[s].m_data[r]=m_symbol_balance[s].m_data[r-1]; //--- continue; } //--- Se o resultado do trade for diferente de zero if((double)m_columns[elements_total-1].m_rows[r]!=0) { balance+=(double)m_columns[elements_total-1].m_rows[r]+(double)m_columns[elements_total-2].m_rows[r]+(double)m_columns[elements_total-3].m_rows[r]; m_symbol_balance[s].m_data[r]=balance; } //--- Caso contrário, escrevemos o valor anterior else m_symbol_balance[s].m_data[r]=m_symbol_balance[s].m_data[r-1]; } } ...
Depois disso, os dados são exibidos nos gráficos da interface gráfica. Veja alguns exemplos de diferentes provedores de sinais:
Fig. 8. Apresentação de resultados (exemplo 1).
Fig. 9. Apresentação de resultados (exemplo 2).
Fig. 10. Apresentação de resultados (exemplo 3).
Fig. 11. Apresentação de resultados (exemplo 4).
Fim do artigo
O artigo mostra uma versão moderna do aplicativo MQL para visualizar gráficos de saldo multissímbolo. Anteriormente, você tinha que usar programas de terceiros para obter esse resultado. Agora tudo pode ser implementado apenas com ajuda de MQL, sem deixar o terminal MetaTrader 5.
Abaixo, você pode baixar arquivos para seu computador, para testar e estudar detalhadamente o código apresentado no artigo. Cada versão do programa tem essa estrutura de arquivos:
Nome do arquivo | Comentário |
---|---|
MacdSampleMultiSymbols.mq5 | Expert Advisor modificado da entrega padrão - MACD Sample |
Program.mqh | Arquivo com a classe do programa |
CreateGUI.mqh | Arquivo com a implementação dos métodos da classe do programa, no arquivo Program.mqh |
Strategy.mqh | Arquivo com a classe modificada de estratégia do MACD Sample (versão multissímbolo) |
FormatString.mqh | Arquivo com funções auxiliares para formatação de linhas |