Oscilador universal com interface gráfica do usuário

13 dezembro 2016, 15:32
Dmitry Fedoseev
0
1 329

Conteúdo

Introdução

A seleção de um indicador adequado para um sistema de negociação muitas vezes é feita após minuciosas observações preliminares de vários indicadores no gráfico, isto é, iterando e experimentando com as opções. Se nós arrastarmos o indicador desde a janela do navegador ou abrirmos a janela de opções para alterar as configurações, poderíamos estar gastando muito tempo. Seria bom acelerar este processo.

Ao criar uma interface gráfica do usuário, disponível diretamente no gráfico, torna-se possível alterar rapidamente os parâmetros do indicador e acompanhar o desempenho da mudança introduzida. A combinação de vários indicadores e o uso de interface gráfica do usuário permitem alterar rapidamente os próprios indicadores.

Análise do problema

Em si mesmo, a criação de um oscilador universal não é uma tarefa particularmente difícil. É preciso um pouco de programação orientada a objetos, isto é, uma classe base e muitas subclasses.

Os parâmetros de cada indicador serão enviados para o construtor da subclasse. Neste caso, ao criar o objeto, o editor MetaEditor abrirá uma dica de tela com uma lista de opções, o que irá facilitar muito o processo de desenvolvimento (Fig. 1).

 
Fig. 1. Dica de tela com os parâmetros do construtor durante a criação de um objeto

A principal dificuldade poderia surgir no uso prático de tal indicador, uma vez que os conjuntos de parâmetros externos variam muito entre osciladores. Se, para cada oscilador, fazer que os parâmetros se diferenciem pelo prefixo, será possível usar o indicador manualmente, no entanto para utilização através da função iCustom() ou IndicatorCreate(), ele pode não ser adequado devido ao grande número de parâmetros. À função IndicatorCreate() é possível transferir não mais do que 256 parâmetros, enquanto à função iCustom(), não mais do que 64. Nesta quantidade são levados em conta os parâmetros universais do tipo de símbolo ou nome do indicador, portanto, o número real de variáveis disponíveis é um pouco menor. É possível usar um pequeno conjunto de parâmetros genéricos, mas, então, o uso do indicador será incômodo: terá que consultar sempre o manual para saber quais parâmetros são usados para o indicador desejado.

Ao usar a interface de usuário, resolve-se esse problema: em sua janela, você pode exibir controles que correspondem apenas ao indicador específico selecionado. A possibilidade de invocar este indicador pela função iCustom() ou IndicatorCreate() deve também ser fornecida, então na janela de propriedade do indicador são exibidos os parâmetros externos, mas, como escrito acima, este será um pequeno conjunto universal.

Conjunto de parâmetros

Definimos o conjunto mínimo exigido de parâmetros externos. Vemos, no terminal, a lista de osciladores: Menu Principal - Inserir - Indicadores - Osciladores, introduzimo-nos na tabela.

Tabela 1. Todos os osciladores do terminal

funçãoNomeBufferParâmetros
iATRAverage True Range1. linha1. int ma_period — período da média
iBearsPowerBears Power1. gráfico de barras1. int ma_period — período da média
iBullsPowerBulls Power1. linha1. int ma_period — período da média
iCCICommodity Channel Index1. linha1. int ma_period — período da média
2. ENUM_APPLIED_PRICE applied_price — tipo de preço
iChaikinOscilador Chaikin1. linha1. int fast_ma_period — período de rápido  
2. int slow_ma_period — período lento
3. ENUM_MA_METHOD ma_method — tipo de suavização
4. ENUM_APPLIED_VOLUME applied_volume — volume usado 
iDeMarkerDeMarker1. linha1. int ma_period — período da média
iForceForce Index1. linha1. int ma_period — período da média  
2. ENUM_MA_METHOD ma_method — tipo de suavização  
3. ENUM_APPLIED_VOLUME applied_volume — tipo de volume para cálculo 
iMomentumMomentum1. linha1. int mom_period — período da média
2. ENUM_APPLIED_PRICE applied_price — tipo de preço
iMACDMoving Averages Convergence-Divergence1. gráfico de barras
2. linha
1. int fast_ema_period — período da média rápida
2. int slow_ema_period — período de média lenta
3. int signal_period — período da média разности
4. ENUM_APPLIED_PRICE  applied_price — tipo de preço
iOsMAMoving Average of Oscillator (MACD histogram)1. gráfico de barras1. int fast_ema_period — período da média rápida 
2. int slow_ema_period — período de média lenta
3. int signal_period — período da média разности 
4. ENUM_APPLIED_PRICE  applied_price — tipo de preço
iRSIRelative Strength Index1. linha1. int ma_period — período da média 
2. ENUM_APPLIED_PRICE applied_price — tipo de preço
iRVIRelative Vigor Index1. linha
2. linha
1. int ma_period — período da média
iStochasticStochastic Oscillator1. linha
2. linha
1. int Kperiod — K-período (número de barras para cálculo)
2. int Dperiod — D-período (período da primeira suavização)
3. int slowing — suavização final
4. ENUM_MA_METHOD ma_method — tipo de suavização
5. ENUM_STO_PRICE price_field — método de cálculo do Stochastic 
iTriXTriple Exponential Moving Averages Oscillator1. linha1. int ma_period — período da média 
2. ENUM_APPLIED_PRICE applied_price — tipo de preço
iWPRWilliams' Percent Range1. linha1. int calc_period — período da média

Na coluna "Opções", elaboramos a lista de todos os tipos de parâmetros e definimos sua quantidade máxima.

Tabela 2. Tipos e número de parâmetros 

TipoNúmero
int3
ENUM_APPLIED_PRICE1
ENUM_MA_METHOD1
ENUM_APPLIED_VOLUME1
ENUM_STO_PRICE1
Na coluna "Buffers", é claro que podem ser utilizados dois indicadores de buffer e é necessário um tipo diferente de plotagem para diferentes indicadores. É evidente que todos eles podem ser desenhados com linhas, mas, uma vez que alguns deles devem ser exibidos no formulário de gráficos de barras e os recursos do terminal permitem fazer isso, ao alterar o tipo de indicador, tratamos de fornecer a alteração do tipo de plotagem. Além disso, será necessário pensar no desenho de níveis horizontais, uma vez que para alguns indicadores (RSI, CCI, etc.) é desejável sua presença no gráfico.

Plano de trabalho

Quanto maior o número de tarefas independentes conseguidas a partir da divisão de uma tarefa geral, será mais fácil leva-la a cabo. Por isso, todo o trabalho será composto de três etapas:

  1. Criação de classes para o oscilador universal e criação deste oscilador sem uma interface gráfica do usuário.
  2. Criação de classes para a interface gráfica do usuário.
  3. União entre o oscilador universal e uma interface gráfica do usuário. 

Um dos pontos importantes a que você deve prestar atenção são as configurações padrão. Os parâmetros devem ser definidos por meio da interface gráfica do usuário ou simplesmente através da janela de propriedades (para garantir a máxima versatilidade do indicador a criar). Ao definir parâmetros através da janela de propriedades, é necessário prever que - quanto às configurações padrão - todos os indicadores correspondam à sua aparência natural.

Vamos olhar os valores padrão para diferentes osciladores. Por exemplo, o Stochastic tem períodos 5, 3, 3 (o primeiro parâmetro é maior do que o segundo), enquanto o MACD possui 12, 26, 9 (o primeiro parâmetro é menor do que o segundo). No indicador MACD, o primeiro parâmetro consiste em um período de média rápida, enquanto o segundo é uma média lenta; portanto, o primeiro parâmetro deve ser menor que o segundo. Essa relação entre o primeiro e o segundo parâmetro é adequado para o oscilador Chaikin (nele também são definidos os períodos das médias lenta e rápida). Em relação ao indicador estocástico não é tão importante esta relação, em qualquer caso sua aparência vai corresponder com o movimento do preço. Se, para o indicador MACD, instalarmos o primeiro parâmetro maior do que o segundo, o indicador se moverá em direção oposta ao movimento do preço, (ao instalar as configurações padrão, isto deve ser evitado).

