English Русский 中文 Español Deutsch 日本語
preview
Aprendendo a construindo um EA que opera de forma automática (Parte 05): Gatilhos manuais (II)

Aprendendo a construindo um EA que opera de forma automática (Parte 05): Gatilhos manuais (II)

MetaTrader 5Negociação | 8 novembro 2022, 17:02
737 1
Daniel Jose
Daniel Jose

Introdução

No artigo anterior,  Aprendendo a construindo um EA que opera de forma automática (Parte 04): Gatilhos manuais (I) demonstrei como você, com o auxilio de um pouco de programação, conseguiria enviar ordens do tipo a mercado e pendurar ordens no book, utilizando para isto o conjunto teclado mouse.

No final daquele artigo, pensei que seria adequado permitir o uso do EA, de uma maneira manual, pelo menos por um tempo. Isto se mostrou bem mais interessante, do que era pretendo falar e mostrar, já que a ideia inicial, era de fazer 3 ou 4 artigos, que mostrasse como de fato fazemos para desenvolver um EA, que consiga operar de forma automática. Apesar de isto ser bastante simples para programadores, é algo muito complicado para entusiastas, que estão querendo aprender a programar. Visto que existe pouco material disponível, explicando de forma clara, como realmente se programa certas coisas. E ficar limitado a um nível de conhecimento, não é de fato, algo que a pessoa deva fazer.

E já que muitos podem estar usando, estes artigos presentes aqui na comunidade, para começar a aprender a programar. Vi uma oportunidade de passar um pouco da minha experiência, de anos programando em C / C++. E acabar mostrando um pouco, como fazer as algumas coisas em MQL5, que é bastante parecida com o C / C++. Tirando assim, esta coisa mítica, que muitos pensam sobre o que é de fato programar.

Bem, para que o nosso EA, possa trabalhar de forma mais cômoda, no modo manual, precisamos fazer algumas coisas. Para quem é programador, a coisa é super simples, e fácil de fazer, podendo então ir direto ao ponto. Que é criar as linhas, que indicam, onde estará os limites da ordem, que será lançada no servidor de negociação.

Estes limites, são mais apropriados de serem visualizados, quando estamos usando o mouse para posicionar as ordens, ou seja, quando estivermos fazendo a criação de uma ordem pendente. Uma vez que a ordem já esteja no servidor, a indicação passa a ser gerenciada pela plataforma MetaTrader 5. Mas antes que isto de fato aconteça, precisamos mostrar ao usuário, onde possivelmente, a ordem e seus limites serão colocados, e posicionados. Isto é feito por nos, programadores. Já que o único suporte, que de fato o MetaTrader 5 nos dará, é a possibilidade de usar linhas horizontais no gráfico. Fora isto, todo o trabalho deverá ser feito via programação do EA.

Para fazer isto, precisamos simplesmente programar algum código, que lance tais linhas no gráfico, nas posições corretas. Mas não queremos fazer isto de qualquer maneira. Queremos fazer isto de uma forma controlada. Já que não desejamos comprometer o código, que já foi criado. E não queremos ter trabalho, caso precisamos e iremos no futuro, retirar a classe C_Mouse, e o tratador de eventos OnChartEvent do nosso EA. Isto por que um EA automático, não precisa de tais coisas, mas um EA manual sim. Ele precisa de tais coisas, para ficar minimamente usável. 


Criando uma classe C_Terminal

Para fazer isto, gerar algum conforto para operações manuais. Precisaremos adicionar as linha que indicam, os prováveis limites de uma ordem, ou posição que será lançada, e aproveitando, iremos fazer a retirada de códigos, que estão sendo repetidos tanto na classe C_Orders, quanto na classe C_Mouse. E assim nascerá uma nova classe: a classe C_Terminal, que irá nos ajudar a construir, e a isolar algumas coisas, nos dando todo o suporte, que precisamos para poder trabalhar, de forma o mais confortável possível. Ao fazer uso desta classe, poderemos no futuro, criar tanto um EA automático, quanto um EA manual. Sem corrermos o risco, de gerar algum tipo de falha catastrófica, no nosso novo EA.

O maior dos problemas, é que muitos, quando vão criar um novo EA automático, fazem isto do zero. Este tipo de atitude, costuma gerar muitas falhas novas, já que o código, muitas das vezes, não terá sido de fato testado.

É bem verdade, que seria até interessante, fazer com que estas classes, fossem transformadas em uma biblioteca particular. Mas como o intuito no momento não é este, irei pensar no assunto. Talvez venha a fazer isto no futuro. Mas agora, vamos ver o que iremos de fato fazer. Começando com o seguinte: Criamos como de costume um arquivo de cabeçalho, chamado C_Terminal.mqh. Este irá começar com o código mais básico de todos, e sempre presente em toda classe a ser criada, este código pode ser visto logo abaixo:

class C_Terminal
{
        private :
        public  :
};

Sempre inicialize seus código, de uma classe desta forma, assim você nunca, irá se esqueçer, de que existem pontos, que devem ser colocados na clausula privativa, e outros que podem ser colocados na clausula publica. Mesmo que você, não venha a ter coisas privativas dentro da classe, é sempre bom deixar as coisas claras. Principalmente pelo fato de que você, pode vim a mostrar o seu código para outras pessoas.

Um código bem delimitado, e bem escrito, ou seja fácil de ler, vai com toda a certeza trazer o interesse de outras pessoas, a fim de analisar ele. Caso você precise de ajuda na correção de algum problema. Um códigos todo bagunçado, sem nenhum tipo de organização, sem usar tabulações, e muitas vezes sem uma explicação por meio de comentários. Torna o código desinteressante, mesmo que a ideia seja boa, ninguém de fato irá querer perder tempo organizando o código, a fim de conseguir entender o que ele esta fazendo.

Então fica a dica. Não que os meus código seja a maravilha da perfeição, de serem lido, mas : Organize seus códigos sempre, use tabulações sempre que precisar colocar diversas linhas, e todas aninhadas dentro de um procedimento único, isto ajuda muito. Não só a outras pessoas, mas principalmente a você, pois as vezes um código esta tão desorganizado, que nem o criador consegue entende-lo, o que dirá outro programador ...

