Modelando séries temporais usando símbolos personalizados de acordo com as leis de distribuição especificadas

Aleksey Zinovik | 20 novembro, 2018

Sumário

Introdução

O terminal de negociação MetaTrader 5 permite criar e usar símbolos personalizados. O trader tem a oportunidade de testar seus próprios pares de moedas e outros instrumentos financeiros. No artigo, são apresentadas maneiras de criar e excluir símbolos personalizados, gerar ticks e barras de acordo com as leis de distribuição especificadas.

Também serão propostas maneiras para formar tendências e vários padrões gráficos. Para trabalhar com símbolos personalizados, estarão disponíveis scripts prontos com configurações mínimas, permitindo que os traders que não possuem habilidades de programação em MQL5 usem todo o de potência dos símbolos personalizados.

Criação e exclusão de símbolos personalizados

O artigo mostra como criar símbolos personalizados na janela "Símbolos" do MetaTrader 5, com base nos símbolos já existentes. 

Sugerimos automatizar esse processo usando um script simples com configurações mínimas.

O script tem 4 parâmetros de entrada:

  • nome do símbolo personalizado
  • nome abreviado do par de moedas ou do instrumento financeiro
  • nome completo do par de moedas ou do instrumento financeiro
  • nome abreviado da moeda base ou do instrumento financeiro, se o símbolo for criado com base no símbolo base.
Aqui está o código do script (o script está anexado ao artigo no arquivo CreateSymbol.mq5):

//+------------------------------------------------------------------+
//|                                                 CreateSymbol.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property script_show_inputs 
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
input string SName="ExampleCurrency";
input string CurrencyName="UCR";
input string CurrencyFullName="UserCurrency";
input string BaseName="EURUSD";
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   ResetLastError();
//crie o símbolo
   if(!CustomSymbolCreate(SName,"\\Forex"))
     {
      if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))
         Print("O símbolo ",SName," já existe!");
      else
         Print("Erro ao criar símbolo. Código de erro: ",GetLastError());
     }
   else
     {
      if(BaseName=="")//crie um novo
        {
         //propriedade do tipo String
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && //unidade monetária base
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&&                         //moeda de lucro
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"USD",""))&&                         //moeda de margem
            (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyName,""))&&                      //descrição do caractere de string (nome completo)
            (SetProperty(SName,SYMBOL_BASIS,"","")) &&                                     //nome do ativo subjacente para o instrumento derivado
            (SetProperty(SName,SYMBOL_FORMULA,"","")) &&                                   //fórmula para construir o preço de um símbolo personalizado
            (SetProperty(SName,SYMBOL_ISIN,"","")) &&                                      //nome do símbolo de negociação no sistema ISIN
            (SetProperty(SName,SYMBOL_PAGE,"","")) &&                                      //endereço da página da web com informações sobre o símbolo
            //propriedades do tipo Integer
            (SetProperty(SName,SYMBOL_CHART_MODE,SYMBOL_CHART_MODE_BID,"")) &&             //plotagem do grafico segundo o preço Bid
            (SetProperty(SName,SYMBOL_SPREAD,3,"")) &&                                     //spread
            (SetProperty(SName,SYMBOL_SPREAD_FLOAT,true,"")) &&                            //spread flutuante
            (SetProperty(SName,SYMBOL_DIGITS,5,"")) &&                                     //precisão (número de casas decimais)
            (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,10,"")) &&                           //tamanho do livro de ofertas
            (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,White,""))&&                        //cor de fundo em que é destacado o símbolo na Observação do mercado
            (SetProperty(SName,SYMBOL_TRADE_MODE,SYMBOL_TRADE_MODE_FULL,""))&&             //tipo de execução de ordens: acesso completo
            (SetProperty(SName,SYMBOL_TRADE_EXEMODE,SYMBOL_TRADE_EXECUTION_INSTANT,""))&&  //modo de transações: execução imediata
            (SetProperty(SName,SYMBOL_ORDER_GTC_MODE,SYMBOL_ORDERS_GTC,""))&&              //validade das ordens StopLoss e TakeProfit: validas até serem canceladas
            (SetProperty(SName,SYMBOL_FILLING_MODE,SYMBOL_FILLING_FOK,""))&&               //modo de execução da ordem: tudo ou nada
            (SetProperty(SName,SYMBOL_EXPIRATION_MODE,SYMBOL_EXPIRATION_GTC,""))&&         //modo de expiração da ordem: ilimitado no tempo, até o cancelamento explícito
            (SetProperty(SName,SYMBOL_ORDER_MODE,127,"")) &&                               //tipos de ordens: todos os tipos de ordens
            (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,SYMBOL_CALC_MODE_FOREX,""))&&        //método de cálculo do valor do contrato
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,false,""))&&                   //modo de cálculo da margem coberta pelo maior lado
            (SetProperty(SName,SYMBOL_SWAP_MODE,SYMBOL_SWAP_MODE_POINTS,""))&&             //modelo de cálculo de swap: cálculo de swap em pontos
            (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,WEDNESDAY,"")) &&                 //dia da semana para o swap triplo
            (SetProperty(SName,SYMBOL_OPTION_MODE,0,"")) &&                                //tipo de opção
            (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,"")) &&                               //direito de opção
            (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,"")) &&                          //recuo mínimo em pontos a partir do preço atual de fechamento para posicionamento de ordens Stop
            (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,"")) &&                         //distância de congelamento de operações de negociação (em pontos)
            (SetProperty(SName,SYMBOL_START_TIME,0,"")) &&                                 //data de início da negociação do instrumento (geralmente usado para futuros)
            (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,"")) &&                            //data do final da negociação do instrumento (geralmente usado para futuros)
            //propriedades do tipo Double
            (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,"")) &&                              //preço de exercício da opção
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,"")) &&                    //preço mínimo permitido para a sessão 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,"")) &&                    //preço máximo permitido para a sessão
            (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,"")) &&                   //preço de entrega para a sessão atual
            (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,"")) &&                     //juros acumulados (para títulos)
            (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,"")) &&                           //valor nominal (para títulos)
            (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,"")) &&                       //índice de liquidez (usado para instrumentos colaterais)
            (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0.00001,"")) &&                      //mudança mínima do preço 
            (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,1,"")) &&                           //valor do tick
            (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,100000,"")) &&                   //tamanho do contrato
            (SetProperty(SName,SYMBOL_POINT,0.00001,"")) &&                                //valor de um ponto
            (SetProperty(SName,SYMBOL_VOLUME_MIN,0.01,"")) &&                              //volume mínimo para realização da transação
            (SetProperty(SName,SYMBOL_VOLUME_MAX,500.00,"")) &&                            //volume máximo para realizar a transação
            (SetProperty(SName,SYMBOL_VOLUME_STEP,0.01,"")) &&                             //incremento mínimo de alteração do volume para realizar a transação
            (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,"")) &&                               //volume total máximo da posição aberta e das ordens pendentes segundo um único símbolo numa só direção (compra ou venda).
            (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,"")) &&                             //margem inicial
            (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,"")) &&                         //margem de manutenção
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED,100000,"")) &&                         //tamanho do contrato ou da margem para um lote de posições multidirecionais segundo um único símbolo 
            (SetProperty(SName,SYMBOL_SWAP_LONG,-0.7,"")) &&                               //valor do swap na compra
            (SetProperty(SName,SYMBOL_SWAP_SHORT,-1,"")))                                  //valor do swap na venda
            Print("Símbolo ",SName," criado com sucesso");
         else
            Print("Erro ao definir as propriedades do símbolo. Código de erro: ",GetLastError());
        }
      else//criamos com base no ativo base
        {
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && 
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyFullName,"")) && 
            (SetProperty(SName,SYMBOL_BASIS,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_FORMULA,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_ISIN,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_PAGE,"",BaseName)) && 

            (SetProperty(SName,SYMBOL_CHART_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SPREAD,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SPREAD_FLOAT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_DIGITS,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_EXEMODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_ORDER_GTC_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_FILLING_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_EXPIRATION_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_ORDER_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_OPTION_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_START_TIME,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,BaseName)) && 

            (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_POINT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_MIN,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_MAX,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_STEP,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_LONG,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_SHORT,0,BaseName)))
            Print("Símbolo ",SName," criado com sucesso");
         else
            Print("Erro ao definir as propriedades do símbolo. Código de erro: ",GetLastError());
        }
      if(SymbolSelect(SName,true))
         Print("Símbolo ",SName," selecionado na Observação do Mercado");
      else
         Print("Erro ao selecionar o símbolo na Observação do Mercado. Código de erro: ",GetLastError());
     }
  }
