Desenvolvendo um EA de negociação do zero (Parte 12): Times And Trade (I)

Daniel Jose | 28 abril, 2022

Introdução

A leitura de fluxo mais conhecida como Tape Reading é uma modalidade de negociação usada por alguns operadores em diversos momentos, ela é extremamente eficaz e quando bem usada promove ganhos constantes e de uma forma muito mais segura e consistente do que seria conseguido usando apenas o conhecido Price Action que é a leitura dos candles pura e simplesmente, mas fazer o uso da leitura de Tape Reading da forma como ele se apresenta, é algo muito complicado e cansativo, nos exigindo muito em termos de concentração e atenção, e com o decorrer do tempo, passamos a ficar sujeitos a cometer erros na leitura, dado o nível de exigência durante todo o processo de leitura.

O grande problema da leitura de fluxo é a quantidade de informações que temos que dar conta durante o período que estamos utilizando este método de negociação, veja abaixo um típico momento de Tape Reading:


O real problema é que nesta analise, você tem que olhar o preço e o que aconteceu com ele e olhar estes números em mini contratos não é algo muito prático, por este motivo normalmente não se olha o conteúdo do fluxo nos mini contratos, damos preferencia em fazer a leitura nos contratos cheios, já que eles é que movem e travam o mercado. Isto é o que de fato acontece, então o sistema fica como mostrado abaixo, algo um pouco mais fácil de interpretar e acompanhar.


Mas mesmo assim este sistema é bem cansativo de ser acompanhado, é algo que nos demanda muita atenção para entender o movimento, e quando acontece o acionamento de posições de stop a coisa fica bem mais tensa e podemos perder parte do movimento, já que a rolagem de informações na tela se torna bastante rápida, ou seja é algo muito complicado que demanda muita experiência.


Planejamento

Bem, mas a plataforma MT5 tem um sistema alternativo e mesmo em mini contratos torna a leitura bem mais eficaz e fácil de ser acompanhada, veja só como a coisa fica quando usada em mini contratos:

Vejam que é bem mais simples a intepretação, mas pelos motivos que falei a pouco, é mais adequado usar contratos cheios, então ficaria como mostrado abaixo:


Mas vejam que os dados de negociação estão sendo contaminados com movimentos de modificação dos valores de BID e ASK, os negócios em si são representados por bolinhas, as vermelhas representa venda, as azuis são compra e as verdes são ordens diretas, bem além do fato de termos informações não necessárias para a leitura em si, temos um outro problema, o sistema fica separado do gráfico que estamos de fato negociando, nos obrigando a olhar duas telas, isto por um lado é uma vantagem, mas em alguns casos pode ser um tormento, então a proposta aqui é criar um sistema que seja simples de ler como este alternativo, ao mesmo tempo que nos permita ter este indicador diretamente no gráfico que estamos negociando.


Implantação

A primeira coisa que vamos fazer, é modificar a classe C_Terminal, para que possamos acessar o ativo de contrato cheio, e isto é feito adicionando o seguintes códigos:

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

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

inline string GetFullSymbol(void) const { return m_Infos.szFullSymbol; }

Com a adição das linhas em destaque, já temos acesso ao ativo correto que iremos usar no Time & Trade que iremos montar. Feito isto podemos começar a criar a classe objeto que irá dar suporte ao nosso Time & Trade, esta classe irá conter algumas rotinas bem interessantes, a primeira coisa a se fazer é gerar uma sub janela que irá comportar o indicador que montaremos, isto é simples de ser feito, mas por questões de praticidade não iremos usar o sistema de sub janelas que já estamos usando, talvez no futuro eu modifique isto, mas por enquanto, iremos tratar o Time & Trade em uma janela separada do sistema de indicadores, ou seja teremos que criar todo um suporte para isto.

Então a primeira coisa a ser feita é criar um novo arquivo de suporte de forma a se ter um nome diferente para o indicador, mas ao invés de ficar criando arquivos em cima de arquivos vamos fazer algo um pouco mais elegante. Vamos modificar o arquivo de suporte de forma a termos possibilidades maiores. Então o novo arquivo de suporte é mostrado abaixo:

#property copyright "Daniel Jose 07-02-2022 (A)"
#property version   "1.00"
#property description "Este arquivo serve apenas como Suporte ao Indicador em SubWin"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
input string user01 = "SubSupport";             //Short Name
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, user01);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+

