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

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

MetaTrader 5Negociação | 25 janeiro 2023, 14:38
918 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior, Aprendendo a construindo um EA que opera de forma automática (Parte 11): Automação (III), expliquei como você faz para alcançar um sistema de maneira solida, a ponto de reduzir, ao máximo, alguns tipos de falhas e brechas, que podem acometer um programa, seja ele qual for.

As vezes vejo pessoas dizendo, que a chance de algo acontecer, é de uma em 500 mil, mas mesmo sendo um valor percentual bem baixo, ainda assim existe a possibilidade de que a coisa aconteça, e se você está ciente de que pode acontecer, por que não criar os meios, para caso ela aconteça, os danos, ou efeitos colaterais, sejam minimizados ao seu nível mais baixo ?!?! Por que ignorar, achando que a possibilidade do evento é baixo, e por conta disto não merece ser de alguma forma corrigido, ou evitado ?!?!

Se você esta acompanhando esta pequena sequencia de artigos, sobre como automatizar um EA, deve ter notado, que a criação do EA, a fim de ter o seu uso voltado para um formato manual, é algo bastante rápido e simples. Mas para um EA 100% automático, a coisa não é tão simples assim. Deve ter notado, que em nenhum momento, expressei que estaríamos ou teríamos, um sistema 100% infalível. E que poderia ser usado sem nenhum tipo de supervisão. Na verdade, acredito que tem ficado bem claro, justamente o contrário desta premissa, que muitos tem sobre EA automático: A de que você pode ligar ele, e deixar a coisa rolando ali, sem de fato entender, o que ele esta fazendo.

Quando falamos em EA 100% automatizado, a coisa realmente fica bastante seria e complicada. Ainda mais pelo fato, de que estaremos sempre sujeitos a erros de RUN-TIME, e temos que fazer um sistema que vá trabalhar em REAL-TIME. Estas duas coisas juntas, aliadas ao fato de que poderemos ter alguma brecha, ou falha no sistema, torna o trabalho extremamente cansativo, pelo ponto de vista de quem irá supervisionar o EA, durante o período que ele estiver em funcionamento.

Mas como programador, você deve sempre analisar alguns pontos chave, coisas que podem de fato dar algum tipo de problema, mesmo quando tudo parece estar funcionando em perfeita harmonia. Isto não é de forma alguma, procurar problemas onde eles podem não existir. Isto é o que um profissional cuidadoso e atencioso de fato irá fazer, ele irá procurar falhas, em um sistema que a primeira vista não tem falhas.

Nosso estimado EA, no atual estágio de desenvolvimento, para uso manual, e até mesmo semi automático ( usando breakeven e trailing stop ), não contem de fato uma falha do tipo: ARRASA QUARTEIRÃO ( este é um personagem dos universo DC ), mas se começarmos a usar o sistema, de uma maneira 100% automática, a coisa muda de figura, neste caso, existe sim o risco de uma falha grave e potencialmente perigosa.

No artigo anterior, deixei esta questão. Para que você tente-se perceber onde esta falha estava, e como ela poderia ser usada, a ponto de causar problemas, de modo que não poderíamos ainda automatizar em 100%, nosso EA. E então conseguiu perceber, onde a falha estava, e como ela pode vim a ser disparada ?!?! Bem, se a resposta para a questão foi NÃO. Tudo bem, faz parte, acreditem, não são todos, que de fato conseguem perceber a falha, apenas observando o código e usando ele de forma manual, ou semi automática, mas se você tentar automatizar o código. Você terá sérios problemas. E esta falha, é com certeza, a mais simples de ser observada, mas não tão simples de ser corrigida, isto para uso em um EA 100% automático.

Então para entender do que se trata, vamos dividir as coisas em tópicos, assim creio que ficará mais simples de você notar, como algo aparentemente sem importância, pode lhe causar grandes aborrecimentos.


Compreendendo o problema