Muito bem, mas vamos começar a codificação, adicionando uma estrutura em nossa classe. Estas primeiras linhas codificadas, pode ser vista logo abaixo:

class C_Terminal
{
        protected:
//+------------------------------------------------------------------+
                struct stTerminal
                {
                        ENUM_SYMBOL_CHART_MODE ChartMode;
                        int     nDigits;
                        double  VolMinimal,
                                VolStep,
                                PointPerTick,
                                ValuePerPoint,
                                AdjustToTrade;
                };
//+------------------------------------------------------------------+
        private :
        public  :
};

Aqui temos uma novidade, uma palavra reservada chamada protected, e o que ele nos diz ?!?! Normalmente usamos apenas, e somente, e em muitos casos, declarações publicas e privadas, mas e esta ?!?! Bem, ela ficaria em um meio termo, entre o que é publico, e o que é privado. Para de fato compreender isto, você precisa entender alguns conceitos básicos, sobre programação orientada a objetos.

Um destes conceitos, é a Herança. Mas antes de entrar na questão da herança, você precisa entender a questão da classe, no nível individual dela. Para que de fato você consiga entender, pense que cada classe seria um individuo, um ser vivo, único e exclusivo. Agora podemos partir para a explicação.

Algumas informações são publicas, isto permite qualquer um, além do próprio individuo, que as mantem, se beneficiar de seu uso e conhecimento, este tipo de coisa, será sempre colocado em uma clausula publica. Outras informações, são privativas do individuo, ou seja, somente ele as contém, quando ele deixar de existir, estas informações irão morrer com ele, e ele é o único que consegue se beneficiar delas. Pense nelas como se fosse um tipo de habilidade pessoal, o individuo não consegue ensinar, ou passar isto para ninguém mais, e ninguém consegue tirar isto dele, este tipo de informação fica na clausula privativa.

Mas existem informações, que não se encaixam em nenhum deste conceitos, e estas ficam na clausula protegida, ou seja, o individuo pode, ou não fazer uso delas. Mas a questão principal é que elas podem ser passadas, para os membros de sua linhagem, e apenas para eles. Mas para entender como isto se dá, é preciso entrar no assunto herança.

Quando entramos no assunto de herança, a maneira mais simples de entender, é pensando em linhagens de sangue. Existem 3 tipos de herança: A herança publica, a herança privada e a herança protegida, aqui estou falando de herança. Não mais das questões individuais, de cada membro da linhagem.

Em uma herança publica, todas as informações, dados e conteúdo dos pais, são passados para os filhos, e para toda a sua descendência, inclusive netos e além, e todos fora da linhagem, conseguem acessar, em tese, estas coisas passadas. Prestem atenção a isto, é em tese, pois existem algumas nuances nesta passagem. Mas depois iremos ver isto com mais calma, vamos primeiro focar na herança. Agora em um herança privativa, somente, e apenas a primeira geração, irá ter acesso as informações passadas, a próxima geração, não conseguira ter acesso as tais informações, mesmo fazendo parte da linhagem sanguínea.

E no último caso, temos a herança protegida. Esta irá gerar algo muito similar a herança privativa. Mas temos um agravante, que faz muita gente não entender estes conceitos: A clausula dos pais. Isto por que, existe um tipo de regra de passagem, mesmo em casos de herança publica. Algumas coisas não podem ser acessadas fora da linhagem. Para entender isto, veja a tabela abaixo, onde mostro resumidamente esta questão:

Definição na classe Pai Tipo de herança Acesso da classe Filho Acesso via chamada a classe Filho 
private public Acesso negado Não é possível acessar dados ou procedimentos na classe base
public public Acesso permitido Acesso permitido aos dados ou procedimentos na classe base
protected public Acesso permitido Não é possível acessar dados ou procedimentos na classe base
private private Acesso negado Não é possível acessar dados ou procedimentos na classe base
public private Acesso permitido Não é possível acessar dados ou procedimentos na classe base
protected private Acesso permitido Não é possível acessar dados ou procedimentos na classe base
private protected Acesso negado Não é possível acessar dados ou procedimentos na classe base
public protected Acesso permitido  Não é possível acessar dados ou procedimentos na classe base
protected protected Acesso permitido Não é possível acessar dados ou procedimentos na classe base

Tabela 01 - Sistema de herança baseado na definição de informações

Notem que dependendo da clausula utilizada, na definição de um tipo de informação, no momento da herança. O filho pode, ou não vim a ter acesso a tais dados, mas qualquer chamada fora da linhagem, não terá acesso. Salvo em um único caso, que é quando os dados do pai são declarados como sendo publico, e o filho herda de forma igualmente publica. Fora isto, não é possível acessar nenhuma informação fora da linhagem.

Por não entender este esquema, mostrado na tabela 01, muitos programadores com menos experiência, simplesmente torcem o nariz, quando o assunto é programação orientada a objetos. Mas isto é por pura falta de conhecimento de como as coisas de fato funcionam. Quem vem acompanhando meus artigos, e observado meus código, deve ter notado que faço uso massivo da programação orientada a objetos.

Isto por que ela nos dá, um nível de segurança na implementação de coisas muito complicadas, que não seria possível fazer de outra forma, fora o fato de que aqui, estou falando apenas, o que rege a herança. Além disto, temos também o polimorfismo, e o encapsulamento, mas estes são assuntos para outro momento. Apesar do encapsulamento, ser parte da tabela 01, ela merece uma explicação mais detalhada, mas isto iria sair do foco deste artigo.

Bem vamos então continuar. Se você reparar com calma, irá notar que a estrutura que pode ser vista no código acima, é a mesma presente na classe C_Orders. Atenção a isto, pois a classe C_Order, irá perder a definição destes dados de dentro dela, e passará a herdar estes dados da classe C_Terminal. Mas por enquanto, vamos continuar dentro da classe C_Terminal.

