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

Desenvolvendo um EA de negociação do zero (Parte 08): Um salto conceitual (I)

MetaTrader 5Exemplos | 31 março 2022, 08:43
1 350 0
Daniel Jose
Daniel Jose

1.0 - Introdução

As vezes quando desenvolvemos algo, vemos novas possibilidades que podem ser adequadas e com isto trazer uma grande melhoria em um sistema que estamos criando, mas ai surge uma questão: Como implementar de forma o mais simples possível uma nova funcionalidade ?

O grande problema é que em algumas vezes somos obrigados a esquecer tudo que já foi desenvolvido e começar do ZERO, e isto é completamente DESMOTIVADOR. Mas existe uma linha de pensamento que adquiri ao longo do tempo, depois de mais de 20 anos programando em C++ a gente desenvolve alguns conceitos que nos ajuda a planejar as coisas e fazer mudanças com o mínimo de esforço, mas as vezes a coisa pode se tornar bem mais complexa do que estimávamos incialmente.

Até o momento o EA tem sido construído de forma a permitir que ele vá recebendo códigos e sem perder suas funcionalidades já existentes, as classes são simplesmente criadas e adicionadas, mas agora teremos que dar um passo para trás para logo em seguida dar dois passos para frente. E dar este um passo para trás nos permitirá colocar um nova funcionalidade no EA, esta funcionalidade é uma classe de janela com algum tipo de informação, baseada em template, esta daqui será a primeira parte, vamos mudar radicalmente o código, mas mantendo toda a funcionalidade existente até o momento, na segunda parte irei tratar da IDE.


2.0 - Planejamento

Nosso EA esta atualmente estruturado em classe objetos, e esta estrutura pode ser vista no diagrama abaixo.

Com tudo isto, temos o funcionamento atual e uma excelente estabilidade do sistema, mas agora o EA será reestruturado de forma a ficar conforme mostrado abaixo, vejam que houve apenas a adição de uma classe e a mudança de posição da classe C_TemplateChart com a classe C_SubWindow.


E por que desta reestruturação ?!?! O problema é que para adicionar janelas flutuantes contendo dados de ativos, a forma como ele estava não era adequado, precisando assim passar por esta mudança, mas esta mudança, não é apenas estética em termos de estrutura, mas foi necessário um mudança extrema no código, então ele esta muito diferente dos códigos anteriormente vistos.

Então vamos colocar a mão na massa, ou melhor código na tela.


3.0 - Implementação na prática

3.1 - Mudanças no código interno do EA

A primeira grande mudança começa no arquivo de inicialização do EA, observem o fragmento abaixo:

input group "Window Indicators"
input string                    user01 = "";                    //Indicadores de sub janela
input group "WallPaper"
input string                    user10 = "Wallpaper_01";        //BitMap a ser usado
input char                      user11 = 60;                    //Transparencia (0 a 100)
input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;   //Tipo de imagem de fundo
input group "Chart Trader"
input int                       user20   = 1;                   //Fator de alavancagem
input int                       user21   = 100;                 //Take Profit ( FINANCEIRO )
input int                       user22   = 75;                  //Stop Loss ( FINANCEIRO )
input color                     user23   = clrBlue;             //Cor da linha de Preço
input color                     user24   = clrForestGreen;      //Cor da linha Take Profit
input color                     user25   = clrFireBrick;        //Cor da linha Stop
input bool                      user26   = true;                //Day Trade ?
input group "Volume At Price"
input color                     user30  = clrBlack;             //Cor das barras
input char                      user31  = 20;                   //Transparencia (0 a 100 )
//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_WallPaper     WallPaper;
C_VolumeAtPrice VolumeAtPrice;
//+------------------------------------------------------------------+
int OnInit()
{
        static string   memSzUser01 = "";
        
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);
        if (memSzUser01 != user01)
        {
                Chart.ClearTemplateChart();
                Chart.AddThese(memSzUser01 = user01);
        }
        Chart.InitilizeChartTrade(user20, user21, user22, user23, user24, user25, user26);
        VolumeAtPrice.Init(user24, user25, user30, user31);
        
   OnTrade();
        EventSetTimer(1);
   
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+

