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

Aprendendo a construindo um EA que opera de forma automática (Parte 06): Tipos de contas (I)

MetaTrader 5Negociação | 17 novembro 2022, 09:23
721 1
Daniel Jose
Daniel Jose

Introdução

No artigo anterior,  Aprendendo a construindo um EA que opera de forma automática (Parte 05: Gatilhos manuais (II), finalizamos um EA, bastante simples, e com um nível bem elevado de robustez, e confiabilidade. Este pode ser utilizado para operar qualquer ativo. Seja no mercado de forex, seja no mercado de bolsa. O mesmo não tem nenhum tipo de automação, sendo operado de forma completamente manual.

Nosso EA, até o momento consegue trabalhar, em qualquer tipo de situação, mas para torná-lo automatizado, ele não está adequado, precisamos fazer algumas coisas. Isto deve ser feito, antes mesmo de adicionar o breakeven, ou o trailing stop. Já que, se fizermos isto antes, adicionar estes mecanismos, será preciso desfazer algumas coisas depois, que serão feitas apenas, adicionar estas automações neste momento. Então seguiremos um caminho um pouco diferente, onde primeiro procuraremos criar um EA genérico.


O nascimento da classe C_Manager

Esta classe, C_Manager, é que será a nossa camada de isolamento, entre o EA e o sistema de ordens. Ao mesmo tempo, esta classe irá começar a promover, algum tipo de automação para nosso EA, deixando ele, com algum tipo de gatilho automático, para fazer, algumas coisas.

Então vamos ver, como esta classe, irá começar a ser produzida. Seu código inicial é visto logo abaixo:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Orders.mqh"
//+------------------------------------------------------------------+
class C_Manager : private C_Orders
{
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage;
                        bool    IsDayTrade;
                }m_InfosManager;
        public  :
//+------------------------------------------------------------------+
                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade)
                        :C_Orders(magic)
                        {
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                        }
//+------------------------------------------------------------------+
                ~C_Manager() { }
//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                C_Orders::CreateOrder(type, Price, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                C_Orders::ToMarket(type, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+
};

O que você esta vendo, no código acima, é o esqueleto básico, do que iremos de fato montar. Notem que agora, o EA, não irá mais precisar informar, certos detalhes durante o envio das ordens. Tudo será gerenciado, nesta classe C_Manager, de tanto, que já na chamada do constructor, passamos todos os valores, que iremos precisar, a fim de criar uma ordem, ou fazer um pedido de abertura de uma posição, a mercado.

Mas gostaria que você note-se um fato, a classe C_Manager, está herdando a classe C_Orders, mas no entanto, esta herança esta sendo feita de forma privativa. Por que disto ?!?! O motivo é segurança, e aumento na confiabilidade, já que ao colocar esta classe daqui, como sendo um tipo de sindico, precisamos que ela, seja o único ponto de comunicação, entre o EA e a classe responsável por enviar as ordens.

Pelo fato de que C_Manager, irá controlar o acesso ao sistema de ordens, podendo ela mesma enviar, fechar ou modificar, ordens e posições, iremos dar ao EA, algum tipo de meio de acessar o sistema de ordens. Mas este acesso, será bem limitado. Então, eis aqui as duas funções iniciais, que o EA poderá usar, para acesso ao sistema de ordens. Vejam que elas, são bem mais limitadas, do que as existentes na classe C_Orders, mas em compensação, são mais seguras.

Para que você entenda, o nível da coisa que estou querendo mostrar, compare o código do EA, presente no artigo anterior, com este daqui, e isto só de ter feito, a criação da classe C_Manager. Para facilitar, veja abaixo o que aconteceu, em duas funções, presentes no EA.

int OnInit()
{
        manager = new C_Orders(def_MAGIC_NUMBER);
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);

        return INIT_SUCCEEDED;
}

O código anterior, foi riscado e no lugar dele, entrou um novo código, é bem verdade, que ele tem um maior numero de parâmetros. Mas isto é apenas um mero detalhe, mas a parte principal, e com certeza a que ao meu ver torna a coisa mais arriscada, é vista a seguir:

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