Ao utilizar a interface gráfica do usuário, gostaria de ver os indicadores começarem a trabalhar, em primeiro lugar, com o conjunto usual de parâmetros padrão: MACD com períodos 12, 26, 9, Stochastic com períodos 5, 3, 3, etc. Além disso, ao selecionar um novo indicador, é desejável ter a oportunidade de ele começar a funcionar quer com as configurações padrão quer com os mesmos parâmetros que estava usando o indicador anterior. Por exemplo, estudamos os indicadores RSI e CCI, e estamos interessados ​​em ver como o tipo de linha muda em diferentes indicadores com o mesmo valor de período. Isto se deve ter em mente no futuro ao desenvolver classes.

Criação da classe base de um indicador

Na pasta Include, criamos a pasta "UniOsc", nela estarão localzados todos os arquivos adicionais do indicador. O conjunto de osciladores é definido acima na tabela 1. De acordo com ele criamos uma enumeração que especifica o tipo de oscilador. É possível que a enumeração seja necessária não só no arquivo do indicador, por isso colocamo-la num arquivo separado com o nome "UniOscDefines.mqh" (o arquivo está localizado na pasta "UniOsc"): 

enum EOscUnyType{
   OscUni_ATR,
   OscUni_BearsPower,
   OscUni_BullsPower,  
   OscUni_CCI,
   OscUni_Chaikin,
   OscUni_DeMarker,
   OscUni_Force,
   OscUni_Momentum,
   OscUni_MACD,
   OscUni_OsMA,
   OscUni_RSI,
   OscUni_RVI,
   OscUni_Stochastic,
   OscUni_TriX,
   OscUni_WPR
};

Neste arquivo não haverá nada mais.

Criamos o arquivo "CUniOsc.mqh", para a classe do indicador, registramos nela o modelo da classe COscUni:

class COscUni{
   protected:
    
   public:

};

No modelo está definida a seção protected, já que alguns membros da classe devem ser protegidos mas disponíveis para os descendentes da classe (os membros da seção private estão protegidos, mas não estão disponíveis a descendentes).

O método básico da classe base é o método correspondente à função OnCalculate() do indicador, chamamo-lo Calculate(). Os primeiros dois parâmetros do método irão corresponder aos parâmetros da função OnCalculate(): rates_total (número total de barras) и prew_calculate (número de barras contadas). Como são usados dados de outro indicador, não é necessário transferir quaisquer matrizes com os dados do método Calculate(). Mas é necessário transferir dois indicadores de buffer que serão preenchidos com dados. Mesmo com a utilização de indicadores com um buffer ainda é necessário controlar a pureza do segundo buffer, de modo que, em qualquer caso, para o método Calcular() serão transferidos dois buffers de indicador. O código do método Calculate() vai depender do tipo de oscilador (de um buffer ou de dois) Assim, o método Calculate() será virtual:

virtual int Calculate( const int rates_total,
               const int prev_calculated,
               double & buffer0[],
               double & buffer1[]
){
   return(rates_total);
}

Ao carregar diferentes indicadores será preciso lidar com a variável para o identificador do indicador. Declaramo-lo na seção protected.

Além disso, serão necessárias diversas variáveis para diferentes propriedades de exibição de buffers. Estas propriedades serão determinadas ao carregar cada indicador individual, ou seja, os valores dessas variáveis ​​serão atribuídos nas subclasses:

int m_handle;           // identificador do indicador
int m_bufferscnt;       // número de buffers usados    
string m_name;          // nome do indicador      
string m_label1;        // nome do buffer 1    
string m_label2;        // nome do buffer 2
int m_drawtype1;        // tipo de desenho do buffer 1    
int m_drawtype2;        // tipo de desenho do buffer 2      
string m_help;          // pequena guia sobre os parâmetros do indicador
int m_digits;           // número de dígitos depois do ponto decimal nos valores do indicador
int m_levels_total;     // número de níveis
double m_level_value[]; // matriz para valores dos níveis

Após indicador carregar, será preciso verificar seu sucesso, portanto, será requerido um método adequado para verificar o identificador do indicador:

bool CheckHandle(){
   return(m_handle!=INVALID_HANDLE);
}

Ao alterar o oscilador através da interface gráfica do usuário, será necessário determinar se o indicador está completamente contado. Isso é feito pela função BarsCalculated() cuja chamada precisa do identificador do indicador. Então, adicionamos o método para obter o identificador:  

int Handle(){
    return(m_handle);
}

No construtor de classe, inicializamos o identificador, enquanto, no destrutor, verificamo-lo e, se necessário, chamamos a função IndicatorRelease():

void COscUni(){
   m_handle=INVALID_HANDLE;
}

void ~COscUni(){
   if(m_handle!=INVALID_HANDLE){
      IndicatorRelease(m_handle);
   }
} 

Fornecemos acesso ao resto das variáveis ​​que determinam a exibição de vários indicadores, criamos métodos para obter seus valores:

string Name(){ // nome do oscilador
   return(m_name);
}    
  
int BuffersCount(){ // número de buffers dos osciladores
   return(m_bufferscnt);
}

string Label1(){ // nome do primeiro buffer
   return(m_label1);
}

string Label2(){ // nome do segundo buffer
   return(m_label2);
}

int DrawType1(){ // tipo de desenho do primeiro buffer
   return(m_drawtype1);
}

int DrawType2(){ // tipo de desenho do segundo buffer
   return(m_drawtype2);
}  

string Help(){ // dica sobre o uso de parâmetros
   return(m_help);
}

int Digits(){ // número de dígitos após o ponto no indicador
   return(m_digits);
}

int LevelsTotal(){ // número de níveis do indicador
   return(m_levels_total);
}

double LevelValue(int index){ // obter o valor do nível de acordo com o índice
   return(m_level_value[index]);
}

Todos esses métodos retornam um valor correspondente à variável, e os valores das variáveis ​​serão atribuídos às subclasses dos osciladores.  

Subclasses Calculate

Criar duas subclasses: para os indicadores com um único buffer dois indicadores tampões. Para indicadores com um buffer:

class COscUni_Calculate1:public COscUni{
   public:
      void COscUni_Calculate1(){
         m_bufferscnt=1;
      }
      virtual int Calculate( const int rates_total,
                     const int prev_calculated,
                     double & buffer0[],
                     double & buffer1[]
      ){
        
         int cnt,start;
        
         if(prev_calculated==0){
            cnt=rates_total;
            start=0;
         }
         else{
            cnt=rates_total-prev_calculated+1;
            start=prev_calculated-1;
         }  
        
         if(CopyBuffer(m_handle,0,0,cnt,buffer0)<=0){
            return(0);
         }
        
         for(int i=start;i<rates_total;i++){
            buffer1[i]=EMPTY_VALUE;
         }        
        
         return(rates_total);
      }
};

Consideremos esta classe. A classe tem o construtor "COscUni_Calculate1", no construtor é definido o número de buffers, neste caso 1. No método Calculate(), de acordo com os valores das variáveis rates_total e prev_calculate, é calculado o número de elementos do buffer para copiar (variável cnt) e o índice da barra a partir da qual é necessário limpar o segundo buffer (variável start). Caso não dar certo a cópia de dados (ao chamar a função CopyBuffer()), a partir do método é retornado 0, para que no seguinte tick todos os cálculos sejam realizados desde o início. No final do método, como de costume, é retornado rates_total.

Subclasse para indicadores com dois buffers:

class COscUni_Calculate2:public COscUni{
   public:
      void COscUni_Calculate2(){
         m_bufferscnt=2;
      }  
      virtual int Calculate( const int rates_total,
                     const int prev_calculated,
                     double & buffer0[],
                     double & buffer1[]
      ){
         int cnt;
         if(prev_calculated==0){
            cnt=rates_total;
         }
         else{
            cnt=rates_total-prev_calculated+1;
         }          
         if(CopyBuffer(m_handle,0,0,cnt,buffer0)<=0){
            return(0);
         }
         if(CopyBuffer(m_handle,1,0,cnt,buffer1)<=0){
            return(0);
         }
         return(rates_total);
      }
};

