English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
preview
Indicadores múltiplos em um gráfico (Parte 02): Primeiros experimentos

Indicadores múltiplos em um gráfico (Parte 02): Primeiros experimentos

MetaTrader 5Exemplos | 11 fevereiro 2022, 09:10
1 483 6
Daniel Jose
Daniel Jose

Introdução

No artigo anterior, múltiplos indicadores em um gráfico , apresentei os conceitos e bases para você usar múltiplos indicadores em um gráfico sem poluir sua tela com informações diversas, mas como aquele artigo tinha a única proposta de apresentar o sistema, e mostrar como criar o banco de dados a ser utilizado e como tirar proveito desse banco de dados, deixei de apresentar o código fonte do sistema neste artigo, aqui iniciaremos a implementação do código, e em artigos futuros iremos expandir a funcionalidade deste sistema, criando um sistema ainda mais completo e versátil, pois o sistema promete e tem grandes possibilidades de melhorias.


Planejamento

Para ficar mais fácil de entender, e principalmente deixar a coisa para que você possa facilmente expandir o sistema, o sistema já está dividido em 2 arquivos separados, e o código principal foi feito usando modelagem OOP (programação orientada a objetos), todos isso irá garantir que o sistema possa crescer de forma sustentável, segura e estável.

Nesta primeira fase usaremos um indicador, então criaremos um para esta janela

mas por que usar um marcador e não outro tipo de arquivo?!?! A razão é que, usando um indicador, não precisamos adicionar lógica interna para criar uma subjanela, podemos dizer ao indicador para fazer isso por nós, o que nos economiza tempo e acelera a criação do sistema. Portanto, nosso cabeçalho de indicador seria semelhante ao código abaixo:

#property indicator_plots 0
#property indicator_separate_window

Apenas usando essas duas linhas podemos criar uma subjanela em um gráfico de ativos, para quem não sabe como funcionam, veja a tabela abaixo:

Código Descrição
indicador_plots 0 Esta linha dirá ao compilador que não traçaremos nenhum tipo de dados, ela impede que o compilador mostre mensagens de aviso
indicador_separar_janela Esta linha diz ao compilador para adicionar a lógica necessária para criar uma subjanela para nós.

bem, a coisa em si tem que ser mantida simples, então para aqueles que não estão familiarizados com programação, algumas coisas encontradas em um código-fonte podem parecer estranhas, mas eles seguem um protocolo que é amplamente divulgado e aceito por toda a comunidade de programadores, e como o MT5 usa MQL5 que é uma linguagem praticamente idêntica ao C ++, com poucas diferenças, podemos usar a mesma forma de programação que usamos em C ++, e isso torna muitas coisas muito mais fáceis, então, aproveitando este fato, usamos um Diretiva de linguagem C e teremos a seguinte linha:

 #include <Auxiliar\C_TemplateChart.mqh>

Esta diretiva diz ao compilador para adicionar (incluir) um arquivo de cabeçalho, que está presente no local definido. Mas espere um minuto, o caminho completo não deve ser Includes \ Auxiliary \ C_TemplateChart.mqh ?!?!? Sim, este é o caminho completo, mas como já está estruturado MQL5 sabe que qualquer arquivo de cabeçalho deve estar na pasta includes, então podemos suprimir este ponto e apenas indicar o resto do caminho, caso o caminho esteja entre o ângulo colchetes e menores, indica um caminho absoluto, se estivesse entre aspas o caminho seria relativo, ou seja, <Auxiliary \ C_TemplateChart.mqh> é diferente de "Auxiliary \ C_TemplateChart.mqh" , um detalhe simples e muda tudo.

Continuando o código, teremos as seguintes linhas:

input string user01 = "" ;       //Indicadores a usar
input string user02 = "" ;       //Ativos a acompanhar

eles permitem a entrada de string, se você já tem algo em mente para usar como comando sempre que abrir o indicador, você já pode deixar este comando como o padrão, por exemplo, suponha que você sempre queira usar RSI com espessamento de 3 e MACD com uma densidade de 2, você pode deixar isso predefinido em seu sistema, então o código acima ficaria assim:

input string user01 = "RSI:3;MACD:2" ;   //Indicadores a usar
input string user02 = "" ;       //Ativos a acompanhar

