Desenvolvendo um EA de negociação do zero (Parte 18): Um novo sistema de ordens (I)

Daniel Jose | 8 junho, 2022

1.0 - Introdução

Deste que este EA começou a ter seu desenvolvimento documentado em artigos, e isto começou no artigo Desenvolvendo um EA de negociação do zero ele tem sofrido diversas mudanças e melhorias, mas no entanto tem mantido o mesmo modelo de sistema de ordens no gráfico, algo bem simples e funcional, mas não é adequado em muitas das vezes em que de fato estamos operando.

Existe a possibilidade de irmos adicionado coisas ao sistema original de forma a ter certas informações sobre as ordens, tanto as pendentes quanto as abertas, mas de certa forma isto irá transformando o código em um frankenstein, que ao longo do tempo torna o processo de melhorias e aperfeiçoamento um verdadeiro pesadelo. Já que por mais que tenhamos uma metodologia, esta vai se perdendo ao longo do tempo enquanto o código vai ficando extremamente grande e complexo.

Bem precisamos criar então um sistema completamente diferente no que diz respeito ao modelo de ordens que esta sendo usado, mas que ao mesmo tempo, seja simples de ser entendido pelo operador, e que nos dê todas a informações que precisamos para de fato operar com segurança.


1.0.1 Aviso extremamente importante

O sistema que irei descrever neste artigo é o que usa o modo de negociação registrado na conta como posições de cobertura do tipo ACCOUNT_MARGIN_MODE_RETAIL_NETTING, pois nestes temos apenas e somente uma única posição aberta por ativo. Caso você esteja utilizando um sistema de negociação registrado na conta como do tipo  ACCOUNT_MARGIN_MODE_RETAIL_HEDGING este artigo nada irá agregar no EA, já que neste caso é possível ter diversas posições abertas ao mesmo tempo sem que uma interfira na outra, podendo desta forma todas as modificações descartadas, ou retiradas do código final.

Este modo  ACCOUNT_MARGIN_MODE_RETAIL_HEDGING é mais comum de ser usado quando você deseja ter um EA executando de forma automática, abrindo e fechando posições, sem a sua intervenção, ao mesmo tempo em que você opera o mesmo ativo que o EA estará operando, ou seja o fato do EA esta vendendo não irá influenciar em nada uma operação que você execute comprando, as posições neste caso serão independentes.

Por este motivo o artigo terá todas as partes adicionadas ou modificadas nos códigos bem destacadas, e as modificações ou adições serão feitas de forma lenta e gradual, pois caso você precise ou deseje retirar as modificações e adições será fácil encontrar onde elas foram feitas.

Mas mesmo podendo retirar as modificações, existe uma parte onde eu irei testar o sistema para que mesmo que você mantenha as mudanças que irão ocorrer aqui, você possa usar este EA em qualquer tipo de conta, seja uma do tipo NETTING ou HEDGING, pois o EA irá contar com um teste para verificar e se ajustar a um modelo ou outro.


2.0 - Planejamento

A primeira coisa a ser feita de fato, é entender o que acontece com as ordens que vão sendo adicionadas ao sistema, e vão sendo executadas conforme os preços onde elas estão penduradas vai sendo atingido, muitos talvez não sabem, ou melhor dizendo, nunca pararam para pensar a este respeito, e por conta disto não efetuaram nenhum teste de forma a entender e assim implementar um sistema que seja adequado ao mesmo tempo se mantenha confiável.

Para entender o que acontece de fato, vamos utilizar um exemplo simples, você tem uma posição aberta, suponhamos de compra, em um ativo, com um volume inicial de, por exemplo 1 lote, daí você pendura,  uma nova ordem de compra, com um volume de 2 lotes a um preço pouco acima, até ai tudo bem, nada de muito extravagante, mas assim que os 2 lotes forem comprados, algo irá acontecer, e é justamente neste ponto é que mora o problema.

Quando os dois lotes forem comprados, você passará a ter 3 lotes, mas o sistema de negociação irá atualizar o seu preço inicial para um preço médio. Até aí tudo bem, é algo perfeitamente compreendido por todos os operadores de mercado, mas a questão é a seguinte: Qual será o valor de Stop e Take da nova posição ?!?!

