Oscilador universal com interface gráfica do usuário
Conteúdo
- Introdução
- Análise do problema
- Conjunto de parâmetros
- Plano de trabalho
- Criação da classe base de um indicador
- Subclasses Calculate
- Subclasses dos indicadores
- Criação de um oscilador universal (início)
- Plano para criar uma interface gráfica do usuário
- Classe de formulário
- Controles no formulário
- Conclusão do oscilador universal
- Conclusão
- Arquivos do aplicativo
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ção | Nome | Buffer | Parâmetros |
---|---|---|---|
iATR | Average True Range | 1. linha | 1. int ma_period — período da média |
iBearsPower | Bears Power | 1. gráfico de barras | 1. int ma_period — período da média |
iBullsPower | Bulls Power | 1. linha | 1. int ma_period — período da média |
iCCI | Commodity Channel Index | 1. linha | 1. int ma_period — período da média 2. ENUM_APPLIED_PRICE applied_price — tipo de preço |
iChaikin | Oscilador Chaikin | 1. linha | 1. 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 |
iDeMarker | DeMarker | 1. linha | 1. int ma_period — período da média |
iForce | Force Index | 1. linha | 1. 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 |
iMomentum | Momentum | 1. linha | 1. int mom_period — período da média 2. ENUM_APPLIED_PRICE applied_price — tipo de preço |
iMACD | Moving Averages Convergence-Divergence | 1. 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 |
iOsMA | Moving Average of Oscillator (MACD histogram) | 1. gráfico de barras | 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 |
iRSI | Relative Strength Index | 1. linha | 1. int ma_period —
período da média 2. ENUM_APPLIED_PRICE applied_price — tipo de preço |
iRVI | Relative Vigor Index | 1.
linha 2. linha | 1. int ma_period — período da média |
iStochastic | Stochastic Oscillator | 1.
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 |
iTriX | Triple Exponential Moving Averages Oscillator | 1. linha | 1. int ma_period —
período da média 2. ENUM_APPLIED_PRICE applied_price — tipo de preço |
iWPR | Williams' Percent Range | 1. linha | 1. 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
Tipo | Número |
---|---|
int | 3 |
ENUM_APPLIED_PRICE | 1 |
ENUM_MA_METHOD | 1 |
ENUM_APPLIED_VOLUME | 1 |
ENUM_STO_PRICE | 1 |
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:
- Criação de classes para o oscilador universal e criação deste oscilador sem uma interface gráfica do usuário.
- Criação de classes para a interface gráfica do usuário.
- 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"):
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:
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:
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_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:
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:
return(m_handle);
}
No construtor de classe, inicializamos o identificador, enquanto, no destrutor, verificamo-lo e, se necessário, chamamos a função IndicatorRelease():
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:
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:
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:
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:
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(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:
No final de algumas cadeias de caracteres para definir os parâmetros de exibição:
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(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:
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:
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:
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/CUniOsc.mqh>
Crie uma variável externa para selecionar o tipo de oscilador:
Variáveis UseDefault e KeepPrevious:
input bool KeepPrev = true;
Variáveis universais diretamente para os parâmetros dos osciladores:
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 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 _Period2;
int _Period3;
long _MaMethod;
long _Price;
long _Volume;
long _StPrice;
EOscUnyType _Type;
Declaramos uma variável-ponteiro para o objeto do oscilador universal:
Anunciamos um par de variáveis adicionais:
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:
_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:
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:
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:
// 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:
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:
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 <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 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/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:
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 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):
Em seguida, após as variáveis externas, declaramos a variável que é um ponteiro para a classe do formulário:
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.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:
frm.Hide();
delete(frm);
}
A partir da função OnChartEvent() chamamos o evento Event():
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:
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:
Em seguida, no método OnInitEvent(), chamamos o método Init() da classe:
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():
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():
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:
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:
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:
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:
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_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_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:
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:
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()):
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 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:
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:
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:
No destruidor, apagamos o objeto:
delete(m_controls);
}
Adicionamos à classe o formulário do método SetType(). Esse método será chamado para especificar o tipo de oscilador.
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:
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():
No OnInit() do indicador, adicionamos a chamada do método SetType () do formulário (depois de chamar o método SetSelectedIndex()):
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():
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():
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:
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:
// 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((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:
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:
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:
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:
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:
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 Ltd.
Artigo original: https://www.mql5.com/ru/articles/2788
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso