English Русский 中文 Español Deutsch 日本語
preview
Aprendendo a construindo um EA que opera de forma automática (Parte 03): Novas funções

Aprendendo a construindo um EA que opera de forma automática (Parte 03): Novas funções

MetaTrader 5Negociação | 25 outubro 2022, 14:19
1 430 2
Daniel Jose
Daniel Jose

Introdução

No artigo anterior,  Aprendendo a construindo um EA que opera de forma automática ( Parte 02 ) - Iniciando a programação, começamos o desenvolvimento do sistema de ordens, para ser utilizado no EA automático, no entanto, ali montamos apenas e somente uma das funções, ou procedimento que realmente precisamos.

Normalmente, um EA automático, precisa de mais algumas coisas dentro do sistema de ordens, para de fato ser montado, além do mais, existem pessoas, que gostam de colocar mais de um EA operando, claro que usando setups diferentes, mas operando o mesmo ativo.

Para contas do tipo NETTING, isto não é aconselhável ser feito. O motivo é o de que, o servidor de negociação, criar o chamado preço médio de posição. Então pode acontecer uma condição de corrida, onde um EA estará tentando vender, e outro estará tentando comprar. Isto irá fazer ambos EA dispararem, fazendo com que o seu patrimônio seja destruído em pouco tempo. Mas este tipo de coisa, a tal condição de corrida, não irá acontecer, se a conta for do tipo HEDGING. Pois neste caso um EA poderá estar vendendo, e outro poderá estar comprando. Isto sem que uma ordem, de fato cancele a outra.

Por conta disto, alguma pessoas costumam até mesmo operar, o mesmo ativo que o EA estará operando, mas para que isto não gere a tal condição de corrida, é preciso que o tipo de conta utilizada, seja uma conta HEDGING. Na duvida, você não deve jamais, colocar dois EA operando o mesmo ativo ( pelo menos na mesma corretora ), e também você não deverá efetuar operações no ativo que o EA, enquanto o EA automático estiver funcionando.

Bem feito este alerta, e dado o aviso, vamos adicionar as demais funções que realmente precisamos, em um EA automático, e isto irá cobrir mais de 90% dos casos.


Planejando e entendendo o por que precisamos de novos procedimentos.

Normalmente, quando um EA automático for fazer uma operação. Ele em grande parte dos casos, irá entrar e sair a mercado, ou seja, dificilmente um EA automático, de fato, irá colocar uma ordem no book. Mas existem condições em que colocar uma ordem no book se torna necessário, e o procedimento para fazer isto, é um dos mais complicados. Por conta disto, o artigo anterior foi dedicado, apenas a implementar este procedimento. Mas apenas e somente aquele procedimento, não é o suficiente para um EA automático. Como foi dito, em grande parte das vezes, ele irá entrar e sair a mercado. Desta forma, precisamos de no mínimo mais 2 procedimentos:

  • Um para enviar ordens de compra ou venda a mercado;
  • Um para poder modificar o preço de uma ordem.

Apenas e somente estes 2 procedimentos, aliados ao procedimento visto no artigo anterior, é que de fato você terá que ter em um EA automático. Agora vamos entender o por que apenas precisaremos adicionar estes 2, e não mais. Bem, quando um EA automático, abre uma posição, seja de venda ou compra, ele irá muitas vezes fazer isto a mercado, com um volume predefinido pelo operador.

E quando ele precisar fechar a posição, ele pode fazer isto de duas maneiras: A primeira é a mercado, fazendo uma operação na mão contrária, e no mesmo volume que que estiver em aberto, isto irá fechar a posição. É verdade que este tipo de operação, é bem eficaz no caso de uma conta do tipo NETTING. Para uma conta do tipo HEDGING, este procedimento não irá fazer com que a posição seja de fato fechada. Neste caso precisamos e uma operação, e requerimento de fechamento mesmo.

Apesar de que ao abrir uma operação na mão contrária, no mesmo volume, em uma conta HEDGING, fará com que você trave o preço. Isto não indicaria de fato, que a posição foi fechada. Apenas o preço estaria travado, não gerando lucro ou prejuízo. Então, por conta deste detalhe, iremos adicionar uma forma de o EA fechar a posição, tendo assim um terceiro procedimento precisando ser implementado. Mas lembre-se: para uma conta do tipo NETTING, você pode simplesmente enviar uma ordem a mercado, no mesmo volume, porém na mão contrária e a posição será fechada.

Vamos entender o fato de precisarmos ter um procedimento, para modificar o preço da ordem. Existem alguns modelo operacionais, onde o EA trabalha da seguinte forma: Ele abre uma posição a mercado, e logo em seguida, de forma imediata, envia uma ordem pendente, para servir como STOP LOSS. Esta ordem irá para o book de oferta, permanecendo lá por todo o tempo, até que a posição seja fechada. Neste caso, o EA não irá de fato enviar uma ordem de fechamento, seja ela uma ordem na mão contrária, seja qualquer outro tipo de requerimento, ele simplesmente irá administrar a ordem presente no book, de forma a ter uma ordem de fechamento sempre ativa.

Isto funciona muito bem, para contas do tipo NETTING, mas para contas do tipo HEDGING, este sistema não irá funcionar como o esperado, já que neste caso, a ordem no book simplesmente iria fazer o que expliquei acima, sobre a forma de fechar uma operação. Mas voltando ao ponto, este mesmo procedimento, que irá administrar a ordem presente no book, também serve para mover os pontos de take profit e stop loss, e isto em uma ordem do tipo OCO ( ordem cancela ordem ).

