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

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

MetaTrader 5Exemplos | 18 agosto 2022, 13:41
732 1
Daniel Jose
Daniel Jose

1.0 - Introdução

Quando o sistema de ordens começou de fato a ser desenvolvido e isto lá em Desenvolvendo um EA de negociação do Zero ( Parte 18 ), eu não tinha ideia de quanto tempo iria demorar para chegar a este momento. Pois bem, passamos por diversas coisa, mudanças, ajustes. Demonstrei como fazer algumas coisas especificas, como fazer marcações, ter um sistema mais intuitivo, mas ainda assim existia algo que eu não podia mostrar até este momento pois a estrada ainda não estava de toda pavimentada, nos permitindo construir um conceito de forma que todos entendam a ideia e saibam como o sistema funciona.

Em todos os artigos anteriores eu fui pavimentando a estrada de forma que podesse chegar a este artigo tendo você em um mesmo nível de entendimento de como o sistema funciona, isto para não deixar este artigo especifico extremamente confuso, ou difícil de ser entendido. Desde o começo existia uma questão que eu evitava entrar em detalhes, e que faz muita falta para um operador mais experiente, pode a primeira vista ser algo tolo, ou bobo, mas quando se esta de fato operando é que você se dará conta que lhe falta algo no EA. E o que seria isto ?!?! Estou falando de uma forma de você conseguir repor os valores de take e stop, que foram retirados por um motivo, e você os quer novamente no gráfico.

Se você já tentou fazer isto, deve ter notado que é algo bastante complicado, e muito lento de ser feito, isto por que você precisa ter que seguir um certo roteiro para que as coisas deem certo, caso contrário irá acabar fazendo bobagens.

Pois bem a plataforma MetaTrader 5 conta com uma sistema de boleta, que nos permite criar, ou ajustar os valores das ordens. Isto é fato, mas a ideia de se ter um EA é para nos ajudar a fazer esta mesma boletagem de uma maneira mais rápida e eficiente, não que o sistema do MetaTrader 5 seja inadequado, mas ele é consideravelmente mais lento e sujeito a erros do que fazer uso do EA que estou mostrando como desenvolver.

Mas em nenhum momento até agora, eu expliquei como fazer para gerar os valores de limites ( Take e Stop ) de uma ordem ou posição, retirar estes limites acredito que tenha ficado bastante claro e suficientemente intuitivo a ponto de você saber exatamente como proceder. Mas e quanto a maneira de fazer isto, colocar limites ou repor eles diretamente no gráfico, como deveremos proceder ?!?! ... pois bem, isto é algo bastante intrigante, porém envolve algumas questões, e este é o motivo da criação deste artigo. Mostrar uma de tantas formas que você poderá usar para criar estes limites, e isto diretamente no gráfico, sem recorrer a nenhum recurso externo, apenas usando o sistema de ordens do EA.


2.0 - Iniciando a implementação

Bem antes de fazer qualquer coisa devemos impedir que o EA execute uma checagem que vem sendo feita a muito e muito tempo, desde os primórdios de seu desenvolvimento. A retirada desta checagem é feita removendo todo o código que se encontra riscadas e adicionando o código destacado:

inline double SecureChannelPosition(void)
{
        double Res = 0, sl, profit, bid, ask;
        ulong ticket;
                                
        bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
        ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                IndicatorAdd(ticket = PositionGetInteger(POSITION_TICKET));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), Res += PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (ask < sl) ClosePosition(ticket);
                }else
                {
                        if ((bid > sl) && (sl > 0)) ClosePosition(ticket);
                }
                Res += profit;
        }
        return Res;
};

mas o código riscado não irá de fato deixar de existir, ele irá retornar em um outro momento, mas por agora ele irá mais nos atrapalhar do que qualquer outra coisa. Feito isto podemos começar a pensar em como iremos de fato implementar o nosso sistema de criação de take e stop diretamente no gráfico, sem auxilio de nenhum outro recurso que não seja o EA.

Cada desenvolvedor terá uma ideia diferente para executar esta tarefa, algumas serão mais simples de serem entendidas pelo operador, outras mais complicadas, em alguns casos a implementação será mais complicada e em outros mais simples. Não vou dizer que a forma que irei criar e demonstrar aqui é a ideia mais adequada, ou a mais simples, mas foi de longe a que melhor se encaixou na minha forma de operar e utilizar a plataforma, já que não irei precisar criar nenhum elemento novo, apenas ajustar algumas coisas no código.


2.0.1 - Modelando o sistema de arrasto

Basicamente pelo sistema que irei criar, o próprio código do EA no atual estágio de desenvolvimento já nos dá algumas pistas de como deveremos fazer. Observem o código a seguir:

#define macroUpdate(A, B) if (B > 0) {                                                                  \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                     } else RemoveIndicator(ticket, A);
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetLinePrice(ticket, IT_RESULT);
                                        if ((pr == 0) && (macroGetLinePrice(ticket, IT_PENDING) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                if (m_Selection.tp > 0) macroUpdate(IT_TAKE, tp);
                                if (m_Selection.sl > 0) macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

As linhas em destaque contém uma macro que irá executar uma tarefa na qual deveremos modificar para que venhamos a ter a ajuda necessária de forma a implementar o que precisamos, o indicador de limite, mas vamos olhar com mais calma o próprio código da macro chamada, este pode ser visto no fragmento logo abaixo:

#define macroUpdate(A, B){ if (B > 0) {                                                                 \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                        } else RemoveIndicator(ticket, A); }

O que estamos fazendo é o seguinte: Quando o valor B, que pode ser tanto o Take quanto o Stop, forem maiores que 0, iremos verificar se o indicador esta no gráfico, caso não esteja, iremos criá-lo, posicioná-lo e ajustar o valor que ele irá mostrar para nos, caso o valor B seja igual a 0 iremos remover completamente o indicador do gráfico e isto é feito no ponto em destaque no código da macro. Mas se ao invés de remover completamente o indicador do gráfico, mantivermos um elemento dele, e se este elemento pode-se ser ajustado de forma a representar o que queremos fazer, que seria criar o limite, ou limites faltantes na ordem, ou posição, a tornando uma ordem ou posição do tipo OCO novamente, será que isto nos bastaria ?!?! SIM, e esta é a ideia, deixar um elemento, no caso o objeto que nos permite mover os limites e quanto desejarmos criar o limite faltante, bastaria arrastar este elemento e o limite seria criado. Esta é a base teórica que iremos usar para criar o nosso sistema.

Mas o simples fato de fazer isto não nos dá tudo que precisamos, temos que fazer uma outra modificação no código antes de prosseguirmos, esta modificação pode ser vista no fragmento abaixo em destaque:

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

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

        m_Selection.tp = (valueTp == 0 ? 0 : m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp)));
        m_Selection.sl = (valueSl == 0 ? 0 : m_Selection.pr + (bKeyBuy ? (-valueSl) : valueSl));

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

}

O que estamos fazendo é testando se os valores iniciais informados pelo Chart Trade é ou não zero, caso eles sejam, nenhum indicador será criado, apenas o ponto de entrada será mostrado no gráfico.

Pronto, beleza, agora teremos um funcionamento mais linear em todo o resto do sistema, caso nenhum valor de limite seja indicado, todo a modelagem de ordens terá o mesmo comportamento, da mesma forma como esta sendo feito quando temos um valor indicado, e isto desde o inicio quando ainda vamos colocar uma ordem pendente no gráfico.

Desta forma nenhum operador irá ficar se perguntando: O que são estas figuras penduradas na ordem ou na posição ?!?! Pois ele irá estar ciente de que elas representam elementos que podem ser movidos no gráfico.

