English Русский 中文 Español Deutsch 日本語
preview
Letreiro de Cotação — Versão Básica

Letreiro de Cotação — Versão Básica

MetaTrader 5Negociação | 27 setembro 2022, 14:40
884 3
Daniel Jose
Daniel Jose

Introdução

Muita gente acha bastante legal aqueles letreiros, que algumas plataformas tem, onde aparecem as cotações de alguns ativos, enquanto o leiteiro fica ali sendo mostrado. Se você não sabe do que se trata veja a animação abaixo:

Este tipo de coisa pode ser bastante útil em alguns casos, então aqui irei mostrar como você pode conseguir implementar este tipo de recurso, diretamente na plataforma MetaTrader 5, e usando uma programação 100% em MQL5. Sei que muitos podem achar o que será implementado bastante básico, mas garanto que se você entender os conceitos envolvidos aqui, conseguirá produzir algo bem mais elaborado.

Mas independentemente disto, pretendo fazer alguns outros artigos implementando ainda mais este leiteiro, de forma que ele possa se tornar uma ferramenta extremamente útil, para você que deseja operar acompanhando algumas outra informações em tempo real.

Devo confessar que a ideia deste artigo foi de uma pessoa de dentro da comunidade, mas pelo fato de que a coisa é bastante interessante de ser feita e desenvolvida, mas principalmente, pode ser um recurso bastante útil para muitos, decidi mostrar como construir o código deste letreiro.


Planejamento

A confecção da tal faixa não é algo nem um pouco complicado, na verdade é até bem simples de ser feito, frente a outros tipos de códigos, apesar de tudo, antes de realmente começarmos, temos que planejar algumas coisas e estas irão influenciar totalmente qual será de fato a direção que iremos tomar, para conseguir implementar o recurso. Já que a ideia é simplesmente ter uma faixa que ficará mostrando o ativo, com a sua cotação no momento que ele estiver sendo visto, não teremos grandes problemas a primeira vista, mas lembre-se que irei mostrar como fazer um sistema bem básico, mas que irá servir de ponto inicial para algo ainda mais complexo, sofisticado e até mesmo mais elaborado.

A primeira coisa de fato a se pensar é: Como você irá adicionar a lista de ativos a serem colocados no letreiro. Por um acaso será uma lista fechada ?!?! Onde todos os ativos a serem mostrados serão previamente selecionados ?!? ou você irá digitar um a um, em uma input quando for aplicar o sistema ?!?!

Bem este talvez seja de fato a parte mais complicada de todas, já que talvez você queira ter ativos de maior interesse em alguns momentos, e em outros apenas ter os ativos que você tem em carteira. Desta forma talvez o melhor caminho seja a utilização um arquivo, que irá conter todos os ativos que você deseja utilizar no letreiro. Então fechou: Iremos utilizar um ARQUIVO, que irá conter os ativos a serem mostrados.

Agora surge um outro problema: Como iremos apresentar o recurso ?!?! Parece bobagem, mas isto é algo bastante importante de ser pensado, você poderá utilizar um EA, um Script, um Indicador ou um Serviço, se bem que o uso do serviço não parece assim tão promissor, apesar de pessoalmente gostar bem mais de utilizar o letreiro, como sendo um serviço este tem seus problemas, e estes detalhes e complicações torna criar o letreiro como sendo um serviço, algo complicado demais, então ficamos limitados a outras 2 escolhas, pois é isto mesmo temos apenas 2 formas praticas de fazer o letreiro: Colocar ele em um EA ou em um Indicador. mas por que não usar ele em um script ?!?!

O motivo é simples: Caso o usuário venha a fazer alguma mudança no tempo gráfico, ele acabará por encerrar o script desta forma a cada mudança de no gráfico, o operador irá ser forçado executar novamente o script. Lembrando que estou propondo uma solução 100% em MQL5, até dá para contornar este inconveniente via programação externa, mas este não é o meu objetivo aqui.

Mesmo tendo como ponto de partida, poder usar um EA ou um Indicador para desenvolver o letreiro, não gosto muito da ideia de utilizar um EA, isto por que gosto de usar o EA voltado apenas e somente para o que de fato ele foi desenvolvido: Cuidar do sistema de ordens e me auxiliar no envio e controle das mesmas, nos restando então apenas uma solução utilizar um indicador.

Ainda temos outras questões a serem desenvolvidas e planejadas, mas com base neste planejamento prévio já podemos começar. Então mãos a obra.


Implementando o básico

Vamos começar criando o arquivo do indicador, abaixo você pode ver como ele será neste primeiro momento:

#property copyright "Daniel Jose"
#property description "Gadget para cotações em letreiro."
#property description "Este cria uma faixa que mostra o preço dos ativos."
#property description "Para detalhes de como usá-lo visite:\n"
#property description "https://www.mql5.com/pt/articles/10941"
#property link "https://www.mql5.com/pt/articles/10941"
#property indicator_separate_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+

Apesar do indicador estar com o código totalmente limpo, ou seja ele não irá fazer nada em especial, já temos uma pequena noção do que irá vim pela frente, ou seja iremos utilizar uma janela em separado, precisaremos de tratar um numero maior de eventos do que normalmente teríamos em um indicador clássico, como por exemplo o OnTime que normalmente não aparece em nenhum indicador. Mas também não se esqueçam do seguinte: Não iremos plotar absolutamente nada, tudo que o indicador irá criar e mostrar será feito por ele.

Normalmente já teriamos um código iniciado, mostrando as coisas já nele, mas este artigo em especial, irei mostrar as coisas em um nível de detalhamento um pouco diferente, para que você possa utilizar ele como uma fonte de pesquisa e estudo.

