Gráfico PairPlot baseado em CGraphic para analisar correlações entre arrays de dados (séries temporais)

29 outubro 2018, 08:10
Dmitriy Gizlyk
0
585

Conteúdo

Introdução

Como todos nós sabemos, os traders de forex realizam operações usando as cotações de pares de moedas, nas quais o valor da moeda de um país é expresso na moeda de outro. É fácil ver que a mesma moeda pode ser encontrada em vários pares de moedas. Como o valor de uma moeda é determinado pelo estado econômico de seu país, nós podemos perguntar se as mudanças na economia do país afetam sua moeda uniformemente em diferentes pares de moedas. Uma resposta positiva parece inevitável aqui. Mas justifica-se apenas em um caso perfeito quando o estado econômico de apenas um país se altera. A verdade da vida é que o nosso mundo está mudando constantemente. Uma mudança na economia de um país implica direta ou indiretamente uma mudança na economia global.

Na Internet, você pode encontrar muitas informações sobre como analisar as alterações de preço da moeda em vários pares e pesquisar a correlação entre diferentes pares de moedas. Neste site, você pode encontrar artigos sobre a negociação de cestas de pares de moedas [1, 2, 3]. No entanto, a questão de analisar as correlações entre séries temporais permanece em aberto. Neste artigo, eu sugiro o desenvolvimento de uma ferramenta para análise gráfica de correlações entre séries temporais, que permita visualizar correlações entre as séries temporais de cotações de pares de moedas analisados.


1. Definindo uma tarefa

Antes de começarmos a trabalhar, vamos definir nossos objetivos. Que tipo de ferramenta nós queremos obter no final? Primeiro de tudo, ele deve ser um painel gráfico contendo gráficos de correlações entre séries temporais escolhidas. A ferramenta deve ser versátil o suficiente e capaz de trabalhar com diferentes números de séries temporais.

Para analisar a série temporal no painel, nós construiremos um histograma de distribuição para cada série temporal. Nós também prepararemos os gráficos de dispersão para serem exibidos em pares para as séries temporais analisadas, a fim de procurar uma correlação. As linhas de tendência serão adicionadas aos gráficos de dispersão como uma referência visual.

O layout dos gráficos na forma de uma tabela cruzada melhorará a legibilidade de toda a ferramenta. Essa abordagem unifica a apresentação de dados e simplifica a percepção visual. O layout da ferramenta proposta é fornecido abaixo.

Layout


2. Criação das classes base

2.1. "A base"

Ao desenvolver essa ferramenta, nós devemos ter em mente que os usuários podem trabalhar com um número diferente de instrumentos de negociação. Eu acredito que a solução visual perfeita é uma representação baseada em blocos, em que os gráficos padrão são usados ​​como "tijolos" para construir uma tabela de correlação comum.

Começaremos a desenvolver nossa ferramenta preparando a base para a construção dos gráficos. A biblioteca padrão da MetaTrader 5 apresenta a classe CGraphic destinada a construir gráficos científicos. O artigo [4] fornece uma descrição detalhada desta classe. Nós vamos usá-la como base para a construção de nossos gráficos. Vamos criar a classe base CPlotBase e atribuí-la como a classe derivada da classe padrão CGraphic. Nesta classe, nós criaremos os métodos para criar uma tela do gráfico. Haverá dois métodos: o primeiro é plotar um campo quadrado do gráfico com uma determinada dimensão lateral e o segundo é construir uma área retangular usando as coordenadas fornecidas. Nós também adicionaremos os métodos para exibir um texto nas laterais do gráfico (eles nos ajudarão a exibir os nomes dos instrumentos). Além disso, vamos adicionar o método para alterar a cor de exibição no gráfico da série temporal.

Nós também devemos ter em mente que a classe base CGraphic aplicada não é derivada da classe CObject e não contém os métodos para realocar, ocultar e exibir um objeto em um gráfico. Métodos similares são amplamente utilizados nos painéis gráficos. Portanto, nós precisamos adicionar esses métodos à classe criada para a compatibilidade de nossa ferramenta com as classes padrão para a construção dos painéis gráficos.

class CPlotBase : public CGraphic
  {
protected:
   long              m_chart_id;                // chart ID
   int               m_subwin;                  // chart subwindow

public:
                     CPlotBase();
                    ~CPlotBase();
//--- Criação do objeto
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int size);
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
//--- Altera a cor do rótulo da timeserie
   virtual bool      SetTimeseriesColor(uint clr, uint timeserie=0);
//--- Adiciona o texto ao gráfico
   virtual void      TextUp(string text, uint clr);
   virtual void      TextDown(string text, uint clr);
   virtual void      TextLeft(string text, uint clr);
   virtual void      TextRight(string text, uint clr);
//--- geometria
   virtual bool      Shift(const int dx,const int dy);
//--- estado
   virtual bool      Show(void);
   virtual bool      Hide(void);
  };

