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

Desenvolvendo um EA de negociação do zero (Parte 26): Em direção ao futuro (I)

MetaTrader 5Exemplos | 2 agosto 2022, 17:14
629 0
Daniel Jose
Daniel Jose

1.0 - Introdução

Apesar das correções e melhorias no código mostradas nos artigos Desenvolvendo um EA de negociação do zero (Parte 24) e (Parte 25), onde mostrei como aumentar a robustez do sistema como um todo, ainda assim ficou alguns detalhes meio que de fora daquela jogada, não que eles são menos importantes, na verdade eles são tão importantes quanto.

O problema é que existem questões que são dependentes de como você deseja operar e que tipo de coisa você estará fazendo no momento em que estiver operando. Muitos operadores simplesmente colocam a ordem em um determinado preço e não movem ela daquele ponto, não importa o que aconteça eles estabelecerão que aquele é o ponto de entrada ideal e não mudam a ordem de lugar, podem até mover os limites para outro local, ou em alguns casos até mesmo retirar estes limites, mas eles não mudam o ponto de entrada.

Bem por conta disto as falhas que ainda persistem no código, não irá afetar a forma de como estes operadores irão de fato agir. Na verdade eles se quer irão se dar conta que o sistema de ordens do EA contem falhas, como as que iremos corrigir neste artigo, mas quem gosta de ficar correndo atras do preço tentando entrar de qualquer forma em um Trade, mas não querem entrar a mercado, irá presenciar várias falhas no sistema, algumas podem lhe atrapalhar muito, tornado as operações pouco seguras, e isto para dizer o mínimo, já outras irá lhe tirar dinheiro a rodo, pondo você de joelhos perante o mercado.


2.0 - Implementação

Para começar a nossa jornada neste artigo, vamos começar corrigindo uma falha que torna o EA um verdadeiro TRITURADOR de dinheiro, novamente, se você não fica mudando o ponto de entrada a todo o momento, esta falha não irá lhe afetar, mas pelo sim ou pelo não aconselho você pensar seriamente em corrigir o código, apesar de tudo no código em anexo a correção já estará implementada, mas talvez você ache que isto irá prejudicar o EA, tirando algum desempenho dele, isto é verdade, mas lhe pergunto: Qual é melhor, perder um pouco de desempenho, ou correr o risco de perder dinheiro em uma entrada mal feita ?!


2.0.1 - Falha no ponto de entrada

Esta é a primeira que iremos corrigir, se bem que temos que corrigir elas de alguma forma, mas esta é de longe a mais catastrófica de todas, ela acontece quando se colocar uma entrada pendente, suponhamos BUY STOP, e você move o ponto de entrada de forma que a ordem agora deveria ser do tipo BUY LIMIT, parece não haver problemas aqui, mas esta falha é bastante catastrófica, já que o EA no atual estagio de desenvolvimento não irá conseguir fazer a alteração da forma correta, na verdade muitos EA se quer fazem esta modificação, e caso isto venha a acontecer, você verá no gráfico uma informação, mas o servidor terá outra informação, e o sistema somente será corretamente atualizado quando a posição for aberta, até lá os dados estarão incoerentes, entre o mostrado no gráfico pelo EA e o que consta no servidor.

Apesar de em alguns casos temos apenas esta inconsistência em outros a coisa será um desastre total, para entender continue lendo o artigo com atenção.

Para corrigir este tipo de falha temos uma solução, que na verdade pode passar por caminhos diferentes para ser implementada, mas o principio de funcionamento será sempre o mesmo. Retirar a ordem do book, mover ela para a nova posição, mudar o tipo de ordem a ser executada, recolocar a ordem de volta no book. Basicamente é isto que deve ser feito, agora a forma de se fazer isto irá variar de implementação para implementação.

Vamos então implementar a solução mais básica inicialmente, mas ela não é perfeita, teremos que corrigir alguns problemas.

A solução é modificar a função abaixo adicionado as linhas em destaque.