O problema começa, no momento que criamos um limite de volume máximo, que o EA poderá de fato operar diariamente. Não confunda este volume máximo diário, com o volume de cada operação, aqui o que importa, neste primeiro momento, é o volume diário máximo.

Para efeitos de exemplificação, vamos supor que o volume seja de 100 vezes o volume mínimo. Ou seja, o EA poderá fazer tantas, quantas operações forem possíveis, até que este volume seja atingido. Ou pela ultima regra adicionada na classe C_Manager, que o volume não venha a ultrapassar estes 100.

Vamos entender como isto realmente acontece, para isto veja o código que habilita a negociação. Este pode ser visto abaixo:

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;
                        }

Este ponto, especificamente, evita que o volume seja ultrapassado. Mas como isto se dá de fato ?!?!

Supondo que você, ou o operador, tenha colocado o EA para trabalhar, com uma alavancagem de 3 vezes o volume mínimo necessário, e o volume máximo, isto definido no código do EA, seja de 100 vezes ( isto é feito durante a compilação do código ), nos pontos que podem ser vistos, e foram explicados, nos artigos anteriores. Então depois de 33 operações, o EA terá alcançado o numero de 99 vezes o volume mínimo negociado, ou seja, ele agora irá poder lançar apenas mais uma operação, só que neste caso, por conta da linha destacada, o operador deverá modificar o volume, para 1 vezes, assim teremos o valor de limite sendo alcançado, caso isto não seja feito, o EA não poderá lançar a operação.

A ideia é esta limitar o volume máximo, que pode ser operado, de forma que o EA, não venha a perder, e esta deve sempre ser a preocupação maior, e mais importante, um valor superior, a um dado parâmetro previamente estipulado. Já que se ele não abrir uma posição, com um volume muito acima do que o estipulado. ainda sim teremos perdas, mas estas poderão de alguma forma serem controladas.

Mas você pode pensar: Não vejo falha nenhuma neste código. De fato não existe nenhuma falha neste código, tanto que as funções que o utiliza, e estas podem ser vistas abaixo, conseguem limitar o volume negociado pelo EA, a um limite máximo estipulado.

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!IsPossible(true)) 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;
                                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));
                        }
//+------------------------------------------------------------------+

Mas então, onde esta a falha ?!?! Não estou conseguindo entender. De fato, não é algo simples de entender. Para entender o problema, você precisa pensar, em como o EA, poderá estar trabalhando, não a fim de enviar ordens. Pois isto o código da classe C_Manager, irá conseguir evitar que o EA venha a DISPARAR. O problema acontece, quando passamos a combinar tipos de ordens. Neste ponto, teremos um gatilho, a fim de disparar um problema do tipo ARRASA QUARTEIRÃO. Existem algumas formas de limitar, ou melhor dizendo, evitar a presença deste tipo de gatilho. Então vamos a elas:

  • Escolher um tipo, ou modelo de envio de ordens. Neste caso, o EA iria contar, apenas com ordens do tipo a mercado, ou do tipo pendente. Este tipo de solução, é adequado para alguns modelos de negociação automática, já que existem casos que fazer operações, única e exclusivamente a mercado, é mais adequado e mais usual, mas neste caso, teríamos o problema de que ficaríamos limitados, a alguns tipos de sistemas de negociação, e a ideia aqui, é mostrar como criar um sistema que consiga cobrir, a maior quantidade possível de casos ou modelos, sem se preocupar com o tipo de ordem que estará sendo usado.
  • Uma outra forma de evitar a presença do gatilho, é contabilizar de maneira mais profunda, o que entrará já como sendo posição, e o que irá fazer mudanças na posição ( neste caso as ordens pendentes ). Esta solução, é de certa forma a melhor escolha, mas no entanto, ela tem alguns agravantes, o que torna parte da programação complicada, já que teremos de fazer suposições de movimentação, e não é de maneira alguma, adequado fazermos suposições.
  • Uma outra maneira, e esta é que iremos implementar, é a que usa as bases do sistema já desenvolvido, de forma que iremos contabilizar o que estará sendo feito, mas não faremos suposições do que poderá ser feito, mas mesmo assim, iremos tentar fazer com que o EA, preveja de alguma maneira, o volume que será negociado durante todo o período de um dia.

