English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
preview
Indicadores múltiplos em um gráfico (Parte 03): Desenvolvendo definições para usuários

Indicadores múltiplos em um gráfico (Parte 03): Desenvolvendo definições para usuários

MetaTrader 5Exemplos | 1 março 2022, 09:03
942 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Indicadores múltiplos em um gráfico eu expliquei o código base para poder usar mais de um indicador dentro de uma sub janela, mas aquilo que foi apresentado foi apenas a base inicial de um sistema muito maior. Existem varias coisas que se dá para fazer a partir daquele modelo, mas temos que ir aos pouco, pois uma das intenções destes artigos é motivar você a aprender programação e assim conseguir desenvolver seus próprios sistema, a partir de uma ideia que você tenha. Neste artigo daqui vamos aumentar as funcionalidades, e estas podem ser interessantes para quem já gostou do que o sistema já conseguia fazer, mas gostaria de poder fazer mais.


Planejamento

Muitas vezes quando começamos a implementar um novo sistema, não temos a real ideia de até onde podemos melhorar ele, por isto devemos sempre começar um novo projeto já prevendo que ele poderá sofrer melhorias futuras. Isto é primordial para quem esta começando, sempre planeje as coisas imaginando futuras expansões e melhorias.

Bem, o código do base não sofreu nenhuma mudança, o que de certa forma já era esperado, mas o código da classe objeto, esta passou por uma radical mudança, mas as mudanças foram feitas para implementar as novas funcionalidades e possibilitar novas melhorias de forma ainda mais ágil, já que o reaproveitamento de código passa a ser ainda maior, e esta é uma das filosofias da programação orientada a objetos, reutilizar sempre, criar somente quando for necessário. Então vamos dissecar a nova classe objeto, e notem que irei destacar as mudanças para facilitar o entendimento.

Vamos começar com as novas definições de variáveis privativas da classe.

struct st
{
        string  szObjName,
                szSymbol;
        int     width;
}m_Info[def_MaxTemplates];
int             m_IdSubWin,
                m_Counter,
                m_CPre,
                m_Aggregate;
long            m_Id,
                m_handle;
ENUM_TIMEFRAMES m_Period;

vejam que aumentou em muito o numero de variáveis usadas, isto por que precisamos de mais dados para controlar as novas funcionalidades de forma adequada. Observem que agora temos um estrutura dentro do nosso sistema de variáveis, estas estruturas são muito boas para agrupar variáveis relacionadas umas com as outras, elas asseguram que quando formos manipular os dados, teremos rápido e fácil acesso aos dados.

void SetBase(const string szSymbol, int iScale, int iSize)
{
#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[m_Counter].szObjName, A, B)
        if (m_IdSubWin < 0)
        {
                m_Id = ChartID();
                m_IdSubWin = (int)ChartGetInteger(m_Id, CHART_WINDOWS_TOTAL) - 1;
                m_Aggregate = 0;
        }
        m_Info[m_Counter].szObjName = __FILE__ + (string) MathRand() + (string) ObjectsTotal(m_Id, -1, OBJ_CHART);
        ObjectCreate(m_Id, m_Info[m_Counter].szObjName, OBJ_CHART, m_IdSubWin, 0, 0);
        ObjectSetString(m_Id, m_Info[m_Counter].szObjName, OBJPROP_SYMBOL, (m_Info[m_Counter].szSymbol = szSymbol));

// ....

        macro_SetInteger(OBJPROP_PERIOD, m_Period);
        m_handle = ObjectGetInteger(m_Id, m_Info[m_Counter].szObjName, OBJPROP_CHART_ID);
        m_Aggregate += iSize;
        m_Info[m_Counter].width = iSize;
        m_CPre += (iSize > 0 ? 1 : 0);
        m_Counter++;
#undef macro_SetInteger
};

A principal mudança que logo vemos, é o fato de estarmos usando a estrutura para armazenar o nome do ativo, o nome do objeto e a largura do mesmo, sim agora temos também esta possibilidade, indicar a largura que o indicador irá ter na janela, mas também fazemos algumas anotações para o uso em outras partes da classe. Agora vamos ver a rotina que mais sofreu mudanças.