Grande parte dos operadores não fazem a mínima ideia do que estou falando, mas deveriam de fato pensar nisto. Se você esta usando o sistema de ordens OCO em todas as operações, você irá notar uma coisa bastante curiosa e interessante acontecer cada vez que você abrir ou fechar uma posição com uma quantidade parcial do volume total que de fato você pode operar.

Bem no artigo sobre Ordens Cruzadas, eu apresentei uma forma de você ter os níveis de take e stop diretamente em um gráfico sem necessitar do sistema padrão do MetaTrdaer 5, é bem verdade que aquele método funciona de forma quase que idêntica ao sistema MetaTrdaer 5, já que ele foi pensado para ter uma funcionalidade bem próxima da encontrada na plataforma, mas com as devidas proporções, no entanto ao fazer alguns testes, verifiquei que quando temos uma ordem OCO aberta e uma ordem pendente também OCO é capturada pelo sistema de negociação, pelo fato do preço ter atingido o valor especificado na ordem, além de ser gerado um preço médio, temos a modificação do valor de take e stop para os valores indicados na última ordem OCO capturada, ou seja, dependendo de como ela esteja configurada, o EA irá encerra ela imediatamente após o sistema de negociação reportar um novo valor de take ou stop.

Isto acontece devido ao seguinte teste presente dentro do EA:

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

As linhas em destaque é que são responsáveis por este teste e encerramento caso o preço saia do canal que esta limitado entre o valor de take e o de stop.

É importante saber disto, pois não se trata de uma falha do EA, e tão pouco um problema ou falha no sistema de negociação, o real problema é que grande parte das vezes não prestamos a devida atenção ao que esta acontecendo e este tipo de coisa vai sendo ignorada até que não será mais possível ignorar o fato, isto devido a perdas constantes, ou saídas feitas fora de hora.

Se você operar de forma a não fazer preço médio, você não irá ver este tipo de coisa acontecer, mas existe uma gama muito grande de operacionais onde fazer preço médio é adequado e até mesmo necessário, e nestes casos se você usar o sistema sem saber dos detalhes que descrevi acima, pode acabar saindo da posição antes do desejado, mesmo que você tenha modificado anteriormente a ordem OCO de uma posição aberta de forma adequada, assim que uma ordem OCO pendente for capturada, os valores de limites ( toda a vez que eu falar limites estarei me referindo ao take e stop) anteriores serão modificados para os que estão indicados na ordem OCO pendente recentemente capturada.

A forma de corrigir, ou melhor evitar isto, é não usar ordens OCO, pelo menos depois que você já estiver uma posição aberta, todas as outras ordens emitidas para o sistema de negociação deverão ser ordens do tipo simples, sem que os valores de take e stop estejam definidos.

Basicamente é isto, mas quando um EA está no gráfico, ele esta ali, para nos auxiliar e nos ajudar, facilitando a nossa vida ao máximo, se não for assim, não faria sentido ter todo o trabalho em programar um EA, para depois não o utilizarmos.


3.0 - Implementação do novo sistema


Basicamente para se implementar e assegurar que o sistema será executado da forma como esperamos, ou seja, que o EA irá nos ajudar e evitará que façamos bobagens, teremos que fazer algumas pequenas mudanças no código.

Não é algo muito complicado, no primeiro momento, mas estas mudanças irão garantir que nunca iremos correr o risco de ter uma ordem OCO entrando em um momento indesejado e causando uma verdadeira confusão nas nossas operações.

Vamos começar fazendo as seguintes mudanças:


3.0.1 - Modificando a classe C_Router

A classe C_Router é a responsável por analisar e enviar ordens para nos, então adicionamos uma variável privativa nela, e quando uma posição aberta for detectada, no ativo que o EA estará tomando conta, esta variável irá guardar esta informação para nos, e toda vez que o EA desejar saber se existe uma posição aberta, ela irá reportar isto.

Esta implementação pode ser vista no fragmento abaixo, apesar de tudo este fragmento irá sofrer mudanças em breve neste artigo, mas quero mostrar as coisas aos poucos, para que entendem como o processo de modificação se deu de fato.

//+------------------------------------------------------------------+
inline bool ExistPosition(void) const { return m_bContainsPosition; }
//+------------------------------------------------------------------+
void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        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;
                m_bContainsPosition = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                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);
        }
};
//+------------------------------------------------------------------+

As linhas em destaque são as adições necessárias para executar todos os testes que serão feitos a posterior, em alguns pontos do código do nosso EA.