A próxima coisa a ser adicionada a classe C_Terminal, são as funções que existem em comum acordo, tanto na classe C_Mouse, quanto na classe C_Orders. Estas funções irão ser adicionadas na classe C_Terminal, dentro da clausula protegida, desta forma, quando a classe C_Mouse e C_Order, herdar a classe C_Terminal, estas funções e procedimentos, irão seguir a Tabela 01. Os códigos que iremos adicionar, podem ser visto logo abaixo:

//+------------------------------------------------------------------+
inline double AdjustPrice(const double value)
                        {
                                return MathRound(value / m_TerminalInfo.PointPerTick) * m_TerminalInfo.PointPerTick;
                        }
//+------------------------------------------------------------------+
inline double FinanceToPoints(const double Finance, const uint Leverage)
                        {
                                double volume = m_TerminalInfo.VolMinimal + (m_TerminalInfo.VolStep * (Leverage - 1));
                                
                                return AdjustPrice(MathAbs(((Finance / volume) / m_TerminalInfo.AdjustToTrade)));
                        };
//+------------------------------------------------------------------+

Ou seja, agora estes mesmos códigos, deixarão de estar duplicados em ambas as classe, ficando apenas, e somente dentro da classe C_Terminal, facilitando assim a sua manutenção, testes e possíveis correções. Assim o nosso código, vai ficando cada vez mais robusto, e atraente de ser utilizado, e expandido.

Existem algumas outras coisas, a serem vistas dentro da classe C_Terminal. Mas vamos primeiramente ver o constructor da classe. Este pode ser visto no código abaixo:

        public  :
//+------------------------------------------------------------------+
                C_Terminal()
                        {
                                m_TerminalInfo.nDigits          = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
                                m_TerminalInfo.VolMinimal       = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
                                m_TerminalInfo.VolStep          = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                                m_TerminalInfo.PointPerTick     = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                                m_TerminalInfo.ValuePerPoint    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
                                m_TerminalInfo.AdjustToTrade    = m_TerminalInfo.ValuePerPoint / m_TerminalInfo.PointPerTick;
                                m_TerminalInfo.ChartMode        = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE);
                        }
//+------------------------------------------------------------------+

Reparem que ele é praticamente idêntico, ao que existia na classe C_Orders. Então agora a classe C_Orders, poderá ter seu código modificado a fim de herdar, o que estamos fazendo na classe C_Terminal. Mas tem um detalhe nesta história. Olhe o código onde existe a declaração da estrutura, que esta sendo inicializada no constructor acima. Vejam que não existe, nenhuma variável ali. Por que ?!?!

O motivo é o tal do encapsulamento, você não deve permitir que códigos fora da classe, acessem, e assim possam modificar o conteúdo das variáveis internas da classe. Isto é um grave erro de programação, apesar do que o compilador não irá reclamar, você NUNCA deve permitir isto. Toda, e qualquer variável, global da classe, deve sempre ser declarada, dentro da clausula privativa. Desta forma a declaração da variável, fica como mostrado abaixo:

//+------------------------------------------------------------------+
        private :
                stTerminal m_TerminalInfo;
        public  :
//+------------------------------------------------------------------+

Veja que a variável global da classe, está definida, entre a clausula private, e a clausula prublic. Desta forma ela ficará inacessível para qualquer classe, que herde a classe C_Terminal, ou seja estamos garantindo, o encapsulamento das informações e ao mesmo tempo, estamos adicionando herança no nosso código. Fazendo com que o nível de robustez do mesmo, cresça de forma exponencial, enquanto a sua utilidade, vai sendo expandida.

Mas ai você pode pensar: Como iremos acessar os dados que precisamos nas classes acima ?!?! Precisamos dar algum nível de acesso as variáveis da classe pai, que no caso é a classe C_Terminal. Sim, precisamos, mas não devemos fazer isto, colocando as variáveis, como sendo publicas, ou mesmo protegidas. Isto é uma erro de programação, você deve adicionar algum meio, para que as classes derivadas, possam acessar os valores da classe pai. Mas, é aqui mora um perigo, e isto é importante, VOCÊ NÃO DEVE PERMITIR DE MANEIRA, E FORMA ALGUMA, QUE AS CLASSES DERIVIDADAS, POSSAM MODIFICAR AS VARIÁVEIS DA CLASSE PAI. 

Para fazer isto, você precisa de alguma forma, tornar uma variável em uma constante. Ou seja, a classe pai pode modificar o valor das variáveis, da forma que for preciso, e quando for preciso, e caso alguma classe filho, deseje fazer uma mudança em alguma variável da classe pai. Ele filho, deverá chamar algum procedimento, que a classe pai irá fornecer, de forma a dizer, qual deve ser o valor desejado, para algum tipo de variável presente na classe pai. Tal procedimento, que deverá se implementado na classe pai, irá verificar se os dados passados, pelo filho são de alguma forma validos, e se este for o caso, o procedimento dentro da classe pai, irá promover as mudanças pedidas pelo filho.

Mas nunca, e nunca mesmo, um filho pode mudar os dados do pai, sem que a classe pai fique sabendo da mudança. Já vi muitos códigos, potencialmente perigosos, que fazem isto. Já que muitas das vezes, alguns dizem que ao chamar um procedimento, dentro da classe pai, a fim de validar os dados fornecidos pelo filho, fazem o código ficar mais lento, tornando o programa pouco eficiente na sua execução. Mas isto é bobagem, o custo e risco de lançar valores incorretos, dentro da classe pai, não vale, o ligeiro ganho de velocidade que será promovido, ao não fazer a chamada de procedimento, a fim de validar os dados. Não caia nesta, de que isto irá deixar o código lento.

Desta forma, entramos em um outro ponto. Você como programador, e desejando se tornar um profissional. Deve sempre dar preferencia, a colocar qualquer procedimento, que será herdado por outras classe, primeiramente dentro de uma clausula protegida, e somente em último caso, passar o procedimento para a clausula publica. Isto por conta do motivo, de sempre priorizamos o encapsulamento. Somente se realmente for preciso, deixamos o encapsulamento, e permitimos o uso publico de funções, e procedimentos. Mas nunca, iremos fazer isto com variáveis, estas deverão sempre ser privativas.

