Indicador para Gráfico de Spindles

Dmitriy Zabudskiy | 23 fevereiro, 2016

Introdução

O gráfico de spindles pertence aos gráficos chamados de Volume pelo Preço. Volume pelo Preço é um gráfico plotado com os dados de atividade de um símbolo na forma do volume (geralmente volume tick) de um ou vários preços. Algo como o perfil do mercado aparecerá. Navegar na Internet não trouxe muita informação útil, principalmente sobre os aspectos que compõem o gráfico. Assim, podemos dizer que o gráfico surgiu relativamente há pouco tempo e, portanto, merece atenção.

Eu recebi as informações sobre o gráfico a partir de um dos leitores, que pediu para criar um indicador desse tipo. Considerando a falta de tempo livre e a complexidade de implementação, o desenvolvimento do indicador foi protelado.

O gráfico de Spindles parece com o gráfico de velas japonesas: abertura, fechamento, mínima e máxima estão presentes nele. São usados o Volume Weighted Moving Average (VWMA) e o Volume Ratio (VR), formando assim algo parecido com um fuso (spindle) - Fig 1.

Fig. 1. Comparação da vela japonesa com o spindle

Fig. 1. Comparação da vela japonesa com o spindle

Como podemos ver na Figura 1, os dois parâmetros adicionados (VWMA — Volume Weighted Moving Average e o VR — Volume Ratio) meramente complementam o gráfico, formando uma nova forma que se parece com um carrosel de parque de diversão. Este é o chamado "spindle".

Formação do VWMA e VR:

O Volume Weighted Moving Average (VWMA) é uma variante da média móvel, é calculado utilizando a fórmula (1)

Cálculo do (VWMA)

Onde P — preço, V — volume. E soa mais ou menos assim, "Volume Weighted Moving Average é igual à soma de todas as multiplicações do preço pelo volume do referido período, dividido pela soma dos volumes do mesmo período".

Volume Ratio (VR) também é um tipo de média móvel, mas é exibido de forma diferente no gráfico, em primeiro lugar porque não têm uma valor de faixa de preços, e em segundo lugar porque é responsável pela atividade do mercado em relação aos períodos anteriores, por isso é melhor visualizado tanto em gráfico separado, como no volumes de ticks ou como a largura de cada spindle. É calculado pela fórmula (2).

Cálculo do VR

onde V - volume. Seria algo como: "Volume Ratio é igual ao volume em vigor (atual) dividido pela média aritmética dos volumes do período selecionado."

Assim, depois de todas estas manipulações temos um gráfico de "spindle" (Fig. 2).

Fig. 2. Gráfico de spindles

Fig. 2. Gráfico de spindles

É natural perguntar: "por que os spindles da Figura 2 não são preenchidos com cor igual a Figura 1"? Esta questão será revelada no próximo capítulo - Fundamentos de plotagem.


Fundamentos de plotagem

São sete estilos de plotagem para um indicador personalizado em MQL5 (linha, seção, histograma, seta, área preenchida, barras e velas japonesas), mas nenhum deles cumpre integralmente os requisitos para traçar o gráfico de spindles. Isso significa que é necessário um estilo personalizado. Infelizmente não há nenhum estilo de design construído, mas há uma abundância de outras funções que podem ser usadas para criar o seu próprio estilo que difere principalmente a partir das versões de funcionalidade, velocidade e qualidade de renderização.

Foi decidido organizar a formação do estilo com a ajuda de um objeto "Bitmap". As desvantagens de tal decisão são, em primeiro lugar, a utilização da memória e da complexidade relativa de plotagem, o que leva a outros inconvenientes: velocidade e estabilidade. A vantagem do objeto "Bitmap" em comparação com outros objetos, é uma possibilidade de restringir a plotagem a um espaço específico, bem como a possibilidade de utilizar a transparência e a utilizar uma parte do objeto. Estas são as vantagens necessárias para organizar a plotagem do gráfico de spindles.

Uma representação visual de um "spindle" é mostrado na Figura 1, o corpo do "Spindle" é completamente preenchido. Esta plotagem é bastante complexa e exige a implementação via objeto "Bitmap". O processo está representado na Figura 3:

Fig. 3. Representação técnica de plotagem de um "spindle" com a ajuda do objeto "Bitmap"

Fig. 3. Representação técnica de plotagem de um "spindle" com a ajuda do objeto "Bitmap"

A Figura 3 demonstra três variantes possíveis da representação técnica do "spindle preenchido". Onde:

Assim, para plotar o primeiro spindle (Fig. 3, a), chamamos de "losango gordinho", são necessários quatro objetos tipo "Bitmap" (Fig 3, a; partes: I, II, III, IV). Dependendo do tipo de losango, como sua largura e altura (ou seja, Open, Close, VWMA e VR), as imagens precisam ser escolhidas em 3 diferentes ângulos, a; ângulos: x1, x2, x3, x4). Imagem é um bitmap quadrado com o formato BMP, em que um feixe sai de um canto num ângulo específico em relação a um dos lados mais próximos e divide o quadrado em duas áreas: cheias e transparentes.

O cálculo de uma imagem específica é discutido abaixo. No entanto, já está claro que a plotagem deste modelo em diferentes valores de sua largura e altura (ou seja: Open, Close, VWMA e VR) vai exigir 360 bitmaps (baseado na precisão da plotagem de um grau) para uma única cor e 720 bitmaps para duas cores.

É muito mais complicado com o segundo spindle (Fig. 3, b), apesar do fato de que a plotagem da forma (chamada de "seta") consiste em duas partes. Há muito mais combinações de ângulo aqui, pois é necessário considerar a distância entre Open e Close dos preços. Além disso a plotagem pode ser ignorada na presença de uma outra alternativa (Fig.3, c).

No terceiro caso (figura 3, c), a plotagem é implementada em quatro partes, as duas primeiras (I e II) são as mesmas que no "losango gordinho", enquanto as duas últimas (III e IV) cobrem as partes redundantes a partir do primeiro. Tal aplicação tem uma possibilidade de sobreposição de spindles adjacentes, e também uma ligação para o fundo. No total, existem 180 partes, como no "losango gordinho" e 180 partes na cobertura.

Em geral, a implementação de um gráfico de spindles vai exigir 900 bitmaps, tendo apenas um único gráfico na conta, que por sua vez é um recurso intensivo.

Agora considere os "spindles" menos complexos e uma versão mais rápida do traçado não preenchido (Fig. 2). Saindo na frente, o número de bitmaps é de 360 (180 de uma cor e 180 da outra), independente de fundo do gráfico.

Fig. 4. Representação técnica da plotagem de um "spindle vazio" com a ajuda de um objeto "Bitmap".

Fig. 4. Representação técnica da plotagem de um "spindle vazio" com a ajuda de um objeto "Bitmap".

Assim como na variante anterior, um "spindle vazio" é plotado a partir de quatro imagens, que representam linhas coloridas em ângulos diferentes (0 a 180). Não há necessidade de plotar 360 bitmaps, pois o ângulo varia dependendo do ponto de ancoragem do objeto. Existem apenas dois pontos de ancoragem (Fig 4, p1 e p2): dois objetos para um ponto, dois para o outro.