Mas apesar de tudo temos um pequeno problema e para resolver ele, iremos modificar duas macros, e isto pode ser visto logo abaixo:

#define macroSetLinePrice(ticket, it, price)    ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE, price)
#define macroGetLinePrice(ticket, it)           ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE)
#define macroGetPrice(ticket, it, ev)           ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, ev), OBJPROP_PRICE)

Esta modificação é muito importante para todo o restante do sistema que iremos montar. Mas por que estou retirando as linhas cortadas ?!?! O motivo é que precisamos flexibilizar ainda mais o sistema, e por conta disto este código riscado foi retirado e no lugar dele veio a linha em destaque. Mas você pode pensar por que temos uma macroGetPrice se não temos uma para colocar um valor no preço ?!?! Na verdade existe apenas e somente um único ponto que de fato faz este ajuste no preço, escrevendo ele no objeto do gráfico... este ponto pode ser visto no fragmento abaixo:

#define macroSetPrice(ticket, it, ev, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, ev), OBJPROP_PRICE, price)
//---

// ... Código extra dentro da classe ....

//---
inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price)
                        {
                                int x, y, desl;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                if (it != IT_RESULT) macroSetPrice(ticket, it, EV_MOVE, price);
                                macroSetPrice(ticket, it, EV_LINE, price);
                                macroSetAxleY(it);
                                switch (it)
                                {
                                        case IT_TAKE: desl = 160; break;
                                        case IT_STOP: desl = 270; break;
                                        default: desl = 0;
                                }
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2));
                        }

Por conta disto não há necessidade de que a macro para ajustar o preço nos objetos seja visível para todo os outros pontos do código, na verdade ela nem precisava ser uma macro, mas vou deixar ela como sendo uma, para reduzir a possibilidade de cometer algum erro mais para frente quanto este código vier a sofrer mudanças.

Bom agora que nosso sistema foi atualizado, podemos passar para o próximo tópico e fazer as coisas finalmente funcionarem, como o desejado.


2.0.2 - Uma nova rotina Update

Como eu havia dito no tópico anterior, tudo que temos que fazer é ajustar a rotina Update, e o próprio EA já conseguirá resolver nossos problemas, mas como o foco maior é apenas nos indicadores de Take e Stop, e estes são executados dentro de uma macro, tudo que precisamos é ajustar a macro de forma adequada.

Mas existe um pendencia a ser resolvida e deveremos fazer isto agora, precisamos que o botão de movimentação seja criado de forma independente do restante do indicador, e para fazer isto temos que isolar o código de criação deste botão, e com isto nasce o código que pode ser visto logo abaixo:

// ... código da classe ...

#define def_ColorLineTake       clrDarkGreen
#define def_ColorLineStop       clrMaroon

// ... código da classe ....

inline void CreateBtnMoveIndicator(ulong ticket, eIndicatorTrade it, color C = clrNONE)
                        {
                                string sz0 = macroMountName(ticket, it, EV_MOVE);

                                ObjectDelete(Terminal.Get_ID(), macroMountName(ticket, it, EV_MOVE));
                                m_BtnMove.Create(ticket, sz0, "Wingdings", "u", 17, (C == clrNONE ? (it == IT_TAKE ? def_ColorLineTake : def_ColorLineStop) : C));
                                m_BtnMove.Size(sz0, 21, 23);
                        }

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

Este código irá criar, apenas e somente o botão de movimentação para que possamos usá-lo depois, vejam que é algo bastante simples e direto, sem muitas complicações, até pensei em deixar este código como sendo uma macro também, mas decidi deixá-lo como sendo uma rotina mesmo, mas ele esta declarada como sendo uma rotina do tipo inline, então para o compilador seria a mesma coisa de uma macro. Observem que temos duas novas definições neste mesmo fragmento de código, isto para facilitar a nossa vida, já que em alguns momentos iremos criar apenas o botão de movimento, queremos que ele tenha as mesmas cores usadas no sistema completo, não é desejável que o sistema tenha comportamentos distintos ou aparência igualmente diferente em casos similares, e para reduzir a quantidade de problemas ou dificultar a vida, deixamos as cores declaradas como mostrado acima.

