Decompondo as entradas em indicadores
Introdução
Ao olhar para a série de negócios lucrativos de um trader bem-sucedido, você tem vontade de seguir sua estratégia? Ou, talvez, observando seu histórico de negociação, você quer saber como pode se livrar dos trades mal-sucedidos? Eu acredito que muitos de vocês responderão a pelo menos uma das perguntas positivamente. Neste artigo, quero sugerir métodos de decomposição de históricos de negociação em indicadores, além de compartilhar como escolher os indicadores que ajudarão a aumentar seu desempenho.
1. Definição do problema
Em nosso artigo anterior, falei sobre um expert advisor com base no filtro de Kalman. Durante o teste, não só mostrou lucro, mas também 2 gargalos na estratégia: saída tardia e uma série de trades desfavoráveis em fase de correção.
Assim, nosso objetivo é reduzir o número de trades mal-sucedidos nessa estratégia. Para fazer isso, mantemos os valores de uma série de indicadores, no momento da abertura da posição. Em seguida, analisamos e comparamos os valores desses indicadores com resultados dos trades. O resto é escolher os indicadores que ajudarão a melhorar os desempenhos na negociação.
Primeiro, elaboremos um plano de ação.
- Determinamos o período de teste. Testamos nele e salvamos o relatório.
- Realizamos o parsing do relatório do teste e criamos uma matriz de trades (com o resultado das operações).
- Determinamos a lista de indicadores a serem utilizados e o formato de salvamento de dados. Preparamos as classes para posterior uso.
- Preparamos formulários para apresentação de resultados.
- Montamos o expert advisor analítico.
- Executamos o EA analítico no testador de estratégias e analisamos relatórios.
- Adicionamos os indicadores necessários ao EA.
- Testamos o EA atualizado e comparamos os resultados.
2. Primeiro teste do EA analisado
No artigo mencionado acima, o EA realizou 150 transações no prazo de um mês a contar do teste. Isso não é suficiente para uma análise estatística. Para melhorar a representatividade dos resultados, aumentamos o período de teste oito vezes. Sem qualquer optimização, definimos o período para a plotagem da função autorregressiva em 3 120 barras (cerca de 3 meses), e executamos o teste.
Após os resultados do teste, obtivemos um gráfico de saldo obviamente desfavorável, onde, depois de 1-2 trades lucrativos, vemos uma série de perdas. Em geral, a percentagem de transações rentáveis constituiu um pouco menos de 34%. Embora, o lucro médio exceda a perda média em 45%. Isso não é suficiente para fazer um lucro durante todo o período de teste.
O gráfico de preços mostra que, na ausência de uma tendência claramente definida (em fase de correção), o EA abre e fecha posições desfavoráveis. Nossa tarefa é reduzir esse tipo de transações e, sempre que possível, excluí-las completamente.
Em primeiro lugar, é necessário salvar o relatório de teste para posterior processamento. No entanto, há uma nuance, isto é, por motivos de segurança, em MQL5 o trabalho com arquivos é estritamente controlado. Os arquivos para executar operações por meio dos recursos da linguagem MQL5 devem estar no arquivo área restrita [sandbox]. Portanto, o relatório deve ser salvo nele. Mas, como vamos iniciar o programa no Testador de estratégias, devemos levar em consideração que cada agente trabalha em seu área restrita [sandbox]. Consequentemente, para que, durante o teste em qualquer agente, o programa possa acessar o relatório, nós o salvamos na pasta compartilhada do terminal.
Para descobrir o caminho para a pasta compartilhada do terminal de cliente, abrimos o menu "File" no MetaEditor e selecionamos "Open Common Data Folder".
Na janela aberta, vamos para a pasta "Files".
Em seguida, copiamos a linha do caminho completa para a área de transferência pressionando "Ctrl + C".
Conhecido o caminho para a área restrita, podemos salvar nosso relatório de teste. Para fazer isso, no "Strategy Tester", escolhemos a guia "Result" e, em qualquer espaço, clicamos no botão direito do mouse. No menu que aparece, selecionamos "Report" -> "HTML (Internet Explorer)".
Depois de realizar estas operações, será aberta uma janela de sistema para salvar o arquivo. Em primeiro lugar, na caixa de edição do nome do arquivo, colocamos o caminho de nossa área restrita e clicamos em "Salvar". Esta operação altera a pasta para salvar o arquivo.
Na etapa a seguir, especifique o nome sob o qual o relatório de teste será salvo, e salvamos o arquivo.
Depois de salvar o relatório no área restrita, avançamos para a seguinte etapa, isto é, a criação de uma matriz de transações para análise subsequente.
3. Criamos uma matriz de transações
3.1. Ideia geral sobre a análise sintática
Na seção anterior, salvamos o relatório de teste do EA. Agora, temos que gerar a partir dele uma matriz de transações para processamento. No navegador, vemos uma lista de trades, mas os programas MQL5 não podem baixar matrizes de dados diretamente de um arquivo HTML. Por isso, é necessário realizar a análise sintática do relatório.
Basicamente, um arquivo HTML é um texto separado por marcas que descrevem a formatação e design. Tendo aberto o relatório num editor de texto, você pode encontrar facilmente nele duas marcas "<table>", o que significa que todos os dados no relatório são divididos em 2 tabelas de dados. Informações sobre as transações se encontram na segunda tabela. No seu início, há informações sobre ordens e depois - informações sobre trades.
As linhas da tabela são marcadas pelas marcas "<tr>...</tr>". Dentro das linhas, as informações são separadas em células pelas marcas "<td>...</td>".
3.2. Classe para salvar informações sobre o trade
Determinamos o formato de apresentação dos dados do relatório. Passamos agora para o formato de armazenamento de dados em nossa matriz. Como o EA a ser analisado funciona somente num único instrumento, o fato de salvar o nome do instrumento é opcional. No entanto, precisamos dele para inicializar os indicadores. No final das contas, nossa a estrutura de registro de transações terá as seguintes posições:
- hora de abertura de posição;
- volume de abertura de posição;
- direção do trade;
- volume de fechamento da posição;
- montante da comissão;
- valor do swap;
- montante do lucro.
Determinamos os principais aspectos desta e desta etapa do trabalho. Comecemos a escrever o código. Primeiro, criamos a classe de transação CDeal.
class CDeal : public CObject { private: datetime OpenTime; // Time of open position double OpenedVolume; // Volume of opened position ENUM_POSITION_TYPE Direct; // Direct of opened position double ClosedVolume; // Closed volume double Comission; // Comission to position double Swap; // Swap of position double Profit; // Profit of position public: CDeal(); ~CDeal(); };
Inicializaremos a classe ao registrar uma nova transação aberta, quando conhecidos a hora de abertura de posição, o volume e direção do trade. Portanto, transmitiremos aos parâmetros da função de inicialização seus valores e comissões (se disponíveis). Na inicialização, zeraremos os outros parâmetros. Como resultado, a função de inicialização da classe será a seguinte:
CDeal::CDeal(ENUM_POSITION_TYPE type,datetime time,double volume,double comission=0.0) : ClosedVolume(0), Swap(0), Profit(0) { OpenTime = time; OpenedVolume = volume; Direct = type; Comission = comission; }
Em trabalhos futuros, teremos que verificar o estado das transações já salvas. Para fazer isso, criamos a função IsClosed, ela verificará se uma transação já está fechada na base de dados. Nela, serão comparados os volumes de abertura com os de fechamento da transação. O fato de eles serem iguais significa que a transação está fechada, e a função deve retornar true. Se a transação não estiver fechada, a função retornará false e o volume restante no mercado.
bool CDeal::IsClosed(double &opened_volume) { opened_volume=OpenedVolume-ClosedVolume; return (opened_volume<=0); }
No caso de nós precisarmos verificar apenas o estado de uma transação e não haver necessidade de saber o volume não fechado, criamos mais uma função com o mesmo nome.
bool CDeal::IsClosed(void) { double opened_volume; return IsClosed(opened_volume); }
Para fecha corretamente a transação, precisamos saber seu tipo. Criamos a função GetType. Ela retornará o valor private da variável Direct. A função é bastante curta, portanto, pode ser reescrita no corpo da classe.
ENUM_POSITION_TYPE Type(void) { return Direct; }
Verificado o estado, as transações não fechadas devem ser fechadas. Para fazer isso, criamos a função Close. Para ela serão transmitidos os parâmetros: volume de fechamento, lucro da transação, comissão e o swap acumulado. Se o volume transmitido for superior ao volume não fechado da transação, a função retornará false. Em outros casos, os parâmetros passados serão armazenados nas variáveis de classe correspondentes, e a função retornará true.
bool CDeal::Close(double volume,double profit,double comission=0.0,double swap=0.0) { if((OpenedVolume-ClosedVolume)<volume) return false; ClosedVolume += volume; Profit += profit; Comission += comission; Swap += swap; return true; }
Mais tarde, na análise das transações, precisaremos de uma função que retorna a pedido o lucro do trade. Vamos chamar essa função GetProfit.
double CDeal::GetProfit(void) { return (Comission+Swap+Profit); }
Além disso, para a recepção atempada de dados sobre o estado dos indicadores, precisamos saber o tempo da transação. Para isso, vamos criar a função GetTime.
datetime GetTime(void) { return OpenTime; }
3.3. Classe de análise sintática de relatório
Depois de criar uma classe para armazenar informações sobre cada transação, prosseguimos diretamente com a análise sintática do relatório. Para fazer isso, criamos a classe CParsing. Na classe, declaramos:
- objeto da classe CArrayObj - para armazenar a matriz de transações;
- objeto da classe CFileTxt - para trabalhar com o arquivo do relatório;
- variável do tipo string - para armazenar o nome do instrumento.
Além das funções de inicialização e desinitialização, a classe terá mais duas funções:
- ReadFile - diretamente para análise sintática;
- GetSymbol - para retorno do nome do instrumento, mediante solicitação.
class CParsing { private: CArrayObj *car_Deals; //Array of deals CFileTxt *c_File; //File to parsing string s_Symbol; //Symbol of deals public: CParsing(CArrayObj *&array); ~CParsing(); bool ReadFile(string file_name); string GetSymbol(void) { return s_Symbol; } };
O principal objetivo das funções desta classe é a criação de uma série de transações para posterior processamento. Assim, a matriz criada deve estar disponível para funcionar no programa principal. Para esta finalidade, declaramos o objeto da classe CArrayObj para armazenar a matriz de transações no programa principal, e, durante a inicialização, para classe transferimos sua referência. Como resultado, a função de inicialização será a seguinte:
CParsing::CParsing(CArrayObj *&array) : s_Symbol(NULL) { if(CheckPointer(array)==POINTER_INVALID) { array=new CArrayObj(); } car_Deals=array; }
Na função de desinitialização, escrevemos a remoção do objeto da classe CFileTxt. O encerramento do arquivo é especificado na função de desinitialização da classe pai CFile; não fornecemos isso aqui.
CParsing::~CParsing() { if(CheckPointer(c_File)!=POINTER_INVALID) delete c_File; }
Avançamos diretamente para a análise sintática. Ao chamar a função de análise sintática ReadFile nos parâmetros, especificamos o nome de arquivo de relatório. A primeira coisa que fazemos na função é verificar se o parâmetro passado não está vazio. Além disso, verificamos a disponibilidade de uma matriz para armazenar informações sobre as transações. No caso de pelo menos uma condição não ser cumprida, encerramos a execução da função e retornamos a false.
bool CParsing::ReadFile(string file_name) { //--- if(file_name==NULL || file_name=="" || CheckPointer(car_Deals)==POINTER_INVALID) return false;
Em seguida, inicializamos o objeto da classe CFileTxt e tentamos abrir o arquivo transferido para o parâmetro da função. Se ocorrer um erro, saímos da função com o resultado false.
if(CheckPointer(c_File)==POINTER_INVALID) { c_File=new CFileTxt(); if(CheckPointer(c_File)==POINTER_INVALID) return false; } //--- if(c_File.Open(file_name,FILE_READ|FILE_COMMON)<=0) return false;
Depois de abrir o arquivo, conferimos todo seu conteúdo na variável do tipo string. Se o arquivo estiver vazio, saímos da função com resultado false.
string html_report=NULL; while(!c_File.IsEnding()) html_report+=c_File.ReadString(); c_File.Close(); if(html_report==NULL || html_report=="") return false;
Na próxima etapa, procuramos um símbolo que não ocorra no texto do relatório e possa ser utilizado como separador. Na ausência de tal símbolo, saímos da função com o resultado false.
string delimiter = NULL; ushort separate = 0; for(uchar tr=1;tr<255;tr++) { string temp = CharToString(tr); if(StringFind(html_report,temp,0)>0) continue; delimiter = temp; separate = tr; break; } if(delimiter==NULL) return false;
Como mencionado acima, na estrutura de arquivo HTML, as tabelas são fechadas pela marca "</table>". Substituímos essa marca pelo separador e dividimos todo o relatório em linhas de acordo com ele. Assim, dividimos a tabela desejada em uma linha separada.
if(StringReplace(html_report,"</table>",delimiter)<=0) return false; //--- s_Symbol=NULL; car_Deals.Clear(); //--- string html_tables[]; int size=StringSplit(html_report,separate,html_tables); if(size<=1) return false;
Repetindo este procedimento com a marca "</tr>", dividimos a tabela em linhas.
if(StringReplace(html_tables[size-2],"</tr>",delimiter)<=0) return false; size=StringSplit(html_tables[size-2],separate,html_tables); if(size<=1) return false;
Agora, processamos a matriz de linhas obtida num ciclo. Primeiro, ignoramos todas as linhas que contêm informações sobre ordens. Sendo assim, vamos nós orientar pela linha com o texto "Deals", que divide as ordens e transações, norelatório.
bool found_start=false; double opened_volume=0; for(int i=0;i<size;i++) { //--- if(!found_start) { if(StringFind(html_tables[i],"Deals",0)>=0) found_start=true; continue; }
Após isso, dividimos cada linha em células e convertemos as informações no formato apropriado.
string columns[]; int temp=StringFind(html_tables[i],"<td>",0); if(temp<0) continue; if(temp>0) html_tables[i]=StringSubstr(html_tables[i],temp); StringReplace(html_tables[i],"<td>",""); StringReplace(html_tables[i],"</td>",delimiter); temp=StringSplit(html_tables[i],separate,columns); if(temp<13) continue; //--- ENUM_POSITION_TYPE e_direction = (ENUM_POSITION_TYPE)(columns[3]=="buy" ? POSITION_TYPE_BUY : columns[3]=="sell" ? POSITION_TYPE_SELL : -1); if(e_direction==-1) continue; //--- datetime dt_time = StringToTime(columns[0]); StringReplace(columns[5]," ",""); double d_volume = StringToDouble(columns[5]); StringReplace(columns[8]," ",""); double d_comission = StringToDouble(columns[8]); StringReplace(columns[9]," ",""); double d_swap = StringToDouble(columns[9]); StringReplace(columns[10]," ",""); double d_profit = StringToDouble(columns[10]); if(s_Symbol==NULL || s_Symbol=="") { s_Symbol=columns[2]; StringTrimLeft(s_Symbol); StringTrimRight(s_Symbol); }
Na próxima etapa, verificamos se a transação é uma operação de fechamento de posição. Se o resultado for positivo, fechamos as posições em nossa base de acordo com o método FIFO.
if(opened_volume>0 && StringFind(columns[4],"out",0)>=0) { int total=car_Deals.Total(); double total_volume=MathMin(opened_volume,d_volume); for(int d=0;(d<total && e_direction!=(-1) && total_volume>0);d++) { CDeal *deal=car_Deals.At(d); if(CheckPointer(deal)==POINTER_INVALID) continue; //--- if(deal.Type()==e_direction) continue; //--- double deal_unclosed=0; if(deal.IsClosed(deal_unclosed)) continue; double close_volume = MathMin(deal_unclosed,total_volume); double close_comission = d_comission/d_volume*close_volume; double close_swap = d_swap/total_volume*close_volume; double close_profit = d_profit/total_volume*close_volume; if(deal.Close(close_volume,close_profit,close_comission,close_swap)) { opened_volume -= close_volume; d_volume -= close_volume; total_volume -= close_volume; d_comission -= close_comission; d_swap -= close_swap; d_profit -= close_profit; } } }
Em seguida, verificamos se a abertura de posição foi feita. Se necessário, criamos uma nova transação em nosso banco de dados.
if(d_volume>0 && StringFind(columns[4],"in",0)>=0) { CDeal *deal = new CDeal(e_direction,dt_time,d_volume,d_comission); if(CheckPointer(deal)==POINTER_INVALID) return false; if(!car_Deals.Add(deal)) return false; opened_volume += d_volume; } }
Se pelo menos uma transação for salva, no final a função retornará true, caso contrário - "false".
return (car_Deals.Total()>0); }
Avançamos para a próxima etapa do trabalho.
4. Preparação de classes para trabalhar com indicadores
Como dissemos anteriormente, um dos objetivos visa eliminar as transações desfavoráveis caso não exista uma tendência forte. Regularmente, levantam-se dúvidas sobre a definição da tendência, incluindo aqui, neste site (por exemplo, nos artigos [3] e [4]). Não pretendo descobrir métodos extraordinários para determinar a tendência. Eu só quero apresentar uma técnica de comparação de transações e indicadores para posterior análise e otimização consciente de sistemas de negociação. Por isso, consideramos aqui os indicadores mais difundidos que já estão disponíveis no pacote de distribuição do terminal.
4.1. Classe para inclusão do indicador ATR
Primeiro, consideraremos o indicador de tipo oscilador Average True Range. Como se sabe, durante as tendências, a volatilidade do mercado aumenta. O crescimento do valor do oscilador é um sinal disso. Quais são os valores que precisamos salvar? Como o EA analisado coloca ordens apenas na abertura da vela, sugiro manter tanto o valor do indicador na última vela fechada quanto a relação entre este valor e o anterior. O primeiro valor mostra a volatilidade atual, enquanto o segundo - a dinâmica da variação na volatilidade.
O indicador estudado tem só um buffer e é considerado típico em sua classe. Portanto, faz sentido para nós realizar uma única classe para trabalhar com esses indicadores.
A maneira de salvar os valores dos indicadores será semelhante à das transações: primeiro, criamos uma classe para armazenar os valores de indicadores para um trade, em seguida, realizamos uma classe de nível superior para trabalho imediato com o indicador de acordo com solicitações externas e armazenamento de dados na matriz.
Vamos chamar a primeira classe como CValue. Ela conterá 3 variáveis private para armazenar: as informações sobre o valor do indicador (Value), a relação dos dois últimos valores do indicador (Dinamic) e o número de bilhete da ordem para a qual foram salvos os valores (Deal_Ticket). Nós precisaremos do número do bilhete para comparação subsequente dos valores dos indicadores com as ordens durante a análise. Todos os valores necessários para armazenar o valor na instância da classe serão transmitidos durante sua inicialização. Para extrair as informações desejadas, criamos umas funções GetTicket, GetValue e GetDinamic que retornarão os valores das variáveis correspondentes. Além disso, criamos uma função GetValues que retornará simultaneamente o valor do indicador e sua dinâmica.
class CValue : public CObject { private: double Value; //Indicator's value double Dinamic; //Dinamics value of indicator long Deal_Ticket; //Ticket of deal public: CValue(double value, double dinamic, long ticket); ~CValue(void); //--- long GetTicket(void) { return Deal_Ticket; } double GetValue(void) { return Value; } double GetDinamic(void) { return Dinamic; } void GetValues(double &value, double &dinamic); };
Em seguida, criamos uma classe de nível superior para armazenamento da matriz de dados COneBufferArray. No bloco private, ela conterá a matriz de dados armazenados e o identificador do indicador. Lembre que decidimos criar uma classe genérica para trabalhar com todos os indicadores de um buffer. Mas a chamada de diferentes indicadores acarreta o uso de um conjunto diferente de parâmetros. Portanto, na minha opinião, a opção mais fácil é inicializar o indicador no programa principal, e só então inicializar a classe e transferir para ela o identificador do indicador desejado. Para posterior identificação do indicador no relatório, introduzimos a variável s_Name.
class COneBufferArray : CObject { private: CArrayObj *IndicatorValues; //Array of indicator's values int i_handle; //Handle of indicator string s_Name; string GetIndicatorName(int handle); public: COneBufferArray(int handle); ~COneBufferArray(); //--- bool SaveNewValues(long ticket); //--- double GetValue(long ticket); double GetDinamic(long ticket); bool GetValues(long ticket, double &value, double &dinamic); int GetIndyHandle(void) { return i_handle; } string GetName(void) { return (s_Name!= NULL ? s_Name : "..."); } };
Para salvar os dados a partir de uma solicitação externa, criamos uma função SaveNewValues que conterá apenas um parâmetro, isto é, o bilhete da ordem. No início da função, verificamos o estado das matrizes para armazenamento de dados e do identificador do indicador. Em caso de um erro, a função retornará false.
bool COneBufferArray::SaveNewValues(long ticket) { if(CheckPointer(IndicatorValues)==POINTER_INVALID) return false; if(i_handle==INVALID_HANDLE) return false;
Depois disso, obtemos os dados do indicador. Se não for possível carregar os valores do indicador, a função retorna false.
double ind_buffer[]; if(CopyBuffer(i_handle,0,1,2,ind_buffer)<2) return false;
Na seguinte etapa, criamos a instância da classe CValue e transferimos para ele os valores requeridos. Em caso de erro, a função retorna false.
CValue *object=new CValue(ind_buffer[1], (ind_buffer[0]!=0 ? ind_buffer[1]/ind_buffer[0] : 1), ticket); if(CheckPointer(object)==POINTER_INVALID) return false;
Se a classe ainda não souber o nome do indicador, devemos obtê-lo do gráfico chamando a função GetIndicatorName (o código da função é mostrado o anexo).
if(s_Name==NULL) s_Name=GetIndicatorName(i_handle);
Finalmente, adicionamos à matriz a instância da classe de dados recém-criada e, tendo retornado o resultado da operação, saímos da função.
return IndicatorValues.Add(object);
}
Para retorno de dados da matriz a pedido, criamos umas funções GetValue, GetDinamic e GetValues que retornarão os valores requeridos pelo número do bilhete da ordem.
O código completo das classes pode ser encontrado no apêndice.
Eu apliquei esta classe para coletar dados pelos indicadores CCI, Volumes, Force, Chaikin oscillator e desvio padrão.
4.2. Classe para inclusão do indicador MACD
Vamos adicionar à nossa coleção um indicador padrão mais, nomeadamente, o MACD. Como é bem conhecido, ele é utilizado para determinar a força e direção da tendência.
Em contraste com os indicadores considerados anteriormente, MACD tem dois buffers de indicador (Main e Signal). Por isso, vamos salvar as informações de duas linhas. Usando o algoritmo mostrado para os indicadores acima, o código da classe para armazenamento de dados toma a seguinte forma:
class CMACDValue : public CObject { private: double Main_Value; //Main line value double Main_Dinamic; //Dinamics value of main lime double Signal_Value; //Signal line value double Signal_Dinamic; //Dinamics value of signal lime long Deal_Ticket; //Ticket of deal public: CMACDValue(double main_value, double main_dinamic, double signal_value, double signal_dinamic, long ticket); ~CMACDValue(void); //--- long GetTicket(void) { return Deal_Ticket; } double GetMainValue(void) { return Main_Value; } double GetMainDinamic(void) { return Main_Dinamic; } double GetSignalValue(void) { return Signal_Value; } double GetSignalDinamic(void) { return Signal_Dinamic; } void GetValues(double &main_value, double &main_dinamic, double &signal_value, double &signal_dinamic); };
Alterações correspondentes tiveram lugar na classe para trabalho com a matriz de dados. Ao contrário da classe universal, conforme descrito na seção 4.1, esta classe funcionará com um determinado indicador, portanto, na inicialização da classe, serão transferidos para ele os parâmetros necessários para sua inicialização, em vez de passar o identificador do indicador. A inicialização do indicador será realizada diretamente na classe.
class CMACD { private: CArrayObj *IndicatorValues; //Array of indicator's values int i_handle; //Handle of indicator public: CMACD(string symbol, ENUM_TIMEFRAMES timeframe, uint fast_ema, uint slow_ema, uint signal, ENUM_APPLIED_PRICE applied_price); ~CMACD(); //--- bool SaveNewValues(long ticket); //--- double GetMainValue(long ticket); double GetMainDinamic(long ticket); double GetSignalValue(long ticket); double GetSignalDinamic(long ticket); bool GetValues(long ticket, double &main_value, double &main_dinamic, double &signal_value, double &signal_dinamic); };
Toda a lógica das funções permanece a mesma, as mudanças afetaram apenas o número de buffers de indicador e variáveis armazenadas.
bool CMACD::SaveNewValues(long ticket) { if(CheckPointer(IndicatorValues)==POINTER_INVALID) return false; if(i_handle==INVALID_HANDLE) return false; double main[], signal[]; if(!CopyBuffer(i_handle,0,1,2,main)<2 || !CopyBuffer(i_handle,1,1,2,signal)<2) return false; CMACDValue *object=new CMACDValue(main[1], (main[0]!=0 ? main[1]/main[0] : 1), signal[1], (signal[0]!=0 ? signal[1]/signal[0] : 1), ticket); if(CheckPointer(object)==POINTER_INVALID) return false; return IndicatorValues.Add(object); }
Tal lógica de escala se aplica a qualquer quantidade de buffers de indicador. Se você deseja salvar apenas os buffers de indicador selecionados, basta descrever isso na função SaveNewValues da classe correspondente. Porém, não recomendo fazer isso nesta fase, porque ainda não sabemos se existe uma relação entre as transações rentáveis e os valores de buffers de indicadores específicos, e se houver, não conhecemos qual é o seu grau.
Para consolidar o material, por assim dizer, vou dar outro exemplo de armazenamento de dados de indicador con 3 buffers de dados.
4.3. Classe para inclusão do indicador ADX
Indicador ADX é amplamente utilizado para determinar a força e a direção da tendência. Ele se ajusta bem ao nosso problema, e vale a pena adicioná-lo ao nosso "mealheiro."
Existem 3 buffers de indicador neste indicador e, de acordo com o método de escala sugerido acima, aumentamos o número de variáveis salvas. Assim, a classe de armazenamento de dados terá a seguinte aparência:
class CADXValue : public CObject { private: double ADX_Value; //ADX value double ADX_Dinamic; //Dinamics value of ADX double PDI_Value; //+DI value double PDI_Dinamic; //Dinamics value of +DI double NDI_Value; //-DIvalue double NDI_Dinamic; //Dinamics value of -DI long Deal_Ticket; //Ticket of deal public: CADXValue(double adx_value, double adx_dinamic, double pdi_value, double pdi_dinamic, double ndi_value, double ndi_dinamic, long ticket); ~CADXValue(void); //--- long GetTicket(void) { return Deal_Ticket; } double GetADXValue(void) { return ADX_Value; } double GetADXDinamic(void) { return ADX_Dinamic; } double GetPDIValue(void) { return PDI_Value; } double GetPDIDinamic(void) { return PDI_Dinamic; } double GetNDIValue(void) { return NDI_Value; } double GetNDIDinamic(void) { return NDI_Dinamic; } void GetValues(double &adx_value, double &adx_dinamic, double &pdi_value, double &pdi_dinamic, double &ndi_value, double &ndi_dinamic); };
Um aumento nos dados a serem armazenados implicará mudanças na classe de trabalho com a matriz.
class CADX { private: CArrayObj *IndicatorValues; //Array of indicator's values int i_handle; //Handle of indicator public: CADX(string symbol, ENUM_TIMEFRAMES timeframe, uint period); ~CADX(); //--- bool SaveNewValues(long ticket); //--- double GetADXValue(long ticket); double GetADXDinamic(long ticket); double GetPDIValue(long ticket); double GetPDIDinamic(long ticket); double GetNDIValue(long ticket); double GetNDIDinamic(long ticket); bool GetValues(long ticket,double &adx_value,double &adx_dinamic,double &pdi_value,double &pdi_dinamic,double &ndi_value,double &ndi_dinamic); }; bool CADX::SaveNewValues(long ticket) { if(CheckPointer(IndicatorValues)==POINTER_INVALID) return false; if(i_handle==INVALID_HANDLE) return false; double adx[], pdi[], ndi[]; if(!CopyBuffer(i_handle,0,1,2,adx)<2 || !CopyBuffer(i_handle,1,1,2,pdi)<2 || !CopyBuffer(i_handle,1,1,2,ndi)<2) return false; CADXValue *object=new CADXValue(adx[1], (adx[0]!=0 ? adx[1]/adx[0] : 1), pdi[1], (pdi[0]!=0 ? pdi[1]/pdi[0] : 1), ndi[1], (ndi[0]!=0 ? ndi[1]/ndi[0] : 1), ticket); if(CheckPointer(object)==POINTER_INVALID) return false; return IndicatorValues.Add(object); }
Agora, acho que já todos entenderam o princípio de construção de classes para trabalhar com indicadores. Portanto, não vamos escrever em detalhes sobre o código para os seguintes indicadores, a fim de poupar espaço. Do mesmo modo, para a análise, eu adicionei os indicadores BW IFM e Alligator ao "mealheiro". Quem quiser pode ler o código completo das classes no apêndice.
5. Preparamos os formulários dos relatórios para exibir os resultados
Após obter informações dos indicadores de nosso interesse no momento da transação, é hora de pensar sobre a análise dos resultados. O mais ilustrativo, na minha opinião, será plotar os gráficos de dependência entre o lucro das transações e os respectivos valores dos indicadores. Sugiro construir os gráficos de acordo com a técnica proposta por Victor no artigo [2].
Só quero mencionar que como eu estou otimizando uma estratégia de negociação, vou procurar a dependência entre o lucro e os valores dos indicadores. Se o leitor estiver tentando copiar certo tipo de negociação, ele precisará de olhar para a relação entre o número de transações e os valores dos indicadores.
Primeiro, criamos as classes que prepararão as informações para cada indicador.
5.1. Classe universal de indicadores de um buffer
Primeiro, criamos a classe para trabalhar com indicadores de um buffer. Que tipo de informação podemos analisar? Vamos lembrar que temos salvo o valor do buffer de indicador e suas a dinâmica de mudanças. Portanto, podemos analisar:
- dependência entre o lucro das operações realizadas e os valores do indicador na posição de abertura,
- impacto da tendência da linha do indicador sobre o lucro,
- bem como o efeito combinado do valor do indicador e da sua dinâmica sobre o resultado das operações realizadas.
Para desenho de gráficos, criamos a classe CStaticOneBuffer. Esta classe conterá a referência para a matriz de dados armazenados DataArray, matriz de valores Value com etapa definida d_Step, bem como dois matrizes de lucro total separadas para posições longas e curtas. Note que matrizes para calcular o lucro total serão bidimensionais. O tamanho da primeira dimensão corresponderá ao tamanho da matriz Value. A segunda dimensão conterá três elementos: o primeiro será para a dinâmica descendente do indicador, a segunda será para o movimento horizontal do indicador e a terceira, para o movimento ascendente.
Na inicialização da classe, para os parâmetros vamos transferir, por um lado, a referência para a matriz de dados e, por outro, o tamanho da etapa para os valores do indicador.
class CStaticOneBuffer : CObject { private: COneBufferArray *DataArray; double d_Step; //Step in values Array double Value[]; //Array of values double Long_Profit[][3]; //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2 double Short_Profit[][3]; //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2 bool AdValues(double value, double dinamic, double profit, ENUM_POSITION_TYPE type); int GetIndex(double value); bool Sort(void); public: CStaticOneBuffer(COneBufferArray *data, double step); ~CStaticOneBuffer(); bool Ad(long ticket, double profit, ENUM_POSITION_TYPE type); string HTML_header(void); string HTML_body(void); };
Na função de inicialização, salvamos os valores enviados e zeramos as matrizes utilizadas.
CStaticOneBuffer::CStaticOneBuffer(COneBufferArray *data,double step) { DataArray = data; d_Step = step; ArrayFree(Value); ArrayFree(Long_Profit); ArrayFree(Short_Profit); }
Para recolher informações estatísticas, criamos uma função Ad para a qual transmitiremos informações sobre a transação. Dentro da função estarão os parâmetros do indicador correspondentes, e os dados serão guardados nos elementos da matriz requeridos.
bool CStaticOneBuffer::Ad(long ticket,double profit,ENUM_POSITION_TYPE type) { if(CheckPointer(DataArray)==POINTER_INVALID) return false; double value, dinamic; if(!DataArray.GetValues(ticket,value,dinamic)) return false; value = NormalizeDouble(value/d_Step,0)*d_Step; return AdValues(value,dinamic,profit,type); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CStaticOneBuffer::AdValues(double value,double dinamic,double profit,ENUM_POSITION_TYPE type) { int index=GetIndex(value); if(index<0) return false; switch(type) { case POSITION_TYPE_BUY: if(dinamic<1) Long_Profit[index,0]+=profit; else if(dinamic==1) Long_Profit[index,1]+=profit; else Long_Profit[index,2]+=profit; break; case POSITION_TYPE_SELL: if(dinamic<1) Short_Profit[index,0]+=profit; else if(dinamic==1) Short_Profit[index,1]+=profit; else Short_Profit[index,2]+=profit; break; } return true; }
Para visualização de gráficos, criamos umas funções HTML_header e HTML_body que gerarão trechos do código do cabeçalho e do corpo da página HTML. Princípios de construção de código de páginas HTML estão detalhados no artigo [ 2 ], não vamos nos concentrar nisto. O código completo da função é mostrado no anexo.
5.2. Classe para exibição de dados do indicador Bill Williams MFI
A seguir, consideraremos o indicador Bill Williams MFI. Seu método de exibição no gráfico lembra os indicadores de um buffer, mas existe uma diferença, isto é, BW IMF tem um buffer de paleta de cores que também tem um valor. Ao mesmo tempo, ao contrário de indicadores de dois buffers, não vamos estar interessado na dinâmica da mudança no buffer de cor. Portanto, aos gráficos acima sugeridos de indicadores de um buffer são adicionados gráficos de dependência entre o lucro e a cor do indicador, bem como gráficos de impacto complexo de valores e dinâmicas do indicador sujeito à cor do indicador atual.
Para a coleta de dados estatísticos e criação de gráficos analíticos, criamos a classe CStaticBWMFI. Estrutura da classe é semelhante à considerada acima. As mudanças tocaram nas matrizes de contagem de lucro, elas agora têm três dimensões. A terceira dimensão recebeu os 4 elementos de acordo com o número de cores utilizadas.
class CStaticBWMFI : CObject { private: CBWMFI *DataArray; double d_Step; //Step in values Array double Value[]; //Array of values double Long_Profit[][3][4]; //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2 double Short_Profit[][3][4]; //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2 bool AdValues(double value, double _color, double dinamic, double profit, ENUM_POSITION_TYPE type); int GetIndex(double value); bool Sort(void); public: CStaticBWMFI(CBWMFI *data, double step); ~CStaticBWMFI(); bool Ad(long ticket, double profit, ENUM_POSITION_TYPE type); string HTML_header(void); string HTML_body(void); };
O código de classe completo pode ser encontrado no anexo.
5.3. Classe para exibição dos dados do indicador MACD
Está na hora de considerarmos o indicador MACD. Como é bem conhecido, ele tem dois buffers: o histograma e a linha de sinal. De acordo com as regras de interpretação deste indicador, são importantes: o valor e direção do histograma, bem como a posição da linha de sinal (acima ou abaixo do histograma). Para a análise abrangente, criaremos uma série de gráficos.
- Dependência da rentabilidade das transações sobre os valores do histograma e sua direção (separada o complexamente).
- Dependência da rentabilidade de transações sobre os valores valores de linha de sinal e sua direção.
- Dependência entre o lucro e a posição da linha de sinal em relação ao histograma.
- Dependência do lucro sobre o efeito conjunto dos valores do histograma, direção e posição da linha de sinal em relação ao histograma.
class CStaticMACD : CObject { private: CMACD *DataArray; double d_Step; //Step in values Array double Value[]; //Array of values double SignalValue[]; //Array of values double Long_Profit[][3][3]; //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2 double Short_Profit[][3][3]; //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2 double Signal_Long_Profit[][3]; //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2 double Signal_Short_Profit[][3]; //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2 bool AdValues(double main_value, double main_dinamic, double signal_value, double signal_dinamic, double profit, ENUM_POSITION_TYPE type); int GetIndex(double value); int GetSignalIndex(double value); bool Sort(void); public: CStaticMACD(CMACD *data, double step); ~CStaticMACD(); bool Ad(long ticket, double profit, ENUM_POSITION_TYPE type); string HTML_header(void); string HTML_body(void); };
Como você pode ver, a estrutura da classe, o nome e a finalidade das funções permanecem as mesmas. Só o conteúdo das funções foi alterado. Ele pode ser encontrado em anexo.
5.4. Classe para exibição dos dados do indicador ADX
Agora, consideraremos a classe CStaticADX. Ela coletará estatísticas sobre os valores do indicador ADX. Regras de interpretação dos sinais do indicador: a linha +DI mostra a força do movimento positivo, -DI indica a força do movimento negativo, enquanto ADX apresenta a forma média do movimento. Com base nestas regras, serão plotados os gráficos de dependências:
- dependência do lucro sobre valor da +DI, sua direção e posição em relação ao ADX;
- dependência do lucro sobre valor da -DI, sua direção e posição em relação ao ADX.
Criada a classe para coleta de estatísticas, decidi recolher mais alguns dados. Como resultado, tive de armazenar informações sobre:
- valor do indicador;
- direção das linhas;
- posição em relação à linha de movimento oposto;
- direção da linha de movimento oposto;
- posição em relação à linha do ADX;
- direção da linha do ADX.
class CProfitData { public: double Value; double LongProfit[3]/*UppositePosition*/[3]/*Upposite Direct*/[3]/*ADX position*/[3]/*ADX direct*/; double ShortProfit[3]/*UppositePosition*/[3]/*Upposite Direct*/[3]/*ADX position*/[3]/*ADX direct*/; CProfitData(void) { ArrayInitialize(LongProfit,0); ArrayInitialize(ShortProfit,0); } ~CProfitData(void) {}; }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CStaticADX : CObject { private: CADX *DataArray; double d_Step; //Step in values Array CProfitData *PDI[][3]; //Array of values +DI CProfitData *NDI[][3]; //Array of values -DI bool AdValues(double adx_value, double adx_dinamic, double pdi_value, double pdi_dinamic, double ndi_value, double ndi_dinamic, double profit, ENUM_POSITION_TYPE type); int GetPDIIndex(double value); int GetNDIIndex(double value); bool Sort(void); public: CStaticADX(CADX *data, double step); ~CStaticADX(); bool Ad(long ticket, double profit, ENUM_POSITION_TYPE type); string HTML_header(void); string HTML_body(void); };
Em outros aspectos, foram preservados abordagens e princípios de construção a partir de classes anteriores. O código de classe completo pode ser encontrado no anexo.
5.5. Classe para exibição dos dados do indicador Alligator
No final deste bloco, vamos criar uma classe para a coleta de estatísticas do indicador Alligator. Sinais deste indicador são baseados em três médias móveis de diferentes períodos. Por isso, ao interpretar os sinais do indicador, certos valores de linhas não têm nenhuma importância para nós. O que é muito mais importante é a direção e posição das linhas.
Para indicar com exatidão os sinais do indicador, introduzimos a definição da tendência de acordo com a posição das linhas. Se a linha LIPS estiver acima da TEETH, e, ao mesmo tempo, a TEETH se encontrar acima da JAW, teremos uma tendência BUY. Se a LIPS estiver abaixo da TEETH, e, ao mesmo tempo, a TEETH se encontrar abaixo da JAW, estaremos perante uma tendência SELL. Em caso de ausência de uma ordem estrita de linhas, consideramos a tendência como indefinida ou FLAT.
Respectivamente, gráficos de dependência serão construídos a partir de sinais de direção de tendência e dinâmica das linhas do indicador.
Após os dados de entrada especificados acima, criamos a classe CStaticAlligator. Princípios de construção da classe são herdados das classes anteriores.
class CStaticAlligator : CObject { private: CAlligator *DataArray; double Long_Profit[3]/*Signal*/[3]/*JAW direct*/[3]/*TEETH direct*/[3]/*LIPS direct*/; //Array of long deals profit double Short_Profit[3]/*Signal*/[3]/*JAW direct*/[3]/*TEETH direct*/[3]/*LIPS direct*/; //Array of short feals profit bool AdValues(double jaw_value, double jaw_dinamic, double teeth_value, double teeth_dinamic, double lips_value, double lips_dinamic, double profit, ENUM_POSITION_TYPE type); public: CStaticAlligator(CAlligator *data); ~CStaticAlligator(); bool Ad(long ticket, double profit, ENUM_POSITION_TYPE type); string HTML_header(void); string HTML_body(void); };
O código completo da classe pode ser encontrado no anexo.
6 Construímos um EA para a coleta e análise de informações
Agora, quando todo o trabalho preparatório está concluído, criamos um EA que será iniciado imediatamente no testador de estratégias para coleta de informações e saída de dados analíticos. Primeiro de tudo, nos parâmetros de entrada do EA, especificamos o nome do arquivo de relatório de teste para análise, o timeframe utilizado e todos os parâmetros necessários dos indicadores utilizados.
input string FileName = "Kalman_test.html" ; input ENUM_TIMEFRAMES Timefarame = PERIOD_CURRENT ; input string s1 = "ADX" ; //--- input uint ADX_Period = 14 ; input string s2 = "Alligator" ; //--- input uint JAW_Period = 13 ; input uint JAW_Shift = 8 ; input uint TEETH_Period = 8 ; input uint TEETH_Shift = 5 ; input uint LIPS_Period = 5 ; input uint LIPS_Shift = 3 ; input ENUM_MA_METHOD Alligator_Method = MODE_SMMA ; input ENUM_APPLIED_PRICE Alligator_Price = PRICE_MEDIAN ; input string s3 = "ATR" ; //--- input uint ATR_Period = 14 ; input string s4 = "BW MFI" ; //--- input ENUM_APPLIED_VOLUME BWMFI_Volume = VOLUME_TICK ; input string s5 = "CCI" ; //--- input uint CCI_Period = 14 ; input ENUM_APPLIED_PRICE CCI_Price = PRICE_TYPICAL ; input string s6 = "Chaikin" ; //--- input uint Ch_Fast_Period = 3 ; input uint Ch_Slow_Period = 14 ; input ENUM_MA_METHOD Ch_Method = MODE_EMA ; input ENUM_APPLIED_VOLUME Ch_Volume = VOLUME_TICK ; input string s7 = "Force Index" ; //--- input uint Force_Period = 14 ; input ENUM_MA_METHOD Force_Method = MODE_SMA ; input ENUM_APPLIED_VOLUME Force_Volume = VOLUME_TICK ; input string s8 = "MACD" ; //--- input uint MACD_Fast = 12 ; input uint MACD_Slow = 26 ; input uint MACD_Signal = 9 ; input ENUM_APPLIED_PRICE MACD_Price = PRICE_CLOSE ; input string s9 = "Standart Deviation" ; //--- input uint StdDev_Period = 14 ; input uint StdDev_Shift = 0 ; input ENUM_MA_METHOD StdDev_Method = MODE_SMA ; input ENUM_APPLIED_PRICE StdDev_Price = PRICE_CLOSE ; input string s10 = "Volumes" ; //--- input ENUM_APPLIED_VOLUME Applied_Volume = VOLUME_TICK ;
Logo depois, declaramos as instâncias de todas as classes descritas acima.
CArrayObj *Deals;
CADX *ADX;
CAlligator *Alligator;
COneBufferArray *ATR;
CBWMFI *BWMFI;
COneBufferArray *CCI;
COneBufferArray *Chaikin;
COneBufferArray *Force;
CMACD *MACD;
COneBufferArray *StdDev;
COneBufferArray *Volume;
CStaticOneBuffer *IndicatorsStatic[];
CStaticBWMFI *BWMFI_Stat;
CStaticMACD *MACD_Stat;
CStaticADX *ADX_Stat;
CStaticAlligator *Alligator_Stat;
6.1. Função de inicialização do EA
Como nosso EA é projetado para analisar dados no testador de estratégias, primeiro, verificamos o ambiente em que ele é executado. Se a inicialização ocorrer fora do testador, devemos interromper a inicialização.
int OnInit() { //--- if(!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION)) return INIT_FAILED;
Em seguida, realizamos a análise sintética de dados a partir do arquivo de relatório de teste. Após a leitura de dados a partir do relatório, a instância da classe de análise sintático deixa de ser necessária, portanto, excluimo-la da memória.
CParsing *Parsing = new CParsing(Deals); if(CheckPointer(Parsing)==POINTER_INVALID) return INIT_FAILED; if(!Parsing.ReadFile(FileName) || CheckPointer(Deals)==POINTER_INVALID || Deals.Total()<=0) { delete Parsing; return INIT_FAILED; } delete Parsing;
Depois disso, inicializamos as classes de indicador.
//--- ADX = new CADX(_Symbol,Timefarame,ADX_Period); if(CheckPointer(ADX)==POINTER_INVALID) return INIT_FAILED; //--- Alligator = new CAlligator(_Symbol,Timefarame,JAW_Period,JAW_Shift,TEETH_Period,TEETH_Shift,LIPS_Period,LIPS_Shift,Alligator_Method,Alligator_Price); if(CheckPointer(Alligator)==POINTER_INVALID) return INIT_FAILED; //--- int handle=iATR(_Symbol,Timefarame,ATR_Period); if(handle>0) { ATR = new COneBufferArray(handle); if(CheckPointer(ATR)==POINTER_INVALID) return INIT_FAILED; } //--- BWMFI = new CBWMFI(_Symbol,Timefarame,BWMFI_Volume); if(CheckPointer(BWMFI)==POINTER_INVALID) return INIT_FAILED; //--- handle=iCCI(_Symbol,Timefarame,CCI_Period,CCI_Price); if(handle>0) { CCI = new COneBufferArray(handle); if(CheckPointer(CCI)==POINTER_INVALID) return INIT_FAILED; } //--- handle=iChaikin(_Symbol,Timefarame,Ch_Fast_Period,Ch_Slow_Period,Ch_Method,Ch_Volume); if(handle>0) { Chaikin = new COneBufferArray(handle); if(CheckPointer(Chaikin)==POINTER_INVALID) return INIT_FAILED; } //--- handle=iForce(_Symbol,Timefarame,Force_Period,Force_Method,Force_Volume); if(handle>0) { Force = new COneBufferArray(handle); if(CheckPointer(Force)==POINTER_INVALID) return INIT_FAILED; } //--- MACD = new CMACD(_Symbol,Timefarame,MACD_Fast,MACD_Slow,MACD_Signal,MACD_Price); if(CheckPointer(MACD)==POINTER_INVALID) return INIT_FAILED; //--- handle=iStdDev(_Symbol,Timefarame,StdDev_Period,StdDev_Shift,StdDev_Method,StdDev_Price); if(handle>0) { StdDev = new COneBufferArray(handle); if(CheckPointer(StdDev)==POINTER_INVALID) return INIT_FAILED; } //--- handle=iVolumes(_Symbol,Timefarame,Applied_Volume); if(handle>0) { Volume = new COneBufferArray(handle); if(CheckPointer(Volume)==POINTER_INVALID) return INIT_FAILED; }
No final da função OnInit, definimos o contador de ordens como 0 e saímos da função.
cur_ticket = 0; //--- return(INIT_SUCCEEDED); }
6.2. Coleta de dados estatísticos
A coleta de dados sobre o estado dos indicadores será realizada na função OnTick. No início da função, verificamos se estão coletadas as informações sobre todas as ordens. Se for assim, saímos da função.
void OnTick() { if(cur_ticket>=Deals.Total()) return;
Na próxima etapa, o tempo de execução da transação analisada é comparado com o tempo do tick processado. Se o tempo da transação ainda não tiver chegado, saímos da função.
CDeal *object = Deals.At(cur_ticket); if(object.GetTime()>TimeCurrent()) return;
Se já tivermos passado os anteriores exames, verificamos o estado das instâncias das classes de indicador e salvamos as informações necessárias chamando a função SaveNewValues para cada indicador da classe.
if(CheckPointer(ADX)!=POINTER_INVALID) ADX.SaveNewValues(cur_ticket); //--- if(CheckPointer(Alligator)!=POINTER_INVALID) Alligator.SaveNewValues(cur_ticket); //--- if(CheckPointer(ATR)!=POINTER_INVALID) ATR.SaveNewValues(cur_ticket); //--- if(CheckPointer(BWMFI)!=POINTER_INVALID) BWMFI.SaveNewValues(cur_ticket); //--- if(CheckPointer(CCI)!=POINTER_INVALID) CCI.SaveNewValues(cur_ticket); //--- if(CheckPointer(Chaikin)!=POINTER_INVALID) Chaikin.SaveNewValues(cur_ticket); //--- if(CheckPointer(Force)!=POINTER_INVALID) Force.SaveNewValues(cur_ticket); //--- if(CheckPointer(MACD)!=POINTER_INVALID) MACD.SaveNewValues(cur_ticket); //--- if(CheckPointer(StdDev)!=POINTER_INVALID) StdDev.SaveNewValues(cur_ticket); //--- if(CheckPointer(Volume)!=POINTER_INVALID) Volume.SaveNewValues(cur_ticket);
No final da função, incrementamos o contador de ordens processadas e saímos da função.
cur_ticket++;
return;
}
6.3. Exibição de gráficos para análise
Realizamos a análise de dados e exibição de relatório na função OnTester. Ao iniciar a função, verificamos o número de transações para análise.
double OnTester() { double ret=0.0; int total=Deals.Total();
Se for necessário realizar a análise, inicializamos as classes estatísticas.
Para facilitar o processamento posterior, reunimos as classes estatísticas de indicadores de um buffer na matriz. Por isso, em paralelo com inicialização, calculamos os indicadores de um buffer utilizados.
int total_indy=0; if(total>0) { if(CheckPointer(ADX)!=POINTER_INVALID) ADX_Stat=new CStaticADX(ADX,1); //--- if(CheckPointer(Alligator)!=POINTER_INVALID) Alligator_Stat=new CStaticAlligator(Alligator); //--- if(CheckPointer(ATR)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(ATR,_Point*10); if(CheckPointer(indy)!=POINTER_INVALID) { if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } } //--- if(CheckPointer(BWMFI)!=POINTER_INVALID) BWMFI_Stat=new CStaticBWMFI(BWMFI,_Point*100); //--- if(CheckPointer(CCI)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(CCI,10); if(CheckPointer(indy)!=POINTER_INVALID) if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } //--- if(CheckPointer(Chaikin)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(Chaikin,100); if(CheckPointer(indy)!=POINTER_INVALID) if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } //--- if(CheckPointer(Force)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(Force,0.1); if(CheckPointer(indy)!=POINTER_INVALID) if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } //--- if(CheckPointer(MACD)!=POINTER_INVALID) MACD_Stat=new CStaticMACD(MACD,_Point*10); //--- if(CheckPointer(StdDev)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(StdDev,_Point*10); if(CheckPointer(indy)!=POINTER_INVALID) if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } //--- if(CheckPointer(Volume)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(Volume,100); if(CheckPointer(indy)!=POINTER_INVALID) if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } }
Em seguida, comparamos os dados dos indicadores com as transações correspondentes e agrupamos as informações de acordo com as direções requeridas para a saída de relatórios gráficos. Para este fim, em cada classe estatística, chamamos a função Ad passando para seus parâmetros as informações sobre a transação.
for(int i=0;i<total;i++) { CDeal *deal = Deals.At(i); ENUM_POSITION_TYPE type = deal.Type(); double d_profit = deal.GetProfit(); for(int ind=0;ind<total_indy;ind++) IndicatorsStatic[ind].Ad(i,d_profit,type); if(CheckPointer(BWMFI_Stat)!=POINTER_INVALID) BWMFI_Stat.Ad(i,d_profit,type); if(CheckPointer(MACD_Stat)!=POINTER_INVALID) MACD_Stat.Ad(i,d_profit,type); if(CheckPointer(ADX_Stat)!=POINTER_INVALID) ADX_Stat.Ad(i,d_profit,type); if(CheckPointer(Alligator_Stat)!=POINTER_INVALID) Alligator_Stat.Ad(i,d_profit,type); }
Depois do agrupamento de dados, criamos o arquivo de relatório report.html e guardamo-lo na pasta compartilhada do terminal.
if(total_indy>0 || CheckPointer(BWMFI_Stat)!=POINTER_INVALID || CheckPointer(MACD_Stat)!=POINTER_INVALID || CheckPointer(ADX_Stat)!=POINTER_INVALID || CheckPointer(Alligator_Stat)!=POINTER_INVALID ) { int handle=FileOpen("Report.html",FILE_WRITE|FILE_TXT|FILE_COMMON); if(handle<0) return ret;
No início do arquivo, escrevemos o cabeçalho do nosso relatório HTML.
FileWrite(handle,"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">"); FileWrite(handle,"<html> <head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"); FileWrite(handle,"<title>Deals to Indicators</title> <!-- - -->"); FileWrite(handle,"<script src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.js\" type=\"text/javascript\"></script>"); FileWrite(handle,"<script src=\"https://code.highcharts.com/highcharts.js\" type=\"text/javascript\"></script>"); FileWrite(handle,"<!-- - --> <script type=\"text/javascript\">$(document).ready(function(){");
Em seguida, chamada a função HTML_header de todas as classes estatísticas, inserimos os dados em nosso arquivo para plotagem de gráficos.
for(int ind=0;ind<total_indy;ind++) FileWrite(handle,IndicatorsStatic[ind].HTML_header()); if(CheckPointer(BWMFI_Stat)!=POINTER_INVALID) FileWrite(handle,BWMFI_Stat.HTML_header()); if(CheckPointer(MACD_Stat)!=POINTER_INVALID) FileWrite(handle,MACD_Stat.HTML_header()); if(CheckPointer(ADX_Stat)!=POINTER_INVALID) FileWrite(handle,ADX_Stat.HTML_header()); if(CheckPointer(Alligator_Stat)!=POINTER_INVALID) FileWrite(handle,Alligator_Stat.HTML_header());
Depois disso, chamando alternadamente a função HTML_body de cada classe estatística, criamos um modelo para a saída do relatório. Atenção: chamando esta função, finalizamos o trabalho com a classe estatística, e devemos removê-la para limpeza da memória.
FileWrite(handle,"});</script> <!-- - --> </head> <body>"); for(int ind=0;ind<total_indy;ind++) { FileWrite(handle,IndicatorsStatic[ind].HTML_body()); delete IndicatorsStatic[ind]; } if(CheckPointer(BWMFI_Stat)!=POINTER_INVALID) { FileWrite(handle,BWMFI_Stat.HTML_body()); delete BWMFI_Stat; } if(CheckPointer(MACD_Stat)!=POINTER_INVALID) { FileWrite(handle,MACD_Stat.HTML_body()); delete MACD_Stat; } if(CheckPointer(ADX_Stat)!=POINTER_INVALID) { FileWrite(handle,ADX_Stat.HTML_body()); delete ADX_Stat; } if(CheckPointer(Alligator_Stat)!=POINTER_INVALID) { FileWrite(handle,Alligator_Stat.HTML_body()); delete Alligator_Stat; }
No final, terminamos de por as marcas de fim, fechamos o arquivo, limpamos as matrizes e saímos da função.
FileWrite(handle,"</body> </html>"); FileFlush(handle); FileClose(handle); } //--- ArrayFree(IndicatorsStatic); //--- return(ret); }
Não esqueçamos remover as classes restantes na função OnDeinit.
7. Análise de informações
Nosso trabalho está chegando a sua conclusão. É hora de ver nossos resultados. Para fazer isso, voltamos para o testador de estratégias, repetimos todas as configurações que usamos no teste do EA estudado - na segunda seção deste artigo - e executamos nosso EA analítico recém-criado.
Após a conclusão do teste, abrimos a pasta compartilhada do terminal e procuramos lá o arquivo report.html. Abrimo-la no navegador. Agora, vou fornecer exemplos de seu relatório.
7.1. ATR
Durante a análise de gráficos de dependência entre o lucro e o indicador ATR, não vejo algumas áreas potencialmente lucrativas, e, portanto, a filtragem de transações está fora de questão.
7.2. CCI.
Os gráficos de dependência entre o lucro e o indicador CCI permitem tirar certo lucro em transações BUY quando o valor do indicador é superior a 200 e a linha do indicador é crescente. Porém, em transações SELL, não há áreas lucrativas.
7.3. Chaikin
O oscilador Chaikin, como o ATR, não mostrou relação entre a os valores do indicador e o lucro das transações.
7.4. indicador de força
Os gráficos analíticos do indicador de força também não mostraram nenhuma dependência.
7.5. Desvio padrão
A análise das dependências dos valores do indicadores stdDev permite revelar algumas áreas rentáveis para ordens de compra, mas, não há hipótese de filtrar transações de venda.
7.6. Indicador de volume
Não foi possível detectar dependências na análise de dados do indicador de volumes.
7.7. Bill Williams MFI
O indicador BW MFI permite tirar lucro durante a filtragem de transações de compra, só se elas estiverem abertas na cor 0. Porém, não foi possível detectar dependências para as transações de venda.
7.8. MACD.
Os sinais do indicador MACD permitem filtrar transações de compra rentáveis. Isso pode ser feito realizando operações de compra, quando a linha de sinal está acima do histograma. Mas, a análise não mostra as áreas rentáveis para transações de venda. Ao mesmo tempo, o indicador reduz operações desfavoráveis eliminando transações de venda, quando o histograma está crescendo e as linhas de sinal estão iguais ou abaixo do histograma.
7.9. ADX
A análise do indicador ADX não permite filtrar a transações.
7.10. Alligator
O uso do indicador de Alligator para filtração de transações, na minha opinião, é o mais promissor. Padrões para executar transações podem ser encontrados em combinações de posição e direção das linhas. Assim, transações rentáveis para a compra podem ser feitas, se:
- a posição das linhas do indicador mostra uma tendência de venda e as linhas LIPS e JAW viram para cima;
- a posição das linhas do indicador indica uma tendência de compra e as linhas LIPS e TEETH apontam para cima;
- a tendência é indefinida e as linhas TEETH e JAW apontam para baixo.
Para transações de venda, vamos usar sinais de espelho.
8. Corrigimos o EA inicial
Temos realizado um trabalho muito extenso na análise de transações de nosso EA. Agora, vamos ver como isso vai influenciar o desempenho da nossa estratégia. Para isso, ao módulo de sinais de negociação do artigo [1] adicionamos indicadores com as regras de filtragem da análise acima. Proponho adicionar o MACD e Alligator ao nosso módulo.
Eu recomendaria adicionar os filtro de indicador sequencialmente e executar ciclicamente o procedimento de decomposição de transações em indicadores após a adição de cada filtro. Isto fornecerá uma compreensão mais clara da influência de cada filtro sobre toda a estratégia e ajudará a avaliar seu complexo impacto. Além disso, o fato de, na primeira etapa, a análise não permitir revelar a dependência entre o lucro e os valores de certo indicador não significa que você não vai ver essa dependência em iterações subsequentes. Não faço isso agora simplesmente para não empolar este artigo, que para já é bastante grande.
Primeiro, adicionamos os parâmetros dos indicadores na descrição do módulo.
//| Parameter=JAW_Period,uint,13,JAW Period | //| Parameter=JAW_Shift,uint,8,JAW Shift | //| Parameter=TEETH_Period,uint,8,TEETH Period | //| Parameter=TEETH_Shift,uint,5,TEETH Shift | //| Parameter=LIPS_Period,uint,5,LIPS Period | //| Parameter=LIPS_Shift,uint,3,LIPS_Shift | //| Parameter=Alligator_Method,ENUM_MA_METHOD,MODE_SMMA,Method | //| Parameter=Alligator_Price,ENUM_APPLIED_PRICE,PRICE_MEDIAN,Alligator Price | //| Parameter=MACD_Fast,uint,12,MACD Fast | //| Parameter=MACD_Slow,uint,26,MACD Slow | //| Parameter=MACD_Signal,uint,9,MACD Signal | //| Parameter=MACD_Price,ENUM_APPLIED_PRICE,PRICE_CLOSE,MACD Price |
Assim, no bloco private adicionamos as variáveis para armazenamento de parâmetros, enquanto no bloco public, as funções para sua gravação.
uint ci_MACD_Fast; uint ci_MACD_Slow; uint ci_MACD_Signal; ENUM_APPLIED_PRICE ce_MACD_Price; uint ci_JAW_Period; uint ci_JAW_Shift; uint ci_TEETH_Period; uint ci_TEETH_Shift; uint ci_LIPS_Period; uint ci_LIPS_Shift; ENUM_MA_METHOD ce_Alligator_Method; ENUM_APPLIED_PRICE ce_Alligator_Price; void JAW_Period(uint value) { ci_JAW_Period = value; } void JAW_Shift(uint value) { ci_JAW_Shift = value; } void TEETH_Period(uint value) { ci_TEETH_Period= value; } void TEETH_Shift(uint value) { ci_TEETH_Shift = value; } void LIPS_Period(uint value) { ci_LIPS_Period = value; } void LIPS_Shift(uint value) { ci_LIPS_Shift = value; } void Alligator_Method(ENUM_MA_METHOD value) { ce_Alligator_Method = value; } void Alligator_Price(ENUM_APPLIED_PRICE value) { ce_Alligator_Price= value; } void MACD_Fast(uint value) { ci_MACD_Fast = value; } void MACD_Slow(uint value) { ci_MACD_Slow = value; } void MACD_Signal(uint value) { ci_MACD_Signal = value; } void MACD_Price(ENUM_APPLIED_PRICE value) { ce_MACD_Price = value; }
Também é necessário adicionar as classes para trabalhar com os indicadores e funções de inicialização de aquisição de dados necessários. Para trabalho com o MACD, eu utilizei a classe padrão. Como para o Alligator não existe uma classe padrão, eu a substitui por três classes de médias móveis dando-lhes nomes de acordo com os nomes das linhas de indicador.
protected: CiMACD m_MACD; // object-oscillator CiMA m_JAW; CiMA m_TEETH; CiMA m_LIPS; //--- method of initialization of the indicators bool InitMACD(CIndicators *indicators); bool InitAlligator(CIndicators *indicators); //--- methods of getting data double Main(int ind) { return(m_MACD.Main(ind)); } double Signal(int ind) { return(m_MACD.Signal(ind)); } double DiffMain(int ind) { return(Main(ind+1)!=0 ? Main(ind)-Main(ind+1) : 0); } int AlligatorTrend(int ind); double DiffJaw(int ind) { return(m_JAW.Main(ind+1)!=0 ? m_JAW.Main(ind)/m_JAW.Main(ind+1) : 1); } double DiffTeeth(int ind) { return(m_TEETH.Main(ind+1)!=0 ? m_TEETH.Main(ind)/m_TEETH.Main(ind+1) : 1); } double DiffLips(int ind) { return(m_LIPS.Main(ind+1)!=0 ? m_LIPS.Main(ind)/m_LIPS.Main(ind+1) : 1); }
No próximo passo, inserimos as mudanças na função InitIndicators para adicionar nossos indicadores à biblioteca do EA.
bool CSignalKalman::InitIndicators(CIndicators *indicators) { //--- initialization of indicators and timeseries of additional filters if(!CExpertSignal::InitIndicators(indicators)) return(false); //--- initialize close serias if(CheckPointer(m_close)==POINTER_INVALID) { if(!InitClose(indicators)) return false; } //--- create and initialize MACD oscilator if(!InitMACD(indicators)) return(false); //--- create and initialize Alligator if(!InitAlligator(indicators)) return(false); //--- create and initialize Kalman Filter if(CheckPointer(Kalman)==POINTER_INVALID) Kalman=new CKalman(ci_HistoryBars,ci_ShiftPeriod,m_symbol.Name(),ce_Timeframe); //--- ok return(true); }
Em seguida, introduzimos complementos na função de tomada de decisão. Ao fazer isto, lembre que os indicadores adicionados atuam como filtro. Portanto, só depois de receber o sinal principal é que vamos acessar os indicadores.
int CSignalKalman::LongCondition(void) { if(!CalculateIndicators()) return 0; int result=0; //--- if(cd_correction>cd_forecast) { if(Signal(1)>Main(1)) result=80; else { switch(AlligatorTrend(1)) { case 1: if(DiffLips(1)>1 && DiffTeeth(1)>1 && DiffJaw(1)<=1) result=80; break; case -1: if(DiffLips(1)>1 || DiffJaw(1)>1) result=80; break; case 0: if(DiffJaw(1)<1) { if(DiffLips(1)>1) result=80; else if(DiffTeeth(1)<1) result=80; } break; } } } return result; }
Alterações similares são feitas para a função ShortCondition. O código completo de módulo de soluções de negociação pode ser encontrado no anexo.
9. Teste do EA após introduzir alterações
Depois de introduzir alterações no módulo de soluções de negociação, criamos um novo EA (descrição detalhada de criação do EA com uso do módulo de sinais de negociação é fornecida no artigo [5]). Vamos testar o EA recém-criado com os parâmetros semelhantes aos do teste inicial na secção 2 deste artigo.
Como mostraram os resultados do teste, sem alterar os parâmetros do EA, o uso de filtros permitiu aumentar o fator de lucro de 0,75 para 1,12. Ou seja, apesar dos parâmetros desfavoráveis do EA fonte, conseguimos obter lucro. Lembre que no começo, eu tomei parâmetros não-otimizados do EA inicial.
Fim do artigo
Este artigo mostrou o processo de decomposição do histórico de transações em indicadores, o que permitiu providenciar um sistema de filtros com base em indicadores padrão. No final do teste, este sistema mostrou um resultado tangível quanto à rentabilidade do EA analisado. O sistema proposto pode ser aplicado não só na otimização do sistema de negociação existente, mas também na tentativa de criar um novo.
Referências
- Uso do filtro de Kalman na previsão da tendência.
- Gráficos e diagramas em HTML.
- Quanto dura a tendência?
- Diversas maneiras de se encontrar uma tendência em MQL5
- Examinemos na prática o método adaptativo de acompanhamento do mercado.
Programas utilizados no artigo:
# | Nome | Tipo | Descrição |
---|---|---|---|
1 | Kalman.mqh | Biblioteca de classes | Classe do filtro de Kalman |
2 | SignalKalman.mqh | Biblioteca de classes | Módulo de sinais de negociação sobre o filtro de Kalman |
3 | SignalKalman+Filters.mqh | Biblioteca de classes | Examinemos na prática o método adaptativo de acompanhamento do mercado |
4 | Kalman_expert.mq5 | Expert Advisor | Expert Advisor original usado na estratégia com o filtro de Kalman |
5 | Kalman+Filters.mq5 | Expert Advisor | Expert Advisor modificado usado na estratégia com o filtro de Kalman |
6 | Deals_to_Indicators.mq5 | Expert Advisor | Expert Advisor para decomposição do histórico de transações em indicadores |
7 | Deal.mqh | Biblioteca de classes | Classe para salvar informações sobre o trade |
8 | Parsing.mqh | Biblioteca de classes | Classe para analise sintática do histórico de transações a partir do relatório de teste |
9 | Value.mqh | Biblioteca de classes | Classe para armazenar dados sobre o estado do buffer de indicador |
10 | OneBufferArray.mqh | Biblioteca de classes | Classe para armazenar o histórico de dados de um indicador de buffer |
11 | StaticOneBuffer.mqh | Biblioteca de classes | Classe para coleta e análise de estatísticas de um indicador de buffer |
12 | ADXValue.mqh | Biblioteca de classes | Classe para armazenar dados sobre o estado do indicador ADX |
13 | ADX.mqh | Biblioteca de classes | Classe para armazenar o histórico de dados do indicador ADX |
14 | StaticADX.mqh | Biblioteca de classes | Classe para coleta e análise estatística do indicador ADX |
15 | AlligatorValue.mqh | Biblioteca de classes | Classe para armazenar dados sobre o estado do indicador Alligator |
16 | Alligator.mqh | Biblioteca de classes | Classe para armazenar o histórico de dados do indicador Alligator |
17 | StaticAlligator.mqh | Biblioteca de classes | Classe para coleta e análise estatística do indicador Alligator |
18 | BWMFIValue.mqh | Biblioteca de classes | Classe para armazenar dados sobre o estado do indicador BW MFI |
19 | BWMFI.mqh | Biblioteca de classes | Classe para armazenar o histórico de dados do indicador BW MFI |
20 | StaticBWMFI.mqh | Biblioteca de classes | Classe para coleta e análise estatística do indicador BW MFI |
21 | MACDValue.mqh | Biblioteca de classes | Classe para armazenar dados sobre o estado do indicador MACD |
22 | MACD.mqh | Biblioteca de classes | Classe para armazenar o histórico de dados do indicador MACD |
23 | StaticMACD.mqh | Biblioteca de classes | Classe para coleta e análise estatística do indicador MACD |
24 | Reports.zip | Arquivo | O arquivo contém os resultados dos testes dos EAs no testador de estratégias e um relatório analítico. |
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/3968
- 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
Estou tentando executar ele mas não dá.
Dá erro no StaticMACD.mqh
2017.12.29 08:11:23.672 2017.12.14 18:16:59 array out of range in 'StaticMACD.mqh' (375,45)