Bem, tendo em vista agora o fato do problema acontecer, quando temos a combinação das ordens. Vamos entender, definitivamente, como a coisa realmente se dá. Voltando ao exemplo de uma alavancagem de 3 vezes, onde já ocorreram 33 operações, com um limite diário de 100 vezes. Tudo estaria em perfeito controle se, e somente se, o EA trabalhasse apenas com ordens a mercado. Mas por qualquer motivo, e isto não importa, temos uma ordem pendente no servidor de negociação. Mais uma vez, não importa o motivo da ordem pendente, estar no servidor, se esta ordem, venha a adicionar mais 3 unidade, ao volume mínimo assim que ela for capturada, terá ultrapassado o volume máximo estipulado no EA.

Você pode imaginar, que estas 2 unidades de volume, que ultrapassaram as 100 estipuladas, não é muito, e de fato pode não parecer muito. Mas pense no mesmo caso, onde o EA foi configurado, pelo operador para lançar um volume de 50 unidades, ele poderá executar 2 ordens a mercado, isto sem transgredir a regra dos 100. Mas depois de enviar uma ordem a mercado, ele envie uma ordem pendente de maneira a aumentar o volume da posição. Agora ele terá alcançado o volume de 100, mas por qualquer motivo. Esta ordem ainda não foi executada, já que ela é uma ordem pendente, e em um dado momento, o EA decida, que pode enviar uma outra ordem a mercado, e neste caso o volume será os mesmos 50.

Imediatamente a classe C_Manager, percebe que o EA alcançou o limite diário, já que ele terá uma mão de 100. Mas a classe C_Manager, apesar de estar ciente que existe uma ordem pendente, não sabe de fato, o que fazer com ela. Neste caso, quando a ordem for executada no servidor, o EA irá infligir a regra dos 100, passando a ter uma mão, de 150 unidade de volume, perceberão o problema ?!?! A trava que foi colocada, a um bom tempo, a fim de evitar que o EA, venha a pendurar muitas ordens no BOOK, ou venha a abrir muitas posições, com um certo volume, foi quebrada pelo simples fato de que o gatilho, que automatiza o EA, não previu que isto poderia acontecer. Houve a suposição, de que a classe C_Manager, iria conseguir segurar o EA, impedindo de que ele viesse a ultrapassar o volume máximo definido, o que não aconteceu, devido ao uso conjunto de ordens pendentes, com ordens a mercado. 

Muitos programadores, irão simplesmente contornar este problema, usando apenas e somente ordens a mercado. Outros irão usar apenas, e somente ordens pendentes. Mas isto apesar de sanar o problema, pelo menos no que diz respeito as suas causas, não faz com que ele desapareça. Fazendo com que a cada novo EA automático, que deva ser projetado, tenha que passar pelo mesmo regime de testes e analises, a fim de evitar que o gatilho apareça e seja disparado.

Apesar do problema descrito acima, ser passivo de ser visto, mesmo que você esteja utilizando o sistema de uma maneira manual. É bem menos provável, que um operador, venha de fato a cometer tal atrocidade. Já que isto, jogaria a falha na conta do operador, e não do sistema de proteção. Mas para um sistema 100% automático, isto é completamente inaceitável. E por este e outros motivos, é que você NUNCA e JAMAIS, deve deixar um EA 100% automatizado, operando sem supervisão, mesmo que você o tenha programado. Você pode ser um excelente programador, ainda assim, não é de maneira alguma, aconselhável deixá-lo ligado ali, sem que ninguém o esteja observando.