Agora podemos partir para a rotina update, ela é mostrada na integra logo abaixo, notem que a única diferença entre a versão abaixo e a versão apresentada anteriormente neste mesmo artigo, é somente o código que esta em destaque, e este código é a macro utilizada pela própria rotina update, nos pontos destacados dentro da mesma.

#define macroUpdate(A, B){                                                                                                      \
                if (B == 0) {   if (macroGetPrice(ticket, A, EV_LINE) > 0) RemoveIndicator(ticket, A);                          \
                                if (macroGetPrice(ticket, A, EV_MOVE) == 0) CreateBtnMoveIndicator(ticket, A);                  \
                            } else if (b0 = (macroGetPrice(ticket, A, EV_LINE) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, (B == 0 ? pr : B));                                                                \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                                        \
                        }
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetPrice(ticket, IT_RESULT, EV_LINE);
                                        if ((pr == 0) && (macroGetPrice(ticket, IT_PENDING, EV_MOVE) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetPrice(ticket, IT_PENDING, EV_MOVE));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                macroUpdate(IT_TAKE, tp);
                                macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

Mas vamos ver esta macro com mais detalhes, para entender o que esta acontecendo de fato, pois entender isto é primordial para se poder resolver alguns problemas que ainda aparecem quando você usar o sistema, lembre-se ele ainda não esta pronto, precisaremos fazer mais algumas mudanças.

Na primeira etapa teremos o seguinte comportamento: Quando um dos indicadores de limite, seja o Take ou Stop, forem retirados, imediatamente teremos a remoção deste indicador do gráfico do ativo operado, isto é feito verificando se existe ou não uma linha que é um dos pontos que indicam a presença ou não do indicador, logo depois iremos verificar se existe um objeto de movimento, deste mesmo indicador, no gráfico, caso ele não exista, ela será criado, então não teremos o indicador no gráfico, mas teremos um remanescente dele ainda presente no gráfico, que seria um objeto de movimentação.

A segunda etapa irá acontecer caso um limite seja criado, ou seja a ordem no servidor irá ter um limite que deverá aparecer no gráfico, neste caso o objeto que representava o movimento será retirado e um indicador completo será criado e colocado no local adequado, indicando onde estará o atual limite da ordem, seja ele um take ou stop.

Na ultima etapa iremos posicionar o indicador no ponto correto, um detalhe curioso aqui, caso o indicador de limite seja apenas um objeto que representa a possibilidade de movimento, o ponto onde ele será colocado é exatamente o preço da ordem ou posição, ou seja o objeto de movimento que permite criar o limite de take ou stop estará grudado na linha do preço da ordem ou da posição a qual ele pertence, desta forma ficará fácil notar se uma ordem ou posição tem um de seus limites ausentes na ordem ou posição.

Basicamente é isto que precisamos fazer, quando clicamos no objeto que indica o movimento, um fantasma será criado, com de costume, e ao mesmo tempo uma representação de um indicador completo também seria criada, isto é feito sem que nenhum outro código seja adicionado ou modificado, a partir deste ponto podemos mover e ajustar os pontos de limites de forma normal, da mesma forma como era feito anteriormente, mas até que cliquemos em um determinado ponto, o limite de fato não irá existir, isto ficará claro no video de demonstração no final este artigo, onde irei mostrar como o sistema funciona em uma conta real.

Mas apesar de parecer estar tudo bem, temos alguns inconvenientes aqui, que nos força a criar, ou melhor dizendo modificar o código em alguns pontos, e isto será mostrado no próximo tópico, pois esta etapa daqui terminou.


2.0.3 - Resolvendo o inconveniente dos indicadores flutuantes.

O primeiro destes inconvenientes é quanto estamos no modo de indicador flutuante, este esta no gráfico mas não esta no servidor, para mais detalhes sobre ele veja os artigos Desenvolvendo um EA de negociação do Zero ( Parte 26 ) e ( Parte 27 ), nestes artigos mostrei como o indicador flutuante funciona e como ele foi implementado. Pois bem, tais indicadores tem sua utilidade e não serão retirados do EA, mas eles não se adequam ao sistema que foi visto acima, já que a forma como eles trabalham é bastante diferente dos indicadores que de fato representam ordens ou posições que estão no servidor de negociação. Desta forma para resolver os problemas que aparecem ao usar um indicador flutuante, teremos que ir a rotina DispatchMessage e ajustar as coisas lá, abaixo vemos as mudanças que devem ser feitas.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;

// ... Código interno ...
                        
        switch (id)
        {

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

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_TYPE:
                                        if (ticket == def_IndicatorFloat)
                                        {
                                                macroGetDataIndicatorFloat;
                                                m_Selection.tp = (m_Selection.tp == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.tp - m_Selection.pr) * (m_Selection.bIsBuy ? 1 : -1)));
                                                m_Selection.sl = (m_Selection.sl == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.sl - m_Selection.pr) * (m_Selection.bIsBuy ? -1 : 1)));
                                                m_Selection.ticket = 0;
                                                UpdateIndicators(def_IndicatorFloat, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                                        } else m_BtnInfoType.SetStateButton(sparam, !m_BtnInfoType.GetStateButton(sparam));
                                        break;
                                case EV_DS:
                                        if (ticket != def_IndicatorFloat) m_BtnInfo_DS.SetStateButton(sparam, !m_BtnInfo_DS.GetStateButton(sparam));
                                        break;
                                case EV_CLOSE:
                                        if (ticket == def_IndicatorFloat)
                                        {
                                                macroGetDataIndicatorFloat;
                                                RemoveIndicator(def_IndicatorFloat, it);
                                                if (it != IT_PENDING) UpdateIndicators(def_IndicatorFloat, (it == IT_TAKE ? 0 : m_Selection.tp), (it == IT_STOP ? 0 : m_Selection.sl), m_Selection.vol, m_Selection.bIsBuy);
                                        }else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)

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