Normalmente um EA automático, não utiliza ordens do tipo OCO, ele simplesmente trabalha com uma ordem a mercado, e uma ordem que ficará no book, mas para o caso de contas HEDGING, este mecanismo pode ser feito usando a ordem OCO, onde apenas o ponto de stop loss, seria configurado, ou se o projetista desejar, ele pode simplesmente entrar a mercado, e manter o EA observando o mercado de alguma forma, e assim que um dado ponto, ou nível de preço for alcançado, o EA envia uma ordem de fechamento.

Tudo isto que foi explicado acima, é apenas para mostrar, que você pode ter mais de um caminho para fazer o mesmo tipo de coisa, FECHAR UMA POSIÇÃO. Abrir a posição é a parte fácil do processo, mas fechar é a parte complicada, já que você tem que levar em conta:

  • Possíveis momentos de grande volatilidade, onde as ordens podem simplesmente pular ( isto para ordens OCO ), ordens de BOOK, salvo o tipo STOP LIMIT, nunca irão pular, elas podem ser disparadas fora do ponto desejado, mas pular: NUNCA.
  • Problemas de conexão, onde o EA pode ficar um tempo sem poder acessar o servidor;
  • Problemas envolvidos com liquidez, onde uma ordem pode ficar ali, esperando ser executada, mas o volume não é suficiente para que ela de fato seja executada;
  • E o pior de todos: o EA poder entrar em uma condição de corrida, onde ele simplesmente dispara, começando a executar ordens a esmo;

Todos este pontos, devem ser pensados e observados ao se criar um EA automático, existem outros pontos, como o fato de alguns programadores adicionarem horário para o EA de fato poder operar. Quanto a este ponto, eu vou ser bastante sincero e honesto. Isto é algo completamente INUTIL e uma completa BURRICE. Apesar de ser mostrado, em outro artigo, como fazer isto, não é algo que aconselho, e vou explicar o motivo ...

Pense no seguinte: Você não sabe operar no mercado, arruma um EA para fazer isto, de forma automática, coloca um horário para ele operar. Beleza, você agora pensa: Posso ir fazer outra coisa, cuidar de outras atividades ... ( Barulho de buzina ) ... ERRADO. Você NUNCA, e vou repetir, VOCÊ NUNCA deve deixar um EA, mesmo automático, OPERANDO SEM SUPERVISÃO. NUNCA MESMO. Enquanto ele estiver ligado, você, ou alguém de sua confiança deve ficar ali. Observando o que ele está fazendo.

Deixar o EA ligado sem supervisão é chamar problema, por conta disto adicionar métodos, ou gatilhos de disparo por horário, onde o EA irá começar e encerrar suas atividade, é o maior sinal de burrice que alguém pode desejar colocar em um EA automático. Por favor, não faça isto. Se quer que o EA opere para você. Tudo bem, ligue-o e fique ali observando, quando tiver que sair desligue ele, e vá fazer o que você precisa fazer, mas não deixe ele ali ligado, operando sabe DEUS como. Não faça isto, pois os resultados podem lhe machucar muito.

Implementando as funções necessárias 

Por conta que o procedimento, para executar uma ordem a mercado, se parecer em muito com o procedimento usado para enviar uma ordem pendente, se faz possível criar um procedimento comum, de forma a preencher todos os campos que irão ter o mesmo tipo de preenchimento, ficando assim apenas e somente os pontos, que são pendentes de cada tipo de requerimento, sendo preenchidos localmente. Então vamos ver como ficou esta função de preenchimento comum, ela pode ser vista logo abaixo:

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

Observem que tudo isto, presente no procedimento comum, estava presente na função de criação de uma ordem pendente, vista no artigo anterior, está agora aqui, mas também adicionei um coisinha extra, que não existia antes, mas pode ser bastante útil, caso você esteja trabalhando com uma conta HEDGING, ou pretende criar um EA automático, onde ele irá apenas observar as ordens que ele criou, o chamado numero mágico, normalmente não faço uso deste numero, mas se você for fazer isto, já terá o sistema implementado de forma a dar suporte a ele.

Com isto, vamos ver como ficou o novo procedimento responsável, por enviar uma ordem pendente, este procedimento é visto no código a seguir:

                ulong CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                        {
                                double  bid, ask, Desloc;                               
                                
                                Price = AdjustPrice(Price);
                                bid = SymbolInfoDouble(_Symbol, (m_Infos.PlotLast ? SYMBOL_LAST : SYMBOL_BID));
                                ask = (m_Infos.PlotLast ? bid : SymbolInfoDouble(_Symbol, SYMBOL_ASK));
                                CommonData(type, AdjustPrice(Price), FinanceStop, FinanceTake, Leverage, IsDayTrade);
                                m_TradeRequest.action   = TRADE_ACTION_PENDING;
                                m_TradeRequest.type     = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : 
                                                                                    (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));                              
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.action           = TRADE_ACTION_PENDING;
                                m_TradeRequest.symbol           = _Symbol;
                                m_TradeRequest.volume           = NormalizeDouble(m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - 1)), m_Infos.nDigits);
                                m_TradeRequest.type             = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : 
                                                                                            (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));
                                m_TradeRequest.price            = NormalizeDouble(Price, m_Infos.nDigits);
                                Desloc = FinanceToPoints(FinanceStop, Leverage);
                                m_TradeRequest.sl               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), m_Infos.nDigits);
                                Desloc = FinanceToPoints(FinanceTake, Leverage);
                                m_TradeRequest.tp               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), m_Infos.nDigits);
                                m_TradeRequest.type_time        = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
                                m_TradeRequest.type_filling     = ORDER_FILLING_RETURN;
                                m_TradeRequest.deviation        = 1000;
                                m_TradeRequest.comment          = "Order Generated by Experts Advisor.";
                                
                                return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
                        };