Deixe-me explicar outra vez porque são usados menos bitmaps aqui. Imagine que há um losango simétrico na Figura 4 (a), então a parte I poderia ser substituída pela parte IV. Para fazer isso seria necessário alterar o ponto de ancoragem do canto superior direito para o canto inferior esquerdo do objeto. Como resultado, somente é necessário preparar um total de 180 objetos da mesma cor e alterar o ponto de ancoragem, dependendo do uso.

Agora um pouco de matemática, geometria, para ser exato. Considere o processo de cálculo e seleção de uma imagem para plotar um "spindle vazio" (Fig. 5 e 6).

Fig. 5. Cálculo matemático do "losango gordinho"

Fig. 5. Cálculo matemático do "losango gordinho"

A Figura 5 mostra o já familiar "losango gordinho" (a) e o seu lado direito (b). Todas as distâncias marcadas ( a, b, c, d ) são fáceis de calcular quando Open, Close, VWMA e VR são conhecidos, ou seja:

Conhecendo os lados a, b, d, , é possível calcular a hipotenusa nos triângulos retângulos e & f utilizando as fórmulas 3.1 e 3.3. Assim, sabendo que num triângulo retângulo, um cateto dividido por hipotenusa é igual ao seno do ângulo oposto, calculamos os senos dos ângulos x1 e x2 usando fórmulas 3.2 e 3.4. Em seguida, usando uma tabela ou uma calculadora, achamos os ângulos x1 e x2 e então calculamos x3 até x2. O mesmo sistema é utilizado para desenhar a forma "seta"

Fig. 6. Cálculo matemático da "seta"

Fig. 6. Cálculo matemático da "seta"

Após as noções básicas de plotagem, analisaremos o código do indicador.


O código do indicador

Antes de escrever o código, era necessário preparar os recursos gráficos do indicador: tamanho 540 х 540 pixels BMP, formato bitmaps com fundos transparentes. Os bitmaps contém um feixe que se prolonga a partir de um canto. Os primeiros 89 bitmaps do feixe estendem-se a partir do canto superior esquerdo, o ângulo varia de 1 a 89 graus, no segundo 89 bitmaps do feixe, eles estendem-se a partir do canto inferior esquerdo, o ângulo varia de 91 a 179 graus (de 1 a 89 graus em relação ao horizontal). Os bitmaps com ângulos de 0, 90, 180 têm tamanhos de 1 x 540 pixels e 540 pixels х 1, respectivamente, e não requerem um fundo transparente.

No total, são 362 bitmaps - 181 bitmaps de uma cor e 181 bitmaps de outra (bitmaps 1 e 181 são os mesmos). Os nomes de arquivos foram escolhidos em função das cores da linha em inglês (vermelhos - primeira letra "r", e azul - primeiro letra "b") e o ângulo que eles estão posicionados.


Parte Um

A Parte Um do código refere-se a função OnInit . Considere todas as etapas

//+------------------------------------------------------------------+
//|                                                          SPC.mq5 |
//|                                   Azotskiy Aktiniy ICQ:695710750 |
//|                          https://login.mql5.com/ru/users/aktiniy |
//+------------------------------------------------------------------+
#property copyright "Azotskiy Aktiniy ICQ:695710750"
#property link      "https://login.mql5.com/ru/users/aktiniy"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 11
#property indicator_plots 4
//---
#property indicator_label1  "Shadow"
#property indicator_type1   DRAW_COLOR_HISTOGRAM2
#property indicator_color1  clrRed,clrBlue,clrGray
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//---
#property indicator_label2  "Open"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//---
#property indicator_label3  "Close"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrBlue
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1
//---
#property indicator_label4  "VWMA"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrMagenta
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1
//--- recursos para carregamento de arquivos
#resource "\\Images\\for_SPC\\b0.bmp";
#resource "\\Images\\for_SPC\\b1.bmp";
#resource "\\Images\\for_SPC\\b2.bmp";
#resource "\\Images\\for_SPC\\b3.bmp";
//...
//...
//...
#resource "\\Images\\for_SPC\\b176.bmp";
#resource "\\Images\\for_SPC\\b177.bmp";
#resource "\\Images\\for_SPC\\b178.bmp";
#resource "\\Images\\for_SPC\\b179.bmp";
#resource "\\Images\\for_SPC\\b180.bmp";
#resource "\\Images\\for_SPC\\r0.bmp";
#resource "\\Images\\for_SPC\\r1.bmp";
#resource "\\Images\\for_SPC\\r2.bmp";
#resource "\\Images\\for_SPC\\r3.bmp";
//...
//...
//...
#resource "\\Images\\for_SPC\\r176.bmp";
#resource "\\Images\\for_SPC\\r177.bmp";
#resource "\\Images\\for_SPC\\r178.bmp";
#resource "\\Images\\for_SPC\\r179.bmp";
#resource "\\Images\\for_SPC\\r180.bmp";
//+------------------------------------------------------------------+
//| Tipo de Desenho                                                  |
//+------------------------------------------------------------------+
enum type_drawing
  {
   spindles=0,       // Spindles
   line_histogram=1, // Linha e histograma
  };
//+------------------------------------------------------------------+
//| Tipo de Preço                                                    |
//+------------------------------------------------------------------+
enum type_price
  {
   open=0,   // Abertura
   high=1,   // Máxima
   low=2,    // Mínima
   close=3,  // Fechamento
   middle=4, // Meio
  };
//--- parâmetros de entrada
input long         magic_numb=65758473787389; // Número Mágico
input type_drawing type_draw=0;               // Tipo de desenho do indicador
input int          period_VR=10;              // Período de formação do Volume Ratio
input int          correct_VR=4;              // Número de Correção do Volume Ratio 
input int          period_VWMA=10;            // Período de formaçao do Volume Weighted Moving Average
input int          spindles_num=1000;         // Número de spindles
input type_price   type_price_VWMA=0;         // Tipo de preço para plotar o Volume Weighted Moving Average
                                              // open=0; high=1; low=2; close=3; middle=4
//--- variáveis de saída
int ext_period_VR=0;
int ext_correct_VR;
int ext_period_VWMA=0;
int ext_spin_num=0;
int long_period=0;
//--- variáveis de parâmetros do gráfico
double win_price_max_ext=0; // Valor máximo do gráfico
double win_price_min_ext=0; // Valor mínimo do gráfico
double win_height_pixels_ext=0; // altura em pixels
double win_width_pixels_ext=0;  // largura em pixels
double win_bars_ext=0; // largura em barras
//--- Variáveis Auxiliares
int end_bar;
//--- Buffers do Indicador
double         Buff_up[];   // Buffer dos pontos superiores do histograma
double         Buff_down[]; // Buffer dos pontos inferiores do histograma
double         Buff_color_up_down[]; // Buffer da cor do histograma
double         Buff_open_ext[];  // Buffer de saída da Abertura do preço
double         Buff_close_ext[]; // Buffer de saída do Fechamento do preço
double         Buff_VWMA_ext[];  // Buffer de saída do Volume Weighted Moving Average
double         Buff_open[];  // Buffer da Abertura do preço
double         Buff_close[]; // Buffer do Fechamento do preço
double         Buff_VWMA[];  // Buffer do Volume Weighted Moving Average 
double         Buff_VR[];   // Buffer do Volume Ratio 
double         Buff_time[]; // Buffer de tempo da abertura da barra

