English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
preview
Desenvolvendo um EA de negociação do zero (Parte 11): Sistema CROSS ORDER

Desenvolvendo um EA de negociação do zero (Parte 11): Sistema CROSS ORDER

MetaTrader 5Negociação | 21 abril 2022, 08:52
1 487 3
Daniel Jose
Daniel Jose

Introdução

Existem uma classe de ativos que dificulta muito a vida dos operadores, estes são os ativos de contrato futuro, e por que eles dificultam a vida do operador ?!?! Por ele ter uma data de vencimento, quando depois disto um novo contrato é criado e passa a ser negociado. Basicamente quando um ativo vence, você deve finalizar todo e qualquer estudo nele, salvar tudo como um template e importar este template para o novo contrato de forma a manter os estudos. Isto é algo meio rotineiro por parte de quem negocia tais ativos, mas mesmo eles, contratos futuros, tem algum tipo de histórico e usando este histórico podemos manter os estudos de forma continua.

Mas operadores profissionais gostam de analisar certas informações passadas, e isto nos força a ter um segundo gráfico na tela. Mas isto se torna desnecessário quando usamos os meios corretos para contornar este tipo de coisa, e um destes recursos é o uso de um sistema cross order.


Planejamento

No artigo que iniciou esta serie, (Desenvolvendo um EA de negociação do zero) eu falei sobre este tipo de ordem, mas lá eu não promovi a sua implementação, isto por que eu desejava mostrar uma serie de coisas e iniciar um sistema mais completo para uso na plataforma MetaTrader 5, mas aqui irei mostrar como implementar aquela funcionalidade.

Para entender a motivação para se criar tal funcionalidade, veja as duas imagens abaixo:

           

A imagem da esquerda é um típico contrato futuro no caso o MINI DOLAR FUTURO, ele se iniciou a poucos dias, como pode ser notado no gráfico, mas mesmo assim o gráfico da direita reflete este contrato, e contém dados extras que na verdade são valores dos contratos já vencidos, então o gráfico da direita é um gráfico de histórico, quando vamos analisar pontos de suporte ou resistência antigas damos preferência em usar justamente o gráfico da direita. Mas existe um problema quando vamos operar, e isto pode ser visto abaixo:

          

Vejam que no CHART TRADE é indicado o ativo que esta sendo negociado, mesmo quando usamos o histórico, o CHART TRADE informa que o envio de ordem pode ser feito, e isto é demonstrado pela caixa de ferramenta, veja que na imagem da esquerda temos apenas a ordem criada ali, no contrato vigente, mas na imagem da direita vemos apenas a ordem sendo indicada na caixa de mensagem, e nenhuma ordem pode ser vista no gráfico.

Você pode estar imaginando que é simplesmente uma questão de visualização, mas não, a coisa é bem mais complexa, mas é justamente isto que irei mostrar como resolver aqui neste artigo.

Um detalhe importante: Aqui irei mostrar como criar as regras para poder utilizar o histórico para negociação, e no caso estas regras irão ser voltadas para operar o Mini Dólar ( WDO ) e o Mini Índice ( WIN ) que fazem parte da bolsa brasileira ( B3 ) mas o correto entendimento irá permitir você adaptar as regras de forma a ter o mesmo funcionamento para qualquer tipo de contrato futuro de qualquer bolsa pelo mundo.

O sistema em si não se limita a um ativo ou outro, é tudo uma questão de adaptar os pontos adequados do código, se isto for feito da forma correta, você terá um EA do qual você não precisará se preocupar se o ativo esta ou não próximo do vencimento e qual será o próximo contrato, o próprio EA irá fazer isto para você, trocando o contrato para o correto assim que for necessário.


Entendendo as regras do jogo

Os contratos futuros do WDO ( Mini Dólar ), WIN ( Mini Índice ), DOL ( Dólar Futuro ) e IND ( Índice Futuro ) seguem regras bem especificas na questão de seus vencimentos e na nomenclatura do contrato, mas vamos ver primeiramente como saber a data de vencimento do contrato, isto pode ser visto na imagem abaixo:


Observe as áreas destacadas, em AZUL temos um resumo da data de vencimento do contrato, mas em vermelho temos o momento exato em que o contrato deixará de existir não sendo mais negociado por nenhum sistema, saber disto é muito importante pois sem saber como consultar estes momentos ficaríamos no limbo apenas imaginando quando o vencimento aconteceria.