Esta classe é ainda mais fácil do que a classe para indicadores com um buffer. No início do método Calculate(), é calculado o número de elementos (variável cnt) para cópia e leva-se a cabo a cópia de buffers. 

Subclasses dos indicadores

Agora criamos as subclasses diretamente para oscilador. Estas classes serão subclasses para a classe COscUni_Calculate1 ou COscUni_Calculate2. Em cada uma destas classes haverá apenas um construtor. Para o construtor de cada classe serão enviados os parâmetros correspondentes ao oscilador dessa classe, e um par de parâmetros adicionais no começo. Os parâmetros adicionais irão determinar se deve usar os valores dos parâmetros transferidos ​​para o construtor ou definir o valor padrão (variável use_default). O segundo parâmetro denominado keep_previous determina se deve definir o valor padrão para todos os parâmetros ou apenas para aqueles que nunca foram utilizados.

O primeiro indicador na lista é o ATR, começamos a escrever a subclasse para ele. Primeiro, modelo da classe:

class COscUni_ATR:public COscUni_Calculate1{
   public:
   void COscUni_ATR(bool use_default,bool keep_previous,int & ma_period){

   }
};

Nota: o parâmetro ma_period é transferido por referência, com o fim de, ao instalar os parâmetros para o indicador, ter por padrão acesso ao seu valor, no oscilador universal criado.

Escrevemos o código no construtor:

if(use_default){
   if(keep_previous){
      if(ma_period==-1)ma_period=14;
   }
   else{
      ma_period=14;
   }      
}  

Nesta parte do código, se use_default=true, é levada a cabo a definição das configurações padrão. Se keep_previous=true, o valor padrão é definido apenas se o parâmetro resultante é definido como -1, ou seja, não foi usado anteriormente. Por conseguinte, na inicialização do oscilador universal, será necessário atribuir o valor -1 a todas as variáveis ​​para os parâmetros estabelecidos.

Agora, a cadeia de caracteres mais importante de código no construtor da subclasse é o carregamento do indicator:

m_handle=iATR(Symbol(),Period(),ma_period);

No final de algumas cadeias de caracteres para definir os parâmetros de exibição:

m_name=StringFormat("ATR(%i)",ma_period); // nome do indicador
m_label1="ATR"; // nomes dos buffers
m_drawtype1=DRAW_LINE;  // tipo de desenho
m_help=StringFormat("ma_period - Period1(%i)",ma_period); // dica  
m_digits=_Digits+1; // número de dígitos depois do ponto decimal dos valores  
m_levels_total=0; // número de níveis    

Consideremos algumas das etapas envolvidas na criação de uma subclasse para um indicador mais complexo, nomeadamente, para o MACD. O princípio de criação é o mesmo, mas terá um pouco mais de código. Por isso, consideramos os fragmentos. Definição das configurações padrão:

if(use_default){
   if(keep_previous){
      if(fast_ema_period==-1)fast_ema_period=12;
      if(slow_ema_period==-1)slow_ema_period=26;
      if(signal_period==-1)signal_period=9;
      if(applied_price==-1)applied_price=PRICE_CLOSE;            
   }
   else{
      fast_ema_period=12;
      slow_ema_period=26;
      signal_period=9;
      applied_price=PRICE_CLOSE;
   }      
}

Definição dos parâmetros de exibição:

m_handle=iMACD(Symbol(),
               Period(),
               fast_ema_period,
               slow_ema_period,
               signal_period,
               (ENUM_APPLIED_PRICE)applied_price);

m_name=StringFormat( "iMACD(%i,%i,%i,%s)",
                     fast_ema_period,
                     slow_ema_period,
                     signal_period,
                     EnumToString((ENUM_APPLIED_PRICE)applied_price));
                    
m_label1="Main";
m_label2="Signal";      
m_drawtype1=DRAW_HISTOGRAM;            
m_drawtype2=DRAW_LINE;

m_help=StringFormat( "fast_ema_period - Period1(%i), "+
                     "slow_ema_period - Period2(%i), "+
                     "signal_period - Period3(%i), "+
                     "applied_price - Price(%s)",
                     fast_ema_period,
                     slow_ema_period,
                     signal_period,
                     EnumToString((ENUM_APPLIED_PRICE)applied_price));  
                    
m_digits=_Digits+1;

Damos uma olhada mais de perto nos parâmetros do construtor:

void COscUni_MACD(bool use_default,
                  bool keep_previous,
                  int & fast_ema_period,
                  int & slow_ema_period,
                  int & signal_period,
                  long & applied_price
){

Observe que a variável applied_price para a enumeração padrão ENUM_APPLIED_PRICE é declarada como long. Isto é feito a fim de ter a possibilidade de atribuir uma variável um valor -1, que indica que o parâmetro não foi usado.

Consideremos um outro fragmento a partir da classe para o indicador RSI, parte do código em que são instalados os níveis: 

m_levels_total=3;
ArrayResize(m_level_value,3);
m_level_value[0]=30;
m_level_value[1]=50;
m_level_value[2]=70;

É definido o número de níveis, alterado o tamanho da matriz e, ela por sua vez, é preenchida com os valores dos níveis.

Aqui não vamos dar mais detalhes de como foram criadas as classe dos outros osciladores. Anexado ao artigo há uma classe completamente pronta com um conjunto de osciladores (nome do arquivo "CUniOsc.mqh").

Criação de um oscilador universal (início)

Uma vez que as classes de osciladores estão prontas, com sua utilização já é possível criar um oscilador universal, mas sem a interface gráfica.

Crie um novo indicador, deixe seu nome ser "iUniOsc". Em seguida, no assistente de criação de indicador, selecione o tipo de função OnCalculate(...open,high,low,close), crie uma variável externa (para depois encontrar facilmente lugar para as variáveis ​​externas) e dois buffers de tipo Line.

Antes da variável externa, associe os arquivos com enumerações e classes de osciladores: 

#include <UniOsc/UniOscDefines.mqh>
#include <UniOsc/CUniOsc.mqh>

Crie uma variável externa para selecionar o tipo de oscilador:

input EOscUnyType          Type        =  OscUni_ATR;

Variáveis UseDefault e KeepPrevious:

input bool                 UseDefault  =  true;
input bool                 KeepPrev    =  true;

Variáveis universais diretamente para os parâmetros dos osciladores:

input int                  Period1     =  14;
input int                  Period2     =  14;
input int                  Period3     =  14;
input ENUM_MA_METHOD       MaMethod    =  MODE_EMA;
input ENUM_APPLIED_PRICE   Price       =  PRICE_CLOSE;  
input ENUM_APPLIED_VOLUME  Volume      =  VOLUME_TICK;  
input ENUM_STO_PRICE       StPrice     =  STO_LOWHIGH;

Alguns indicadores desenham uma linha, alguns duas. O primeiro buffer é exibido às vezes como uma linha, às vezes como um histograma. Seria bom que o buffer como linha tivesse uma cor brilhante, e o histograma fosse cinza, então vamos criar três variáveis para as cores:

input color                ColorLine1  =  clrLightSeaGreen;
input color                ColorLine2  =  clrRed;
input color                ColorHisto  =  clrGray;

Como é planejado criar uma interface gráfica do usuário, será possível mudar - sem reiniciar o indicador - o tipo de oscilador e valores de parâmetros, por isso criamos duplicados da variável Type e das variáveis para os parâmetros: 

int                  _Period1;
int                  _Period2;
int                  _Period3;
long                 _MaMethod;
long                 _Price;  
long                 _Volume;  
long                 _StPrice;
EOscUnyType          _Type;

Declaramos uma variável-ponteiro para o objeto do oscilador universal:

COscUni * osc;

Anunciamos um par de variáveis adicionais:

string ProgName;
string ShortName;

Essas variáveis irão ajudar a formar o nome do indicador exibido no canto superior esquerdo da subjanela. 

Agora vamos adicionar o código no fim da função OnInit(), mas primeiro vamos executar o trabalho preparatório. Preparamos as configurações para os osciladores de acordo com os valores das variáveis UseDefault e KeepPrevious (também atribuimos o valor da variável _Type), registramos isto na forma de função para que o código fique estruturado:

void PrepareParameters(){

   _Type=Type;

   if(UseDefault && KeepPrev){
      _Period1=-1;
      _Period2=-1;
      _Period3=-1;
      _MaMethod=-1;
      _Volume=-1;
      _Price=-1;  
      _StPrice=-1;
   }
   else{  
      _Period1=Period1;
      _Period2=Period2;
      _Period3=Period3;
      _MaMethod=MaMethod;
      _Volume=Volume;
      _Price=Price;  
      _StPrice=StPrice;
   }
}

Se forem usados UseDefault e KeepPrevious, a todas as é atribuído o valor -1, para, no construtor de classe, distinguir variáveis que nós ainda não usamos e definir para eles valores padrão. Em outros casos, são atribuídos valores a partir da janela de propriedades que irão ser usadas como estão ou serão substituídas por valores padrão, quando o objeto é criado.  

Depois de preparar os parâmetros, carregamos o oscilador selecionado. O código de carregamento também é apresentado como uma função, aqui está um fragmento:

void LoadOscillator(){
   switch(_Type){
      case OscUni_ATR:
         osc=new COscUni_ATR(UseDefault,KeepPrev,_Period1);
      break;
      case OscUni_BearsPower:
         osc=new COscUni_BearsPower(UseDefault,KeepPrev,_Period1);
      break;
      case OscUni_BullsPower:
         osc=new COscUni_BullsPower(UseDefault,KeepPrev,_Period1);
      break;      
      ...
   }  
}

Após o carregamento do oscilador verificamos o identificador:

if(!osc.CheckHandle()){
   Alert("Erro de carregamento do indicador"+osc.Name());
   return(INIT_FAILED);
}

Se o download for concluído, instalamos os estilos de desenho, obtendo-os através dos métodos apropriados do objeto. Esta parte do código também é executada como uma função, o código é reproduzido na íntegra:

void SetStyles(){  

   // definição de estilos
   if(osc.BuffersCount()==2){
      PlotIndexSetInteger(0,PLOT_DRAW_TYPE,osc.DrawType1());
      PlotIndexSetInteger(1,PLOT_DRAW_TYPE,osc.DrawType2());
      PlotIndexSetInteger(0,PLOT_SHOW_DATA,true);
      PlotIndexSetInteger(1,PLOT_SHOW_DATA,true);
      PlotIndexSetString(0,PLOT_LABEL,osc.Label1());
      PlotIndexSetString(1,PLOT_LABEL,osc.Label2());
      if(osc.DrawType1()==DRAW_HISTOGRAM){
         PlotIndexSetInteger(0,PLOT_LINE_COLOR,ColorHisto);
      }
      else{
         PlotIndexSetInteger(0,PLOT_LINE_COLOR,ColorLine1);        
      }
      PlotIndexSetInteger(1,PLOT_LINE_COLOR,ColorLine2);  
   }
   else{
      PlotIndexSetInteger(0,PLOT_DRAW_TYPE,osc.DrawType1());
      PlotIndexSetInteger(1,PLOT_DRAW_TYPE,DRAW_NONE);  
      PlotIndexSetInteger(0,PLOT_SHOW_DATA,true);
      PlotIndexSetInteger(1,PLOT_SHOW_DATA,false);  
      PlotIndexSetString(0,PLOT_LABEL,osc.Label1());
      PlotIndexSetString(1,PLOT_LABEL,"");
      if(osc.DrawType1()==DRAW_HISTOGRAM){
         PlotIndexSetInteger(0,PLOT_LINE_COLOR,ColorHisto);
      }
      else{
         PlotIndexSetInteger(0,PLOT_LINE_COLOR,ColorLine1);        
      }        
   }
  
   // instalação de digits
   IndicatorSetInteger(INDICATOR_DIGITS,osc.Digits());

   // instalação de níveis
   int levels=osc.LevelsTotal();
   IndicatorSetInteger(INDICATOR_LEVELS,levels);
   for(int i=0;i<levels;i++){
      IndicatorSetDouble(INDICATOR_LEVELVALUE,i,osc.LevelValue(i));
   }

}    

Em primeiro lugar, dependendo do número de buffers do oscilador, é executada uma das duas opções para o estilo. Neste caso, se o primeiro buffer é um histograma, é estabelecido o tipo de buffer correspondente. Em seguida, é definido o número de casas decimais dos valores do indicador. No final são definidos os níveis.

A seguir está o código completo da função OnInit() com uma chamada à função recém-criada:

int OnInit(){

   SetIndexBuffer(0,Label1Buffer,INDICATOR_DATA);
   SetIndexBuffer(1,Label2Buffer,INDICATOR_DATA);

   PrepareParameters();

   LoadOscillator();
  
   if(!osc.CheckHandle()){
      Alert("Erro de carregamento do indicador"+osc.Name());
      return(INIT_FAILED);
   }

   SetStyles();
  
   Print("Parameters matching: "+osc.Help());
  
   ShortName=ProgName+": "+osc.Name();  
   IndicatorSetString(INDICATOR_SHORTNAME,ShortName);
  
   return(INIT_SUCCEEDED);
}

Observe que, no final da função, é executada a chamada da função Print com uma dica de tela sobre os parâmetros usados da janela de propriedades, em seguida, é definido o nome curto do indicador.

Desse modo a primeira parte quanto à criação do oscilador universal fica totalmente concluída, obtermos um indicador com o qual é possível testar as classes criadas anteriormente. Em seguida, vamos criar uma classe de interface gráfica do usuário.

No anexo do artigo, há um indicador completamente pronto, chamado "iUniOsc" (posteriormente no seu código será introduzida uma pequena alteração e irá diferir ligeiramente do indicador obtido nesta fase). 

Plano para criar uma interface gráfica do usuário

Para criar uma interface gráfica, é possível simplesmente usar objetos gráficos: para inserir valores numéricos use o objeto gráfico "campo de entrada", para parâmetros de tipo enumeração (listas drop-down) use alguns botões. No entanto, esta abordagem será muito demorada. Até à data, tem sido escritas - em MQL5 - várias bibliotecas para criação de uma interface gráfica. As bibliotecas permitem criar controles padrão: caixas de diálogo, campos e caixas com botões para aumentar e diminuir valores (caixas de rotação), listas drop-down e muito mais. O conjunto do terminal inclui um jogo de classes padrão para criar painéis e caixas de diálogo. Na seção "Artigos" há uma enorme série de artigos dedicada à criação de uma interface gráfica.

Há também uma pequena série de três publicações (artigo 1, artigo 2, artigo 3), dedicadas a uma simples e rápida criação de interfaces gráficas. Além de examinar a teoria, nestes artigos é criada uma biblioteca para um trabalho rápido com objetos gráficos e para criar uma interface gráfica. Todas as opções acima têm suas vantagens e desvantagens, elas foram tratadas com muito detalhe durante a escrita deste artigo. Foi escolhida a última opção listada (biblioteca incGUI).

O terminal MetaTrader 5 é muito ativamente desenvolvido e melhorado, por isso alguns controles da biblioteca oferecida podem ser considerados desatualizados (nomeadamente barras de rolagem), mas, eles podem ser usados. Para começar a utilizar a biblioteca, baixe o anexo do artigo "Controles de gráfico personalizados. Parte 3. Formulários para MetaTrader 5", descompacte-o, coloque o arquivo "incGUI_v3.mqh" na pasta Include, localizada na pasta de dados do terminal.

Classe de formulário

Todo o trabalho de criação da interface gráfica do usuário será realizado no arquivo separado "UniOscGUI.mqh". Em primeiro lugar, conectamos a biblioteca:

#include <IncGUI_v3.mqh>

Executamos a compilação para verificar. Ao compilar, aparecem vários alertas sobre os quais o compilador não tinha relatado anteriormente. Agora compilador melhorado permite detectar estas deficiências e fazer correções de código. No anexo do artigo é possível encontrar o arquivo de patch "inc_GUI_v4". Em vez de "IncGUI_v3.mqh" conectamos "IncGUI_v4.mqh" e "UniOscDefines.mqh". 

#include <IncGUI_v4.mqh>
#include <UniOsc/UniOscDefines.mqh>

Salvamos uma cópia do indicador "iUniOsc" com o nome "iUniOscGUI". Em seguida, o indicador "iUniOsc" pode ser editado um pouco — ocultar parâmetros UseDefault e KeepPrev. Em um indicador sem uma interface gráfica, eles não fazem sentido, mas você precisa defini-los com o valor false:

bool                 UseDefault  =  false;
bool                 KeepPrev    =  false;

Neste ponto, o indicador "iUniOsc" é considerado totalmente concluído.

Continuamos com o indicador "iUniOscGUI". Conectamos a ele o arquivo "UniOscGUI.mqh". Em total, devem ser anexados os três arquivos:

#include <UniOsc/UniOscDefines.mqh>
#include <UniOsc/CUniOsc.mqh>
#include <UniOsc/UniOscGUI.mqh>

Compilando o indicador, é possível verificar o código e ver imediatamente no gráfico a interface gráfica do usuário. Mas até agora, todo o trabalho será realizado no arquivo "UniOscGUI.mqh". 

A interface gráfica do usuário constituirá uma caixa de diálogo em cuja parte superior será colocada uma lista drop-down para selecionar o oscilador e abaixo haverá um conjunto de controles que corresponderá a cada oscilador. Assim, no arquivo estará uma classe para a criação de formulários e grupos de classes (classes e algumas subclasses) para criar os controles neste formulário.

Vamos começar com o formulário. Para conferir o procedimento passo-a-passo para criar um formulário veja o artigo "Controles de gráfico personalizados. Parte 3. Formulários para MetaTrader 5". Aqui iremos executar este procedimento no que se refere a nossa tarefa.

1. A partir do arquivo "IncGUI_v4.mqh" copiamos a classe CFormTemplate no arquivo "UniOscGUI.mqh" e mudamos o nome para CUniOscForm.

2. Definimos as propriedades. Isso é feito no método MainProperties() da classe CUniOscForm. Definimos as seguintes propriedades:

void MainProperties(){
      m_Name         =  "UniOscForm";
      m_Width        =  FORM_WIDTH;
      m_Height       =  150;
      m_Type         =  0;
      m_Caption      =  "UniOsc";
      m_Movable      =  true;
      m_Resizable    =  true;
      m_CloseButton  =  true;
}

Observe que à variável m_Heigh é atribuído o valor da constante FORM_WIDTH. Na fase final do trabalho deverá ser ajustado o tamanho do formulário e seus controles, por isso, na parte superior do arquivo, adicionamos algumas constantes:

#define FORM_WIDTH 210        // largura do formulário
#define SPIN_BOX_WIDTH 110    // largura da caixa de rotação
#define COMBO_BOX_WIDTH 110   // largura da lista drop-down

Em seguida, você pode aplicar o formulário no indicador. No indicador declaramos a variável externa UseGUI com um valor padrão de true (no início da janela de propriedades):

input bool                 UseGUI      =  true;

Em seguida, após as variáveis externas, declaramos a variável que é um ponteiro para a classe do formulário:

CUniOscForm * frm;

Na função OnInit() do indicador, se o valor da variável UseGUI é igual a true, criamos um objeto e preparamo-lo para usar ao chamar os métodos apropriados para definir propriedades adicionais:

frm=new CUniOscForm();  // criação do objeto
frm.Init();             // inicialização
frm.SetSubWindow(0);    // configuração da subjanela na qual é exibido o formulário
frm.SetPos(10,30);      // configuração da posição do formulário
frm.Show();             // habilitar a visibilidade do formulário

Na função OnDeinit() ocultamos o formulário e excluímos o objeto:

if(CheckPointer(frm)==POINTER_DYNAMIC){
   frm.Hide();
   delete(frm);
}

A partir da função OnChartEvent() chamamos o evento Event():

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   frm.Event(id,lparam,dparam,sparam);  
}