void SetPriceSelection(double price)
{
        char Pending;
                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

Bem, apesar desta solução resolver em parte o problema, ela não resolve o problema como um todo, no caso das ordens BUY STOP e SELL STOP o problema é resolvido apenas adicionado estas simples linhas, mas para as ordens BUY LIMIT e STOP LIMIT o servidor irá executar imediatamente a ordem assim que você clicar para modificar o ponto de entrada, e o que é pior, você já entrará na posição perdendo, e caso a ordem esteja configurada como uma ordem oco, ou seja ela terá limites de ganho ou perda, e o ponto de Stop Loss esteja fora dos limites do preço, além do servidor executar imediatamente a ordem, ele também irá encerrar ela logo em seguida, ou seja um desastre total em sua conta de negociação, por isto sistemas de negociação são tão complexos de serem projetados, pois a pessoa faz alguns teste em uma conta DEMO, e se tudo parecer funcionar a pessoa parte para a conta REAL, e neste momento começa a perder dinheiro sem saber de fato o que esta acontecendo.

Vou reiterar mais uma vez: Esta falha NÃO AFETA que coloca o ponto de entrada no gráfico e não muda este ponto, o problema é quando este ponto é movimentado pelo operador.

Agora o problema a ser resolvido é com relação as ordens pendentes do tipo LIMIT já que as de STOP estão funcionando. Mas apesar de parecer simples resolver esta questão, é preciso que você entenda uma coisa: NÃO existe uma solução perfeita, existe a solução mais adequada por parte de quem esta desenvolvendo o sistema, mas esta solução pode não ser exatamente a que irá lhe beneficiar.

Sabendo disto, vou mostrar uma das possíveis soluções para este problema. A solução será implementada nesta mesma rotina acima, então vamos ver como o código dela ficou, depois da solução ter sido implementada.

void SetPriceSelection(double price)
{
        char Pending;
        double last;
        long orderType;
                                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        orderType = OrderGetInteger(ORDER_TYPE);
                        if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                        {
                                last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
                                {
                                        RemoveOrderPendent(m_Selection.ticket);
                                        RemoveIndicator(m_Selection.ticket);
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                                        break;
                                }
                        }
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

O que esta sendo feito é o seguinte: Quando vamos mudar o ponto de entrada de uma ordem pendente, verificamos se a ordem que se encontra no BOOK é uma ordem do tipo STOP LIMIT ou BUY LIMIT, caso isto não seja o caso, o fluxo de execução continua para um outro ponto no código, caso a ordem seja do tipo especificado, fazemos a captura imediata do preço atual do ativo, e iremos nos basear no seguinte critério: Caso estejamos comprando, iremos capturar o valor ASK atual, caso estejamos vendendo iremos usar o valor BID, isto faz a substituição do antigo método de usar o valor LAST, já que este não é usado em alguns mercados não iremos usar este como referencia. Uma vez feito isto fazemos uma verificação de forma a saber se a ordem no BOOK será invalidada ou se ela irá apenas ser modificada.

Caso a ordem ainda seja valida, o sistema ignora o código de verificação e irá passar para a parte onde a ordem é modificada. Mas caso a ordem do book seja invalida, o sistema irá executar o seguinte código:

RemoveOrderPendent(m_Selection.ticket);
RemoveIndicator(m_Selection.ticket);
CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
break;

Apesar de isto ser bonito, este código acima irá apenas modificar as ordens do tipo SELL LIMIT e BUY LIMIT passando elas para SELL STOP e BUY STOP respectivamente, mas e se desejarmos reverter estes tipos, fazendo eles voltarem para o original, ou simplesmente impedindo que esta mudança aconteça, como deveremos proceder nestes casos ?!?!

Caso você não queira que o sistema mude o tipo de ordem a ser executada, a forma de resolver é simplesmente mudando o trecho em destaque acima pelo seguinte código:

if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
{
        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
        {
                RemoveOrderPendent(m_Selection.ticket);
                RemoveIndicator(m_Selection.ticket);
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                m_TradeLine.SpotLight();
                break;
        }
}

Este código irá impedir que o tipo de ordem seja modificado, você pode modificar o ponto em que a ordem pendente irá se executada, mas não poderá mudar uma ordem do tipo LIMIT para uma do tipo STOP ou vice versa. Agora caso você de fato deseje ficar correndo atras do preço de forma a forçar uma entrada em um determinado ponto, deverá usar o código mostrado a seguir, este será o código presente no EA.

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, m_Selection.pr,  m_Selection.tp, m_Selection.sl, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorFloat);
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

Aviso Importante: Muito cuidado ao analisar este código por conta da macroForceNewType, notem que esta macro contem um break interno, quando este break é executado ele irá fazer com que o código saia de dentro do bloco case, então muita, mas muita atenção ao modificar ou estudar este código.

Mas fora isto, o sistema agora não irá mais ter a falha de movimento de pontos de entrada, mas temos outros problemas a serem resolvidos, lembrando que aqui mostrei a forma de corrigir o problema com a modificação ou mantendo o mesmo tipo de ordem, você escolhe o que melhor lhe atende, lembrando que cada uma destas soluções tem seus prós e contras. Mas não irei entrar neste mérito, irei apenas mostrar como corrigir e implementar o sistema, o que cada um irá fazer com ele, é uma questão pessoal.

O resultado das alterações acima podem ser vista no video abaixo:



2.0.2 - Preparando-se para o futuro

Apesar da mudança feita acima resolver o problema, existe algo a mais que pode ser feito, aqui irei mostrar o inicio desta mudança. Se você observar, o sistema de ordens do EA ainda pode ser muito melhorado, de forma a tornar o operacional mais simples ou mais claro para quem esta de fato usando o sistema. As mudanças necessárias não são muitas, mas quero explicar elas de forma que vocês possam fazer as escolhas de qual caminho será melhor para o seu modo de operar, já que cada operador tem uma forma própria de observar e agir dentro do mercado, não quero que ninguém se sinta obrigado a utilizar o sistema que estou mostrando, mas quero que ele sirva de base para que cada um consiga criar um EA adequado ao seu estilo de operar.

Dito isto vamos ao seguinte fato: Desde o artigo Desenvolvendo um EA do Zero ( Parte 18 ), estou mostrando como desenvolver um sistema de ordens fácil de ser usado por quem esta operando um determinado ativo. mas na Parte 20 desta serie o sistema de ordens recebeu elementos visuais, mas aqueles elementos não estão ali por um acaso, eles estão ali pois em algum momento o Chart Trade irá se tornar desnecessário para se operar, pois tudo estará e será indicado pelo próprio sistema de ordens, podendo ser mudado e ajustado muito facilmente diretamente no gráfico, mas para chegar neste ponto temos que começar de algum lugar, e iremos fazer isto neste exato momento.

Que tal mudar o volume negociado diretamente na ordem, sem realmente ter que tirar a ordem do gráfico, mudar o volume no Chart Trade, e depois repor a ordem no gráfico ?!?! Ficou curioso ... não é mesmo ... pois isto é uma funcionalidade que iremos implementar neste exato momento, e que ajuda muito em vários cenários, mas já vou avisando, é preciso aprender e entender como usar o sistema, pois não é algo que você irá encontrar em nenhuma outra plataforma, para dizer a verdade eu nunca vi nenhum EA tendo esta funcionalidade, mas vamos ver o que será preciso fazer para se ter esta funcionalidade no nosso amado EA.

Em primeiro lugar iremos definir um novo index de indicador.

#define def_IndicatorFloat      3

quando uma ordem pendente ficar com este valor como sendo um ticket, ela poderá ser manipulada de uma forma totalmente diferente. mas tudo que foi visto até aqui irá se manter no sistema de ordens, apenas adicionamos um novo index para ser usado.

Feito isto vamos adicionar um novo objeto no nosso sistema:

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose,
                        m_BtnCheck;
C_Object_Edit           m_EditInfo1,
                        m_EditInfo2;
C_Object_Label          m_BtnMove;

Este objeto irá nos permitir fazer diversas coisas, desde que a ordem seja do tipo pendente, poderemos fazer várias coisas.

Agora iremos na classe C_Object_BitMap, e faremos algumas mudanças estas classe, a primeira coisa a fazer é adicionar algumas definições:

#define def_BtnClose            "Images\\NanoEA-SIMD\\Btn_Close.bmp"
#define def_BtnCheckEnabled     "Images\\NanoEA-SIMD\\CheckBoxEnabled.bmp"
#define def_BtnCheckDisabled    "Images\\NanoEA-SIMD\\CheckBoxDisabled.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
#resource "\\" + def_BtnCheckEnabled
#resource "\\" + def_BtnCheckDisabled

mas precisamos ter uma forma de saber o que esta acontecendo nesta classe, então adicionamos as seguintes rotinas nela:

bool GetStateButton(string szObjectName) const
{
        return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
}
//+------------------------------------------------------------------+
inline void SetStateButton(string szObjectName, bool bState)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, bState);
}