De certa forma poderíamos fazer todos os testes e ajustes apenas na classe C_Router, mas isto não será o suficiente, conforme irei explicar mais para frente, mas vamos continuar a fazer as modificações. Depois de ter criado o teste visto acima, iremos adicionar um construtor, para poder inicializar de forma adequada a variável recem adicionada.

C_Router() : m_bContainsPosition(false) {}

Agora modificamos a função de colocação de ordens pendentes da seguinte forma:

ulong CreateOrderPendent(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        double last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST);
                                
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action             = TRADE_ACTION_PENDING;
        TradeRequest.symbol             = Terminal.GetSymbol();
        TradeRequest.volume             = Volume;
        TradeRequest.type               = (IsBuy ? (last >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : (last < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));
        TradeRequest.price              = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl                 = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits());
        TradeRequest.tp                 = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits());
        TradeRequest.type_time          = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.stoplimit          = 0;
        TradeRequest.expiration         = 0;
        TradeRequest.type_filling       = ORDER_FILLING_RETURN;
        TradeRequest.deviation          = 1000;
        TradeRequest.comment            = "Order Generated by Experts Advisor.";
        if (!Send()) return 0;
                                                                
        return TradeResult.order;
};

Os pontos em destaque são as modificações a serem feitas.

Agora voltemos ao código updade para fazer uma nova modificação nele, lembre-se que ele é chamado pela função OnTrade, e esta é chamada toda vez que as ordens sofrem alteração, e esta pode ser vista no fragmento abaixo:

void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        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;
                m_bContainsPosition = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                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())
        {
                if (m_bContainsPosition)
                {
                        ModifyOrderPendent(ul, OrderGetDouble(ORDER_PRICE_OPEN), 0, 0);
                        (OrderSelect(ul) ? 0 : 0);
                }
                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);
        }
};

O que estamos fazendo agora, é garantido que o usuário não venha a transformar uma ordem pendente simples, em uma ordem pendente do tipo OCO, e isto quando já existe uma posição aberta, ou seja caso ele venha a abrir a caixa de ferramentas e tentar editar os valores de take ou stop. Então quando o usuário tentar fazer isto, o servidor de negociação reportar isto para nos via função OnTrade, assim o EA irá saber da alteração imediatamente e irá desfazer a alteração feita pelo usuário, garantindo a confiabilidade do sistema.

Mas existe um outro ponto que também devemos modificar que é as ordens a mercado, esta é uma modificação bem simples de ser feita, já que não é necessário fazer muita coisa no que diz respeito a testes, então novo o código da função ficará como mostrado logo abaixo:

ulong ExecuteOrderInMarket(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action             = TRADE_ACTION_DEAL;
        TradeRequest.symbol             = Terminal.GetSymbol();
        TradeRequest.volume             = Volume;
        TradeRequest.type               = (IsBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
        TradeRequest.price              = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl                 = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits());
        TradeRequest.tp                 = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits());
        TradeRequest.type_time          = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.stoplimit          = 0;
        TradeRequest.expiration         = 0;
        TradeRequest.type_filling       = ORDER_FILLING_RETURN;
        TradeRequest.deviation          = 1000;
        TradeRequest.comment            = "[ Order Market ] Generated by Experts Advisor.";
        if (!Send()) return 0;
                                                
        return TradeResult.order;
};

Apesar de parecer estranho estas modificações já garantem um nível adequado de segurança, pelo menos aceitável, para que não passemos uma ordem OCO, pendente ou a mercado, quando uma posição já esta aberta no ativo que o EA esta sendo executado, então o EA já estará de fato tomando conta do sistema de envio de ordens.

Bem, esta tudo muito bonito e muito maravilhoso para ser verdade, não é mesmo. Você pode pensar que isto já irá lhe garantir uma boa margem de segurança, mas não é bem assim, estas modificações irão garantir que uma ordem OCO não fique pendente ou não entre a mercado quando temos uma posição aberta, mas existe uma falha mortal nestas modificações, e que se não for devidamente corrigida, esta falha poderá lhe dar uma baita dor de cabeça e um tremendo prejuízo, podendo fazer você quebrar a sua conta, ou ter a posição encerrada pela corretora por falta de margem.