//função para definir as propriedades do símbolo
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_STRING SProp,string PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetString(SymName,SProp,PropValue))
         return true;
      else
         Print("Erro ao definir a propriedade do símbolo: ",SProp," .Código de erro: ",GetLastError());
     }
   else
     {
      string SValue=SymbolInfoString(BaseSymName,SProp);
      if(CustomSymbolSetString(SymName,SProp,SValue))
         return true;
      else
         Print("Erro ao definir a propriedade do símbolo: ",SProp," .Código de erro: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_INTEGER IProp,long PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetInteger(SymName,IProp,PropValue))
         return true;
      else
         Print("Erro ao definir a propriedade do símbolo: ",IProp," .Código de erro: ",GetLastError());
     }
   else
     {
      long IValue=SymbolInfoInteger(BaseSymName,IProp);
      if(CustomSymbolSetInteger(SymName,IProp,IValue))
         return true;
      else
         Print("Erro ao definir a propriedade do símbolo: ",IProp," .Código de erro: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_DOUBLE DProp,double PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetDouble(SymName,DProp,PropValue))
         return true;
      else
         Print("Erro ao definir a propriedade do símbolo: ",DProp," .Código de erro: ",GetLastError());
     }
   else
     {
      double DValue=SymbolInfoDouble(BaseSymName,DProp);
      if(CustomSymbolSetDouble(SymName,DProp,DValue))
         return true;
      else
         Print("Erro ao definir a propriedade do símbolo: ",DProp," .Código de erro: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+

Considere o código do script em mais detalhes. Primeiro, é feita uma tentativa de criar um símbolo usando a função CustomSymbolCreate:

if(!CustomSymbolCreate(SName,"\\Forex"))
     {
      if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))
         Print("O símbolo ",SName," já existe!");
      else
         Print("Erro ao criar símbolo. Código de erro: ",GetLastError());
     }

O símbolo é criado na pasta Custom/Forex. Se você criar sua própria subpasta (grupo personalizado) na pasta Custom, especifique seu nome no segundo parâmetro da função CustomSymbolCreate.

Em seguida, são definidas as propriedades do símbolo criado. Quando não definido o parâmetro BaseName, é executada a definição dos parâmetros do símbolo estabelecidos pelo usuário. Por exemplo, vejas propriedades definidas do par de moedas EURUSD:

    //propriedade do tipo String
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && //unidade monetária base
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&&                         //moeda de lucro
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"UCR",""))&&                         //moeda da margem
...

Por conveniência, as propriedades são divididas em grupos, primeiro são definidas as propriedades do tipo String, Integer e, finalmente, Double. No caso de configuração bem-sucedida de propriedades, no log é registrada uma mensagem sobre a criação bem-sucedida do símbolo, caso contrário, no log é registrado o código de erro ao definir as propriedades do símbolo. 

Se o valor do parâmetro BaseName não estiver vazio, as propriedades do símbolo que está sendo criado são copiadas das propriedades do símbolo base cujo nome é especificado pelo parâmetro BaseName, por exemplo, podem ser os pares de moedas EURUSD, USDCAD, GBPUSD e outros.

A função SetProperty, descrita após o código da função base do script, é responsável por definir as propriedades dos símbolos. Esta função não é usada em outros scripts, portanto, não a coloque numa classe plug-in separada.

Para propriedades do tipo String, Integer e Double, são criadas instâncias separadas da função SetProperty. Para definir as propriedades do símbolo personalizado, são usadas as funções CustomSymbolSetStringCustomSymbolSetInteger, CustomSymbolSetDouble. Para obter as propriedades do símbolo base, são usadas as funções SymbolInfoStringSymbolInfoIntegerSymbolInfoDouble

Após a configuração bem-sucedida das propriedades, o símbolo personalizado criado é selecionado na Observação do mercado usando a função SymbolSelect:

  if(SymbolSelect(SName,true))
         Print("Símbolo ",SName," selecionado na Observação do Mercado");
      else
         Print("Erro ao selecionar o símbolo na Observação do Mercado. Código de erro: ",GetLastError());

Para abrir o gráfico do símbolo criado, você precisa carregar no símbolo ticks ou barras. O script para gerar ticks e barras será discutido abaixo. 

Agora, considere o processo para excluir um símbolo personalizado. Se você quiser excluir um símbolo personalizado selecionando-o na guia Símbolos, nem sempre será possível conseguir isso:

Tentando excluir um símbolo

Fig. 1. Tentando excluir um símbolo quando selecionado na Observação do mercado.

Para excluir um símbolo, é necessário removê-lo da Observação do mercado clicando no símbolo duas vezes na janela Símbolos. O símbolo que você deseja desabilitar não deve ter gráficos abertos e posições, portanto, é necessário fechar todos os gráficos e posições para este símbolo manualmente. Este processo não é muito rápido, especialmente se você tiver muitos gráficos nesse símbolo. Mostraremos como implementar a exclusão de símbolo usando um script pequeno (o script está anexado ao artigo no arquivo DeleteSymbol.mq5):

//+------------------------------------------------------------------+
//|                                                 DeleteSymbol.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property script_show_inputs 
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
input string SName="ExampleCurrency";
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   ResetLastError();
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//se o símbolo existir
     {
      if(!CustomSymbolDelete(SName))//tentamos remover
        {
         if(SymbolInfoInteger(SName,SYMBOL_SELECT))//se estiver selecionado na Observação do mercado
           {
            if(SymbolSelect(SName,false))//Tentamos desativar e remover
              {
               if(!CustomSymbolDelete(SName))
                  Print("Erro ao remover o símbolo ",SName," Código de erro: ",GetLastError());
               else
                  Print("Símbolo ",SName," removido com sucesso");
              }
            else
              {
               //tentamos fechar gráficos com um símbolo
               int i=0;
               long CurrChart=ChartFirst();
               int i_id=0;
               long ChartIDArray[];
               while(CurrChart!=-1)
                 {
                  //iteramos a lista de gráficos e salvamos os identificadores de gráficos abertos do símbolo SName
                  if(ChartSymbol(CurrChart)==SName)
                    {
                     ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1);
                     ChartIDArray[i_id]=CurrChart;
                     i_id++;
                    }
                  CurrChart=ChartNext(CurrChart);
                 }
               //fechamos todos os gráficos do símbolo SName
               for(i=0;i<i_id;i++)
                 {
                  if(!ChartClose(ChartIDArray[i]))
                    {
                     Print("Erro ao fechar o gráfico do símbolo ",SName,". Código de erro: ",GetLastError());
                     return;
                    }
                 }
               //desativamos e removemos o símbolo 
               if(SymbolSelect(SName,false))
                 {
                  if(!CustomSymbolDelete(SName))
                     Print("Erro ao remover o símbolo ",SName," Código de erro: ",GetLastError());
                  else
                     Print("Símbolo ",SName," removido com sucesso");
                 }
               else
                  Print("Erro ao desativar o símbolo ",SName," na Observação do mercado. Código de erro: ",GetLastError());
              }//end else SymbolSelect 
           } //end if(SymbolSelect(SName,false))
         else
            Print("Erro ao remover o símbolo ",SName," Código de erro: ",GetLastError());
        }
      else
         Print("Símbolo ",SName," removido com sucesso");
     }
   else
      Print("O símbolo ",SName," não existe");
  }
//+------------------------------------------------------------------+

Considere o funcionamento do script:

  • primeiro, é verificada a presença de um símbolo com o nome SName,
  • se o símbolo for encontrado, é feita uma tentativa para removê-lo usando a função CustomSymbolDelete,
  • se o símbolo não puder ser excluído, tente desativá-lo na Observação do mercado usando a função SimbolSelect,
  • se não for possível desabilitar o símbolo na Observação do mercado, feche todos os gráficos abertos para este símbolo.