Vejam que agora temos apenas uma única variável para dizer quais são os templates a serem carregados, o restante do código parece estar igual ao que vem sendo visto, com exceção da parte destacada. Vocês talvez não façam a mínima ideia do que isto faz, ou por que este código destacado foi colocado aqui, na inicialização do EA. Pois bem, quando carregamos nosso EA para o gráfico, algumas coisas são criadas, e durante o uso normal elas podem ser modificadas, anteriormente não fazia sentido adicionar o código em destaque, e o motivo era que estava tudo orientado a funcionar em conjunto, onde mudanças não afetava o comportamento ou aparência do EA, mas quando adicionamos indicadores flutuantes, começa a ocorrer uma coisa CHATA, sempre que mudamos o TIMEFRAME o EA é REINICIALIZADO, e as janelas voltam para seu estado inicial, se elas não forem destruídas, começa a se ter um acumulo coisas inúteis no gráfico e caso elas sejam destruídas como deve ser, elas serão reconstruídas nos locais iniciais, e isto é um grande incomodo, então caso o usuário não mude os templates desejados, o código em destaque irá impedir das janelas flutuantes sejam destruídas indevidamente, mas se houver mudanças nos templates, o EA é reiniclalizado normalmente. Algo muito simples e singelo, mas extremamente eficiente.

A próxima coisa a chamar atenção é quanto o sistema de mensagens internas, anteriormente existia uma variável extra, mas ela foi retirada, então o código ficou como mostrado abaixo:

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, NanoEA.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
}

Agora o sistema utiliza de forma mais eficiente o próprio sistema de mensagens presentes no MQL5, e o envio de mensagens se parece em muito a própria função OnChartEvent, podendo assim passar os parâmetros sem nenhum tipo de stress para as classes objetos para que cada classe trabalhe as mensagens de evento geradas pelo MT5 da forma mais adequada a própria classe, ou seja a coisa toma ainda mais corpo e força, isolando ainda mais cada classe objeto, podendo assim o EA tomar uma aparência mais diferente para a cada tipo de usuário, e com um esforço cada vez menor.


3.2 - Mudanças no código de suporte a Sub Janela

Até então o código de sub janelas era bem simples, mas tinha um problema, toda a vez que por um motivo ou outro o EA não conseguia retirar a sub janela criada, ao abrir novamente o EA, uma nova sub janela era criada, provocando um descontrole muito grande sobre o próprio sistema, mas isto foi corrigido, e por incrível que pareça de uma forma muito simples, a primeira coisa a ser notada é o fragmento do arquivo de suporte que pode ser visto abaixo:

int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "SubWinSupport");
        
        return INIT_SUCCEEDED;
}

A linha destacada irá criar um alias para o arquivo de suporte, e este alias é que será visto pelo EA para verificar se o sistema de sub janelas esta ou não carregado, não importando o nome do arquivo, para o EA o que importa é o alias. Este mesmo tipo de código será usado em um outro momento para dar suporte a outro tipo de coisa dentro do EA, aqui não irei entrar em muitos detalhes sobre ele, mas futuramente em outro artigo irei explicar como tirar proveito deste código destacado.

Uma vez feito isto vamos ver o código de carregamento e criação de sub janelas, ele agora pode ser visto no fragmento abaixo:

void Init(void)
{
        int i0;
        if ((i0 = ChartWindowFind(Terminal.Get_ID(), def_Indicador)) == -1)
                ChartIndicatorAdd(Terminal.Get_ID(), i0 = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_Resource));
        m_IdSubWinEA = i0;
}

Vejam que ele é muito mais simples, porém este fragmento não é publico, ele é acessado por meio de um outro fragmento que é publico, e pode ser visto a seguir:

inline int GetIdSubWinEA(void)
{
        if (m_IdSubWinEA < 0) Init();
        return m_IdSubWinEA;
}

Mas por que disto ?!?! Bem, o fato é que se você olhar a inicialização do EA, verá que pode acontecer de o EA não usar nenhum indicador em uma sub janela, e quando o sistema percebe isto, ele retira a sub janela do gráfico e só irá criar ela caso seja necessário. Mas quem toma esta decisão não é o código do EA, mas sim a classe C_TemplateChart.


3.3 - A Nova Classe C_TemplateChart

Vejam a animação abaixo:

Reparem que agora temos uma linha vertical que indica onde estamos analisando, estas linhas são independentes uma da outra, mas antes ela não existia, ficando difícil analisar alguns pontos do indicador com base no gráfico, esta é uma das melhorias adicionadas a classe C_TemplateChart, mas vamos analisar o código dentro da classe para entender ainda mais, pois as mudanças foram grandes.

