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

Daniel Jose | 15 junho, 2022

1.0 - Introdução

No artigo anterior Desenvolvendo um EA de negociação do zero (Parte 18), fizemos diversas correções, mudanças e ajustes no sistema de ordens ao ponto de chegarmos a criar um sistema que nos permite-se operar de formas distintas entre uma conta do tipo NETTING e uma do tipo HEDGING, pois existem diferenças entre operar uma e outra, o tipo NETTING o sistema de negociação vai criando um preço médio de forma que iremos ter apenas uma única posição aberta em um ativo, já o tipo HEDGING podemos ter diversas posições em aberto cada uma com seus limites específicos, podendo inclusive estar ao mesmo tempo vendido e comprado em um mesmo ativo, isto só é possível em uma conta do tipo HEDGING, e esta é a base que muitos entenderiam como sendo operar opções binárias.

Mas agora finalmente chegou a hora de subir a régua e tornar o sistema de ordens totalmente visual, de forma que iremos poder dispensar a caixa de mensagens para analisar quais são os valores que cada posição, e seremos capazes de fazer isto apenas observando o novo sistema de ordens, isto irá nos permitir ajustar diversas coisas ao mesmo tempo que será fácil saber quais são os limites de ganho ou perda de uma posição OCO, ou de uma ordem pendente do tipo OCO, pois o próprio EA ira informar isto para nos em tempo real, sem que precisemos fazer cálculos para isto.

Aqui será a primeira parte desta implementação, mas não iremos partir do zero, iremos modificar o sistema já existente adicionando ainda mais objetos e eventos ao gráfico do ativo que estamos operando.


2.0 - Planejamento

Planejar o sistema que será usado aqui, não é algo muito difícil, já que estaremos modificando um sistema já existente, iremos apenas alterar o sistema de apresentação de ordens no gráfico. Esta é a ideia básica, parece ser algo bastante fácil de ser feito, mas na prática demanda bastante criatividade, já que teremos que manipular e modelar os dados de forma que a plataforma MetaTrader 5 cuide da parte pesada para nos.

Existem diversas formas de modelar os dados, mas cada uma tem suas vantagens e desvantagens.

Se você acha que isto não é facilmente conseguido, veja na classe C_HLineTrade o seguinte fragmento:

inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select)
{
        string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1;
                                
        ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);

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

Este fragmento destacado, mostra justamente isto, podemos criar quantas linhas horizontais quanto for desejado e elas irão receber eventos de forma totalmente independente, mas tudo que teremos que fazer é implementar os eventos baseados no nome que cada uma das linhas terá, já  que os nomes serão únicos, a plataforma MetaTrader 5 irá cuidar do resto para nos, então o resultado de fato será como o visto abaixo:


Mas apesar de parecer algo ideia, esta modelagem não é suficiente para o que de fato precisamos, a ideia é esta e pode de fato ser implementada, mas a modelagem que o EA tem no momento não é a ideia para se conseguir ter um numero ilimitado de objetos baseados no mesmo nome, temos que fazer mudanças e estas implicam em modificar o código de forma bastante profunda.

Bem vamos então começar a implementar esta nova forma de modelar os dados, mas iremos mudar apenas o necessário para que isto aconteça, de forma a não desestabilizar o código, o mesmo deverá continuar funcionando da forma o mais estável quanto for possível, e todo o trabalho tem que ser feito pela plataforma MetaTrader 5, nos iremos apenas dizer como a plataforma deverá entender a nossa modelagem.


3.0 - Implementação

A primeira mudança é a troca da classe C_HLineTrade para uma nova classe, C_ObjectsTrade, esta nova classe irá conseguir dar suporte ao que desejamos, uma forma de poder ter um numero ilimitado de objetos ligados uns ao outros.

Bem vamos começar vendo as definições iniciais, elas podem ser vistas no fragmento abaixo