Então, você já deve ter pensado que iremos precisar de muito mais coisas, para realmente fazer tudo funcionar. De certa forma, isto é verdade, mas não serão tantas coisas assim, mas a primeira que você deve começar de fato a pensar é como gerenciar o gráfico, e para isto temos uma classe, que apesar de muitos já terem visto ela em outros artigos que postados por mim, aqui ela será um pouco diferente, já que iremos precisar utilizar bem menos coisas, então lhes apresento, para quem ainda não conhece, a classe C_Terminal. ela se encontra no arquivo de cabeçalho C_Terminal.mqh, seu código é bastante simples, então vamos vê-lo por completo logo abaixo:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
class C_Terminal
{
//+------------------------------------------------------------------+
        private :
                struct st00
                {
                        long    ID;
                        int     Width,
                                Height,
                                SubWin;
                }m_Infos;
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+          
                void Init(const int WhatSub)
                        {
                                ChartSetInteger(m_Infos.ID = ChartID(), CHART_EVENT_OBJECT_DELETE, m_Infos.SubWin = WhatSub, true);
                                Resize();
                        }
//+------------------------------------------------------------------+
inline long Get_ID(void)   const { return m_Infos.ID; }
inline int GetSubWin(void) const { return m_Infos.SubWin; }
inline int GetWidth(void)  const { return m_Infos.Width; }
inline int GetHeight(void) const { return m_Infos.Height; }
//+------------------------------------------------------------------+
                void Resize(void)
                        {
                                m_Infos.Width = (int) ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
                                m_Infos.Height = (int) ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
                        }
//+------------------------------------------------------------------+
inline string ViewDouble(double Value)
                        {
                                Value = NormalizeDouble(Value, 8);
                                return DoubleToString(Value, ((Value - MathFloor(Value)) * 100) > 0 ? 2 : 0);
                        }
//+------------------------------------------------------------------+
                void Close(void)
                        {
                                ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, m_Infos.SubWin, false);
                        }
//+------------------------------------------------------------------+          
};
//+------------------------------------------------------------------+

E sim, este é todo o código do qual precisamos, se bem que esta classe é bem maior, mas aqui temos apenas as partes realmente necessárias, já que não quero contaminar o artigo com coisas desnecessárias.

Então para quem não sabe o que esta classe faz, vamos dar uma rápida passada por suas partes. Já que desejamos que o MetaTrader 5 nos informe de qualquer tentativa de remoção de algum objeto, temos que declarar isto neste local, e logo depois iremos capturar as dimensões da janela que estamos usando, aqui na verdade estamos criando um nível extra de abstração para nos ajudar na programação.

Mas isto não é de fato necessário, caso você deseje fazer as coisas de uma outra forma, mas por conta deste nível de abstração, onde escondemos o que de fato não esta sendo montado, já que as coisas podem não ser necessariamente o que se parece, temos algumas chamadas para acessar os dados da classe. Quando a classe está finalizando, temos que evitar de gerar eventos, quando o indicador começar a remover os objetos, e para isto, utilizamos esta função. Agora já que existe um ponto no código em que precisaremos criar uma formatação, fazemos isto aqui, desta forma centralizamos qualquer coisa relacionada ao terminal nesta classe.

Notem que as coisas aqui são bem simples e bastante diretas, mas vamos começar a complicar as coisas, e vamos fazer isto a partir de agora.


Implementando os objetos básico.

Por mais estranho que pareça, nos iremos utilizar apenas e somente dois único objeto neste modelo mais básico do letreiro, mas por conta que estou utilizando um modelo, que vocês irão ver muito na serie de artigos Desenvolvendo um EA de negociação do Zero, de minha autoria, que foi postado aqui na comunidade MQL5, vou pegar emprestado o sistema de lá, apesar de na serie sobre o EA, a descrição e o uso ser consideravelmente muito mais intensa que aqui, ainda assim irei dar uma rápida explicada sobre como o sistema funciona, para que você não fique muito perdido, caso não tenha todo o conhecimento de como o MetaTrader 5 lida com os objetos.

Vamos então começar com a classe base dos objetos, tem o seu código visto logo abaixo:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "..\Auxiliar\C_Terminal.mqh"
//+------------------------------------------------------------------+
class C_Object_Base
{
        public  :
//+------------------------------------------------------------------+
virtual void Create(string szObjectName, ENUM_OBJECT typeObj)
                        {
                                ObjectCreate(Terminal.Get_ID(), szObjectName, typeObj, Terminal.GetSubWin(), 0, 0);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTABLE, false);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTED, false);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, true);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TOOLTIP, "\n");
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, false);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
                        };
//+------------------------------------------------------------------+
                void PositionAxleX(string szObjectName, int X)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XDISTANCE, X);
                        };
//+------------------------------------------------------------------+
                void PositionAxleY(string szObjectName, int Y, int iArrow = 0)
                        {
                                int desl = (int)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, (iArrow == 0 ? Y - (int)(desl / 2) : (iArrow == 1 ? Y : Y - desl)));
                        };
//+------------------------------------------------------------------+
virtual void SetColor(string szObjectName, color cor)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor);
                        }
//+------------------------------------------------------------------+
                void Size(string szObjectName, int Width, int Height)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XSIZE, Width);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE, Height);
                        };
//+------------------------------------------------------------------+
};

Vejam que o código é simples e bastante compacto, nos permitindo elevar o nível de abstração, para um patamar um pouco mais alto, de forma que precisaremos de bem menos código depois, se você observar irá ver que temos uma função, só que esta função é virtual, mas ela é responsável por criar, de forma o mais genérica possível, qualquer um dos objetos, mas já que iremos utilizar apenas um, neste modelo mais básico, você pode achar que esta função é meio que perda de tempo, mas na verdade não é, se você ver este código no sistema de ordens do EA, irá entender do que estou falando.

Da mesma forma contamos com duas outras funções para posicionar o objeto no gráfico de forma adequada. Contamos também com uma função que é usada para modificar a cor do objeto, e igual a função de criação, esta também é uma função virtual, já que alguns objetos tem um padrão de cores mais complexo, e por ultimo temos uma função que ajusta as dimensões do objeto.