Visando isto, criar um procedimento, ou função que permita a classe filho, acessarem os dados da classe pai, surge a função abaixo:

inline const stTerminal GetTerminalInfos(void) const
                        {
                                return m_TerminalInfo;
                        }

Agora quero que prestem muita, mas muita atenção, ao que irei explicar, pois isto é extremamente importante, e faz toda a diferença, entre um código bem escrito, e um meramente bem feito.

Durante este artigo, falei que precisamos de alguma forma, permitir que o código fora da classe, no qual as variáveis estão sendo declaradas, e utilizadas, possa acessa-las. Disse que o ideal, seria que dentro da classe onde a variável é declarada, ela possa ser modificada, sempre que for necessário. Mas fora da classe, a variável deveria ser tratada, como sendo uma constante, ou seja, não poderia ter o seu valor modificado.

Este singelo código acima, que apesar de ser extremamente simples, consegue fazer justamente isto. Assegurar que dentro da classe C_Terminal, teremos uma variável acessível, e que pode ter o seu valor modificado, mas fora da classe, esta mesma variável, será vista como sendo uma constante. E como consegui fazer isto, e por que temos duas palavras reservadas const, aqui ?!?!

Vamos por partes: A primeira palavra const, diz para o compilador que a variável m_TerminalInfo, que estará sendo retornada, deverá ser tratada como uma constante, no chamador da função. Ou seja, caso o chamador, tente modificar o valor, de algum dos membros da estrutura presente, na variável retornada, o compilador, deverá gerar um erro, e impedir que o código seja compilado. Já a segunda palavra const, diz ao compilador, que caso aqui, neste código, por um motivo ou outro, algum valor seja modificado, ele compilador, deve gerar um erro. Assim, você não poderá modificar, mesmo querendo fazer isto, nenhum dados, dentro desta função, ela existe apenas para retornar um valor.

Alguns programadores, as vezes cometem este tipo de erro. Modificando valores de variáveis, dentro de funções ou procedimentos, nos quais estas variáveis, deveriam ser usadas apenas para acesso externo, não para algum tipo de fatoração. Fazendo uso deste tipo de programação, mostrada acima, você evita este tipo de erro.

Muito bem, agora que a nossa classe C_Terminal, básica, já que ainda não terminamos ela, está criada. Podemos remover as partes duplicadas no código, fazendo assim, com que a classe C_Mouse, fique com o mesmo tipo de código, que a classe C_Orders. Mas já que a mudança na classe C_Mouse, é bem mais simples. Vamos ver como ela irá ficar agora, que estará herdando a classe C_Terminal, isto pode ser visto no código abaixo:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Terminal.mqh"
//+------------------------------------------------------------------+
#define def_MouseName "MOUSE_H"
//+------------------------------------------------------------------+
#define def_BtnLeftClick(A)     ((A & 0x01) == 0x01)
#define def_SHIFT_Press(A)      ((A & 0x04) == 0x04)
#define def_CTRL_Press(A)       ((A & 0x08) == 0x08)
//+------------------------------------------------------------------+
class C_Mouse : private C_Terminal
{
// Código interno da classe ....
};

Aqui, estamos incluindo o arquivo de cabeçalho da classe C_Teminal, observem que aqui, o nome do arquivo esta entre aspas duplas. Isto é para dizer ao compilador, que o arquivo C_Terminal.mqh, será encontrado no mesmo diretório, que o arquivo C_Mouse.mqh. Desta forma, se você precisar mudar ambos arquivos para outro local, o compilador, irá sempre conseguir achar o arquivo correto, já que para o compilador, ambos estarão no mesmo diretório.

Agora seguindo a ideia, de sempre iniciar as coisas dando o mínimo de acesso possível, fazemos a classe C_Mouse, herdar de forma privativa, a classe C_Terminal. Feito isto, você já poderá remover a função AdjustPrice, da classe C_Mouse, assim como a variável PointPerTick, presente na classe C_Mouse. Pois agora ela estará utilizando o procedimento presente na classe C_Terminal, e como a classe foi herdada de forma privativa, e a função AdjustPrice esta dentro da clausula protegida, na classe C_Terminal, você terá o resultado da tabela 01. Assim não será possível chamar o procedimento AdjustPrice, fora da classe C_Mouse, da mesma forma como era feito antes.

Mas estas mudanças, na classe C_Mouse, são por enquanto. Pois iremos fazer algumas outras, para adicionar as linhas de limite que precisamos, quando formos usar o EA, de uma forma manual. Mas não se preocupe por enquanto com isto. Vamos ver agora, como fazer as mudanças na classe C_Orders, que serão bem mais profundas, e merece por isto um tópico apenas e somente para ela. Então vamos ao próximo tópico.


Mudando a classe C_Orders depois de que ele passou a herdar a classe C_Terminal

Começamos as mudanças, quase da mesma forma como foi feito na classe C_Mouse. Mas aqui, já começa as diferenças, conforme você pode ver no código abaixo:

#include "C_Terminal.mqh"
//+------------------------------------------------------------------+
class C_Orders : private C_Terminal
{
        private :
//+------------------------------------------------------------------+
                MqlTradeRequest m_TradeRequest;
                ulong           m_MagicNumber;
                struct st00
                {
                        int     nDigits;
                        double  VolMinimal,
                                VolStep,
                                PointPerTick,
                                ValuePerPoint,
                                AdjustToTrade;
                        bool    PlotLast;
                        ulong   MagicNumber;
                }m_Infos;
//+------------------------------------------------------------------+

Todo o inicio, é praticamente igual a classe C_Mouse, mas aqui, já começa a surgir as diferenças, primeiro, estaremos removendo a estrutura da classe C_Orders, conforme é mostrado, com as linhas riscadas. Mas precisamos de um dos dados, de dentro desta estrutura, então tornaremos ele privativo, mas como sendo apenas uma variável normal.

Ao remover as partes riscadas, você deve estar pensando, que isto irá dar muito trabalho recodificar. Na verdade, o trabalho será bem pequeno, mas logo de cara, já iremos direto ao código do constructor, desta classe C_Orders. E o motivo é que a mudança irá se iniciar de fato nele. Você pode ver como ficou o novo código do constructor, logo abaixo:

                C_Orders(const ulong magic)
                        :C_Terminal(), m_MagicNumber(magic)
                        {
                                m_Infos.MagicNumber     = magic;
                                m_Infos.nDigits         = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
                                m_Infos.VolMinimal      = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
                                m_Infos.VolStep         = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                                m_Infos.PointPerTick    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                                m_Infos.ValuePerPoint   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
                                m_Infos.AdjustToTrade   = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
                                m_Infos.PlotLast        = (SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST);
                        };

Muito bem, conforme você pode ver, todo o conteúdo interno do constructor, foi removido, mas aqui estamos forçando a chamada do constructor da classe C_Terminal. Isto para garantir, que ele seja chamado, antes de qualquer coisa. Normalmente o compilador faz isto para nos, mas aqui estamos fazendo isto de forma explicita, e ao mesmo tempo estamos inicializando a variável, que indica o número mágico, em um outro ponto do código. 

Normalmente isto é feito em constructores, por conta que queremos que uma variável, tenha o seu valor definido, antes mesmo do qualquer código seja executado, desta forma o compilador, irá gerar um código adequado. Mas se o valor for constante, como normalmente o será, ganhamos algum tempo, na inicialização da classe C_Orders, ao fazer isto. Mas lembre-se do seguinte detalhe: Você só terá algum beneficio, se o valor for uma constante, caso contrário o compilador, irá gerar um código que não nos dará, nenhum beneficio pratico.

A próxima coisa a se fazer, é remover as funções AdjustPrice, e FinanceToPoints, da classe C_Orders, mas já que isto pode ser feito diretamente, não irei mostrar isto aqui. De agora em diante, estas chamadas irão usar o código dentro da classe C_Terminal.

Agora vamos ver um código, que irá usar a variável que esta sendo declarada na classe C_Terminal. E desta forma vamos a partir de agora, entender como acessar variáveis, em uma classe pai. Para saber como isto será feito, veja o código abaixo:

inline void CommonData(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                        {
                                double Desloc;
                                
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.magic            = m_Infos.MagicNumber;
                                m_TradeRequest.magic            = m_MagicNumber;
                                m_TradeRequest.symbol           = _Symbol;
                                m_TradeRequest.volume           = NormalizeDouble(m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - 1)), m_Infos.nDigits);
                                m_TradeRequest.volume           = NormalizeDouble(GetTerminalInfos().VolMinimal + (GetTerminalInfos().VolStep * (Leverage - 1)), GetTerminalInfos().nDigits);
                                m_TradeRequest.price            = NormalizeDouble(Price, m_Infos.nDigits);
                                m_TradeRequest.price            = NormalizeDouble(Price, GetTerminalInfos().nDigits);
                                Desloc = FinanceToPoints(FinanceStop, Leverage);
                                m_TradeRequest.sl               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), m_Infos.nDigits);
                                m_TradeRequest.sl               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), GetTerminalInfos().nDigits);
                                Desloc = FinanceToPoints(FinanceTake, Leverage);
                                m_TradeRequest.tp               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), m_Infos.nDigits);
                                m_TradeRequest.tp               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), GetTerminalInfos().nDigits);
                                m_TradeRequest.type_time        = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
                                m_TradeRequest.stoplimit        = 0;
                                m_TradeRequest.expiration       = 0;
                                m_TradeRequest.type_filling     = ORDER_FILLING_RETURN;
                                m_TradeRequest.deviation        = 1000;
                                m_TradeRequest.comment          = "Order Generated by Experts Advisor.";
                        }

As partes riscadas, foram removidas, e no lugar delas, vieram outros códigos que estão em destaque, mas quero mesmo, é que prestem atenção aos códigos que estão, destacados em amarelo. Estes tem uma coisa, que muitos talvez, nunca tenham de fato visto. Vejam que nestes códigos, em amarelo, temos a presença se uma função, que. Está sendo tratada, como se fosse uma estrutura ?!?! mas que doideira, é esta 😵😱 ?!?!

Calma, meu caro amigo leitor. Calma. Não é nenhuma doideira. É apenas, e somente, o uso da programação, de uma forma um pouco mais exótica, do que pode, e é vista normalmente. Para entender, por que isto é permitido, e por que funciona, vamos separar a função, em um fragmento mostrado abaixo:

GetTerminalInfos().nDigits

Agora quero que voltem, ao código da classe C_Terminal, e vamos ver, como esta função esta declarada. Isto pode ser visto logo abaixo:

inline const stTerminal GetTerminalInfos(void) const
                        {
                                return m_TerminalInfo;
                        }

Reparem que a função, GetTerminalInfos, está retornando uma estrutura, esta estrutura é vista no fragmento abaixo:

                struct stTerminal
                {
                        ENUM_SYMBOL_CHART_MODE ChartMode;
                        int     nDigits;
                        double  VolMinimal,
                                VolStep,
                                PointPerTick,
                                ValuePerPoint,
                                AdjustToTrade;
                };

Então, para o compilador, o que estamos fazendo, ao usar o código GetTerminalInfos().nDigits, seria equivalente a dizer que, GetTerminalInfos() não é uma função, e sim uma variável 😲. Ficou confuso ?!?! Pois a coisa ainda fica mais interessante, pois para o compilador, o código GetTerminalInfos().nDigits, seria equivalente ao seguinte código:

stTerminal info;
int value = info.nDigits;

value = 10;
info.nDigits = value;

Ou seja, você pode tanto ler um valor, quanto também escrever um valor. Então se por um acaso, você digitasse o seguinte fragmento:

GetTerminalInfos().nDigits = 10;

O compilador, iria entender, que é para colocar o valor 10, na variável que estive sendo referenciada, pela função GetTerminalInfos(). E isto seria um problema, já que a variável, que esta sendo referenciada, esta na classe C_Terminal, e esta variável, é declarada em uma clausula privativa. Ou seja, ela não poderia, ser modificada, pela chamada feita acima. Mas por conta que a função GetTerminalInfos(), está como protegida ( mas também poderia estar como publica, e daria no mesmo ), a variável declarada como privativa, passa a ter o mesmo nível de acesso da função, que esta referenciando ela.