Os pontos em destaque são as modificações que precisamos fazer no arquivo original, mas agora temos que fazer uma modificação no nosso código do EA. Uma nova classe será criada e ela pode ser vista integralmente abaixo:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Terminal.mqh"
//+------------------------------------------------------------------+
class C_FnSubWin
{
        private :
                string  m_szIndicator;
                int             m_SubWin;
//+------------------------------------------------------------------+
                void Create(const string szIndicator)
                        {
                                int i0;
                                m_szIndicator = szIndicator;
                                if ((i0 = ChartWindowFind(Terminal.Get_ID(), szIndicator)) == -1)
                                        ChartIndicatorAdd(Terminal.Get_ID(), i0 = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_Resource, szIndicator));
                                m_SubWin = i0;
                        }
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+
                C_FnSubWin()
                        {
                                m_szIndicator = NULL;
                                m_SubWin = -1;
                        }
//+------------------------------------------------------------------+
                ~C_FnSubWin()
                        {
                                Close();
                        }
//+------------------------------------------------------------------+
                void Close(void)
                        {
                                if (m_SubWin >= 0) ChartIndicatorDelete(Terminal.Get_ID(), m_SubWin, m_szIndicator);
                                m_SubWin = -1;
                        }
//+------------------------------------------------------------------+
inline int GetIdSubWinEA(const string szIndicator = NULL)
                        {
                                if ((szIndicator != NULL) && (m_SubWin < 0)) Create(szIndicator);
                                return m_SubWin;
                        }
//+------------------------------------------------------------------+
inline bool ExistSubWin(void) const { return m_SubWin >= 0; }
//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+

O que está classe faz é substituir a classe C_SubWindow de forma que agora esta será a classe que dará suporte a criação de sub janelas no gráfico, para entender de fato como esta classe funciona teremos que dar uma rápida olhada na nova classe C_SubWindow que também é vista integralmente logo abaixo:

#include "C_ChartFloating.mqh"
#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh>
//+------------------------------------------------------------------+
class C_SubWindow : public C_ChartFloating
{
//+------------------------------------------------------------------+
        private :
                C_FnSubWin      m_fnSubWin;
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+
                ~C_SubWindow()
                        {
                                Close();
                        }       
//+------------------------------------------------------------------+
                void Close(void)
                        {
                                m_fnSubWin.Close();
                                CloseAlls();
                        }
//+------------------------------------------------------------------+
inline int GetIdSubWinEA(void)
                        {
                                return m_fnSubWin.GetIdSubWinEA("SubWinSupport");
                        }
//+------------------------------------------------------------------+
inline bool ExistSubWin(void) const { return m_fnSubWin.ExistSubWin(); }
//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+

Notem que nesta classe temos uma definição do indicador que será usado para dar suporte aos templates, isto é marcado no código em destaque, mas agora vem o pulo do gato, se ao invés do nome SubWinSupport usarmos outro, a classe C_FnSubWin irá verificar a existência de um indicador diferente, e é este o truque que irei usar para não ficar criando arquivos de indicadores, irei apenas informar a classe C_FnSubWin qual deve ser o nome curto do indicador que deverá ser usado para outro proposito, desta forma não fico limitado a um numero de sub janelas ou de arquivos de indicadores desnecessários, usados apenas para criar a sub janela para o EA.

Bem feito isto podemos partir para a criação da classe C_TimeAndTrade.


A Classe C_TimesAndTrade

A classe objeto C_TimesAndTrade, é composta de vários pequenos pedaços, cada um responsável por algo especifico, a primeira coisa chamada pelo EA para esta classe é o código visto abaixo:

void Init(const int iScale = 2)
{
        if (!ExistSubWin())
        {
                CreateCustomSymbol();
                CreateChart();
        }
        ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_CHART_SCALE, (iScale > 5 ? 5 : (iScale < 0 ? 0 : iScale)));
}

Este código irá testar se a sub janela de suporte já existe, caso ela não exista, ela será criada, mas vamos olhar com um pouco de carinho o próximo código de suporte inicial da classe, ele é mostrado logo abaixo:

inline void CreateCustomSymbol(void)
{
        m_szCustomSymbol = "_" + Terminal.GetFullSymbol();
        SymbolSelect(Terminal.GetFullSymbol(), true);
        SymbolSelect(m_szCustomSymbol, false);
        CustomSymbolDelete(m_szCustomSymbol);
        CustomSymbolCreate(m_szCustomSymbol, StringFormat("Custom\\Robot\\%s", m_szCustomSymbol), Terminal.GetFullSymbol());
        CustomRatesDelete(m_szCustomSymbol, 0, LONG_MAX);
        CustomTicksDelete(m_szCustomSymbol, 0, LONG_MAX);
        SymbolSelect(m_szCustomSymbol, true);
};

Este código irá criar um símbolo customizado, e irá zerar todos os dados dentro deste mesmo símbolo, para que o conteúdo do símbolo apareça dentro de uma janela que iremos criar depois, precisamos que ele apareça primeiramente na Observação de Mercado e isto é feito usando esta linha:

SymbolSelect(m_szCustomSymbol, true);

O símbolo customizado será criado na seguinte localização Custom\Robot\<Nome do Símbolo> e os dados iniciais dele serão fornecidos pelo símbolo original, isto é conseguido usando a seguinte fragmento de código:

CustomSymbolCreate(m_szCustomSymbol, StringFormat("Custom\\Robot\\%s", m_szCustomSymbol), Terminal.GetFullSymbol());

Basicamente é isto, e quando adicionamos no EA a classe e a iniciamos pelo fragmento abaixo:

// ... Código do EA

#include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>

// ... Código do EA

input group "Times & Trade"
input   int     user041 = 2;    //Escala
//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_WallPaper     WallPaper;
C_VolumeAtPrice VolumeAtPrice;
C_TimesAndTrade TimesAndTrade;
//+------------------------------------------------------------------+
int OnInit()
{
// ... Código do EA

        TimesAndTrade.Init(user041);
        
        OnTrade();
        EventSetTimer(1);
   
        return INIT_SUCCEEDED;
}

Obtemos a imagem logo abaixo:


E era justamente isto que era esperado, mas agora vamos adicionar os valores dos negócios executados no gráfico _DOLH22, este gráfico irá ser montado de forma a refletir isto, os negócios executados de forma a termos uma representação gráfica do Times & Trade, esta representação será feita usando o modelo de candlestick, isto por conta de facilidades em usar justamente este padrão. Para fazermos isto precisamos fazer algumas coisas antes, precisamos conectar e sincronizar o símbolo, e isto é feito pela seguinte rotina:

inline void Connect(void)
{
        switch (m_ConnectionStatus)
        {
                case 0:
                        if (!TerminalInfoInteger(TERMINAL_CONNECTED)) return; else m_ConnectionStatus = 1;
                case 1:
                        if (!SymbolIsSynchronized(Terminal.GetFullSymbol())) return; else m_ConnectionStatus = 2;
                case 2:
                        m_LastTime = TimeLocal();
                        m_MemTickTime = macroMinusMinutes(60, m_LastTime) * 1000;
                        m_ConnectionStatus = 3;
                default:
                        break;
        }
}

Esta rotina faz exatamente o que ela se propõe a fazer, verificar se o terminal esta conectado para logo depois sincronizar o símbolo depois disto feito, podemos começar a capturar os valores e apresentar eles na tela, mas precisamos fazer uma pequena mudança no código de inicialização, a mudança esta em destaque no codigo a seguir:

void Init(const int iScale = 2)
{
        if (!ExistSubWin())
        {
                CreateCustomSymbol();
                CreateChart();
                m_ConnectionStatus = 0;
        }
        ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_CHART_SCALE, (iScale > 5 ? 5 : (iScale < 0 ? 0 : iScale)));
}

Feito isto vamos ver a rotina de captura.

inline void Update(void)
{
        MqlTick Tick[];
        MqlRates Rates[def_SizeBuff];
        int i0, p1, p2 = 0;
        int iflag;

        if (m_ConnectionStatus < 3) return;
        if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0)
        {
                for (p1 = 0, p2 = 0; (p1 < i0) && (Tick[p1].time_msc == m_MemTickTime); p1++);
                for (int c0 = p1, c1 = 0; c0 < i0; c0++)
                {
                        if (Tick[c0].volume == 0) continue;
                        iflag = 0;
                        iflag += ((Tick[c0].flags & TICK_FLAG_BUY) == TICK_FLAG_BUY ? 1 : 0);
                        iflag -= ((Tick[c0].flags & TICK_FLAG_SELL) == TICK_FLAG_SELL ? 1 : 0);
                        if (iflag == 0) continue;
                        Rates[c1].high = Tick[c0].ask;
                        Rates[c1].low = Tick[c0].bid;
                        Rates[c1].open = Tick[c0].last;
                        Rates[c1].close = Tick[c0].last + ((Tick[c0].volume > 200 ? 200 : Tick[c0].volume) * (Terminal.GetTypeSymbol() == C_Terminal::WDO ? 0.02 : 1.0) * iflag);
                        Rates[c1].time = m_LastTime;
                        p2++;
                        c1++;
                        m_LastTime += 60;
                }
                CustomRatesUpdate(m_szCustomSymbol, Rates, p2);
                m_MemTickTime = Tick[i0 - 1].time_msc;
        }
}