Todas as parte riscada acima, foram retiradas do código, já que estes campos, estão sendo preenchidos por esta função comum, tudo que realmente precisamos fazer, é ajustar estes dois valores, e o sistema de ordens irá continuar a criar uma ordem pendente, como foi visto no artigo anterior.

Bem, então vamos ver o que precisamos de fato programar, para ter o sistema de ordens, capaz de enviar requerimentos de execução a mercado, o código necessário é visto abaixo:

                ulong ToMarket(const ENUM_ORDER_TYPE type, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                        {
                                CommonData(type, SymbolInfoDouble(_Symbol, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID)), FinanceStop, FinanceTake, Leverage, IsDayTrade);
                                m_TradeRequest.action   = TRADE_ACTION_DEAL;
                                m_TradeRequest.type     = type;

                                return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
                        };

Notem o quando é simples, fazer a codificação de uma ordem a mercado, tudo que precisamos mudar, frente ao que é uma ordem pendente, são estes dois pontos. Desta forma, garantimos que o servidor irá sempre receber dados compatíveis, já que a única mudança, será no tipo de requisição.

A forma de trabalhar, tanto analisando o retorno, quanto a forma de chamar os procedimentos de posicionar uma ordem pendente, ou executar uma operação a mercado, é praticamente igual, a única real diferença, para quem irá chamar os procedimentos, é que quando for executar uma ordem a mercado, você não precisará informa o preço, pois a classe irá preencher este valor da forma correta, já na ordem pendente, você precisará informa o valor, fora isto, todo o trabalha será igual.

Agora vamos ver uma coisa, que mudou no sistema. Já que foi adicionado um valor para ser utilizado, como numero mágico, precisamos fazer com que a classe receba este valor, e assim possa utilizar ele. Isto é, e deve ser feito, no constructor da classe, assim, agora a nossa classe passará a ter que receber um parâmetro, na sua chamada. Veja só como ficou o constructor no código abaixo:

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

Vamos entender o que está acontecendo neste código acima. Quando declaramos, um valor default, como esta sendo feito aqui, não precisarmos de fato informar ele, no momento que a classe for ser criada, tornando assim, este constructor como se fosse um constructor default ( aqueles nos quais não recebem nenhum tipo de argumento ).

Mas, se você desejar de fato forçar o usuário da classe, entenda-se programador, a informar, quais são os valores a serem usados, durante a fase onde a classe é criada. Você deverá retirar o valor do parâmetro, assim quando o compilador tentar gerar o código, ele irá notar que estará faltando alguma coisa, e irá pedir para que você diga, quais são os valores a ser utilizados. Mas isto somente irá funcionar, se a classe, não contiver um outro constructor, e este for um constructor default ... Isto é um pequeno detalhe, que deixa muita gente sem de fato entender, por que as vezes, temos que informar alguns valores, e em outros casos, não.

Veja como as vezes, a programação pode ser bastante interessante. Em muitos casos, o que de fato fazemos, é tentar criar uma solução, mas utilizando o menor dos esforços, reduzindo assim a quantidade de coisas a realmente serem programadas, e testadas. No entanto, não terminamos o nosso trabalho na classe C_Orders, lembre-se de que ainda precisamos criar, uma outra função, obrigatoriamente, e uma que pode ser opcional, mas ainda assim, será criada por conta do fato de que quando se está operando, em uma conta HEDGING, as coisas devem ser feitas de uma maneira diferente, de uma conta NETTING. Então vamos ao próximo procedimento a ser criado, e este pode ser visto logo abaixo:

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

Este procedimento acima, é extremamente importante, mas muito importante mesmo. Talvez até mais importante, do que o próximo que iremos ver depois. O motivo é que este procedimento, é responsável por manipular as posições de preço, seja as que estão no book de oferta, no caso de uma ordem pendente, seja os limites de uma posição em aberto. Esta função acima é tão poderosa, que pode criar, ou remover os limites de uma ordem ou posição. Mas para que você não fique a ver navios, e não entenda como ela funciona, vamos destrinchar o seu código interno, desta forma você irá entender como trabalhar, e utilizar esta função de uma forma correta.

Para facilitar a explicação, e por consequência o entendimento, vamos dividir a coisa em partes, então preste bastante atenção, para não ficar perdido na explicação.

Vamos começar entendendo o seguinte: Quando você envia uma pedido, seja de colocação de uma ordem pendente no book, seja o envio de uma ordem a mercado, você terá como retorno um valor, e se não tiver ocorrido nenhum tipo de erro na requisição, este valor será diferente de zero. Só que este valor, não é um valor que você deve de fato ignorar, o valor retornado pelas funções de criação da ordem, ou de uma posição, devem ser armazenados com bastante carinho e cuidado, pois ele representa o ticket da ordem ou posição.

Este ticket, que é o bilhete que serve como um tipo de passe, irá lhe dar diversas possibilidades, entre elas a de poder manipular as ordens, ou posições, que estão no servidor de mercado. Então aquele valor, que você obtém quando executa uma operação a mercado, ou tenta colocar uma ordem no book, e é retornado pelas funções, que fazem tal procedimento, serve na verdade, quando for diferente de zero, como um passaporte para que o EA, possa se comunicar com o servidor, a fim de conseguir trabalhar, ou manipular os preços, usando justamente este valor, o tal bilhete ( ticket ).