Apesar de parecer bobagem, o fato de fazermos este tipo de abstração, nos ajuda em muito depois, visto que todos os objetos serão tratados de uma forma única, não importando qual objeto seja. E isto nos traz algumas vantagens, mas as vantagens envolvidas ficarão para um outro momento, vamos ver então qual será o objeto escolhido para criar o letreiro. E o feliz objeto escolhido é: OBJ_EDIT. E temos o seu código completo logo abaixo:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Base.mqh"
//+------------------------------------------------------------------+
#define def_ColorNegative       clrCoral
#define def_ColoPositive        clrPaleGreen
//+------------------------------------------------------------------+
class C_Object_Edit : public C_Object_Base
{
        public  :
//+------------------------------------------------------------------+
                template < typename T >
                void Create(string szObjectName, color corTxt, color corBack, T InfoValue)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_EDIT);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, "Lucida Console");
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, 10);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_LEFT);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, corTxt);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, corBack);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, corBack);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true);
                                if (typename(T) == "string") ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, (string)InfoValue); else SetTextValue(szObjectName, (double)InfoValue);
                        };
//+------------------------------------------------------------------+
                void SetTextValue(string szObjectName, double InfoValue, color cor = clrNONE)
                        {
                                color clr;
                                clr = (cor != clrNONE ? cor : (InfoValue < 0.0 ? def_ColorNegative : def_ColoPositive));                                
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, Terminal.ViewDouble(InfoValue < 0.0 ? -(InfoValue) : InfoValue));
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clr);
                        };
//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+
#undef def_ColoPositive
#undef def_ColorNegative
//+------------------------------------------------------------------+

Cara mais é somente isto ?!?! SIM, apesar de ele ser um pouco diferente do código que vocês encontraram no sistema de ordens do EA, sim é somente isto. Aqui temos tudo, uma função para colocar valores do tipo double, e este é o tipo que realmente utilizamos muito dentro do MQL5, temos aqui criação do objeto de edição do tipo Obj_Edit, mas existe uma coisa que pode deixar vocês bastante confusos, caso vocês estejam iniciando na programação, observem com calma a função de criação do objeto, vamos vê-la melhor no fragmento abaixo:

template < typename T >
void Create(string szObjectName, color corTxt, color corBack, T InfoValue)

estas duas linhas, na verdade são consideradas pelo compilador como sendo apenas uma única linha, mas você tem noção do que esta realmente acontecendo aqui, ou está completamente perdido ?!?! Achando que estou inventando coisas para dificultar a vida de vocês ?!?!

Pois bem, quando usamos template < typename T > Observação : O T pode ser substituído por qualquer outra coisa desde que satisfaça as regras de nomenclatura, estará valendo. Estamos de fato definindo uma forma de sobrecarga, é bastante comum muitas vezes termos que criar funções iguais, mas que receberão termos ou tipos de dados diferentes, isto é extremamente comum, para facilitar a nossa vida nestas horas, utilizamos esta sintaxe, um pouco estranha para alguns, mas bastante comum quando não queremos reescrever toda uma função, apenas por que um de seus dados serão diferentes, mas todo o corpo interno da função será igual.

Se você prestar atenção, irá ver que existe apenas uma única linha no final do procedimento, que contem um código engraçado:

if (typename(T) == "string") ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, (string)InfoValue); else SetTextValue(szObjectName, (double)InfoValue);

o que este código esta fazendo é: Testar qual o tipo do dado esta sendo informado na variável InfoValue, ATENÇÃO : Estou dizendo TIPO e não VALOR, não confunda os dois.

Caso o tipo seja uma string, teremos a execução de um código, caso seja outro, teremos a execução de um outro código, mas isto não é de fato feito pelo compilador ou linkeditor, este tipo de analise, normalmente é feita em RunTime ( Tempo de Execução ), por conta disto temos que informar de uma maneira explicita, qual o tipo de dado que deverá ser processado, para que o linkeditor consiga ajustar as coisas de forma adequada, e isto é feito usando as seguintes medidas em destaque.

Então ao invés de criar duas funções, praticamente idênticas, com apenas e somente uma única diferença, fazemos a sobrecarga da mesma e ajustamos as coisas onde é preciso, de forma que no final teremos bem menos trabalho.

É um fato que este tipo de abordagem não foi necessária no código do EA, pois lá esta função irá trabalhar apenas e somente com um tipo básico que é o double, mas aqui além do double, ainda iremos trabalhar com string e eu não queria ter que criar todo o código igualzinho apenas para separar estes dois tipos.

Quem desejar mais detalhes sobre isto, pode dar uma olhada em Templates de Função, lá ficará mais simples de você entender como e por que a sobrecarga de funções é tão utilizada, e como fazer para evitar ter que reescrever todo o código, apenas por conta de tipos diferentes.

Mas antes de finalizar este tópico sobre os objetos, não posso deixar de dar uma rápida passada pelo objeto que irá ficar no fundo do letreiro, pois é isto mesmo, precisamos criar um fundo, ou você espera que tudo irá funcionar de forma toda bonitinha, sem que tenhamos um fundo ?!?! mas não se preocupem, o código para isto é extra simples, e pode ser visto logo abaixo:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Base.mqh"
//+------------------------------------------------------------------+
class C_Object_BackGround : public C_Object_Base
{
        public:
//+------------------------------------------------------------------+
                void Create(string szObjectName, color cor)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_RECTANGLE_LABEL);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
                                this.SetColor(szObjectName, cor);
                        }
//+------------------------------------------------------------------+
virtual void SetColor(string szObjectName, color cor)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, cor);
                        }
//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+

Este código é tão simples, e direto que praticamente não será preciso que entremos em muitos detalhes, dado que a única utilidade dele será criar um fundo para o letreiro, mas estou mostrando ele aqui apenas para constar, caso alguém fique imaginando como é o código responsável pela criação do fundo.