Bem mas apesar da data e momento de vencimento estar especificado no próprio contrato como pode ser visto, o mesmo não acontece com o nome, mas felizmente este pode ser deduzido com base em regras conhecidas por todo o mercado de negociação e a regra é bem rígida. No caso dos contratos futuros de Dólar e Índice temos a seguinte regra para nomenclatura:

As 3 primeiras letras indicam o tipo de contrato:

Código Contrato
WIN Mini contrato futuro de índice ibovespa 
IND Contrato futuro de índice ibovespa
WDO Mini contrato futuro de dólar
DOL Contrato futuro de dólar

depois deste código temos uma letra que indica o mês de vencimento do contrato:

Mês de vencimento Letra indicativa para WDO e DOL Letra indicativa para WIN e IND 
Janeiro F  
Fevereiro  G  G
Março  H  
Abril  J  J
Maio  K  
Junho  M  M
Julho  N  
Agosto  Q  Q
Setembro  U  
Outubro  V  V
Novembro  X  
Dezembro  Z  Z

e para finalizar temos dois dígitos que representam o ano de vencimento do contrato, então um exemplo para um contrato futuro de dólar com vencimento em Abril de 2022 teremos o seguinte código : DOLJ22 e este seria o contrato que deveria ser negociado até o inicio de Maio, quando o mês de maio se iniciar este contrato estará vencido, já a regra para o WIN e IND é um pouco diferente o contrato vence na quarta feira mais próxima do dia 15 no mês indicado, ou seja a regra aparentemente é bem mais complexa, mas você notará que o EA irá conseguir lidar muito bem com ela, e sempre informará o contrato correto.


Implementação

Nosso EA já tem os pontos necessários para receber as regras de nomenclatura, teremos que fazer apenas alguns ajustes com relação ao sistema de apresentação de ordens, mas vamos iniciar o trabalho, a primeira coisa a ser feita é na classe objeto C_Terminal, vamos adicionar o seguinte código a esta classe:

void CurrentSymbol(void)
{
        MqlDateTime mdt1;
        string sz0, sz1;
        datetime dt = TimeLocal();
            
        sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);                           
        if ((sz0 != "WDO") && (sz0 != "DOL") && (sz0 != "WIN") && (sz0 != "IND")) return;
        sz1 = ((sz0 == "WDO") || (sz0 == "DOL") ? "FGHJKMNQUVXZ" : "GJMQVZ");
        TimeToStruct(TimeLocal(), mdt1);
        for (int i0 = 0, i1 = mdt1.year - 2000;;)
        {
                m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1);
                if (i0 < StringLen(sz1)) i0++; else
                {
                        i0 = 0;
                        i1++;
                }
                if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break;
        }
}

Este código acima utiliza as regras descritas anteriormente para gerar o nome do ativo, e para garantir que estaremos sempre usando um contrato vigente fazemos um teste, e este é feito pela linha em destaque, ou seja o ativo é válido pela própria plataforma de negociação e o EA irá usar o nome gerado, se você deseja operar outros contratos futuros tudo que terá que fazer será adaptar este código acima de forma a ter uma correta geração do nome, isto vai de caso a caso, mas o código não se limita a apenas aos ativos que estão atrelados a ele, o mesmo pode ser usado de forma a refletir qualquer tipo de contrato futuro, desde que a regra correta seja criada.

Agora vem a parte dos detalhes sobre a ordem. Bem se você utilizar o sistema neste ponto de desenvolvimento irá ter o seguinte comportamento:


Ou seja você já consegue ter um modo CROSS ORDER, mas ele ainda não esta completamente implementado, falta a indicação da ordem na tela gráfica, isto não é algo tão difícil de ser feito, como muitos já devem estar imaginando, tudo que precisamos fazer é indicar isto por linhas horizontais, mas não é somente isto, quando usamos uma ordem cruzada perdemos algumas coisas que o MetaTrader 5 nos fornece e precisamos implementar esta lógica para que o sistema de ordens continue funcionando e seja igualmente seguro, estável e confiável, caso contrário poderemos ter problemas ao usar ordens cruzadas.