Vejam o fragmento a seguir, onde as variáveis são declaradas:

class C_TemplateChart : public C_SubWindow
{
#define def_MaxTemplates                8
#define def_NameTemplateRAD     "IDE"
//+------------------------------------------------------------------+
        private :
//+------------------------------------------------------------------+
        enum eParameter {TEMPLATE = 0, PERIOD, SCALE, WIDTH, HEIGHT};
//+------------------------------------------------------------------+
                struct st
                {
                        string          szObjName,
                                        szSymbol,
                                        szTemplate,
                                        szVLine;
                        int             width,
                                        scale;
                        ENUM_TIMEFRAMES timeframe;
                        long            handle;
                }m_Info[def_MaxTemplates];
                int     m_Counter,
                        m_CPre,
                        m_Aggregate;
                struct st00
                {
                        int     counter;
                        string  Param[HEIGHT + 1];
                }m_Params;

A primeira coisa a se notar é que a classe C_TemplateChart irá estender a classe C_SubWindow, este fragmento não parece assim tão especial, mas reparem com atenção a parte destacada, ela indica um sistema interno de analise dos dados para poder construir e apresentar de forma adequada os indicadores pedidos pelo usuário, ou seja agora o sistema para descrever como o usuários irá indicar as coisas passou a ser padronizada, mesmo que no inicio pareça mais confuso, a coisa se dará de forma bem natural com o tempo. Para explicar o novo formato é preciso analisar o fragmento a seguir, que é responsável por analisar o pedido do usuário:

int GetCommand(int iArg, const string szArg)
{
        for (int c0 = TEMPLATE; c0 <= HEIGHT; c0++) m_Params.Param[c0] = "";
        m_Params.counter = 0;
        for (int c1 = iArg, c2 = 0; szArg[iArg] != 0x00; iArg++) switch (szArg[iArg])
        {
                case ')':
                case ';':
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        for (; (szArg[iArg] != 0x00) && (szArg[iArg] != ';'); iArg++);
                        return iArg + 1;
                case ' ':
                        c2 += (c1 == iArg ? 0 : 1);
                        c1 = (c1 == iArg ? iArg + 1 : c1);
                        break;
                case '(':
                case ',':
                        c2 = (m_Params.counter == SCALE ? (c2 >= 1 ? 1 : c2) : c2);
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        c2 = 0;
                        c1 = iArg + 1;
                        break;
                default:
                        c2++;
                        break;
        }
        return -1;
}

A primeira coisa que fazemos é limpar qualquer dado anteriormente encontrado na estrutura, feito isto começamos a analisar e pegar parâmetro por parâmetro caso eles existam, eles não são obrigatórios, mas indicam como as coisas serão apresentadas ao usuário, e este sistema é auto expansível, ou seja caso você deseje adicionar mais informações, bastará indicar isto na enumeração eParameter. Atualmente o sistema conta com 5 parâmetros, e eles devem ser fornecidos na ordem indicada no enumerador eParameter que foi destacado no fragmento de declaração de variáveis, abaixo vemos cada parâmetro na ordem e o que eles informam ao sistema.

Parâmetro Resultado
1. TEMPLATE ou ATIVO Indica qual será o template ou ativo a ser visualizado
2. PERIODO Caso ele seja indicado, irá travar o indicador em um dado período, da mesma forma como estava sendo usado anteriormente
3. ESCALA Caso um valor seja indicado, irá travar o indicador em uma escala fixa.
4. LARGURA Caso indicado irá dizer qual é a largura que o indicador terá na janela
5. ALTURA Este é um parâmetro novo e será visto mais a frente neste artigo, servindo para indicar o uso de uma janela flutuante.

Apesar de tudo, a única estrutura que não se beneficiará no momento do parâmetro numero 5 é a IDE, mas isto será corrigido, e no próximo artigo irei mostrar como implementar isto na IDE, este artigo focará apenas nos demais sistemas.

Agora vamos supor, que por um motivo ou outro, que você deseje permitir que o usuário controle a cor da linha vertical no indicador, você não precisará fazer nenhuma mudança no código de analise dos parâmetros, mas apenas fazer a mudança conforme o exemplo abaixo:

class C_TemplateChart : public C_SubWindow
{
#define def_MaxTemplates        8
#define def_NameTemplateRAD     "IDE"
//+------------------------------------------------------------------+
        private :
//+------------------------------------------------------------------+
        enum eParameter {TEMPLATE = 0, COLOR_VLINE, PERIOD, SCALE, WIDTH, HEIGHT};
//+------------------------------------------------------------------+

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