No construtor de classe, exibição da legenda do gráfico é removida e é definido o número mínimo de rótulos ao longo dos eixos.

CPlotBase::CPlotBase()
  {
   HistoryNameWidth(0);
   HistorySymbolSize(0);
   m_x.MaxLabels(3);
   m_y.MaxLabels(3);
  }

Encontre o código inteiro de todos os métodos de classe em anexo no artigo.

2.2. Gráfico de dispersão

Em seguida, foi desenvolvido a classe CScatter para exibir o gráfico de dispersão. Esta classe conterá apenas dois métodos para criar e atualizar os dados da série temporal.

class CScatter : public CPlotBase
  {

public:
                     CScatter();
                    ~CScatter();
//---
   int               AddTimeseries(const double &timeseries_1[],const double &timeseries_2[]);
   bool              UpdateTimeseries(const double &timeseries_1[],const double &timeseries_2[],uint timeserie=0);

  };

Dois arrays das séries temporais dos instrumentos analisados ​​no qual o gráfico de dispersão é baseado devem ser passados ​​para o método de criação de curva do AddTimeseries. A classe padrão CGraphic é capaz de exibir um gráfico de pontos baseado em dois arrays de dados. Nós vamos usar esse recurso. No início do método, crie uma curva de ponto com base em dois arrays de dados. Se a criação da curva falhar, saia da função com o resultado "-1". Se a curva for criada com sucesso, defina o tamanho dos pontos da curva e defina o sinalizador de exibição da linha de tendência. Depois de executar todas as operações, o método retorna o índice da curva criada.

int CScatter::AddTimeseries(const double &timeseries_1[],const double &timeseries_2[])
  {
   CCurve *curve=CGraphic::CurveAdd(timeseries_1,timeseries_2,CURVE_POINTS);
   if(curve==NULL)
      return -1;
   curve.PointsSize(2);
   curve.TrendLineVisible(true);
   return (m_arr_curves.Total()-1);
  }

Para atualizar os dados da curva, crie o método UpdateTimeseries. Dois arrays de dados para criar a curva e o índice da curva, cujos dados devem ser alterados, devem ser passados ​​para ela. No início da função, verifique a validade do número da curva especificada. Se o número for especificado erroneamente, a função é encerrada com 'false'.

Em seguida, compare a dimensão da série temporal recebida. Se os tamanhos dos arrays forem diferentes ou os arrays estiverem vazios, a função é encerrada com 'false'.

O próximo passo é especificar o ponteiro para um objeto da curva por índice. Se o ponteiro estiver incorreto, a função é encerrada com 'false'.

Depois de todas as verificações, passe a série temporal para a curva e a função é encerrada com 'true'.

bool CScatter::UpdateTimeseries(const double &timeseries_1[],const double &timeseries_2[], uint timeserie=0)
  {
   if((int)timeserie>=m_arr_curves.Total())
      return false;
   if(ArraySize(timeseries_1)!=ArraySize(timeseries_2) || ArraySize(timeseries_1)==0)
      return false;
//---
   CCurve *curve=m_arr_curves.At(timeserie);
   if(CheckPointer(curve)==POINTER_INVALID)
      return false;
//---
   curve.Update(timeseries_1,timeseries_2);
//---
   return true;
  }

2.3. Histograma

O histograma é um outro "tijolo" para a construção de nossa ferramenta. Nós precisamos da classe CHistogram para criá-la. Como o CScatter, a classe recebe seus próprios métodos de geração e atualização dos dados da curva. No entanto, ao contrário do seu predecessor, a classe atual aplica uma série temporal para a construção da curva. Os princípios de construção desses métodos são semelhantes aos métodos da classe anterior.

Tenha em mente que a classe base CGraphic pode construir o histograma apenas em seu formato padrão. Para adicionar a possibilidade de construir um histograma vertical do tipo Market Profile, nós teremos que reescrever o método HistogramPlot. Além disso, nós devemos adicionar a variável e_orientation para armazenar o tipo de construção do histograma e reescrever os métodos de criação da tela de gráfico adicionando a capacidade de especificar o tipo de histograma para eles.

Outra diferença entre nossa classe e a classe base em CGpraphic é o tipo de dados iniciais que nós obtemos. Na classe base, um array dos valores obtidos é usado para a saída direta para o gráfico. Nossa classe receberá uma série temporal e, antes de processar o histograma, será necessário processar os dados obtidos. A preparação dos dados para a construção do histograma é realizada pelo método CalculateHistogramArray, o número de colunas do histograma é definido pelo método SetCells e salvo na variável i_cells.