Bem olhando por esta ótica, a coisa não parece mais assim tão simples, na verdade não é tão simples, já que teremos que criar toda a lógica que inicialmente era feita pela plataforma MetaTrader  , então a primeira coisa a ser feita é esquecer o sistema interno do MetaTrader  , ele não nos dará suporte a partir deste momento que começaremos a usar o sistema de ordem cruzada.

A partir de agora quem irá ditar as regras será o ticket da ordem, mas isto tem vários custos, o um dos maiores é que não sabemos quantas ordens um determinado operador irá colocar em seu gráfico, e limitar a quantidade não será algo que irá agradar ao operador, então precisamos fazer algo para permitir que o operador possa usar o sistema da mesma forma que ele usaria se estivesse usando o suporte fornecido pelo MetaTrader  , este é o nosso primeiro problema a ser resolvido.


A Classe C_HLineTrade

Para resolver este problema vamos criar uma nova classe para isto: a classe C_HLineTrade, esta classe irá substituir o sistema fornecido pelo MetaTrader 5 para apresentar as ordens no gráfico. Vamos então ver esta classe com calma, começando com a declaração dela:

class C_HLineTrade
{
#define def_NameHLineTrade "*HLTSMD*"
        protected:
                enum eHLineTrade {HL_PRICE, HL_STOP, HL_TAKE};
        private :
                color   m_corPrice,
                        m_corStop,
                        m_corTake;
                string  m_SelectObj;

Observe que definimos algumas coisas, e isto será muito usado durante todo o código. E por motivo será preciso bastante atenção as mudanças, e elas não serão poucas, na verdade serão muitas. A próxima coisa é declarar o construtor e destructor da classe:

C_HLineTrade() : m_SelectObj("")
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        RemoveAllsLines();
};
//+------------------------------------------------------------------+  
~C_HLineTrade()
{
        RemoveAllsLines();
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, true);
};

O construtor irá impedir que as linhas originais sejam visíveis, já o destructor irá forçar que elas voltem a ser vistas no gráfico. Temos uma rotina comum em ambas rotinas então vamos ver ela abaixo

void RemoveAllsLines(void)
{
        string sz0;
        int i0 = StringLen(def_NameHLineTrade);
                                
        for (int c0 = ObjectsTotal(Terminal.Get_ID(), -1, -1); c0 >= 0; c0--)
        {
                sz0 = ObjectName(Terminal.Get_ID(), c0, -1, -1);
                if (StringSubstr(sz0, 0, i0) == def_NameHLineTrade) ObjectDelete(Terminal.Get_ID(), sz0);
        }
}

A linha destacada irá testar se o objeto, no caso uma linha horizontal, é um dos objetos usados pela classe, e se for ela irá deletar o objeto, veja que não sabemos quantos objetos temos, mas o sistema irá testar objeto por objeto para limpar todos que foram criados pela classe. A próxima rotina de destaque na classe é vista abaixo:

inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select)
{
        string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1;
                                
        if (price <= 0)
        {
                ObjectDelete(Terminal.Get_ID(), sz0);
                return;
        }
        if (!ObjectGetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, 0, sz1))
        {
                ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (hl == HL_PRICE ? m_corPrice : (hl == HL_STOP ? m_corStop : m_corTake)));
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_WIDTH, 1);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_STYLE, STYLE_DASHDOT);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTABLE, select);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_BACK, true);
                ObjectSetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, (string)ticket + " "+StringSubstr(EnumToString(hl), 3, 10));
        }
        ObjectSetDouble(Terminal.Get_ID(), sz0, OBJPROP_PRICE, price);
}

Para esta rotina não interessa quantos objetos serão criados, se ele existe ou não quando ela é chamada, ela garante que a linha será criada e posicionada no local indicado, esta linha é que será o indicador substituto da linha originalmente usada pelo MetaTrader  .

A coisa aqui é para ser funcional e não bonita ou agradável, por conta disto as linhas não estão selecionadas quando são criadas, você pode mudar este comportamento, mas eu estou usando o sistema de mensagens do MetaTrader 5 para posicionar as linhas,  então para poder mover elas, você terá que indicar isto explicitamente, e para que seja indicado qual linha estará sendo ajustada temos outra rotina que é vista abaixo:

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (m_SelectObj != "") ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        m_SelectObj = sparam;
                };
        }
}