                struct st00
                {
                        int     counter;
                        string  Param[HEIGHT + 1];
                }m_Params;

feito isto o sistema irá automaticamente reconhecer que poderá vim 6 parâmetros em uma chamada. Agora temos um problema, apesar do código GetCommand visto acima funcionar bem, ele tem uma falha, e esta falha muitas vezes passa desapercebido quando nos mesmos estamos criando um sistema que iremos utilizar, mas quando o sistema é colocado para outras pessoas usarem, a falha se tornará evidente e pode deixar alguns programadores menos experientes sem saber como resolver o problema, por isto a programação orientada a objetos é tão bem vista e é quando feita de forma correta o modelo mais adequado a ser usado em programas de grande interesse, uma das premissas da OOP ( Programação Orientada em Objetos ) é garantir que dos dados e variáveis internas da classe sejam inicializados da forma correta, e para garantir isto temos que testar as coisas, e apesar do código GetCommand parecer correto, ele contem uma falha, ele não testa o limite máximo de parâmetros, ou seja se o modelo inicial aceitar apenas 5 parâmetros, o que acontece se o usuário colocar 6 parâmetros ?!?! É isto que temos que evitar, não devemos supor que as coisas irão funcionar, devemos garantir que elas irão funcionar, então para corrigir o código ele deverá ficar como mostrado abaixo, onde os pontos de correção estão em destaque.

int GetCommand(int iArg, const string szArg)
{
        for (int c0 = TEMPLATE; c0 <= HEIGHT; c0++) m_Params.Param[c0] = "";
        m_Params.counter = 0;
        for (int c1 = iArg, c2 = 0; szArg[iArg] != 0x00; iArg++) switch (szArg[iArg])
        {
                case ')':
                case ';':
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        for (; (szArg[iArg] != 0x00) && (szArg[iArg] != ';'); iArg++);
                        return iArg + 1;
                case ' ':
                        c2 += (c1 == iArg ? 0 : 1);
                        c1 = (c1 == iArg ? iArg + 1 : c1);
                        break;
                case '(':
                case ',':
                        if (m_Params.counter == HEIGHT) return StringLen(szArg) + 1;
                        c2 = (m_Params.counter == SCALE ? (c2 >= 1 ? 1 : c2) : c2);
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        c2 = 0;
                        c1 = iArg + 1;
                        break;
                default:
                        c2++;
                        break;
        }
        return -1;
}

Vejam que simplesmente adicionando esta única linha evitamos que o sistema gere algo inesperado, já que se o último parâmetro esperado é um HEIGHT, mas ele não esta sendo o último, logicamente significa que algo esta errado, então o sistema ignora todas coisas declaradas depois evitando assim problemas.

Então você devem ter notado como o sistema reconhece o templates e os parâmetros, mas para quem não entendeu, a sintaxe é vista abaixo:

Parâmetro_00 ( Parâmetro_01 , Parâmetro_02 , Parâmetro_03 , Parârametro_04 )

Onde o parâmetro_00 indica o template a ser usado, e os demais são separados por virgula ( , ) e indicam os valores que estão definidos na enumeração eParameter, caso você deseje mudar apenas o parâmetro_03, pode deixar os outros vazios, conforme mostrado na figura abaixo, onde mostro o sistema funcionado confome o desejado pelo usuário.

       

Observem que temos uma indicação padronizada, lembrando muito chamadas de função, mas talvez pareça confuso para você, mas vamos entender o que aconteceu de fato: Observem que indicamos o template RSI, depois disto não informamos nem um período, nem uma escala, estes valores estão vazios então o sistema entende que deve seguir o gráfico principal, mas informamos uma largura e uma altura, o sistema entende que isto deve ser mostrado em uma janela flutuante, e é o que acontece, o indicador RSI é apresentado em uma janela flutuante. Já no template ADX, informamos apenas a largura então o sistema irá apresentar ele na largura definida dentro da sub janela. O indicador Stoch irá ocupar todo o restante da sub janela, dividindo espaço com o ADX. Mas caso o usuário deseje mudar as coisas, não será nada complicado, veja só o que acontece quando indicamos uma altura para o ADX.

O sistema imediatamente muda a forma de apresentar o ADX colocando ele em uma janela flutuante, e deixa toda a sub janela para o Stoch. Vejam que cada janela flutuante é totalmente independente uma da outra. Mas a coisa vai além do que esta sendo visto, veja a próxima animação.

vejam que a sub janela foi retirada já que não é mais necessária. Mas qual rotina é responsável por tudo isto ?!?! Bem, ela pode ser vista a seguir, e notem como podemos fazer as coisas ficarem bem interessantes, com poucas modificações:

void AddTemplate(void)
{
        ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;
        string sz0 = m_Params.Param[PERIOD];
        int w, h, i;
        bool bIsSymbol;

        if (sz0 == "1M") timeframe = PERIOD_M1; else
        if (sz0 == "2M") timeframe = PERIOD_M2; else
        if (sz0 == "3M") timeframe = PERIOD_M3; else
        if (sz0 == "4M") timeframe = PERIOD_M4; else
        if (sz0 == "5M") timeframe = PERIOD_M5; else
        if (sz0 == "6M") timeframe = PERIOD_M6; else
        if (sz0 == "10M") timeframe = PERIOD_M10; else
        if (sz0 == "12M") timeframe = PERIOD_M12; else
        if (sz0 == "15M") timeframe = PERIOD_M15; else
        if (sz0 == "20M") timeframe = PERIOD_M20; else
        if (sz0 == "30M") timeframe = PERIOD_M30; else
        if (sz0 == "1H") timeframe = PERIOD_H1; else
        if (sz0 == "2H") timeframe = PERIOD_H2; else
        if (sz0 == "3H") timeframe = PERIOD_H3; else
        if (sz0 == "4H") timeframe = PERIOD_H4; else
        if (sz0 == "6H") timeframe = PERIOD_H6; else
        if (sz0 == "8H") timeframe = PERIOD_H8; else
        if (sz0 == "12H") timeframe = PERIOD_H12; else
        if (sz0 == "1D") timeframe = PERIOD_D1; else
        if (sz0 == "1S") timeframe = PERIOD_W1; else
        if (sz0 == "1MES") timeframe = PERIOD_MN1;
        if ((m_Counter >= def_MaxTemplates) || (m_Params.Param[TEMPLATE] == "")) return;
        bIsSymbol = SymbolSelect(m_Params.Param[TEMPLATE], true);
        w = (m_Params.Param[WIDTH] != "" ? (int)StringToInteger(m_Params.Param[WIDTH]) : 0);
        h = (m_Params.Param[HEIGHT] != "" ? (int)StringToInteger(m_Params.Param[HEIGHT]) : 0);
        i = (m_Params.Param[SCALE] != "" ? (int)StringToInteger(m_Params.Param[SCALE]) : -1);
        i = (i > 5 || i < 0 ? -1 : i);
        if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else
        {
                SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w);
                if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl");
                if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD)
                {
                        C_Chart_IDE::Create(GetIdSubWinEA());
                        m_Info[m_Counter - 1].szVLine = "";
                }else
                {
                        m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
                        ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0);
                        ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack);
                }
                ChartRedraw(m_Info[m_Counter - 1].handle);
        }
}