class CHistogram : public CPlotBase
  {
private:
   ENUM_HISTOGRAM_ORIENTATION    e_orientation;
   uint                          i_cells;

public:
                                 CHistogram();
                                ~CHistogram();
//---
   bool              Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int size, ENUM_HISTOGRAM_ORIENTATION orientation=HISTOGRAM_HORIZONTAL);
   bool              Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2, ENUM_HISTOGRAM_ORIENTATION orientation=HISTOGRAM_HORIZONTAL);
   int               AddTimeserie(const double &timeserie[]);
   bool              UpdateTimeserie(const double &timeserie[],uint timeserie=0);
   bool              SetCells(uint value)    {  i_cells=value; }

protected:
   virtual void      HistogramPlot(CCurve *curve);
   bool              CalculateHistogramArray(const double &data[],double &intervals[],double &frequency[], 
                                             double &maxv,double &minv);
};

O método CalculateHistogramArray é baseado no algoritmo oferecido na Referência MQL5 com uma pequena adição. No início do método, verifique a suficiência dos dados iniciais para plotar o histograma, determine os valores mínimo e máximo, calcule a largura de intervalo de cada intervalo e prepare os arrays para armazenar os intervalos e frequências.

Depois disso, os centros de intervalo são definidos no loop e a matriz de frequência é definida como zero.

No próximo loop, percorra a série temporal e conte os valores correspondentes ao intervalo correspondente.

Finalmente, normalize as frequências convertendo as conexões mencionadas em porcentagem do número total de elementos na série temporal.

bool CHistogram::CalculateHistogramArray(const double &data[],double &intervals[],double &frequency[], 
                             double &maxv,double &minv) 
  { 
   int size=ArraySize(data); 
   if(size<(int)i_cells*10) return (false); 
   minv=data[ArrayMinimum(data)]; 
   maxv=data[ArrayMaximum(data)]; 
   double range=maxv-minv; 
   double width=range/i_cells; 
   if(width==0) return false; 
   ArrayResize(intervals,i_cells); 
   ArrayResize(frequency,i_cells); 
//--- configura os centros do intervalo 
   for(uint i=0; i<i_cells; i++) 
     { 
      intervals[i]=minv+(i+0.5)*width; 
      frequency[i]=0; 
     } 
//--- preenche as frequências de ajuste do intervalo 
   for(int i=0; i<size; i++) 
     { 
      uint ind=int((data[i]-minv)/width); 
      if(ind>=i_cells) ind=i_cells-1; 
      frequency[ind]++; 
     } 
//--- normaliza as frequências em porcentagem
   for(uint i=0; i<i_cells; i++) 
      frequency[i]*=(100.0/(double)size); 
   return (true); 
  } 

O histograma é plotado no gráfico pelo método HistogramPlot. Essa função é construída com base no algoritmo da classe base CGraphic corrigido para o uso das séries temporais e a orientação de construção do histograma.

No início do método, prepare os dados para traçar o histograma. Para fazer isso, nós obtemos uma série temporal a partir dos dados da curva e chamamos o método CalculateHistogramArray. Depois que a função é executada com sucesso, nós obtemos a largura dos blocos do histograma e verificamos o tamanho do array de dados de construção.

Em seguida, formate os valores pelos eixos de acordo com o tipo de exibição do histograma.

Finalmente, organize o loop para exibir as colunas do diagrama no campo do gráfico.

CHistogram::HistogramPlot(CCurve *curve)
  {
   double data[],intervals[],frequency[];
   double max_value, min_value;
   curve.GetY(data);
   if(!CalculateHistogramArray(data,intervals,frequency,max_value,min_value))
      return;
//--- parâmetros do histograma
   int histogram_width=fmax(curve.HistogramWidth(),2);
//--- Verifica
   if(ArraySize(frequency)==0 || ArraySize(intervals)==0)
      return;
//---
   switch(e_orientation)
     {
      case HISTOGRAM_HORIZONTAL:
        m_y.AutoScale(false);
        m_x.Min(intervals[ArrayMinimum(intervals)]);
        m_x.Max(intervals[ArrayMaximum(intervals)]);
        m_x.MaxLabels(3);
        m_x.ValuesFormat("%.0f");
        m_y.Min(0);
        m_y.Max(frequency[ArrayMaximum(frequency)]);
        m_y.ValuesFormat("%.2f");
        break;
      case HISTOGRAM_VERTICAL:
        m_x.AutoScale(false);
        m_y.Min(intervals[ArrayMinimum(intervals)]);
        m_y.Max(intervals[ArrayMaximum(intervals)]);
        m_y.MaxLabels(3);
        m_y.ValuesFormat("%.0f");
        m_x.Min(0);
        m_x.Max(frequency[ArrayMaximum(frequency)]);
        m_x.ValuesFormat("%.2f");
        break;
     }
//---
   CalculateXAxis();
   CalculateYAxis();
//--- cálculo original de y
   int originalY=m_height-m_down;
   int originalX=m_width-m_right;
   int yc0=ScaleY(0.0);
   int xc0=ScaleX(0.0);
//--- obtém a cor da curva
   uint clr=curve.Color();
//--- desenha 
   for(uint i=0; i<i_cells; i++)
     {
      //--- verifica as coordenadas
      if(!MathIsValidNumber(frequency[i]) || !MathIsValidNumber(intervals[i]))
         continue;
      if(e_orientation==HISTOGRAM_HORIZONTAL)
        {
         int xc=ScaleX(intervals[i]);
         int yc=ScaleY(frequency[i]);
         int xc1 = xc - histogram_width/2;
         int xc2 = xc + histogram_width/2;
         int yc1 = yc;
         int yc2 = (originalY>yc0 && yc0>0) ? yc0 : originalY;
         //---
         if(yc1>yc2)
            yc2++;
         else
            yc2--;
         //---
         m_canvas.FillRectangle(xc1,yc1,xc2,yc2,clr);
        }
      else
        {
         int yc=ScaleY(intervals[i]);
         int xc=ScaleX(frequency[i]);
         int yc1 = yc - histogram_width/2;
         int yc2 = yc + histogram_width/2;
         int xc1 = xc;
         int xc2 = (originalX>xc0 && xc0>0) ? xc0 : originalX;
         //---
         if(xc1>xc2)
            xc2++;
         else
            xc2--;
         //---
         m_canvas.FillRectangle(xc1,yc1,xc2,yc2,clr);
        }
     }
//---
  }