As partes riscadas, foram removidas do mapa, em compensação, veja que o novo código, que surgiu, é consideravelmente mais simples. Mas não somente isto, o fato de usarmos uma classe, fazendo o trabalho de sindico, garante que o EA, irá usar exatamente os parâmetros definidos, na inicialização da classe. Desta forma, não corremos o risco de colocar, um tipo de informação errada, ou equivocada, em uma das chamadas, concentramos tudo em um só lugar, na classe C_Manager, que agora esta fazendo papel de intermediar, a conversa entre o EA e a classe C_Orders. Isto sim, aumenta bastante o nível de segurança, e confiabilidade do código, presente no EA.


Conta NETTING, conta EXCHANGE ou Conta HEDGING ... eis a questão

Apesar de muita gente ignorar este fato, ou desconhecer este fato, a verdade é que existe um problema, bastante grave aqui. Um problema, que pode fazer um EA funcionar, bem ou mal, e o problema, é o tipo de conta usada. A maior parte dos operadores, ou usuários da plataforma MetaTrader 5, não fazem a mínima ideia, de que existem 3 tipos de contas, no mercado. Mas para quem deseja desenvolver um EA, que opere de forma genérica, e de maneira totalmente automática, este conhecimento é muito importante.

De uma forma geral, para fins de artigo, irei mencionar apenas duas, durante esta sequencia, a conta NETTING ou HEDGING. E o motivo é simples, uma conta NETTING, tem o funcionamento, na visão do EA, igual uma conta EXCHANGE. Então, não irei ficar falando de 3 modelos, mas apenas de 2.

Mesmo um EA, com uma automação simples. Como o gatilho de breakeven, ou de trailing stop. O fato de ele estar, em uma conta NETTING, faz com que o trabalho dele, deva ser totalmente, e completamente diferente, de um EA, que esteja em uma conta HEDGING. E o motivo é a forma como o servidor de negociação trabalha. Em uma conta NETTING, o servidor de negociação, irá criar um preço médio, conforme você vai aumentando, ou reduzindo, a sua posição.

Mas o servidor, de uma conta HEDGING, não faz isto. Para ele cada posição, pode ser totalmente diferente de outra, ou seja, neste caso você poderá ter uma posição vendida, ao mesmo tempo que uma comprada, e isto no mesmo ativo, ao mesmo tempo. Mas este tipo de coisa, não irá acontecer, em uma conta NETTING. Se for tentado fazer isto, o servidor irá finalizar a posição, fazendo com que ela seja fechada.

Por conta disto. ao usar um EA. devemos procurar saber se ele é voltado para ser usado. em uma conta NETTING. ou uma conta HEDGING. Pois a forma de trabalhar é totalmente diferente, mas isto somente se aplica. a casos de um EA automático, ou com algum nível de automação. Para um EA manual, isto não faz a mínima diferença.

Por conta deste fato, não podemos criar, nenhum nível de automação, sem gerar alguma dificuldade em termos, seja de programação, seja de usabilidade.

Para criar tal coisa, precisamos padronizar um pouco as coisas. Ou seja, precisamos fazer com que o EA, consiga trabalhar em qualquer tipo de conta, de uma maneira bastante padronizada. É verdade que isto irá reduzir, e muito o que o EA, irá poder fazer. Isto em termos de um grau de liberdade. Mas para um EA automático, não queremos, e tal pouco, é desejável que ele tenha, assim um grau grande de liberdade. O melhor é que ele seja bem, mas bem, contido, a fim de sempre andar na linha, se desviar um pouco, ele deve ser banido, ou no mínimo colocado de castigo.