class C_ObjectsTrade
{
//+------------------------------------------------------------------+
#define def_NameObjectsTrade 	"SMD_OT"
#define def_SeparatorInfo       '*'
#define def_IndicatorTicket0    1
//+------------------------------------------------------------------+
        protected:
                enum eIndicatorTrade {IT_NULL, IT_STOP= 65, IT_TAKE, IT_PRICE};
//+------------------------------------------------------------------+

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

Aqui temos a base inicial que iremos de fato implementar, no futuro isto irá ser ampliado, mas no momento quero manter o sistema estável enquanto ele é modificado e tem uma nova modelagem dos dados.

Bem ainda dentro dos limites da declaração protected temos as seguintes funções

inline double GetLimitsTake(void) const { return m_Limits.TakeProfit; }
//+------------------------------------------------------------------+
inline double GetLimitsStop(void) const { return m_Limits.StopLoss; }
//+------------------------------------------------------------------+
inline bool GetLimitsIsBuy(void) const { return m_Limits.IsBuy; }
//+------------------------------------------------------------------+
inline void SetLimits(double take, double stop, bool isbuy)
{
        m_Limits.IsBuy = isbuy;
        m_Limits.TakeProfit = (m_Limits.TakeProfit < 0 ? take : (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 : (isbuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop)));
}
//+------------------------------------------------------------------+
inline int GetBaseFinanceLeveRange(void) const { return m_BaseFinance.Leverange; }
//+------------------------------------------------------------------+
inline int GetBaseFinanceIsDayTrade(void) const { return m_BaseFinance.IsDayTrade; }
//+------------------------------------------------------------------+
inline int GetBaseFinanceTakeProfit(void) const { return m_BaseFinance.FinanceTake; }
//+------------------------------------------------------------------+
inline int GetBaseFinanceStopLoss(void) const { return m_BaseFinance.FinanceStop; }

Estas funções no momento são apenas uma segurança para um outro esquema que será feito no futuro, mas apesar dos dados e analises feitas por elas poderem ser feitas em outros locais, é bom deixar algumas coisas no nível mais baixo possível da cadeia de herança, e mesmo que os valores de retorno serão usados apenas pelas classes herdeiras, não quero permitir isto diretamente, ou seja, a classe herdeira simplesmente acesse os valores que estão dentro desta classe objeto C_ObjectsTrade, isto irá quebrar a ideia de encapsulamento da classe objeto, dificultando futuras modificações, ou correções de falhas, onde uma classe herdeira modifica o valor da classe base sem que esta mudança seja feita via chamada de algum procedimento.

Já que desejo reduzir ao máximo o overlay de chamadas, todas as funções estão declaradas como inline, isto aumenta um pouco o executável, mas temos o beneficio de um sistema um pouco mais seguro.

Agora chegamos as declarações privativas.

//+------------------------------------------------------------------+
        private :
                string  m_SelectObj;
                struct st00
                {
                        double  TakeProfit,
                                StopLoss;
                        bool    IsBuy;
                }m_Limits;
                struct st01
                {
                        int     FinanceTake,
                                FinanceStop,
                                Leverange;
                        bool    IsDayTrade;
                }m_BaseFinance;
//+------------------------------------------------------------------+
                string MountName(ulong ticket, eIndicatorTrade it)
                {
                        return StringFormat("%s%c%c%c%d", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket);
                }
//+------------------------------------------------------------------+

A grande questão aqui é o fragmento em destaque, este irá modelar os nomes dos objetos, no momento estou mantendo o básico, ainda presente no sistema, como eu disse, primeiro criamos e modificamos a modelagem, mantendo o sistema estável, depois adicionamos os novos objetos, só que isto será feito de forma bastante simples, rápida e mantendo toda a estabilidade já conseguida.

Bom apesar do código ter sofrido muito mais mudanças do que as mostradas aqui, a partir deste momento irei focar apenas nas novas funções, ou em pontos que a mudança foi extremamente drástica com relação os códigos anteriores.

Então seguindo esta ideia, a primeira função a ser vista é mostrada abaixo:

inline string CreateIndicatorTrade(ulong ticket, eIndicatorTrade it, bool select)
{
        string sz0 = MountName(ticket, it);
                                
        ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (it == IT_PRICE ? clrBlue : (it == IT_STOP ? clrFireBrick : clrForestGreen)));
        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(it), 3, 10));
                                
        return sz0;
}

Esta ira criar, neste momento apenas uma linha horizontal, mas vejam ali o código de geração do nome, observem também que as cores agora serão definidas dentro do código, e não mais pelo usuário, mas isto foi uma decisão minha.

A próxima coisa a ser feita é sobrecarregar esta mesma função, e isto é visto logo abaixo.

inline string CreateIndicatorTrade(ulong ticket, double price, eIndicatorTrade it, bool select)
{
        if (price <= 0)
        {
                RemoveIndicatorTrade(ticket, it);
                return NULL;
        }
        string sz0 = CreateIndicatorTrade(ticket, it, select);
        ObjectMove(Terminal.Get_ID(), sz0, 0, 0, price);
                                
        return sz0;
}