Perceberam com a coisa pode ser perigosa ?!?! Ou seja, mesmo que você, declare um variável como sendo privativa. Mas não codifica, de forma adequada, as funções ou procedimentos, que estão referenciando ela. Você, ou qualquer outro, pode inadvertidamente, modificar o valor dela. E isto quebra toda a questão do encapsulamento.

Mas por conta que, durante a declaração da função, ela foi iniciada com a palavra const. Isto muda as coisas de figura, pois agora, o compilador, irá ver a função GetTerminalInfos(), de uma outra maneira. Para entender, bastará você tentar usar o código abaixo, em qualquer ponto da classe C_Orders:

GetTerminalInfos().nDigits = 10;

Se você tentar fazer isto, o compilador, irá gerar um erro. Já que para o compilador, GetTerminalInfos().nDigits, ou qualquer outra coisa dentro da estrutura, que GetTerminalInfos(), estará referenciando. É tido como uma constante, e não é possível, você modificar o valor de uma constante. Isto é tido como erro.

Entenderam agora como referenciar dados constantes, usando para isto uma variável ? Ou seja, para a classe C_Terminal, a estrutura referenciada pela função GetTerminalInfos(), é uma variável, mas para qualquer outra parte do código, a estrutura será uma constante. 😁

Muito bom, agora que já expliquei esta parte. Vamos continuar a conversão, pois agora acredito que você irá conseguir, entender o que estará acontecendo, e de onde estarão vindo os dados referenciados, na classe C_Orders. A próxima função a ser modificada, é vista logo abaixo:

                ulong CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                        {
                                double  bid, ask;
                                
                                bid = SymbolInfoDouble(_Symbol, (m_Infos.PlotLast ? SYMBOL_LAST : SYMBOL_BID));
                                bid = SymbolInfoDouble(_Symbol, (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : SYMBOL_BID));
                                ask = (m_Infos.PlotLast ? bid : SymbolInfoDouble(_Symbol, SYMBOL_ASK));
                                ask = (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? bid : SymbolInfoDouble(_Symbol, SYMBOL_ASK));
                                CommonData(type, AdjustPrice(Price), FinanceStop, FinanceTake, Leverage, IsDayTrade);
                                m_TradeRequest.action   = TRADE_ACTION_PENDING;
                                m_TradeRequest.type     = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : 
                                                                                    (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));                              
                                
                                return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
                        };

E a última função, a ser modificada, é vista a seguir:

                bool ModifyPricePoints(const ulong ticket, const double Price, const double PriceStop, const double PriceTake)
                        {
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.symbol   = _Symbol;
                                if (OrderSelect(ticket))
                                {
                                        m_TradeRequest.action   = (Price > 0 ? TRADE_ACTION_MODIFY : TRADE_ACTION_REMOVE);
                                        m_TradeRequest.order    = ticket;
                                        if (Price > 0)
                                        {
                                                m_TradeRequest.price      = NormalizeDouble(AdjustPrice(Price), m_Infos.nDigits);
                                                m_TradeRequest.sl         = NormalizeDouble(AdjustPrice(PriceStop), m_Infos.nDigits);
                                                m_TradeRequest.tp         = NormalizeDouble(AdjustPrice(PriceTake), m_Infos.nDigits);
                                                m_TradeRequest.price      = NormalizeDouble(AdjustPrice(Price), GetTerminalInfos().nDigits);
                                                m_TradeRequest.sl         = NormalizeDouble(AdjustPrice(PriceStop), GetTerminalInfos().nDigits);
                                                m_TradeRequest.tp         = NormalizeDouble(AdjustPrice(PriceTake), GetTerminalInfos().nDigits);
                                                m_TradeRequest.type_time  = (ENUM_ORDER_TYPE_TIME)OrderGetInteger(ORDER_TYPE_TIME) ;
                                                m_TradeRequest.expiration = 0;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        m_TradeRequest.action   = TRADE_ACTION_SLTP;
                                        m_TradeRequest.position = ticket;
                                        m_TradeRequest.tp       = NormalizeDouble(AdjustPrice(PriceTake), m_Infos.nDigits);
                                        m_TradeRequest.sl       = NormalizeDouble(AdjustPrice(PriceStop), m_Infos.nDigits);
                                        m_TradeRequest.tp       = NormalizeDouble(AdjustPrice(PriceTake), GetTerminalInfos().nDigits);
                                        m_TradeRequest.sl       = NormalizeDouble(AdjustPrice(PriceStop), GetTerminalInfos().nDigits);
                                }else return false;
                                ToServer();
                                
                                return (_LastError == ERR_SUCCESS);
                        };

Agora finalizamos esta parte. Com isto, nosso código continua com o mesmo nível de estabilidade, visto anteriormente. Mas em termos de robustez, acabamos melhorando ele. Já que não temos mais funções sendo duplicadas, podendo correr o risco de que em uma classe, ela fosse modificada, enquanto na outra classe, ela permaneceria igual. E caso viesse a ocorrer algum tipo de erro, não seria tão simples corrigi-lo. Já que poderíamos corrigir o erro em uma classe, mas ele iria permanecer na outra, deixando assim o código menos robusto, e confiável.

Pense sempre nisto: Um pouco de trabalho a fim de melhorar o seu código, em termos de estabilidade, e robustez. Nunca é um trabalho, é apenas um passatempo.😁

Mas ainda não terminamos o que viemos fazer, neste artigo. Lembra-se, queremos adicionar as linhas de limite de preço. Os tais pontos de take profit, e stop loss, para termos uma noção durante a colocação de uma ordem pendente, de uma maneira totalmente manual, ainda falta este ponto, para finalmente finalizarmos este artigo, e partirmos para a próxima etapa. Mas para separar as coisas, do que foi visto até o momento, vamos criar um novo tópico no artigo.


Criando as linha de take profit e stop loss

Agora temos uma questão, precisamos pensar um pouco: Onde seria mais adequado colocar o código destas linhas ?!?! Bem, o ponto de chamada já temos. E pode ser visto logo abaixo:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        uint            BtnStatus;
        double  Price;
        static double mem = 0;
        
        (*mouse).DispatchMessage(id, lparam, dparam, sparam);
        (*mouse).GetStatus(Price, BtnStatus);
        if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
        {
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  (*manager).ToMarket(ORDER_TYPE_BUY, user03, user02, user01, user04);
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))(*manager).ToMarket(ORDER_TYPE_SELL, user03, user02, user01, user04);
        }
        if (def_SHIFT_Press(BtnStatus) != def_CTRL_Press(BtnStatus))
        {
// This point ...
                if (def_BtnLeftClick(BtnStatus) && (mem == 0)) (*manager).CreateOrder(def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, mem = Price, user03, user02, user01, user04);
        }else mem = 0;
}

