English Русский 中文 Español Deutsch 日本語
preview
Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 11): Automação (III)

Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 11): Automação (III)

MetaTrader 5Negociação | 20 janeiro 2023, 10:51
581 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior, Aprendendo a construindo um EA que opera de forma automática (Parte 10): Automação (II), expliquei uma forma de você adicionar, um controle de horário, no qual o EA poderia funcionar. Apesar de todo o sistema do EA, esta ter sido, e esta sendo construído, de maneira a promover um sistema autônomo. Antes de passarmos para a última etapa, onde de fato iremos ter um EA 100% automático, precisamos fazer alguns pequenos ajustes, e mudanças no código.

Na fase da automação, não devemos de maneira alguma modificar, criar, ou alterar, qualquer parte do código já criado, se quer devemos mexer nele. Devemos apenas e somente remover os pontos, onde existia alguma interação do operador com o EA, e no lugar desta interação, colocar algum tipo de gatilho automático. Que de outra forma seria feito de maneira manual, pelo operador, apenas e somente isto, é que de fato deverá ser feito. Se por qualquer motivo você precisar mexer no código antigo, a fim de que a automação possa ser feita. Isto significa quer o código antigo, esta mal planejado, você deverá refazer todo o planejamento, de maneia a ter um sistema com as seguintes características:

  • Robusto, ele não deve conter falhas primarias, que possa comprometer a integridade de nenhuma parte do código;
  • Confiável, um sistema confiável, é aquele que foi submetido a diversas situações potencialmente perigosas, e no entanto se saiu bem, sem falhas;
  • Estável, nada adianta ter um sistema que funciona bem em alguns momentos, mas sem explicação trava a plataforma;
  • Escalonável, um sistema deve crescer de maneira suave, sem necessidade de muita programação envolvida;
  • Encapsulado, apenas e somente as rotinas realmente necessárias para o uso, devem ser visíveis fora do local onde o código esta sendo criado;
  • Rápido, nada adianta você ter o melhor modelo, se ele é lento por conta de um código mal elaborado.

Algumas destas características, nos remete ao modelo de programação orientada a objetos. De fato, este modelo é de longe o mais indicado para criação de programas, onde precisamos de um bom nível de segurança. Por conta disto, deste o inicio desta serie, você deve ter notado que toda a programação foi desenvolvida, sendo voltada ao uso de classe, ou seja, uma programação orientada a objetos. Sei que no inicio, este tipo de programação, parece ser bastante confusa e difícil de aprender, mas acredite, você irá se beneficiar muito, se de fato se esforçar e aprender a criar seus códigos como sendo classes.

Estou dizendo estas coisas, para mostrar a você, aspirante a programador profissional, como proceder ao criar seus códigos. Você deve se acostumar a trabalhar, utilizando 3 pastas, nas quais o código será mantido, e não importa o qual complexo ou simples ele seja, você deverá trabalhar sempre em 3 etapas:

  1. Na primeira etapa, que será a pasta de desenvolvimento, todo o código irá ser modificado, trabalhado e testado, qualquer nova função, ou mudança deverá ser feita, apenas e somente, no código presente nesta pasta.
  2. Uma vez que o código planejado, tenha sido construído e devidamente testado, você o transporta para a segunda pasta, a pasta de trabalho, nesta pasta, o código ainda pode conter algum tipo de falha, mas você, NÃO deve mexer nele, caso precise, mexer no código, que esteja nesta pasta, ele deverá, voltar para a pasta de desenvolvimento. Um detalhe: Se a mudança for feita, a fim de corrigir alguma falha, que foi verificada, mas não foi feita, nenhum tipo de modificação, mais extrema, o código desta pasta de trabalho, poderá ser mantido nela, recebendo as devidas correções.
  3. Finalmente, depois que o código, tenha sido utilizado por diversas vezes, em várias situações diferentes, e sem nenhum tipo de nova mudança, ele irá passar para uma terceira e última pasta, esta é as pasta estável, onde o código presente ali, já demonstrou que não contém falhas, sendo bastante útil e eficaz na tarefa, para a qual ele foi projetado para exercer. Nenhum código novo, deverá entrar nesta última pasta, JAMAIS.

Se você fizer isto, por um certo tempo, acabará criando um banco de dados de rotinas e procedimentos, bem interessante, a ponto de conseguir programar as coisas de maneira extremamente rápida e segura. Este tipo de coisa, é bastante apreciada, principalmente em uma atividade como é a do mercado financeiro. Onde ninguém de fato está interessado em usar um código, que não seja adequado ao risco que o mercado sempre nos oferece. Já que estamos sempre trabalhando em uma atividade, onde erros de RUN-TIME, não são apreciados e tudo acontece no pior tipo de cenário, que é o de REAL TIME, ou seja, seu código tem que conseguir resistir, a um tipo de evento inesperado, que pode acontecer a qualquer momento.