Muito bom, com isto podemos encerrar este tópico e agora que já implementamos os objetos que precisamos e já temos a estrutura de suporte para o terminal, podemos passar para a próxima etapa.


Implementando a Classe Principal

Bem, até agora preparamos o terreno para esta etapa, que diga-se por passagem é a mais emocionante de todas, já que aqui iremos de fato fazer o sistema funcionar. O código para isto se encontra no arquivo de cabeçalho C_Widget.mqh, mas vamos dar uma especulada nele, começando com as declarações de iniciais que podem ser vista logo abaixo:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "Elements\C_Object_Edit.mqh"
#include "Elements\C_Object_BackGround.mqh"
//+------------------------------------------------------------------+
C_Terminal Terminal;
//+------------------------------------------------------------------+
#define def_PrefixName          "WidgetPrice"
#define def_NameObjBackGround	def_PrefixName + "BackGround"
#define def_MaxWidth            80
//+------------------------------------------------------------------+
#define def_CharSymbol          "S"
#define def_CharPrice           "P"
//+------------------------------------------------------------------+
#define macro_MaxPosition (Terminal.GetWidth() >= (m_Infos.nSymbols * def_MaxWidth) ? Terminal.GetWidth() : m_Infos.nSymbols * def_MaxWidth)
#define macro_ObjectName(A, B) (def_PrefixName + (string)Terminal.GetSubWin() + A + "#" + B)
//+------------------------------------------------------------------+

Aqui fazemos as declarações dos arquivos de cabeçalho que realmente precisamos, apesar de existirem outros, não precisamos declarar todos, bastará estes, já que eles estaram absorvendo todos os demais.

Declaramos também a classe de terminal, de forma a poder utilizá-la aqui, e para facilitar bastante a nossa vida durante o processo de programação, temos diversas definições e macros a serem utilizadas neste arquivo de cabeçalho C_Widget.mqh, mas você precisa de fato tomar o máximo cuidado é com as macros, já que elas tem que ser utilizadas da forma correta, mas não existem grandes problemas, desde que você utilize elas corretamente, elas lhe ajudarão muito.

Uma vez que isto foi feito, passamos para a declaração da classe, com as suas variáveis iniciais.

class C_Widget
{
        protected:
                enum EventCustom {Ev_RollingTo};
        private :
                struct st00
                {
                        color   CorBackGround,
                                CorSymbol,
                                CorPrice;
                        int     nSymbols,
                                MaxPositionX;
                        struct st01
                        {
                                string szCode;
                        }Symbols[];
                }m_Infos;

Este enumerador será bastante útil depois, apesar de não ser de todo exigido, é bom termos ele para nos ajudar a deixar as coisas mais abstratas, e assim ter um código mais fácil de ser lido e compreendido. Logo depois declaramos uma estrutura que irá nos ajudar a controlar várias coisas, mas no momento não se preocupe com ela, apenas entenda que ela está ai, e é uma parte totalmente privada da classe, ou seja, nenhum código externo conseguirá ter acesso a ela.

Agora vamos entrar nos procedimentos propriamente ditos, e o primeiro é visto logo abaixo:

void CreateBackGround(void)
{
        C_Object_BackGround backGround;
                        
        backGround.Create(def_NameObjBackGround, m_Infos.CorBackGround);
        backGround.Size(def_NameObjBackGround, Terminal.GetWidth(), Terminal.GetHeight());
}

aqui estamos de fato criando o fundo do letreiro, notem que iremos utilizar toda a área da sub janela, e colocar ali um objeto preenchendo tudo com uma única cor. Desta fora obteremos de fato um fundo uniforme. Esta foi fácil não é mesmo ?!?! como falei no tópico anterior, estamos criando um nível de abstração que nos permite programar muito menos e obter resultados mais rapidamente, mas agora vamos a algo um pouco mais complicado.

void AddSymbolInfo(const string szArg, const bool bRestore = false)
        {
#define macro_Create(A, B, C)   {                                               \
                edit.Create(A, m_Infos.CorSymbol, m_Infos.CorBackGround, B);    \
                edit.PositionAxleX(A, def_MaxWidth * m_Infos.nSymbols);         \
                edit.PositionAxleY(A, C);                                       \
                edit.Size(A, def_MaxWidth - 1, 22);                             \
                                }
                        
                C_Object_Edit edit;

                macro_Create(macro_ObjectName(def_CharSymbol, szArg), szArg, 10);
                macro_Create(macro_ObjectName(def_CharPrice, szArg), 0.0, 32);
                if (!bRestore)
                {
                        ArrayResize(m_Infos.Symbols, m_Infos.nSymbols + 1, 10);
                        m_Infos.Symbols[m_Infos.nSymbols].szCode = szArg;
                        m_Infos.nSymbols++;
                }
#undef macro_Create
        }

Este procedimento, já começa sendo ignorante e vou logo declarando uma macro para utilizar dentro dele, observem que antes de encerrar o procedimento irei destruir a macro por conta que ela não tem nenhuma utilidade fora do procedimento.

O que estamos fazendo aqui é criar um objeto do tipo C_Object_Edit, logo depois fazer um posicionamento temporário do mesmo e informar o tamanho que ele deverá ter. Tudo isto dentro da macro. Já nestes pontos fazemos o pedido pela macro de forma a ter um código mais fácil de ler, já que todo o processo é praticamente igual, claro que existem a questão dos valores, mas o procedimento é igual, então usamos a macro para facilitar ao máximo, lembre-se menos digitação, mais produção.

Agora tem um detalhe, este procedimento também é chamado quando o usuário remove um objeto que não deveria ser removido, desta forma se este for o caso, não teremos a execução das próximas linhas, mas durante o processo normal de criação teremos a execução, onde primeiro alocamos espaço na memória, depois colocamos o nome do símbolo na posição alocada e incrementamos para a próxima chamada. Desta forma podemos passar para o próximo código.