Os trechos que estão em destaque é que fazem a seleção do como os dados serão apresentados na tela, o código não é muito diferente do que já existia, mas é justamente estes testes é que garantem que o sistema irá se comportar conforme o desejado pelo usuário. Tudo isto até agora não precisava de fato de uma reestruturação no código em um novo modelo, mas quando olhamos a rotina responsável pelo redimensionamento da sub janela, a coisa muda de figura.

void Resize(void)
{
#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, A, B)
        int x0, x1, y;
        if (!ExistSubWin()) return;
        x0 = 0;
        y = (int)(ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS, GetIdSubWinEA()));
        x1 = (int)((Terminal.GetWidth() - m_Aggregate) / (m_Counter > 0 ? (m_CPre == m_Counter ? m_Counter : (m_Counter - m_CPre)) : 1));
        for (char c0 = 0; c0 < m_Counter; x0 += (m_Info[c0].width > 0 ? m_Info[c0].width : x1), c0++)
        {
                macro_SetInteger(OBJPROP_XDISTANCE, x0);
                macro_SetInteger(OBJPROP_XSIZE, (m_Info[c0].width > 0 ? m_Info[c0].width : x1));
                macro_SetInteger(OBJPROP_YSIZE, y);
                if (m_Info[c0].szTemplate == "IDE") C_Chart_IDE::Resize(x0);
        }
        ChartRedraw();