Esta rotina irá manter a linha destacada, e quando uma outra for selecionada, ela irá remover a seleção da anterior, isto é feito de forma bastante simples, mas apenas as linhas gerenciadas pela classe serão de fato manipuladas. Existe uma ultima rotina que desta classe que merece destaque, e ela é vista a seguir:

bool GetNewInfosOrder(const string &sparam, ulong &ticket, double &price, eHLineTrade &hl)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                hl = (eHLineTrade) StringToInteger(StringSubstr(sparam, i0, 1));
                ticket = (ulong)StringToInteger(StringSubstr(sparam, i0 + 1, StringLen(sparam)));
                price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE);
                return true;
        }
        return false;
}

Esta rotina é talvez a mais importante desta classe, já que não sabemos quantas linhas estão no gráfico, precisamos saber qual linha esta sendo manipulada pelo operador, esta rotina faz justamente isto, ela diz ao sistema quais são os dados da linha que esta sendo manipulada pelo usuário.

Bem mas isto é apenas uma pequena parte de tudo que temos que fazer, o sistema não esta nem próximo de ser funcional, então vamos para a próxima etapa, adicionar e modificar as rotinas da classe C_Router, que é a classe responsável por fazer o roteamento de ordens. Esta classe recebe as por herança as funcionalidade que acabamos me montar na classe C_HLineTrade isto pode ser visto no fragmento a seguir:

#include "C_HLineTrade.mqh"
//+------------------------------------------------------------------+
class C_Router : public C_HLineTrade


A nova classe C_Router

Na classe C_Router, original, existia a limitação de ter apenas e somente uma única ordem aberta, mas a partir de agora não teremos mais esta limitação, mas isto nos traz mudanças drásticas na classe C_Router.

A primeira mudança e esta é bem drástica é na rotina update desta classe, vejam como a nova rotina ficou:

void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal() - 1;
        o = OrdersTotal() - 1;
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true);
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};

Antes esta rotina apenas captura os dados da única posição aberta e a mantinha em seu observatório, mas agora a coisa explode na sua frente, esta rotina irá colocar todas, e absolutamente todas as posições abertas ou pendentes no gráfico, isto definitivamente é a substituição do sistema encontrado no MetaTrader  , a coisa aqui é coisa seria, esta rotina tem que ser muito bem entendida, pois se ela falhar irá por todo o sistema de ordens cruzadas em jogo, então antes de operar em uma conta real, coloque este sistema para ser testado em uma conta demo, justamente por conta de coisas como o que acontece nesta rotina, TESTE e TESTE novamente até ter absoluta convicção de que a coisa esta funcionando da forma como você espera, ajuste o que for necessário pois a forma como este sistema funciona é ligeiramente diferente do que o MetaTrader 5 funciona.

Reparem nas linhas destacadas, prestem muita atenção a elas, e responda, sinceramente: Você tem ideia do que elas realmente fazem ?!?! Estas duas linhas estão ai por um motivo que será visto quando eu falar sobre a classe C_OrderView mais abaixo neste artigo, mas sem estas duas linhas o código fica muito instável, funcionando de uma maneira estranha, mas tirando a parte em destaque, o resto da rotina é bem simples, ela cria via classe objeto C_HLineTrade, cada uma das linhas, e no caso existe uma única linha que não pode ser selecionada, e isto é facilmente indicado conforme mostrado no fragmento:

SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);

Ou seja, o sistema ficou bem simples e direto, esta rotina é chamada pelo EA durante um evento em OnTrade conforme pode ser visto pelo fragmento a seguir:

C_TemplateChart Chart;

// ... Código do EA ...

void OnTrade()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
        Chart.UpdatePosition();
}

// ... Restante do código do EA ...

O código destacado irá promover a atualização do sistema de ordens na tela, mas tem uma coisa, reparem que estamos usando a classe C_TemplateChart para isto, é por que o sistema passou por uma radical mudança na estrutura das classes, a nova estrutura das classes pode ser visto abaixo:

O fato de ela esta desta forma faz com que o fluxo de mensagens dentro do EA seja direcionado, sempre que estiver na duvida de como o fluxo de mensagens esta chegando a um classe em particular, observe este gráfico de herança das classes. A única classe que é tratada de forma publica é a classe objeto C_Terminal, todas as outras são tratadas pela herança entre classes, e nenhuma e absolutamente nenhuma variável é publica neste sistema.