A maneira de padronizar as coisas, é fazer com que uma conta HEDGING, trabalhe de uma forma parecida com uma conta NETTING, isto no ponto de vista do EA. Sei que parece confuso, e complicado fazer isto, mas na verdade, o que iremos de fato fazer, é permitir que o EA, tenha apenas, e somente, uma posição em aberto, e tenha apenas, e somente, uma ordem pendente. Ou seja, ele será extremamente limitado, não podendo fazer qualquer coisa, além disto.

Desta forma, irá nascer o seguinte código na classe C_Manager:

class C_Manager : private C_Orders
{
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage;
                        bool    IsDayTrade;
                }m_InfosManager;
//---
                struct st01
                {
                        ulong   Ticket;
                        double  SL,
                                TP,
                                PriceOpen,
                                Gap;
                        bool    EnableBreakEven,
                                IsBuy;
                        int     Leverage;
                }m_Position;
                ulong           m_TicketPending;
                bool            m_bAccountHedging;
		double		m_Trigger;

Nesta estrutura, estamos criando tudo que iremos precisar, para que poder trabalhar, com a posição que esteja aberta. Inclusive, já temos nela, algumas coisas relacionadas ao primeiro nível de automação, que será mostrado futuramente, que é o breakeven, e o trailing stop. Já a ordem pendente, irá ser armazenada de uma forma mais simples, apenas o ticket da mesma. Mas se no futuro, precisarmos de mais dados, eles serão implementados, mas no momento, iremos usa apenas isto. E temos uma outra variável, que nos diz, se estamos usando, uma conta HEDGING ou NETTING, isto será útil para algumas coisas. E de praxe, ainda foi adicionada mais uma variável, que não será usada neste primeiro momento, mas será necessária quando formos criar, o gatilho do breakeven, e do Trailing stop.

Bem, começamos assim a padronizar as coisas, logo depois de ter feito isto, podemos fazer a mudança no constructor da classe, este pode ser visto abaixo:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                        }

Estou mostrando o código aos pouco, para quem não tem costume, ou conhecimento sobre programação, espero não estar sendo muito chato com relação a isto, já que quero, que todos consigam entender o que esta sendo feito. Mas vamos as explicações aqui. Estas linhas estão dizendo ao compilador, que queremos que estas variáveis, sejam inicializadas antes mesmo do código do constructor, realmente começar a ser executado. Pense nisto da seguinte forma: Quando uma variável é criada, normalmente o compilador, atribui a ela um valor igual a zero.

Usando estas linhas estamos dizendo, ao compilador, qual deverá ser o valor atribuído a uma dada variável, assim que ela for criada. Neste ponto, zeramos todo o conteúdo da estrutura, desta forma, fazemos uso de menos código, e obtemos um resultado mais rápido. Aqui estamos anotando, o fato de que estaremos lidando, com uma conta do tipo HEDGING, caso seja necessário saber disto, em algum momento, temos esta variável, que nos dirá. Já aqui, estamos informando no terminal, qual o tipo de conta que foi encontrada. Isto para que o usuário, fique ciente do tipo, caso ele não saiba o tipo.

Mas antes de vermos tais procedimentos. Vamos pensar uma coisa: E se o EA encontrar mais de uma posição ( conta HEDGING ), ou mais de uma ordem pendente. Oque irá acontecer ?!?! Bem, neste caso teremos um erro, já que o EA não poderá trabalhar, com mais de uma posição, e uma ordem, para isto, criamos a seguinte enumeração no código:

class C_Manager : private C_Orders
{
        enum eErrUser {ERR_Unknown};
        private :

// ... Resto do código ...

};

Aqui iremos utilizar uma enumeração, pelo motivo que é mais simples adicionar novos códigos de erros nela. Para isto, bastará você dar um novo nome, que o compilador irá gerar, assim o valor para o código, sem que você corra o risco de criar, um valor duplicado por desatenção, no momento da digitação. Atenção ao fato de que a enumeração, está antes cada clausula privativa, então ela será publica, e queremos que seja assim. Mas para acessá-la, fora da classe, precisaremos usar um pequeno detalhe, a fim de informar ao compilador, qual é a enumeração correta. Isto é muito bom e útil quando você deseja usar enumerações relacionadas a uma classe especifica. Agora vamos ver os procedimentos, que irão carregar algo que pode ter ficado no gráfico, e o EA tem que reaver, antes de realmente começar a trabalhar. A primeira é vista logo abaixo:

inline void LoadOrderValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = OrdersTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = OrderGetTicket(c0)) == 0) continue;
                                        if (OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
                                        if (OrderGetInteger(ORDER_MAGIC) != GetMagicNumber()) continue;
                                        if (m_TicketPending > 0) SetUserError(ERR_Unknown); else m_TicketPending = value;
                                }
                        }

Vamos entender como este código funciona, e por que ele tem esta aparência peculiar. Aqui usamos um laço para fazer a leitura, de qualquer ordem que esteja pendente no book, a função OrdersTotal, irá retornar um valor maior que zero, caso exista alguma ordem, mas o index sempre irá começar em zero, isto vem do C / C++. Mas temos duas condições para finalizar o laço, a primeira, é que o valor da variável c0, seja menor que zero, e a segunda condição, é que _LastError, seja diferente de ERR_SUCESS, o que indica que aconteceu algum tipo de falha no EA.

Desta forma, entramos no laço. e capturamos a primeira ordem, cujo index é indicado pela variável c0, OrderGetTicket, irá retornar o valor do ticket da ordem ou zero. Caso seja zero, retornaremos ao laço, mas agora iremos subtrair uma unidade da variável c0.

Como OrderGetTicket, carrega os valores da ordem, e o sistema não diferencia entre elas, precisamos então filtrar as coisas, de forma que o EA, fique sabendo apenas sobre a sua ordem especifica. Então, o primeiro filtro, que usamos é o nome do ativo, para isto testamos o nome do ativo contido na ordem, com o nome do ativo, no qual o EA está sendo executado. Se eles forem diferentes, a ordens será ignorada, e retornaremos para procurar uma outra, caso exista mais alguma.

O próximo filtro na sequencia, é o numero mágico, já que pode haver mais ordens de outros EA, ou mesmo ordens penduradas pelo usuário no book. A forma de saber que a ordem, foi ou não postada lá pelo EA, que esta sendo inicializado. É por meio deste numero, que cada EA deverá ter, sendo que eles devem ser diferentes, pelo motivo que você acaba de ficar sabendo. Caso o numero mágico, seja diferente do numero mágico, usado pelo EA, esta ordem deverá ser ignorada, então voltaremos ao inicio, a procura de uma nova ordem.

Agora chegamos na encruzilhada. Caso o EA tenha encontrado uma ordem, que ele havia postado, antes de ser removido, por qualquer motivo do gráfico ( depois iremos ver quais podem ser estes motivos ) ele terá na sua memória, ou melhor dizendo, na variável que indica o ticket da ordem pendente, um valor que será diferente de zero. Então, se uma segunda ordem, for encontrada, isto será considerando um erro, e assim esta função, irá usar a enumeração para dizer que ocorreu um erro.

Aqui estou usando um valor genérico, ERR_Unknown, mas você pode criar um valor para especificar qual foi o erro, e este será indicado na variável _LastError. Quem faz este trabalho de colocar o valor do erro na variável _LastError, é justamente esta função SetUserError. Mas se tudo estiver bem, e a variável que conterá o ticket da ordem, estiver com o valor zero, o valor do ticket encontrado, e que passou pelos filtros, será armazenado na variável m_TicketPending, para poder ser usado depois. Com isto fechamos a explicação deste procedimento, vamos ver o próximo, que é responsável por procurar alguma posição aberta, este tem o seu código que pode ser visto abaixo:

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_Position.Ticket > 0) SetUserError(ERR_Unknown); else
					{
						m_Position.Ticket = value;
						SetInfoPositions();
					}
                                }
                        }

Tudo que foi dito, para o código anterior, vale também para este daqui. A diferença é que antes, estávamos manipulando ordens, e agora posições. Mas a lógica é a mesma. Até que temos, a seguinte chamada: SetInfoPositions, que irá armazenar,  ajustar, e tratar os dados mais recentes da posição. E para isto, iremos usar o código que pode ser visto logo abaixo:

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);
                                m_Position.EnableBreakEven = (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return m_Position.Leverage - tmp;
                        }

Este código, é bem interessante, no que tange trabalhar os dados mais recentes de uma posição. Mas cuidado, antes de chamar ele, será preciso fazer a atualização dos dados da posição, via uma das seguintes chamadas: PositionGetTicketPositionSelectPositionGetSymbol e PositionSelectByTicket. Mas de uma forma geral, aqui temos tudo sendo inicializado, ou ajustado conforme é necessário. O fato de ter colocado este código a parte, é por que iremos utilizá-lo em outros pontos, de forma a atualizar os dados da posição, sempre que for necessário.

Basicamente é isto, mas agora precisamos fazer uma nova modificação no constructor da classe, para assim o EA, conseguir ser totalmente inicializado, da forma correta. Tudo que precisamos fazer, é adicionar as chamadas vistas acima. Então o código final do constructor, ficará sendo que que é mostrado logo abaixo:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                                LoadPositionValid();
                                LoadOrderValid();
                                if (_LastError == ERR_SUCCESS)
                                {
                                        szInfo = "Successful upload...";
                                        szInfo += StringFormat("%s", (m_Position.Ticket > 0 ? "\nTicket Position: " + (string)m_Position.Ticket : ""));
                                        szInfo += StringFormat("%s", (m_TicketPending > 0 ? "\nTicket Order: " + (string)m_TicketPending : ""));
                                        Print(szInfo);
                                }
                        }

Estas duas linhas, irá fazer com que o EA consiga carregar a posição que estiver aberta, e a ordem que permanece pendente. Agora prestem atenção a seguinte fato, um constructor, não pode retornar nenhum tipo de valor, pois isto é um erro. No entanto, precisamos de algum meio, a fim de informar ao restante do código, que aconteceu alguma coisa de errado, e isto no constructor.

Cada sistema de programação, nos fornece, ou nos força a criar tais meio, mas no entanto, aqui no MQL5, temos uma forma bastante prática, que é usar a variável _LastError para isto. Se tudo estiver em perfeito estado na inicialização, você verá esta mensagem daqui no terminal. Caso o sistema tenha encontrado alguma posição, você também verá esta mensagem, indicando qual é o ticket da posição que o EA irá observar. Se foi encontrado alguma ordem, você verá esta mensagem daqui também, informando o ticket da ordem pendente encontrada pelo EA.

Este valor de _LastError, irá ser usado como uma forma de verificar se o EA, saiu da linha em algum ponto. Então pode ser interessante você coloca, mais tipos de mensagens na enumeração de erro, a fim de ter uma indicação mais precisa, do que aconteceu de fato.


Um problema na conta HEDGING para um EA automático

Apesar de tudo parecer lindo e maravilhoso, principalmente para quem esta começando a aprender a programação. A coisa esta sim sendo encaminhada, de forma a dar o nível de robustez, que desejamos e precisamos que um EA automático tenha. Mas ainda assim, temos um problema em potencial no sistema, quando usado em contas HEDGING. E isto antes mesmo de ver, o código responsável por permitir que o EA possa enviar pedidos, ou requerimentos para o servidor. O problema esta no fato, de que diferente de uma conta NETTING, onde o servidor vai criando um preço médio conforme a posição vai sendo modificada, seja por entradas de novos pedidos a mercado, seja por execução de ordens pendentes. Na conta HEDGING, não existe tão controle, assim tão fácil e oriundo do servidor.