#undef macro_SetInteger
}

A linha destacada impede que o sistema seja construído na base antiga, esta linha irá testar se existe uma sub janela aberta e mantida pelo EA, em caso negativo, ela retorna e nada mais será feito pela rotina, mas se tal sub janela existir, todas as coisas presentes nela devem ser redimensionadas conforme o necessário, e é apenas por conta deste teste, que o sistema foi totalmente remodelado.

A próxima rotina que sofreu mudanças pode ser vista abaixo:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        int mx, my;
        datetime dt;
        double p;

        C_Chart_IDE::DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        mx = (int)lparam;
                        my = (int)dparam;
                        ChartXYToTimePrice(Terminal.Get_ID(), mx, my, my, dt, p);
                        for (int c0 = 0; c0 < m_Counter; c0++)  if (m_Info[c0].szVLine != "")
                        {
                                ObjectMove(m_Info[c0].handle, m_Info[c0].szVLine, 0, dt, 0);
                                ChartRedraw(m_Info[c0].handle);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        Resize();
                        for (int c0 = 0; c0 < m_Counter; c0++)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, OBJPROP_PERIOD, m_Info[c0].timeframe);
                                ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, OBJPROP_CHART_SCALE,(m_Info[c0].scale < 0 ? ChartGetInteger(Terminal.Get_ID(),CHART_SCALE):m_Info[c0].scale));
                        }
                        break;
        }
}

A parte em destaque é que de fato merece atenção especial. O que ela faz ?!?! É justamente este código que irá apresentar a linha vertical no local correto e no template correto. O restante do código irá simplesmente manter e ajustar os templates conforme vai ocorrendo mudanças no gráfico.

Existem vantagens enormes em fazer isto aqui, na classe objeto, do que fazer isto no EA, dentro do sistema de evento OnChartEvent, e o principal é que cada classe pode tratar da forma adequada o evento que o MT5 esta enviando para o EA, ao invés de centralizar tudo em uma única rotina, deixamos que cada classe faça o seu trabalho, e caso não queiramos usar a classe no EA, bastará retirar ela não tendo nenhum efeito colateral no restante do código.

Programação é ou não é algo LINDO ?!?! E eu AMO programar....

Antes de passar para o próximo ponto dentro deste artigo, irei comentar rapidamente os valores que podem ser usados nos parâmetros 1 e 2. O parâmetro 1 pode receber os seguintes valores: 1M, 2M, 3M, 4M, 5M, 6M, 10M, 12M, 15M, 20M, 30M, 1H, 2H, 3H, 4H, 6H, 8H, 12H, 1D, 1S, 1MES este valores não são aleatórios e nem foram escolhidos por acaso, eles derivam da enumeração ENUM_TIMEFRAME e replicam exatamente o que você faria se estivesse usando o gráfico normal. Já o parâmetro 2 pode receber valores entre 0 e 5, sendo o zero a escala mais distante e o cinco a mais próxima, para mais detalhes veja CHART_SCALE.


3.4 Dando Suporte a Janelas Flutuantes

Agora vamos entender como as janelas flutuantes são criadas e mantida, pois sem entender isto não será possível tirar proveito real do sistema. A classe objeto responsável por isto recebeu o nome de C_ChartFloating, mas vocês podem pensar: Por que você não utiliza a classe Control da biblioteca padrão do MQL5 ? O motivo é simples. A classe control nos permite criar e manter uma janela com uma funcionalidade bastante similar ao do sistema operacional presente na máquina, mas ela para nosso proposito é demasiadamente exagerada, precisamos de algo muito mais simples, usar a classe control para fazer o que queremos, seria como usar uma bazuca para matar uma mosca, então por este motivo a classe C_ChartFloating foi concebida, ela contém o mínimo de elementos necessários para dar suporte a janelas flutuantes, ao mesmo tempo que nos permite controlar a mesma.