Se você acha que estou falando bobagens, veja o fato, de que existem sistema de piloto automático em aviões, que permitem ele decolar, percorrer a rota traçada, e pousar sem nenhuma intervenção humana. Mas mesmo assim, na cabine, sempre existe um piloto habilitado para operar o avião. Por que você acha que isto acontece ? Já que o piloto automático, consegue fazer todo o trabalho sem a interferência humana ?!?! A indústria não usaria fortunas, a fim de desenvolver um piloto automático, para no fim ter que treinar um piloto, para operar o avião. Isto não faria sentido, a não ser que a mesma indústria, de fato não confia-se no piloto automático. Pense um pouco a este respeito.


Corrigindo a falha

De fato, apenas mostrar a falha, não faz com que ela seja corrigida. É preciso um pouco mais do que isto. Mas o fato de conhecermos a falha, e entendermos como ela é disparada, e suas consequências no momento que ela disparar, faz com que possamos de fato, tentar gerar algum tipo de solução.

O fato de confiarmos, que a classe C_Manager, irá conseguir evitar a violação do volume, faz toda a diferença. Para resolver o problema, precisaremos fazer algumas coisas no código, logo de cara, a primeira coisa a ser feita, é adicionar uma nova variável no sistema, conforme pode ser visto no fragmento abaixo:

                struct st01
                {
                        ulong   Ticket;
                        double  SL,
                                TP,
                                PriceOpen,
                                Gap;
                        bool    EnableBreakEven,
                                IsBuy;
                        uint    Leverage;
                }m_Position, m_Pending;
                ulong   m_TicketPending;

Esta nova variável, irá nos ajudar a resolver o problema do volume, de maneira que poderemos gerar algum tipo de previsão, por conta do surgimento dela, um outro ponto do código foi riscado.

Feito isto, uma serie de mudanças irá acontecer, mas darei ênfase, apenas nas novas partes. As modificações poderão ser vistas com mais detalhes no código completo, no anexo. A primeira coisa a ser feita, é criar uma rotina para capturar os dados da ordem pendente:

inline void SetInfoPending(void)
                        {
                                ENUM_ORDER_TYPE eLocal = (ENUM_ORDER_TYPE) OrderGetInteger(ORDER_TYPE);
                                
                                m_Pending.Leverage = (uint)(OrderGetDouble(ORDER_VOLUME_CURRENT) / GetTerminalInfos().VolMinimal);
                                m_Pending.IsBuy = ((eLocal == ORDER_TYPE_BUY) || (eLocal == ORDER_TYPE_BUY_LIMIT) || (eLocal == ORDER_TYPE_BUY_STOP) || (eLocal == ORDER_TYPE_BUY_STOP_LIMIT));
                                m_Pending.PriceOpen = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Pending.SL = OrderGetDouble(ORDER_SL);
                                m_Pending.TP = OrderGetDouble(ORDER_TP);
                        }

Diferente da rotina que captura os dados da posição. Capturar os dados da ordem, é um pouco diferente. Mas a principal coisa, é com relação a saber, se estamos comprando ou vendendo, e isto é feito ao verificar a possibilidade de cada um dos tipos possíveis, que indicam uma ordem de compra. O restante do procedimento, é alto explicativo.

Precisamos de um outro novo procedimento:

                void UpdatePending(const ulong ticket)
                        {
                                if ((ticket == 0) || (ticket != m_Pending.Ticket) || (m_Pending.Ticket == 0)) return;
                                if (OrderSelect(m_Pending.Ticket)) SetInfoPending();
                        }

Este procedimento atualizar os dados, quando a ordem pendente, recebe algum tipo de atualização. Esta atualização é enviada do servidor, sendo repassado para o EA.

Para que o EA possa executar a chamada acima, precisamos adicionar um novo evento na rotina de tratamento OnTradeTransaction:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
        switch (trans.type)
        {
                case TRADE_TRANSACTION_POSITION:
                        manager.UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                        if (trans.order == trans.position) (*manager).PendingToPosition();
                        else (*manager).UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        (*manager).UpdatePending(trans.order);
                        break;
                case TRADE_TRANSACTION_REQUEST: if ((request.symbol == _Symbol) && (result.retcode == TRADE_RETCODE_DONE) && (request.magic == def_MAGIC_NUMBER)) switch (request.action)
                        {
                                case TRADE_ACTION_DEAL:
                                        (*manager).UpdatePosition(request.order);
                                        break;
                                case TRADE_ACTION_SLTP:
                                        (*manager).UpdatePosition(trans.position);
                                        break;
                                case TRADE_ACTION_REMOVE:
                                        (*manager).EraseTicketPending(request.order);
                                        break;
                        }
                        break;
        }
}