Tudo isto para chegar ao seguinte ponto, vejam a figura 01:

Figura 01

Figura 01 - Sistema de operação manual

Muita gente, não tem a devida noção do que está na verdade programando, ou criando, e isto se deve ao fato de que muitos, não entendem realmente o que está acontecendo dentro do sistema, e acabam por achar que a plataforma. Seja ela qual for, deve prover o que o operador está querendo fazer, quando na verdade, olhando a figura 01, você nota que a plataforma de fato, não deve ter como foco prover o que o operador deseja. Ela deve sim, nos dar forma de interação com o operador, mas ao mesmo tempo, se manter a ponto de conseguir se comunicar de forma estável, rápida e eficiente, com o servidor de negociação. Ao mesmo tempo que isto se dá, ela deve manter-se funcionando, e dando suporte as coisas que serão utilizadas para interagir com o operador.

Veja que em momento algum, nenhuma das setas, vai além do seu ponto. Este tipo de coisa é um sistema de negociação manual, notem que o EA, está sendo mantido pela plataforma, e não o contrário, e que o operador, não entra de fato em contato com o EA. Isto parece estranho, mas o que de fato acontece, é que o operador acessa o EA, via plataforma, e a plataforma orienta o EA, enviando os eventos que o operador está criando ou executando. Como resposta, o EA envia para a plataforma, requerimentos para serem enviados para o servidor de negociação, quando o servidor responde, ele envia tais respostas para dentro da plataforma, e esta manda para o EA, o que foi respondido pelo servidor. O EA depois de analisar e tratar a resposta do servidor, envia algum tipo de informação para a plataforma, para que esta mostre ao operador, o que esta acontecendo, ou qual foi o resultado do pedido que o operador fez.

Muitos não veem de fato as coisas assim, então se alguma falha acontecer no EA, a plataforma não irá de fato ter problemas. O problema é no EA, mas erroneamente, operadores menos experientes irão culpar a plataforma, dizendo que ela não está fazendo o que eles pretendiam fazer.

Como programador, você não tem que tentar, salvo se você for um dos que desenvolvem e mantem a plataforma, mudar o como a plataforma funciona, você tem que fazer exatamente o contrário. Cuidar para que seu código, responda de forma adequada, aos requerimentos da plataforma.

Nisto, caímos finalmente na questão: Por que primeiramente criar um EA manual, utilizá-lo durante um tempo, desta forma, para somente depois automatizar ele ?!?! O motivo é justamente este. Criar uma forma de realmente testarmos o código, e criar exatamente o que precisamos, nem mais, nem menos.

E já que para automatizar adequadamente o sistema, sem precisar mexer em nenhuma linha dentro do código, que será usado como ponto de controle e sistema de ordens, precisamos fazer algumas adições e pequenas mudanças, agora neste momento. Desta forma, o código criado até o artigo anterior, será colocada na pasta de trabalho, o código criado no artigo que antecede o anterior, será colocada na pasta estável, e este que será visto neste artigo, irá para a pasta de desenvolvimento. Compreenderam como a coisa vai crescendo, e se desenvolvendo de forma a termos uma codificação bem mais rápida. Sempre se algo der errado, temos como voltar 2 versões para traz, onde de fato a coisa funcionava sem problemas.


Implementando as mudanças

A primeira coisa, de fato que faremos, é uma mudança no sistema de controle de tempo. Esta modificação é vista no código abaixo:

virtual const bool CtrlTimeIsPassed(void) final
                        {
                                datetime dt;
                                MqlDateTime mdt;
                                
                                TimeToStruct(TimeLocal(), mdt);
                                TimeCurrent(mdt);
                                dt = (mdt.hour * 3600) + (mdt.min * 60);
                                return ((m_InfoCtrl[mdt.day_of_week].Init <= dt) && (m_InfoCtrl[mdt.day_of_week].End >= dt));
                        }

A linha riscada, foi substituída pela linha em destaque, e por qual motivo se deu esta mudança ?!?! O motivo são dois, o primeiro, é que estamos substituindo duas chamadas por uma. Já que na linha riscada, era feita primeiro a chamada a fim de saber a hora, e logo depois uma segunda para converter a hora em uma estrutura. O segundo motivo, é que TimeLocal, retorna na verdade o horário do computador, e não a horário do visto na observação de mercado, como mostrado na figura 02.