O problema da conta HEDGING, neste caso, é o fato de que poderemos ter uma posição aberta, e se for permitido haver uma ordem pendente, esta quando executada, não irá mudar a posição aberta, não diretamente. O que pode acontecer, e irá de fato acontecer, é que uma nova posição será aberta, quando a ordem pendente for executada. Esta nova posição aberta, poderá travar o preço, de forma que não tenhamos nem lucro ou prejuízo. Mas também pode aumentar a nossa posição geral. Isto é fato, e acontecerá assim que a ordem for executada.

Este detalhe, que existe na conta HEDGING, nos força a tomar uma outra medida, podemos impedir que o EA, envie pedidos a mercado, caso uma posição esteja aberta, ou uma ordem pendente, já esteja no book. Isto é simples de fazer, com base no código que estou mostrando. Mas o problema é que durante a inicialização, pode ser que o EA, acabe encontrando em uma conta HEDGING, uma posição aberta, e uma ordem pendente. Isto não é problema para a conta NETTING, como expliquei acima.

Mas para este caso, o que o EA deverá fazer ?!?! Lembre-se, a classe C_Manager, que irá controlar o EA, não permite que ele tenha duas posições abertas, ou duas ordens pendentes. Neste caso, precisaremos remover a ordem pendente, ou fechar a posição aberta. De uma forma ou de outra, algo deve ser feito, pois não devemos permitir isto, em um EA automático, e volto a frisar isto, um EA automático, nunca deverá trabalhar com mais de uma posição aberta, ao mesmo tempo, ou mais que uma ordem pendente, ao mesmo tempo. Em um EA manual, a conversa é outra.

Desta forma, você deverá decidir qual deverá ser a medida a ser tomada. Fechar a posição, ou remover a ordem pendente ? No caso de desejar fechar a posição, a classe C_Orders, já oferece o procedimento para isto. Mas no caso de remover a ordem pendente, ainda não temos nenhum procedimento, presente na classe C_Orders. Então precisamos implementar, um meio de que isto possa ser feito. Vamos começar por este ponto, dando ao sistema, uma forma de remover posições pendentes. Para fazer isto, precisaremos adicionar um novo código no sistema, e este pode ser visto logo abaixo:

class C_Orders : protected C_Terminal
{
        protected:
//+------------------------------------------------------------------+
inline const ulong GetMagicNumber(void) const { return m_MagicNumber; }
//+------------------------------------------------------------------+
                void RemoveOrderPendent(const ulong ticket)
                        {
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.action   = TRADE_ACTION_REMOVE;
                                m_TradeRequest.order    = ticket;
                                ToServer();
                        };

// ... Restante do código da classe 

}

Agora atenção a alguns detalhe neste código: Primeiro, ele esta sendo colocado dentro da clausula protected, ou seja, mesmo que você tente usar diretamente a classe C_Orders no EA, você não terá acesso a este código, pelo motivo que já explique em um artigo anterior, desta mesma serie. O segundo ponto, é que ele serve para remover ordens pendentes, ele não serve para fechar um posição, ou modificar a ordem pendente, apenas para remover ela.

Com este código já implementado, na classe C_Orders, podemos voltar para a classe C_Manager, e implementar o sistema de forma a impedir que o EA automático tenha, em caso de estar em uma conta HEDGING, uma ordem pendente, quando ele já tem uma posição aberta. Mas se você deseja que ele feche a posição, e mantenha a ordem pendente, bastará fazer as mudanças no código, de forma a ter este tipo de comportamento. Mas a única coisa que não poderá acontecer, é de o EA automático, em uma conta HEDGING, ter uma posição aberta, e uma ordem pendente. Isto não pode ser tolerado.

Um detalhe: Se você estiver em uma conta HEDGING, poderá usar mais de um EA ao mesmo tempo, no mesmo ativo. Caso isto aconteça, o fato de um EA, ter uma posição aberta, e outro ter uma ordem pendente, não irá afetar em nada o funcionamento de ambos EA. Neste caso, eles são independentes, e pode sim, acontecer de termos mais de uma posição aberta, ou mais de uma ordem pendente, no mesmo ativo. O que não pode, é um único EA, se encontrar neste tipo de situação. Isto para EA automáticos. Vou ficar frisando isto, até que você consiga colocar isto na cabeça.