Isso não impedirá que você altere o comando mais tarde, mas tornará a abertura do indicador mais fácil, pois será pré-configurado para usar este comando. A próxima linha irá criar um alias para que possamos acessar a classe de objeto que contém todo o código "pesado", permitindo-nos acessar suas funções públicas.

C_TemplateChart SubWin;

Praticamente nosso código dentro do arquivo do indicador personalizado está quase pronto, apenas adicionando mais 3 linhas e apenas 3 linhas para que tudo esteja pronto e funcional, já que é claro que nossa classe de objeto não contém erros, mas neste artigo também veremos isso por dentro. Portanto, para finalizar o arquivo do indicador teremos que adicionar as linhas em verde, conforme indicado abaixo:

 //+------------------------------------------------------------------+
int OnInit ()
{
         SubWin.AddThese(C_TemplateChart::INDICATOR, user01);
         SubWin.AddThese(C_TemplateChart::SYMBOL, user02);

         return INIT_SUCCEEDED ;
}
//+------------------------------------------------------------------+

//...... demais linhas sem interesse para nos ......

//+------------------------------------------------------------------+
void OnChartEvent ( const int id,
                   const long &lparam,
                   const double &dparam,
                   const string &sparam)
{
         if (id == CHARTEVENT_CHART_CHANGE ) SubWin.Resize();
}
//+------------------------------------------------------------------+

e isso é exatamente o que o arquivo de indicador personalizado terá. Agora vamos dar uma boa olhada na caixa preta do arquivo que contém nossa classe de objeto. A partir de agora, a atenção deve ser redobrada, mas para facilitar, vamos começar ver quais funções estão presentes em nossa classe de objeto e para que serve cada uma delas.

Ocupação Funcionalidade
SetBase Irá criar o objeto necessário para apresentar os dados do indicador
decodificar Decodifica o comando passado para ele
AddTemplate Ajusta as coisas de acordo, dependendo do tipo de dados apresentados
C_Template Construtor de classe padrão
~ C_Template destruidor de classe
redimensionar Redimensione a subjanela de acordo
AddThese Função responsável por acessar e construir objetos internos

E é isso, você deve ter notado que em nosso indicador personalizado usamos as funções RESIZE e ADDTHESE, e elas são as únicas funções públicas no momento, o que significa que temos pouco com que nos preocupar, pois todo o resto está escondido dentro de nosso objeto, garantindo que elas não será modificado desnecessariamente, o que garante um alto grau de confiabilidade ao nosso código final. Mas vamos nos aprofundar no código e ele começa com a seguinte definição:

 #define def_MaxTemplates         6

Esta linha é muito importante para a nossa classe de objeto, ela define o número máximo de indicadores que podemos criar, se você quiser adicionar mais ou menos basta alterar esse número neste único ponto, fazer desta forma torna as coisas muito mais fáceis, pois ganhamos estar usando uma alocação de memória dinâmica e não queremos um número muito grande de indicadores em nossa tela. Este é talvez o único ponto que você realmente terá que mudar se desejar, mas acredito que 6 é um número adequado para a maioria das pessoas e monitores usados.

A próxima linha é uma enumeração que torna muito mais fácil controlar os dados em alguns pontos do programa:

 enum eTypeChart {INDICATOR, SYMBOL};

o fato desta linha estar dentro de nossa classe vai garantir que ela possa ter o mesmo nome em outra classe diferente, mas os dados por ela indicados pertencem somente a esta classe de objeto, então para acessar corretamente esta enumeração devemos usar a forma apresentada na função OnInit de nosso arquivo de indicador personalizado, se o nome da classe for omitido, será considerado um erro de sintaxe e o código não será compilado. A próxima linha é uma palavra reservada.

 private :