Você pode ver que há apenas alguns parâmetros de entrada:

Variáveis de saída são necessárias para validação e ajuste dos parâmetros de entrada. Variáveis de parâmetro do gráfico acompanham mudanças de janela do indicador. Consulte a próxima seção para obter mais detalhes.

A declaração dos buffers de indicador conclui a primeira parte. Existem 11 buffers aqui.


Função OnInit

//+------------------------------------------------------------------+
//| Função de inicialização do indicador personalizado               |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- verifica as variáveis de entrada
   if(period_VR<=0)
     {
      ext_period_VR=10; // altera o valor da variável
      Alert("Período de formação do Volume Ratio  foi introduzido  incorretamente e foi alterado.");
     }
   else ext_period_VR=period_VR;
   if(correct_VR<=0)
     {
      ext_correct_VR=10; // altera o valor da variável
      Alert("Número de correção do Volume Ratio foi introduzido  incorretamente e foi alterado.");
     }
   else ext_correct_VR=correct_VR;
   if(period_VWMA<=0)
     {
      ext_period_VWMA=10; // altera o valor da variável
      Alert("Período de formação do Volume Weighted Moving Average foi introduzido incorretamente e foi alterado.");
     }
   else ext_period_VWMA=period_VWMA;
   if(spindles_num<=0)
     {
      ext_spin_num=10; // altera o valor da variável
      Alert("Número de spindles foi introduzido  incorretamente e foi alterado.");
     }
   else ext_spin_num=spindles_num;
//--- procurar o maior período para desenhar o gráfico
   if(ext_period_VR>ext_period_VWMA)long_period=ext_period_VR;
   else long_period=ext_period_VWMA;
//--- mapeamento buffers do  indicador
   SetIndexBuffer(0,Buff_up,INDICATOR_DATA);
   SetIndexBuffer(1,Buff_down,INDICATOR_DATA);
   SetIndexBuffer(2,Buff_color_up_down,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(3,Buff_open_ext,INDICATOR_DATA);
   SetIndexBuffer(4,Buff_close_ext,INDICATOR_DATA);
   SetIndexBuffer(5,Buff_VWMA_ext,INDICATOR_DATA);
   SetIndexBuffer(6,Buff_open,INDICATOR_CALCULATIONS);
   SetIndexBuffer(7,Buff_close,INDICATOR_CALCULATIONS);
   SetIndexBuffer(8,Buff_VWMA,INDICATOR_CALCULATIONS);
   SetIndexBuffer(9,Buff_VR,INDICATOR_CALCULATIONS);
   SetIndexBuffer(10,Buff_time,INDICATOR_CALCULATIONS);
//--- definir o nome do indicador
   IndicatorSetString(INDICATOR_SHORTNAME,"SPC "+IntegerToString(magic_numb));
   PlotIndexSetString(0,PLOT_LABEL,"SPC");
//--- definir a precisão
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);
//--- definir a primeira barra a partir do qual iniciará o desenho do indicador

   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,long_period+1);
//--- proibir a exibição dos resultados dos valores correntes do indicador
   PlotIndexSetInteger(0,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(1,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(2,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(3,PLOT_SHOW_DATA,false);
//--- definir os valores que não serão exibidos
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);
   PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,0);
   PlotIndexSetDouble(3,PLOT_EMPTY_VALUE,0);
//--- criar objetos para uso
   if(type_draw==0)
     {
      for(int x=0; x<=ext_spin_num; x++)
        {
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"1",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"2",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"3",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"4",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
        }
     }
//---
   return(INIT_SUCCEEDED);
  }

Aqui nós checamos se as entradas dos parâmetros estão corretas, fazemos a correção usando as variáveis declaradas anteriormente (variáveis de saída), se necessário. Descubra qual dos períodos utilizados anteriores é maior, inicie os buffers e configure a aparência do indicador. Crie objetos gráficos para trabalhar com um array pequeno (limitado pelo parâmetro "número de spindles").


Função OnChartEvent

//+------------------------------------------------------------------+
//| Função ChartEvent                                                |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- evento keypress 
   if(id==CHARTEVENT_KEYDOWN)
     {
      if(lparam==82)
        {
         if(ChartGetDouble(0,CHART_PRICE_MAX,ChartWindowFind())>0)// verificar a presença de dados no gráfico
           {
            if(func_check_chart()==true)
              {
               if(type_draw==0)func_drawing(true,ext_spin_num,end_bar);
              }
           }
        }
     }
  }

Esta função vincula a tecla "R" (código 82) para atualizar (ou melhor, para redesenhar) o gráfico. Serve para ajustar (redesenhar) o gráfico, caso altere o tamanho da janela dos indicadores. Se deve ao fato de que as imagens se alongam quando se altera o tamanho da janela. Naturalmente o gráfico é redesenhado quando ocorre evento de alteração de preço, mas às vezes é necessário atualizar rapidamente a plotagem. Esta função serve a este propósito.

A função em si consiste de operadores inteiramente condicionais if-else e inclui uma função que verifica as alterações nas dimensões da janela do indicador (func_check_chart ), bem como uma função de desenho gráfico (func_drawing).


Função de verificação da janela do indicador

//+------------------------------------------------------------------+
//| Função de verificação do indicador                               |
//+------------------------------------------------------------------+
bool func_check_chart()
  {
//--- variável de resposta
   bool x=false;
//--- descobre o tamanho do gráfico
   int win=ChartWindowFind(); // define sub janela, pois o indicador funciona numa janela separada
   double win_price_max=ChartGetDouble(0,CHART_PRICE_MAX,win); // valor máximo do gráfico
   double win_price_min=ChartGetDouble(0,CHART_PRICE_MIN,win); // valor mínimo do gráfico
   double win_height_pixels=(double)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,win); // altura em pixels
   double win_width_pixels=(double)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,win); // largura em pixels
   double win_bars=(double)ChartGetInteger(0,CHART_WIDTH_IN_BARS,win); // largura em barras

//--- verifica se os valores mudaram
   int factor=(int)MathPow(10,_Digits);// define o tipo double para fator de conversão do tipo int
   if(int(win_price_max*factor)!=int(win_price_max_ext*factor))
     {
      win_price_max_ext=win_price_max;
      x=true;
     }
   if(int(win_price_min*factor)!=int(win_price_min_ext*factor))
     {
      win_price_min_ext=win_price_min;
      x=true;
     }
   if(int(win_height_pixels*factor)!=int(win_height_pixels_ext*factor))
     {
      win_height_pixels_ext=win_height_pixels;
      x=true;
     }
   if(int(win_width_pixels*factor)!=int(win_width_pixels_ext*factor))
     {
      win_width_pixels_ext=win_width_pixels;
      x=true;
     }
   if(int(win_bars*factor)!=int(win_bars_ext*factor))
     {
      win_bars_ext=win_bars;
      x=true;
     }
   if(func_new_bar(PERIOD_CURRENT)==true)
     {
      x=true;
     }
   return(x);
  }