Seguindo na ordem que os códigos vão aparecendo, a próxima rotina é bastante interessante, vamos a ela:

inline void UpdateSymbolInfo(const int x, const string szArg)
{
        C_Object_Edit edit;
        string sz0 = macro_ObjectName(def_CharPrice, szArg);
        MqlRates Rate[1];
                                
        CopyRates(szArg, PERIOD_M1, 0, 1, Rate);                                
        edit.PositionAxleX(macro_ObjectName(def_CharSymbol, szArg), x);
        edit.SetTextValue(sz0, Rate[0].close, m_Infos.CorPrice);
        edit.PositionAxleX(sz0, x);
}

Muita gente acha que precisamos de objetos a nível global, mas na verdade dentro do MT5 e usando o MQL5, isto não é de todo uma verdade, já que todos objetos criados ficam disponíveis para serem manipulados conforme a necessidade, para saber qual o nome do objeto, você apenas precisará observar a janela que lista todos os objetos presentes no gráfico do ativo, desta forma podemos usar um acesso local e manipular os objetos contidos no gráfico do ativo, desde é claro você saiba o nome dele.

Então criamos o nome do objeto para poder manipular o mesmo, para facilitar utilizaremos a macro para este trabalho. Feito isto agora vem uma coisa interessante, normalmente teríamos que ter todo o ativo que desejamos obter informações na janela de observação de mercado, mas para nos, aqui no letreiro de cotação, isto faria com que o usuário viesse a ser desmotivado a utilizar o nosso sistema, caso tivesse que abrir, dezenas, ou centenas de ativos e estes ficassem ali na janela de observação de mercado, então para não fazer isto, utilizamos um outro método, porém isto tem um custo, nada é realmente de graça, o custo é que a cada chamada a este procedimento iriemos fazer uma copia da última barra, assim ficamos sabendo o que aconteceu.

Feito isto posicionamos os objetos corretos na posição adequada, e informamos o valor para que ele seja plotado, mas lembre-se que a cada chamada teremos um pequeno retardo na execução, mas iremos melhorar isto mais para frente, neste mesmo artigo.

A próxima rotina na sequencia pode ser observada a seguir:

bool LoadConfig(const string szFileConfig)
{
        int file;
        string sz0;
        bool ret;
                                
        if ((file = FileOpen("Widget\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
        {
                PrintFormat("Arquivo de configuração %s não encotrado.", szFileConfig);
                return false;
        }
        m_Infos.nSymbols = 0;
        ArrayResize(m_Infos.Symbols, 30, 30);
        for (int c0 = 1; (!FileIsEnding(file)) && (!_StopFlag); c0++)
        {
                if ((sz0 = FileReadString(file)) == "") continue;
                if (SymbolExist(sz0, ret)) AddSymbolInfo(sz0); else
                {
                        FileClose(file);
                        PrintFormat("Ativo na linha %d não foi reconhecido.", c0);
                        return false;
                }
        }
        FileClose(file);
        m_Infos.MaxPositionX = macro_MaxPosition;
                
        return !_StopFlag;
}

Aqui iremos ler o arquivo, que contém todos os ativos a serem utilizados no letreiro, observem que não estou forçando nenhum tipo de extensão, apenas uma localização para que o arquivo seja encontrado, desta forma você estará livre para dar qualquer nome para o arquivo, podendo assim ter arquivos distintos para coisas distintas.

Deve-se tomar cuidado em apontar para um arquivo contendo dados corretos, caso contrário você poderá experimentar alguns problemas, mas no anexo onde também estará o código completo do sistema, irei colocar um arquivo para demonstrar uma forma de formatação interna, neste arquivo você poderá encontrar todos os ativos presentes atualmente no Index Ibovespa ( IBOV ), utilize este arquivo como base para criar todos os demais, pois quando forem implementadas as melhorias neste sistema, irei utilizar a mesma formatação que se encontra no arquivo do anexo.

Se o arquivo foi encontrado e podendo ser aberto, executamos uma chamada para alocar espaço em memória, de forma a armazenar os dados conforme eles vão entrando. A seguir começamos ler linha por linha, até o final do arquivo, ou até que o usuário peça para interromper o sistema. Caso alguma linha esteja em branco, ou sem nenhuma informação, fazemos uma nova chamada de leitura. Agora algo importante, o ativo somente será adicionado se ele existir, caso não exista será informado um erro indicando em qual linha ele ocorreu, esta mensagem poderá ser vista na janela de ferramentas e nenhuma outra linha será lida, sendo retornado um erro. No final ajustamos uma informação crucial para nosso futuro, isto evitará cálculos desnecessários depois.

~C_Widget()
{
        Terminal.Close();
        ObjectsDeleteAll(Terminal.Get_ID(), def_PrefixName);
        ArrayFree(m_Infos.Symbols);
}

A função acima é um destructor da classe, ela é chamada automaticamente quando a classe é encerrada, no caso quando isto acontecer, todo o sistema será encerrado junto com ela, ao mesmo tempo que todos os objetos criados dentro da serão destruídos, e a memória alocada será liberada.

Já no código abaixo temos o sistema de inicialização da classe:

bool Initilize(const string szFileConfig, const string szNameShort, color corText, color corPrice, color corBack)
{
        IndicatorSetString(INDICATOR_SHORTNAME, szNameShort);
        Terminal.Init(ChartWindowFind());
        Terminal.Resize();
        m_Infos.CorBackGround = corBack;
        m_Infos.CorPrice = corPrice;
        m_Infos.CorSymbol = corText;
        CreateBackGround();

        return LoadConfig(szFileConfig);
}

Não existe muita coisa a se dizer sobre ele, já que no decorre do tempo fui explicando cada coisa que é utilizada aqui, menos os seguintes pontos, aqui iremos definir um nome curto para o nosso indicador, este nome é informado como um parâmetro, então fique atento a isto, agora neste código aqui é para capturar o index da sub janela que o indicador estará utilizando, isto é importante por conta dos objetos, precisamos saber qual é a sub janela que esta sendo utilizada, caso contrário corremos o risco de colocar os objetos no lugar errado.

E como última rotina dentro deste arquivo de cabeçalho, temos o sistema de tratamento de mensagens.

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        static int tx = 0;
        string szRet[];
                                                        
        switch (id)
        {
                case (CHARTEVENT_CUSTOM + Ev_RollingTo):
                        tx = (int) (tx + lparam);
                        tx = (tx < -def_MaxWidth ? m_Infos.MaxPositionX : (tx > m_Infos.MaxPositionX ? -def_MaxWidth : tx));
                        for (int c0 = 0, px = tx; (c0 < m_Infos.nSymbols); c0++)
                        {
                                if (px < Terminal.GetWidth()) UpdateSymbolInfo(px, m_Infos.Symbols[c0].szCode);
                                px += def_MaxWidth;
                                px = (px > m_Infos.MaxPositionX ? -def_MaxWidth + (px - m_Infos.MaxPositionX) : px);
                        }
                        ChartRedraw();
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        m_Infos.MaxPositionX = macro_MaxPosition;
                        ChartRedraw();
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (StringSubstr(sparam, 0, StringLen(def_PrefixName)) == def_PrefixName) if (StringSplit(sparam, '#', szRet) == 2)
                        {
                                AddSymbolInfo(szRet[1], true);
                                ChartRedraw();
                        }else if (sparam == def_NameObjBackGround)
                        {
                                ObjectsDeleteAll(Terminal.Get_ID(), def_PrefixName);
                                CreateBackGround();
                                for (int c0 = 0; c0 < m_Infos.nSymbols; c0++) AddSymbolInfo(m_Infos.Symbols[c0].szCode, true);
                                ChartRedraw();
                        }
                        break;
        }
}

A maior parte deste código é bastante simples de ser compreendido, temos 2 eventos que são gerados pela plataforma e são repassados para o indicador para que ele os trate, mas também temos um tipo de evento que para muitos não faz sentido, pois se trata de um evento customizado, este tipo de evento é bastante comum em alguns tipos de projetos, mas aqui ele server mais para que possamos centralizar o tratamento das mensagens, ou eventos que possam estar acontecendo. Pois apesar de muitos não entenderem, a plataforma MetaTrader 5 assim como a linguagem MQL5 são voltadas a eventos, ou seja não trabalhamos de fato de uma forma processual, trabalhamos com eventos e os tratamos conforme eles vão surgindo.

Para entender como este evento customizado é gerado, temos que ver o código do indicador, então antes de explicar, pois acredito que é justamente neste evento que muitos terão dificuldade de entender o que esta acontecendo, vamos ver o código do indicador, que agora já estará de uma forma funcional, diferente do que foi mostrado no inicio do artigo.

#property copyright "Daniel Jose"
#property description "Gadget para cotações em letreiro."
#property description "Este cria uma faixa que mostra o preço dos ativos."
#property description "Para detalhes de como usá-lo visite:\n"
#property description "https://www.mql5.com/pt/articles/10941"
#property link "https://www.mql5.com/pt/articles/10941"
#property indicator_separate_window
#property indicator_plots 0
#property indicator_height 45
//+------------------------------------------------------------------+
#include <Widget\Rolling Price\C_Widget.mqh>
//+------------------------------------------------------------------+
input string    user00 = "Config.cfg";  //Arquivo de configuração
input int       user01 = -1;            //Deslocamento
input int       user02 = 60;            //Pausa em milissegundos
input color     user03 = clrWhiteSmoke; //Cor do Ativo
input color     user04 = clrYellow;     //Cor do Preço
input color     user05 = clrBlack;      //Cor de Fundo
//+------------------------------------------------------------------+
C_Widget Widget;
//+------------------------------------------------------------------+
int OnInit()
{
        if (!Widget.Initilize(user00, "Widget Price", user03, user04, user05))
                return INIT_FAILED;
        EventSetMillisecondTimer(user02);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        EventChartCustom(Terminal.Get_ID(), C_Widget::Ev_RollingTo, user01, 0.0, "");
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Widget.DispatchMessage(id, lparam, dparam, sparam);
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

Aqui temos algo muito pouco visto em grande parte dos código de indicadores, uma indicação da altura que a janela do indicador irá ter, mas a questão aqui não é esta e sim outra, observem o seguinte detalhe:

Quando o usuário definir um valor para este parâmetro, teremos um dado para ser usado como temporizador, é bem verdade que devemos evitar ao máximo colocar eventos do tipo OnTime em um indicador, mas aqui infelizmente não temos outra maneira, precisamos de um evento deste tipo, agora prestem muita atenção, quando um evento OnTime for disparado pela plataforma MT5, irá ser gerado uma chamada a função de tratamento deste evento, que é a OnTime, dentro desta função temos apenas e somente uma única linha de código, e esta linha irá disparar um evento assíncrono, ou seja, não sabemos exatamente quando o código irá ser de fato chamado, este evento gerado é um evento customizado.

Reparem nos parâmetros dentro deste evento customizado, estes não são parâmetros casuais, eles estão ali por um motivo muito forte, cada um deles indicam uma coisa, mas no final teremos como resultado uma chamada a função OnChartEvent, e esta irá chamar a função presente dentro da classe C_Widget, que irá tratar as mensagens geradas pelos eventos.

Agora prestem atenção ao seguinte detalhe: Quando a função EventChartCustom é usada, fazemos o valor a ser usado como ID da função OnChartEvent, um valor que será identificado lá na função de tratamento de mensagens, se você chama-se diretamente a função de tratamento de mensagens, o código iria ser síncrono, ou seja você iria por todo o restante do código em modo de espera aguardando a função de tratamento de mensagens retornar, mas como a coisa foi feita desta forma, usando uma chamada a EventChartCustom, o código não precisa ficar esperando, desta forma evitamos travar todos os outros indicadores, com algo que não saberemos quanto tempo irá demorar para ser resolvido.

E o fato de fazermos a chamada via EventChartCustom tem mais uma outra vantagem, esta chamada pode ser oriunda de qualquer ponto do código, ou seja não importa de onde você faça a chamada, ela sempre irá disparar um evento ChartEvent e este irá por consequência chamar OnChartEvent, então a coisa se desenrola de uma forma muito mais natural.

Este tipo de abordagem será vista em outro artigo futuro que já esta pronto, sobre um outro tema, igualmente interessante, mas não vou falar do que se trata, vou deixar vocês imaginando e ansiosos até ele vim a publico.

Acredito que esta parte tenha ficado entendida, como o evento customizado é gerado, e por que de eu usar um evento customizado no lugar de fazer uma chamada diretamente ao código que irá mover o letreiro, então vamos voltar ao código onde temos o tratamento deste evento customizado para mover o letreiro, lembrando que existe um parâmetro informado pelo usuário que é muito importante na movimentação então observem este valor contido no indicador.

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        static int tx = 0;
        string szRet[];
                                                        
        switch (id)
        {
                case (CHARTEVENT_CUSTOM + Ev_RollingTo):
                        tx = (int) (tx + lparam);
                        tx = (tx < -def_MaxWidth ? m_Infos.MaxPositionX : (tx > m_Infos.MaxPositionX ? -def_MaxWidth : tx));
                        for (int c0 = 0, px = tx; (c0 < m_Infos.nSymbols); c0++)
                        {
                                if (px < Terminal.GetWidth()) UpdateSymbolInfo(px, m_Infos.Symbols[c0].szCode);
                                px += def_MaxWidth;
                                px = (px > m_Infos.MaxPositionX ? -def_MaxWidth + (px - m_Infos.MaxPositionX) : px);
                        }
                        ChartRedraw();
                        break;

A matemática envolvida, no fragmento acima pode parecer confusa para muitos, mas o que estou fazendo aqui é usando aquele valor fornecido pelo usuário, para mover os objetos em uma determinada distância, ou seja se o valor for positivo, o objeto irá se mover da esquerda para a direita, se ele for negativo da direita para a esquerda, e se ele for zero o objeto irá permanecer parado. A ideia é simples, mas onde esta o calculo que não se pode ver ?!?! Por isto foi dito que o código acima parece confuso, já que quem faz isto, o cálculo é justamente estas duas linhas.

Mas você pode não estar de fato, ainda entendendo como isto é possível, como um cálculo tão simples consegue fazer isto, mas se você prestar bastante atenção, irá ver que o cálculo esta envolvendo limites, e quando o limite é alcançado imediatamente a posição é recalculada para o limite imediatamente ligado a ele, ou seja fechamos o laço, fazendo com que quando o valor é alcançar um determinado ponto, ele será ajustado para que se inicie no ponto que estará no limite oposto, para melhor entender, seria como se você tivesse que contar de 0 até 99 e não soubesse contar além destes valor, o que aconteceria se você tentasse adicionar 1 ao 99 ?!?! na lógica você iria obter 100.

Correto .... mas não neste caso, neste caso você iria imediatamente voltar ao 0, mas se você tentar adicionar 3 ao 98, não irá obter um valor maior que 99, irá obter o valor 1, parece estranho mas é assim mesmo, a mesma coisa se você estiver subtraindo, você ao tentar tirar 3 de 2 iria obter como resultado 99 ... DOIDEIRA não é mesmo 😵😵 ... mas esta é a base do sistema de contagem de um computador, se você estudar com calma, irá ver que o computador não consegue contar quantidades infinitas, muito pelo contrário, existe um limite para o valor máximo obtido, e isto se aplica a uma outra área que é a criptografia, mas isto já é uma outra história. 

Vamos voltar para o código. Se você não entendeu o que expliquei acima, tente primeiro entender isto, pois quando entrarmos no laço FOR a coisa irá ficar ainda mais estranha.

Agora dentro do laço FOR, vamos fazer o seguinte: Não  sabemos onde e quanto deveremos terminar, já que o fato de fazer a contagem acima, não nos indica exatamente o que deverá ou não ser mostrado na tela. Para fazer, isto precisamos criar uma janela, ou melhor dizendo: iremos utilizar os limites da janela do gráfico, para saber o que deve ou não ser mostrado.

Esta parte pode ser extremamente confusa, se você não entendeu o conceito explicado acima, mas as duas únicas informações que temos é: Quantos elementos deveremos mostrar, e qual é o valor que esta sendo atualmente usado, e com base nestes dados precisamos fazer todo o restante. Então vamos passando de elemento em elemento, sempre começando do elemento zero, e conforme vamos progredindo, iremos adicionar a largura de cada elemento a posição inicial do contador de limite. Em um determinado momento, iremos estourar este limite, seja na banda superior, seja na banda inferior, quando este for ultrapassado, o valor que estamos usando para indicar onde o elemento atual seria plotado, deverá ser ajustado de forma adequada, quando isto acontecer, teremos um desvio da posição de forma que a informação irá magicamente desaparecer de um lado da tela para começar a aparecer do outro lado da tela.

E então o ciclo se repete até que o indicador seja encerrado, desta forma todas a informações irão aparecer na tela, não importa a quantidade delas, todas irão aparecer.

No caso de texto puro, este tipo de coisa é consideravelmente mais simples de ser feita e planejada, mas o fato é que apesar da técnica ser bastante parecida, normalmente grande parte das pessoas costumam utilizar um código que faz uso de uma matriz, onde os elementos são movidos dentro da matriz e cada uma das células da matriz, já tem uma posição bem definida para ser mostrada, mas aqui, este tipo de coisa não traria bons resultados, então tive que usar um método um pouco diferente, onde usamos cálculos matemáticos puros, de forma a se ter a movimentação suave e adequada.

Um outro detalhe é o fato que que você deverá evitar, utilizar valores maiores que -1 ou 1, isto pelo fato que que o movimento fica meio que pulsante, dando uma impressão estranha, mas nada impede de você usar outros valores, mas os resultados são bem estranhos ao meu ver.

No video abaixo vocês podem ver o sistema funcionando, com dados dos ativos do IBOV ( Index Ibovespa ), mas ele é apenas para demonstrar como o sistema estará funcionando ....




Conclusão

Apesar de parecer ser um sistema completo e totalmente terminado, ele ainda poderá sofrer melhorias, então no próximo artigo, irei mostrar como fazer para que estas melhorias seja implementadas e adicionada o sistema. Fiquem ligados pois teremos novidades neste sistema. No anexo vocês encontraram todo o código deste artigo, para usar e abusar.


Arquivos anexados |
Últimos Comentários | Ir para discussão (3)
Guilherme Mendonca
Guilherme Mendonca | 27 set 2022 em 16:21

Parabéns Daniel.
Mais um ótimo artigo com um nível de programação profissional.

Será muito bem aproveitado pela comunidade MQL5.

Cleverson Santos
Cleverson Santos | 12 out 2022 em 17:27

Parabéns Daniel!!!!! Estou aprendendo muito com todas as suas postagens!!!


So uma pergunta... funciona para pares de moedas no Forex, se sim,  como fazer com que use so pares e ou ativos específicos e não tudo!!

Daniel Jose
Daniel Jose | 12 out 2022 em 21:14
Cleverson Santos #:

Parabéns Daniel!!!!! Estou aprendendo muito com todas as suas postagens!!!


So uma pergunta... funciona para pares de moedas no Forex, se sim,  como fazer com que use so pares e ou ativos específicos e não tudo!!

Sim é possível. Apesar deste artigo ter vindo a publico recentemente, o mesmo é bem antigo. De lá para cá mudei muito a forma como estou escrevendo os artigos e o foco de cada um deles. Se você os acompanhar irá notar isto com o tempo. Mas vamos a sua questão:

Você deve ter notado que o sistema utiliza um arquivo externo. Durante a explicação do artigo foi mencionado isto:

Aqui iremos ler o arquivo, que contém todos os ativos a serem utilizados no letreiro, observem que não estou forçando nenhum tipo de extensão, apenas uma localização para que o arquivo seja encontrado, desta forma você estará livre para dar qualquer nome para o arquivo, podendo assim ter arquivos distintos para coisas distintas.

Deve-se tomar cuidado em apontar para um arquivo contendo dados corretos, caso contrário você poderá experimentar alguns problemas, mas no anexo onde também estará o código completo do sistema, irei colocar um arquivo para demonstrar uma forma de formatação interna, neste arquivo você poderá encontrar todos os ativos presentes atualmente no Index Ibovespa ( IBOV ), utilize este arquivo como base para criar todos os demais, pois quando forem implementadas as melhorias neste sistema, irei utilizar a mesma formatação que se encontra no arquivo do anexo.

Dentro deste arquivo, em cada uma das linhas você coloca o nome do ativo, e o nome deve ser o mesmo que você irá ver na janela de observação de mercado. Salve o arquivo e o resto é por conta do Letreiro. Você não precisa modificar nada no código, absolutamente NADA. Apenas editar este arquivo que é indicado no artigo e seja feliz.

Existe uma limitação independente do que você possa vim a fazer: Você não poderá, na mesma instancia de execução do MetaTrader 5, observar pares de moedas e mercado de bolsa ao mesmo tempo. O motivo é que existe uma separação entre estes dois tipos de mercado. Mas fora isto, você não terá nenhum tipo de problema.😁👍

Ciência de Dados e Aprendizado de Máquina (Parte 05): Árvores de Decisão Ciência de Dados e Aprendizado de Máquina (Parte 05): Árvores de Decisão
As árvores de decisão imitam a maneira como os humanos pensam para classificar os dados. Vamos ver como construir árvores e usá-las para classificar e prever alguns dados. O principal objetivo do algoritmo de árvores de decisão é separar os dados impuros em puros ou próximos a nós.
Como desenvolver um sistema de negociação baseado no indicador Volumes Como desenvolver um sistema de negociação baseado no indicador Volumes
Aqui está um novo artigo da nossa série sobre como aprender a desenvolver um sistema de negociação com base nos indicadores técnicos mais populares. O artigo atual será dedicado ao indicador de Volumes. O volume como conceito é um dos fatores mais importantes na negociação nos mercados financeiros e nós temos que prestar atenção quanto a isso. Através deste artigo, nós aprenderemos como desenvolver um sistema de negociação simples pelo indicador Volumes.
Letreiro de Cotação — Versão Melhorada Letreiro de Cotação — Versão Melhorada
Que tão darmos uma apimentada na versão básica do Letreiro. Primeira coisa que iremos fazer, é modificar o letreiro de forma a acrescentar uma imagem, seja ela o logotipo do ativo, ou uma outra imagem qualquer, apenas para facilitar uma rápida identificação, de qual ativo estamos vendo.
Gráfico de montanha ou gráfico Iceberg Gráfico de montanha ou gráfico Iceberg
Que tal adicionar um novo tipo de gráfico ao MetaTrader 5 ? Muita gente diz, que ele carece de algumas coisas, que já estão presentes em outras plataformas, mas a verdade, é que o MetaTrader 5, é uma plataforma muito prática, que nos permite fazer coisas, que em muitas das outras, não é possível de ser feita, pelo menos, não com tanta facilidade.