Cada ordem, ou posição, tem um bilhete único, então cuide para não perder este numero de maneira alguma, e não tente criá-lo a esmo, existem forma de você o obter, se você não souber qual o valor dele, ou o tiver perdido. Mas de uma forma, ou de outra, isto irá tomar tempo do EA, por conta deste motivo, você deve evitar perder este numero.

Pois bem, vamos primeiramente supor que tenhamos uma ordem, e queremos remover, ou modificar os valores de limite ( take profit ou stop loss ) da ordem. Não confunda ordem com posição. Quando digo ordem estou me referindo a uma possível e futura posição, normalmente as ordens ficam no book, já a posição é quando de fato uma ordem já foi executada. Neste caso, você irá informar o ticket da ordem, e os novos valores de preço, preste atenção a isto, agora são valores de preço, você não vai mais informar o financeiro. O valor esperado agora é o valor de face, ou valor que você visualiza no gráfico, por conta disto, que você não deve trabalhar de qualquer forma nesta função, caso contrário, a sua requisição será negada.

Entendido isto, você pode colocar, virtualmente, qualquer valor de take profit, e stop loss, em uma ordem, virtualmente, pois na verdade, isto não é de todo correto, se você estiver com uma ordem de compra, o valor de stop loss, não poderá ser maior do que o preço de abertura da posição, e o valor de take profit, da mesma forma, não poderá ser menor que o preço de abertura da posição, se você tentar fazer isto, o servidor irá devolver um erro.

Agora muita atenção, ao seguinte fato: o valor de take profit, e stop loss, na ordem, tem que seguir estes critérios, mas no caso de uma posição, o valor de stop loss, no caso de uma posição comprada, pode sim ser maior que o preço de abertura da posição, e neste caso o stop loss, passa a ser um stop gain, ou seja, você já irá ter algum lucro, caso a ordem de stop seja disparada, mas depois iremos falar mais sobre isto. No momento entenda que, no caso da ordem o stop loss, tem que ficar do lado contrário ao que você espera, ou seja na compra, o stop loss tem que ficar abaixo do preço de abertura, na venda, ele tem que ficar acima. Já no caso de uma posição, o stop loss pode ficar em qualquer lugar.

A explicação acima, cobre apenas e somente os limites da ordem, ou posição, mas se você observar a função acima, irá notar que podemos também, manipular o preço de abertura da posição, enquanto ela ainda, é uma ordem. Agora vem um detalhe, quando você for fazer isto, terá que mover junto a ordem de stop loss, e a ordem de take profit, se você não fizer isto, em algum momento, o servidor irá negar a sua solicitação de mudança do preço de abertura.

Para entender de fato esta questão, vamos criar um pequeno programinha EA, para testar estes casos. Crie um novo arquivo EA, e depois copie, e cole o código abaixo, neste arquivo que será aberto, feito isto, compile o código, e jogue ele em um gráfico, e vamos a explicação:

#property copyright "Daniel Jose"
#property description "This one is an automatic Expert Advisor"
#property description "for demonstration. To understand how to"
#property description "develop yours in order to use a particular"
#property description "operational, see the articles where there"
#property description "is an explanation of how to proceed."
#property version   "1.03"
#property link      "https://www.mql5.com/pt/articles/11226"
//+------------------------------------------------------------------+
#include <Generic Auto Trader\C_Orders.mqh>
//+------------------------------------------------------------------+
C_Orders *orders;
//+------------------------------------------------------------------+
input int       user01   = 1;           //Fator de alavancagem
input int       user02   = 100;         //Take Profit ( FINANCEIRO )
input int       user03   = 75;          //Stop Loss ( FINANCEIRO )
input bool      user04   = true;        //Day Trade ?
input double    user05   = 84.00;       //Preço de entrada...
//+------------------------------------------------------------------+
int OnInit()
{
        orders = new C_Orders(1234456789);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        delete orders;
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
#define KEY_UP                  38
#define KEY_DOWN                40
#define KEY_NUM_1               97
#define KEY_NUM_2               98
#define KEY_NUM_3               99
#define KEY_NUM_7               103
#define KEY_NUM_8               104
#define KEY_NUM_9               105

        static ulong sticket = 0;
        int key = (int)lparam;
        
        switch (id)
        {
                case CHARTEVENT_KEYDOWN:
                        switch (key)
                        {
                                case KEY_UP:
                                        if (sticket == 0)
                                                sticket = (*orders).CreateOrder(ORDER_TYPE_BUY, user05, user03, user02, user01, user04);
                                        break;
                                case KEY_DOWN:
                                        if (sticket == 0)
                                                sticket = (*orders).CreateOrder(ORDER_TYPE_SELL, user05, user03, user02, user01, user04);
                                        break;
                                case KEY_NUM_1:
                                case KEY_NUM_7:
                                        if (sticket > 0) ModifyStop(key == KEY_NUM_7, sticket);
                                        break;
                                case KEY_NUM_2:
                                case KEY_NUM_8:
                                        if (sticket > 0) ModifyPrice(key == KEY_NUM_8, sticket);
                                        break;
                                case KEY_NUM_3:
                                case KEY_NUM_9:
                                        if (sticket > 0) ModifyTake(key == KEY_NUM_9, sticket);
                                        break;
                        }
                        break;
        }
}
//+------------------------------------------------------------------+
void ModifyPrice(bool IsUp, const ulong ticket)
{
        double p, s, t;
        
        if (!OrderSelect(ticket)) return;
        p = OrderGetDouble(ORDER_PRICE_OPEN) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        s = OrderGetDouble(ORDER_SL);
        t = OrderGetDouble(ORDER_TP);
        (*orders).ModifyPricePoints(ticket, p, s, t);
}
//+------------------------------------------------------------------+
void ModifyTake(bool IsUp, const ulong ticket)
{
        double p, s, t;
        
        if (!OrderSelect(ticket)) return;
        p = OrderGetDouble(ORDER_PRICE_OPEN);
        s = OrderGetDouble(ORDER_SL);
        t = OrderGetDouble(ORDER_TP) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        (*orders).ModifyPricePoints(ticket, p, s, t);
}
//+------------------------------------------------------------------+
void ModifyStop(bool IsUp, const ulong ticket)
{
        double p, s, t;
        
        if (!OrderSelect(ticket)) return;
        p = OrderGetDouble(ORDER_PRICE_OPEN);
        s = OrderGetDouble(ORDER_SL) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        t = OrderGetDouble(ORDER_TP);
        (*orders).ModifyPricePoints(ticket, p, s, t);
}
//+------------------------------------------------------------------+