O código completo de todas as classes e métodos está disponível no anexo.

2.4. Classe para trabalhar com séries temporais

Para construir a ferramenta, nós precisamos de outro "tijolo" que baixe os dados históricos necessários e prepare a série temporal para traçar os gráficos. Este trabalho será realizado na classe CTimeserie. O nome do instrumento, o período de tempo e o preço aplicado devem ser passados ​​ao inicializar a classe. Além disso, devem ser criados os métodos para a alteração subsequente do nome do instrumento, período de tempo, preço aplicado e profundidade do histórico.

class CTimeserie :  public CObject
  {
protected:
   string               s_symbol;
   ENUM_TIMEFRAMES      e_timeframe;
   ENUM_APPLIED_PRICE   e_price;
   double               d_timeserie[];
   int                  i_bars;
   datetime             dt_last_load;
   
public:
                     CTimeserie(void);
                    ~CTimeserie(void);
   bool              Create(const string symbol=NULL, const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
// --- Altera as configurações da série temporal
   void              SetBars(const int value)            {  i_bars=value;  }
   void              Symbol(string value)                {  s_symbol=value;      dt_last_load=0;  }
   void              Timeframe(ENUM_TIMEFRAMES value)    {  e_timeframe=value;   dt_last_load=0;  }
   void              Price(ENUM_APPLIED_PRICE value)     {  e_price=value;       dt_last_load=0;  }
//---
   string            Symbol(void)                        {  return s_symbol;     }
   ENUM_TIMEFRAMES   Timeframe(void)                     {  return e_timeframe;  }
   ENUM_APPLIED_PRICE Price(void)                        {  return e_price;      }
//--- Carrega os dados
   virtual bool      UpdateTimeserie(void);
   bool              GetTimeserie(double &timeserie[])   {  return ArrayCopy(timeserie,d_timeserie)>0;   }
  };

O principal trabalho de preparação de dados deve ser feito no método UpdateTimeserie. No início do método, verifique se as informações necessárias foram previamente carregadas na barra atual. Se a informação estiver pronta, a função é encerrada com 'true'. Se for necessário preparar os dados, carregue os dados do histórico necessários de acordo com o preço especificado. Se for impossível carregar as informações, a função é encerrada com 'false'. Note que nós não estamos interessados ​​no preço em si, mas em sua mudança. Portanto, o upload dos dados do histórico excede o valor especificado em 1 barra. No próximo estágio, nós recalculamos a alteração do preço em cada barra e salvamos ela no array em um loop. Mais tarde, o usuário pode obter essa informação usando o método GetTimeserie.

bool CTimeserie::UpdateTimeserie(void)
  {
   datetime cur_date=(datetime)SeriesInfoInteger(s_symbol,e_timeframe,SERIES_LASTBAR_DATE);
   if(dt_last_load>=cur_date && ArraySize(d_timeserie)>=i_bars)
      return true;
//---
   MqlRates rates[];
   int bars=0,i;
   double data[];
   switch(e_price)
     {
      case PRICE_CLOSE:
        bars=CopyClose(s_symbol,e_timeframe,1,i_bars+1,data);
        break;
      case PRICE_OPEN:
        bars=CopyOpen(s_symbol,e_timeframe,1,i_bars+1,data);
      case PRICE_HIGH:
        bars=CopyHigh(s_symbol,e_timeframe,1,i_bars+1,data);
      case PRICE_LOW:
        bars=CopyLow(s_symbol,e_timeframe,1,i_bars+1,data);
      case PRICE_MEDIAN:
        bars=CopyRates(s_symbol,e_timeframe,1,i_bars+1,rates);
        bars=ArrayResize(data,bars);
        for(i=0;i<bars;i++)
           data[i]=(rates[i].high+rates[i].low)/2;
        break;
      case PRICE_TYPICAL:
        bars=CopyRates(s_symbol,e_timeframe,1,i_bars+1,rates);
        bars=ArrayResize(data,bars);
        for(i=0;i<bars;i++)
           data[i]=(rates[i].high+rates[i].low+rates[i].close)/3;
        break;
      case PRICE_WEIGHTED:
        bars=CopyRates(s_symbol,e_timeframe,1,i_bars+1,rates);
        bars=ArrayResize(data,bars);
        for(i=0;i<bars;i++)
           data[i]=(rates[i].high+rates[i].low+2*rates[i].close)/4;
        break;
     }
//---
   if(bars<=0)
      return false;
//---
   dt_last_load=cur_date;
//---
   if(ArraySize(d_timeserie)!=(bars-1) && ArrayResize(d_timeserie,bars-1)<=0)
      return false;
   double point=SymbolInfoDouble(s_symbol,SYMBOL_POINT);
   for(i=0;i<bars-1;i++)
      d_timeserie[i]=(data[i+1]-data[i])/point;
//---
   return true;
  }

O código completo de todas as classes e seus métodos está disponível no anexo.


3 Montagem do PairPlot

Depois de criar os "tijolos", nós podemos começar a desenvolver nossa ferramenta. Vamos criar a classe CPairPlot derivada da classe CWndClient. Essa abordagem facilitará o uso de nossa ferramenta em painéis gráficos construídos usando a classe CAppDialog padrão (os detalhes de sua aplicação podem ser encontrados nos artigos [5,6]).

No bloco 'private' de nossa classe, declare o array de ponteiros para os objetos da classe CPlotBase para armazenar os ponteiros nos gráficos, os objetos da classe CArrayObj para armazenar os ponteiros dos objetos das séries temporais, assim como as variáveis ​​para armazenar o período de tempo, preço e orientação do histograma, profundidade do histórico e a cor para exibir os nomes dos instrumentos no gráfico.

class CPairPlot : public CWndClient
  {
private:
   CPlotBase                    *m_arr_graphics[];
   CArrayObj                     m_arr_symbols;
   ENUM_TIMEFRAMES               e_timeframe;
   ENUM_APPLIED_PRICE            e_price;
   int                           i_total_symbols;
   uint                          i_bars;
   ENUM_HISTOGRAM_ORIENTATION    e_orientation;
   uint                          i_text_color;
      
public:
                     CPairPlot();
                    ~CPairPlot();
//---
   bool              Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2, const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
   bool              Refresh(void);
   bool              HistogramOrientation(ENUM_HISTOGRAM_ORIENTATION value);
   ENUM_HISTOGRAM_ORIENTATION    HistogramOrientation(void)    {  return e_orientation;   }
   bool              SetTextColor(color value);
//--- geometria
   virtual bool      Shift(const int dx,const int dy);
//--- estado
   virtual bool      Show(void);
   virtual bool      Hide(void);
  };

Declare os métodos da classe no bloco 'public'. A inicialização da classe é executada pelo método Create que recebe o ID do gráfico, nome do objeto, índice da subjanela aplicada, coordenadas de construção, array de símbolos usados, período de tempo aplicados, preço, profundidade do histórico e o número de colunas do histograma em sua chamada.

No início do método, verifique o array de nomes dos instrumentos passados ​​e a profundidade do histórico especificado. Se eles não satisfizerem nossos requisitos mínimos, a função é encerrada com 'false'. Em seguida, salve os valores dos parâmetros de entrada para os gráficos de plotagem.

bool CPairPlot::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2, const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10, const ENUM_APPLIED_PRICE price=PRICE_CLOSE)
  {
   i_total_symbols=0;
   int total=ArraySize(symbols);
   if(total<=1 || bars<100)
      return false;
//---
   e_timeframe=timeframe;
   i_bars=bars;
   e_price=price;

Em seguida, crie as instâncias das classes CTimeserie para cada instrumento em um loop. Se não for possível criar uma série temporal para cada instrumento especificado, a função é encerrada com 'false'.

   for(int i=0;i<total;i++)
     {
      CTimeserie *temp=new CTimeserie;
      if(temp==NULL)
         return false;
      temp.SetBars(i_bars);
      if(!temp.Create(symbols[i],e_timeframe,e_price))
         return false;
      if(!m_arr_symbols.Add(temp))
         return false;
     }
   i_total_symbols=m_arr_symbols.Total();
   if(i_total_symbols<=1)
      return false;

Após a conclusão bem sucedida do trabalho preparatório, prossiga para a criação direta dos objetos gráficos. Primeiro, chame o método Create da classe pai. Em seguida, obtenha o tamanho do array m_arr_graphics (para armazenar ponteiros para gráficos) de acordo com o número de instrumentos analisados. Calcule a largura e a altura de cada gráfico com base no tamanho do instrumento inteiro e no número de instrumentos analisados.

Depois disso, organize dois loops aninhados para iterar todos os instrumentos analisados ​​e crie a tabela usando os objetos gráficos. Crie os histogramas na interseção dos instrumentos de mesmo nome e os gráficos de dispersão em outros casos. Se todos os objetos forem criados com sucesso, o método é encerrado com 'true'.

   if(!CWndClient::Create(chart,name,subwin,x1,y1,x2,y2))
      return false;
//---
   if(ArraySize(m_arr_graphics)!=(i_total_symbols*i_total_symbols))
      if(ArrayResize(m_arr_graphics,i_total_symbols*i_total_symbols)<=0)
         return false;
   int width=Width()/i_total_symbols;
   int height=Height()/i_total_symbols;
   for(int i=0;i<i_total_symbols;i++)
     {
      CTimeserie *timeserie1=m_arr_symbols.At(i);
      if(timeserie1==NULL)
         continue;
      for(int j=0;j<i_total_symbols;j++)
        {
         string obj_name=m_name+"_"+(string)i+"_"+(string)j;
         int obj_x1=m_rect.left+j*width;
         int obj_x2=obj_x1+width;
         int obj_y1=m_rect.top+i*height;
         int obj_y2=obj_y1+height;
         if(i==j)
           {
            CHistogram *temp=new CHistogram();
            if(CheckPointer(temp)==POINTER_INVALID)
               return false;
            if(!temp.Create(m_chart_id,obj_name,m_subwin,obj_x1,obj_y1,obj_x2,obj_y2,e_orientation))
               return false;
            m_arr_graphics[i*i_total_symbols+j]=temp;
            temp.SetCells(cells);
           }
         else
           {
            CScatter *temp=new CScatter();
            if(CheckPointer(temp)==POINTER_INVALID)
               return false;
            if(!temp.Create(m_chart_id,obj_name,m_subwin,obj_x1,obj_y1,obj_x2,obj_y2))
               return false;
            CTimeserie *timeserie2=m_arr_symbols.At(j);
            if(timeserie2==NULL)
               continue;
            m_arr_graphics[i*i_total_symbols+j]=temp;
           }
        }
     }
//---
   return true;
  }