Se agora anexamos o indicador no gráfico, será possível ver o formulário (fig. 2).


Fig. 2. O formulário é criado pela classe CUniOscForm quando você anexa o indicador iUniOscGUI no gráfico 


Todos os botões do formulário funcionam: formulário pode ser movido usando o botão no canto superior esquerdo (especifique o novo local do formulário clicando com o mouse), você pode recolher (com o botão no canto superior direito do retângulo). Ao clicar no botão com uma cruz, o formulário fecha-se, além disso, é necessário remover o indicador do gráfico. Para remover o indicador no gráfico aplica-se a função ChartIndicatorDelete(). Para usar essa função, você tem que saber o número da subjanela do indicador, ele pode ser descoberto usando a função ChartWindowFind(), e para este fim, por sua vez, deve ser usado o nome curto do indicador.

Ao clicar no botão para fechar o formulário, o método de Event() retorna o valor 1. Verificamos o valor de retorno e, se necessário, removemos o indicador do gráfico:

int win=ChartWindowFind(0,ShortName);  // pesquisa da subjanela
ChartIndicatorDelete(0,win,ShortName); // remoção do indicador
ChartRedraw();                         // aceleração de redesenho do gráfico

Agora, ao clicar no botão com uma cruz, não só será fechado o formulário, mas também será removido o indicador no gráfico.  

Adicionamos ao formulário o controle mestre: uma lista drop-down para selecionar o tipo de oscilador. Para sua criação, usamos a classe CComBox. Adicionamos o código à classe CUniOscForm. Declaramos a variável para o objeto:

CComBox m_cmb_main;

Em seguida, no método OnInitEvent(), chamamos o método Init() da classe:

m_cmb_main.Init("cb_main",100," select oscillator");

O método leva o nome do controle (o prefixo para os nomes dos objetos gráficos), a largura do controle e um rótulo ao lado dele. 

No método OnShowEvent(), chamamos o método Show():

m_cmb_main.Show(aLeft+10,aTop+10);

Ao chamar o método, é especificado o local do controle no formulário (recuado de 10 pixels do canto superior esquerdo do espaço de usuário do formulário). 

No método OnHideEvent(), chamamos o método Hide():

m_cmb_main.Hide();

Se acontecer um evento de mudança de seleção na lista principal, será necessário fazer upload de um indicador diferente. Será mais fácil fazer isto no arquivo do indicador, portanto, chamamos o método Event() - que pertence à lista do oscilador - não a partir do método EventsHandler() do formulário, mas sim a partir da função OnChartEvent() do indicador, neste ponto processamos imediatamente o evento:

int me=frm.m_cmb_main.Event(id,lparam,dparam,sparam);
if(me==1){
   Alert(frm.m_cmb_main.SelectedText());
}  

Os parâmetros padrão de evento do gráfico são passados para este método, e quando o método retornar 1, a janela de mensagem é aberta.

É preciso preencher a lista de opções. Pode haver várias abordagens:

  • fazer tudo no método OnInitEvent() do formulário
  • Adicionar à classe do formulário o método adicional e chamá-lo a partir do indicador após o método Init()
  • evocar os métodos da lista diretamente a partir do indicador.

Usamos a terceira opção (requer o mínimo de código). Primeiro, no indicador, vamos criar a matriz com variantes de osciladores:

EOscUniType osctype[]={
   OscUni_ATR,
   OscUni_BearsPower,
   OscUni_BullsPower,  
   OscUni_CCI,
   OscUni_Chaikin,
   OscUni_DeMarker,
   OscUni_Force,
   OscUni_Momentum,
   OscUni_MACD,
   OscUni_OsMA,
   OscUni_RSI,
   OscUni_RVI,
   OscUni_Stochastic,
   OscUni_TriX,
   OscUni_WPR
};

Depois de chamar frm.Init(), no indicador, preenchemos a lista e definimos o item selecionado por padrão:

for(int i=0;i<ArraySize(osctype);i++){
   frm.m_cmb_main.AddItem(EnumToString(osctype[i]));
}
frm.m_cmb_main.SetSelectedIndex(0);

Neste ponto, é possível executar a validação. No formulário deve ser exibido uma lista drop-down com tipos de osciladores, e se você alterar o item selecionado deve abrir uma janela com o texto correspondente (fig. 3):

 
Fig. 3. Formulário com uma lista de osciladores e janela de mensagem, depois que você alterar a seleção na lista 

Controles no formulário

No início deste artigo, foi determinado o número máximo de parâmetros externos de acordo com tipos (três para inserir valores numéricos e quatro para enumerações padrão). Para inserir valores numéricos, usamos o elemento CSpinInputBox (uma caixa de edição com botões), biblioteca incGUI para enumerações padrão — elemento CComBox (lista suspensa).

No início do arquivo com a classe de interface gráfica do usuário, declaramos as matrizes com valores padrão para enumerações:

ENUM_APPLIED_PRICE e_price[]={   PRICE_CLOSE,
                                 PRICE_OPEN,
                                 PRICE_HIGH,
                                 PRICE_LOW,
                                 PRICE_MEDIAN,
                                 PRICE_TYPICAL,
                                 PRICE_WEIGHTED
};

ENUM_MA_METHOD e_method[]={MODE_SMA,MODE_EMA,MODE_SMMA,MODE_LWMA};

ENUM_APPLIED_VOLUME e_volume[]={VOLUME_TICK,VOLUME_REAL};

ENUM_STO_PRICE e_sto_price[]={STO_LOWHIGH,STO_CLOSECLOSE};

Na classe de formulário, declaramos as variáveis para os controles (três tipos de CSpinInputBox e quatro CComBox): 

CSpinInputBox m_value1;
CSpinInputBox m_value2;      
CSpinInputBox m_value3;
      
CComBox m_price;
CComBox m_method;
CComBox m_volume
CComBox m_sto_price;

Na classe do formulário, no método OnInitEvent(), inicializamos as listas drop-down (objetos da classe CComBox) e preenchemo-las usando as matrizes declaradas anteriormente:

m_price.Init("price",COMBO_BOX_WIDTH," price");
m_method.Init("method",COMBO_BOX_WIDTH," method");
m_volume.Init("volume",COMBO_BOX_WIDTH," volume");
m_sto_price.Init("sto_price",COMBO_BOX_WIDTH," price");              

for(int i=0;i<ArraySize(e_price);i++){
   m_price.AddItem(EnumToString(e_price[i]));            
}
for(int i=0;i<ArraySize(e_method);i++){
   m_method.AddItem(EnumToString(e_method[i]));              
}            
for(int i=0;i<ArraySize(e_volume);i++){
   m_volume.AddItem(EnumToString(e_volume[i]));            
}            
for(int i=0;i<ArraySize(e_sto_price);i++){
   m_sto_price.AddItem(EnumToString(e_sto_price[i]));            
}

Como para os vários indicadores no formulário devem ser exibidos diferentes conjuntos de controles, criamos as classes (básicas e subclasses) para formar os conjuntos. Nome da classe base CUniOscControls, o seguinte é o seu modelo:

class CUniOscControls{
   protected:
      CSpinInputBox * m_value1;
      CSpinInputBox * m_value2;      
      CSpinInputBox * m_value3;
      CComBox * m_price;
      CComBox * m_method;
      CComBox * m_volume;
      CComBox * m_sto_price;
   public:
   void SetPointers(CSpinInputBox & value1,
                        CSpinInputBox & value2,      
                        CSpinInputBox & value3,
                        CComBox & price,
                        CComBox & method,
                        CComBox & volume,
                        CComBox & sto_price){
       ...
   }
   void Hide(){
       ...
   }
   int Event(int id,long lparam,double dparam,string sparam){
      ...
      return(0);
   }
   virtual void InitControls(){
   }  
   virtual void Show(int x,int y){
   }  
   virtual int FormHeight(){
      return(0);
   }
};

No início do uso do objeto dessa classe é chamado o método SetPointers(), para o método são passados ponteiros para todos os controles, enquanto no método eles são armazenados em suas próprias variáveis de classe: 

void SetPointers(CSpinInputBox & value1,
                     CSpinInputBox & value2,      
                     CSpinInputBox & value3,
                     CComBox & price,
                     CComBox & method,
                     CComBox & volume,
                     CComBox & sto_price){
   m_value1=GetPointer(value1);
   m_value2=GetPointer(value2);      
   m_value3=GetPointer(value3);            
   m_price=GetPointer(price);
   m_method=GetPointer(method);
   m_volume=GetPointer(volume);
   m_sto_price=GetPointer(sto_price);
}

Através desses indicadores, serão escondidos todos os controles (método Hide()):

void Hide(){
   m_value1.Hide();
   m_value2.Hide();
   m_value3.Hide();
   m_price.Hide();
   m_method.Hide();
   m_volume.Hide();
   m_sto_price.Hide();
}

Ele vai lidar com seus eventos (método Event()):

int Event(int id,long lparam,double dparam,string sparam){
   int e1=m_value1.Event(id,lparam,dparam,sparam);
   int e2=m_value2.Event(id,lparam,dparam,sparam);
   int e3=m_value3.Event(id,lparam,dparam,sparam);
   int e4=m_price.Event(id,lparam,dparam,sparam);
   int e5=m_method.Event(id,lparam,dparam,sparam);
   int e6=m_volume.Event(id,lparam,dparam,sparam);
   int e7=m_sto_price.Event(id,lparam,dparam,sparam);
   if(e1!=0 || e2!=0 || e3!=0 || e4!=0 || e5!=0 ||e6!=0 || e7!=0){
      return(1);
   }
   return(0);
}

Os métodos restantes são virtuais, cada oscilador terá seu próprio código nas subclasses. O método Show() será usado para exibir os controles. O método FormHeight() irá retornar a altura do formulário. O método InitControls() é destinado para mudar os rótulos perto dos controles (fig. 4).


Fig. 4. Diferentes rótulos perto do mesmo controle para osciladores diferentes 

A coisa é que os controles da biblioteca incGUI tem apenas o mínimo necessário de conjuntos de métodos e não tem métodos para alterar os rótulos. Mas as classes são projetadas para que, se necessário,, você possa alterar o rótulo, chamando o método Init(). Como a alteração de rótulos é levada a cabo pelo método Init(), o método é chamado InitControls().  

Vamos olhar algumas das subclasses. A mais simples delas — para o indicador de ATR, a mais difícil — para o Stochastic.

Para ATR:

class CUniOscControls_ATR:public CUniOscControls{
   void InitControls(){
      m_value1.Init("value1",SPIN_BOX_WIDTH,1," ma_period");
   }
   void Show(int x,int y){
      m_value1.Show(x,y);
   }  
   int FormHeight(){
      return(70);
   }  
};

No método InitContrlos() é chamado o método Init() do controle, o mais importante (para o qual eu tive que fazer este método virtual) é passado para o rótulo "ma_period" que aparecerá à direita do controle.

No método Show() da classe do formulário, é executada a chamada do método Show() da classe CUniOscControls, quando você chamar, são especificadas as coordenadas do canto superior esquerdo do primeiro (superior) controle. O método FormHeight() simplesmente retorna um valor.

Para Stochastic:

class CUniOscControls_Stochastic:public CUniOscControls{
   void InitControls(){
      m_value1.Init("value1",SPIN_BOX_WIDTH,1," Kperiod");
      m_value2.Init("value2",SPIN_BOX_WIDTH,1," Dperiod");  
      m_value3.Init("value3",SPIN_BOX_WIDTH,1," slowing");          
   }
   void Show(int x,int y){
      m_value1.Show(x,y);
      m_value2.Show(x,y+20);      
      m_value3.Show(x,y+40);
      m_method.Show(x,y+60);      
      m_sto_price.Show(x,y+80);
   }
   int FormHeight(){
      return(150);
   }    
};

No método Show() são calculadas as coordenadas para cada controle, o resto já deve estar claro.

Finalmente, vamos olhar diretamente para a adição de controles no formulário. Na classe do formulário, declaramos uma variável-ponteiro para a classe com os controles:

CUniOscControls * m_controls;

No destruidor, apagamos o objeto:

void ~CUniOscForm(){
   delete(m_controls);
}

Adicionamos à classe o formulário do método SetType(). Esse método será chamado para especificar o tipo de oscilador. 

      void SetType(long type){
         if(CheckPointer(m_controls)==POINTER_DYNAMIC){
            delete(m_controls);
            m_controls=NULL;
         }
        
         switch((EOscUniType)type){
            case OscUni_ATR:
               m_controls=new CUniOscControls_ATR();
            break;
            case OscUni_BearsPower:
               m_controls=new CUniOscControls_BearsPower();
            break;
            case OscUni_BullsPower:
               m_controls=new CUniOscControls_BullsPower();
            break;
            case OscUni_CCI:
               m_controls=new CUniOscControls_CCI();
            break;
            case OscUni_Chaikin:
               m_controls=new CUniOscControls_Chaikin();
            break;
            case OscUni_DeMarker:
               m_controls=new CUniOscControls_DeMarker();
            break;
            case OscUni_Force:
               m_controls=new CUniOscControls_Force();
            break;
            case OscUni_Momentum:
               m_controls=new CUniOscControls_Momentum();
            break;
            case OscUni_MACD:
               m_controls=new CUniOscControls_MACD();
            break;
            case OscUni_OsMA:
               m_controls=new CUniOscControls_OsMA();
            break;
            case OscUni_RSI:
               m_controls=new CUniOscControls_RSI();
            break;
            case OscUni_RVI:
               m_controls=new CUniOscControls_RVI();
            break;
            case OscUni_Stochastic:
               m_controls=new CUniOscControls_Stochastic();
            break;
            case OscUni_TriX:
               m_controls=new CUniOscControls_TriX();
            break;
            case OscUni_WPR:
               m_controls=new CUniOscControls_WPR();
            break;
         }
        
         m_controls.SetPointers(m_value1,m_value2,m_value3,m_price,m_method,m_volume,m_sto_price);
         m_controls.InitControls();
        
         m_value1.SetReadOnly(false);
         m_value2.SetReadOnly(false);
         m_value3.SetReadOnly(false);
        
         m_value1.SetMinValue(1);        
         m_value2.SetMinValue(1);
         m_value3.SetMinValue(1);
        
         m_Height=m_controls.FormHeight();        
        
      }  

No início do método, é executada a exclusão do objeto se ele existir. Em seguida, dependendo do tipo de indicador, é executado o carregamento da classe apropriada. Na parte inferior do método, é chamado o método SetPointers(), e o método InitControls(). Em seguida, são executadas algumas etapas extras: para os controles SpinBox, é habilitada entrada desde o teclado (chamada de método ReadOnly()), é estabelecido o valor mínimo (chamada do método SetMinValue()) e à variável m_Height é atribuído um novo valor para a altura do formulário.

Nos métodos OnShowEvent() e OnHideEvent() do formulário, chamamos os métodos correspondentes do objeto m_controls:

void OnShowEvent(int aLeft, int aTop){
   m_cmb_main.Show(aLeft+10,aTop+10);
   m_controls.Show(aLeft+10,aTop+10+20);
}
void OnHideEvent(){
   m_cmb_main.Hide();            
   m_controls.Hide();          
}  

Resta "reviver" os eventos de objeto m_controls. No indicador, à função OnChartEvent() adicionamos a chamada do método Event():

int ce=frm.m_controls.Event(id,lparam,dparam,sparam);

No OnInit() do indicador, adicionamos a chamada do método SetType () do formulário (depois de chamar o método SetSelectedIndex()):

frm.SetType(_Type);

Após o carregamento do oscilador, é necessário que no formulário sejam exibidos os valores de seus parâmetros, para fazer isso, na classe do formulário, adcionamos o método SetValues():

void SetValues(int period1,
               int period2,
               int period3,
               long method,
               long price,
               long volume,  
               long sto_price  
){
  
   m_value1.SetValue(period1);
   m_value2.SetValue(period2);      
   m_value3.SetValue(period3);
  
   for(int i=0;i<ArraySize(e_price);i++){
      if(price==e_price[i]){
         m_price.SetSelectedIndex(i);
         break;
      }
   }
  
   for(int i=0;i<ArraySize(e_method);i++){
      if(method==e_method[i]){
         m_method.SetSelectedIndex(i);
         break;
      }
   }            

   for(int i=0;i<ArraySize(e_volume);i++){
      if(volume==e_volume[i]){
         m_volume.SetSelectedIndex(i);
         break;
      }
   }
  
   for(int i=0;i<ArraySize(e_sto_price);i++){
      if(sto_price==e_sto_price[i]){
         m_sto_price.SetSelectedIndex(i);
         break;
      }
   }

}      

No método SetValues(), para os controles de tipo SpinBox, os valores são definidos como de costume, enquanto para as enumerações é executada a pesquisa de índice nas matrizes com os valores das enumerações. Chamamos o método SetType() após a chamada do método SetValues():

frm.SetValues(_Period1,_Period2,_Period3,_MaMethod,_Price,_Volume,_StPrice);

Neste ponto, a interface gráfica do usuário pode ser considerada totalmente preparada (fig. 5), mas o indicador ainda não sabe como reagir a ela.


Fig. 5. Janela de exibição com controles para o indicador ATR

Conclusão do oscilador universal

As classes do oscilador estão prontas, as classes da interface gráfica também estão prontas, resta juntá-las. 

Nesta fase, a função OnChatEvent() função deve ter a seguinte aparência:

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{

   int e=frm.Event(id,lparam,dparam,sparam);  
   if(e==1){
      int win=ChartWindowFind(0,ShortName);
      ChartIndicatorDelete(0,win,ShortName);
      ChartRedraw();
   }
  
   int me=frm.m_cmb_main.Event(id,lparam,dparam,sparam);
  
   int ce=frm.m_controls.Event(id,lparam,dparam,sparam);

}

É necessário processar o evento de mudança do indicador (variável me) e o evento de alteração de parâmetros (variável ce).

Alteração do indicador:

if(me==1){

   // reinicialização do indicador
  
   _Type=osctype[frm.m_cmb_main.SelectedIndex()]; // novo tipo
  
   delete(osc); // ecluir o objeto antigo
   LoadOscillator(); // carregamento de um novo indicador
  
   if(!osc.CheckHandle()){
      Alert("Erro de carregamento do indicador"+osc.Name());
   }
  
   SetStyles(); // configuração de estilos
  
   // configuração de nome curto
   ShortName=ProgName+": "+osc.Name();  
   IndicatorSetString(INDICATOR_SHORTNAME,ShortName);

   // atualização de formulário

   frm.SetType(osctype[frm.m_cmb_main.SelectedIndex()]); // configuração do tipo
   frm.SetValues(_Period1,_Period2,_Period3,_MaMethod,_Price,_Volume,_StPrice); // definição de valores
   frm.Refresh(); // atualização de formulário
  
   // recálculo do indicador
   EventSetMillisecondTimer(100);

}  

Consideremos este código em detalhes. Ao selecionar o oscilador na lista principal, o método Event() retorna 1, neste caso, à variável _Type é atribuído um novo valor de tipo, é removido o antigo objeto, o novo objeto é carregado, são instalados estilos, nome curto. Depois de carregar o indicador, é atualizada a aparência do formulário: é definido o tipo, parâmetros e é chamado o método Refresh() para exibir o formulário alterado em conformidade com as novas configurações. No final, é executado o temporizador (falaremos sobre isso mais tarde).