É justamente a linha em destaque acima, que efetuará a chamada a classe C_Manager.

Bem, mas vamos voltar a classe C_Manager, para continuar a implementar uma solução, para o problema do volume.

Precisamos promove uma correção do sistema, conforme esta sendo descrito nesta seção, isto para conseguirmos leva o sistema, a um nível mais adequado de segurança. A falha que acabaram por perceber, e que estava sendo ignorada, durante muito tempo, é responsável por atualizar, qual é o volume que se encontra em aberto. Isto não atrapalhava um sistema manual, mas para um automático, significa a morte. Então para sanar esta falha, foi preciso adicionar a seguinte linha no código:

inline void LoadPositionValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = PositionsTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = PositionGetTicket(c0)) == 0) continue;
                                        if (PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
                                        if (PositionGetInteger(POSITION_MAGIC) != GetMagicNumber()) continue;
                                        if ((m_bAccountHedging) && (m_TicketPending > 0))
                                        {
                                                C_Orders::ClosePosition(value);
                                                continue;
                                        }
                                        if (m_Position.Ticket > 0) SetUserError(ERR_Unknown); else
                                        {
                                                m_Position.Ticket = value;
                                                SetInfoPositions();
                                                m_StaticLeverage = m_Position.Leverage;
                                        }
                                }
                        }

Por isto, que desenvolver um sistema 100% automático, é tão complicado e difícil. Para um sistema manual ou semi - automático, a ausência desta linha indicada acima, não faz a menor diferença. Mas para um sistema automatizado, qualquer falha, por menor que seja, pode significar uma possibilidade de catástrofe. Ainda mais se a pessoa, ou operador não supervisionar o EA, ou não souber o que ele de fato está fazendo. Isto com toda a certeza, irá fazer você perder dinheiro no mercado.

A próxima coisa, é modificar as rotinas de envio de ordens e pedido a mercado. Precisamos que elas consigam retornar um valor para o chamador. Assim, ficaremos com o seguinte código inicial visto abaixo, mas já com a capacidade de informar ao chamador, o que está acontecendo:

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(true)) return bRet;
                                m_Pending.Ticket = 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);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(false)) return bRet;
                                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));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

Percebam que estou usando um teste, baseado na tentativa de verificar, se de fato o ticket, esta valido e continua valido. O motivo, é que em uma conta NETTING, você pode fechar uma posição, enviando um volume igual a da posição. Neste caso, se a posição foi fechada, precisamos remover os dados da mesma. Mas uma vez, isto serve para promover uma melhor segurança e confiabilidade para um sistema. Isto para um EA 100% automático, para um EA manual, tais coisas são desnecessárias.

A próxima coisa que precisamos, será adicionar uma forma do EA saber, qual deverá ser o volume enviado. Caso se queira inverter a mão, no sistema atual, até dá para fazer isto, no entanto será necessário 2 chamadas. Ou melhor, dois envios de requerimento ao servidor, ao invés de um único requerimento. Atualmente você precisa fechar a posição, e depois enviar o pedido de abertura de uma nova posição. Com o sistema, dando a informação do volume em aberto, o EA pode saber, qual o volume enviar.

const uint GetVolumeInPosition(void) const
                        {
                                return m_Position.Leverage;
                        }

Este singelo código acima, é o suficiente para promover isto. Mas no código da classe, não existe uma forma de realmente virarmos a mão.