Figura 02

Figura 02 - Horário informado pelo servidor na última atualização.

O fato de usar o horário do computador, não é problema desde que ele esteja sincronizado, via servidor NTP ( aqueles que mantem a hora oficial atualizada ). Mas como na grande maioria das vezes, muitos não estarão de fato usando tais servidores, então pode acontecer, de que o sistema de controle de tempo, permita o EA entrar antes ou o force a sair antes. De qualquer forma, a mudança se fez necessário justamente para evitar este tipo de inconveniente.

Isto é a tal mudança feita, não de maneira a mudar radicalmente o código, e sim para promover uma maior estabilidade, esperada pelo operador, já que se o EA entrar e sair, antes ou depois do previsto. Você pode ficar tentando entender o motivo, achando que se trata de um erro na plataforma, ou no seu código. Sendo que na verdade, se trata de uma simples: Falta de sincronização do horário, entre o computador e a o servidor de negociação, pois este último, com toda a certeza, estará usando um servidor NTP para se manter no horário oficial, e o computador usado para negociação, pode não estar usando este tipo de servidor.

A próxima mudança, é no sistema de ordens, está pode ser vista logo abaixo:

                ulong ToServer(void)
                        {
                                MqlTradeCheckResult     TradeCheck;
                                MqlTradeResult          TradeResult;
                                bool bTmp;
                                
                                ResetLastError();
                                ZeroMemory(TradeCheck);
                                ZeroMemory(TradeResult);
                                bTmp = OrderCheck(m_TradeRequest, TradeCheck);
                                if (_LastError == ERR_SUCCESS) bTmp = OrderSend(m_TradeRequest, TradeResult);
                                if (_LastError != ERR_SUCCESS) MessageBox(StringFormat("Error Number: %d", GetLastError()), "Order System", MB_OK);
                                if (_LastError != ERR_SUCCESS) PrintFormat("Order System - Error Number: %d", _LastError);
                                return (_LastError == ERR_SUCCESS ? TradeResult.order : 0);
                        }

Esta também é uma mudança necessária, para adequar o sistema, a fim de que o EA possa ser automatizado sem muitos problemas. De fato, a linha riscada, não foi substituída pela linha em destaque, apenas pelo fato de que o código, pode ser mais rápido ou estável, muito pelo contrário, já que o fato de haver uma ocorrência de erro, precisa ser devidamente tratada. Mas quando temos um EA automático, alguns tipos de falhas podem ser ignoradas, como foi dito em artigos passados.

Mas a grande questão, é que a linha riscada, irá sempre lançar uma janela de mensagem informando o erro, e em alguns casos, quando o erro irá ser tratado de um forma adequada pelo código. Não faz sentido ficar lançando tais janelas, podemos simplesmente imprimir uma mensagem no terminal, informando a ocorrência, desta forma o operador poderá tomar as devidas medidas.

Lembre-se, um EA 100% automático, não pode ficar esperando que o operador tomar alguma decisão. Mesmo por que, isto pode vim a demorar, mas também não pode fazer as coisas, sem informar que tipo de problema aconteceu. E mais uma vez, o código foi modificado, a fim de melhorar a sua agilidade, não houve grandes mudanças, a ponto que precisemos colocar o sistema em uma fase de testes mais intensa a procura de falhas, que podem ter surgido por conta das mudanças.

Mas diferente destas mudanças feitas acima, agora teremos outras das quais será preciso fazer testes mais aprofundados, visto que acontecerá mudanças na forma do sistema trabalhar.


Pavimentando a estrada antes da automação

As mudanças que serão feitas agora, irão permitir que consigamos efetivamente criar um sistema 100% automático. Sem estas mudanças, ficaríamos de mãos atadas para o próximo artigo, onde irei mostrar como tornar um EA já testado, e espero que estejam realizando os testes, para entender como a coisa de fato funciona, pode se tornar autônomo. Para promover as mudanças necessárias, precisaremos fazer a remoção, ou melhor dizendo, modificação de algumas coisas, e adicionar outras. Vamos começar então pela modificação. O que iremos modificar, é visto no fragmento abaixo:

//+------------------------------------------------------------------+
#include "C_ControlOfTime.mqh"
//+------------------------------------------------------------------+
#define def_MAX_LEVERAGE                10
#define def_ORDER_FINISH                false
//+------------------------------------------------------------------+
class C_Manager : public C_ControlOfTime