Examinemos um pedaço de código para alterar as configurações: 

if(ce==1){
  
   if((int)frm.m_value1.Value()>0){
      _Period1=(int)frm.m_value1.Value();
   }
   if((int)frm.m_value2.Value()>0){
      _Period2=(int)frm.m_value2.Value();
   }
   if((int)frm.m_value3.Value()>0){
      _Period3=(int)frm.m_value3.Value();
   }      
   if(frm.m_method.SelectedIndex()!=-1){
      _MaMethod=e_method[frm.m_method.SelectedIndex()];
   }
   if(frm.m_price.SelectedIndex()!=-1){
      _Price=e_price[frm.m_price.SelectedIndex()];
   }
   if(frm.m_volume.SelectedIndex()!=-1){
      _Volume=e_volume[frm.m_volume.SelectedIndex()];
   }
   if(frm.m_sto_price.SelectedIndex()!=-1){
      _StPrice=e_sto_price[frm.m_sto_price.SelectedIndex()];
   }
  
   delete(osc);
   LoadOscillator();
   if(!osc.CheckHandle()){
      Alert("Erro de carregamento do indicador"+osc.Name());
   }  
  
   ShortName=ProgName+": "+osc.Name();  
   IndicatorSetString(INDICATOR_SHORTNAME,ShortName);
  
   EventSetMillisecondTimer(100);
    
}

Quando você alterar as configurações, o método Event() da classe de controle retorna 1. Neste caso, a todas as variáveis são atribuídos novos valores, mas eles são pré-testados. Valores de controles de SpinBox devem ser maiores que zero, e as listas drop-down não devem ser iguais a -1. A seguir, tudo é como ao mudar o indicador.

Agora sobre o temporizador. Para executar o cálculo do indicador, requer-se de algum tempo. Portanto, o temporizador é iniciado e, em sua função, é verificada periodicamente a prontidão do indicador, usando a função BarsCalculated(). Se o valor de retorno for maior que zero, significara que o indicador já foi calculado totalmente, além disso, é chamado o método Calculate() do objeto osc:

void OnTimer(){
   if(BarsCalculated(osc.Handle())>0){
      if(osc.Calculate(Bars(Symbol(),Period()),0,Label1Buffer,Label2Buffer)!=0){
         ChartRedraw();    
         EventKillTimer();
      }
   }
}

Como primeiro parâmetro no método Calculate() é transferido o número de barras, e como segundo -0, para um recálculo completo do indicador. Depois disso, o gráfico é atualizado (ChartRedaraw()) e é desativado o temporizador. 

Agora o indicador deve responder à interface gráfica do usuário. Isso significa que ele está quase pronto.

Adicionamos um traço final: fornecemos a oportunidade de funcionamento do indicador sem uma interface gráfica. Para fazer isso, adicionamos a variável externa UseGUI:

input bool                 UseGUI      =  true;

A porção de código da função OnInit(), associada com a criação do formulário, será executada por nós somente se a variável UseGUI está habilitada:

if(UseGUI){
   frm=new CUniOscForm();
   frm.Init();
   int ind=0;
  
   for(int i=0;i<ArraySize(osctype);i++){
      frm.m_cmb_main.AddItem(EnumToString(osctype[i]));
      if(osctype[i]==_Type){
         ind=i;
      }
   }
  
   frm.m_cmb_main.SetSelectedIndex(ind);      
   frm.SetType(_Type);
   frm.SetValues(_Period1,_Period2,_Period3,_MaMethod,_Price,_Volume,_StPrice);
  
   frm.SetSubWindow(0);
   frm.SetPos(10,30);
   frm.Show();
}

E mais um pequeno traço de acabamento. A biblioteca IncGUI suporta a alteração de esquemas de cores dos controles. Usamos esta possibilidade.

Imediatamente após os parâmetros externos, adicionamos o seguinte código:

enum eColorScheme{
   DefaultScheme=0,
   YellowBrownScheme=1,
   BlueScheme=2,
   GreenScheme=3,
   YellowBlackScheme=4,
   LimeBlackScheme=5,
   AquaBlackScheme=6
};

input eColorScheme ColorScheme=DefaultScheme;

Este código adiciona na janela de propriedades do indicador uma lista suspensa para selecionar um esquema de cores. No início da função OnInit(), adicionamos uma cadeia de caracteres:

ClrScheme.SetScheme(ColorScheme);

Agora o indicador IUniOscGUI está totalmente concluído e a interface gráfica pode até ter uma cor diferente (fig. 6).

 
Fig. 6. Vários esquemas de cores da interface gráfica do usuário do indicador iUniOscGUI 

Conclusão

O indicador resultante acabou por ser muito útil, não só em termos de uma comparação dos vários indicadores, mas também para o monitoramento da influência de parâmetros externos sobre o tipo de indicadores. O indicador muda quase instantaneamente sua aparência ao alterar as configurações. Usando a janela de propriedades, este efeito não é alcançável e, por conseguinte, não é possível obter a mesma impressão sobre a influência dos parâmetros sobre o tipo de indicador.

Arquivos do aplicativo

  • UniOscDefines.mqh — arquivo com a lista dos tipos de osciladores.
  • CUniOsc.mqh — classes universais do oscilador.
  • iUniOsc.mq5 — oscilador universal sem interface gráfica.
  • UniOscGUI.mqh — classes para criação da interface gráfica do usuário do oscilador. 
  • iUniOscGUI.mq5 — oscilador universal com interface gráfica do usuário. 
  • IncGUI_v4.mqh — biblioteca para trabalhar com objetos gráficos e criar uma interface gráfica do usuário. Anteriormente, houve confusão com a versão da biblioteca. Havia dois arquivos da versão 3 com o mesmo nome: no artigo e no CodeBase (com uma classe atualizada de criação da tabela CTable). No arquivo IncGUI_v4 não só há correções, mas também é substituída a classe de criação de tabelas por uma mais recente (a partir do CodeBase).    

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

Arquivos anexados |
files.zip (92.15 KB)
LifeHack para traders: relatório comparativo de vários testes LifeHack para traders: relatório comparativo de vários testes

No artigo, é tratada a execução simultânea do teste de Experts em quatro símbolos diferentes. A comparação final dos quatro relatórios respetivos é realizada numa tabela, como seria feito durante a seleção de produtos numa loja. Uma vantagem adicional consiste na geração automática de gráficos de distribuição para cada símbolo.

Interfaces Gráficas X: O Controle Gráfico Padrão (build 4) Interfaces Gráficas X: O Controle Gráfico Padrão (build 4)

Desta vez, nós vamos discutir o controle gráfico padrão. Ele permitirá criar arrays de objetos gráficos com a possibilidade de sincronizar o deslocamento horizontal. Além disso, nós continuaremos a otimizar o código da biblioteca para reduzir o consumo de recursos do CPU.

Fundamentos Básicos da Programação: Variáveis Globais do Terminal Fundamentos Básicos da Programação: Variáveis Globais do Terminal

As variáveis globais do terminal possibilitam uma ferramenta essencial para o desenvolvimento de Expert Advisors sofisticados e confiáveis. Se você dominar as variáveis globais, você nunca mais vai querer desenvolver EAs no MQL5 sem elas.

Distribuições estatísticas em forma de histogramas sem buffers de indicador e matrizes Distribuições estatísticas em forma de histogramas sem buffers de indicador e matrizes

O artigo considera a possibilidade de criar histogramas, distribuições estatísticas das características do mercado usando memória gráfica, ou seja, sem o uso de buffers de indicador e matrizes. Aqui você tem à sua disposição não só exemplos detalhados de como construir esses histogramas, mas também pode conhecer a funcionalidade "oculta" dos objetos gráficos da linguagem MQL5.