esta linha indica que tudo a partir deste ponto será privado para esta classe de objeto, não sendo visível fora da classe, ou seja, se você tentar acessar qualquer coisa a partir deste ponto não será possível se você não estiver dentro da classe, isso garante que o código ficará ainda mais seguro, já que não é possível acessar nada que seja considerado privado para a classe, as próximas linhas irão declarar algumas variáveis internas e privadas, até chegarmos à primeira função real de nossa classe.

 void SetBase( const string szSymbol, int scale)
{
#define macro_SetInteger(A, B) ObjectSetInteger (m_Id, m_szObjName[m_Counter], A, B)

...

         ObjectCreate (m_Id, m_szObjName[m_Counter], OBJ_CHART , m_IdSubWin, 0 , 0 );
         ObjectSetString (m_Id, m_szObjName[m_Counter], OBJPROP_SYMBOL , szSymbol);
        macro_SetInteger( OBJPROP_CHART_SCALE , scale);
...
        macro_SetInteger( OBJPROP_PERIOD , _Period );
        m_handle = ObjectGetInteger (m_Id, m_szObjName[m_Counter], OBJPROP_CHART_ID );
        m_Counter++;
#undef macro_SetInteger
};

Vamos entender este fragmento de código SetBase, começamos declarando uma macro, indica ao compilador como um código simplificado com o nome da macro, deve ser interpretado, ou seja, se tivermos que repetir algo várias vezes, podemos usar esse recurso da linguagem C para produzir algo mais simples, e se por acaso tivermos que mudar algo, vamos mudar apenas na macro, isso acelera muito e reduz a possibilidade de erros em códigos onde apenas um ou outro argumento irá ser modificado.

Feito isso, criamos um objeto do tipo CHART, você pode estar pensando: Mas o quê?!?! Vamos usar algo que não pode ser alterado para mudar as coisas?!?! Sim está certo. O próximo passo é declarar o ativo a ser usado, e aqui está o primeiro ponto, se no momento de salvar as configurações do gráfico nenhum ativo estiver presente, o ativo ao qual este objeto está vinculado será o ativo a ser usado, se houver ativo está presente quando você cria as configurações, esse ativo será usado, agora um detalhe, você pode indicar um ativo diferente, e com isso usar uma configuração genérica, mas explicarei isso com mais detalhes no próximo artigo, pois estamos vamos implementar algumas melhorias neste código para que possamos fazer algumas coisas que não são possíveis aqui. A seguir temos o nível de densificação das informações, isso é indicado na propriedade OBJPROP_CHART_SCALE, por isso usamos valores entre 0 e 5, embora possamos usar valores fora dessa faixa, é bom manter um padrão.

A próxima coisa a olhar é a propriedade OBJPROP_PERIOD, e note que estamos usando o período do gráfico atual, ou seja, se mudarmos este também mudará, no futuro faremos algumas modificações que nos permitem travar isso, mas se você quiser experimentar, pode usar um período definido por MT5, como por exemplo: PERIOD_M10 que indicaria mostrar os dados em um período fixo de 10 minutos, mas vamos melhorar isso no futuro, por agora don não se preocupe com isso. Depois disso, incrementamos o número de subindicadores em uma unidade e destruímos a macro, ou seja, ela não terá mais nenhuma representação depois disso e deverá ser redefinida para ser usada em outro lugar. Mas espere um minuto, eu não esqueci de nada?!?! SIM da linha que talvez seja o mais importante nessa rotina.

m_handle = ObjectGetInteger (m_Id, m_szObjName[m_Counter], OBJPROP_CHART_ID );

esta linha irá capturar algo que poderia ser entendido como um ponteiro, não é realmente um ponteiro em si, mas nos permite fazer algumas manipulações extras no objeto OBJ_CHART que criamos, precisamos deste valor para poder aplicar algumas definições dentro deste objeto , e essas configurações estão no arquivo de configuração que criamos anteriormente. Seguindo com o código chegamos à próxima rotina que pode ser vista na íntegra a seguir:

 void AddTemplate( const eTypeChart type, const string szTemplate, int scale)
{
	if (m_Counter >= def_MaxTemplates) return ;
	if (type == SYMBOL) SymbolSelect (szTemplate, true );
	SetBase((type == INDICATOR ? _Symbol : szTemplate), scale);
	ChartApplyTemplate (m_handle, szTemplate + ".tpl" );
	ChartRedraw (m_handle);
}