Se você observou bem, no código do constructor, primeiro fazemos a captura de uma posição, para somente depois capturarmos a ordem. Desta forma fica mais simples, fazer a remoção da ordem, caso seja necessário. No entanto, se você deseja fechar a posição, e manter a ordem, bastará inverter isto no constructor, de maneira que primeiro será capturado as ordens, e depois será capturada a posição, e caso exista a necessidade, ela será fechada. Mas vamos ver como faremos, no caso que estou demonstrando. Capturar a posição, e depois caso seja necessário, remover qualquer ordem pendente encontrada. O código para isto é visto logo abaixo:

inline void LoadOrderValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = OrdersTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = OrderGetTicket(c0)) == 0) continue;
                                        if (OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
                                        if (OrderGetInteger(ORDER_MAGIC) != GetMagicNumber()) continue;
                                        if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                        {
                                                RemoveOrderPendent(value);
                                                continue;
                                        }
                                        if (m_TicketPending > 0) SetUserError(ERR_Unknown); else m_TicketPending = value;
                                }
                        }

A mudança que precisamos fazer, é adicionar este código em destaque, notem como é extremamente simples de ser feito, a remoção de uma ordem pendente, mas aqui, ela somente será removida. Caso tenhamos alguma posição em aberto, e a conta seja do tipo HEDGING, teremos uma situação que neste caso, a ordem pendente será removida. Mas se a conta for do tipo NETTING, ou não houver uma posição aberta, este código não será executado, permitindo assim o EA trabalhar tranquilamente.

Mas já que você talvez deseje fazer o fechamento da posição, e manter a ordem pendente, vamos ver como deverá ser o código neste caso. Não será preciso mudar o código de carregamento da ordem pendente, pode deixa-lo como mostrado acima. Mas será preciso fazer algumas mudanças, e a primeira é adicionar o seguinte código no procedimento que carrega a posição, conforme pode ser visto abaixo:

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))
                                        {
                                                ClosePosition(value);
                                                continue;
                                        }
                                        if (m_Position.Ticket > 0) SetUserError(ERR_Unknown); else
					{
						m_Position.Ticket = value;
						SetInfoPositions();
					}
                                }
                        }

Adicionando este código, que esta em destaque, você conseguirá fechar a posição que estiver aberta, mantendo assim a ordem pendente. mas tem um detalhe. Para que você consiga manter a ordem pendente, e fechar a posição, em uma conta HEDGING, você precisará mudar um ponto no código do constructor, de forma que agora ele ficará da seguinte forma:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                                LoadOrderValid();
                                LoadPositionValid();
                                if (_LastError == ERR_SUCCESS)
                                {
                                        szInfo = "Successful upload...";
                                        szInfo += StringFormat("%s", (m_Position.Ticket > 0 ? "\nTicket Position: " + (string)m_Position.Ticket + "\n" : ""));
                                        szInfo += StringFormat("%s", (m_TicketPending > 0 ? "\nTicket Order: " + (string)m_TicketPending : ""));
                                        Print(szInfo);
                                }
                        }

Você pode achar que não houve nenhuma mudança, mas compare este código, com o que é visto no final do tópico anterior, você irá notar, que esta parte em destaque se encontra diferente. Neste caso, a posição será fechada, no original a ordem será removida. Esta é a graça na programação, as vezes um simples detalhe, faz toda a diferença. Aqui apenas mudamos a ordem, como o código é executado. Mas no entanto, o resultado é completamente diferente.

Em tese, e que fique bem claro isto, em tese, este código visto até aqui, não tem nenhum tipo de problema. E irá executar perfeitamente, mas e quero ressaltar isto. Mas pode ser que o servidor de negociação, reporte algum erro, não por conta de que existe de fato alguma coisa errada, no que rege os requerimentos, que estão sendo enviados. Mas por conta de algum tipo de interação, que pode acontecer e assim a variável _LastError, irá conter um valor indicando a presença de algum tipo de falha.