O método Refresh é usado para atualizar os dados da série temporal e exibir os dados no gráfico. O início do método apresenta o loop para atualizar os dados de todas as séries temporais. Observe que, quando você cria um gráfico de distribuição, as séries temporais são usadas em pares. Portanto, os dados devem ser comparáveis. Para garantir a compatibilidade dos dados, os dados do objeto gráfico não são atualizados e o método retorna 'false' caso ocorra um erro ao atualizar pelo menos uma das séries temporais.

Depois de atualizar os dados da série temporal, o loop é organizado para passar as séries temporais atualizadas para os objetos gráficos. Preste atenção em chamar o método Update de um objeto gráfico com o parâmetro 'false'. Essa chamada garante que o objeto gráfico seja atualizado sem atualizar o gráfico em que o aplicativo está sendo executado. Essa abordagem exclui as atualizações dos gráficos após atualizar cada objeto gráfico, reduzindo a carga no terminal e reduzindo o tempo de execução da função. O gráfico é atualizado uma vez após atualizar todos os elementos gráficos antes de sair da função.

bool CPairPlot::Refresh(void)
  {
   bool updated=true;
   for(int i=0;i<i_total_symbols;i++)
     {
      CTimeserie *timeserie=m_arr_symbols.At(i);
      if(timeserie==NULL)
         continue;
      updated=(updated && timeserie.UpdateTimeserie());
     }
   if(!updated)
      return false;
//---
   for(int i=0;i<i_total_symbols;i++)
     {
      CTimeserie *timeserie1=m_arr_symbols.At(i);
      if(CheckPointer(timeserie1)==POINTER_INVALID)
         continue;
      double ts1[];
      if(!timeserie1.GetTimeserie(ts1))
         continue;
//---
      for(int j=0;j<i_total_symbols;j++)
        {
         if(i==j)
           {
            CHistogram *temp=m_arr_graphics[i*i_total_symbols+j];
            if(CheckPointer(temp)==POINTER_INVALID)
               return false;
            if(temp.CurvesTotal()==0)
              {
               if(temp.AddTimeserie(ts1)<0)
                  continue;
              }
            else
              {
               if(!temp.UpdateTimeserie(ts1))
                  continue;
              }
            if(!temp.CurvePlotAll())
               continue;
            if(i==0)
               temp.TextUp(timeserie1.Symbol(),i_text_color);
            if(i==(i_total_symbols-1))
               temp.TextDown(timeserie1.Symbol(),i_text_color);
            if(j==0)
               temp.TextLeft(timeserie1.Symbol(),i_text_color);
            if(j==(i_total_symbols-1))
               temp.TextRight(timeserie1.Symbol(),i_text_color);
            temp.Update(false);
           }
         else
           {
            CScatter *temp=m_arr_graphics[i*i_total_symbols+j];
            if(CheckPointer(temp)==POINTER_INVALID)
               return false;
            CTimeserie *timeserie2=m_arr_symbols.At(j);
            if(CheckPointer(timeserie2)==POINTER_INVALID)
               continue;
            double ts2[];
            if(!timeserie2.GetTimeserie(ts2))
               continue;
            if(temp.CurvesTotal()==0)
              {
               if(temp.AddTimeseries(ts1,ts2)<0)
                  continue;
              }
            else
               if(!temp.UpdateTimeseries(ts1,ts2))
                  continue;
            if(!temp.CurvePlotAll())
               continue;
            if(i==0)
               temp.TextUp(timeserie2.Symbol(),i_text_color);
            if(i==(i_total_symbols-1))
               temp.TextDown(timeserie2.Symbol(),i_text_color);
            if(j==0)
               temp.TextLeft(timeserie1.Symbol(),i_text_color);
            if(j==(i_total_symbols-1))
               temp.TextRight(timeserie1.Symbol(),i_text_color);
            temp.Update(false);
           }
        }
     }
//---
   ChartRedraw(m_chart_id);
//---
   return true;
  }