Para fazer isso, passe por todos os gráficos abertos e salve os identificadores dos gráficos abertos no símbolo com o nome SName:
               while(CurrChart!=-1)
                 {
                  //iteramos a lista de gráficos e salvamos os identificadores de gráficos abertos do símbolo SName
                  if(ChartSymbol(CurrChart)==SName)
                    {
                     ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1);
                     ChartIDArray[i_id]=CurrChart;
                     i_id++;
                    }
                  CurrChart=ChartNext(CurrChart);
                 }

Feche todos os gráficos com identificadores armazenados na matriz ChartIDArray:

              for(i=0;i<i_id;i++)
                 {
                  if(!ChartClose(ChartIDArray[i]))
                    {
                     Print("Erro ao fechar o gráfico do símbolo ",SName,". Código de erro: ",GetLastError());
                     return;
                    }
                 }
  • depois que todos os gráficos são fechados, tente desabilitar o símbolo novamente na Observação do mercado e exclui-lo, caso contrário, uma mensagem de erro será registrada no log.

Como você pode ver, o script não toma em consideração o fechamento automático de posições no símbolo selecionado. Isso é feito para que a inicialização aleatória do script não afete as operações de negociação do usuário. Se você quiser excluir um símbolo cujas posições estão abertas, feche-as antecipadamente.

Considerada a criação e exclusão de símbolos, prosseguimos com a descrição do processo de criação de ticks e de barras.

Geração de ticks e barras

Após criar o símbolo, você precisa carregar nele o histórico de transações: você pode carregar barras e testar EAs e indicadores usando o modo de geração de ticks incorporado ao Testador de Estratégia, ou carregar ticks e barras e realizar testes com os ticks carregados. O método de carregamento de ticks e barras com base nos dados de preços existentes é mostrado no artigo, daí que ofereceremos scripts para geração automática de ticks e barras de acordo com as leis de distribuição.

Aqui está o código do script para gerar barras (GetCandle.mq5, arquivo do script):

//+------------------------------------------------------------------+
//|                                                    GetCandle.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property link      ""
#property version   "1.00"
#property script_show_inputs 

#include </Math/Stat/Beta.mqh>
#include </Math/Stat/Binomial.mqh>
#include </Math/Stat/Cauchy.mqh>
#include </Math/Stat/ChiSquare.mqh>
#include </Math/Stat/Exponential.mqh>
#include </Math/Stat/F.mqh>
#include </Math/Stat/Gamma.mqh>
#include </Math/Stat/Geometric.mqh>
#include </Math/Stat/Hypergeometric.mqh>
#include </Math/Stat/Logistic.mqh>
#include </Math/Stat/Lognormal.mqh>
#include </Math/Stat/NegativeBinomial.mqh>
#include </Math/Stat/NoncentralBeta.mqh>
#include </Math/Stat/NoncentralChiSquare.mqh>
#include </Math/Stat/NoncentralF.mqh>
#include </Math/Stat/NoncentralT.mqh>
#include </Math/Stat/Normal.mqh>
#include </Math/Stat/Poisson.mqh>
#include </Math/Stat/T.mqh>
#include </Math/Stat/Uniform.mqh>
#include </Math/Stat/Weibull.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum Distribution
  {
   Beta,
   Binomial,
   Cauchy,
   ChiSquare,
   Exponential,
   F,
   Gamma,
   Geometric,
   Hypergeometric,
   Logistic,
   Lognormal,
   NegativeBinomial,
   NoncentralBeta,
   NoncentralChiSquare,
   NoncentralF,
   NoncentralT,
   Normal,
   Poisson,
   T,
   Uniform,
   Weibull
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
/*input params*/
input string SName="ExampleCurrency";
input datetime TBegin=D'2018.01.01 00:00:00';    //Hora de início da geração de barras
input datetime TEnd=D'2018.02.01 00:00:00';      //Hora final da geração de barras
input int BarForReplace=1000;                    //Número de barras após o qual são substituídas as barras
input double BaseOCHL=1;                         //Valor base do preço OCHL
input double dOCHL=0.001;                        //Taxa de variação do preço OCHL
input ulong BaseRealVol=10000;                   //Valor base do volume
input ulong dRealVol=100;                        //Taxa de variação do volume real
input ulong BaseTickVol=100;                     //Valor base do volume
input ulong dTickVol=10;                         //Taxa de variação do volume de ticks
input ulong BaseSpread=0;                        //Valor base do spread
input ulong dSpread=1;                           //Taxa de variação do spread
input Distribution DistOCHL=Normal;              //Tipo de distribuição para os preços OCHL
input Distribution DistRealVol = Normal;         //Tipo de distribuição para volume real
input Distribution DistTickVol = Normal;         //Tipo de distribuição para o volume de ticks
input Distribution DistSpread = Uniform;         //Tipo de distribuição para ob spread
input bool DiffCandle=false;                     //Gerando velas de vários tipos
input double DistOCHLParam1=0;                   //Parâmetro 1 da distribuição para os preços OCHL
input double DistOCHLParam2=1;                   //Parâmetro 2 da distribuição para os preços OCHL
input double DistOCHLParam3=0;                   //Parâmetro 3 da distribuição para os preços OCHL
input double DistRealParam1=0;                   //Parâmetro 1 da distribuição para o volume real
input double DistRealParam2=1;                   //Parâmetro 2 da distribuição para o volume real
input double DistRealParam3=0;                   //Parâmetro 3 da distribuição para o volume real
input double DistTickParam1=0;                   //Parâmetro 1 da distribuição para o volume de ticks
input double DistTickParam2=1;                   //Parâmetro 2 da distribuição para o volume de ticks
input double DistTickParam3=0;                   //Parâmetro 3 da distribuição para o volume de ticks
input double DistSpreadParam1=0;                 //Parâmetro 1 da distribuição para o spread
input double DistSpreadParam2=50;                //Parâmetro 2 da distribuição para o spread
input double DistSpreadParam3=0;                 //Parâmetro 3 da distribuição para o spread
input bool FiveDayOfWeek=true;                   //true - não gerar ticks nos fins de semana
/*----input params----*/
int i_bar=0;                                     //contador, barras de minutos
MqlRates MRatesMin[];                            //matriz para armazenar barras como barras 
MqlDateTime  StructCTime;                        //estrutura para trabalhar com o tempo
int DistErr=0;                                   //número do erro
bool IsErr=false;                                //erro
double DistMass[4];                              //matriz para armazenar os valores OCHL gerados
int ReplaceBar=0;                                //número de barras substituídas
double BValue[1];                                //matriz para copiar o último preço Close
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   int i=0;                                      //contador de loops
   double MaxVal,MinVal;                         //valores High e Low
   int i_max,i_min;                              //índices dos maiores e menores valores da matriz DistMass 
   datetime TCurrent=TBegin;
   BValue[0]=BaseOCHL;
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//se o símbolo existir
     {
      while(TCurrent<=TEnd)
        {
         if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6)))
              {
               if(StructCTime.day_of_week==0)
                  TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               else
                  TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               if(TCurrent>=TEnd)
                 {
                  if(ReplaceBar==0)
                     Print("Não há trades no intervalo especificado");
                  return;
                 }
              }
           }
         ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);
         MRatesMin[i_bar].open=0;
         MRatesMin[i_bar].close=0;
         MRatesMin[i_bar].high=0;
         MRatesMin[i_bar].low=0;
         //preenchemos Open      
         if(i_bar>0)
            MRatesMin[i_bar].open=MRatesMin[i_bar-1].close;
         else
           {
            if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1))
               MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            else
               MRatesMin[i_bar].open=BValue[0];
           }
         //geramos os preços High, Low
         MaxVal=2.2250738585072014e-308;
         MinVal=1.7976931348623158e+308;
         i_max=0;
         i_min=0;
         for(i=0;i<3;i++)
           {
            DistMass[i]=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            if(IsErrCheck(DistErr)) return;
            if(MaxVal<DistMass[i])
              {
               MaxVal=DistMass[i];
               i_max=i;
              }
            if(MinVal>DistMass[i])
              {
               MinVal=DistMass[i];
               i_min=i;
              }
           }
         if(MaxVal<MRatesMin[i_bar].open)
            MRatesMin[i_bar].high=MRatesMin[i_bar].open;
         else
            MRatesMin[i_bar].high=MaxVal;
         if(MinVal>MRatesMin[i_bar].open)
            MRatesMin[i_bar].low=MRatesMin[i_bar].open;
         else
            MRatesMin[i_bar].low=MinVal;
         //preenchemos Close
         for(i=0;i<3;i++)
            if((i!=i_max) && (i!=i_min))
              {
               MRatesMin[i_bar].close=DistMass[i];
               break;
              }
         //geramos o volume, spread
         MRatesMin[i_bar].real_volume=(long)(BaseRealVol+dRealVol*GetDist(DistRealVol,DistRealParam1,DistRealParam2,DistRealParam3));
         if(IsErrCheck(DistErr)) return;
         MRatesMin[i_bar].tick_volume=(long)(BaseTickVol+dTickVol*GetDist(DistTickVol,DistTickParam1,DistTickParam2,DistTickParam3));
         if(IsErrCheck(DistErr)) return;
         MRatesMin[i_bar].spread=(int)(BaseSpread+dSpread*GetDist(DistSpread,DistSpreadParam1,DistSpreadParam2,DistSpreadParam3));
         if(IsErrCheck(DistErr)) return;
         //registramos o tempo
         MRatesMin[i_bar].time=TCurrent;
         if(DiffCandle)
           {
            i=MathRand()%5;
            switch(i)
              {
               case 0://Doji
                 {
                  MRatesMin[i_bar].close=MRatesMin[i_bar].open;
                  break;
                 }
               case 1://Hammer
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                    }
                  break;
                 }
               case 2://Star
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                    }
                  break;
                 }
               case 3://Maribozu
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                    {
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                    }
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                       {
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                       }
                    }
                  break;
                 }
               default: break;
              }
           }
         //verificamos se é hora de substituir as barras  
         if(i_bar>=BarForReplace-1)
           {
            ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time);
            TCurrent=TCurrent+60;
            BValue[0]=MRatesMin[i_bar].close;
            i_bar=0;
            ArrayFree(MRatesMin);
           }
         else
           {
            i_bar++;
            TCurrent=TCurrent+60;
           }
        }
      if(i_bar>0)
        {
         i_bar--;
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time);
        }
     }
   else
      Print("O símbolo ",SName," não existe");
  }