Algumas falhas podem ser admitidas, já que não são criticas, e outras não podem ser de forma alguma toleradas. Então, caso você tenha compreendido esta diferença, e aceite esta ideia. Você pode, em determinados pontos do código, adicionar uma chamada a ResetLastError, a fim de evitar que o EA, venha a ser expulso do gráfico, por ter gerado algum tipo de erro, que muito provavelmente, não foi ele que gerou, mas foi a interação errônea, que pode ter ocorrido entre o EA e o servidor de negociação.

Neste primeiro momento, não irei mostrar em que pontos você pode adicionar tais chamadas. Isto para que você, não se sinta tentando a fazer estas chamadas, de forma indiscriminada, em qualquer ponto, ou que venha a ignorar, o valor contido na variável _LastError. Pois fazer isto, iria quebrar toda a tese de construção de um EA forte, robusto, confiável, mas principalmente automático.


Conclusão

Neste artigo, apresentei as primeiras bases, para você começar a pensar em como automatizar um EA, de uma forma segura, estável e robusta. Programar um EA, para ser usado de forma automática, não é uma tarefa que pessoas com pouco experiência, irão de fato conseguir fazer, de forma a não gerar algum tipo de problema. Isto é algo extremamente complicado, e que exige muito cuidado por parte do programador.

No próximo artigo, iremos ver mais algumas coisas, que precisam ser implementadas, de maneira que possamos ter um EA automatizado. Mas não qualquer um. Iremos de fato produzir um, que seja, o mais seguro possível, de ser colocado em um gráfico. Mas sempre, com as devidas cautelas, e medidas corretas, a fim de não causar, nenhum estrago em nosso, suado patrimônio.


Últimos Comentários | Ir para discussão (1)
arikmt5
arikmt5 | 14 mar 2023 em 08:39
could you share the source code? many thanks.
Redes neurais de maneira fácil (Parte 24): Melhorando a ferramenta para transferência de aprendizado Redes neurais de maneira fácil (Parte 24): Melhorando a ferramenta para transferência de aprendizado
No último artigo, elaboramos uma ferramenta para criar e editar a arquitetura de redes neurais. E hoje quero convidá-lo a continuar trabalhando nela, para torná-la mais amigável. De certa forma, ao fazer isso, estamos nos afastando um pouco do nosso tópico. Mas convenhamos que a organização do espaço de trabalho desempenha um papel importante na obtenção do resultado.
DoEasy. Controles (Parte 14): Novo algoritmo para nomear elementos gráficos. Continuando o trabalho no objeto WinForms TabControl DoEasy. Controles (Parte 14): Novo algoritmo para nomear elementos gráficos. Continuando o trabalho no objeto WinForms TabControl
Neste artigo, elaboraremos um novo algoritmo para nomear todos os elementos gráficos que permitem criar gráficos personalizados, e continuaremos desenvolvendo o objeto WinForms TabControl.
DoEasy. Controles (Parte 15): Objeto WinForms TabControl - múltiplas fileiras de cabeçalhos de guias, métodos de manuseio de guias DoEasy. Controles (Parte 15): Objeto WinForms TabControl - múltiplas fileiras de cabeçalhos de guias, métodos de manuseio de guias
Neste artigo, continuaremos trabalhando no objeto WinForm TabControl, e para tal criaremos a classe do objeto-campo de guia, tornaremos possível colocar cabeçalhos de guias em várias linhas e adicionaremos métodos para trabalhar com as guias do objeto.
Como desenvolver um sistema de negociação baseado no indicador Bear's Power Como desenvolver um sistema de negociação baseado no indicador Bear's Power
Bem-vindo a um novo artigo em nossa série sobre como desenvolver um sistema de negociação com base nos indicadores técnicos mais populares, aqui está um novo artigo sobre como aprender a desenvolver um sistema de negociação pelo indicador técnico Bear's Power.