Esta função é usada como um sinal para alterar o tamanho da janela do indicador. Primeiro descubra os parâmetros atuais da janela (altura e largura em preço e pixels) usando as funções de gráfico (ChartGetInteger e ChartGetDouble) e comparar com os valores das variáveis globais que foram previamente declaradas (variáveis de parâmetros do gráfico).


Função de controle da plotagem do gráfico

//+------------------------------------------------------------------+
//| Função de Desenho                                                |
//+------------------------------------------------------------------+
void func_drawing(bool type_action,// tipo de ação modificadora: 0-dois últimos, 1-tudo
                  int num,         // quantidade de spindles realizados
                  int end_bar_now) // última barra corrente
  {
   int begin;
   if(end_bar_now>num)begin=end_bar_now-num;
   else begin=long_period+1;
//--- encontrar o valor máximo de VR  
   double VR_max=0;
   for(int x=begin; x<end_bar_now-1; x++)
     {
      if(Buff_VR[x]<Buff_VR[x+1])VR_max=Buff_VR[x+1];
      else VR_max=Buff_VR[x];
     }
//--- cálculo da escala
   double scale_height=win_height_pixels_ext/(win_price_max_ext-win_price_min_ext);
   double scale_width=win_width_pixels_ext/win_bars_ext;
//--- plotagem (x - parte do nome do objeto, y - plotagem do índice array de dados)
   if(type_action==false)//   false - atualização últimos dois spindles
     {
      for(int x=num-2,y=end_bar_now-2; y<end_bar_now; y++,x++)
        {
         func_picture("SPC"+IntegerToString(magic_numb)+IntegerToString(x),Buff_open[y],Buff_close[y],datetime(Buff_time[y]),Buff_VR[y],VR_max,Buff_VWMA[y],ext_correct_VR,scale_height,scale_width);
        }
     }
//---
   if(type_action==true)//   false - atualização últimos dois spindles
     {
      for(int x=0,y=begin; y<end_bar_now; y++,x++)
        {
         func_picture("SPC"+IntegerToString(magic_numb)+IntegerToString(x),Buff_open[y],Buff_close[y],datetime(Buff_time[y]),Buff_VR[y],VR_max,Buff_VWMA[y],ext_correct_VR,scale_height,scale_width);
        }
     }
   ChartRedraw();
  }

A função é uma estrutura de controle da plotagem do gráfico. Os parâmetros de entrada são: parâmetro modificador (seja os últimos dois ou todos de uma vez), a quantidade total de spindles e o último spindle. O parâmetro modificador está aqui para mudar apenas o último spindle no caso do preço alterar apenas ele mesmo, assim garante que os outros spindles são deixados intactos, tem o objetivo de aumentar o desempenho do indicador.

Em seguida a barra começa a ser calculada. Se houver menos informações sobre as barras do que o número de spindles, então a plotagem inicia com o maior período de formação no gráfico.

Depois encontra o maior Volume Ratio (necessário como um parâmetro a ser trasnferido pela função func_picture discutida abaixo) e calcula a escala para desenhar o gráfico. Dependendo do parâmetro modificador, um ciclo de alteração de spindles é chamado (objetos gráficos previamente criados com a ajuda da função func_picture ).


Função de plotagem gráfica

//+------------------------------------------------------------------+
//| Função Imagem                                                    |
//+------------------------------------------------------------------+
void func_picture(string name,        // nome do objeto
                  double open,        // preço de Abertura da barra
                  double close,       // preço de Fechamento da barra
                  datetime time,      // tempo da barra
                  double VR,          // valor do Volume Ratio
                  double VR_maximum,  // valor máximo do Volume Ratio
                  double VWMA,        // valor do Volume Weighted Moving Average
                  int correct,        // parâmetro de correção do Volume Ratio 
                  double scale_height,// escala de altura (pixels/preço)
                  double scale_width) // escala de largura (pixels/barras)
  {
   string first_name;// a primeiro letra do nome do arquivo usado na plotagem

   string second_name_right;// o resto do nome do arquivo  usado na plotagem do lado direito
   string second_name_left; // o resto do nome do arquivo  usado na plotagem do lado direito
   double cathetus_a;// cateto a
   double cathetus_b;// cateto b
   double hypotenuse;// hipotenusa 
   int corner;// canto
//--- 
   cathetus_b=int(VR/VR_maximum/correct*scale_width);// largura em pixels
                                                     //540 imagens
   if(open<=close) first_name="r";// barra de alta ou Doji 
   if(open>close) first_name="b"; // barra de baixa
//---
   if(open<VWMA)// VWMA está acima do preço de abertura
     {
      cathetus_a=int((VWMA-open)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int(180-(MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_LEFT_LOWER,time,open);
      func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_RIGHT_LOWER,time,open);
     }
   if(open>VWMA)// VWMA está abaixo do preço de abertura
     {
      cathetus_a=int((open-VWMA)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int((MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_LEFT_UPPER,time,open);
      func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_RIGHT_UPPER,time,open);
     }
   if(open==VWMA)// VWMA está no nível do preço de Abertura
     {
      func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_LEFT,time,open);
      func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_RIGHT,time,open);
     }
   if(close<VWMA)// VWMA está abaixo do preço de fechamento
     {
      cathetus_a=int((VWMA-close)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int(180-(MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_LEFT_LOWER,time,close);
      func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_RIGHT_LOWER,time,close);
     }
   if(close>VWMA)// VWMA está abaixo do preço de fechamento
     {
      cathetus_a=int((close-VWMA)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int((MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_LEFT_UPPER,time,close);
      func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_RIGHT_UPPER,time,close);
     }
   if(close==VWMA)// VWMA está no nível do fechamento do preço
     {
      func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_LEFT,time,close);
      func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_RIGHT,time,close);
     }
  }

O "coração" do gráfico de plotagem — o cálculo de objetos gráficos bitmap e a função de substituição. É nesta função que o bitmap (ou seja, quatro bitmaps) utilizado numa barra é calculado de modo a plotar o spindle. Em seguida, com a ajuda da função func_obj_mod o bitmap do objeto gráfico é alterado (todos os objetos gráficos foram criados no final da função OnInit, no início do código).

Parâmetros da barra atual modificada são trasferidos para a função, entre eles está o Volume Ratio máximo, mencionado anteriormente, que serve como um parâmetro relativo para a chamada do cálculo de cateto b (Fig 5, b; marcado como o tamanho d).