//+------------------------------------------------------------------+

void ReplaceHistory(datetime DBegin,datetime DEnd)
  {
   ReplaceBar=CustomRatesReplace(SName,DBegin,DEnd,MRatesMin);
   if(ReplaceBar<0)
      Print("Erros ao substituir barras. Código de erro: ",GetLastError());
   else
      PrintFormat("Histórico de preços para o período: %s - %s formado com sucesso. %i barras criadas, %i barras adicionadas (substituídas)",TimeToString(DBegin),TimeToString(DEnd),i_bar+1,ReplaceBar);
  }
//+------------------------------------------------------------------+

double GetDist(Distribution d,double p1,double p2,double p3)
  {
   double res=0;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
   switch(d)
     {
/*Distribuição beta*/
      //p1,p2 - o primeiro e o segundo parâmetro 
      case Beta: {res=MathRandomBeta(p1,p2,DistErr); break;}
/*Distribuição binomial*/
      //p1 - número de testes, p2 - probabilidade de sucesso para cada teste    
      case Binomial: {res=MathRandomBinomial(p1,p2,DistErr); break;};
/*Distribuição de Cauchy*/
      //p1 - coeficiente de deslocamento, p2 - fator de escala
      case Cauchy: {res=MathRandomCauchy(p1,p2,DistErr); break;};
/*Distribuição de qui ao quadrado*/
      //p1 - número de graus de liberdade
      case ChiSquare: {res=MathRandomChiSquare(p1,DistErr); break;};
/*Distribuição exponencial*/
      //p1 - parâmetro (lambda) da distribuição 
      case Exponential: {res=MathRandomExponential(p1,DistErr); break;};
/*Distribuição de Fisher*/
      //p1, p2 - número de graus de liberdade
      case F: {res=MathRandomF(p1,p2,DistErr); break;};
/*Distribuição gama*/
      //p1 - parâmetro de distribuição (inteiro), p2 - fator de escala
      case Gamma: {res=MathRandomGamma(p1,p2,DistErr); break;};
/*Distribuição geométrica*/
      //p1 - taxa de sucesso (ocorrência do evento no teste)
      case Geometric: {res=MathRandomGeometric(p1,DistErr); break;};
/*Distribuição hipergeométrica*/
      //p1 - número total de objetos, p2 - número de objetos com a característica desejada, p3 - número de objetos na amostra
      case Hypergeometric: {res=MathRandomHypergeometric(p1,p2,p3,DistErr); break;};
/*Distribuição logística*/
      //p1 - valor esperado, p2 - fator de escala 
      case Logistic: {res=MathRandomLogistic(p1,p2,DistErr); break;};
/*Distribuição logarítmico-normal*/
      //p1 - logaritmo do valor esperado, p2 - logaritmo de desvio padrão
      case Lognormal: {res=MathRandomLognormal(p1,p2,DistErr); break;};
/*Distribuição binomial negativa*/
      //p1 - número de testes bem sucedidos, p2 - probabilidade de sucesso  
      case NegativeBinomial: {res=MathRandomNegativeBinomial(p1,p2,DistErr); break;};
/*Distribuição beta não-central*/
      //p1,p2 - primeiro e o segundo parâmetro, p3 - parâmetro de não centralidade       
      case NoncentralBeta: {res=MathRandomNoncentralBeta(p1,p2,p3,DistErr); break;};
/*Qui-quadrado não-central*/
      //p1 - número de graus de liberdade, p2 - parâmetro de não centralidade
      case NoncentralChiSquare: {res=MathRandomNoncentralChiSquare(p1,p2,DistErr); break;};
/*Distribuição F de Fisher-Snedecor não-central*/
      //p1, p2 - número de graus de liberdade, p3 - parâmetro de não centralidade
      case NoncentralF: {res=MathRandomNoncentralF(p1,p2,p3,DistErr); break;};
/*Distribuição t de Student não-central*/
      //p1 - número de graus de liberdade, p2 - parâmetro de não centralidade
      case NoncentralT: {res=MathRandomNoncentralT(p1,p2,DistErr); break;};
/*Distribuição normal*/
      //p1 - valor esperado, p2 - desvio padrão
      case Normal: {res=MathRandomNormal(p1,p2,DistErr); break;};
/*Distribuição de Poisson*/
      //p1 - valor esperado
      case Poisson: {res=MathRandomPoisson(p1,DistErr); break;};
/*Distribuição t de Student*/
      //p1 - número de graus de liberdade
      case T: {res=MathRandomT(p1,DistErr); break;};
/*Distribuição uniforme*/
      //p1 - limite inferior do intervalo, p2 - limite superior do intervalo
      case Uniform: {res=MathRandomUniform(p1,p2,DistErr); break;};
/*Distribuição de Weibull*/
      //p1 - parâmetro do formulário, p2 - parâmetro de escala
      case Weibull: {res=MathRandomWeibull(p1,p2,DistErr); break;};
     }
   if(DistErr!=0)
      return -1;
   else
      return res;
  }
//+------------------------------------------------------------------+
bool IsErrCheck(int Err)
  {
//verificamos se há erro ao gerar números pseudo-aleatórios
   switch(DistErr)
     {
      case(1):
        {
         MessageBox("Os parâmetros de distribuição especificados não são números reais","Erro nos parâmetros de entrada",MB_ICONWARNING);
         return true;
        }
      case(2):
        {
         MessageBox("Os parâmetros de distribuição especificados não são válidos","Erro nos parâmetros de entrada",MB_ICONWARNING);
         return true;
        }
      case(4):
        {
         MessageBox("Divisão de erro por zero","Erro nos parâmetros de entrada",MB_ICONWARNING);
         return true;
        }
     }
   return false;
  }
//+------------------------------------------------------------------+