GetStateButton irá retornar o status do botão, apesar de tudo o MT5 faz a alteração do status para nos, isto sem que tomemos nenhuma providencia extra, por conta disto temos que saber se o botão esta com um valor Verdadeiro ou Falso, mas pode acontecer do status não representar o que de fato desejamos, então usamos SetStateButton para colocar o status de forma a refletir a real condição vista pelo servidor de negociação e pelo EA.

Também iremos fazer uma outra modificação, agora na classe C_Object_Edit, esta será algo bastante simples:

inline void SetOnlyRead(string szObjectName, bool OnlyRead)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, OnlyRead);
}

isto irá dizer se podemos ou não editar o valor. Lembre-se queremos mudar o volume das ordens diretamente no gráfico, sem ter que recorrer ao Chart Trade para isto. Toda e qualquer ordem pendente que esta sendo criada, sempre estará no modo somente leitura, mas iremos criar um sistema que nos permita mudar isto.

Voltemos então para a classe C_IndicatorTradeView, e vamos promover mais algumas mudanças no código. Vamos criar uma nova rotina para o sistema, esta pode ser vista logo abaixo:

#define macroSwapAtFloat(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorFloat, A, B));
                bool PendingAtFloat(ulong ticket)
                        {
                                eIndicatorTrade it;
                                
                                if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;
                                macroSwapAtFloat(IT_PENDING, EV_CHECK);
                                for (char c0 = 0; c0 < 3; c0++)
                                {
                                        switch(c0)
                                        {
                                                case 0: it = IT_PENDING;        break;
                                                case 1: it = IT_STOP;           break;
                                                case 2: it = IT_TAKE;           break;
                                                default:
                                                        return false;
                                        }
                                        macroSwapAtFloat(it, EV_CLOSE);
                                        macroSwapAtFloat(it, EV_MOVE);
                                        macroSwapAtFloat(it, EV_EDIT);
                                        macroSwapAtFloat(it, EV_GROUND);
                                        macroSwapAtFloat(it, EV_LINE);
                                        m_EditInfo1.SetOnlyRead(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT), false);
                                }
                                return true;
                        }