void Decode(string &szArg, int &iScale, int &iSize)
{
#define def_ScaleDefault 4
#define macro_GetData(A)                \
        b0 = false;                     \
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);     \
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != A); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);        \
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
                                                                
        string sz1;
        int i0, i1, c1 = StringLen(szArg);
        bool b0 = true;
        StringToUpper(szArg);
        iScale = def_ScaleDefault;
        m_Period = _Period;
        for (int c0 = 0, max = StringLen(szArg); c0 < max; c0++) switch (szArg[c0])
        {
                case ':':
                        b0 = false;
                        for (; (c0 < max) && ((szArg[c0] < '0') || (szArg[c0] > '9')); c0++);
                        iScale = (int)(szArg[c0] - '0');
                        iScale = ((iScale > 5) || (iScale < 0) ? def_ScaleDefault : iScale);
                        break;
                case ' ':
                        break;
                case '<':
                        macro_GetData('>');
                        if (sz1 == "1M") m_Period = PERIOD_M1; else

//....

                        if (sz1 == "1MES") m_Period = PERIOD_MN1;
                        break;
                case '[':
                        macro_GetData(']');
                        iSize = (int) StringToInteger(sz1);
                        break;
                default:
                        c1 = (b0 ? c0 : c1);
                        break;
        }
        szArg = StringSubstr(szArg, 0, c1 + 1);
#undef macro_GetData
#undef def_ScaleDefault
}

As linhas verdes são adições no código, já a linha amarela, já existia no código original, mas foi deslocada por motivos práticos. Mas vamos entender o que toda esta adição faz no código e o principal, o que melhora em termos de funcionalidade o sistema original. Bem basicamente criamos meios do usuário configurar algumas coisas especificas, e tentamos fazer isto adicionando novas regras a sintaxe já existente, veja na tabela abaixo:

Delimitador Funcionalidade Exemplo  Resultado 
< > Indica o período gráfico a ser usado  < 15m > Trava o período do indicador em 15 min, o gráfico de origem pode usar um período diferente, mas o indicador somente mostrará com dados de 15 minutos.
 [ ] Indica a largura do indicador  [ 350 ] Trava a largura do indicador em 350 pixels 

O delimitador que trava o período gráfico irá fazer isto apenas no indicador no qual ele esteja presente, e não irá afetar em nada nenhuma troca que possa ser feita pelo usuário, todos os outros indicadores e o gráfico principal sofreram a atualização para o novo período gráfico desejado pelo usuário, mas o indicador travado não irá acompanhar o novo período gráfico que passará a ser usado. Isto pode ser interessante em alguns casos, veja a imagem abaixo um caso especifico.

         

isto facilita em muito em vários tipos de setups, onde você deve manter gráficos do mesmo ativo mas em períodos diferentes visíveis na tela de negociação, agora o delimitador que trava a largura do gráfico, facilitará em casos ainda mais específicos, mas ele tem uma outra utilidade muito grande que apresentarei em outro artigo, mas no momento você pode usá-lo para poder controlar qual indicador deverá ter uma largura maior, por um motivo ou outro.

Você pode usar a combinação de todos os delimitadores, ou usar apenas aquele que realmente é necessário usar em um indicador, não existe regras quanto a precedência deles, a única regra, é que o nome do indicador deverá vim antes de qualquer outra coisa. Voltando a explicação do código, observe as seguintes linhas:

#define macro_GetData(A)                \
        b0 = false;                     \
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);     \
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != A); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);        \
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
           
//....                                                     
                case '<':
                        macro_GetData('>');

Observem que definimos algo que para muitos pode ser estranho, mas o nome é bem sugestivo: macro_GetData(A), isto irá criar um trecho de código que é uma macro, quando o compilador encontrar esta definição no código, ele, compilador, irá substituir a declaração pelo código da macro, isto é muito útil quando iremos repetir um determinado trecho de código em vários pontos, mas com uma variação mínima entre uma declaração e outra, no exemplo acima a linha verde seria substituída e o código gerado pelo compilador seria como visto abaixo:

case '<':
	b0 = false;
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != '>'); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
//....

este tipo de coisa é a representação mais fiel da filosofia, reutilizar o máximo possível, escrever o mínimo possível. Agora vamos ver algo que você pode mudar se desejar deixar a sintaxe mais clara, é um pequeno detalhe, mas que talvez possa lhe ser muito mais agradável adaptar ao seu estilo pessoal. Observe a seguinte linha:

//.....

if (sz1 == "1M") m_Period = PERIOD_M1; else

//.....

A informação destacada é que é o grande detalhe, pelo modelo que usei, o usuário quando desejar usar um período de 1 minuto deverá informar isto usando a seguinte sintaxe: < 1M >, mas isto pode ser mudado caso você queira deixar a coisa com o seu estilo pessoal, mas as letras tem que estar em caixa alta ( MAIUSCULAS ) e sem espaço, você pode por exemplo substituir o trecho destacado por "1MIN" , "MIN_1" , "1_MINUTO" ou algo ainda mais evidente como por exemplo: "LOCK_IN_1_MIN" ou em português "TRAVE_EM_1_MIN", isto pode ser feito e funcionará, desde que não exista espaços entre as palavras, esta limitação quanto ao espaço é algo que pode ser retirado, mas ao meu ver talvez não aja uma real necessidade de ser feita, vejam como é lindo o fato de se conhecer programação, você pode deixar as coisas com o seu estilo ou a sua cara. O próximo código que sofreu mudanças foi o destructor padrão.

~C_TemplateChart() 
{
        for (char c0 = 0; c0 < m_Counter; c0++)
        {
                ObjectDelete(m_Id, m_Info[c0].szObjName);
                SymbolSelect(m_Info[c0].szSymbol, false);
        }
}

A linha destacada foi adicionada de forma a caso o ativo não esteja aberto em uma janela em separado, ele deverá deixar de aparecer na Observação de Mercado, isto evita que ativos que não estão em uso fiquem ali ocupando espaço desnecessário e poluindo a janela. Agora vamos ver algo que eu prometi falar no artigo anterior, mas que não constava no código original, mas que fará parte do código deste momento em diante.