Estas duas definições, irão deixar de existir, e no lugar delas irá surgir 2 novas variáveis, que não poderão ser modificadas pelo operador, mas poderão ser definidas por você, programador, e por que fazer tal mudança ?!?! O motivo é que ao promover estas mudança, onde as definições serão trocadas por variáveis, perderemos um pouco na questão de velocidade. Mesmo que sejam poucos ciclos de máquina, de fato irá acontecer uma pequena perda de performance, já que é consideravelmente mais rápido acessar um valor constante, do que acessar uma variável. Mas em compensação, iremos ganhar em reutilização da classe, e você irá entender isto melhor no próximo artigo. Acredite, a diferença em termos de facilidade e portabilidade, compensa a pequena perda de performance que iremos ter. Então, as duas linhas acima, foram substituídas pelo seguinte fragmento:

class C_Manager : public C_ControlOfTime
{
        enum eErrUser {ERR_Unknown, ERR_Excommunicate};
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage,
                                MaxLeverage;
                        bool    IsDayTrade,
                                IsOrderFinish;                                          
                }m_InfosManager;

Agora muito cuidado ao trabalhar com o código, já que você não deverá, como programador, modificar o valor destes duas variáveis, fora do local onde elas irão ser inicializadas. Tome muito cuidado para não fazer isto. O local onde elas serão inicializadas, é justamente no constructor da classe, como mostrado no fragmento logo abaixo:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger, const bool IsOrderFinish, const uint MaxLeverage)
                        :C_ControlOfTime(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.IsOrderFinish    = IsOrderFinish;
                                m_InfosManager.MaxLeverage      = MaxLeverage;
                                m_InfosManager.FinanceStop      = FinanceStop;
                                m_InfosManager.FinanceTake      = FinanceTake;
                                m_InfosManager.Leverage         = Leverage;
                                m_InfosManager.IsDayTrade       = IsDayTrade;

Agora o constructor, irá receber dois novos argumentos, e estes irão inicializar as nossas variáveis. Uma vez feito isto, iremos mudar os pontos onde as definições eram instanciadas. Estas mudanças, foram feitas nos seguintes pontos:

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                int tmp = m_Position.Leverage;
                                
                                m_Position.Leverage = (int)(PositionGetDouble(POSITION_VOLUME) / GetTerminalInfos().VolMinimal);
                                m_Position.IsBuy = ((ENUM_POSITION_TYPE) PositionGetInteger(POSITION_TYPE)) == POSITION_TYPE_BUY;
                                m_Position.TP = PositionGetDouble(POSITION_TP);
                                v1 = m_Position.SL = PositionGetDouble(POSITION_SL);
                                v2 = m_Position.PriceOpen = PositionGetDouble(POSITION_PRICE_OPEN);
                                if (def_ORDER_FINISH) if (m_TicketPending > 0) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Position.EnableBreakEven = (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                if (m_InfosManager.IsOrderFinish) if (m_TicketPending > 0) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_TicketPending == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return m_Position.Leverage - tmp;
                        }
inline void TriggerBreakeven(void)
                        {
                                double price;
                                
                                if (PositionSelectByTicket(m_Position.Ticket))
                                        if (PositionGetDouble(POSITION_PROFIT) >= m_Trigger)
                                        {
                                                price = m_Position.PriceOpen + (GetTerminalInfos().PointPerTick * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH)
                                                if (m_InfosManager.IsOrderFinish)
                                                {
                                                        if (m_TicketPending > 0) m_Position.EnableBreakEven = !ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                }else m_Position.EnableBreakEven = !ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                        }
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                tmp = C_Orders::ToMarket(type, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                        {
// ... Restante do código ...
                void UpdatePosition(const ulong ticket)
                        {
                                int ret;
                                double price;
                                
                                if ((ticket == 0) || (ticket != m_Position.Ticket) || (m_Position.Ticket == 0)) return;
                                if (PositionSelectByTicket(m_Position.Ticket))
                                {
                                        ret = SetInfoPositions();
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                price = m_Position.PriceOpen + (FinanceToPoints(m_InfosManager.FinanceStop, m_Position.Leverage) * (m_Position.IsBuy ? -1 : 1));
                                                if (m_TicketPending > 0) if (OrderSelect(m_TicketPending))
                                                {
                                                        price = OrderGetDouble(ORDER_PRICE_OPEN);
                                                        C_Orders::RemoveOrderPendent(m_TicketPending);
                                                }
                                                if (m_Position.Ticket > 0)      m_TicketPending = C_Orders::CreateOrder(m_Position.IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY, price, 0, 0, m_Position.Leverage, m_InfosManager.IsDayTrade);
                                        }
                                        m_StaticLeverage += (ret > 0 ? ret : 0);
                                }else
                                {
                                        ZeroMemory(m_Position);
                                        if ((def_ORDER_FINISH) && (m_TicketPending > 0))
                                        if ((m_InfosManager.IsOrderFinish) && (m_TicketPending > 0))
                                        {
                                                RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                        }
                                }
                                ResetLastError();
                        }
inline void TriggerTrailingStop(void)
                        {
                                double price, v1;
                                
                                if ((m_Position.Ticket == 0) || (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.SL == 0)) return;
                                if ((m_Position.Ticket == 0) || (m_InfosManager.IsOrderFinish ? m_TicketPending == 0 : m_Position.SL == 0)) return;
                                if (m_Position.EnableBreakEven) TriggerBreakeven(); else
                                {
                                        price = SymbolInfoDouble(_Symbol, (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : (m_Position.IsBuy ? SYMBOL_ASK : SYMBOL_BID)));
                                        v1 = m_Position.SL;
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                                if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                        if (v1 > 0) if (MathAbs(price - v1) >= (m_Position.Gap * 2)) 
                                        {
                                                price = v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH)
                                                if (m_InfosManager.IsOrderFinish)
                                                        ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                else    ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                                }
                        }

Todas e absolutamente todas as partes riscadas, foram substituídas pelas partes em destaque. Desta forma, agora conseguimos promover a tal sonhada reutilização da classe, e a sua ampliação em termos de usabilidade. Apesar de não ficar claro, neste artigo, este aumento de usabilidade, você entenderá muito bem como isto se dará no próximo.

Além desta mudanças que foram promovidas, ainda precisamos fazer algumas adições, isto para que a reutilização da classe, aconteça de maneira a maximizar o acesso do sistema de automação, ao sistema de envio de ordens. Para fazer isto de maneira correta, precisaremos adicionar em primeiro lugar, a seguinte rotina:

inline void ClosePosition(void)
	{
		if (m_Position.Ticket > 0)
		{
			C_Orders::ClosePosition(m_Position.Ticket);
			ZeroMemory(m_Position.Ticket);
		}                               
	}

Esta rotina se faz necessária, em alguns tipos de modelos operacionais, por conta disto, precisamos fazer com que ela seja incluída no código da classe C_Manager. Agora um detalhe, ao adicionar esta rotina, serão gerados diversos avisos do compilador ao se tentar compilar o código, estes podem ser visto na figura 03.

Figura 03

Figura 03 - Alertas do compilador ....

Diferente de alguns tipos de alertas gerados pelo compilador, que podem ser ignorados. Apesar de não ser prudente fazer tal coisa, este tipo de alerta, visto na figura 03, pode ser em vários casos, potencialmente danoso ao programa, podendo fazer com que o código gerado, não funcione de uma maneira adequada.

O ideal, é você ao notar que o compilador gerou tais avisos, tentar corrigir a falha, que está gerando este tipo de aviso. As vezes é algo bastante simples de ser resolvido, outras vezes é algo um pouco mais complicado. Já que pode estar sendo uma mudança de tipo, onde parte dos dados poderão se perder, durante a conversão. De uma maneira ou de outra, é sempre muito importante observar o motivo pelo qual o compilador gerou tais alertas, mesmo que o código venha, e muitas vezes será compilado.

Ter alertas do compilador, é sinal de que algo não vai bem no código, já que o compilador, esta com dificuldades de entender o que você está programando. Se ele não consegue entender, ele não irá gerar um código 100% confiável.

Algumas plataformas de programação, permitem você desligar os alertas de compilação. Pessoalmente, desaconselho tal pratica. Na verdade, quando você deseja ter um código 100% confiável, o melhor seria que todos os alertas fossem ligados, mas em uma grande maioria das vezes. Isto acontece frequentemente, e com o tempo você aprenderá isto, o simples fato de deixar as coisas como sendo o padrão da plataforma de programação, já permitirá que seu código, seja o mais confiável possível, desde é claro, não tenhamos alertas que seriam dados na configuração padrão de compilação.

Para resolver estes alertas, que foram dados acima, temos dois caminhos. O primeiro é substituir as chamadas a função ClosePosition, que estarão referenciando a função presente na classe C_Orders, por esta que acabamos de adicionar na classe C_Manager, o que de certa forma, seria a melhor decisão. Já que iriamos testar a chamada presente em C_Manager. A outra solução, é dizer ao compilador, que as chamadas estariam de fato se referindo a classe C_Orders.

Mas irei mudar o código, de maneira a usar a chamada recém criada. Então os pontos com problemas, que estão gerando os alertas, ficam como mostrado abaixo, desta forma o compilador, irá conseguir entender o que estamos querendo de fato fazer.

                ~C_Manager()
                        {
                                if (_LastError == (ERR_USER_ERROR_FIRST + ERR_Excommunicate))
                                {
                                        if (m_TicketPending > 0) RemoveOrderPendent(m_TicketPending);
                                        if (m_Position.Ticket > 0) ClosePosition(m_Position.Ticket);
                                        ClosePosition();
                                        Print("EA was kicked off the chart for making a serious mistake.");
                                }
                        }

Aqui no destructor, foi a parte fácil, mas temos um ponto um pouco mais complicado de resolver. Este pode ser visto abaixo:

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                if (PositionSelectByTicket(m_Position.Ticket)) ClosePosition(m_Position.Ticket);
                                                ClosePosition();
                                                if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); else RemoveOrderPendent(m_TicketPending);
                                                ZeroMemory(m_Position.Ticket);
                                                m_TicketPending = 0;
                                                ResetLastError();
                                        }else   SetUserError(ERR_Unknown);
                                }else m_Position.Ticket = (m_Position.Ticket == 0 ? m_TicketPending : m_Position.Ticket);
                                m_TicketPending = 0;
                                if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket);
                                CheckToleranceLevel();
                        }