A classe em si não necessita de muita explicação já que tudo que fazemos é criar 4 objetos gráficos, mas tem entre as funções interna tem 2 que merecem mais destaque, vamos começar vendo a função que cria a janela, esta tem seu código visto abaixo:

bool AddIndicator(string sz0, int x = 0, int y = -1, int w = 300, int h = 200, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1)
{
        m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS);
        m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS);
        if (m_MaxCounter >= def_MaxFloating) return false;
        y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);
        CreateBarTitle();
        CreateCaption(sz0);
        CreateBtnMaxMin();
        CreateRegion(TimeFrame, Scale);
        m_Win[m_MaxCounter].handle = ObjectGetInteger(Terminal.Get_ID(), m_Win[m_MaxCounter].szRegionChart, OBJPROP_CHART_ID);
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, sz0 + ".tpl");   
        m_Win[m_MaxCounter].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
        ObjectCreate(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJ_VLINE, 0, 0, 0);
        ObjectSetInteger(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJPROP_COLOR, clrBlack);
        m_Win[m_MaxCounter].PosX = -1;
        m_Win[m_MaxCounter].PosY = -1;
        m_Win[m_MaxCounter].PosX_Minimized = m_Win[m_MaxCounter].PosX_Maximized = x;
        m_Win[m_MaxCounter].PosY_Minimized = m_Win[m_MaxCounter].PosY_Maximized = y;
        SetDimension(w, h, true, m_MaxCounter);
        SetPosition(x, y, m_MaxCounter);
        ChartRedraw(m_Win[m_MaxCounter].handle);
        m_MaxCounter++;
        return true;
}

O que este código acima faz, é criar todo o suporte necessário para podermos aplicar um template em um objeto CHART, este é aplicado no ponto destacado no código, observem que para chamar esta função, o único parâmetro realmente necessário é o nome do template, todos os outros valores são pre inicializados, mas nada impede de você indicar quais são os desejados. Para cada nova janela criada a próxima será deslocada levemente de forma a não sobrepor, por padrão, as outras, isto é conseguido na seguinte linha:

y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);

A próxima rotina de real interesse nesta classe é a que fica responsável pelo tratamento das mensagens, e ela pode ser vista abaixo:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        int mx, my;
        datetime dt;
        double p;
        static int six = -1, siy = -1, sic = -1;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        mx = (int)lparam;
                        my = (int)dparam;
                        if ((((int)sparam) & 1) == 1)
                        {
                                if (sic == -1)  for (int c0 = m_MaxCounter - 1; (sic < 0) && (c0 >= 0); c0--)
                                        sic = (((mx > m_Win[c0].PosX) && (mx < (m_Win[c0].PosX + m_Win[c0].Width)) && (my > m_Win[c0].PosY) && (my < (m_Win[c0].PosY + def_SizeBarCaption))) ? c0 : -1);
                                if (sic >= 0)
                                {
                                        if (six < 0) ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
                                        six = (six < 0 ? mx - m_Win[sic].PosX : six);
                                        siy = (siy < 0 ? my - m_Win[sic].PosY : siy);
                                        SetPosition(mx - six, my - siy, sic);
                                }
                        }else
                        {
                                if (six > 0) ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
                                six = siy = sic = -1;
                        }
                        ChartXYToTimePrice(Terminal.Get_ID(), mx, my, my, dt, p);
                        for (int c0 = 0; c0 < m_MaxCounter; c0++)
                                ObjectMove(m_Win[c0].handle, m_Win[c0].szVLine, 0, dt, 0);
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        for (int c0 = 0; c0 < m_MaxCounter; c0++) if (sparam == m_Win[c0].szBtnMaxMin)
                        {
                                SwapMaxMin((bool)ObjectGetInteger(Terminal.Get_ID(), m_Win[c0].szBtnMaxMin, OBJPROP_STATE), c0);
                                break;
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        for(int c0 = 0; c0 < m_MaxCounter; c0++)
                        {
                               ObjectSetInteger(Terminal.Get_ID(), m_Win[c0].szRegionChart, OBJPROP_PERIOD, m_Win[c0].TimeFrame);
                               ObjectSetInteger(Terminal.Get_ID(), m_Win[c0].szRegionChart, OBJPROP_CHART_SCALE,(m_Win[c0].Scale < 0 ? ChartGetInteger(Terminal.Get_ID(),CHART_SCALE):m_Win[c0].Scale));
                        }
                        m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS);
                        m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS);
                        break;
        }
        for (int c0 = 0; c0 < m_MaxCounter; c0++)
                ChartRedraw(m_Win[c0].handle);
}