Para promover isto, vamos novamente modificar as funções de envio de ordens. Mas é preciso se atentar, que isto não é de fato, algo que um EA manual ou semi automático, precisa ter. O fato de que estas mudanças, estão acontecendo, se deve ao fato, de que precisamos, destes meios para produzir um EA automatizado. Além do mais, é interessante, você colocar em alguns pontos, algum tipo de mensagem, a fim de que algum operador, que esteja supervisionando o EA, consiga saber o que o EA está de fato fazendo. Mesmo que aqui no código de demonstração, não esteja sendo feito isto, você deve seriamente pensar em fazer tal coisa. Pois ficar cego, apenas observando o gráfico na tela, não irá de fato ser o suficiente, a fim de notar algum comportamento estranho do EA.

Bem, finalmente depois de implementar todas estas mudanças, chegamos ao nosso ponto chave, que de fato irá resolver o nosso problema, que começamos a tratar desde o inicio deste artigo. Ao adicionarmos uma forma de o EA, conseguir lançar qualquer volume no servidor. Isto será apenas por meio de duas chamadas, nas quais a classe C_Manager, irá dar acesso ao EA. Poderemos corrigir o problema onde o EA, iria poder em algum tipo de cenário, acabar usando um volume negociado, maior do que o indicado no código de gerenciamento. Isto por que, agora iremos começar a prever o volume, que possivelmente, irá entrar ou sair da posição, que possa estar montada, ou sendo montada.

O novo código das chamadas, pode ser visto abaixo:

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const uint LeverageArg = 0)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                m_Pending.Ticket = 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);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type, const uint LeverageArg = 0)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                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));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

O que adicionamos no código, foi justamente este argumento daqui, mas notem que ele tem um valor default de zero. Caso você no momento de declarar a chamada da função, não indique este valor, o valor a ser utilizado, será o valor informado durante a chamada do constructor.

Mas o tratamento, não será feito aqui. Ele será feito em outro local, já que a forma de analisar o que deverá ser executado, ou permitido pela classe C_Manager, a fim de que a ordem seja enviada, é algo comum, tanto para colocar uma ordem pendente, quanto para enviar um pedido de execução a mercado. Mas como precisamos saber qual o valor que o EA estará pendido permissão para a classe C_Manager habilitar, usamos este pequeno teste, com o operador terciário, a fim de preencher, de forma adequada, o valor a ser usado no teste de habilitação. Agora vamos ver, o que precisou ser adicionado na rotina, a fim de configurar adequadamente o teste, esta pode ser vista logo abaixo:

inline bool IsPossible(const ENUM_ORDER_TYPE type, const uint Leverage)
                        {
                                int i0, i1;
                                
                                if (!CtrlTimeIsPassed()) return false;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
                                if ((m_Pending.Ticket > 0) || (Leverage > INT_MAX)) return false;
                                i0 = (int)(m_Position.Ticket == 0 ? 0 : m_Position.Leverage) * (m_Position.IsBuy ? 1 : -1);
                                i1 = i0 + ((int)(Leverage * (type == ORDER_TYPE_BUY ? 1 : -1)));
                                if (((i1 < i0) && (i1 >= 0) && (i0 > 0)) || ((i1 > i0) && (i1 <= 0) && (i0 < 0))) return true;
                                if ((m_StaticLeverage + MathAbs(i1)) > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return false;
                                }
                                
                                return true;
                        }

Temos uma coisa aqui, da qual você deverá estar atendo ao programar o mecanismo de disparo. Isto para evitar, de que os pedidos sejam negados. Se existir uma ordem pendente, ela deverá ser removida antes de qualquer pedido de modificação do volume. Caso isto não seja feito, o pedido será negado ao esbarrar nestas linha. Por que disto ?!?! É verdade, que parece ser algo absolutamente estranho. Então de fato merece uma explicação.

O motivo é que uma ordem pendente, é um volume morto. Ou melhor, não tem como você de fato saber, se o EA ,irá ou não permitir, que a ordem pendente, irá ser executada. Mas se o volume, que esta em aberto mudar, e o sistema, esteja utilizando uma ordem pendente, como uma forma de estopar a posição. É importante que esta ordem, seja removida, antes da mudança do volume. Mas de uma forma ou de outra, tal ordem seria removida, para que uma nova, contendo o volume correto, seja posicionada.