O que a rotina acima faz é capturar todos os ticks negociados, absolutamente todos para logo em seguida verificar se eles são ticks de venda ou de compra, caso sejam tick de mudanças de BID ou ASK, ou seja sem volume, nenhuma informação é armazenada, assim como ticks que são ordens diretas, estas não tem nenhuma influência no movimento dos preços, apesar de muitas vezes se a causa para o movimento, já que existem players que forçam o preço a um determinado valor apenas para executar uma ordem direta, para logo depois deixar o preço mover livremente. Bem estes ticks, usados para mudar o BID e ASK, serão usados em um outra versão que irei mostrar no próximo artigo, já que eles tem uma importância secundaria no sistema geral. Depois de testar o tipo de negócio, temos uma sequencia de linhas que é importante vocês entenderem, estas linhas que estão no fragmento logo abaixo e irão construir 1 candlestick por tick de negociação que passou pelo sistema de analise, e deve ser armazenado.

Rates[c1].high = Tick[c0].ask;
Rates[c1].low = Tick[c0].bid;
Rates[c1].open = Tick[c0].last;
Rates[c1].close = Tick[c0].last + ((Tick[c0].volume > 200 ? 200 : Tick[c0].volume) * (Terminal.GetTypeSymbol() == C_Terminal::WDO ? 0.02 : 1.0) * iflag);
Rates[c1].time = m_LastTime;

A máxima e mínima do candle indica o Spread no momento que o tick foi negociado, ou seja aquele valor que existia entre o BID e o ASK serão os pavis do candle criado, o valor de abertura do candle é o preço onde o negocio de fato saiu, agora observem com atenção a linha em destaque no fragmento, quando um tick é negociado, temos um volume, esta linha irá criar um pequeno ajuste neste volume, para que a escala não estoure, você pode ajustar os valores conforme ficar melhor para ser analisado por você, dependendo do ativo em questão, isto fica a critério de cada um.

Agora um ultimo detalhe que é o tempo, cada candle irá ser corresponder a 1 minuto, já que não é possível plotar valores menores que este, dai cada um irá ficar na sua devida posição, de 1 em 1 minuto, isto não é um tempo real, este tempo é virtual, não confunda o tempo entre os negócios com o tempo gráfico, os negócios podem acontecer em milissegundos, não a informação gráfica será plotada a cada 1 minuto na escala gráfica, pode-se usar qualquer outro valor, mas este por ser o menor possível, facilita em muito a programação. O resultado deste sistema pode ser visto abaixo:

Vejam que a leitura é perfeitamente possível e a interpretação fica bastante simples, é bem verdade que o fluxo de ordens estava bem lento no momento que a captura foi feita, mas acredito que deu para entender a ideia.

Uma ultima informação sobre este sistema, veja as figuras abaixo:

Vejam que existem 4 configurações diferentes, que podem ser vistas no sistema, mas por que ?!?! O motivo será visto com mais detalhes no próximo artigo onde você entenderá o por que que existem 4 configurações vistas no Times & Trade, mas aqui já temos um sistema já funcional, e pode ser que ele desta forma já lhe seja suficiente para uso intenso, mas se você entender o que esta acontecendo e que faz gerar 4 configurações de candle você conseguirá tirar um proveito muito maior deste sistema e quem sabe este será o seu indicador principal ...


Conclusão

Aqui criamos um sistema de Times & Trade para ser usado no nosso EA de forma a analisar o Tape Reading com a mesma agilidade que é possível com o sistema alternativo presente no MT5, mas fizemos isto criando um sistema gráfico, ao invés de ficar lendo e tentando entender uma enormidade de números e valores, no próximo artigo iremos completar o sistema com as informações que estão faltando, já que para fazer isto precisaremos acrescentar várias coisas novas ao nosso código do EA.