void AddTemplate(const eTypeChart type, const string szTemplate, int scale, int iSize)
{
        if (m_Counter >= def_MaxTemplates) return;
        if (type == SYMBOL) SymbolSelect(szTemplate, true);
        SetBase((type == INDICATOR ? _Symbol : szTemplate), scale, iSize);
        if (!ChartApplyTemplate(m_handle, szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, "Default.tpl");
        ChartRedraw(m_handle);
}

A linha destacada faz algo que eu disse que poderia ser feito, usar um arquivo de configuração padrão se todos os ativos a serem observados viessem a utilizar as mesmas configurações. Não é bom fazer algo sem de fato entender como você deverá de fato proceder se as coisas não estiverem de acordo com o esperado, mas se as configurações forem EXATAMENTE iguais por que não usar a mesma configuração ?!?! Pois bem, observem que quando não for encontrado o arquivo de configuração do ativo especificado, será considerado que você deseja usar as configurações padrão do MT5, e isto é definido como sendo o arquivo de nome DEFAULT.TPL que se encontra no diretório Profiles\Templates mas vamos entender uma coisa aqui e isto é importante. Por que eu não informei nenhum diretório de pesquisa na função ChartApplyTemplate ??? O motivo é que a pesquisa é feita pelo MT5 seguindo uma determinada lógica, e saber como esta lógica funciona pode lhe ajudar a encarar as coisas de forma bem interessante e com menos stress.

Vamos supor o seguinte cenário, onde iriamos substituir a linha destacada por esta:

if (!ChartApplyTemplate(m_handle, "MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, ""MyTemplates\\Default.tpl");

O MT5 iria procurar o arquivo de configuração primeiramente dentro do sub diretório MYTEMPLATES do diretório onde se encontra o executável do indicador personalizado, ou seja se na mesma pasta onde se encontra o executável que estamos usando para produzir múltiplos indicadores, tiver uma pasta chamada MYTEMPLATES, o MT5 irá procurar ali o arquivo que queremos. No entanto se nada for encontrado ali, ele irá procurar o mesmo arquivo só que agora na pasta MQL5\Profiles\Templates\MyTemplates, por isto eu não mostrei isto antes, mas não é só isto tem um detalhe extra neste mesmo código, suponhamos que ele seja conforme mostrado abaixo:

if (!ChartApplyTemplate(m_handle, "\\MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, ""\\MyTemplates\\Default.tpl");

Um pequeno detalhe que muda tudo, agora o MT5 irá procurar primeiro o arquivo que desejamos no diretório MQL5\MyTemplates, e caso não encontre o arquivo requerido ele irá executar os passos informados acima. Isto pode ser visto observando na documentação da função ChartApplyTemplate , ou seja eu não queria confundir você que talvez não saiba como o MT5 funciona lhe dando uma falsa ideia de seu funcionamento, mas agora que você já compreende como ele procura o arquivo de configuração, poderá criar variações e saberá onde deverá colocar os arquivos.

A próxima função da nossa classe que sofreu grandes mudanças pode ser visto abaixo:

void Resize(void)
{
#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[c0].szObjName, A, B)
        int x0 = 0, x1, y = (int)(ChartGetInteger(m_Id, CHART_HEIGHT_IN_PIXELS, m_IdSubWin));
        x1 = (int)((ChartGetInteger(m_Id, CHART_WIDTH_IN_PIXELS, m_IdSubWin) - 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);
        }
        ChartRedraw();
#undef macro_SetInteger
}

Os pontos destacadas são os cálculos mais importantes desta rotina, eles irão ajustar as coisas de forma que as janelas se ajustem conforme o desejo do usuário, mas notem que as janelas que foram definidas com tamanho fixo irão criar uma área livre onde as outras terão as suas dimensões ajustadas, mas se a janela principal tiver sua largura reduzida, o calculo em azul não fará as janelas de tamanho fixo terem suas larguras reduzidas, isto pode ser um problema, mas vamos deixar isto para o futuro, já que as janelas de largura fixa tem uma outra utilidade que será mais bem exploradas no futuro. E a última rotina da nossa classe pode se vista abaixo:

void AddThese(const eTypeChart type, string szArg)
{
        string szLoc;
        int i0, iSize;
//....
        Decode(szLoc, i0, iSize);
        AddTemplate(type, szLoc, i0, iSize);
//....
}

Notem que a única coisa que sofreu modificação se encontra destacado, ou seja, praticamente nada.


Conclusão

Espero que este artigo aliado aos demais mostrem o qual interessante é uma programação estruturada, com pequenas modificações e um pouco de calma, podemos adicionar muito em funcionalidade a um sistema e a reutilização de códigos torna o trabalho de manutenção bem menor já que quanto mais um código é utilizado, menor é a possibilidade de ele conter erros. No próximo artigo iremos transportar este sistema para outros locais, onde ele pode ser mais interessante para muitos e assim entender mais coisas sobre programação. Então até ...



Melhorando o reconhecimento de padrões de velas por meio de velas Doji Melhorando o reconhecimento de padrões de velas por meio de velas Doji
Como encontrar padrões de velas com mais frequência do que o habitual. Por trás da simplicidade dos padrões de velas, há também uma séria desvantagem que pode ser evitada usando os abundantes recursos das modernas ferramentas de automação de negociação.
Indicadores múltiplos em um gráfico (Parte 04): Iniciando pelo EA Indicadores múltiplos em um gráfico (Parte 04): Iniciando pelo EA
Em artigos anteriores, eu expliquei como criar um indicador com múltiplas sub janela, mas apesar de ser interessante de se fazer, quando usamos um indicador personalizado. Aqui vamos entender como adicionar múltiplas janelas em um EA.
Usando o AutoIt com MQL5 Usando o AutoIt com MQL5
Este artigo descreve como criar scripts para o terminal MetraTrader 5, integrando MQL5 com AutoIt. Vou mostrar como automatizar várias tarefas usando a interface do usuário do terminal e apresentar uma classe que usa a biblioteca AutoItX.
Stop-loss fixo com base na ação do preço e RSI (stop-loss "inteligente") Stop-loss fixo com base na ação do preço e RSI (stop-loss "inteligente")
O Stop-loss é a principal ferramenta de gerenciamento de dinheiro na negociação. O uso eficaz do stop-loss, take-profit e tamanho do lote pode tornar a negociação mais consistente e, em geral, mais lucrativa. No entanto, fazer uso disto tem suas próprias dificuldades. A principal delas é a caça ao stop-loss. Neste artigo analisaremos como minimizar o efeito da caça ao stop-loss e compararemos isto com o uso clássico de stop loss para determinar lucratividade.