Esta rotina concentra em si todo o tratamento de eventos que são suportadas pela classe C_ChartFloating, não importa a quantidade de janelas presentes, ela irá tratar a todas da mesma forma, se fossemos fazer isto dentro da função OnChartEvent no EA, a função ficaria extremamente complexa e muito sujeita a falhas, mas fazendo aqui na classe objeto, garantimos a integridade do código, pois caso venhamos a desejar não usar janelas flutuantes tudo que temos que fazer é retirar o arquivo da classe e os pontos onde ele seria acessado, e isto é feito de forma muito mais rápida e simples se fizermos a coisa da forma como está.

Mas no código acima tem uma parte interessante que esta em destaque e que tem seu código interno mostrado abaixo:

void SwapMaxMin(const bool IsMax, const int c0)
{
        m_Win[c0].IsMaximized = IsMax;
        SetDimension((m_Win[c0].IsMaximized ? m_Win[c0].MaxWidth : 100), (m_Win[c0].IsMaximized ? m_Win[c0].MaxHeight : 0), false, c0);
        SetPosition((m_Win[c0].IsMaximized ? m_Win[c0].PosX_Maximized : m_Win[c0].PosX_Minimized), (m_Win[c0].IsMaximized ? m_Win[c0].PosY_Maximized : m_Win[c0].PosY_Minimized), c0);
}

O que o código acima esta fazendo ?!?! Parece muito confuso para você ? Para entender vejam a animação abaixo com atenção.


O que foi feito é o seguinte: Quando a janela flutuante é criada ela começa tendo um ponto de ancoragem inicial, este é indicado pelo pelo programador, ou pelo sistema de posicionamento da própria classe, este ponto de ancoragem é o mesmo tanto para quando ela estiver maximizada, quanto para o momento em que ela estiver minimizada. Estes valores não são fixos, ou seja, caso o usuário o pode modificar estes pontos facilmente.

Vamos supor o seguinte, que você precise de um dado local do gráfico limpo, então você pode mover a janela maximizada para um local de fácil e rápida leitura, depois minimize a mesma janela e a mova para um local diferente, por exemplo no canto da tela. Pronto, o sistema irá memorizar isto e quando você maximizar a janela ela irá para o ultimo ponto de ancoragem onde ela estava antes de ser minimizada, a mesma coisa vale para a fase em que a janela esta minimizada.


Conclusão

Bem por enquanto é isto, no próximo artigo irei estender esta funcionalidade para a classe de suporte da IDE.


Arquivos anexados |
EA.zip (3616.46 KB)
Desenvolvendo um EA de negociação do zero (Parte 09): Um salto conceitual (II) Desenvolvendo um EA de negociação do zero (Parte 09): Um salto conceitual (II)
Colocando o Chart Trade em uma janela flutuante. No artigo anterior criamos o sistema base para utilizar templates dentro de uma janela flutuante.
Matemática na negociação: indices de Sharpe e de Sortino Matemática na negociação: indices de Sharpe e de Sortino
A rentabilidade é a medida mais óbvia que investidores e operadores novatos utilizam para analisar o desempenho da negociação. Já os traders profissionais empregam ferramentas mais robustas para analisar estratégias, entre elas os índices de Sharpe e de Sortino.
Desenvolvendo um EA de negociação do zero (Parte 10): Acessando indicadores personalizados Desenvolvendo um EA de negociação do zero (Parte 10): Acessando indicadores personalizados
Como acessar Indicadores personalizados diretamente no EA ? Um EA de negociação, só será realmente bem explorado se for possível você usar indicadores personalizados nele, caso contrário ele será apenas um conjunto de códigos e instruções.
Desenvolvendo um EA de negociação do zero (Parte 07): Adicionando o Volume At Price (I) Desenvolvendo um EA de negociação do zero (Parte 07): Adicionando o Volume At Price (I)
Este é um dos indicadores mais poderosos que existe. Para quem opera e tenta ter um certo grau de assertividade, não pode deixar de ter este indicador em seu gráfico, apesar de ele ser mais utilizado por quem opera observando o Fluxo (Tape Reading ) ele também pode ser usado por aqueles que fazem uso apenas do Price Action.