As partes riscadas, foram removidas, adicionamos a chamada para fechar a posição. Mas temos um problema, caso o ticket pendente, venha a se tornar posição, por qualquer motivo, precisamos remover esta posição, conforme era feito no código original. Mas a posição não foi de fato ainda capturada pela classe C_Manager. Neste caso, dizemos ao compilador, que a chamada estará se referindo de fato, a chamada dentro da classe C_Orders, conforme é mostrado em destaque.

E uma última mudança, que também precisará ser feita, é vista logo abaixo:

inline void EraseTicketPending(const ulong ticket)
                        {
                                if ((m_TicketPending == ticket) && (m_TicketPending > 0))
                                {
                                        if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); 
                                        else RemoveOrderPendent(m_TicketPending);
                                        m_TicketPending = 0;
                                }
                                ResetLastError();
                                m_TicketPending = (ticket == m_TicketPending ? 0 : m_TicketPending);
                        }

O código riscado, que era o código presente originalmente, foi substituído por um código um pouco mais complexo. Mas em compensação, agora nos dá uma maior capacidade de remover a ordem pendente, ou caso ela tenha se tornado uma provável posição, também conseguirmos eliminá-la. Antes apenas respondíamos a um evento que a plataforma MetaTrader 5 nos informava, fazendo com que o valor indicado, como sendo o ticket da ordem pendente, fosse zerado, a fim que que poderíamos enviar uma nova ordem pendente. Mas agora iremos fazer um pouco mais do que isto, já que de fato precisaremos de tal funcionalidade em um sistema 100% automatizado.