Então, nada de muito anormal no fato de que a classe C_Manager, venha a exigir, que não exista uma ordem pendente, durante a mudança de volume em aberto. Um outro motivo para isto, é que se você estiver, com uma posição comprada, e inverta a mão, você o fará, colocando uma ordem com um volume maior do que o volume em aberto. Se a ordem pendente, que era inicialmente de stop, for mantida, poderemos ter problema, no momento que a ordem for executada. O volume irá aumentar ainda mais. Assim temos mais um motivo, de que a classe, de fato venha a exigir, que não haja nenhuma ordem pendente, na troca de volume.

Espero, que tenha de fato dado para compreender tais motivos. E você não se sinta tentado em remover esta linha. Isto por conta que o EA, não estará enviando requerimentos para o servidor.

Agora temos um calculo bastante estranho. E para muitos não deve de fato fazer muito sentido. Além de um teste, ainda mais estranho. Que pode acabar lhe dando uma baita de uma dor de cabeça, se você tentar entende-lo, sem de fato compreender o calculo, que é executado antes. E no final, um outro teste, que a primeira vista não faz sentido algum.

Vamos com calma, para que todos consigam de fato entender esta completa insanidade matemática. Para facilitar, vamos ver a coisa, por meios de alguns exemplos, então bastante atenção, na leitura de cada um dos exemplos, para conseguir compreender, todo o código em destaque.

Exemplo 1:

Supondo, que não exista nenhuma posição aberta, desta forma o valor i1, será exatamente o valor absoluto, contido na variável Leverage, e este, é o caso mais simples de todos. Estaremos desta forma, abrindo uma posição, ou pendurando uma ordem no book, não importa, se a soma do valor de i1, com o acumulado pelo EA, for menor que o máximo indicado, o pedido será enviado ao servidor.

Exemplo 2:

Supondo agora, que temos uma posição vendida, com um volume X, mas não temos nenhuma ordem pendente, neste caso, poderemos ter algumas situações diferentes, descritas abaixo:

  • Se o pedido do EA, é de vender um volume Y, então o valor de i1, será igual a soma entre X e Y, neste caso, o pedido pode não passar, já que estaremos aumentando a mão;
  • Se o pedido do EA, é de comprar um volume Y, e este é menor que o volume X, o valor de i1, será igual a diferença entre X e Y, neste caso, o pedido irá passar, já que a mão estará sendo reduzida;
  • Se o pedido do EA, é de comprar um volume Y, e este é igual ao volume X, o valor de i1, será igual a zero, neste caso, o pedido irá passar, já que a posição estará sendo fechada;
  • Se o pedido do EA, é de comprar um volume Y, e este será maior que o volume X, o valor de i1, será igual a diferença entre X e Y, neste caso, o pedido poderá não passar, já que estaremos virando a mão;

A mesma coisa é valida, para o caso de uma posição comprada. No entanto, o pedido do EA, será igualmente modificado, de maneira que no final, teremos o mesmo comportamento, sendo indicado na variável i1. Agora notem que no teste, em que verificamos se a soma de i1, com o acumulado pelo EA até então. Temos uma chamada a função MathAbs, o fato de fazer isto, é por conta que em alguns casos, teremos o valor de i1, sendo negativo. Precisamos que ele seja positivo, para que o teste ocorra de forma adequada.

Mas ainda temos um ultimo problema, a ser solucionado. É justamente o fato, de quando invertemos a mão, a atualização do volume negociado, não irá se dar de maneira correta. Desta forma, para solucionar isto, precisamos fazer uma pequena mudança no sistema de analise. Esta mudança, é executada no procedimento visto abaixo:

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                uint tmp = m_Position.Leverage;
                                bool tBuy = m_Position.IsBuy;
                                
                                m_Position.Leverage = (uint)(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 (m_InfosManager.IsOrderFinish) if (m_Pending.Ticket > 0) v1 = m_Pending.PriceOpen;
                                m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_Pending.Ticket == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return (int)(tBuy == m_Position.IsBuy ? m_Position.Leverage - tmp : m_Position.Leverage);
                        }

