Desenvolvendo um sistema de Replay (Parte 26): Projeto Expert Advisor — Classe C_Terminal
Introdução
No artigo anterior Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 25): Preparação para a próxima etapa deixamos o replay / simulador, adequado para um modo mínimo de uso. Apesar disto, não temos nada além de poder rever os movimentos, ou possíveis movimentos que aconteceram. Então precisamos de algo mais. Algo que nos permita de fato fazer estudos direcionados, como se estivéssemos de fato operando em um mercado real. Para isto precisamos então de um Expert Advisor, a fim de poder fazer este tipo de coisa. Mas iremos ainda mais além, iremos criar um Expert Advisor que possa ser usado em qualquer tipo de mercado, seja no mercado de bolsa, seja no mercado de forex. E também poder ser usado no nosso sistema de replay / simulador.
Certamente dado a magnitude da proposta, a coisa será bem mais intensa do que o normal. O desenvolvimento, não é a parte complicada, como muitos podem imaginar. Isto por que já mostrei em outros artigos como fazer grande parte do que será feito aqui. Uma das sequencias é Desenvolvendo um EA de negociação do zero (Parte 31): Em direção ao futuro (IV) e a outra, Aprendendo a construindo um EA que opera de forma automática (Parte 15): Automação (VII), onde visei explicar como desenvolver um Expert Advisor totalmente automático. Mas independentemente destas duas sequencias de artigos, temos aqui um desafio completamente diferente, e muito mais desafiador. Já que teremos que fazer com que a plataforma MetaTrader 5 se comporte de alguma maneira, como se estivesse ligada ao servidor de negociação. Isto a fim de promover uma correta simulação como se de fato o mercado estivesse aberto, ou seja o desafio é consideravelmente muito mais complexo.
Mas não nos deixemos nos levar pelo grau de dificuldade neste primeiro momento. Temos de começar a fazer as coisas partindo de algum ponto. Caso contrário apenas iremos nos conformar, imaginando o qual difícil o desafio é, sem ao menos tentarmos de fato superar este obstáculo. Vida de programador de fato é isto: Encontrar um obstáculo e tentar superar ele, via estudo, testes e bastante pesquisa. Antes de começar de fato, gostaria de deixar registrado, o imenso prazer que tem sido mostrar e explicar como as coisas realmente vão nascendo. Acredito que muitos tem aprendido bastante com estas series de artigos. Saindo daquele básico que muitos mostram, e vendo que podemos ir bem mais longe do que alguns acreditam ser possível.
Conceitos para implementação do Expert Advisor
Como você já deve ter notado, gosto muito de utilizar o modelo de codificação baseada em OOP ( Programação orientada a objetos ). O motivo se deve a todas as possibilidades que temos, aliada ao fato de que uma OOP nos traz logo de inicio um modo de codificar que torna o código muito mais robusto, seguro e confiável. Para começar precisamos definir uma preconcepção do que precisaremos. Dividindo a coisa em algum tipo de estruturação. Ao longo do tempo, tanto como usuário, como também programador, notei que um Expert Advisor para ser de fato agradável, deve usar o que temos sempre em mãos, ou seja o teclado e o mouse. Já que a plataforma Metatrader 5 usa gráfico, o ideal é usarmos sempre o mouse como uma forma de interagir com a parte gráfica. Mas não somente o mouse, precisamos também do teclado para nos ajudar em vários momentos. Mas a questão aqui, não é bem o uso ou não do mouse e do teclado, como você pode ver na sequencia sobre automação. Em alguns casos isto não é de fato necessário, já que você pode estar usando um Expert Advisor automático. No entanto ao fazer uso de tais instrumentos ( Mouse e Teclado ), precisamos nos atentar ao que iremos de fato estar operando. Por conta disto, alguns Expert Advisores, não são adequados para uso em um ativo ou em outro.
Isto se deve ao fato, de que alguns ativos tem um movimento de preço na ordem de 0,01. Outros podem ser de 0,5, enquanto outros podem ser de 5. No caso do forex, este valor é bem diferente destes mostrados aqui. Esta diversidade de valores, faz com que alguns programadores simplesmente decidam criar um Expert Advisor, voltado para ser usado em um ativo especifico. Isto é fato, e o motivo é que o servidor de negociação, não aceita qualquer valor, é preciso seguir as regras impostas pelo servidor. Assim também será para o sistema de replay / simulador. Não podemos nos dar ao luxo de permitir que o Expert Advisor, lance ordens em qualquer valor.
Fazer isto, não é simplesmente necessário, é uma obrigação. Já que nada adianta você ter um replay / simulador funcionando, treinar e se acostumar com ele, e quando for de fato operar em uma conta real, o sistema funcionar de forma completamente diferente. Por isto precisamos que o sistema tenha de fato uma certa padronização. Mas ele também precisa se adequar o mais perfeitamente ao que acontece em uma conta real. Ou seja, teremos que desenvolver um Expert Advisor que consiga agir como se fosse o servidor de negociação, estivesse do outro lado, independente do que esteja do outro lado.
Começando pela primeira das classes: A classe C_Terminal
Apesar de no geral, muitas das vezes podermos criar todo o código, sem nenhum tipo de direcionamento. Isto não se torna adequado quando o projeto no qual estamos trabalhando, pode vim a ser tornar algo extremamente grande e complexo. Não sabemos bem como o projeto irá ser desenvolvido. Temos que começar sempre visando procurar usar as melhores praticas de programação. Isto para que não fiquemos com uma imensa quantidade de código, em mãos e ele totalmente desorganizado e sem nenhum tipo de modelagem pratica. Por isto sempre devemos começar pensando em um projeto grande, mesmo que lá no final ele não seja assim tão grandioso e complicado. Mas fazer uso de boas praticas, nos torna mais organizados em projetos menores e acostumados a sempre seguir uma metodologia. Então vamos começar desenvolvendo a primeira das classe. Criaremos um novo arquivo de cabeçalho com o nome C_Terminal.mqh. Acostumem sempre a dar o mesmo nome do arquivo, ao nome que será usado na classe, isto ajuda bastante na hora de encontrar o arquivo da classe na qual você deverá trabalhar. Então o código começa da seguinte forma:
class C_Terminal { protected: private : public : };
No artigo Aprendendo a construindo um EA que opera de forma automática ( Parte 05 ): Gatilhos manuais ( II ) expliquei algumas questões sobre conceitos de classes e estas palavras reservadas mostradas aqui. É bom dar uma olhada, caso você não tenha visto a sequencia sobre a criação de um EA automático, já que lá contém muitas das coisas que usaremos aqui. Se bem que aquele código, já está ultrapassado e completamente obsoleto. Aqui iremos ver um código que conterá novas formas de interação. Isto por conta da necessidade em cobrir certas coisas, e manter o sistema ainda mais robusto, confiável e eficaz. Feito este primeiro inicio de codificação. A primeira coisa que irá de fato aparecer no código da classe é uma estrutura que pode ser vista logo abaixo:
class C_Terminal { protected: //+------------------------------------------------------------------+ struct st_Terminal { long ID; string szSymbol; int Width, Height, nDigits; double PointPerTick, ValuePerPoint, VolumeMinimal, AdjustToTrade; }; //+------------------------------------------------------------------+
Observe o fato de que esta estrutura esta sendo declarada dentro da clausula protegida. Isto é importante para o que iremos de fato fazer. Um detalhe é que você pode notar que não foi de maneira alguma declarada nenhuma variável aqui. De fato, toda e qualquer variável global dentro de uma classe, deverá sempre ser declarada dentro da clausula privativa. Assim teremos o mais alto nível de segurança e encapsulamento das informações. Mas iremos voltar a este assunto, no decorrer da implementação, para melhor entendimento. Mas como uma boa pratica de programação, nunca permita que variáveis internas a classe, sejam acessadas por outros pontos do código que não fazem parte da classe.
Então vamos ver como está sendo declaradas as variáveis da classe, isto pode ser visto no fragmento logo abaixo:
private :
st_Terminal m_Infos;
No momento temos apenas uma variável global e privativa da classe C_Terminal. Aqui iremos armazenar os dados pertinentes a qualquer informação. Depois veremos como fazer para ler estas mesmas informações, quando precisarmos delas fora da classe. Mas por hora, você deve ter em mente que nenhum dado, ou informação irá vazar ou entrar na classe, sem que a mesma fique ciente. É muito importante você seguir este conceito. Muitos programadores iniciante, permitem que código fora da classe modifiquem os valores, presentes nas variáveis internas da classe. Mas isto é um erro, mesmo que o compilador não veja desta forma. Este tipo de pratica, quebra o encapsulamento, tornando assim seu código consideravelmente menos seguro e administrável, pelo simples fato de que um valor estará sendo modificado sem o conhecimento da classe, podendo gerar erros e falhas difíceis de serem solucionadas e encontrados.
Feito isto precisaremos de um novo arquivo de cabeçalho, isto para manter uma certa organização. Este arquivo que irá se chamar Macros.mqh, irá neste primeiro momento conter apenas e somente uma única linha.
#define macroGetDate(A) (A - (A % 86400))
Esta linha irá servir para isolar a informação referente a data. O fato de usarmos uma macro no lugar de usar uma função, pode parecer estranho. Mas em vários casos o uso de macros é consideravelmente mais adequado, isto pelo ponto de vista que a macro irá ser colocada no código como se fosse uma função inline, Assim esta será executada de maneira o mais rápida possível. Mas o uso de macros é adequado pelo ponto de vista, que reduz a possibilidade de cometermos erros grosseiros durante a codificação, ainda mais quando um determinado tipo de fatoração tem que ser feita diversas vezes no código.
Nota: Aqui neste sistema, irei em diversos momentos tentar, usar uma linguagem de nível mais alto. Isto para facilitar a leitura e compreensão por parte daqueles que estão começando a aprender a programar. O uso de uma linguagem de alto nível, não significa que o código irá ficar mais lento. Mas sim que ele será mais simples de ser lido. Em breve irei mostrar como você pode fazer isto em seus próprios códigos.
Mas sempre que possível, tente criar um código em uma linguagem de alto nível, isto facilita muito o processo de correção de erros e melhorias. Além do mais o código não é para a máquina e sim para que outro programador consiga entender.
Agora que temos o arquivo de cabeçalho Macros.mqh, e todas a macros globais estarão dentro deste arquivo. Vamos adicionar ele o nosso arquivo de cabeçalho C_Terminal.mqh. Ele aparecerá da seguinte forma:
#include "Macros.mqh"
Observem o fato de que o nome do arquivo de cabeçalho estar entre aspas duplas. Por que ele esta sendo declarado assim e não entre os sinais de maior e menor ( < > ) ?!?! Existe alguma razão especial para isto ?!?! Sim existe uma razão para isto. Ao se usar aspas duplas, estamos dizendo ao compilador que o caminho para localizar o arquivo de cabeçalho, irá começar no diretório onde o arquivo de cabeçalho, no caso C_Terminal.mqh se encontra. Como não existe nenhum tipo de caminho sendo indicado, o compilador irá procurar o arquivo Macro.mqh no mesmo diretório onde o arquivo C_Terminal.mqh se encontra. Desta forma, caso mudemos a estrutura de diretórios do projeto, mas continuemos a manter o arquivo Macros.mqh no mesmo diretório onde o arquivo C_Terminal.mqh se encontra, não iremos precisar informar o novo caminho para o compilador.
No caso de usar o nome entre o sinal de maior e menor ( < > ) isto irá dizer para o compilador fazer a pesquisa partindo de um diretório pré-definido dentro do sistema de compilação. No caso do MQL5 este diretório será o INCLUDE. Então todo o caminho até o arquivo Macros.mqh deverá ser informado partindo deste diretório INCLUDE, que se encontra na pasta MQL5. Caso venhamos a mudar a estrutura de diretórios no projeto, teremos que redefinir todos os caminhos de maneira que o compilador consiga encontrar os arquivos de cabeçalho. Parece ser algo aparentemente banal, mas o simples fato de você usar um método, ou outro faz toda a diferença.
Tendo entendido esta parte. Vamos ver o primeiro código presente na classe C_Terminal. Este é um código que será privativo da classe não podendo ser assim visto fora da classe:
void CurrentSymbol(void) { MqlDateTime mdt1; string sz0, sz1; datetime dt = macroGetDate(TimeCurrent(mdt1)); enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); switch (eTS) { case DOL: case WDO: sz1 = "FGHJKMNQUVXZ"; break; case IND: case WIN: sz1 = "GJMQVZ"; break; default : return; } for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break; }
Este lindo código que pode ser visto acima, pode parecer complicado e muito confuso. Mas ele nada mais faz do que gerar o nome do ativo, de forma a podermos fazer uso de um sistema crossorder. Para entender como isto realmente se dá, vamos analisar com calma o que está sendo feito. Já que em principio estamos usando a criação de nomes, voltados apenas para operar índice futuro e dólar futuro, com base na nomenclatura utilizada pela B3 ( Bolsa do Brasil ). Se você entender como o nome está sendo criado, irá conseguir fazer com que este código, possa ser utilizado para gerar qualquer nome. Poderá assim operar qualquer contrato futuro, usando o histórico do contrato, ou seja via sistema de crossorder que foi mostrado em um artigo passado, Desenvolvendo um EA de negociação do zero (Parte 11): Sistema CROSS ORDER. Mas aqui iremos muito além daquilo. Precisamos fazer com que o Expert Advisor, se adapte a qualquer tipo de condição, cenário, ativo ou mercado. Para fazer isto precisaremos que ele saiba de alguma forma, com que tipo de coisa que ele estará lidando. Dependendo do caso pode ser necessário adicionar mais tipos de ativos aqui. Por isto é extremamente necessário, que você consiga entender este código acima. Para melhor explicar o mesmo, vamos desmembrar ele em partes menores.
MqlDateTime mdt1; string sz0, sz1; datetime dt = macroGetDate(TimeCurrent(mdt1));
Estas três linha acima, são variáveis que iremos usar dentro do código. Acredito que a única dificuldade, é com relação a forma como elas estão sendo inicializadas. O que fazemos é usar a rotina TimeCurrent a fim de inicializar em um único passo duas variáveis distintas. A primeira é a mdt1 que na verdade é uma estrutura do tipo MqlDateTime. Neste ponto teremos o desmembramento da informação de tempo ( Data e Hora ) sendo colocados dentro da variável mdt1, mas TimeCurrent também retorna o mesmo valor colocado em mdt1. E a segunda usamos a macro para isolar o valor da data e colocar este na variável dt. Desta forma temos uma inicialização completa de duas variáveis na mesma linha de código.
enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER;
De fato devo concordar que esta linha acima, é muito estranha. Já que estamos criando uma enumeração ao mesmo tempo declarando uma variável e definindo um valor inicial para ela. Entender esta linha ajudará a compreender uma outra dentro do procedimento. Então atenção aos detalhes: Em MQL5 não podemos criar uma enumeração sem um nome. Por conta disto, informamos este nome daqui. Dentro da enumeração temos elementos, que por padrão sempre iniciam como sendo um valor zero, isto pode ser mudado e veremos como em um outro momento. Mas por hora tenha isto em mente, a enumeração por padrão começa em zero. Então o valor WIN é zero, IND é um, WDO é dois e assim por diante. Mas por um motivo que você verá depois, o último elemento deverá ser o valor OTHER, não importa quantos elementos você deseja colocar, o último deverá ser o OTHER. Depois que a enumeração foi definida, declaramos uma variável que irá usar os dados desta enumeração, e iniciamos o valor desta variável como sendo o valor do último elemento, ou seja OTHER.
Nota Importante: Observem a declaração da enumeração. Isto lhe parece de alguma forma familiar ?!?! Pois bem observem que também estão sendo declaradas em caixa alta ( caracteres em maiúsculo ) isto também é importante. O fato é: Caso você deseje adicionar mais ativos aqui a fim de poder usar o seu contrato futuro, deve fazer adicionando os 3 primeiros caracteres do novo do contrato, e isto antes do elemento OTHER, para que o procedimento consiga de fato gerar o nome do contrato atual. Por exemplo, suponhamos que você deseje adicionar o contrato do boi. Neste caso você deverá adicionar o valor BGI na enumeração, este é o primeiro passo, existe um outro que veremos depois, mas para reforçar se você deseja adicionar o milho, deverá adicionar o valor CCM e assim por diante. Sempre antes do valor OTHER, caso contrário a enumeração não irá de fato funcionar.
Muito bem, a próxima coisa que precisaremos ver é o fragmento abaixo. Juntamente com a enumeração vista acima irá completar o primeiro ciclo de trabalho.
sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); switch (eTS) { case DOL: case WDO: sz1 = "FGHJKMNQUVXZ"; break; case IND: case WIN: sz1 = "GJMQVZ"; break; default : return; }
A primeira coisa que fazemos é armazenar o nome do ativo na variável global privativa da classe. Para facilitar a nossa vida iremos usar a função StringSubstr a fim de capturar e colocar na variável sz0 as 3 primeiras letra do nome do ativo do gráfico na qual o código da classe estará sendo executado. Esta é a parte fácil. Agora vamos fazer algo bem incomum, no entanto possível. Iremos usar a enumeração para procurar qual será a regra de nomenclatura usada no contrato. Para fazer isto usaremos um laço for. Esta linha do laço pode parecer extremamente estranha. No entanto o que estamos fazendo é justamente varrendo a enumeração a fim de procurar o nome do contrato, que estará definido inicialmente na enumeração, da maneira como expliquei. Já que a enumeração sempre começa, por padrão, com o valor zero, iniciamos a nossa variável local do laço em zero. Não importa qual é o primeiro elemento, o laço sempre irá iniciar nele. Iremos varre a enumeração até o elemento OTHER ou até que a variável eTS seja diferente do valor OTHER. A cada interação incrementamos uma posição dentro da enumeração. Agora vem a parte interessante, com a ajuda do MQL5, iremos usar a função EnumToString a cada interação do laço iremos converter o valor da enumeração em um valor de string a fim de comparar ele com o valor que estará presente na variável sz0. Quando estes valores forem iguais, a posição será armazenada na variável eTS, tornando ela assim diferente do valor OTHER. Este tipo de coisa é bastante interessante. Então não pense na enumeração da mesma forma que ela normalmente é usada em outras linguagens. Aqui no MQL5, pense nela como se fosse um array de strings, onde podemos ter muito mais funcionalidades com mais praticidade do que seria encontrado em outras linguagens.
Uma vez que já temos o valor da pesquisa dentro da variável eTS, podemos analisar qual será a regra de nomenclatura. Para isto iremos inicializar a variável sz1 de uma forma adequada, e isto irá depender de cada contrato específico. Para saber qual deverá ser a letra que deverá estar na sequência de sz1, você deverá pesquisar o contrato no qual deseja adicionar a regra de nomenclatura, e seguir a coisa da mesma forma como esta sendo mostrado aqui.
Caso o ativo não faça parte da enumeração para uma criação de nomenclatura especial, e a regra não tenha sido criada, o procedimento irá se encerrar neste ponto. Desta forma caso estejamos usando o ativo do replay / simulador, a coisa toda terminará aqui. Já que este ativo é um ativo customizado e especial por natureza. Este encerramento se dá neste local.
Agora vamos ver mais um laço presente no procedimento, " ... mas agora é que a porca torce o rabo ... ", como alguns dizem. O motivo é que este laço contém uma forma de codificar que deixa muita gente bastante confusa e sem entender absolutamente nada do que esteja acontecendo. Por conta disto peço que tenham uma atenção ainda mais que redobrada na explicação do mesmo. Vamos então ver o código dele no fragmento abaixo:
for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break;
Apesar de parecer confuso e complicado, este código em si é bastante simples. No entanto ele apenas está condensado de uma maneira a torna a codificação um pouco mais eficiente, apesar de aqui por motivos de deixar o código menos complexo, estejamos usando um comando IF, mas este de fato não seria necessário. Isto por conta que todo o comando poderia de fato estar presente no comando FOR, mas dificultaria muito mais a explicação. Assim estaremos usando um IF neste laço, que na verdade faz, a verificação do nome do contrato criado, com o nome presente no servidor de negociação, a fim de procurar saber qual é o contrato futuro mais atual. Para entender como isto se dá, é preciso que você saiba qual é a regra de nomenclatura utilizada para gerar o nome do contrato. Para exemplificar, irei mostrar o exemplo do contrato de míni dólar futuro, que é negociado na B3 ( Bolsa do Brasil ). Este contrato tem em sua nomenclatura a seguinte regra:
- Os 3 primeiros caracteres do nome do contrato serão WDO. Independente do vencimento ou se ele é um contrato histórico ou não;
- Em seguida temos um carácter que informa o mês de vencimento;
- Seguindo este carácter de vencimento, temos um valor de dois dígitos que informa o ano de vencimento.
Desta forma podemos, de maneira matemática construir o nome do contrato, e é justamente isto que este laço acima faz. Usando regras matemáticas simples e um laço, iremos construir o nome do contrato e verificar se ele esta vigente ou não. Então acompanhem a explicação para entender de fato como isto é feito.
Primeiramente iniciamos 3 variáveis locais do laço, estas servem com unidades de contabilidade que precisaremos. Agora o laço irá fazer a sua primeira interação. Ela não será feita no corpo do laço, e si no comando if, mas o mesmo código presente no comando if poderia estar entre este dois ponto-virgula e ainda assim o laço iria funcionar da mesma forma. Então vamos entender o que se passa nesta interação. Primeiro iremos criar o nome do contrato seguindo exatamente as regras conhecidas na criação do nome do mesmo. Usando a função StringFormat, no final teremos o nome que precisamos. Este valor será armazenado no nome do símbolo que poderemos acessar depois. Tendo agora o nome do contrato, enviamos um pedido ao servidor de negociação a fim de saber uma das propriedades do ativo: O momento em que o contrato deixará ser ser vigente. Isto é feito usando a seguinte enumeração: SYMBOL_EXPIRATION_TIME. A função SymbolInfoInteger irá retornar um valor, mas no caso desejamos que este valor seja apenas e somente a data. Para filtrar isto, usamos a nossa macro, assim podemos comparar o valor de vencimento, com a data atual. Se o valor informado for uma data futura, o laço irá terminar e nenhuma nova interação ocorrerá, pois já teremos o contrato mais atual sendo representado na variável. Mas como muito provavelmente este não será o caso, já que o ano irá iniciar em 2000, e isto é passado, teremos uma nova interação. Mas antes que todo este processo descrito se repita, temos que incrementar a posição a fim de conseguir construir um novo nome de contrato. E é aqui que temos que tomar cuidado, pois este incremento tem que ser feito primeiro no código de vencimento. Somente se nenhum dos códigos forem satisfeitos naquele ano, iremos incrementar o ano. Isto é feito em 3 pontos, mas estaremos fazendo isto no código utilizando 2 operadores ternário.
Antes do laço fazer uma nova interação, e antes mesmo dos operadores ternários serem executados. Incrementamos o valor que indica qual o carácter do mês de vencimento. Depois disto verificamos se ele está ou não dentro dos limites. Isto usando o primeiro operador ternário, assim o index sempre estará indicando um dos valores do mês de vencimento. O próximo passo é verificar, este mês de vencimento, no segundo operador ternário. Se o mês de vencimento tem como index o valor zero, isto indica que todos os meses foram testado. Então incrementaremos o ano atual. E uma nova interação acontecerá no comando if. Isto irá se perpetuar até que um contrato válido seja encontrado. E é desta forma que o sistema consegue procurar o nome do contrato vigente. Não é mágica, é matemática aliada a programação.
Espero que você tenha conseguido entender como o código deste procedimento funciona. Apesar da grande quantidade de texto em destaque, tentei explicar da melhor forma possível, a fim de que todos consigam usar este mesmo código para implementar a coisa toda, a fim de cobrir outros contratos futuros, operando eles via histórico. Sem realmente importa se estarão ou não no contrato vigente, pois o próprio código irá sempre usar o contrato correto.
Mas vamos passar para o próximo código. Este é o constructor da nossa classe, e pode ser visto logo abaixo:
C_Terminal() { m_Infos.ID = ChartID(); CurrentSymbol(); ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true); ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); m_Infos.AdjustToTrade = m_Infos.PointPerTick / m_Infos.ValuePerPoint; }
Este código garante que os valores dentro da estrutura da variável global, sejam corretamente inicializados. Talvez as partes que merecem algum destaque, são as que mudam o comportamento da plataforma MetaTrader 5. Então vamos entender o que está acontecendo. Aqui estamos dizendo a plataforma MetaTrader 5 de que no gráfico onde este código estará presente não deve gerar a descrição dos objetos no gráfico. Nesta linha, dizemos que sempre que algum objeto for removido do gráfico, que a plataforma MetaTrader 5 gere um evento informando qual o objeto que foi removido. Já nesta linha, dizemos que a escala de tempo deve ser removida. Basicamente é isto que precisaremos neste primeiro momento, o restante das linhas estará capturando dados e informações sobre o ativo.
O próximo código que iremos ver é o destructor da classe. Este pode ser visto abaixo:
~C_Terminal() { ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, true); ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, true); ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, false); }
Neste código do destructor, restabelecemos as condições antes que o código do constructor da classe tenha sido executado. Fazendo com que o gráfico retorne ao seu estado original. É verdade que talvez não seja de todo o estado original, já que a escala de tempo irá voltar a ser visível novamente no gráfico. Mas de qualquer forma, acredito que você conseguiu compreender a ideia. Bem, vamos sanar este problema de o comportamento do gráfico pode esta sendo modificado pela classe quando o código sair do gráfico. Para fazer isto vamos criar uma pequena estrutura e modificar tanto o código do constructor, quanto o código do destructor a fim de realmente fazer o sistema devolver o gráfico da maneira como ele era antes da classe o modificar. Isto é conseguido da seguinte forma:
private : st_Terminal m_Infos; struct mem { long Show_Descr, Show_Date; }m_Mem; //+------------------------------------------------------------------+ public : //+------------------------------------------------------------------+ C_Terminal() { m_Infos.ID = ChartID(); CurrentSymbol(); m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true); ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); m_Infos.AdjustToTrade = m_Infos.PointPerTick / m_Infos.ValuePerPoint; } //+------------------------------------------------------------------+ ~C_Terminal() { ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date); ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr); ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, false); }
Esta variável global irá representar a estrutura que irá manter os dados para nos. Assim a classe irá saber como o gráfico estava antes de que o código a muda-se. Neste ponto capturamos os dados antes de muda-los, e neste ponto devolvemos o gráfico a sua condição original, antes do código da classe o mudar. Notaram como uma simples mudança no código, torna o sistema bem mais agradável e amigável. Lembrando que a variável global, irá armazenar os dados durante todo o tempo de vida da classe. Mas para entender isto, você não deve ver a classe como se ela fosse um código simples. Você deve sempre pensar na classe como se ela fosse um objeto, ou uma variável especial. No momento que ela for criada, o código do constructor será chamado, e no momento que ela for removida ou não for mais necessária o código do destructor será chamado. Isto tudo de forma automática. Se você não entendeu como de fato isto funciona, não se preocupe, mais para frente este tipo de ideia ficará mais clara que a luz do dia. Por enquanto entenda o seguinte: Uma classe não é um simples amontoado de código, ela é de fato algo especial, e merece ser tratada como tal.
Antes de terminarmos, vamos dar uma rápida passada por outros dois procedimentos. Estes serão melhor compreendidos no próximo artigo. Mas já vamos ver agora, pelo menos a parte referente ao código. Estes podem ser visto logo abaixo:
//+------------------------------------------------------------------+ inline const st_Terminal GetInfoTerminal(void) const { return m_Infos; } //+------------------------------------------------------------------+ virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) { switch (id) { case CHARTEVENT_CHART_CHANGE: m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); break; } } //+------------------------------------------------------------------+
Estes dois procedimentos são especiais em todos os sentidos. Mas aqui irei dar uma breve explicação sobre eles, já que estes serão bem melhor explicados conforme forem sendo utilizado. Esta função serve para que qualquer código fora do corpo da classe possa ler os dados da variável global presente na classe. Este será muito explorado ao logo de todo o código que faremos. É verdade que fazendo assim, da forma como esta sendo feita, garantimos que não iremos correr o risco de modificar os valores da variável, sem que a classe saiba ou perceba, já que iremos de fato usar o compilador para nos ajudar a evitar este tipo de problema. Mas existe um problema aqui, mas isto será visto no futuro. Já este procedimento serve para atualizar dos dados da classe, conforme o gráfico for sendo modificado. Estes valores serão muito usados em outros pontos do código quando formos desenhar coisas no gráfico. Mas igual ao outro procedimento, este será melhor compreendido no futuro.
Conclusão
Com base nisto que foi visto neste artigo, já temos a nossa classe C_Terminal básica. Mas ainda falta um procedimento. No entanto, este será visto no próximo artigo, onde iremos criar a classe C_Mouse. Porém o que foi visto aqui, já nos permite usar a classe a fim de poder gerar alguma coisa útil. Apesar de não haver nenhum código no anexo, isto se deve ao fato de que na verdade o trabalho esta apenas começando. Se algum código viesse a ser postado ele de fato não teria nenhuma utilidade prática. No próximo artigo iremos de fato criar algo útil para assim podermos começar a trabalhar no gráfico. Criando indicadores e outras coisas para nos dar suporte em operações, tanto em contas DEMO, conta REAL e até mesmo no Simulador / Replay. Então até o próximo artigo.
- Aplicativos de negociação gratuitos
- VPS Forex grátis por 24 horas
- 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
Caramba, que bacana esse seu projeto!
Estou aqui de curioso pois não sou programador, e gostaria de saber se você pretende vender como um produto.
Parabéns pelo trabalho.
Valeu.