Não se preocupe, se este código lhe parece complicado a primeira vista, ele é apenas para demonstrar uma coisa, que pode acontecer, e você precisa entender, para no futuro, poder usar isto ao seu favor.

Este código em si, se parece muito com o visto no artigo anterior, mas aqui podemos fazer algo mais, podemos manipular as ordens, modificando o valor de take profit, stop loss, e ponto de abertura. O único inconveniente, ainda presente, no código é que, uma vez você tendo colocado uma ordem no gráfico, não adianta simplesmente remove-la, para poder colocar outra, você pode até deixar a ordem no gráfico, isto não importa, mas ao usar este EA, para criar uma ordem pendente, e por enquanto só funciona para ordens pendentes, você irá conseguir usando o teclado numérico, aquele no canto direito do teclado físico, mudar o ponto de preço que informa onde a ordem estará.

Isto é feito, usando este manipulador de eventos. Note que cada uma das teclas, serve para uma coisa, como subir o stop loss, ou descer o preço da ordem, faça isto, observando na aba de negociação da caixa de ferramenta o resultado da manipulação, fazendo isto, você vai aprender bastante coisa.

Para quem talvez não se sinta totalmente confiante, em colocar este código para executar na plataforma, apesar de ele ser inofensivo, salvo o fato de você ser totalmente imprudente, pode ver no video abaixo, a demonstração do que este código acima faz na prática.



Demonstração do código acima.

O que você deve ter notado, é que quando movemos o stop loss, ou o take profit, temos uma movimentação adequada, e até mesmo esperada, mas caso movamos o preço de abertura, tanto o take profit, quanto o stop loss, ficam parados ... mas por que disto acontecer ?!?! O motivo disto, é que para o servidor de negociação, o que você na verdade esta fazendo, é movendo um ordem que possivelmente será o stop de uma outra operação, que pode estar aberta.

Lembra que lá no inicio deste artigo, foi dito, que uma das formas de estopar uma operação aberta, seria colocar uma ordem no book, e ir movendo ela aos poucos ?!?! Pois bem, é justamente isto que estaria acontecendo aqui, ou seja, para o servidor, o preço a ser movimentado é apenas, e somente aquele que esta sendo informado. Ele não vê a ordem OCO, como sendo uma entidade só. Para ele a ordem OCO, seria como se fossem pontos de preço diferente. Mas uma vez, que um dos limites, tenham sido atingidos, ele, servidor, irá enviar um evento que destruirá o preço, onde a posição se encontra aberta. Ao fazer isto ambas ordens, take profit e stop loss, irão deixar de existir, pois o ticket a qual elas estão atreladas, irá ser removido do sistema, ficando disponível para um uso futuro.

Então você precisará fazer com, que caso existe uma ordem OCO, sendo gerada pelo EA, que ao mover o preço de abertura, tanto o take profit, quanto o stop loss, sejam movidos junto, para isto, bastaria fazer a seguinte modificação no código acima:

void ModifyPrice(bool IsUp, const ulong ticket)
{
        double p, s, t;
        
        if (!OrderSelect(ticket)) return;
        p = OrderGetDouble(ORDER_PRICE_OPEN) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        s = OrderGetDouble(ORDER_SL) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        t = OrderGetDouble(ORDER_TP) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        (*orders).ModifyPricePoints(ticket, p, s, t);
}

Agora sim, adicionando estes pontos no código, assim que o preço de abertura se mover, tanto o preço de take profit, quanto o de stop loss, também irão se mover, mantendo sempre a mesma distância do ponto de abertura.

A coisa toda funciona, da mesma forma para o caso de posições, mas neste caso não podemos, obviamente, mover o preço de abertura da posição, mas para mover o take profit, e o stop loss, usamos o mesmo método, e a mesma função, mas neste caso, apenas o requerimento será diferente, como mostrado no fragmento abaixo:

                bool ModifyPricePoints(const ulong ticket, const double Price, const double PriceStop, const double PriceTake)
                        {
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.symbol   = _Symbol;
                                if (OrderSelect(ticket))
                                {
// ... Código para movimentar ordens ...
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        m_TradeRequest.action   = TRADE_ACTION_SLTP;
                                        m_TradeRequest.position = ticket;
                                        m_TradeRequest.tp       = NormalizeDouble(AdjustPrice(PriceTake), m_Infos.nDigits);
                                        m_TradeRequest.sl       = NormalizeDouble(AdjustPrice(PriceStop), m_Infos.nDigits);
                                }else return false;
                                ToServer();
                                
                                return (_LastError == ERR_SUCCESS);
                        };