Consideremos o funcionamento do script. O script usa arquivos da biblioteca padrão - localizada no diretório Estatísticas - que implementam várias distribuições estatísticas. Para selecionar a lei (tipo) de distribuição de acordo com a qual serão gerados números pseudo-aleatórios para formar os parâmetros de cada barra, é criada a enumeração Distribution.

Usando números pseudo-aleatórios, são gerados os preços Close, High, Low, volume real, volume de tick, spread usando a seguinte fórmula:

form1 (1)

Onde P(i) - valor do parâmetro, Base - valor base do parâmetro, step - fator de incremento (passo) da variável pseudo-aleatória, DistValue(i) - variável pseudo-aleatória gerada distribuída de acordo com a lei definida. O parâmetros Base e step são definidos pelo usuário. Como exemplo, veja o código para formar o valor do preço de abertura:

         if(i_bar>0)
            MRatesMin[i_bar].open=MRatesMin[i_bar-1].close;
         else
           {
            if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1))
               MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            else
               MRatesMin[i_bar].open=BValue[0];
           }

Se, antes de iniciar o script, faltarem barras ou por algum motivo a função CopyClose não conseguir copiar a última barra do histórico de preços, o preço de abertura da primeira barra será formado usando a fórmula descrita acima:

Para as barras subsequentes, o preço Open é igual ao preço Close anterior, ou seja, a nova barra é aberta no final da anterior. 

A função GetDist é responsável pela a geração do valor da variável pseudo-aleatória.

Para lidar com erros que ocorrem ao gerar a variável pseudo-aleatória, é criada a função IsErrCheck. Na entrada da função é recebido um código de erro, que é determinado durante a execução da função GetDist. Se ocorrer um erro, é interrompida a execução do script e é registrada uma mensagem de erro. O código das funções Getdist e Iserrcheck é dado no final do script.   

O script gera ticks de minutos e possui os seguintes recursos:

1) Os ticks são gerados apenas no intervalo de tempo especificado, é possível não gerar ticks nos fins de semana (parâmetro de entrada FiveDayOfWeek=true)

A desativação da geração de ticks nos fins de semana é implementada no seguinte código:

if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6)))
              {
               if(StructCTime.day_of_week==0)
                  TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               else
                  TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               if(TCurrent>=TEnd)
                 {
                  if(ReplaceBar==0)
                     Print("Não há trades no intervalo especificado");
                  return;
                 }
              }
           }

Se a hora atual coincidir com um dia de folga, ele muda para o próximo dia de negociação. 

2) O script permite substituir barras por partes, liberando memória após substituir as barras geradas

O número de barras que, após serem substituídas, faz com que seja redefinida a matriz de ticks gerados é definido como variável BarForReplace. A substituição de barras é implementada no seguinte código:

if(i_bar>=BarForReplace)
           {
            ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
            i_bar=0;
            ArrayFree(MRatesMin);
           }

Para substituir barras, é criada a função ReplaceHistory cujo código é dado no final do script. A substituição de barras é realizada pela função CustomRatesReplace. Após a substituição de barras, é zerado o contador de barras e é liberado o buffer da matriz dinâmica MRatesMin que armazena as barras criadas. 

3) O script permite gerar velas de vários tipos

A geração de vários tipos de velas é implementada da seguinte forma (parâmetro DiffCande = true):

 if(DiffCandle)
           {
            i=MathRand()%5;
            switch(i)
              {
               case 0://Doji
                 {
                  MRatesMin[i_bar].close=MRatesMin[i_bar].open;
                  break;
                 }
               case 1://Hammer
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                    }
                  break;
                 }
               case 2://Star
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                    }
                  break;
                 }
               case 3://Maribozu
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                    {
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                    }
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                       {
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                       }
                    }
                  break;
                 }
               default: break;
              }
           }

Assim, o script permite gerar as habituais velas longas ou curtas "doji", "hammer", "star" e "mariboza" com a mesma probabilidade.

Vamos mostrar o trabalho do script, para isso, inicie o script com os seguintes parâmetros de entrada:

input

Fig. 1. Parâmetros de entrada do script

Como resultado, aparece um novo símbolo ExampleCurrency. Durante a operação do script, são geradas 33 121 barras de minutos. A Figura 2 mostra um fragmento do gráfico de minutos do símbolo ExampleCurrency.

ExChart

Fig. 2. Gráfico de minutos do símbolo ExampleCurrency

Às vezes, as barras de minutos podem não ser suficientes para testar um EA ou indicador, e o teste é realizado em ticks reais ou modelados. 

Considere um script que simula ticks e formula barras de minutos com base em ticks modelados. O código completo do script é fornecido no arquivo GetTick.mq5 anexado ao artigo. Aqui está o código da função OnStart():

void OnStart()
  {
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//se o símbolo existir
     {
      MqlDateTime  StructCTime;
      long TBeginMSec=(long)TBegin*1000;      //tempo de início da geração de ticks em ms
      long TEndMSec=(long)TEnd*1000;          //hora de término da geração de ticks em ms
      int ValMsec=0;                          //variável para gerar um deslocamento de tempo aleatório em ms
      int SumSec=0;                           //contador de segundos
      int SumMSec=0;                          //contador de milissegundos
      int PrevTickCount=0;                    //variável para armazenar os ticks anteriores num minuto
      datetime TCurrent=TBegin;
      bool   NewMinute=false;
       //copiamos o valor do preço no qual começará a geração de ticks
      if(CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1)
         BValue[0]=Base;
      //preenchemos a estrutura LastTick
      LastTick.ask=BValue[0];
      LastTick.bid=BValue[0];
      LastTick.last=BValue[0];
      LastTick.volume=baseVol;

      while(TBeginMSec<=TEndMSec)
        {
         if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if((StructCTime.day_of_week==0) || (StructCTime.day_of_week==6))
              {
               if(StructCTime.day_of_week==0)
                 {
                  TCurrent=TCurrent+86400;
                  TBeginMSec=TBeginMSec+86400000;
                 }
               else
                 {
                  TCurrent=TCurrent+2*86400;
                  TBeginMSec=TBeginMSec+2*86400000;
                 }
               if(TBeginMSec>=TEndMSec)
                  break;
              }
           }
         GetTick(TCurrent,TBeginMSec);
         if(IsErrCheck(DistErr)) return;
         i_tick++;

         if(RandomTickTime)
           {
            //geramos o deslocamento aleatório de tempo
            
            ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1);
            SumSec=SumSec+ValMsec;
            SumMSec=SumMSec+ValMsec;
            if(i_tick-PrevTickCount>=MaxTickInMinute)
              {
               TimeToStruct(TCurrent,StructCTime);
               StructCTime.sec=0;
               TCurrent=StructToTime(StructCTime)+60;
               TBeginMSec=TBeginMSec+60000-SumSec+ValMsec;
               SumSec=0;
               SumMSec=0;
               NewMinute=true;
              }
            else
              {
               if(SumSec>=60000)
                 {
                  //redefinimos o contador de ticks por minuto
                  SumSec=SumSec-60000*(SumSec/60000);
                  NewMinute=true;
                 }
               //formação de um novo tempo de tick    
               TBeginMSec=TBeginMSec+ValMsec;
               if(SumMSec>=1000)
                 {
                  TCurrent=TCurrent+SumMSec/1000;
                  SumMSec=SumMSec-1000*(SumMSec/1000);
                 }
              }
           }
         else
           {
            TBeginMSec=TBeginMSec+60000/MaxTickInMinute;
            SumSec=SumSec+60000/MaxTickInMinute;
            SumMSec=SumMSec+60000/MaxTickInMinute;
            if(SumMSec>=1000)
              {
               TCurrent=TCurrent+SumMSec/1000;
               SumMSec=SumMSec-1000*(SumMSec/1000);
              }
            if(SumSec>=60000)
              {
               SumSec=SumSec-60000*(SumSec/60000);
               NewMinute=true;
              }
           }
         if(NewMinute)
           {
            //adicionamos a nova barra à matriz 
            ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);
            if(ArraySize(MRatesMin)==1)//se o primeiro minuto
              {
               MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick;
              }
            else
              {
               MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount;
              }
            MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits);
            if(ValHigh>MRatesMin[i_bar].open)
               MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits);
            else
               MRatesMin[i_bar].high=MRatesMin[i_bar].open;
            if(ValLow<MRatesMin[i_bar].open)
               MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits);
            else
               MRatesMin[i_bar].low=MRatesMin[i_bar].open;
            MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume;
            MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point);
            TimeToStruct(MTick[i_tick-1].time,StructCTime);
            StructCTime.sec=0;
            MRatesMin[i_bar].time=StructToTime(StructCTime);
            i_bar++;
            PrevTickCount=i_tick;
            ValHigh=2.2250738585072014e-308;
            ValLow=1.7976931348623158e+308;
            NewMinute=false;
            if(i_bar>=BarForReplace)
              {
               ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
               LastTick.bid=MTick[i_tick-1].bid;
               LastTick.ask=MTick[i_tick-1].ask;
               LastTick.last=MTick[i_tick-1].last;
               LastTick.volume=MTick[i_tick-1].volume;
               i_tick=0;
               i_bar=0;
               PrevTickCount=0;
               ArrayFree(MRatesMin);
               ArrayFree(MTick);
              }
           }
        }//end while
      if(i_bar>0)
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
     }
   else
      Print("O símbolo ",SName," não existe");
  }