Mas por conta destas pequenas mudanças, passamos a ter um bônus em todo o sistema, e este irá nos promover um aumento na reutilização e teste do código.


Últimos aperfeiçoamentos antes da fase final

Com as melhorias feitas, podemos ainda tirar algum proveito, gerando assim algumas melhorias, ao mesmo tempo que elevamos a reutilização do código. A primeira delas, é vista no código abaixo:

                void UpdatePosition(const ulong ticket)
                        {
                                int ret;
                                double price;
                                
                                if ((ticket == 0) || (ticket != m_Position.Ticket) || (m_Position.Ticket == 0)) return;
                                if (PositionSelectByTicket(m_Position.Ticket))
                                {
                                        ret = SetInfoPositions();
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                price = m_Position.PriceOpen + (FinanceToPoints(m_InfosManager.FinanceStop, m_Position.Leverage) * (m_Position.IsBuy ? -1 : 1));
                                                if (m_TicketPending > 0) if (OrderSelect(m_TicketPending))
                                                {
                                                        price = OrderGetDouble(ORDER_PRICE_OPEN);
                                                        C_Orders::RemoveOrderPendent(m_TicketPending);
                                                        EraseTicketPending(m_TicketPending);
                                                }
                                                if (m_Position.Ticket > 0)      m_TicketPending = C_Orders::CreateOrder(m_Position.IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY, price, 0, 0, m_Position.Leverage, m_InfosManager.IsDayTrade);
                                        }
                                        m_StaticLeverage += (ret > 0 ? ret : 0);
                                }else
                                {
                                        ZeroMemory(m_Position);
                                        if ((m_InfosManager.IsOrderFinish) && (m_TicketPending > 0))
                                        {
                                                RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                        }
                                        if (m_InfosManager.IsOrderFinish) EraseTicketPending(m_TicketPending);
                                }
                                ResetLastError();
                        }