Ou seja, é desta forma, que você consegue fazer o tal breakeven, e trailing stop, usando justamente o fragmento mostrado acima, a única coisa que é preciso fazer, será observar o valor que servirá de gatilho para disparar o movimento, que todos conhecem como sendo breakeven, uma vez que o gatilho seja disparado, você captura o preço de abertura da posição, e coloca ele como sendo o preço de stop loss, dai executa a chamada de modificação do preço da posição, e terá como resultado o breakeven.

Já o trailing stop, funciona quase da mesma forma, só que neste caso, o movimento irá se dar quando o gatilho, seja ele uma distancia que o preço se moveu, ou outra coisa qualquer, disparar, quando isto acontecer, você pegará o novo valor, que será utilizado, como sendo o stop loss, e chama a função acima, ou seja, é algo extremamente simples e fácil de fazer.

Esta questão, dos gatilhos, tanto de breakeven, quanto de trailing stop, irei falar em mais detalhes depois, quando for mostrar a forma de desenvolve os gatilhos, para que o EA consiga operar de forma automática, mas quem vem estudando, sendo um entusiasta, já deve estar pensando, e bolando formas de fazer estes gatilhos, antes mesmo de entramos nos detalhes, se este é o seu caso: Parabéns. Você já esta no caminho certo.