Ao fazer estas mudança, que estão em destaque no código, praticamente eliminamos qualquer outro problema relacionado ao modo de ajustar os indicadores flutuantes, ou seja agora teremos o mesmo modo de ajustar os dados de um indicador flutuante, ou de algo que represente dados no servidor de negociação. Mas isto não finaliza completamente nossos problemas, ainda temos mais um inconveniente a ser resolvido, e este estava persistindo a muito tempo, mas para falar dele vamos para o próximo tópico, pois ele merece ter uma discusão a parte para um correto entendimento.


2.0.4 - O inconveniente do valor de Take Negativo

O último inconveniente a ser resolvido neste artigo, é o fato de que o valor de take muitas vezes pode ser configurado de forma negativa e isto já vinha acontecendo a bastante tempo, mas para o sistema de negociação isto não faz o mínimo sentido, tanto que se você tentar enviar isto para o servidor, receberá uma mensagem de erro, por conta disto temos que corrigir isto, mas também temos que resolver um outro problema, que é o a possibilidade de uma ordem pendente tem seu valor de stop modificado de forma que este fique positivo.

Sim o EA até o presente momento permite isto, e pior, o sistema de ordens indica que isto esta no servidor, quando na verdade o servidor retorna um erro, mas o EA simplesmente o ignorava, o problema é mais complicado no caso de ordens pendentes, pois no caso de posições o comportamento deve ser outro, por isto esta falha não havia sido corrigida ainda, mas a partir deste momento em que temos a possibilidade de definir os limites diretamente no gráfico, esta falha deverá deixar de existir.