Anteriormente, eu já mencionei que os elementos gráficos são baseados na classe CGraphic, não sendo derivado da classe CObject. Por esse motivo, nós adicionamos os métodos Shift, Hide e Show à classe base CPlotBase. Pela mesma razão, nós também temos que reescrever os métodos correspondentes na classe CPairPlot. O código completo de todas as classes e seus métodos está disponível no anexo.


4 Exemplo de uso da classe CPairPlot

Agora, depois de tanto trabalho, é hora de dar uma olhada nos resultados. Para demonstrar a ferramenta em ação, nós vamos fazer um indicador que exibe os gráficos de correlação, por exemplo, para as últimas 1000 velas, a cada nova barra.

Como já mencionado acima, a ferramenta é construída para uso em painéis gráficos. Portanto, vamos criar primeiro a classe CPairPlotDemo, derivada da classe CAppDialog. Detalhes sobre como trabalhar com a classe CAppDialog podem ser encontrados nos artigos [5, 6]. Aqui, eu vou apenas apontar as peculiaridades de usar a ferramenta.

Declare a instância da classe CPairPlot no bloco "private". No bloco 'public', declare o método Create com todos os parâmetros de entrada necessários para a inicialização e operação da ferramenta. Aqui, nós também declararemos os métodos Refresh e HistogramOrientation, que chamarão os métodos correspondentes da nossa ferramenta.