Por conta que o sistema agora não analisa apenas uma única ordem, é necessário entender uma outra coisa: Como entender o resultado das operações ?!?! Por que isto é importante ?!?! Bem, quando se tem apenas uma única posição aberta o sistema é simples de ser entendido, mas conforme a quantidade de posições abertas aumenta, você tem que entender o que esta acontecendo. Bem, a rotina que informa isto é vista abaixo:

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
}

Basicamente ela não mudou, mas a rotina destacada SIM, então vamos ver o código desta rotina destacada:

inline double CheckPosition(void)
{
        double Res = 0, last, sl;
        ulong ticket;
                        
        last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ticket = PositionGetInteger(POSITION_TICKET);
                Res += PositionGetDouble(POSITION_PROFIT);
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (last < sl) ClosePosition(ticket);
                }else
                {
                        if ((last > sl) && (sl > 0)) ClosePosition(ticket);
                }
        }
        return Res;
};

A rotina tem 3 partes destacadas, a parte em amarelo é que informa o resultado das posições em aberto, já as partes em verde vão fazer um check na posição para caso o stop loss seja pulado por motivos de uma grande volatilidade, se isto acontecer a posição deve ser encerrada o mais rápido possível. Então esta rotina não retorna o resultado de uma única posição, salvo o fato de você estar com apenas uma única operação em aberto em um ativo especifico.

Mas além destas rotinas temos outras para auxiliar e permitir que o sistema continue funcionando quando usamos um modelo de ordens cruzadas, e elas também merecem algum destaque, duas delas podem ser vista logo abaixo:

bool ModifyOrderPendent(const ulong Ticket, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        if (Ticket == 0) return false;
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_MODIFY;
        TradeRequest.order      = Ticket;
        TradeRequest.price      = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.type_time  = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.expiration = 0;
        return OrderSend(TradeRequest, TradeResult);
};
//+------------------------------------------------------------------+
bool ModifyPosition(const ulong Ticket, const double Take, const double Stop)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        if (!PositionSelectByTicket(Ticket)) return false;
        TradeRequest.action     = TRADE_ACTION_SLTP;
        TradeRequest.position   = Ticket;
        TradeRequest.symbol     = PositionGetString(POSITION_SYMBOL);
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        return OrderSend(TradeRequest, TradeResult);
};

A primeira é responsável por modificar uma ordem que esteja ainda aberta, já a outra modifica uma posição aberta, apesar de parecer ser iguais, elas não são, e por ultimo temos uma rotina igualmente importante ao sistema, ela é vista a seguir:

bool RemoveOrderPendent(ulong Ticket)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_REMOVE;
        TradeRequest.order      = Ticket;       
        return OrderSend(TradeRequest, TradeResult);
};

Com esta última rotina fechamos a classe C_Router, ela desta forma está implementando o sistema básico necessário para suprimir o suporte que nos era fornecido pelo MetaTrader  , que agora por conta do sistema de ordens cruzadas não iremos poder contar mais com este apoio. No entanto o sistema ainda não esta concluído neste ponto, temos mais coisas a serem adicionadas antes de que o sistema seja de fato funcional, no entanto até o momento caso exista uma ordem ela será vista conforme mostrado abaixo, é importante que isto esteja acontecendo, para que o próximo passo seja executado com sucesso.


Observe com atenção a imagem acima, a caixa de mensagem temos a ordem aberta e é indicado qual é o ativo na qual ela esta aberta, no CHART TRADE é indicado o ativo negociado, repare que é o mesmo que esta sendo indicado na caixa de mensagem, agora olhe qual é o ativo que esta sendo mostrado o gráfico, isto pode ser visto no titulo da janela gráfica ... é completamente diferente não é mesmo, o ativo no gráfico, é o histórico do mini índice, ou seja não estamos usando o sistema do MetaTrader 5 para nos dar suporte, estamos usando o sistema de ordem cruzada, do qual esta sendo descrito neste artigo. Até o presente momento temos apenas esta funcionalidade, que permite mostrar onde a ordem esta posicionada, mas isto não é suficiente para termos um sistema funcional e que nos permita de fato operar via sistema de ordens cruzadas, precisamos de mais algumas coisas, no caso eventos de movimentação da ordem, e isto será conseguido em outra classe.


Uma nova funcionalidade na classe C_OrderView