não confunda as duas funções, pois apesar de parecerem iguais, elas são diferentes, usar a sobrecarga é algo bastante comum, onde criamos uma função simples, e depois adicionamos novos parâmetros nela, de forma a acumular um tipo especifico de modelagem, se isto não fosse feito via sobrecarga, teríamos que repetir em alguns momentos, uma mesma sequencia de código, e isto além de ser perigoso pelo ponto de vista de que venhamos a esquecer de declarar algo, é pouco prático, então sobrecarregamos a função de forma a fazer apenas uma única chamada ao invés de fazer várias chamadas.

Uma coisa que merece ser mencionado aqui é a parte que esta em destaque nesta segunda versão, ela de certa forma não precisaria ser criada aqui, poderíamos fazer isto em outro local, mas quando o tentamos criar algum objeto que esta com o preço zerado, este na verdade deverá ser destruído.

Para de fato ver um momento em que isto acontece, veja o fragmento logo abaixo:

class C_Router : public C_ObjectsTrade
{

// ... Código interno da classe ....

                void UpdatePosition(int iAdjust = -1)
                        {

// ... Código interno da função ...

                                for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
                                {
                                        ul = PositionGetInteger(POSITION_TICKET);
                                        m_bContainsPosition = true;
                                        CreateIndicatorTrade(ul, PositionGetDouble(POSITION_PRICE_OPEN), IT_PRICE, false);
                                        CreateIndicatorTrade(ul, take = PositionGetDouble(POSITION_TP), IT_TAKE, true);
                                        CreateIndicatorTrade(ul, stop = PositionGetDouble(POSITION_SL), IT_STOP, true);

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

Toda vez que o EA recebe um evento OnTrade, ele irá executar a função acima, e nos pontos em destaque, será feita a tentativa de criar o indicador, mas se o usuário retirar o limite, este estará zerado, desta forma quando for feita a chamada, ela de fato irá remover o indicador que existir no gráfico, nos poupando de ter objetos inuteis na memória, assim temos algum ganho em alguns momento, já que o teste será feito justamente no momento da criação.

Mas ainda temos a questão da sobrecarga, que talvez alguns não entendem de fato como usar em um código real, mas para entender, olhe os dois fragmentos de código abaixo:

class C_OrderView : public C_Router
{
        private  :
//+------------------------------------------------------------------+
        public   :
//+------------------------------------------------------------------+
                void InitBaseFinance(int nContracts, int FinanceTake, int FinanceStop, bool b1)
                        {                       
                                SetBaseFinance(nContracts, FinanceTake, FinanceStop, b1);
                                CreateIndicatorTrade(def_IndicatorTicket0, IT_PRICE, false);
                                CreateIndicatorTrade(def_IndicatorTicket0, IT_TAKE, false);
                                CreateIndicatorTrade(def_IndicatorTicket0, IT_STOP, false);
                        }
//+------------------------------------------------------------------+

// ... Restante do código ....
class C_Router : public C_ObjectsTrade
{

// ... Código da classe ...

                void UpdatePosition(int iAdjust = -1)
                        {
// ... Código da função ....
                                for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
                                {
                                        ul = PositionGetInteger(POSITION_TICKET);
                                        m_bContainsPosition = true;
                                        CreateIndicatorTrade(ul, PositionGetDouble(POSITION_PRICE_OPEN), IT_PRICE, false);

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

Vejam que em ambos casos temos o mesmo nome de função sendo usando e eles fazem parte da mesma classe, C_ObjectsTrade, mas mesmo assim o compilador consegue distinguir entre eles e o fato é o numero de parâmetros, mas se você olhar com atenção irá ver que a única diferença é um parâmetro extra. o preço, mas poderia ter vários outros, mas veja que é muito mais simples usar apenas uma chamada copiar todo o código que esta presente em uma das versões sobrecarregadas, então temos no final um código mais limpo e de manutenção mais simples.

Bem, mas vamos voltar para a classe C_ObjectsTrade, e a próxima função a ser entendida é vista abaixo:

bool GetInfosOrder(const string &sparam, ulong &ticket, double &price, eIndicatorTrade &it)
{
        string szRet[];
        char szInfo[];
                                
        if (StringSplit(sparam, def_SeparatorInfo, szRet) < 2) return false;
        if (szRet[0] != def_NameObjectsTrade) return false;
        StringToCharArray(szRet[1], szInfo);
        it = (eIndicatorTrade)szInfo[0];
        ticket = (ulong) StringToInteger(szRet[2]);
        price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE);
                                
        return true;
}

Esta é de fato o coração, a mente e o corpo de todo o nosso novo sistema, apesar de parecer bastante simples e singela, ela faz um trabalho que é extremamente importante para todo o EA vim a funcionar conforme nosso novo sistema de modelagem exige.

Prestem muita atenção ao código em destaque: Reparem na função StringSplit, se ela não existisse no MQL5, teríamos que codifica-la, mas felizmente ela está presente no MQL5, então vamos usa e abusar desta rotina. Bem o que ela faz é decompor o nome do objeto em dados relevantes para nos, quando o nome do objeto é criado, ele é modelado de uma forma bastante especifica, e por este motivo, podemos também desfazer a esta modelagem codificadora, então o que a função StringFormat faz a StringSplit irá desfazer.

Vejam que o resto da rotina é justamente isto, a capturar os dados presentes no nome do objeto, de forma que poderemos testar e usar isto depois, ou seja o MetaTrader 5 gera os dados para nos, nos os decompomos de forma a saber do que aconteceu, e depois falamos para o MetaTrader 5 quais as providencias que ele deverá tomar, nosso trabalho será este, fazer com que o MetaTrader 5 trabalhe para nos, não quero criar uma plataforma do ZERO, mas sim modelar uma interface e um EA do ZERO e para fazer isto temos que tirar proveito de cada mínimo suporte que o MQL5 nos dá antes de procurar uma solução externa.

Bem da mesma forma, que fizemos acima, iremos fazer algo muito parecido no código abaixo:

inline void RemoveAllsIndicatorTrade(bool bFull)
{
        string sz0, szRet[];
        int i0 = StringLen(def_NameObjectsTrade);
                                
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        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_NameObjectsTrade)
                {
                        if (!bFull)
                        {
                                StringSplit(sz0, def_SeparatorInfo, szRet);
                                if (StringToInteger(szRet[2]) == def_IndicatorTicket0) continue;
                        }
                }else continue;                                         
                ObjectDelete(Terminal.Get_ID(), sz0);
        }
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
}

Toda vez que retiramos uma linha do gráfico, seja uma posição que será fechada, um limite que será retirado, temos que remover o objeto correspondente, assim como no momento em que o EA e retirado do gráfico, temos que remover os objetos, mas temos também um conjunto de linhas que não deverá ser removido sem que de fato seja necessário, que é o Ticket0, este não deverá ser removido a não ser que seja extremamente necessário, então para evitar isto temos o código em destaque, sem este código, teríamos que ficar recriando a todo momento este Ticket0, pois este ticket é muito importante em um outro ponto do código, que irei mostrar um pouco mais a frente.

Em outras vezes vezes temos que retirar algo bastante especifico, neste caso temos uma outra rotina de remoção de objetos, e esta é vista a seguir.

inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it != NULL) && (it != IT_PRICE))
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, it));
        else
        {
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_PRICE));
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_TAKE));
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_STOP));
        }
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
}