Veja que primeiro testamos a possibilidade ou não de adicionar um novo indicador, se possível, verificaremos se é um SÍMBOLO, e se for o caso o símbolo deve estar presente na janela Market Watch , isto é garantido pela rotina , a partir disso criamos o objeto que receberá as informações. Feito isso, o template é aplicado ao OBJ_CHART, é justamente neste ponto que a mágica acontece, então pedimos que o objeto seja reapresentado, mas agora ele conterá os dados de acordo com as definições contidas no arquivo de configuração que foi utilizado para definir o OBJ_CHART, simples, bonito e puro, torna-se divino pela sua simplicidade.

Com apenas essas 2 rotinas seria possível fazer coisas, mas precisamos de pelo menos mais uma rotina, que é mostrada na íntegra a seguir:

 void Resize( void )
{
         int x0 = 0 , x1 = ( int )( ChartGetInteger (m_Id, CHART_WIDTH_IN_PIXELS , m_IdSubWin) / (m_Counter > 0 ? m_Counter : 1 ));
         for ( char c0 = 0 ; c0 < m_Counter; c0++, x0 += x1)
        {
                 ObjectSetInteger (m_Id, m_szObjName[c0], OBJPROP_XDISTANCE , x0);
                 ObjectSetInteger (m_Id, m_szObjName[c0], OBJPROP_XSIZE , x1);
                 ObjectSetInteger (m_Id, m_szObjName[c0], OBJPROP_YSIZE , ChartGetInteger (m_Id, CHART_HEIGHT_IN_PIXELS , m_IdSubWin));
        }
         ChartRedraw ();
}

O que essa rotina acima faz é colocar tudo em seu devido lugar, mantendo os dados sempre dentro da área da subjanela, não há muito o que falar sobre isso e com isso finalizamos todo o código necessário para que tudo funcione perfeitamente bem. Mas você pode estar pensando: e as outras rotinas?!?! Calma, as outras rotinas não são necessárias de jeito nenhum, elas apenas suportam a interpretação das linhas de comando, mas vamos lá, mas primeiro vamos ver algo que também é importante para quem quer modificar esse código no futuro, que é a seguinte palavra reservada que aparece no código de nossa classe de objeto:

 public   :

esta palavra irá garantir que a partir deste ponto todos os dados e funções possam ser acessados e vistos por outras partes do código, mesmo que não façam parte da classe de objeto, então aqui declaramos o que pode realmente ser manipulado ou acessado por outros objetos . Na realidade, a conduta de um bom código orientado a objetos nunca é permitir acesso direto aos dados de um objeto, em um código bem desenhado só teremos acesso a funções ou rotinas, e o motivo é simples, segurança, quando permitimos externos código para modificar os dados dentro de uma classe, corremos o risco de que esses dados não sejam consistentes com o que o objeto espera, e isso causa muitos problemas e dores de cabeça tentando resolver inconsistências ou defeitos quando tudo parece estar correto. Portanto, se eu puder dar o conselho de alguém que já programa há anos em C ++, NUNCA permita que objetos externos modifiquem ou acessem diretamente os dados da classe que você criou, forneça funções ou procedimentos para que os dados possam ser acessados, mas nunca deixe o os dados sejam acessados diretamente e certifique-se de que as funções e procedimentos mantenham os dados conforme o esperado pela classe que você criou. Dada a mensagem, vamos passar para as duas últimas rotinas de nossa aula, uma é pública (AddThese) e a outra privada (Decode) elas podem ser vistas na íntegra a seguir:

void Decode( string &szArg, int &iScale)
{
#define def_ScaleDefault 4
         StringToUpper (szArg);
        iScale = def_ScaleDefault;
         for ( int c0 = 0 , c1 = 0 , max = StringLen (szArg); c0 < max; c0++) switch (szArg[c0])
        {
                 case ':' :
                         for (; (c0 < max) && ((szArg[c0] < '0' ) || (szArg[c0] > '9' )); c0++);
                        iScale = ( int )(szArg[c0] - '0' );
                        iScale = ((iScale > 5 ) || (iScale < 0 ) ? def_ScaleDefault : iScale);
                        szArg = StringSubstr (szArg, 0 , c1 + 1 );
                         return ;
                 case ' ' :
                         break ;
                 default :
                        c1 = c0;
                         break ;
        }
#undef def_ScaleDefault
}
//+------------------------------------------------------------------+
// ... Códigos que não interessam a esta parte ...
//+------------------------------------------------------------------+
void AddThese( const eTypeChart type, string szArg)
{
         string szLoc;
         int i0;
         StringToUpper (szArg);
         StringAdd (szArg, ";" );
         for ( int c0 = 0 , c1 = 0 , c2 = 0 , max = StringLen (szArg); c0 < max; c0++) switch (szArg[c0])
        {
                 case ';' :
                         if (c1 != c2)
                        {
                                szLoc = StringSubstr (szArg, c1, c2 - c1 + 1 );
                                Decode(szLoc, i0);
                                AddTemplate(type, szLoc, i0);
                        }
                        c1 = c2 = (c0 + 1 );
                         break ;
                 case ' ' :
                        c1 = (c1 >= c2 ? c0 + 1 : c1);
                         break ;
                 default :
                        c2 = c0;
                         break ;
        }
}