A classe objeto C_OrderView é capaz de fazer diversas coisas, mas ainda não é capaz de manipular os dados de uma ordem seja aberta ou pendente, mas quando adicionamos um sistema de mensagem nela passamos a ter mais possibilidades de uso, e esta é a única adição que faremos na classe, neste momento, e a rotina pode ser vista na integra logo abaixo:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eHLineTrade     hl;
        
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        MoveTo((int)lparam, (int)dparam, (uint)sparam);
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                if (OrderSelect(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        RemoveOrderPendent(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        ClosePosition(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyPosition(ticket, OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyPosition(ticket, 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        C_HLineTrade::Select(sparam);
                        break;
                case CHARTEVENT_OBJECT_DRAG:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                price = AdjustPrice(price);
                                if (OrderSelect(ticket)) switch(hl)
                                {
                                        case HL_PRICE:
                                                pp = price - OrderGetDouble(ORDER_PRICE_OPEN);
                                                pt = OrderGetDouble(ORDER_TP);
                                                ps = OrderGetDouble(ORDER_SL);
                                                if (!ModifyOrderPendent(ticket, price, (pt > 0 ? pt + pp : 0), (ps > 0 ? ps + pp : 0))) UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), price)) UpdatePosition();
                                                break;
                                        case HL_TAKE:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), price, OrderGetDouble(ORDER_SL))) UpdatePosition();
                                                break;
                                }
                                if (PositionSelectByTicket(ticket)) switch (hl)
                                {
                                        case HL_PRICE:
                                                UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                ModifyPosition(ticket, PositionGetDouble(POSITION_TP), price);
                                                break;
                                        case HL_TAKE:
                                                ModifyPosition(ticket, price, PositionGetDouble(POSITION_SL));
                                                break;
                                }
                        };
                break;
        }
}

Este código completa o sistema de ordens cruzadas, com isto temos as possibilidades aumentadas a ponto de conseguir fazer quase as mesmas coisas que era possível antes do sistema de ordem cruzada ser implementado. Basicamente não existe muita coisa de anormal nesta função, mas tem um tipo de evento que não é muito comum, é o CHARTEVENT_OBJECT_DELETE, aqui quando o usuário deletar uma linha isto irá refletir no gráfico e no sistema de ordens, por isto cuidado quando começar a retirar linhas do seu gráfico, mas você não precisa se preocupar quando for retirar o EA do gráfico, as ordens irão continuar intactas como mostrado na animação abaixo:


Mas se o EA estiver no gráfico, você deve tomar o máximo cuidado ao deletar linhas do seu gráfico, ainda mais as que estiverem ocultas na lista de objetos, por que senão veja abaixo, o que acontece no sistema de ordens quando você retirar as linhas que foram criadas pelo sistema de ordens cruzadas.

E para terminar a demonstração do sistema, veja o que acontece com uma ordem quando arrastamos as linhas de preço, isto pode ser visto logo abaixo, mas lembre-se do seguinte, a linha para ser arrastada deverá estar selecionada, se ela não estiver selecionada não será possível movimentar ela, a alteração de preço só ocorrerá quando a linha for solta no gráfico, antes disto o preço irá se manter na mesma posição anterior.


Se estiver complicado de saber quando uma linha esta ou não selecionada faça a mudança no código de seleção, as mudanças estão em destaque no código, na versão que esta em anexo, esta mudança já se encontrará implementada.

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                
        if (m_SelectObj != "")
        {
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_WIDTH, 1);
        }
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_WIDTH, 2);
                        m_SelectObj = sparam;
                };
        }
}

O resultado desta modificação no código pode ser vista na animação abaixo.


Conclusão

Bem aqui mostrei como criar um sistema de ordens cruzadas no MetaTrader  , espero que este sistema seja útil para todos que vierem a utilizar este conhecimento, mas lembre-se do seguinte, antes de sair operando na conta real com este sistema, teste ele o máximo possível, em diversos cenários de mercado, pois apesar de estar sendo implementado na plataforma MetaTrader  , este sistema praticamente não tem quase nenhum apoio da plataforma quanto ao tratamento de erros, então se por ventura eles ocorrerem você terá que agir rapidamente, para não ter grandes perdas, mas testando ele em cenários diferentes, você poderá notar onde os problemas surgem, como movimentação, quantidade máxima de ordens que seu computador conseguirá suportar, spread máximo aceitável para o sistema de analise, nível de volatilidade aceitável, para ordens que estão em aberto, já que quanto mais ordens abertas e informações para serem analisadas, maior a possibilidade de algo ruim acontecer, já que cada uma das ordens é analisada a cada tick recebido pelo sistema, e isto pode ser um problema quando tem muitas ordens abertas ao mesmo tempo.