Vale uma ressalva aqui, no caso de uma posição aberta podemos ter um stop com um valor positivo, e isto indicará que caso o stop seja acionado teremos um valor creditado em nossa conta, mas em ordens pendentes isto é um erro que impede o servidor de montar a ordem corretamente. Para resolver esta questão temos que fazer um teste do valor de take e quando ele se tornar igual ou menor que 0 devemos evitar que ele continue a ser modificado para um valor menor, e no caso de uma ordem pendente, devemos evitar que o valor de stop fique maior que 0, no bem da verdade o que iremos fazer é forçar o EA a utilizar um valor mínimo quando a condição de 0 for verificada, desta forma para o sistema de negociação a ordem ou posição fará algum sentido, já que não faz sentido abrir uma posição com stop ou take igual ao ponto de abertura.

Para fazermos isto de uma forma o mais simples possível temos que criar uma variável no sistema, esta pode ser vista abaixo:

struct st00
{
        eIndicatorTrade it;
        bool            bIsBuy,
                        bIsDayTrade;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl,
                        MousePrice;
}m_Selection;

Mas ai você pode ficar pensando o seguinte: Por que você simplesmente não muda o ponto do preço na linha do mouse ?!?! O motivo é que para manipular corretamente o mouse é preciso usar uma chamada de sistema, ou seja seria necessário manipular os valores de posição do mouse via API do WINDOWS, e isto nos forçaria a habilitar o uso de dlls externas, e não quero fazer isto, não aqui e não agora, desta forma é mais simples montar um valor local dentro do EA, e o dado em destaque irá armazenar este valor para nos.

Este valor será manipulado em 3 locais diferentes, o primeiro local é na rotina de movimentação, e no fragmento abaixo, pode ser visto o local exato em que isto acontece:

void MoveSelection(double price)
{
        if (m_Selection.ticket == 0) return;
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        UpdateIndicators(m_Selection.ticket, price, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        break;
                case IT_STOP:
                        UpdateIndicators(m_Selection.ticket, m_Selection.tp, price, m_Selection.vol, m_Selection.bIsBuy);
                        break;
                case IT_PENDING:
                        PositionAxlePrice(m_Selection.ticket, IT_PENDING, price);
                        UpdateIndicators(m_Selection.ticket, (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.vol, m_Selection.bIsBuy);
                        m_Selection.MousePrice = price;
                        break;
        }
        if (Mouse.IsVisible())
        {
                m_TradeLine.SpotLight(macroMountName(m_Selection.ticket, m_Selection.it, EV_LINE));
                Mouse.Hide();
        }
}

E por que não colocar tudo nesta rotina acima, já que ela irá ser responsável por todo o movimento dos pontos de limite ?!?! O motivo é que temos que fazer alguns cálculos para ajustar corretamente o limite seja o take ou stop, e é bem mais simples fazer isto em outro local, mas como você pode estar pensando, sim, precisamos também mudar este valor em um outro ponto, e este é o segundo ponto onde o valor é referenciado pode ser visto no fragmento logo a seguir:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;
        bool    bKeyBuy,

// ... Código interno ....      
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Mouse.GetPositionDP(dt, price);
                        mKeys   = Mouse.GetButtonStatus();
                        bEClick  = (mKeys & 0x01) == 0x01;    //Clique esquerdo
                        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT Pressionada
                        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL Pressionada
                        if (bKeyBuy != bKeySell)
                        {
                                if (!bMounting)
                                {
                                        m_Selection.bIsDayTrade = Chart.GetBaseFinance(m_Selection.vol, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                        m_Selection.it = IT_PENDING;
                                        m_Selection.pr = price;
                                }
                                m_Selection.tp = (valueTp == 0 ? 0 : m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp)));
                                m_Selection.sl = (valueSl == 0 ? 0 : m_Selection.pr + (bKeyBuy ? (-valueSl) : valueSl));
                                m_Selection.bIsBuy = bKeyBuy;
                                m_BtnInfoType.SetStateButton(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_TYPE), bKeyBuy);
                                if (!bMounting)
                                {
                                        IndicatorAdd(m_Selection.ticket = def_IndicatorTicket0);
                                        bMounting = true;
                                }
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0)) SetPriceSelection(memLocal = price);
                        }else if (bMounting)
                        {
                                RemoveIndicator(def_IndicatorTicket0);
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                        {
                                if (bEClick) SetPriceSelection(m_Selection.MousePrice); else MoveSelection(price);
                        }
                        break;

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

Com isto temos um comportamento bastante previsível dentro do sistema, mas existe um outro ponto em que este valor também é referenciado, mas por conta da grande complexidade envolvida, resolvi modificar a coisa toda, fazendo com que uma macro deixasse de existir passando agora ser uma rotina, então a nova rotina Update é vista logo abaixo:

void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
{
        double pr;
        bool b0, bPen = true;
                                
        if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
        {
                bPen = (pr = macroGetPrice(ticket, IT_RESULT, EV_LINE)) == 0;
                if (bPen && (macroGetPrice(ticket, IT_PENDING, EV_MOVE) == 0))
                {
                        CreateIndicator(ticket, IT_PENDING);
                        PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                        ChartRedraw();
                }
                pr = (pr > 0 ? pr : macroGetPrice(ticket, IT_PENDING, EV_MOVE));
                SetTextValue(ticket, IT_PENDING, vol);
        }
        b0 = UpdateIndicatorsLimits(ticket, IT_TAKE, tp, vol, pr, isBuy, bPen);
        b0 = (UpdateIndicatorsLimits(ticket, IT_STOP, sl, vol, pr, isBuy, bPen) ? true : b0);
        if (b0) ChartRedraw();
}

Os pontos em destaque substituem a antiga macro, mas como eu disse o código necessário é bem mais complexo, então vamos ver onde de fato fica o terceiro e ultimo ponto em que a nova variável adicionada ao EA é de fato referenciada, notem pelo código acima que não existe de fato uma diferença entre o indicador de limite de take e o indicador de stop, ambos são tratados da mesma forma, a única diferença é que um representa ganho máximo e o outro não, mas para o EA ambos são iguais, mas vejamos o código abaixo.

inline bool UpdateIndicatorsLimits(ulong ticket, eIndicatorTrade it, double price, double vol, double pr, bool isBuy, bool isPen)
{
        bool b0 = false;
        double d1 = Terminal.GetPointPerTick();
                
        if (
    price  == 0)
        {
                if (macroGetPrice(ticket, it, EV_LINE) > 0) RemoveIndicator(ticket, it);
                if (macroGetPrice(ticket, it, EV_MOVE) == 0) CreateBtnMoveIndicator(ticket, it);
        } else if (b0 = (macroGetPrice(ticket, it, EV_LINE) == 0 ? true : b0)) CreateIndicator(ticket, it);
        switch (it)
        {
                case IT_TAKE:
                        price = (price == 0 ? 0 : (((isBuy ? price - pr : pr - price) > 0) ? price : (isBuy ? pr + d1 : pr - d1)));
                        break;
                case IT_STOP:
                        price = (price == 0 ? 0 : (isPen ? (((isBuy ? price - pr : pr - price) < 0) ? price : (isBuy ? pr - d1 : pr + d1)) : price));
                        break;
        }
        if (m_Selection.it == it) m_Selection.MousePrice = price;
        PositionAxlePrice(ticket, it, (price == 0 ? pr : price));
        SetTextValue(ticket, it, vol, (isBuy ? price - pr : pr - price));
                        
        return b0;
}

A partir de agora o valor não poderá em caso de uma ordem pendente ser errático, temos um limite máximo admissível para ser usado no indicador, então caso você posicione uma ordem pendente de compra e tente mover o valor de Take para um valor NEGATIVO, ou seja abaixo do ponto de entrada, isto não será mais possível, o cálculo do indicador take irá impedir que isto aconteça, a vantagem de como isto foi codificado é que independentemente de ser uma ordem ou posição o valor de take jamais poderá ser negativo, o próprio EA irá impedir isto.

Agora com relação ao valor de stop, temos um cálculo ligeiramente diferente, nele verificamos se a estamos manipulando uma ordem ou posição, caso seja uma ordem, o valor do stop jamais irá ser positivo, caso seja uma posição o EA irá simplesmente ignorar qualquer outra condição, neste caso o EA irá aceitar o valor que o operador estiver colocando ou indicando, assim você passa a poder ter um valor de stop positivo, mas somente no caso de posições, sem nenhum tipo de prejuízo ao restante do código do sistema de ordens, o EA finalmente irá conversar com o servidor de negociação de uma forma que ele não rejeite os dados enviados.


3.0- Conclusão

Finalmente depois de vários artigos, chegamos ao nosso clímax, onde temos um sistema de ordens praticamente completo e bastante adaptável a diversas situações e condições de mercado, a partir de agora você poderá operar com um sistema inteiramente gráfico, usando o mouse e o teclado, mas suas analises de mercado para entrar ou sair das operações.

Para quem esta chegando agora e deseja ver como o sistema se comporta ou a aparência que o mesmo se encontra no atual ponto de desenvolvimento, veja o video abaixo ... e obrigado a todos que acompanharam esta sequencia até aqui ... mas o trabalho ainda não terminou, ainda temos muito trabalho até este EA se tornar algo memorável ... nos vemos no próximo artigo .... então até lá ....👍



Últimos Comentários | Ir para discussão (1)
DoEasy. Controles (Parte 6): Controle "Painel", redimensionamento automático do contêiner para adequá-lo ao seu conteúdo DoEasy. Controles (Parte 6): Controle "Painel", redimensionamento automático do contêiner para adequá-lo ao seu conteúdo
Neste artigo, continuaremos trabalhando no objeto WinForms "Painel" e geraremos seu redimensionamento automático em função do tamanho geral dos objetos Dock localizados dentro dele. Além disso, adicionaremos novas propriedades ao objeto de biblioteca "Símbolo".
Vídeo: Configurando MetaTrader 5 e MQL5 para negociação automatizada simples Vídeo: Configurando MetaTrader 5 e MQL5 para negociação automatizada simples
Neste pequeno curso em vídeo, você aprenderá como baixar, instalar e configurar o MetaTrader 5 para começar a negociar de maneira automatizada. Você também aprenderá a configurar o gráfico e as opções de negociação automatizada. Você fará seu primeiro backtest e aprenderá a importar um EA que pode negociar por conta própria 24 horas por dia, 7 dias por semana, sem precisar estar atrelado à tela de seu computador.
Indicadores com controles interativos no gráfico Indicadores com controles interativos no gráfico
O artigo oferece uma nova perspectiva sobre as interfaces dos indicadores. Eu vou focar na conveniência. Tendo tentado dezenas de diferentes estratégias de negociação ao longo dos anos, além de ter testado centenas de indicadores diferentes, eu cheguei a algumas conclusões que quero compartilhar com você neste artigo.
Vídeo: Negociação automatizada simples, como criar um EA simples mediante MQL5 Vídeo: Negociação automatizada simples, como criar um EA simples mediante MQL5
A maioria dos alunos dos meus cursos achava que a linguagem MQL5 era difícil de entender. Naquele momento, eles estavam procurando maneiras simples de automatizar alguns processos. Neste artigo, você aprenderá como começar a trabalhar logo em MQL5 mesmo sem conhecimentos de programação e mesmo que já tenha tentado, sem sucesso, dominar este tópico.