#undef macroSwapAtFloat

O que esta rotina faz é o seguinte: quando ela for chamada, todos os objetos do indicador serão renomeados, ou seja o valor que indicava o ticket da ordem será substituído por um outro valor, que no caso é indicador lá no começo deste tópico, mas temos uma outra questão. Não estou usando nenhuma estrutura para manter a lista de objetos dos indicadores, estou fazendo isto de outra forma, o que fazemos é deixar o MT5 cuidar desta lista para nos, por conta disto não posso criar ilimitadas ordens flutuantes, teremos a limitação de ter apenas uma única ordem flutuante, e para testar isto fazemos o uso da linha:

if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;

O teste aqui é simples, caso a linha do indicador esteja posicionada em algum ponto, a macro irá retornar um valor diferente de 0, assim saberemos que existe um indicador já usando o ticket reservado. Isto será importante depois para que o EA repare os dados do indicador que esta tendo seu pedido negado, lembre-se o MT5 modifica o status dos objeto Bitmap de forma automática, por conta disto temos que informar ao chamador que ocorreu uma falha.

A próxima mudança a ser feita é na rotina que cria os indicadores:

#define macroCreateIndicator(A, B, C, D)        {                                                                               \
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                        \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                                     \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : (A == IT_PENDING ? 108 : 92)), (A == IT_RESULT ? 34 : 22));       \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                                   \
                m_EditInfo1.Size(sz0, 60, 14);                                                                                  \
                if (A != IT_RESULT)     {                                                                                       \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);            \
                        m_BtnMove.Size(sz0, 21, 23);                                                                            \
                                        }else                   {                                                               \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);                   \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                                       \
                                                }
                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING:
                                                macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit);
                                                m_BtnCheck.Create(ticket, sz0 = macroMountName(ticket, it, EV_CHECK), def_BtnCheckEnabled, def_BtnCheckDisabled);
                                                m_BtnCheck.SetStateButton(sz0, true);
                                                break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