Próximo passo, as variáveis auxiliares são adicionados (o primeiro caractere é a cor, o resto da esquerda até a direita são — o ângulo no nome do arquivo, cateto a & b, hipotenusa e o ângulo), a cor do spindle é definida por um operador condicional "if". Dependendo dos níveis da abertura e fechamento de preço relativo ao Volume Weighted Moving Average (WVMA) e com base nas fórmulas conhecidas nas figuras 5 e 6, um cálculo do bitmap (quatro bitmaps) ocorre, bem como uma modificação do objeto gráfico com a ajuda da função func_obj_mod.


Função de modificação de objetos

//+------------------------------------------------------------------+
//| Função de modificação dos objetos                                |
//+------------------------------------------------------------------+
void func_obj_mod(string name,             // nome do objeto
                  string file,             // caminho para encontrar o arquivo 
                  int pix_x_b,             // visisbilidade em X
                  int pix_y_a,             // visisbilidade em Y
                  int shift_y,             // deslocamento em
                  ENUM_ANCHOR_POINT anchor,// ponto de ancoragem
                  datetime time,           // coordenada de tempo
                  double price)            // coordenada de preço
  {
   ObjectSetString(0,name,OBJPROP_BMPFILE,file);
   ObjectSetInteger(0,name,OBJPROP_XSIZE,pix_x_b);// visisbilidade em X
   ObjectSetInteger(0,name,OBJPROP_YSIZE,pix_y_a);// visisbilidade em Y
   ObjectSetInteger(0,name,OBJPROP_XOFFSET,0);// sem deslocamento no eixo X
   ObjectSetInteger(0,name,OBJPROP_YOFFSET,shift_y);// definir deslocamento no eixo Y
   ObjectSetInteger(0,name,OBJPROP_BACK,false);// primeiro plano de exibição
   ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);// desabilitar modo de arrastar 
   ObjectSetInteger(0,name,OBJPROP_SELECTED,false);
   ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);// ocultar o nome do objeto gráfico
   ObjectSetInteger(0,name,OBJPROP_ANCHOR,anchor);// definir ponto de ancoragem 
   ObjectSetInteger(0,name,OBJPROP_TIME,time);// definir coordenada de tempo
   ObjectSetDouble(0,name,OBJPROP_PRICE,price);// definir coordenada de preço
  }

A função e muito simples, ela substitui os valores transferidos dentro da função de mudança da propriedade do objeto. As principais propriedades mutáveis são o bitmap de objeto, visibilidade e o ponto de ancoragem.


Função OnCalculate

//+------------------------------------------------------------------+
//| Função de iteração do indicador personalizado                    |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- verificar a disponibilidade do período histórico
   if(rates_total<long_period)
     {
      Alert("período  VR ou VWMA é maior do que os dados histórico ou os dados históricos não são carregados.");
      return(0);
     }
//--- pesquisa de posição
   int position=prev_calculated-1;
   if(position<long_period)position=long_period; // Mudança de posição