Fica ai a dica, não confie neste sistema antes de testar ele em conta demo no maior numero possível de cenário, mesmo que o código parece estar perfeito, ele não conta com nenhum tipo de analise de erro.

Anexo o código de todo o EA até o presente momento.


Arquivos anexados |
EA.zip (12013.17 KB)
Últimos Comentários | Ir para discussão (3)
Guilherme Mendonca
Guilherme Mendonca | 25 mai 2022 em 20:01

Parabéns por mais esse excelente artigo Daniel.


Acredito que o único problema será na virada do ano, quando a função "CurrentSymbol" precisar buscar o nome do símbolo do próximo ano. Me parece que o valor de i1 irá retornar sempre o número do ano vigente (22), porém em dezembro já começamos a usar o símbolo com final 23.


Daniel Jose
Daniel Jose | 26 mai 2022 em 12:52
Guilherme Mendonca #:

Parabéns por mais esse excelente artigo Daniel.


Acredito que o único problema será na virada do ano, quando a função "CurrentSymbol" precisar buscar o nome do símbolo do próximo ano. Me parece que o valor de i1 irá retornar sempre o número do ano vigente (22), porém em dezembro já começamos a usar o símbolo com final 23.


Na verdade este problema não acontecerá e o motivo é o que faz o LAÇO terminar ....

                                for (int i0 = 0, i1 = mdt1.year - 2000;;)
                                {
                                        m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1);
                                        m_Infos.szFullSymbol = StringFormat("%s%s%d", sz2, StringSubstr(sz1, i0, 1), i1);
                                        if (i0 < StringLen(sz1)) i0++; else
                                        {
                                                i0 = 0;
                                                i1++;
                                        }
                                        if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break;
                                }

Somente quando esta condição em destaque for atingida o laço irá ser encerrado, e o valor de i1 sempre será incrementado ... desta forma quando for feito a troca de ano o ativo será modificado automaticamente ....

Guilherme Mendonca
Guilherme Mendonca | 30 mai 2022 em 23:10
Daniel Jose #:

Na verdade este problema não acontecerá e o motivo é o que faz o LAÇO terminar ....

Somente quando esta condição em destaque for atingida o laço irá ser encerrado, e o valor de i1 sempre será incrementado ... desta forma quando for feito a troca de ano o ativo será modificado automaticamente ....

 Você está correto.

Não havia me atentado para a linha de incremento do valor de i1.

WebSocket para o MetaTrader 5: Usando a API do Windows WebSocket para o MetaTrader 5: Usando a API do Windows
Neste artigo vamos usar WinHttp.dll com o intuito de criar um cliente WebSocket para os programas MetaTrader 5. Em última instância, o cliente deve ser implementado como uma classe e testado em interação com o WebSocket API da Binary.com.
Desenvolvendo um EA de negociação do zero (Parte 10): Acessando indicadores personalizados Desenvolvendo um EA de negociação do zero (Parte 10): Acessando indicadores personalizados
Como acessar Indicadores personalizados diretamente no EA ? Um EA de negociação, só será realmente bem explorado se for possível você usar indicadores personalizados nele, caso contrário ele será apenas um conjunto de códigos e instruções.
Como escolher o Expert Advisor certo no Mercado MetaTrader? Como escolher o Expert Advisor certo no Mercado MetaTrader?
Neste artigo veremos as coisas às quais você deve prestar atenção ao comprar uma EA em primeiro lugar. Também analisaremos formas de aumentar os lucros e, o mais importante, como gastar o dinheiro sabiamente e obter lucro. Além disso, após a leitura, você perceberá que é possível ganhar dinheiro mesmo com produtos simples e gratuitos.
Desenvolvendo um EA de negociação do zero (Parte 09): Um salto conceitual (II) Desenvolvendo um EA de negociação do zero (Parte 09): Um salto conceitual (II)
Colocando o Chart Trade em uma janela flutuante. No artigo anterior criamos o sistema base para utilizar templates dentro de uma janela flutuante.