Outro ponto que também pode se beneficiar, é visto no código a seguir:

                ~C_Manager()
                        {
                                if (_LastError == (ERR_USER_ERROR_FIRST + ERR_Excommunicate))
                                {
                                        if (m_TicketPending > 0) RemoveOrderPendent(m_TicketPending);
                                        EraseTicketPending(m_TicketPending);
                                        ClosePosition();
                                        Print("EA was kicked off the chart for making a serious mistake.");
                                }
                        }

E um último ponto que também se beneficia, com esta reutilização, é mostrado logo abaixo:

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                ClosePosition();
                                                EraseTicketPending(m_TicketPending);
                                                if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); else RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                                ResetLastError();
                                        }else   SetUserError(ERR_Unknown);
                                }else m_Position.Ticket = (m_Position.Ticket == 0 ? m_TicketPending : m_Position.Ticket);
                                m_TicketPending = 0;
                                if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket);
                                CheckToleranceLevel();
                        }

E para terminar a questão das melhorias e mudanças, existe um pequeno detalhe, onde o EA poderá, por exemplo, ter um volume máximo definido de 10 vezes o volume mínimo. Mas se o operador do EA, no momento de configura o volume, indicar o valor de alavancagem de 3 vezes, ao executar a terceira operação, o EA estaria muito próximo de estourar o volume máximo permitido. Então assim que ele enviasse uma quarta solicitação, esta estaria violando o volume máximo permitido.

Parece ser algum tolo e bobo, talvez muitos considerariam algo de baixo potencial de perigo. De fato devo concordar em tese, já que um quinto pedido, com toda a certeza, não seria aceito. Mas no entanto, na definição da programação do EA, o volume indicado era de 10 vezes. Então quando o quarto pedido fosse aceito, o EA teria executado o volume 12 vezes, ultrapassando em 2 vezes, o volume máximo configurado. Isto por conta que o operador fez a configuração de uma alavancagem, de 3 vezes, mas e se ele tivesse indicado uma alavancagem de 9 vezes, na certa ele esperaria que o EA, fosse executar apenas e somente uma única operação.

Pense só na surpresa, e no espanto que ele teria, ao ver que o EA iria acabar abrindo uma segunda operação, ultrapassando desta forma, em 8 vezes o volume máximo, que ele informou ao programador para limitar. O mesmo poderia até sofrer um ataque cardíaco, dado o tamanho susto.

Como deu para ver, apesar de ser uma falha de pouco potencial, ainda sim, é uma falha, que não deve ser admitida, ainda mais para um EA automático. Vale ressaltar, que para um EA manual, isto não seria nenhum tipo de problema. Já que o próprio operador, se encarregaria de não efetuar uma outra entra, com o mesmo nível de alavancagem. Mas de uma forma ou de outra, devemos promover algum tipo de bloqueio do EA neste caso. Mas o motivo principal, é que isto já deverá existir, para o próximo artigo, não quero, de maneira alguma, me preocupar com qualquer tipo de problema desta natureza depois.

Para sanar isto, bastará adicionar algumas poucas linhas de código, conforme você pode ver logo abaixo:

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {                               
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return;
                                }
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return;
                                }
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

O código em verde, é que faz a vez de evitar o evento que foi explicado acima, mas quero que você observe com atenção todo o código, que se encontra em destaque, perceberam que ele é exatamente igual ?!?! Pois bem, podemos fazer, duas coisas aqui, uma seria criar uma macro para comportar todo este código. Mas estou evitando utilizar macros aqui, devido ao fato de que todos estes artigos, são basicamente voltados para pessoas que estejam começando a programar. Muitos destes entusiastas não tem muita experiência, ou tão pouco conhecimento sobre programação, mas possivelmente conseguirão entender um código, que tem como foco a simplicidade e a clareza na informação que esta sendo exposta.

Então vamos criar uma função para substituir, ou melhor, acumular todo este código, que esta sendo comum ou reprisado em ambas as funções. Desta forma, nasce uma nova rotina, que será colocada dentro da clausula privativa, já que não é preciso que nenhum outro código, dentro do EA, saiba de sua existência. Eis abaixo como ficou a função:

inline bool IsPossible(const bool IsPending)
	{
		if (!CtrlTimeIsPassed()) return false;
		if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
		if ((IsPending) && (m_TicketPending > 0)) return false;
		if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
		{
			Print("Request denied, as it would violate the maximum volume allowed for the EA.");
			return false;
		}
                                
		return true;
	}