class CPairPlotDemo : public CAppDialog
  {
private:
   CPairPlot         m_PairPlot;
public:
                     CPairPlotDemo();
                    ~CPairPlotDemo();
//---
   bool              Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2,const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10);
   bool              Refresh(void);
//---
   bool              HistogramOrientation(ENUM_HISTOGRAM_ORIENTATION value)   {  return m_PairPlot.HistogramOrientation(value);   }
   ENUM_HISTOGRAM_ORIENTATION    HistogramOrientation(void)                   {  return m_PairPlot.HistogramOrientation();   }
   };

No método Create, primeiro chame o método apropriado da classe pai, chame o mesmo método da instância do elemento e adicione o ponteiro à ocorrência da classe CPairPlot à coleção de elementos de controle.

bool CPairPlotDemo::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2,const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10)
  {
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return false;
   if(!m_PairPlot.Create(m_chart_id,m_name+"PairPlot",m_subwin,0,0,ClientAreaWidth(),ClientAreaHeight(),symbols,timeframe,bars,cells))
      return false;
   if(!Add(m_PairPlot))
      return false;
//---
   return true;
  }

Agora vamos criar o indicador. A string com os nomes separados por vírgulas dos instrumentos utilizados, a profundidade do histórico analisado em barras, o número de colunas e a orientação do histograma devem ser usados ​​como parâmetros de entrada do nosso indicador.

input string   i_Symbols   =  "EURUSD, GBPUSD, EURGBP";
input uint     i_Bars      =  1000;
input uint     i_Cells     =  50;
input ENUM_HISTOGRAM_ORIENTATION i_HistogramOrientation  =  HISTOGRAM_HORIZONTAL;

Declare a instância da classe CPairPlotDemo nas variáveis ​​globais.

CPairPlotDemo     *PairPlot;

Na função OnInit, crie o array de instrumentos aplicados a partir da string dos parâmetros externos do indicador. Em seguida, crie uma instância da classe CPairPlotDemo, passe a orientação do histograma especificada para ela e chame o seu método Create. Após a inicialização bem-sucedida, inicie a execução da classe pelo método Run e atualize os dados usando o método Refresh.