Observem que não existe uma checagem se a ordem pendente esta ou não dentro dos limites da posição aberta, e isto é mortal, pois da forma como o sistema está, quando você adicionar uma ordem OCO pendente fora dos limites da posição aberta, o EA não permitirá que esta ordem seja do tipo OCO, ou seja, a ordem não terá limites, será uma ordem sem limites de ganhos ou perdas, então quando a posição for fechada e esta ordem pendente entrar, você terá que ajustar o mais rápido possível os limites da mesma, caso esqueça de fazer isto, correrá o risco de ter uma posição aberta sem limites.

E para ajustar estes limites você terá que recorrer a caixa de mensagens, abrir a ordem e editar os valores de limites, mas isto será corrigido em breve, vamos primeiro resolver o atual problema.

Então precisamos modificar esta forma de como o EA esta tomando conta das ordens pendentes, pois se o usuário desejar criar uma ordens sem tais limites, o EA irá simplesmente entender isto como normal, mas caso o usuário venha a criar uma ordem com limites o EA deverá ajustar a ordem da forma adequada, colocando limites caso a ordem seja posicionada fora dos limites da posição aberta, ou tirando os limites da ordem pendente se a ordem for colocada dentro dos limites da posição aberta.


3.0.2 - Trabalhando dentro dos limites

A primeira coisa que iremos fazer é criar limites de verificação, isto é bem simples de ser feito, pelo menos a questão conceitual, no entanto é necessário muita atenção aos detalhes pois existem 2 casos possíveis, que podem ser estendidos a mais casos, mas para entender, bastará saber como proceder nestes 2 casos.


O primeiro caso é visto logo acima, neste temos uma ordem pendente fora dos limites, o limite no caso é a região em degrade, ou seja temos uma posição aberta, que não importa se é compra ou venda, mas temos um ponto de limite superior, onde quando o preço atingir ou ultrapassar este limite, a posição será fechada, neste caso a ordem pendente pode ter configurada pelo usuário como sendo uma ordem OCO, e o EA deverá aceitar a forma como a ordem esta configurada pelo usuário, seja ela uma ordem simples ou uma ordem OCO, o EA não deverá interferir em nada neste caso.

Já o segundo caso é visto logo abaixo, neste a ordem pendente está dentro da área limitada pela posição aberta, neste caso o EA deverá retirar os limites que possam estar, ou venham a ser configurados pelo usuário.


Notem que não importa até onde o limite vai, se estamos comprando ou vendendo, se a ordem pendente entrar nesta região, o EA deverá retirar os valores de limite da ordem pendente, mas se sairmos desta região o EA deverá deixar, a ordem conforme ela esta configurada pelo usuário.

Definido isto precisamos criar alguns variáveis, e estas estão mostradas abaixo:

class C_Router : public C_HLineTrade
{
        protected:
                MqlTradeRequest TradeRequest;
                MqlTradeResult  TradeResult;
        private  :
                bool            m_bContainsPosition;
                struct st00
                {
                        double  TakeProfit,
                                StopLoss;
                        bool    IsBuy;
                }m_Limits;

// ... Restante do código

Agora temos como verificar os limites durante a fase em que ocorre um evento que dispara a ordem OnTrade, então mais uma vez vamos modificar a função update da classe C_Router

// Restante do código....

//+------------------------------------------------------------------+
#define macro_MAX(A, B) (A > B ? A : B)
#define macro_MIN(A, B) (A < B ? A : B)
void UpdatePosition(void)
{
        static int      memPositions = 0, memOrder = 0;
        ulong           ul;
        int             p, o;
        double          price;
        bool            bTest;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        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;
                m_bContainsPosition = false;
                m_Limits.StopLoss = -1;
                m_Limits.TakeProfit = -1;
                m_Limits.IsBuy = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
                {
                        bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                        bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
                        bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
                }
                if ((m_bContainsPosition) && (bTest))
                {
                        ModifyOrderPendent(ul, price, 0, 0);
                        (OrderSelect(ul) ? 0 : 0);
                }
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};
#undef macro_MAX
#undef macro_MIN
//+------------------------------------------------------------------+

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

Agora a classe irá tratar as posições pendentes de forma a diferenciar quando elas estão ou não dentro de uma área que não pode haver ordens pendentes do tipo OCO, reparem que a cada mudança de status no sistema de ordens, esta função será chamada, a primeira coisa que ela irá fazer, é inicializar as variáveis de forma adequada.

m_Limits.StopLoss = -1;
m_Limits.TakeProfit = -1;
m_Limits.IsBuy = false;

feito isto, iremos testar se existe ou não uma posição aberta, e isto pode ser feito a qualquer momento. A partir do momento que temos uma posição aberta, ela irá delimitar a região onde não se poderá ter ordens pendentes do tipo OCO e isto é conseguido neste ponto

SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;

Agora podemos testar cada uma das ordens pendentes para verificar se elas estão ou não dentro da área delimitada. Um detalhe, aqui temos que saber se a ordem é de compra ou venda, já que pode ser que não tenhamos um take, mas poderemos ter um stop, e neste caso é preciso saber a mão da posição, para entender veja as figuras abaixo:

 

Em uma mão de venda o Stop marca o limite máximo ....

 

Em uma mão de compra o Stop marca o limite mínimo ....

Ou seja, em um caso as ordens pendentes poderão ser do tipo OCO caso esteja acima de um máximo estabelecido, em outros ela deverá estar abaixo de um mínimo estabelecido. Mas também pode acontecer um outro caso onde as ordens pendentes podem também ser do tipo OCO, este é mostrado abaixo:

 

Ordens pendentes fora dos limites ... caso típico ....

Então para verificar isto usamos o seguinte fragmento

price = OrderGetDouble(ORDER_PRICE_OPEN);
if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
{
        bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
        bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
        bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
}

Este irá testar se existe alguma posição aberta, caso negativo, o EA deverá respeitar a configuração da ordem feita pelo usuário, em caso de haver alguma posição, iremos testar as coisa na seguinte ordem:

  1. Se estamos vendidos, o preço onde a ordem pendente estará posicionada deverá ser maior que o valor de stop da posição aberta;
  2. Se estamos comprados, o preço onde a ordem pendente estará posicionada, deverá ser menor que o valor de stop da posição aberta;
  3. Caso ainda o sistema esteja aceitando a ordem como uma do tipo OCO, fazemos um último testes que será verificar se a ordem esta fora dos limites da posição

Feito isto, teremos uma segurança que a ordem pendente pode ser deixada ou não da forma como o usuário a configurou, e vida que segue.... mas existe uma ultima adição antes de passarmos para a próxima etapa, na verdade um ultimo teste, que eu mencionei no inicio do artigo, e este esta no fragmento abaixo:

void UpdatePosition(void)
{

// ... Código interno ...

        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
        }
        if (AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
                m_bContainsPosition = false;
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                if (m_bContainsPosition)
                {
                        if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
                        {
                                bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                                bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
                                bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
                        }
                        if (bTest)
                        {
                                ModifyOrderPendent(ul, price, 0, 0);
                                (OrderSelect(ul) ? 0 : 0);
                        }
                }
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};

A parte destacada irá verificar se o tipo de conta é uma HEDGING e se este for o caso mesmo que variável tenha indique que teremos que trabalhar com limites, neste ponto ela indicará que não será necessário lidar com limites, então o EA irá ignorar qualquer tipo de limitação que poderia ocorrer, e aceitará as ordens conforme foram configuradas pelo usuário, trata-se de um teste bastante simples, mas é necessário que ele seja feito neste ponto para garantir que teremos um funcionamento adequado de todo o sistema.


3.1.0 - Ajustando o sistema de posicionamento

Apesar de grande parte dos problemas terem sido resolvidos fazendo ajustes na classe objeto C_Router, ainda não temos um sistema adequado, temos um outro problema a ser resolvido: Corrigir o sistema de posicionamento via Mouse, este é um outro passo igualmente importante, mas que tem diversas implicações, já que temos que definir como o sistema presente na classe C_OrderView deverá funcionar.

A grande questão, e esta vai depender de como você deseja de fato operar, é se a classe C_OrderView irá ou não criar limites para as ordens pendentes quando elas sairem dos limites de uma posição aberta.

Apesar de ser tentador fazer isto todas as vezes, existem coisas que devem ser levadas em conta ao se tomar esta decisão, mas vamos por partes como diria JACK, basicamente a única e real mudança que teremos de fato que fazer na classe C_OrderView é mostrada no código abaixo:


inline void MoveTo(uint Key)
{
        static double local = 0;
        datetime dt;
        bool    bEClick, bKeyBuy, bKeySell, bCheck;
        double  take = 0, stop = 0, price;
                                
        bEClick  = (Key & 0x01) == 0x01;    //Clique esquerdo
        bKeyBuy  = (Key & 0x04) == 0x04;    //SHIFT Pressionada
        bKeySell = (Key & 0x08) == 0x08;    //CTRL Pressionada                          
        Mouse.GetPositionDP(dt, price);
        if (bKeyBuy != bKeySell)
        {
                Mouse.Hide();
                bCheck = CheckLimits(price);
        } else Mouse.Show();
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = (bCheck ? 0 : price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1))));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = (bCheck ? 0 : price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1))));
        if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade);
        local = (local != price ? 0 : local);                           
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE));
};