A próxima rotina que apareceu pode ser vista logo baixo:

inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy)
{
        double ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal());
        ObjectMove(Terminal.Get_ID(), MountName(ticket, it), 0, 0, price);
        if (it == IT_PRICE)
        {
                ObjectMove(Terminal.Get_ID(), MountName(ticket, IT_TAKE), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))));
                ObjectMove(Terminal.Get_ID(), MountName(ticket, IT_STOP), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)));
        }
}

Esta irá posicionar os objetos no eixo dos preços, mas não se apeguem muito a ela, já que ela irá deixar de existir em breve, isto por conta de vários motivos, mas entre eles tem um que já foi trabalhado e explicando em um outro artigo desta mesma serie o artigo em questão é: Indicadores múltiplos em um gráfico ( Parte 05) - Transformando o MetaTrader 5 em um sistema RAD ( I ), neste em um dado ponto existe uma tabela que mostra os objetos que pode usar coordenadas cartesianas para serem posicionadas, e estas coordenadas são do tipo X e Y, as coordenadas de preço e tempo, apesar de ser útil em alguns casos não é algo muito viável quando vamos posicionar elementos que tem que ficar posicionados em pontos específicos da tela, apesar de ser mais rápido desenvolver as coisas usando coordenadas de preço e tempo, elas são muito mais complicadas de se trabalhar do que o sistema do tipo X e Y.