A região marcada, em amarelo, é o ponto onde deveremos colocar a chamada, para mostrar a linhas se take profit e stop loss. Mas o detalhe é, onde devemos, codificar estas linhas ?!?!

A melhor alternativa, e com toda a certeza, acredito que todos irão concordar: É colocar o código na classe C_Mouse. Assim quando formos remover o mouse, as linhas irão junto. Então é isto. Vamos agora para a classe C_Mouse, criar as linhas que irão representar, o take profit, e o stop loss.

Mas irei fazer algo, um pouco diferente, do imaginado antes. Não irei adicionar as linhas, no evento OnChartEvent. Iremos adicionar elas, ao tratador de eventos, dentro da classe C_Mouse. Assim ficará bem melhor, apesar de termos que fazer algumas outras mudanças, no código do EA, mas isto ficará para depois, vamos então para o arquivo de cabeçalho C_Mouse.mqh, e implementar o que precisamos.

A primeira coisa que faremos, é adicionar algumas novas definições, conforme mostrado abaixo:

#define def_PrefixNameObject    "MOUSE_"
#define def_MouseLineName       def_PrefixNameObject + "H"
#define def_MouseLineTake       def_PrefixNameObject + "T"
#define def_MouseLineStop       def_PrefixNameObject + "S"
#define def_MouseName           "MOUSE_H"

Observem que a antiga definição, foi removida. Assim poderemos fazer o trabalho de uma forma um pouco diferente, mas que seja agradável de ser programada. E para reduzir o trabalho de programação, vamos modificar o procedimento de criação, para outra forma como pode ser visto logo abaixo:

                void CreateLineH(void)
                void CreateLineH(const string szName, const color cor)
                        {
                                ObjectCreate(m_Infos.Id, def_MouseName, OBJ_HLINE, 0, 0, 0);
                                ObjectSetString(m_Infos.Id, def_MouseName, OBJPROP_TOOLTIP, "\n");
                                ObjectSetInteger(m_Infos.Id, def_MouseName, OBJPROP_BACK, false);
                                ObjectSetInteger(m_Infos.Id, def_MouseName, OBJPROP_COLOR, m_Infos.Cor);
                                ObjectCreate(m_Infos.Id, szName, OBJ_HLINE, 0, 0, 0);
                                ObjectSetString(m_Infos.Id, szName, OBJPROP_TOOLTIP, "\n");
                                ObjectSetInteger(m_Infos.Id, szName, OBJPROP_BACK, false);
                                ObjectSetInteger(m_Infos.Id, szName, OBJPROP_COLOR, cor);
                        }

Agora todas a linhas serão criadas, de uma forma única, bastando informa o nome, e a cor da mesma. Foi preciso criar mais 2 variáveis para armazenar as cores, mas acredito, não ser necessário mostrar isto aqui. Então vamos passar para o constructor, pois agora ele precisará receber, bem mais dados do que antes, como você pode ver abaixo:

                C_Mouse(const color corPrice, const color corTake, const color corStop, const double FinanceStop, const double FinanceTake, const uint Leverage)
                        {
                                m_Infos.Id        = ChartID();
                                m_Infos.CorPrice  = corPrice;
                                m_Infos.CorTake   = corTake;
                                m_Infos.CorStop   = corStop;
                                m_Infos.PointsTake= FinanceToPoints(FinanceTake, Leverage);
                                m_Infos.PointsStop= FinanceToPoints(FinanceStop, Leverage);
                                ChartSetInteger(m_Infos.Id, CHART_EVENT_MOUSE_MOVE, true);
                                ChartSetInteger(m_Infos.Id, CHART_EVENT_OBJECT_DELETE, true);
                                CreateLineH(def_MouseLineName, m_Infos.CorPrice);
                        }

Como informei, foi preciso criar mais algumas variáveis, mas o custo de fazer isto é baixo, frente ao que iremos conseguir ganhar, em termos de possibilidades. Observem um fato aqui: Não vou ficar esperando o EA chamar, para converter os valores financeiros em pontos. Já vamos fazer isto aqui, e agora. Desta forma, iremos economizar algum tempo depois. Já que é mais rápido acessar uma variável, do que chamar uma função. Mas e ai, o destructor, ficou mais complicado ? Na verdade não. Tudo que foi preciso fazer nele, foi mudar o tipo de função responsável por remover os objetos, como pode ser visto no código a seguir:

                ~C_Mouse()
                        {
                                ChartSetInteger(m_Infos.Id, CHART_EVENT_OBJECT_DELETE, false);
                                ObjectsDeleteAll(m_Infos.Id, def_PrefixNameObject);
                                ObjectDelete(m_Infos.Id, def_MouseName);
                        }