No início da função OnStart(), ​​são inicializadas as variáveis. Geração de ticks e barras é executada no ciclo principal do script:

while(TBeginMSec<=TEndMSec)
{
...
}

No início do ciclo, caso o parâmetro FiveDayOfWeek = true, é desativada a geração de ticks nos fins de semana, adicionalmente, a hora do tick é alterado em 1 dia (se o horário do tick corresponder ao domingo) ou 2 dias (se o horário do tick corresponder ao sábado):

if(FiveDayOfWeek)
{
...
}

Em seguida, um tick é gerado usando a função GetTick:

 GetTick(TCurrent,TBeginMSec);
	if(IsErrCheck(DistErr)) return;
 	i_tick++;

Se ocorrer um erro durante a geração do tick (o valor da função IsErrCheck = true), o script será finalizado. A função IsErrCheck é descrita acima no código do script GetCandle.

Considere a função GetTick:

void GetTick(datetime TDate,long TLong)
  {
   ArrayResize(MTick,ArraySize(MTick)+1);
//preenchemos o novo tempo  
   MTick[i_tick].time=TDate;
   MTick[i_tick].time_msc=TLong;
//preenchemos o tick atual com o valor do anterior
   if(ArraySize(MTick)>1)
     {
      MTick[i_tick].ask=MTick[i_tick-1].ask;
      MTick[i_tick].bid=MTick[i_tick-1].bid;
      MTick[i_tick].volume=MTick[i_tick-1].volume;
      MTick[i_tick].last=MTick[i_tick-1].last;
     }
   else
     {
      MTick[i_tick].ask=LastTick.ask;
      MTick[i_tick].bid=LastTick.bid;
      MTick[i_tick].last=LastTick.last;
      MTick[i_tick].volume=LastTick.volume;
     }
//preenchemos o tick atual  
   if(RandomTickValue)
     {
      double RBid=MathRandomUniform(0,1,DistErr);
      double RAsk=MathRandomUniform(0,1,DistErr);
      double RVolume=MathRandomUniform(0,1,DistErr);
      if(RBid>=0.5)
        {
         if(i_tick>0)
            MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
         MTick[i_tick].last=MTick[i_tick].bid;
         MTick[i_tick].flags=10;
         if(RAsk>=0.5)
           {
            MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
            MTick[i_tick].flags=MTick[i_tick].flags+4;
           }
         if(RVolume>=0.5)
           {
            MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
            MTick[i_tick].flags=MTick[i_tick].flags+16;
           }
        }
      else
        {
         if(RAsk>=0.5)
           {
            MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
            MTick[i_tick].flags=4;
            if(RVolume>=0.5)
              {
               MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
               MTick[i_tick].flags=MTick[i_tick].flags+16;
              }
           }
        }
     }//end if(RandomTickValue)
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
   else
     {
      MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
      MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
      MTick[i_tick].last=MTick[i_tick].bid;
      MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
      MTick[i_tick].flags=30;
     }//end if(RandomTickValue)  
  //armazenamos os valores mais altos e mais baixos para a formação de barras de minutos  
   if(MTick[i_tick].bid>ValHigh)
      ValHigh=MTick[i_tick].bid;
   if(MTick[i_tick].bid<ValLow)
      ValLow=MTick[i_tick].bid;
  }//end 

A entrada da função recebe o tempo do tick no formato datetime e em milissegundos. Primeiro, o tick atual é preenchido com os valores do anterior, depois, os valores do tick atual são alterados da seguinte forma:

1) Se o valor do parâmetro RandomTickValue = true, cada um dos parâmetros Ask, Bid e Volume será alterado com uma probabilidade de 0.5. Para isso, são geradas 3 variáveis aleatórias uniformemente distribuídas :

      double RBid=MathRandomUniform(0,1,DistErr);
      double RAsk=MathRandomUniform(0,1,DistErr);
      double RVolume=MathRandomUniform(0,1,DistErr);

Se RBid>0.5, RAsk>0.5 - os preços Bid e/ou Ask são alterados de acordo com a fórmula (1), descrita acima para o script GetCandle. O volume é alterado de acordo com a fórmula (1), caso mude o preço Ask ou Bid e o parâmetro RVolume > 0.5. O valor do preço Bid é atribuído ao preço Last quando ele muda.

2) Se o valor do parâmetro RandomTickValue = false, os valores dos parâmetros Ask, Bid e Volume são calculados de acordo com a fórmula (1). 

O valor do sinalizador dos ticks (flags) é definido da seguinte maneira:

  • alteração no preço Bid - flags=flags+2
  • alteração no preço Ask - flags=flags+4
  • alteração no preço Last - flags=flags+8
  • alteração no preço Volume - flags=flags+16

Alterados os preços Ask, Bid ou Volume, nas variáveis ValHigh e ValLow são armazenados os valores do preço Bid máximo e mínimo. Os valores das variáveis ValHigh e ValLow são utilizados para formar os preços High e Low da barra de minutos. 

Continuemos estudando o código da função OnStart().

Após gerar o tick atual, é realizada a formação do tempo do aparecimento do tick novo:

1) Se o parâmetro  RandomTickTime = true, o novo tempo do tick é formado da seguinte maneira:

ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1);

Em seguida, é verificada a ocorrência de uma nova barra de minutos e é ajustada a hora atual para o número de segundos gerado. O número de ticks que podem ser formados dentro de um minuto é limitado pela variável MaxTickInMinute. Se o número de ticks gerados exceder o valor da variável MaxTickInMinute, são redefinidos os contadores de segundos (Sumsec) e de milissegundos (SumMSec) e é formada uma nova barra de minutos (NewMinute = true). 

2) Se o parâmetro RandomTickTime = false, em cada barra de minutos é gerado o mesmo número de ticks, definido na variável MaxTickInMinute.

A formação da barra de minutos baseada nos ticks gerados ocorre da seguinte maneira:

  • matriz de barras de minuto aumenta 1

 ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);

  • são formados valor do preço de abertura da barra atual e o volume de ticks:

            if(ArraySize(MRatesMin)==1)//se o primeiro minuto
              {
               MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick;
              }
            else
              {
               MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount;
              }

Ao formar a primeira barra de minutos, ao preço de abertura é atribuído o preço Bid do último tick da substituição de tick anterior (ticks e barras são substituídos usando a função ReplaceHistory) ou o valor base do preço Bid (parâmetro Base), se a substituição de ticks e barras não tiver sido realizada anteriormente. Ao formar as seguintes barras de minutos, o preço de abertura é atribuído ao valor normalizado do preço Bid do último tick (preço de fechamento) do minuto anterior. A normalização é entendida como arredondamento conforme ao preço do símbolo do gráfico atual no qual o script está sendo executado. 

  • preço Close formado - preço Bid normalizado do último tick:

MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits);

  • valores dos preços High e Low formados. Isso leva em conta que o valor do preço de abertura pode ser maior que o maior (Valhigh) ou menor que o menor (Vallow) preço Bid dos ticks gerados:

 if(ValHigh>MRatesMin[i_bar].open)
     MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits);
 else
     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
 if(ValLow<MRatesMin[i_bar].open)
     MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits);
 else
     MRatesMin[i_bar].low=MRatesMin[i_bar].open;

MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume;
MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point);

O volume do último tick é atribuído ao volume da barra atual, o spread é calculado como a diferença entre os preços Bid e Ask do último tick. 

TimeToStruct(MTick[i_tick-1].time,StructCTime);
StructCTime.sec=0;
MRatesMin[i_bar].time=StructToTime(StructCTime);

Neste ponto, termina o processo de formação do parâmetro da barra de minutos atual, é aumentado o contador de barras de minutos (variável i_bar), às variáveis ValHigh e ValLow são atribuídos os valores mínimo e máximo do tipo de dado double, é redefinido o sinalizador da barra de minutos(NewMinute). Além disso, verifica-se se é hora de substituir o número formado de barras de minutos e ticks. O número de barras que desencadeia sua substituição é definido na variável BarForReplace

Após sair do ciclo principal, são substituídas as barras restantes, se houver:

     if(i_bar>0)
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);

Na função ReplaceHistory, é implementada a substituição de ticks e de barras. A função CustomTicksReplace substitui os ticks, enquanto a função CustomRatesReplace - as barras.

Assim, o script GetTick descrito acima permite gerar ticks como definido, formar barras de minutos e carregar os dados obtidos no histórico de preços do símbolo personalizados.

Modelando a tendência

Os scripts GetCandle e GetTick considerados na seção anterior permitem gerar um histórico de preços sem fortes flutuações (fase de correção). Para formação de situações de mercado mais complexas e testes de EAs e indicadores, é necessário modelar o movimento do preço na tendência.

Para fazer isso, foram escritos os scripts GetCandleTrend (anexado ao artigo no arquivo GetCandleTrend.mq5) e GetTickTrend (anexado ao artigo no arquivo GetTickTrend.mq5) para modelar tendências altistas ou baixistas de acordo com um determinado padrão de movimento de preços. O script GetCandleTrend é projetado para gerar barras de minuto crescentes ou decrescentes, o script Getticktrend gera ticks crescentes ou decrescentes, e, similarmente ao script GetCandleTrend, forma barras de minutos. 

Considere o funcionamento do script GetCandleTrend. A formação de barras de minutos é semelhante ao script Getcandle, portanto, considere apenas o modo como se forma a tendência. Os dados de entrada do script contêm os seguintes parâmetros de tendência:

input TrendModel TModel = Linear;                //Modelo da tendência
input TrendType TType =  Increasing;             //Tipo de tendência (crescente, decrescente, aleatória)
input double RandomTrendCoeff=0.5;               //Coeficiente de tendência (se RandomTrendCoeff<0.5 a tendência decrescente é mais forte, se RandomTrendCoeff>0.5 - a crescente)
input double Coeff1=0.1;                         //Coeficiente k1 do modelo de tendência
input double Coeff2=0.1;                         //Coeficiente k2 do modelo de tendência
input double Coeff3=0.1;                         //Coeficiente k3 do modelo de tendência
input int CountCandle=60;                        //Intervalo de mudança aleatória da direção da tendência (em barras)

O usuário é solicitado a selecionar o modelo de tendência especificado na enumeração TrendModel:

enum TrendModel
  {
   Linear,
   Hyperbolic,
   Exp,
   Power,
   SecondOrderPolynomial,
   LinearAndPeriodic,
   LinearAndStochastic
  };

e o tipo de tendência definido na enumeração TrendType:

enum TrendType
  {
   Increasing,
   Decreasing,
   Random
  };

A tendência é formada de acordo com os seguintes modelos: linear, hiperbólico, exponencial, de potência, parabólica, linear-periódica e linear-estocástica. As fórmulas para a formação da tendência são mostradas na tabela 1:

Modelo da tendênciaFórmula
LinearLinear
Hiperbólicof2
Exponencialf3
Potencialf4
Parabólicof5
Periódico linearf6
Estocástico linearf7

T(i) - valor atual da tendência, k1, k2, k3 - coeficientes que influenciam a taxa de crescimento (baixista) da tendência, N(0,1) - variável aleatória, distribuída de acordo com a lei normal com valor esperado zero e uma variância unitária.

Como o tipo de tendência, propõe-se escolher ascendente, descendente ou aleatória. Uma tendência aleatória é uma tendência cuja direção muda após um determinado número de velas (o número de velas é definido pelo parâmetro CountCandle). 

Formação da tendência implementada em função ChooseTrend:

double ChooseTrend()
  {
   switch(TType)
     {
      case 0: return NormalizeDouble(BValue[0]+dOCHL*(GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
      case 1: return NormalizeDouble(BValue[0]+dOCHL*(-GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
      case 2:
        {
         if((i_trend%CountCandle==0) && (i_trend!=0))
           {
            if(i_bar!=0)
               BValue[0]=MRatesMin[i_bar-1].close;
            LastRand=MathRandomUniform(0,1,DistErr);
            i_trend=0;
           }
         if(LastRand>RandomTrendCoeff)
            return NormalizeDouble(BValue[0]+dOCHL*(GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
         else
            return NormalizeDouble(BValue[0]+dOCHL*(-GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
        }
      default:return 0;
     }
  }

No caso de uma tendência aleatória, para cada N velas de minutos definidas pelo parâmetro CountCandle, é gerada a variável aleatória LastRand uniformemente distribuída na faixa de 0 a 1. Se o valor LastRand for maior do que o parâmetro RandomTrendCoeff, a tendência será altista, caso contrário, baixista. Parâmetro RandomTrendCoeff permite variar a probabilidade de uma mudança na tendência. Se RandomTrendCoeff<0.5 for mais forte do que a tendência altista, se RandomTrendCoeff>0.5 - tendência baixista. 

A tendência deste modelo é formada na função GetModelTrend:

double GetModelTrend()
  {
   switch(TModel)
     {
      case Linear: return Coeff1+Coeff2*i_trend;
      case Hyperbolic:
        {
         if(i_trend==0)
            return Coeff1;
         else
            return Coeff1+Coeff2/i_trend;
        }
      case Exp:
        {
         if(i_trend==0)
            return Coeff1;
         else
            return Coeff1+MathExp(Coeff2*i_trend);
        }
      case Power:return Coeff1+MathPow((double)i_trend,Coeff2);
      case SecondOrderPolynomial:return Coeff1+Coeff2*i_trend+Coeff3*i_trend*i_trend;
      case LinearAndPeriodic: return Coeff1*i_trend+sin(Coeff2*i_trend)+cos(Coeff3*i_trend);
      case LinearAndStochastic:
        {
         LastValue=Coeff1*i_trend+MathSqrt(Coeff2*(1-MathPow(exp(-Coeff3),2)))*MathRandomNormal(0,1,DistErr)+exp(-Coeff3)*LastValue;
         return LastValue;
        }
      default:
         return -1;
     }
  }

Essa função implementa os modelos de tendência mostrados na tabela 1.

Considere a formação de gráficos de preços para vários modelos de tendência. Crie uma tendência linear, para isso inicie o script GetTrendCandle com os seguintes parâmetros:

input2

Fig. 3. Parâmetros do script GetTrendCandle

Iniciado o script, abra o gráfico de minutos do símbolo ExampleCurrency:

Linear

Fig. 4. Gráfico de minutos do símbolo ExampleCurrency para um modelo de tendência linear

No gráfico, vemos que foi gerada uma tendência linear, você pode alterar o ângulo de inclinação (taxa de aumento/diminuição) da tendência variando os coeficientes k1 e k2. 

Nos parâmetros do script, especifique o modelo de tendência hiperbólica: TModel = Hyperbolic, Coeff1 = 1, Coeff2 = 1000. Iniciado o script, obtemos o seguinte gráfico:

Params

Fig. 4. Gráfico de minutos do símbolo ExampleCurrency para o modelo de tendência hiperbólica

Uma característica deste modelo é que, como a função hiperbólica é uma função inversa, ao escolher uma tendência crescente (TType = Increasing), a tendência será decrescente. 

Considere o modelo de tendência exponencial: TModel =Exp, Coeff1 = 1, Coeff2 = 0,1. Iniciado o script, obtemos o seguinte gráfico:

exp

Fig. 5. Gráfico de minutos do símbolo ExampleCurrency para um modelo de tendência exponencial

Como seria de esperar, o gráfico mostra uma tendência crescente exponencial com um aumento do tamanho de velas.

Considere outros modelos de tendência:

Modelo de tendência de potência:  TModel =Power, Coeff1 = 1, Coeff2 = 2. 

Power

Fig. 6. Gráfico de minutos do símbolo ExampleCurrency para o modelo de tendência de potência

Vemos que o gráfico é semelhante ao modelo de tendência exponencial, mas aumenta mais suavemente.

Modelo de tendência parabólica:  TModel = SecondOrderPolynomial, Coeff1 = 1, Coeff2 = 0,05, Coeff3 = 0,05. 

Parab

Fig. 7. Gráfico de minutos do símbolo ExampleCurrency para o modelo de tendência parabólica

O modelo parabólico é semelhante ao modelo de potência, mas tem uma taxa maior de aumento/diminuição da tendência.

Modelo de tendência periódica linear:  TModel = LinearAndPeriodic, Coeff1 = 0.05, Coeff2 = 0,1, Coeff3 = 0,1.

Periodic

Fig. 8. Gráfico de minutos do símbolo ExampleCurrency para um modelo de tendência periódico linear 

No modelo de tendência periódica linear, muda de direção de acordo com a lei periódica.

Modelo de tendência linear-estocástico:  TModel = LinearAndStochastic, Coeff1 = 0.05, Coeff2 = 1, Coeff3 = 1.

Stoh

Fig. 8. Gráfico de minutos do símbolo ExampleCurrency para um modelo de tendência linear-estocástico 

No modelo de tendência linear-estocástico aumenta ou diminui, fazendo oscilações aleatórias em torno de uma linha reta cuja inclinação é determinada pelo coeficiente k1.

Os modelos de tendência acima têm propriedades especificadas apenas em intervalos de minutos, em intervalos de M15, M30, H1 e superior, os gráficos de preços formados nesses modelos parecem funções lineares. Para obter uma tendência que mude sua direção em outros intervalos, exceto em M1, você deve selecionar o tipo de tendência aleatória (TType = Random) e especificar o número de velas de minutos nas quais será feita a tentativa de alterar a direção da tendência.

Inicie o script com os seguintes parâmetros:  TModel = LinearAndStochastic, TType =  Random, Coeff1 = 0.05, Coeff2 = 1, Coeff3 = 1, RandomTrendCoeff=0.5, CountCandle=60. Obtenha o seguinte gráfico no intervalo H1:

H1_1

Fig. Gráfico de 9 horas do símbolo ExampleCurrency com mudança aleatória de tendência

Defina o parâmetro RandomTrendCoeff = 0.7 e inicie o script:

H1_low

Fig. Gráfico de 10 horas do símbolo ExampleCurrency com uma mudança aleatória na tendência com RandomTrendCoeff = 0.7

Vemos que há uma tendência decrescente, mude RandomTrendCoeff = 0.3 e obtenha uma tendência crescente:

H1_high

Fig. Gráfico de 10 horas do símbolo ExampleCurrency com uma mudança aleatória na tendência com RandomTrendCoeff = 0.3

Assim, usando o script GetCandleTrend é possível formar uma tendência em diferentes intervalos de tempo, gerando, ao mesmo tempo, barras de minutos. 

O script GetTickTrend permite gerar ticks e, a partir deles, formar barras de minutos e ter os mesmos recursos que o script GetCandleTrend.

Modelagem de padrões gráficos

Padrões gráficos são amplamente utilizados na análise técnica de mercado. Muitos traders usam padrões típicos para procurar pontos de entrada ou de saída do mercado, além disso, vários indicadores e EAs estão sendo desenvolvidos para analise de padrões no gráfico de preços. 

Nesta seção, mostraremos como criar padrões gráficos usando os scripts descritos acima. Como exemplo, considere o processo de criação dos padrões "Topo Duplo" e "Fundo Duplo". Estes são os padrões:

pattern2

Fig. 11. Padrão "Topo Duplo"

pattern2

Fig. 12. Padrão "Fundo Duplo"

Para criar padrões, será usado o script GetCandleTrend. Padrões serão formados no período H1. Para formar cada padrão, você precisa iniciar o script GetCandleTrend quatro vezes com vários parâmetros de entrada. Escolha os seguintes intervalos de tempo, indicados nas figuras 11 e 12 como t1-t5:

Para gerar o padrão "Topo Duplo", defina as seguintes configurações de script:

  • Hora de início da geração das barras: 00:00 02.01.2018
  • Hora final da geração de barras: 12:00 02.01.2018
  • Modelo de tendência: LinearAndStochastic
  • Tipo de tendência: Random
  • Coeficiente de tendência: 0.15 
  • Coeficiente k1 do modelo de tendência: 0.15
  • Coeficiente k2 do modelo de tendência: 1 
  • Coeficiente k3 do modelo de tendência: 1 
  • Intervalo de mudança aleatória da direção da tendência: 60
As configurações restantes devem permanecer padrão e inicie o script. Em seguida, para a segunda, terceira e quarta inicialização do script, altere as seguintes configurações:

Inicialização №2:

  • Hora de início da geração das barras: 13:00 02.01.2018
  • Hora final da geração de barras: 12:00 03.01.2018
  • Coeficiente de tendência: 0.85 
Inicialização №3:

  • Hora de início da geração das barras: 13:00 03.01.2018
  • Hora final da geração de barras: 12:00 04.01.2018
  • Coeficiente de tendência: 0.15 
Inicialização №4:

  • Hora de início da geração das barras: 13:00 04.01.2018
  • Hora final da geração de barras: 00:00 05.01.2018
  • Coeficiente de tendência: 0.85 

Como resultado, após quatro inicializações do script GetCandleTrend, é obtido o gráfico de preços mostrado na Figura 13.

2high

Fig. 13. Padrão "Topo Duplo" modelado no período H1

Da mesma forma, modele o padrão "Fundo duplo". Para fazer isso, incie o script GetCandleTrend quatro vezes com as configurações especificadas para o padrão "Topo Duplo", alterando apenas o coeficiente de tendência: 0,85 para a primeira execução, 0,15, 0,85, 0,15 para a próxima. O resultado do script é mostrado na Figura 14.

2low

Fig. 14. Padrão "Fundo Duplo" modelado no período H1

Da mesma forma, você pode modelar outros padrões. Os padrões mais realistas são obtidos no gráfico de minutos. Para formar padrões em outros intervalos de tempo, é necessário - no parâmetro "Intervalo de mudança aleatória na direção da tendência"do script GetCandleTrend - especificar o número de velas de minutos contidas no intervalo selecionado, por exemplo, para o intervalo H1 - 60, para o intervalo H4 - 240.

Fim do artigo

Os símbolos personalizados são uma ferramenta conveniente e útil para testar EAs e indicadores. No artigo, criamos e revisamos scripts com os seguintes recursos:

1) Criação e exclusão de símbolos personalizados

Foi mostrado como criar um símbolo personalizado com base num existente ou novo com propriedades especificadas manualmente. O script que implementa a remoção de símbolo permite fechar todos os gráficos contendo um símbolo e desativá-lo na "Observação do mercado".

2) Geração de ticks e barras

Os scripts permitem gerar barras de minutos num determinado intervalo de tempo, com a capacidade de desativar a geração de barras nos fins de semana dias. Foram implementadas a geração de várias velas (longas ou curtas, doji, martelo, estrela e mariboza) e a substituição de barras e ticks por partes para economizar memória ao gerar grandes matrizes de barras e ticks.

3) Modelagem de tendências

Os scripts permitem modelar uma tendência usando vários modelos: linear, hiperbólico, exponencial, de potência, parabólico, linear-periódico e linear-estocástico. Também é possível modelar uma tendência para diferentes períodos.

4) Modelagem de padrões gráficos

Foi exemplificado o uso do script GetCandleTrend para criar os padrões "Topo Duplo" e "Fundo Duplo".

Os scripts apresentados podem ser usados para criar seu próprio histórico de preços, a partir de barras de minutos e ticks, para testes e para otimização de EAs e indicadores.