Agora vamos voltar no procedimento de modificação de preços, pois ali temos algo que ainda não comentei, e é importante que você saiba, como e por que daquilo esta ali. Para facilitar a explicação, vamos nos atentar no fragmento do código, que é visto logo abaixo:

                bool ModifyPricePoints(const ulong ticket, const double Price, const double PriceStop, const double PriceTake)
                        {
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.symbol   = _Symbol;
                                if (OrderSelect(ticket))
                                {
                                        m_TradeRequest.action = (Price > 0 ? TRADE_ACTION_MODIFY : TRADE_ACTION_REMOVE);
                                        m_TradeRequest.order  = ticket;
                                        if (Price > 0)
                                        {
                                                m_TradeRequest.price      = NormalizeDouble(AdjustPrice(Price), m_Infos.nDigits);
                                                m_TradeRequest.sl         = NormalizeDouble(AdjustPrice(PriceStop), m_Infos.nDigits);
                                                m_TradeRequest.tp         = NormalizeDouble(AdjustPrice(PriceTake), m_Infos.nDigits);
                                                m_TradeRequest.type_time  = (ENUM_ORDER_TYPE_TIME)OrderGetInteger(ORDER_TYPE_TIME) ;
                                                m_TradeRequest.expiration = 0;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
// Parte responsável pelo trabalho em posições ...
                                }else return false;
                                ToServer();
                                
                                return (_LastError == ERR_SUCCESS);
                        };

Existem momentos, em que pode ser necessário remover uma ordem do book, ou seja, que ela seja fechada ou excluída. E existe o perigo, de que durante a execução, você tenha feito algo, que irá fazer, com que o EA gere um preço igual a zero. Acredite, acontece, e é algo bastante corriqueiro se for parar para pensar a respeito, principalmente, no caso de um EA automático. Então o EA envia uma pedido, para que o preço da ordem seja modificado, mas por conta de um erro, este preço é enviado, como sendo zero.

Nestes casos, o servidor de negociação irá negar a ordem, mas o EA pode ficar ali, insistindo de forma quase louca, de que o preço de abertura deverá ser zero, e se algo não for feito, ele poderá entrar em loop, o que é algo extremamente desagradável em uma conta de produção ( conta real ). Para evitar que o EA fique ali, insistindo, com algo que não irá ser aceito pelo servidor, incluí a seguinte ideia: Caso o EA envie um preço de abertura de posição, que seja igual a zero, a ordem deverá ser fechada pelo servidor. E é justamente isto, que este código daqui faz.

Informa ao servidor de negociação, que a ordem informada deverá ser fechada, deixando de permanecer lá no book. Quando isto acontecer, a ordem deixará de estar ali, viva no book, e você poderá ser informado a este respeito, mas aqui não inclui um código para isto, já que existem outros usos igualmente uteis para este tipo de coisa, não somente evitar que o EA fique insistindo com algo, mas simplesmente para remover uma ordem do book.

Mas ainda não acabamos o artigo, ainda falta um último procedimento, se bem que este é quase opcional, mas em alguns casos, ele é de fato usado, então já que estou aqui, abrindo a caixa preta de como o sistema de ordens funciona, precisamos ver mais um procedimento, e este é visto logo abaixo:

                bool ClosePosition(const ulong ticket, const uint partial = 0)
                        {
                                double v1 = partial * m_Infos.VolMinimal, Vol;
                                bool IsBuy;
                                
                                if (!PositionSelectByTicket(ticket)) return false;
                                IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
                                Vol = PositionGetDouble(POSITION_VOLUME);
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.action    = TRADE_ACTION_DEAL;
                                m_TradeRequest.type      = (IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY);
                                m_TradeRequest.price     = SymbolInfoDouble(_Symbol, (IsBuy ? SYMBOL_BID : SYMBOL_ASK));
                                m_TradeRequest.position  = ticket;
                                m_TradeRequest.symbol    = _Symbol;
                                m_TradeRequest.volume    = ((v1 == 0) || (v1 > Vol) ? Vol : v1);
                                m_TradeRequest.deviation = 1000;
                                ToServer();
                                
                                return (_LastError == ERR_SUCCESS);
                        };

Este procedimento acima, faz muita gente sonhar alto, imaginando coisas, e vendo estrelas. Ao olhar este procedimento, você deve estar achando, mas ele serve para fechar uma posição. Por que alguém iria ficar sonhando, e delirando ao ver este procedimento ?!?!

Calma, meu caro leitor, você ainda não entendeu, esta com pré-conceito ao ver o nome do procedimento. Mas vamos nos aprofundar um pouco mais, vamos analisar o código, e entender o por que muitos ficam sonhando alto. Observe que aqui, neste procedimento, existem alguns cálculos, mas por que ele tem cálculos ?!?! O motivo é para permitir fazer as conhecidas saídas parciais. Vamos entender como isto realmente se dá. Suponhamos que você esteja com uma posição aberta, com um volume de 300, pois bem, se o volume mínimo negociável for de 100, você poderá sair com 100, 200 ou 300.

Mas para isto você deverá informar um valor, e este é por padrão zero, ou seja, ele diz a função, que a posição será encerrada completamente, mas isto somente acontecerá, se, e somente se, você o mantiver como padrão. Mas tem um detalhe aqui: Você NÃO. E novamente NÃO, deverá informar o valor do volume, você deverá informar a desalavancagem que será feita, ou seja, se você esta com um volume de 300, e o volume mínimo é de 100, isto significa que você está alavancado em 3x, para fazer uma parcial, neste caso, você deverá informa um valor que pode ser 1 e 2, se for informado 0, 3, ou um valor maior que a sua alavancagem, a posição será totalmente fechada, isto é informado neste ponto aqui.

No entanto, existem algumas alternativas a isto, por exemplo, no caso especifico da B3 ( Bolsa do Brasil ), os ativos ( Ações de empresas ), são negociados em lotes de 100, mas existe o mercado fracionário, onde você pode negociar de 1 em 1, neste caso, se você, estiver como o EA sendo executado no fracionário, o valor a ser informado, no mesmo exemplo dos 300, poderá ir de 1 até 299, e mesmo assim a posição não será totalmente fechada, ficando ali um resíduo em aberto.

Agora conseguiram entender, o por que muitos ficam sonhando, ao olhar este procedimento. A forma de se trabalhar aqui, irá depender, é claro do ativo, do tipo de mercado, e qual o interesse do operador. Mas de uma forma ou de outra, caso você esteja trabalhando, em uma conta do tipo HEDGING irá com toda a certeza, precisar desta função acima, sem ela, as posições irão ficar ali, acumulando, tomando tempo, e recurso do EA para analisar as coisas, que já poderiam ter sido fechadas.

Para terminar este artigo, fechando completamente a questão sobre o sistema de ordens, vamos ver como deveria ser o código do EA, para remover aquela limitação, de uma vez criado uma ordem, não poder lançar outras, e o problema de ele não poder manipular os dados, de uma posição. Para sanar estes problemas, teremos que fazer algumas mudanças no código dele, mas ao fazer isto, você já conseguirá se divertir, e fazer bem mais coisas. Talvez isto lhe deixe um pouco mais empolgado com o que se pode fazer, com um código relativamente simples, e que esta sendo exposto de uma forma possa ser entendido, até mesmo por quem tem pouco conhecimento em programação.

Bem, para corrigir, em parte, o EA, vamos ter que mexer no código responsável por tratar dos eventos do gráfico, o novo código é visto abaixo:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
#define KEY_UP                  38
#define KEY_DOWN                40
#define KEY_NUM_1               97
#define KEY_NUM_2               98
#define KEY_NUM_3               99
#define KEY_NUM_7               103
#define KEY_NUM_8               104
#define KEY_NUM_9               105

        static ulong sticket = 0;
        ulong ul0;
        int key = (int)lparam;
        
        switch (id)
        {
                case CHARTEVENT_KEYDOWN:
                        switch (key)
                        {
                                case KEY_UP:
                                        if (sticket == 0)
                                                sticket = (*orders).CreateOrder(ORDER_TYPE_BUY, user05, user03, user02, user01, user04);
                                        ul0 = (*orders).CreateOrder(ORDER_TYPE_BUY, user05, user03, user02, user01, user04);
                                        sticket = (ul0 > 0 ? ul0 : sticket);
                                        break;
                                case KEY_DOWN:
                                        if (sticket == 0)
                                                sticket = (*orders).CreateOrder(ORDER_TYPE_SELL, user05, user03, user02, user01, user04);
                                        ul0 = (*orders).CreateOrder(ORDER_TYPE_SELL, user05, user03, user02, user01, user04);
                                        sticket = (ul0 > 0 ? ul0 : sticket);
                                        break;
                                case KEY_NUM_1:
                                case KEY_NUM_7:
                                        if (sticket > 0) ModifyStop(key == KEY_NUM_7, sticket);
                                        break;
                                case KEY_NUM_2:
                                case KEY_NUM_8:
                                        if (sticket > 0) ModifyPrice(key == KEY_NUM_8, sticket);
                                        break;
                                case KEY_NUM_3:
                                case KEY_NUM_9:
                                        if (sticket > 0) ModifyTake(key == KEY_NUM_9, sticket);
                                        break;
                        }
                        break;
        }
}

O que foi feito para modificar, e assim dar um pouco mais de possibilidades, foi remover as partes riscadas, e adicionar uma nova variável, esta irá receber o valor do ticket, retornado pela classe de ordens, caso o valor seja diferente de zero, o novo ticket será armazenado em uma variável estática, esta irá guardar o valor por todo o tempo, até que um novo valor entre no lugar do antigo. Bem, isto já irá permitir você manipular bem mais coisas, e se por um acaso uma ordem for executada, e você não tenha sobrescrevido o valor, abrindo uma nova ordem, você poderá manipular as informações de limite da ordem.