Esta função tem a capacidade de remover todos os objetos, cujo nome começa de uma determinada maneira. Isto é muito útil, e extremamente versátil em diversas situações, nos poupando muito tempo. Assim chegamos na última rotina, que precisará ser modificada, então vamos ver como foi que fiz, para implementar a linhas de limite de preço, e isto pode ser visto no código abaixo:

                void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
                        {
                                int w;
                                datetime dt;
                                static bool bView = false;
                                
                                switch (id)
                                        {
                                                case CHARTEVENT_OBJECT_DELETE:
                                                        if (sparam == def_MouseName) CreateLineH();
                                                        if (sparam == def_MouseLineName) CreateLineH(def_MouseLineName, m_Infos.CorPrice);
                                                        break;
                                                case CHARTEVENT_MOUSE_MOVE:
                                                        ChartXYToTimePrice(m_Infos.Id, (int)lparam, (int)dparam, w, dt, m_Infos.Price);
                                                        ObjectMove(m_Infos.Id, def_MouseName, 0, 0, m_Infos.Price = AdjustPrice(m_Infos.Price));
                                                        ObjectMove(m_Infos.Id, def_MouseLineName, 0, 0, m_Infos.Price = AdjustPrice(m_Infos.Price));
                                                        m_Infos.BtnStatus = (uint)sparam;
                                                        if (def_CTRL_Press(m_Infos.BtnStatus) != def_SHIFT_Press(m_Infos.BtnStatus))
                                                        {
								if (!bView)
								{
									if (m_Infos.PointsTake > 0) CreateLineH(def_MouseLineTake, m_Infos.CorTake);
									if (m_Infos.PointsStop > 0) CreateLineH(def_MouseLineStop, m_Infos.CorStop);
									bView = true;
								}
								if (m_Infos.PointsTake > 0) ObjectMove(m_Infos.Id, def_MouseLineTake, 0, 0, m_Infos.Price + (m_Infos.PointsTake * (def_SHIFT_Press(m_Infos.BtnStatus) ? 1 : -1)));
								if (m_Infos.PointsStop > 0) ObjectMove(m_Infos.Id, def_MouseLineStop, 0, 0, m_Infos.Price + (m_Infos.PointsStop * (def_SHIFT_Press(m_Infos.BtnStatus) ? -1 : 1)));
                                                        }else if (bView)
                                                        {
                                                                ObjectsDeleteAll(m_Infos.Id, def_PrefixNameObject);
                                                                bView = false;
                                                        }
                                                        ChartRedraw();
                                                        break;
                                        }
                        }

Primeiramente, foi preciso retirar duas linhas de código antigo, mas isto já era previsto, e no lugar delas, vieram outras duas com o código atualizado. Mas o grande detalhe começa no momento, em que iremos tratar do evento de movimento do mouse, onde adicionamos algumas novas linhas, a primeira coisa que fazemos, é um teste para verificar se a tecla SHIFT ou CTRL estão pressionadas, mas não ao mesmo tempo, se isto for verdadeiro, passamos para a próxima etapa.

Agora se for falso, verificamos se as linhas de limite, estão sendo plotadas no gráfico, e se este for o caso, removemos todas a linhas do mouse, mas isto não é problema. Pois imediatamente será gerado pelo MetaTrader 5, um evento alertando que foi removido objetos da tela. Ao chamar tratador de eventos da tela, seremos conduzidos, de forma a recolocar a linha de preço, novamente no gráfico.

Mas vamos voltar ao ponto onde as linhas de limite serão plotadas, caso estejamos com a tecla SHIFT ou CTRL pressionadas. Neste caso iremos verificar se as linhas já estão na tela, e se não estiverem, iremos criá-las, desde que o valor seja maior do que zero, pois não queremos um elemento estranho no gráfico. Marcamos isto como feito, para não ficar tentando recriar estes objetos a cada chamada, e logo depois, iremos posicionar elas no seu devido lugar, dependendo de onde a linha do preço esta localizada.


Conclusão

E desta forma, criamos um sistema de EA, para ser operado de forma totalmente manual, e estaremos prontos para a próxima etapa, que será vista no próximo artigo, onde iremos adicionar, um gatilho para que o sistema, já consiga fazer algo de forma automática, e uma vez feito isto, irei mostrar o que você precisará, para transformar este EA daqui, que está operando de forma manual, para um EA, que opere de forma totalmente automática. Então, nos vemos no próximo artigo, onde iremos começar a automatizar o EA, removendo as decisões humanas do operacional.


Arquivos anexados |
Últimos Comentários | Ir para discussão (1)
Carlos Giovanni
Carlos Giovanni | 8 nov 2022 em 20:25

Prezado Daniel, inicialmente quero agradecer o seu trabalho.

Eu estou iniciando na automatização de estratégias no MetaTrader e seus artigos estão muito didáticos, me ajudando nessa fase.

Já estou aguardando o próximo artigo, que deve tratar da automatização das ordens.

Eu tenho uma estratégia simples que foi implementada apenas em linguagem R para testar o desempenho da lucratividade.

Com seu material, pretendo codificar para MQL5.


Mais uma vez, parabéns ;-)

Ciência de Dados e Aprendizado de Máquina — Redes Neurais (Parte 01): Entendendo as Redes Neurais Feed Forward Ciência de Dados e Aprendizado de Máquina — Redes Neurais (Parte 01): Entendendo as Redes Neurais Feed Forward
Muitas pessoas as amam, mas apenas alguns entendem todas as operações por trás das Redes Neurais. Neste artigo, eu tentarei explicar tudo o que acontece por trás dos bastidores de um perceptron multicamadas feed-forward de maneira simples.
Criação de Indicadores complexos de maneira fácil usando objetos Criação de Indicadores complexos de maneira fácil usando objetos
Este artigo fornece um método para criar os indicadores complexos e, ao mesmo tempo, evitar os problemas que surgem ao lidar com vários gráficos, buffers e/ou combinar dados de várias fontes.
DoEasy. Controles (Parte 12): Objeto base lista, objetos WinForms ListBox e ButtonListBox DoEasy. Controles (Parte 12): Objeto base lista, objetos WinForms ListBox e ButtonListBox
Neste artigo, criaremos um objeto base para listas de objetos WinForms e dois novos objetos, nomeadamente ListBox e ButtonListBox.
Indicador CCI. Três etapas de transformação Indicador CCI. Três etapas de transformação
Neste artigo, eu farei alterações adicionais no CCI afetando a própria lógica desse indicador. Além disso, nós poderemos vê-lo na janela principal do gráfico.