//--- ciclo principal de cálculo de buffer 
   for(int i=position; i<rates_total; i++)
     {
      //--- preencher buffers de histograma
      Buff_up[i]=high[i];
      Buff_down[i]=low[i];
      if(open[i]<close[i])Buff_color_up_down[i]=0;//barra de alta
      if(open[i]>close[i])Buff_color_up_down[i]=1;// barra de baixa
      if(open[i]==close[i])Buff_color_up_down[i]=2;// barra Doji 
      //--- preenchimento de buffers auxiliares
      Buff_open[i]=open[i];
      Buff_close[i]=close[i];
      Buff_time[i]=double(time[i]);
      //--- calcular Volume Ratio
      double mid_vol=0;
      int x=0;
      for(x=i-ext_period_VR; x<=i; x++)
        {
         mid_vol+=double(tick_volume[x]);
        }
      mid_vol/=x;
      Buff_VR[i]=tick_volume[i]/mid_vol; // cálculo VR
      //--- calcular o Volume Weighted Moving Average
      long vol=0;
      double price_vol=0;
      x=0;
      switch(type_price_VWMA)
        {
         case 0:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(open[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 1:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(high[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 2:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(low[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 3:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(close[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 4:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               double price=(open[x]+high[x]+low[x]+close[x])/4;
               price_vol+=double(price*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
        }
      Buff_VWMA[i]=price_vol/vol; // calcular VWMA
      //---
      if(type_draw==1)
        {
         Buff_open_ext[i]=Buff_open[i];
         Buff_close_ext[i]=Buff_close[i];
         Buff_VWMA_ext[i]=Buff_VWMA[i];
        }
      else
        {
         //--- diminuir o tamanho dos arrays não utilizados
         ArrayResize(Buff_open_ext,1);
         ArrayResize(Buff_close_ext,1);
         ArrayResize(Buff_VWMA_ext,1);
         //--- zerar arrays não utilizados
         ZeroMemory(Buff_open_ext);
         ZeroMemory(Buff_close_ext);
         ZeroMemory(Buff_VWMA_ext);
        }
     }
   end_bar=rates_total;// definir o número da última barra
//---
   if(ChartGetDouble(0,CHART_PRICE_MAX,ChartWindowFind())>0 && type_draw==0)// verificar a disponibilidade de dados na janela do indicador para começar a plotagem
     {
      func_drawing(func_check_chart(),ext_spin_num,end_bar);
     }
//--- valor de retorno de prev_calculated para a próxima chamada
   return(rates_total);
  }

Função padrão do indicador calcula e preenche os buffers com dados. Primeiros períodos VR e VWMA e dados sobre barras são validadas, será exibido uma mensagem em caso de incompatibilidade. Em seguida, ao ser encontrado o buffer, começa a ser preenchido pela posição. O buffer do histograma denota os preços mais altos e baixos para preenchimento. Depois os buffers do Volume Ratio (VR) e do Volume Weighted Moving Average (VWMA) são calculados e preenchidos de acordo com as fórmulas 1 e 2 (definido na Introdução) .


Outras funções

Para o indicador trabalhar devidamente, uma função da nova definição de barra (func_new_bar) e uma função do indicador de desinicialização (OnDeinit) estão presentes.

A função func_new_bar determina o aparecimento de uma nova barra no gráfico e serve para auxiliar a função func_check_chart .

//+------------------------------------------------------------------+
//| Função Nova Barra                                                |
//+------------------------------------------------------------------+
bool func_new_bar(ENUM_TIMEFRAMES period_time)
  {
   static datetime old_times; // valores antigos da variável de armazenamento
   bool res=false;            // análise do resultado da variável
   datetime new_time[1];      // tempo da nova barra
   int copied=CopyTime(_Symbol,period_time,0,1,new_time); // copiar o tempo da última barra para dentro da célula new_time  
   if(copied>0) // dados copiados
     {
      if(old_times!=new_time[0]) // se o tempo da barra antiga não é igual ao novo 
        {
         if(old_times!=0) res=true; // se não é o primeiro lançamento, true=new bar
         old_times=new_time[0];     // armazenar o tempo da barra
        }
     }
   return(res);
  }
//+------------------------------------------------------------------+

Esta função já foi apresentada em artigos anteriores e, portanto, não precisa de comentários.

A função OnDeinit exclui objetos gráficos criados no início da função OnInit. A função é padrão para o indicador, é chamada quando o indicador é removido a partir do gráfico.

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- excluir objetos usados
   if(type_draw==0)
     {
      for(int x=0; x<=ext_spin_num; x++)
        {
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"1");
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"2");
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"3");
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"4");
        }
     }
  }

Isto conclui o código do indicador. Se você tem dúvidas sobre o tópico ou definições, não hesite em entrar em contato comigo através da seção de comentários do artigo ou mensagens privadas.


Expert advisor e estratégia de negociação

Antes de considerar uma estratégia de negociação, vamos testar como o expert trabalha neste indicador. O teste será realizado no expert que utiliza apenas um spindle para analisar as suas ações. O VR (Volume Ratio) não é usado. Acontece que a análise vai ter lugar numa espécie de padrão constituído por um único spindler. Há 30 dessas variantes no total, mais detalhes na Figura 7:

Fig. 7. Possíveis formações dos spindles

Fig. 7. Possíveis formações dos spindles

Tipos de spindle que podem ser divididos em três grupos e um subgrupo (Fig. 7). É possível considerarmos as diferenças dos movimentos das direções dos spindles dos preços, abertura e fechamento dos preços em relação ao conjunto de spindle e ao nível do Volume Weighted Moving Average.

Suponha que a primeira diferença dos spindles é sua cor, ou seja, o mercado em alta ou em baixa no período em análise (Fig. 7, coluna 1). Na figura 7, a primeira coluna (0) - pra cima (vermelho) e (1) - para baixo (azul). A próxima coluna mostra diferenças no corpo B (abertura e fechamento de preços) em relação à sombra S (os preços maiores e menores para o período). Esta diferença, no exemplo atual é dividida apenas em três partes (Fig. 7, coluna 2). A terceira coluna considera a comparação do Volume Weighted Moving Average (VWMA) ao nível mais alto e mais baixo dos preços (Máxima e Mínima). Pode ser localizada acima(1), abaixo(2) e entre(3) o maior e o menor preço. Na terceira coluna do spindle (3) também pode diferir pela Abertura e Fechamento dos preços do período em relação ao VWMA, assim, outra coluna 3-3 (derivadas da coluna 3 (3)) é formada na Figura 7.

Tendo em conta todas as combinações possíveis das diferenças acima, obtemos 30 tipos de spindles.

Todos os números da Figura 7 são atribuídos de acordo com os resultados de uma função num expert de negociação de acordo com o código abaixo.


Parâmetros do Expert Advisor

Todo o código está dividido em funções, para diminuir a quantidade de código, as funções são chamadas a partir de subfunções, formando assim uma estrutura hierárquica de funções. As variáveis de entrada declaradas no início do código são idênticas aos parâmetros do indicador, implementadas somente por tamanho do lote, stop loss e trinta padrões de spindles. Variáveis para handle e buffers do indicador para o armazenamento de dados são usadas para determinar o padrão, são declaradas no final.

//+------------------------------------------------------------------+
//|                                                        EASPC.mq5 |
//|                                   Azotskiy Aktiniy ICQ:695710750 |
//|                          https://login.mql5.com/ru/users/aktiniy |
//+------------------------------------------------------------------+
#property copyright "Azotskiy Aktiniy ICQ:695710750"
#property link      "https://login.mql5.com/ru/users/aktiniy"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Tipo de desenho                                                  |
//+------------------------------------------------------------------+
enum type_drawing
  {
   spindles=0,       // Spindles
   line_histogram=1, // Linha e  Histograma
  };
//+------------------------------------------------------------------+
//| Tipo de Preço                                                    |
//+------------------------------------------------------------------+
enum type_price
  {
   open=0,   // Abertura
   high=1,   // Máxima
   low=2,    // Mínima
   close=3,  // Fechamento
   middle=4, // Meio
  };
//--- parâmetro de entrada
input long         magic_numb=65758473787389; // Número mágico
input type_drawing type_draw=1;               // Tipo de desenho do indicador
input int          period_VR=10;              // Período de formação do Volume Ratio
input int          correct_VR=4;              // Número de correção do Volume Ratio
input int          period_VWMA=10;            // Período de formção do Volume Weighted Moving Average 
input int          spindles_num=10;           // Número de spindles
input type_price   type_price_VWMA=0;         // tipo de preço para plotagem do Volume Weighted Moving Average 
                                              // open=0; high=1; low=2; close=3; middle=4
input double lot=0.01;                        // Tamanho do Lote
input int    stop=1000;                       // Stop Loss
//---
input char   p1=1;                            // Ações padrões tipo 1-comprar, 2-vender, 3-posição de fechamento, 4-não fazer nada
input char   p2=1;
input char   p3=1;
input char   p4=1;
input char   p5=1;
input char   p6=1;
input char   p7=1;
input char   p8=1;
input char   p9=1;
input char   p10=1;
input char   p11=1;
input char   p12=1;
input char   p13=1;
input char   p14=1;
input char   p15=1;
input char   p16=1;
input char   p17=1;
input char   p18=1;
input char   p19=1;
input char   p20=1;
input char   p21=1;
input char   p22=1;
input char   p23=1;
input char   p24=1;
input char   p25=1;
input char   p26=1;
input char   p27=1;
input char   p28=1;
input char   p29=1;
input char   p30=1;
//---
int handle_SPC; // handle do indicador
long position_type; // tipo de posição
//--- buffers para valores do  indicador copiado
double         Buff_up[3]; // buffer dos pontos superiores do histograma
double         Buff_down[3]; // buffer dos pontos inferiores do histograma
double         Buff_color_up_down[3]; // buffer da cor do histograma
double         Buff_open_ext[3]; // buffer da abertura de preço
double         Buff_close_ext[3]; // buffer do Fechamento de preço
double         Buff_VWMA_ext[3]; // buffer do Volume Weighted Moving Average
A inicialização do handle do indicador acontece na função OnInit.
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   handle_SPC=iCustom(_Symbol,PERIOD_CURRENT,"SPC.ex5",magic_numb,type_draw,period_VR,correct_VR,period_VWMA,spindles_num,type_price_VWMA);
//---
   return(INIT_SUCCEEDED);
  }


Função de envio de ordens ao servidor

Existem duas destas funções: uma para as ordens de abertura, outra para fechamento de posições. Ambas as funções são baseadas em exemplos da documentação MQL5 e incluem uma colaboração de estrutura de solicitação de negociação e uma chamada à função OrderSend com uma análise mais aprofundada da estrutura de resultado.

//+------------------------------------------------------------------+
//| Função para Enviar Ordens                                        |
//+------------------------------------------------------------------+
bool func_send_order(ENUM_ORDER_TYPE type_order,// Tipo de ordem colocada
                     double volume)             // volume do lote da negociação
  {
   bool x=false; // variável para resposta
//--- declara  variáveis para o envio de ordem
   MqlTradeRequest order_request={0};
   MqlTradeResult order_result={0};
//--- define a variável para o envio de ordem
   order_request.action=TRADE_ACTION_DEAL;
   order_request.deviation=3;
   order_request.magic=555;
   order_request.symbol=_Symbol;
   order_request.type=type_order;
   order_request.type_filling=ORDER_FILLING_FOK;
   order_request.volume=volume;
   if(type_order==ORDER_TYPE_BUY)
     {
      order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      order_request.sl=order_request.price-(_Point*stop);
     }
   if(type_order==ORDER_TYPE_SELL)
     {
      order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      order_request.sl=order_request.price+(_Point*stop);
     }
//--- envia ordem
   bool y=OrderSend(order_request,order_result);
   if(y!=true)Alert("Order sending error.");
//--- verifica o resultado
   if(order_result.retcode==10008 || order_result.retcode==10009) x=true;
   return(x);
  }
//+------------------------------------------------------------------+
//| Função para Excluir Posição                                      |
//+------------------------------------------------------------------+
bool func_delete_position()
  {
   bool x=false;
//--- marcar a posição para trabalhar com
   PositionSelect(_Symbol);
   double vol=PositionGetDouble(POSITION_VOLUME);
   long type=PositionGetInteger(POSITION_TYPE);
   ENUM_ORDER_TYPE type_order;
   if(type==POSITION_TYPE_BUY)type_order=ORDER_TYPE_SELL;
   else type_order=ORDER_TYPE_BUY;
//--- declara variáveis para o envio de ordem
   MqlTradeRequest order_request={0};
   MqlTradeResult order_result={0};
//--- define variáveis para o envio de ordem
   order_request.action=TRADE_ACTION_DEAL;
   order_request.deviation=3;
   order_request.magic=555;
   order_request.symbol=_Symbol;
   order_request.type=type_order;
   order_request.type_filling=ORDER_FILLING_FOK;
   order_request.volume=vol;
   if(type_order==ORDER_TYPE_BUY)order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   if(type_order==ORDER_TYPE_SELL)order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
//--- envia ordem
   bool y=OrderSend(order_request,order_result);
   if(y!=true)Alert("Order sending error.");
//--- verifica o resultado
   if(order_result.retcode==10008 || order_result.retcode==10009) x=true;
   return(x);
  }

Função auxiliar func_new_bar que determina o aparecimento de uma nova barra no gráfico também está presente no código. Está descrita acima e não necessita da publicação do seu código.

Depois de descrever todas as funções normais, considere o "coração" dos cálculos.

A consolidação de todas as ações tem lugar na função OnTick. Primeiro os buffers usados nos cálculos são preenchidos com a ajuda da função CopyBuffer. Em seguida, é verificado se o símbolo atual tem qualquer posição nele, isso é necessário para colocação de outra ordem (posição) e a função controle de remoção, depois que os tamanhos da definição do padrão estão preparados. Corpo do spindle - a distância entre os preços de abertura e de fechamento; sombra — a distância entre as máximas e mínimas dos período em andamento e também as suas relações, que mais tarde são transmitidas para a função func_one (Fig 7, coluna 2).

Posteriormente, as funções func_two e func_three são utilizadas, as colunas 3 e 3-3 na Figura 7, respectivamente, depois de verificar a cor do spindle usando um "switch" de acordo com a Figura 7, coluna 1. Desta forma, obtemos uma árvore de decisão de funções quando o próximo operador "switch" conecta a função func_pre_work (discutida mais tarde), dependendo do valor da variável afun_1_1 e de acordo com a coluna 2 da figura 7, com base nos tamanhos de corpo e sombra do spindle.

//+------------------------------------------------------------------+
//| Função tick do expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(func_new_bar(PERIOD_CURRENT)==true)
     {
      //--- copia os buffers do indicador
      CopyBuffer(handle_SPC,0,1,3,Buff_up);
      CopyBuffer(handle_SPC,1,1,3,Buff_down);
      CopyBuffer(handle_SPC,2,1,3,Buff_color_up_down);
      CopyBuffer(handle_SPC,3,1,3,Buff_open_ext);
      CopyBuffer(handle_SPC,4,1,3,Buff_close_ext);
      CopyBuffer(handle_SPC,5,1,3,Buff_VWMA_ext);
      //--- analisa a situação
      //--- verifica se existe uma ordem colocada
      if(PositionSelect(_Symbol)==true)
        {
         position_type=PositionGetInteger(POSITION_TYPE); // BUY=0, SELL=1
        }
      else
        {
         position_type=-1; // no position for the symbol
        }
      //--- prepara os valores para comparar
      double body=Buff_open_ext[2]-Buff_close_ext[2];
      body=MathAbs(body);
      double shadow=Buff_up[2]-Buff_down[2];
      shadow=MathAbs(shadow);
      if(shadow==0)shadow=1;// evitar a divisão por zero
      double body_shadow=body/shadow;
      //--- variáveis para resposta da função
      char afun_1_1=func_one(body_shadow);
      char afun_2_1=func_two(Buff_up[2],Buff_down[2],Buff_VWMA_ext[2]);
      char afun_3_1=func_three(Buff_open_ext[2],Buff_close_ext[2],Buff_VWMA_ext[2]);
      //---
      switch(int(Buff_color_up_down[2]))
        {
         case 0:
           {
            switch(afun_1_1)
              {
               case 1:
                  func_pre_work(afun_2_1,afun_3_1,p1,p2,p3,p4,p5);
                  break;
               case 2:
                  func_pre_work(afun_2_1,afun_3_1,p6,p7,p8,p9,p10);
                  break;
               case 3:
                  func_pre_work(afun_2_1,afun_3_1,p11,p12,p13,p14,p15);

                  break;
              }
           }
         break;
         case 1:
           {
            switch(afun_1_1)
              {
               case 1:
                  func_pre_work(afun_2_1,afun_3_1,p16,p17,p18,p19,p20);
                  break;
               case 2:
                  func_pre_work(afun_2_1,afun_3_1,p21,p22,p23,p24,p25);
                  break;
               case 3:
                  func_pre_work(afun_2_1,afun_3_1,p26,p27,p28,p29,p30);
                  break;
              }
           }
         break;
        }
     }
  }

A função func_pre_work continua a ramificação da função já formada da árvore de decisão. Ficamos com uma implementação de programa do código baseado na Figura 7, colunas 3 (variável f_2) e 3-3 (variável f_3), a comutação é realizada para a última função da árvore - func_work.

//+------------------------------------------------------------------+
//| Func Pre Work                                                    |
//+------------------------------------------------------------------+
void func_pre_work(char f_2,     // resultado da função Func Two 
                   char f_3,     // resultado da função Func Three 
                   char pat_1,   // padrão 1
                   char pat_2,   // padrão 2
                   char pat_3_1, // padrão 3_1
                   char pat_3_2, // padrão 3_2
                   char pat_3_3) // padrão 3_3
  {
   switch(f_2)
     {
      case 1: //1
         func_work(pat_1);
         break;
      case 2: //2
         func_work(pat_2);
         break;
      case 3:
        {
         switch(f_3)
           {
            case 1: //3_1
               func_work(pat_3_1);
               break;
            case 2: //3_2
               func_work(pat_3_2);
               break;
            case 3: //3_3
               func_work(pat_3_3);
               break;
           }
        }
      break;
     }
  }

A função func_work decide o que fazer com uma posição, escolhendo uma das quatro opções: comprar, vender, fechar próxima posição e não fazer nada. Aqui, o controle final é transferida para as funções já conhecidas: func_send_order e func_delete_position.

//+------------------------------------------------------------------+
//| Func Work                                                        |
//+------------------------------------------------------------------+
void func_work(char pattern)
  {
   switch(pattern)
     {
      case 1: // comprar
         if(position_type!=-1)func_delete_position();
         func_send_order(ORDER_TYPE_BUY,lot);
         break;
      case 2: // vender
         if(position_type!=-1)func_delete_position();
         func_send_order(ORDER_TYPE_SELL,lot);
         break;
      case 3: // fechar posição
         if(position_type!=-1)func_delete_position();
         break;
      case 4: // não fazer nada
         break;
     }
  }

As última funções mencionadas anteriormente são: func_one, func_two e func_three. Eles convertem os dados transmitidos sob a forma de valores dos preços com dados inteiros para o operador "switch". Se você voltar à Figura 7, a função func_one — é a implementação da coluna 2, a func_two — da coluna 3 e func_three — da coluna 3-3. Valores de retorno dessas funções são inteiros 1, 2 e 3 que também correspondem aos números da Figura 7.

//+------------------------------------------------------------------+
//| Func One                                                         |
//+------------------------------------------------------------------+
char func_one(double body_shadow_in)
  {
   char x=0; // variável de resposta
   if(body_shadow_in<=(double(1)/double(3))) x=1;
   if(body_shadow_in>(double(1)/double(3)) && body_shadow_in<=(double(2)/double(3))) x=2;
   if(body_shadow_in>(double(2)/double(3)) && body_shadow_in<=1) x=3;
   return(x);
  }
//+------------------------------------------------------------------+
//| Func Two                                                         |
//+------------------------------------------------------------------+
char func_two(double up,// máxima [Buff_up]
              double down,// mínima [Buff_down]
              double VWMA) // VWMA [Buff_VWMA_ext]
  {
   char x=0; // variável de resposta
   if(VWMA>=up) x=1;
   if(VWMA<=down) x=2;
   else x=3;
   return(x);
  }
//+------------------------------------------------------------------+
//| Func Three                                                       |
//+------------------------------------------------------------------+
char func_three(double open,// abertura[Buff_open_ext]
                double close,// fechamento [Buff_close_ext]
                double VWMA) // VWMA [Buff_VWMA_ext]
  {
   char x=0; // variável de resposta
   if(open>=VWMA && close>=VWMA) x=1;
   if(open<=VWMA && close<=VWMA) x=2;
   else x=3;
   return(x);
  }
//+------------------------------------------------------------------+

Agora que o advisor está pronto para ser usado e testado. Primeiro defina os parâmetros:

A otimização somente será realizada por ações (ou seja, compra, venda, posição perto e não fazer nada), bem como pelo período VWMA. Assim, vamos descobrir quais ações são as mais rentáveis para cada padrão e que o período VWMA é o mais adequado para o trabalho no prazo H1.

Configurações do Testador de Estratégia são apresentadas na Figura 8:

Fig. 8. Configurações do Testador de Estratégias

Fig. 8. Configurações do Testador de Estratégias

Como foi mencionado antes, a otimização vai ser levada a cabo por uma ação de acordo com o padrão e o período VWMA que varia de 10 a 500 barras, Figura 9:

Fig. 9. Parâmetros de otimização

Fig. 9. Parâmetros de otimização

No processo de otimização obtém-se um gráfico, Figura 10:

Fig. 10. Gráfico de otimização

Fig. 10. Gráfico de otimização

Como resultado da otimização, teve um lucro de 138.71, tamanho do lote igual 0.01 e o depósito inicial de 10000, com perda de 2.74% (cerca de 28 unidades), Figura 11:

Fig. 11. Os resultados da otimização

Fig. 11. Os resultados da otimização

Aumentamos o tamanho do lote de 0.1, diminuirmos o depósito inicial para 1000 e realizamos um segundo teste usando parâmetros a partir do resultado da otimização. A fim de aumentar a precisão do teste, alteramos o modo de negociação para OHLC em M1, obtemos Figura 12:

Fig. 12. Resultado do teste (backtest)

Fig. 12. Resultado do teste (backtest)

Como resultado, em dois anos foram feitas 742 negociações (cerca de 3 negócios por dia), o rebaixamento máximo foi de 252 e o lucro líquido - 1407, cerca de 60 (6% do total dos investimentos) por mês. Em teoria, tudo funciona muito bem, mas não existe nenhuma garantia que irá revelar-se tão bem na prática.

É claro que este expert precisa de mais modernização e melhoria, talvez uma introdução de um eixo spindle adicional ao padrão e adição do nível VR. Este é assunto para refletir em outro momento, mas mesmo nestes pequenos parâmetros, os experts mostraram alguns resultados interessantes com este indicador.

Ao lidar com o indicador, a estratégia de negociação é bastante simples - compre quando a seta aponta para cima e venda quando ele aponta para baixo. Rhombus é um tipo de Doji, indica uma reversão. Isto é claramente visto na Figura 13:

Fig. 13. O indicador em ação

Fig. 13. O indicador em ação

Como pode ser visto na Figura 13, o indicador desenha pontas de seta direcionadas para cima até o número 1, então aparece um losango azul, indicando uma possível mudança no sentido do movimento da tendência. A tendência muda e os preços vão para baixo até o número 2, então um losango vermelho aparece, também anunciando uma mudança na tendência e isso acontece.


Conclusão

Eu tinha pouca informação sobre o indicador, mas mesmo assim consegui me interessar por sua originalidade. Talvez a complexidade da sua implementação no código tenha me feito pensar que também tinha uma influência. O tempo não foi gasto em vão e eu espero que este indicador seja útil para muitas pessoas. Eu posso supor que o assunto não está totalmente desenvolvido ainda, pois é bastante extenso, mas acho que isso será discutido no fórum. Deve ser dado uma atenção à discussão e modernização do advisor, mesmo na fase inicial ele mostra alguns resultados decentes. Eu gostaria de receber observações e discussões tanto no artigo como em mensagens privadas.

Este foi mais um artigo sobre o assunto de indicadores, se alguém tiver alguma idéia sobre novos indicadores interessantes, me envie em mensagem privada. Eu não prometo a implementação, mas tenha a certeza que vou considerar, e talvez lhe aconselhe alguma coisa. Meu próximo artigo supostamente vai mudar radicalmente o assunto, mas eu não vou "colocar a carroça na frente dos bois" e falar sobre isso, pois é apenas uma idéia e o código esta apenas na fase de planejamento.