Mas é somente isto ?!?! Sim, é somente isto que precisamos fazer, todo o restante da lógica esta dentro da classe C_Router, e o que não foi modificado é executado pelo próprio sistema de mensagens do MetaTrdaer 5, pois quando ocorre uma mudança na lista de ordens, seja as pendentes ou as posições, a rotina OnTrade será chamada, e quando isto acontecer ela irá disparar a rotina update de dentro da classe C_Router que irá fazer os devidos asjuste, mas tem um código que esta aparecendo nesta rotina e talvez você fique louco procurando onde ele esta, na verdade ele esta dentro da classe C_Router, ele é visto logo abaixo:

#define macro_MAX(A, B) (A > B ? A : B)
#define macro_MIN(A, B) (A < B ? A : B)
inline bool CheckLimits(const double price)
{
        bool bTest = false;
                                
        if ((!m_bContainsPosition) || ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1))) return bTest;
        bTest = ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price));
        if (m_Limits.TakeProfit == 0)
        {
                bTest = (bTest ? bTest : (!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
        }
        return bTest;
};
#undef macro_MAX
#undef macro_MIN

Este código é exatamente o que estava dentro da rotina de update da classe C_Router, ele foi retirado de lá, e no lugar foi adicionado uma chamada para ele ...


3.2.0 - Limitar ou não limitar, eis a questão

Nosso trabalho esta quase concluído, mas existe uma última questão para se decidir, e esta talvez seja a questão mais complicada neste momento. Se você acompanhou e compreendeu o conteúdo até neste ponto, deve ter notado que o sistema esta funcionando muito bem, mas sempre que uma ordem pendente que esteja configurada como sendo uma ordem OCO entra nos limites de uma posição aberta, a ordem perde os limites que foram configurados nela. Isto irá ocorrer sempre.

Mas se por um acaso o operador modificar os limites da posição aberta, ou se você retirar a ordem que era OCO, que agora é uma ordem simples, de dentro destes limites, ela irá continuar sendo uma ordem simples, então temos um problema em potencial neste ponto.

O grande detalhe é: Como o EA deverá proceder ?!?! Ele deve notificar o operador de que uma ordem simples acaba de surgir no ativo ?!?! Ou ele deve simplesmente colocar os limites na ordem e assim a tornar uma ordem OCO ?!?!

Esta questão é algo de extrema relevância se você deseja de fato que o EA lhe ajude nas operações. É de fato prudente que o EA emita um aviso nos informando do acontecido, mas se você estiver em um ativo, em um momento de grande volatilidade também é prudente fazer com que o EA crie automaticamente algum tipo de limite para a ordem, e que ela não fique ali solta, podendo nos dar grandes prejuízos até nos dar conta do que esta acontecendo.

Desta forma para resolver este problema, o sistema passou por uma última modificação, mas como eu expliquei acima, você deve pensar seriamente em como de fato tratar este problema. Mas vamos ver como eu implementei uma possível solução.

A primeira coisa que foi feita é adicionar uma nova variável para que o operador informe ao EA qual será o procedimento a ser tomado, esta pode ser vista abaixo:

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

input group "Chart Trader"
input int       user20   = 1;              //Fator de alavancagem
input int       user21   = 100;            //Take Profit ( FINANCEIRO )
input int       user22   = 75;             //Stop Loss ( FINANCEIRO )
input color     user23   = clrBlue;        //Cor da linha de Preço
input color     user24   = clrForestGreen; //Cor da linha Take Profit
input color     user25   = clrFireBrick;   //Cor da linha Stop
input bool      user26   = true;           //Day Trade ?
input bool      user27   = true;           //Sempre criar limites de ordens soltas

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

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

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

Feito isto vamos voltar para a classe C_Router e adicionar 3 novas rotinas nela. Estas podem ser vistas abaixo:

//+------------------------------------------------------------------+
void SetFinance(const int Contracts, const int Take, const int Stop)
{
        m_Limits.Contract = Contracts;
        m_Limits.FinanceTake = Take;
        m_Limits.FinanceStop = Stop;
}
//+------------------------------------------------------------------+
inline double GetDisplacementTake(const bool IsBuy, const double Vol) const
{
        return (Terminal.AdjustPrice(m_Limits.FinanceTake * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? 1 : -1));
}
//+------------------------------------------------------------------+
inline double GetDisplacementStop(const bool IsBuy, const double Vol) const
{
        return (Terminal.AdjustPrice(m_Limits.FinanceStop * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? -1 : 1));
}
//+------------------------------------------------------------------+

Estas rotinas irão manter o valores que são informados no Chart Trader, conforme pode ser visto logo a seguir na imagem, mas também irá corrigir de forma proporcional o valor que deveremos usar como limites nas ordens pendentes do tipo OCO.

Ou seja, já temos de onde tirar os valores que iremos usar para que o EA possa configurar minimamente uma ordem OCO quando uma ordem pendente ficar solta, mas como você pode estar suspeitando, teremos que fazer uma nova mudança no código update da classe C_Router, e estas mudanças podem ser vista abaixo:

void UpdatePosition(bool bAdjust)
{
        static int      memPositions = 0, memOrder = 0;
        ulong           ul;
        int             p, o;
        long            info;
        double          price, stop, take, vol;
        bool            bIsBuy, bTest;
                        
        p = PositionsTotal();
        o = OrdersTotal();
        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;
                m_bContainsPosition = false;
                m_Limits.StopLoss = -1;
                m_Limits.TakeProfit = -1;
                m_Limits.IsBuy = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, take = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, stop = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
                m_Limits.TakeProfit = (m_Limits.TakeProfit < 0 ? take : (m_Limits.IsBuy ? (m_Limits.TakeProfit > take ? m_Limits.TakeProfit : take) : (take > m_Limits.TakeProfit ? m_Limits.TakeProfit : take)));
                m_Limits.StopLoss = (m_Limits.StopLoss < 0 ? stop : (m_Limits.IsBuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop)));
        }
        if ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
                m_bContainsPosition = false;
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                take = OrderGetDouble(ORDER_TP);
                stop = OrderGetDouble(ORDER_SL);
		bTest = CheckLimits(price);
                if ((take == 0) && (stop == 0) && (bAdjust) && (!bTest))
                {
                        info = OrderGetInteger(ORDER_TYPE);
                        vol = OrderGetDouble(ORDER_VOLUME_CURRENT);
                        bIsBuy = ((info == ORDER_TYPE_BUY_LIMIT) || (info == ORDER_TYPE_BUY_STOP) || (info == ORDER_TYPE_BUY_STOP_LIMIT) || (info == ORDER_TYPE_BUY));
                        take = price + GetDisplacementTake(bIsBuy, vol);
                        stop = price + GetDisplacementStop(bIsBuy, vol);
                        ModifyOrderPendent(ul, price, take, stop);
                }
                if ((take != 0) && (stop != 0) && (bTest))
                        ModifyOrderPendent(ul, price, take = 0, stop = 0);
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, take, HL_TAKE, true);
                SetLineOrder(ul, stop, HL_STOP, true);
        }
};

Todas as linhas em destaque irão testar se a ordem esta solta, e se o EA deverá ou não interferir nisto, caso o EA deva interferir, será feito um calculo sobre o valor financeiro informados no Chart Trader e baseados no volume que está na ordem pendente, depois disto a ordem simples irá receber os limites calculados com base nas informações coletadas, e será criado as linhas que informam as posições de limite da ordem tornando desta forma uma ordem pendente simples em uma ordem pendente OCO.


Conclusão

Apesar de todos os esforços para testar o sistema de forma a verificar se ele estava ou não reconhecendo a conta como uma do tipo HEGDE, não consegui ser efetivo neste ponto, o EA sempre estava reportando que a conta estava em modo NETTING, mesmo que a plataforma MetaTrdaer 5 informando se esta em uma conta HEDGE ... então devesse tomar cuidado pois apesar de estar funcionado conforme o desejado, mesmo em uma conta HEDGE as ordens pendentes estão sendo ajustadas como se a conta fosse do tipo NETTING ...

No video isto fica claro o que acabei de descrever, mas você pode ver que o sistema é bem interessante de ser usado .