O que essas duas rotinas fazem é exatamente o que expliquei acima, elas garantem a integridade dos dados dentro da classe de objeto, evitando que dados inconsistentes tornem-se parte dos dados internos da classe, elas receberão uma linha de comando e decodificarão essa linha seguindo um sintaxe predefinida, porém, eles não falam sobre haver um erro no comando recebido, esse não é o seu propósito, seu propósito é apenas garantir que nenhum dado inconsistente entre no objeto e cause efeitos colaterais que podem ser difíceis de localizar e corrigir.

O resultado final será este visto abaixo:



Conclusão

Bom, espero que esse código te motive, quem sabe, me interessei por programação, porque a coisa é linda e muito empolgante, embora às vezes nos dê muita dor de cabeça poder produzir alguns tipos de resultados específicos, mas na maioria das vezes vale a pena. No próximo artigo vou mostrar como tornar as coisas ainda mais interessantes. O código completo do indicador até presente o momento está anexado, já podendo ser usado conforme descrito neste artigo e no anterior.


Últimos Comentários | Ir para discussão (6)
Daniel Jose
Daniel Jose | 25 nov 2022 em 15:26
unicolea #:

Olá Daniel José . Uma implementação bastante interessante e útil do uso de uma subjanela junto com vários gráficos.

Até agora, usei o minigráfico na janela principal como um recurso adicional de um indicador ou mesmo de um EA. Nele, posso alterar todos os parâmetros principais: Símbolo, Período, Escala, além de outros menos importantes.

Mas eu não poderia usar mais minigráficos porque eles cobriam consideravelmente a janela principal. Muito se resolve com o seu indicador, mas existem alguns pequenos inconvenientes que sugiro que você modifique.

Sugiro adicionar a capacidade de definir o período desejado na linha de comando, para cada minigráfico separadamente, para que, como exemplo, você possa definir 1 símbolo para 3 períodos diferentes ou 3 símbolos diferentes para 1 período.

Por exemplo: “ GBPUSD - M 30:3; GBPUSD - H 2:3; GBPUSD - D 1:3"

ou “ EURUSD - H 2:3; GBPUSD - H 2:3; USDCHF - H 2:3"

Acho que a explicação é bem clara e fácil de modificar no seu código.

Acredito que você não chegou a ver o artigo que veio logo na sequencia ... 😁👍 ... mas dê uma olhada nele Indicadores múltiplos em um gráfico (Parte 03): Desenvolvendo definições para usuários, talvez seja exatamente o que você deseja, já que podemos travar período ou mesmo a largura de cada sub janela ... mas que qualquer forma obrigado pela sugestão.

unicolea
unicolea | 25 nov 2022 em 18:56

Muito obrigado pelo link. O fato é que a princípio apenas a parte 2, traduzida pela MetaQuotes Ltd, estava disponível para mim; não havia link para a próxima parte.

Eu mesmo já iniciei uma pequena modificação no código para se adequar à minha estratégia, mas encontrei dificuldades para entender algumas funções.

A questão é que vou gerar uma linha de comando a partir dos dados obtidos de outro indicador, com os símbolos mais fortes, para posterior análise e tomada de decisão.

Ao mesmo tempo, utilizo uma janela principal, com um símbolo não comercial, para análise geral, e outras janelas com os símbolos mais fortes, com indicadores adicionais e especialistas para negociação.