Todos os pontos em destaque foram adicionados de forma a dar suporte ao nosso novo sistema, basicamente aqui estamos criando um checkbox que sempre será iniciado de forma verdadeira, isto indica que a ordem será colocada de forma imediata no book, não queria modificar esta forma de operar, mas não é o simples fato de mudar o valor do checkbox de verdadeiro para falso que irá fazer com que as ordens não sejam colocadas diretamente no gráfico, para fazer esta mudança seria preciso fazer outra mudanças ainda mais profundas, e o problema é que você em algum momento pode vim a colocar uma ordem e se esquecer de marcar o checkbox, o ponto de entrada passa e você fica imaginando que o EA esta com defeito, quando na verdade foi tudo por esquecimento, então para evitar isto, por padrão as ordens pendentes irão diretamente para o book, você terá que mudar o status dela de forma explicita.

A próxima rotina realmente importante para nos é vista logo abaixo:

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        switch(m_Selection.it)
                                        {
                                                case IT_STOP   : m_Selection.sl = price; break;
                                                case IT_TAKE   : m_Selection.tp = price; break;
                                                case IT_PENDING:
                                                        m_Selection.sl = def_AdjustValue(m_Selection.sl);
                                                        m_Selection.tp = def_AdjustValue(m_Selection.tp);
                                                        m_Selection.pr = price;
                                                        break;
                                        }
                                        m_Selection.ticket = 0;
                                        m_TradeLine.SpotLight();
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

Os trechos em destaque no código acima fazem algo bastante curioso, eles apenas atualizam os valores que deverão ser usados no seletor, mas os valores são de fato armazenados no próprio indicador, mas pode acontecer de movermos o sistema de uma forma mais geral então precisamos que estes valores sejam indicados no seletor, isto para que a rotina que faz os cálculos da posição indique os valores corretos.

Mas tem algo que pode não fazer sentido ao se olhar esta rotina. É fato que ela é a responsável por criar e modificar os dados de uma ordem pendente, mas se você olhar para ela, não é possível ver algum ponto onde a ordem pendente irá voltar para o book, você pode mover, modificar e ajustar o valor de volume da ordem diretamente no gráfico, mas não se conseguirá notar como ela irá voltar para o gráfico.