Agora fica como tarefa extra, para ver se você de fato aprendeu como trabalhar com o sistema de ordens, e isto até o próximo artigo vim a ser publicado. E a resposta dada. Tente fazer com que o sistema de ordens, abra uma posição a mercado, e utilize o valor do ticket, de forma a poder manipular os limites da posição aberta a mercado. Fazer isto, sem que ver o próximo artigo, de fato irá servir para que você mostre a si mesmo, se esta ou não conseguindo acompanhar as explicações.

Muito bem, mas agora vamos ver como ficou o código, que faz os preços mudarem, já que o código do preço de abertura, não irá sofre mudanças, no caso de uma ordem se tornar posição, podemos pular este caso, mas vamos ver o do take profit, que pode ser visto logo abaixo:

void ModifyTake(bool IsUp, const ulong ticket)
{
        double p = 0, s, t;
        
        if (!OrderSelect(ticket)) return;
        if (OrderSelect(ticket))
        {
                p = OrderGetDouble(ORDER_PRICE_OPEN);
                s = OrderGetDouble(ORDER_SL);
                t = OrderGetDouble(ORDER_TP) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        }else if (PositionSelectByTicket(ticket))
        {
                s = PositionGetDouble(POSITION_SL);
                t = PositionGetDouble(POSITION_TP) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        }else return;
        (*orders).ModifyPricePoints(ticket, p, s, t);
}

O código riscado deixou de existir, e por conta disto ganhamos a possibilidade de um novo código, que permite manipular o valor do take profit, de uma posição. A mesma coisa é válida para o código do stop loss, que é visto logo abaixo:

void ModifyStop(bool IsUp, const ulong ticket)
{
        double p = 0, s, t;
        
        if (!OrderSelect(ticket)) return;
        if (OrderSelect(ticket))
        {
                p = OrderGetDouble(ORDER_PRICE_OPEN);
                s = OrderGetDouble(ORDER_SL) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
                t = OrderGetDouble(ORDER_TP);
        }else if (PositionSelectByTicket(ticket))
        {
                s = PositionGetDouble(POSITION_SL) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
                t = PositionGetDouble(POSITION_TP);
        }else return;
        (*orders).ModifyPricePoints(ticket, p, s, t);
}

Use estes EA como meio de aprendizado, use e abuse dele nas contas demo, explore ao máximo o que está sendo mostrado aqui nestes 3 primeiros artigos, pois neste exato momento, considero o sistema de ordens concluído, no próximo artigo irei mostrar, como você faz para inicializar o EA, a fim de capturar algumas informações, os problemas e possíveis soluções para estes, envolvidos nesta inicialização, mas estas questões não fazem realmente parte do sistema de ordens, este termina aqui, já que foi implementado tudo que realmente precisamos, para tornar o EA automático.


Conclusão

Apesar do que foi visto nestes 3 primeiros artigos. Ainda estamos muito longe de termos de fato um EA automático. Tudo que foi visto até o momento, são detalhes que muitos, as vezes ignoram ou não sabem. O fato de você não conhecer tais detalhe é algo perigoso. Mas ignorar tais detalhes é bastante prejudicial. No entanto ainda estamos apenas começando a falar sobre sistema automatizados e os perigos de usá-los em o devido conhecimento.

No anexo, você terá acesso ao código visto até o momento, de forma que você poderá estudar, e aprender como fazer a coisa funcionar. Então espero você no próximo artigo.

Arquivos anexados |
Últimos Comentários | Ir para discussão (2)
filipetagli
filipetagli | 31 dez 2022 em 11:55
Esse código três apresentou erros na minha compilação. O dois funcionou normalmente. Se tiver alguma idéia do que estou esquecendo de olhar agradeço.
Daniel Jose
Daniel Jose | 1 jan 2023 em 13:42
filipetagli #:
Esse código três apresentou erros na minha compilação. O dois funcionou normalmente. Se tiver alguma idéia do que estou esquecendo de olhar agradeço.

Você precisa mostrar detalhadamente o que está acontecendo, caso contrário não tem como lhe orientar ...👀👀

Como desenvolver um sistema de negociação baseado no indicador Índice de Força Como desenvolver um sistema de negociação baseado no indicador Índice de Força
Seja bem-vindo a este novo artigo em nossa série sobre como desenvolver um sistema de negociação com base no indicador técnico mais popular. Neste artigo, nós aprenderemos sobre um novo indicador técnico e como criar um sistema de negociação usando o indicador Índice de Força.
Redes neurais de maneira fácil (Parte 19): Regras de associação usando MQL5 Redes neurais de maneira fácil (Parte 19): Regras de associação usando MQL5
Continuamos o tópico de busca de regras de associação. No artigo anterior, consideramos os aspectos teóricos desse tipo de problema. No artigo de hoje, ensinarei a implementação do método FP-Growth usando MQL5. Também vamos testá-la com dados reais.
Indicador CCI. Atualizações e novos recursos Indicador CCI. Atualizações e novos recursos
Neste artigo, considerarei a possibilidade de atualizar o indicador CCI. Além disso, eu apresentarei uma modificação do indicador.
Aprendendo a construindo um EA que opera de forma automática (Parte 02): Iniciando a programação Aprendendo a construindo um EA que opera de forma automática (Parte 02): Iniciando a programação
Aprenda como criar um EA que opera de forma automática, isto de forma simples e o mais seguro possível. No artigo anterior apresentei as primeiras etapas das quais você precisa compreender, antes mesmo de iniciar a construção de um EA, que opere de forma automática, ali mostrei.