A mudança será feita em um outro momento, neste primeiro o desejo de fato é criar um sistema alternativo ao que esta sendo usado até o momento.

Seguindo temos uma última rotina relevante dentro da classe C_ObjectsTrade, e esta é vista no código seguinte

inline double GetDisplacement(const bool IsBuy, const double Vol, eIndicatorTrade it) const
{
        int i0 = (it == IT_TAKE ? m_BaseFinance.FinanceTake : m_BaseFinance.FinanceStop),
            i1 = (it == IT_TAKE ? (IsBuy ? 1 : -1) : (IsBuy ? -1 : 1));
        return (Terminal.AdjustPrice(i0 * (Vol / m_BaseFinance.Leverange) * Terminal.GetAdjustToTrade() / Vol) * i1);
}

Esta rotina irá fazer uma conversão entre os valores indicados no Chart Trader para a ordem que ficará pendente, ou uma posição que será aberta a mercado.

Todas estas mudanças foram feitas de forma a transformar a rotina C_HLineTrade em C_ObjectsTrade, mas ao fazer estas mudanças, também foi necessário fazer outras mudanças, mas a classe que de fato mudou profundamente foi a classe C_ViewOrder, várias partes desta classe simplesmente deixaram de existir, pois não fazia mais sentido elas existirem, mas as rotinas que ficaram sofreram mudanças, e as que merecem destaque são vista abaixo.

A primeira é a rotina de inicialização dos dados vindos do Chart Trader

void InitBaseFinance(int nContracts, int FinanceTake, int FinanceStop, bool b1)
{                       
        SetBaseFinance(nContracts, FinanceTake, FinanceStop, b1);
        CreateIndicatorTrade(def_IndicatorTicket0, IT_PRICE, false);
        CreateIndicatorTrade(def_IndicatorTicket0, IT_TAKE, false);
        CreateIndicatorTrade(def_IndicatorTicket0, IT_STOP, false);
}

os pontos em destaque são onde de fato o Ticket0 é criado, e este ticket é usado para posicionar uma ordem pendente via mouse e teclado ( SHIFT ) para compra ( CTRL ) para a venda, antes era criada linhas neste ponto que depois era usadas para indicar onde a ordem seria posicionada, mas agora, a coisa é bem mais simples, então da mesma forma que vemos uma ordem que será posicionada, veremos também uma pendente ou uma posição aberta, ou seja, iremos estar sempre testando o sistema, é como se você montasse um veiculo, e a todo momento estaria testando os freios, para que quando tivesse de fato que usa-lo, saberia como ele irá se comportar.

O grande problema de muitos código é que as vezes, uma função é criada, e só iremos saber de fato se ela funciona ou não, no momento em que ela for de fato ser usada, mas fazendo desta forma, o sistema sempre estará sendo testado, mesmo que não usemos todas as funcionalidades, elas sempre estarão sendo testadas, já que a reutilização do código acontece em diversos pontos.

A última rotina que irei mencionar neste artigo é vista abaixo, ela é que irá fazer o posicionamento da ordem pendente, vejam que ela ficou extremamente mais compacta com relação a mesma rotina vista em artigos passados.

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();
        PositionAxlePrice((bKeyBuy != bKeySell ? price : 0), def_IndicatorTicket0, IT_PRICE, (bCheck ? 0 : GetBaseFinanceTakeProfit()), (bCheck ? 0 : GetBaseFinanceStopLoss()), GetBaseFinanceLeveRange(), bKeyBuy);
        if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, local = price);
        local = (local != price ? 0 : local);
}

E o motivo é que agora o sistema irá contar com um novo padrão, desta forma a rotina emagreceu e ficou mais compacta.


4.0 - Conclusão

Aqui apresentei as mudanças que serão de fato usadas no próximo artigo, isto tudo foi para não complicar, ou apresentar as coisas já totalmente diferentes de uma hora para outra, o desejo aqui é que todos acompanhem e aprendam como fazer para programar um EA que será usado para lhe auxiliar nas operações e não simplesmente apresentar um sistema já pronto e terminado, quero mostrar que existem problemas a serem solucionados, e qual foi o caminho que eu tomei para resolver as questões e problemas que vão surgindo durante o desenvolvimento, espero que vocês entendem isto, pois se a ideia fosse criar um sistema e apresenta-lo já pronto, seria melhor eu faze-lo e vender a ideia depois, mas não é esta a minha intenção ...