Primeiro, armazenamos a mão em que o sistema se encontra, antes da atualização. Feito isto, não interferimos em nada, até que no final, verificamos se ocorreu alguma mudança de mão.  A ideia é verificar se continuamos comprados, ou passamos a vendidos. A mesma coisa vale ao contrário. Se a mudança aconteceu, retornaremos o volume em aberto. Caso contrário, efetuamos o calculo normalmente, a fim de saber, qual o volume atualmente exposto. Desta forma, conseguimos saber e atualizar de maneira adequada, o volume que o EA já operou.


Considerações finais

Apesar do sistema atualmente, está bem mais complicado, visto o que precisamos fazer, para garantir que não tenhamos problemas, com o sistema automatizado. Você deve ter notado, que quase não existem mensagens, sendo lançadas no terminal, a fim de que o operador, que esteja supervisionado o EA, consiga acompanhar o que ele estará fazendo. O que é algo bastante importante.

Mas como aqui, estou me propondo, a apenas e somente demonstrar, como você faz para criar um sistema 100% automatizado. Não sei, em que pontos seria mais adequado para você saber, o que esta se passando dentro do EA. Visto que para cada um, certas informações são mais pertinentes do que outras. Mas não aconselho a ninguém, simplesmente pegar o código, e lança-lo diretamente em uma conta real. Isto antes de fazer diversos testes e ajustes, de forma a saber, exatamente o que estará acontecendo.

No demais, no próximo artigo, irei mostrar, como você faz para colocar o sistema, funcionando. Isto para que o EA, seja capaz de operar de forma autônoma. Então, até que o artigo saia, e você venha a ter acesso ao que irei explicar. Procure estudar todo este artigo daqui. Tente entender, como medidas simples, podem resolver algumas questões, que muitas vezes são bem mais complicadas do que parece.

Não estou aqui, tentando mostrar, a derradeira forma de se criar um EA automático. Estou, apenas mostrando, uma entre tantas propostas possíveis, e passiveis de serem criadas e utilizadas. E também os riscos envolvidos no uso de um EA automático, de maneira que você saiba como minimizados ou no mínimo, reduzidos a um nível aceitável.

Uma última dica para quem possa estar tentando usar o EA, de maneira manual ou com alguma automação. Se ele não está permitindo enviar ordens. Dizendo que o volume está violando a regra de utilização. Tudo que você precisa fazer, é mudar o seguinte valor:

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 10);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

Por padrão, o valor estará em 10. Mas pode acontecer, especialmente no mercado de FOREX. Você precisar usar um valor maior. Muitos usam normalmente 100. Então troque o valor de 10, para um valor bem maior. Por exemplo, se você deseja usar 10 vezes o volume usual de 100, coloque o valor de 1000. Assim o código ficará, da forma como mostrado abaixo:

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 1000);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

Desta maneira, você conseguirá lançar 10 operações, com um volume de 100. Isto antes do EA bloquear o lançamento de novas ordens.

No vídeo abaixo, você pode ver o sistema funcionando, da maneira como ele está atualmente configurado.



Arquivos anexados |
Ciência de Dados e Aprendizado de Máquina (Parte 07): Regressão Polinomial Ciência de Dados e Aprendizado de Máquina (Parte 07): Regressão Polinomial
Ao contrário da regressão linear, a regressão polinomial é um modelo flexível destinado a performar melhor em tarefas que o modelo de regressão linear não poderia lidar. Vamos descobrir como fazer modelos polinomiais em MQL5 e tirar algo positivo disso.
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.
Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 13): Automação (V) Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 13): Automação (V)
Você sabe o que é um Fluxograma ? Sabe como usar ele ? Acha que fluxograma é apenas conversa de programador aprendiz ? Bem, veja este artigo e aprenda como trabalhar com fluxogramas.
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.