int OnInit()
  {
//---
   string symbols[];
   int total=StringSplit(i_Symbols,',',symbols);
   if(total<=0)
      return INIT_FAILED;
   for(int i=0;i<total;i++)
     {
      StringTrimLeft(symbols[i]);
      StringTrimRight(symbols[i]);
     }
//---
   PairPlot=new CPairPlotDemo;
   if(CheckPointer(PairPlot)==POINTER_INVALID)
      return INIT_FAILED;
//---
   if(!PairPlot.HistogramOrientation(i_HistogramOrientation))
      return INIT_FAILED;
   if(!PairPlot.Create(0,"Pair Plot",0,20,20,620,520,symbols,PERIOD_CURRENT,i_Bars,i_Cells))
      return INIT_FAILED;
   if(!PairPlot.Run())
      return INIT_FAILED;
   PairPlot.Refresh();
//---
   return INIT_SUCCEEDED;
  }

Na função OnCalculate, chame o método Refresh a cada nova barra. Os métodos da classe apropriados serão chamados a partir das funções OnChartEvent e OnDeinit.

Encontre o código inteiro de todas as funções e classes no anexo.

Abaixo você pode ver como o indicador funciona.

Operação PairPlot


Conclusão

Neste artigo, nós oferecemos uma ferramenta bastante interessante. Ela permite que os traders visualizem com rapidez e facilidade a presença de uma correlação entre quase todos os instrumentos de negociação. O principal objetivo da ferramenta está na análise dos instrumentos de negociação e no desenvolvimento de várias estratégias de arbitragem. É claro que essa ferramenta sozinha é insuficiente para desenvolver um sistema de negociação completo, mas ele pode facilitar enormemente o primeiro estágio do desenvolvimento de um sistema de negociação - a busca por instrumentos correlacionados e suas dependências.


Referências

  1. Trabalhando com cestas de moedas no mercado Forex
  2. Teste de padrões que surgem ao negociar cestas de pares de moedas. Parte I
  3. Teste de padrões que surgem ao negociar cestas de pares de moedas. Part II
  4. Visualize isso! Biblioteca gráfica em MQL5 semelhante à 'plot' da linguagem R
  5. Como criar um painel gráfico de qualquer nível de complexidade
  6. Melhoramos o trablaho com painéis, adicionando transparência, alterando a cor do plano de fundo e herdando da CAppDialog/CWndClient


Programas usados ​​no artigo

#
 Nome
Tipo 
Descrição 
1  PlotBase.mqh  Biblioteca da classe  Classe base para a construção de gráficos
2  Scatter.mqh  Biblioteca da classe  Classe para a construção de gráficos de dispersão
3  Histogram.mqh  Biblioteca da classe  Classe para a construção de histogramas
4  PairPlot.mqh  Biblioteca da classe  Classe da ferramenta PairPlot
5  PairPlotDemo.mqh  Biblioteca da classe  Classe para a demonstração da conexão da ferramenta
6  PairPlot.mq5  Indicador  Indicador demonstrando o trabalho da ferramenta

Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/4820

Arquivos anexados |
MQL5.zip (377.1 KB)
Escrita de indicadores de bolsa com controle de volume usando o indicador delta como exemplo Escrita de indicadores de bolsa com controle de volume usando o indicador delta como exemplo

Este artigo descreve um algoritmo para construir indicadores de bolsa com base em volumes reais usando as funções CopyTicks() e CopyTicksRange(). Também apresenta as particularidades de construção desses indicadores, bem como seus aspetos de funcionamento tanto em tempo real quanto no testador de estratégias.

Redes Neurais Profundas (Parte VIII). Melhorando a qualidade de classificação dos bagging de ensembles Redes Neurais Profundas (Parte VIII). Melhorando a qualidade de classificação dos bagging de ensembles

O artigo considera três métodos que podem ser usados ​​para aumentar a qualidade de classificação do bagging de ensembles, e a estimação de sua eficiência. Os efeitos da otimização dos hiperparâmetros da rede neural ELM e dos parâmetros de pós-processamento são avaliados.

Combinando uma estratégia de tendência com outra de fase de correção Combinando uma estratégia de tendência com outra de fase de correção

Existem diversas estratégias de negociação - algumas procuram movimentos direcionais e operam com a tendência, já outras identificam faixas de preço e negociam dentro desses corredores. Neste ponto, surge a pergunta: é possível combinar as duas abordagens para aumentar a rentabilidade da negociação?

Raios Elder (Bulls Power e Bears Power) Raios Elder (Bulls Power e Bears Power)

Sistema de negociação Raios Elder (em inglês, 'Elder-ray') baseado nos indicadores Bulls Power, Bears Power e Moving Average (EMA — MME, média móvel exponencial). Este sistema foi descrito por Alexander Elder em seu livro "Como se transformar em um operador e investidor de sucesso" (na versão original em inglês, 'Trading for a Living').