Isto é um fato, todo o sistema de mudança e criação de ordens pendentes, irá acontecer nesta rotina acima, mas apesar de parecer estranho, ela não recoloca a ordem no book apenas por que desejamos fazer isto, a rotina que realmente faz o pedido é vista logo abaixo, para facilitar a vida estarei apenas mostrando o trecho que realmente será responsável por criar o pedido de colocar a ordem no book.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{

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

        case CHARTEVENT_OBJECT_CLICK:
                if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                {
                        case EV_CLOSE:
                                if (ticket == def_IndicatorFloat) RemoveIndicator(def_IndicatorFloat, it);
                                else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
                                {
                        case IT_PENDING:
                        case IT_RESULT:
                                if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                                break;
                        case IT_TAKE:
                        case IT_STOP:
                                m_Selection.ticket = ticket;
                                m_Selection.it = it;
                                SetPriceSelection(0);
                        break;
                }
                break;
        case EV_MOVE:
                if (ticket == def_IndicatorFloat)
                {
                        m_Selection.ticket = ticket;
                        m_Selection.it = it;
                }else   CreateGhostIndicator(ticket, it);
                break;
        case EV_CHECK:
                if (ticket != def_IndicatorFloat)
                {
                        if (PendingAtFloat(ticket)) RemoveOrderPendent(ticket);
                        else m_BtnCheck.SetStateButton(macroMountName(ticket, IT_PENDING, EV_CHECK), true);
                } else
                {
                        m_Selection.ticket = def_IndicatorTicket0;
                        m_Selection.it = IT_PENDING;
                        m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
                        m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
                        m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
                        m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
                        m_Selection.bIsDayTrade = true;
                        m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
                        SetPriceSelection(m_Selection.pr);
                        RemoveIndicator(def_IndicatorFloat);
                }

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

Vejam como o sistema vai se auto construindo, vamos programando cada vez menos e o sistema vai crescendo cada vez mais.

Todo o código em destaque tem alguma relação com o indicador que criamos lá no inicio do tópico, mas apesar de tudo funcionar aparentemente bem, temos algumas coisas que serão modificadas depois, já que uma ordem flutuante quando volta para o book ela irá ter como desvantagem o fato de ser uma ordem de day trade, ou seja ela irá se encerrar no final do dia, iremos mudar isto depois, mas tenha esta noção. Agora pode ser que muitos de vocês estejam confusos com tudo isto, e ainda não entenderam como de fato a ordem pendente entra e sai do book, quando clicamos no checkbox. Para tentar entender veja o fluxograma baixo:

Vejam que todas as chamadas são oriundas de um único local, então temos a remoção da ordem do book, mas ela continuará no gráfico, e toda a manipulação é feita conforme foi vista nos artigos anteriores, mas se você procurar encontrar um momento especifico que a ordem volta para o book pode ficar meio perdido no código, mas observando o fluxograma, nota-se que a chamada é originada na função DispatchMessage, pois é o único local que chama a função SetPriceSelection, mas se olhar a função SetPriceSelection, não existe nenhuma referencia de criação de uma ordem com o index usado no sistema flutuante. Mas observem uma coisa, temos a criação da ordem pelo index 0, e é justamente isto que usamos, trocamos o ticket da ordem e informamos que ele será o ticket de index 0, desta forma a ordem será criada. Veja o fragmento abaixo para entender como isto funciona.

m_Selection.ticket = def_IndicatorTicket0;
m_Selection.it = IT_PENDING;
m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
m_Selection.bIsDayTrade = true;
m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
SetPriceSelection(m_Selection.pr);
RemoveIndicator(def_IndicatorFloat);

Todo o código esta perfeito, exceto pela linha destacada. Mas no momento ainda não temos como corrigir isto, esta correção será vista no próximo artigo, já que teremos que fazer algumas mudanças na própria classe.

O resultado das mudanças podem ser vista neste video abaixo, atenção ao como o volume é modificado e como uma nova ordem é enviada no ponto indicado, desta forma fica bem mais fácil de operar.



Arquivos anexados |
DoEasy. Controles (Parte 3): Criando controles vinculados DoEasy. Controles (Parte 3): Criando controles vinculados
Neste artigo, analisaremos a criação de controles subordinados, vinculados ao elemento que serve de base, criados diretamente por meio da funcionalidade do controle base. Além da tarefa definida acima, trabalharemos um pouco no objeto sombra do elemento gráfico, pois ainda persistem alguns erros de lógica ao aplicá-lo a qualquer um dos objetos que permitem ter sombra.
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel
Neste artigo, vamos nos livrar de alguns erros ao trabalhar com elementos gráficos e continuar desenvolvendo o controle CPanel. Isto último irá se tratar de métodos para definir os parâmetros da fonte, que é usada por padrão para todos os objetos de texto do painel, objetos esses que, por sua vez, podem ser localizados nele no futuro.
DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock
Neste artigo, vamos gerar o funcionamento de alguns parâmetros de painel, nomeadamente Padding (margens internas/campos para todos os lados do elemento) e Dock (a forma como o objeto está localizado dentro do contêiner).
DoEasy. Controles (Parte 1): Primeiros passos DoEasy. Controles (Parte 1): Primeiros passos
Com este artigo, iniciamos um tópico extenso sobre a criação de controles em MQL5 com base no estilo do Windows Forms. E vamos começar criando uma classe-painel. Tudo já está se tornando difícil sem a presença de controles. Por isso, criaremos todos os controles possíveis no estilo do Windows Forms.