Vamos entender o que esta acontecendo aqui ... todas as linhas do que estavam repetidas, nos códigos de envio de ordens, passou a ficarem neste código acima. Mas existe uma pequena diferença, entre o envio de uma ordem pendente, e uma ordem a mercado, e a diferença é este ponto daqui. Por conta disto, precisamos de uma forma de verificar, se o pedido esta vindo de uma ordem pendente, ou de uma a mercado. Para fazer esta diferenciação, usamos este argumento, assim conseguimos unir todo o código, em um único código. Para qualquer tipo de pendencia, ou impossibilidade de envio da ordem, retornaremos falso, mas se a ordem poder ser enviada, retornaremos verdadeiro.

Feito isto, o novo código das funções, podem ser visto logo abaixo:

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!IsPossible(true)) return;
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return;
                                }
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!IsPossible(false)) return;
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return;
                                }
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

Observem como ficou as chamadas, dependendo de cada um dos casos, e todas as linhas riscadas, foram retiradas do código, já que não faz mais sentido eles existirem ali. E é desta forma, retirando parte duplicadas, analisando o que se pode mudar, melhorar, testando e reutilizando ao máximo o seu código, é que os programas vão nascendo, crescendo, se desenvolvendo e se tornando seguros, confiáveis, estáveis e robustos.

Ver um código já pronto, nos dá a impressão que ele já nasceu daquela forma. Quando na verdade, foram várias as etapas, até que ele viesse a ficar como você normalmente os vê. Mas tudo vai acontecendo aos pouco, testando e experimentando, sempre procurando reduzir ao máximo a quantidade de falhas e possíveis brechas.


Considerações finais

Apesar de tudo que foi dito, falado e mostrado até aqui, ainda assim existe uma brecha, que teremos de fechar, por ela ser consideravelmente danosa, para um sistema 100% automático. Sei que muitos já devem estar ansiosos por ver o EA funcionar, de forma automática. Mas acreditem, você não vai querer que um EA 100% automático, contenha uma brecha, ou falha em seu funcionamento, para um sistema manual. Algumas falhas menos graves podem até passar, mas para um sistema 100% automático, isto não é admissível.

E já que a questão, é bem mais complicada de se explicar, do que possa parecer, ela será vista no próximo artigo. No anexo você terá acesso ao código até o momento presente. Se você quer de fato saber, se está ou não conseguindo acompanhar e analisar de forma adequada o conteúdo, deixo aqui um desafio. Antes de ler o próximo artigo. Você consegue ver qual a falha, que ainda existe no EA, a ponto de impedir que ele seja de fato automatizado com segurança ?!?! Vou dar uma dica: A falha esta na forma como a classe C_Manager, analisa o trabalho do EA.


Arquivos anexados |
Gestão de risco e capital utilizando Expert Advisors Gestão de risco e capital utilizando Expert Advisors
Este artigo é sobre o que você não pode ver em um relatório de backtest, o que você deve esperar usando um software de negociação automatizado, como gerenciar seu dinheiro se estiver usando expert advisors e como cobrir uma perda significativa para permanecer na atividade de negociação quando você está usando procedimentos automatizados.
Explorando a magia dos períodos de negociação com o auxílio do Frames Analyzer Explorando a magia dos períodos de negociação com o auxílio do Frames Analyzer
Bem, o Frames Analyzer é uma ferramenta para analisar quadros de otimização durante o processo de otimização de parâmetros quer seja no testador de estratégia ou fora do mesmo. Ele permite ler arquivos MQD ou bancos de dados criados após a otimização de parâmetros e compartilhar esses resultados com outros usuários da ferramenta. Ele é projetado para auxiliar a melhorar estratégias de negociação conjuntamente. Adicionalmente, é bom mencionar que quadro de otimização é um conjunto de dados que contém informações sobre as condições de mercado em um determinado momento, como preços, volumes, indicadores técnicos, entre outros, que são usados para avaliar e comparar a eficácia de diferentes estratégias de negociação.
DoEasy. Controles (Parte 24): Objeto WinForms dica DoEasy. Controles (Parte 24): Objeto WinForms dica
Neste artigo, vamos reformular a lógica de especificação dos objetos base e principal para todos os objetos WinForms da biblioteca. Vamos desenvolver também um novo objeto dica que será base e algumas classes herdeiras para indicar a possível direção do movimento da linha divisória.
Indicadores adaptativos Indicadores adaptativos
Neste artigo, exploraremos diferentes enfoques para desenvolver indicadores adaptativos. Esses indicadores se destacam pelo uso de feedback entre as entradas e saídas, o que permite que eles se adaptem de forma autônoma para processar séries temporais financeiras de forma eficiente.