Utilizando este mesmo indicador modificado, ele irá gerar automaticamente uma linha de comando com os símbolos mais fortes do mesmo período na janela principal; e nas janelas de negociação, irá gerar uma linha de comando de um símbolo, com diferentes períodos de negociação.

jandrei.thomas
jandrei.thomas | 15 out 2023 em 05:40
Não sou leigo em programação, mas estou com dificuldade para instalar o arquivo  Chart_In_SubWindow_k_Version_1.0a.zip, para conseguir rodar multiplas ativos em uma mesma janela, poderia me ajudar?
Daniel Jose
Daniel Jose | 16 out 2023 em 10:32
jandrei.thomas #:
Não sou leigo em programação, mas estou com dificuldade para instalar o arquivo  Chart_In_SubWindow_k_Version_1.0a.zip, para conseguir rodar multiplas ativos em uma mesma janela, poderia me ajudar?

Mas não existe grandes dificuldades ... Já que você diz não ser leigo em programação ... Você simplesmente baixa o arquivo ZIP, descompacta ele no diretório MQL5, usa o MetaEditor para compilar o indicador. Assim ele ficará disponível para ser usado no MetaTrader 5. Simples assim. Agora, para usar ele de fato. É preciso ler o ARTIGO e seguir o que é explicado ali. Já que não é só compilar e estará tudo funcionando. É preciso executar alguns passos extras, sem pular nem um dos que é mostrado nos artigos onde explico o tal indicador. Assim ele o indicador irá de fato funcionar como mostrado nas animações. 🙂👍

jandrei.thomas
jandrei.thomas | 24 nov 2023 em 03:44

Boa noite!

Realizei todos os passos conforme demonstrado no artigo, porémo meu gráfico no não carrega as sub janelas do SubWindow. ao aplicar três indicadores, ele entra somente um espaço em branco, da um piscado do ativo e some tudo, já tentei de toda maneira, mas não fixa. Poderia me enviar um tamplates, sendo a Janela principal WINZ23 no 2m, com sub janela com OS ATIVOS: WINZ23 no 5m, WINZ23 15M, WINZ23 60M. E um tamplate do ativo WINZ23 2M, com indicadores, RSI, MACD, VOLUME. Estou achando que o aquivo do Chart In SubWindo não esta certo e bloqueando. Ademais, seri ainteressante disponibilar um video ensinando o pessoal. Sem mais, desde já agradeço pelo atenção e ajuda.

Combinatória e teoria da probabilidade para negociação (Parte IV): lógica de Bernoulli Combinatória e teoria da probabilidade para negociação (Parte IV): lógica de Bernoulli
Neste artigo decidi destacar o conhecido esquema Bernoulli e mostrar como este pode ser usado ao descrever uma matriz de dados relacionados ao trading, para uso posterior no caminho à criação de um sistema de negociação auto-adaptável. Também manusearemos um algoritmo mais geral, nomeadamente a fórmula de Bernoulli e encontraremos sua aplicação.
Modelo de regressão universal para previsão de preços do mercado (Parte 2): funções de processos transitórios naturais, sociais e de origem tecnológica Modelo de regressão universal para previsão de preços do mercado (Parte 2): funções de processos transitórios naturais, sociais e de origem tecnológica
Este artigo é uma continuação lógica do anterior e é escrito para destacar suas conclusões ao longo da década seguinte à sua publicação, no que diz respeito às três funções de processos dinâmicos transitórios que descrevem os padrões de mudança de preços de mercado.
Desenvolvimento de robôs de negociação usando programação visual Desenvolvimento de robôs de negociação usando programação visual
Este artigo demonstra as capacidades do editor botbrains.app, uma plataforma no-code para o desenvolvimento de robôs de negociação. Para criar um robô de negociação você não precisa programar, basta arrastar os blocos necessários para o esquema, definir seus parâmetros e estabelecer as ligações entre eles.
Como se tornar um bom programador (Parte 7): como se tornar um desenvolvedor freelancer de sucesso Como se tornar um bom programador (Parte 7): como se tornar um desenvolvedor freelancer de sucesso
Quer se tornar um desenvolvedor de sucesso no Freelance da MQL5.Community? Então recomendo a leitura das dicas deste artigo.