![Conjunto de ferramentas para marcação manual de gráficos e negociação (Parte III). Otimização e novas ferramentas](https://c.mql5.com/2/49/9914_manual_charting_trading_toolkit_003_600x314.jpg)
Conjunto de ferramentas para marcação manual de gráficos e negociação (Parte III). Otimização e novas ferramentas
Introdução
Nos artigos anteriores (1, 2) descrevi a biblioteca Shortcuts e exemplifiquei como usá-la na forma de EA. De certa forma, ela é como um organismo vivo: criatura que é dada à luz, tem contato com o meio onde deverá viver. O ambiente muda constantemente e estabelece certas regras. Uma delas diz: "desenvolva-se" 🙂. E o organismo tem de se desenvolver para estar em correspondência... Este artigo apresenta alguns resultados de tal processo.
Lembre-se que a biblioteca consiste em 5 arquivos.
Shortcuts.mqh é o arquivo mais importante. Ele não só armazena a lógica de processamento de teclas pressionadas, senão que também é o único que é diretamente incorporado ao Expert Advisor ou ao indicador. Desse modo, ele inclui os outros arquivos e efetua sua inicialização.
No arquivo GlobalVariables.mqh foram colocadas todas as variáveis globais. Elas são, basicamente, configurações que permitem definir as cores, espessura da linha, fator de prolongação de linhas retas, etc.
O arquivo Mouse.mqh contém a descrição da classe para movimentação do mouse. Ele armazena as coordenadas atuais do cursor (tanto em pixels quanto em "preço-tempo") e o número da barra atual.
O arquivo Utilites.mqh contém funções auxiliares. Ele permite calcular os extremos das barras, os pontos de intersecção de linhas e outras coisas úteis que não estão diretamente relacionadas ao desenho, mas que o definem completamente.
O arquivo Graphics.mqh trata, em essência, do desenho, e, para isso, usa os dados dos outros arquivos. Além disso, é o arquivo Shortcuts.mqh que chama suas funções, principalmente.
Nem sempre sou rigoroso com o escopo das funções. Às vezes, os cálculos podem ser realizados dentro das funções de desenho. Por enquanto, estou à vontade com desenvolver e dar manutenção desse jeito. Talvez um dia corrija estas imperfeições.
Este projeto ilustra como o código da biblioteca pode ser usado num indicador.
Otimizando a velocidade da biblioteca
Neste caso, as alterações são mínimas.
Respeito a projetos com versões iniciais da biblioteca, por que escolhi um EA e não um indicador? A resposta é simples: cada EA funciona em seu próprio thread de execução (e, idealmente, não afeta outros EAs), dessa forma, o terminal é menos lento se for necessário manipular atalhos de teclado em vários gráficos.
O objetivo de um EA é negociar, porém este produto de software não faz isso. Além disso, quando há um indicador no gráfico, é muito mais fácil colocar outro EA. Assim, em algum momento foi decidido que o indicador deveria existir, e a questão da velocidade veio à tona. Isto último é especialmente importante se o usuário tiver muitas janelas abertas, p.e. se usuário tiver 40 guias abertas (longe do limite), será difícil processar as verificações de teclas pressionadas se elas ocorrerem em todos os gráficos simultaneamente.
E foi aí que uma ideia brilhante iluminou minha cabeça: por que verificar todos os gráficos? Se apenas a janela ativa estiver em funcionamento, as verificações só deverão ocorrer nessa janela.
O código é muito simples.
/* Shortcuts.mqh */ void CShortcuts::OnChartEvent( const int id, const long &lparam, const double &dparam, const string &sparam ) { //... if(ChartGetInteger(0,CHART_BRING_TO_TOP)==false) { return; } //...
No início da função é necessário verificar se o gráfico em questão está ativo, ou seja, se ele está em primeiro plano. Nós não precisamos fazer nada se este não for o caso.
Eu não medi o desempenho real ou o ganho de velocidade como porcentagem. Mas de acordo com o feedback dos usuários que baixaram a biblioteca e que realmente usam muitas guias, o aplicativo se tornou mais rápido, mesmo na forma de indicador. Isto é exatamente o que precisamos.
Neste ponto, devemos lembrar o objetivo do aplicativo.
Em primeiro lugar, ele é projetado para executar funções ocasionais (portanto, não é chamado a cada tick) e, por isso, consome recursos por um tempo muito limitado.
Em segundo lugar, os gráficos em si são uma fonte de problemas de velocidade se seu computador não for suficientemente potente. Quanto mais objetos no gráfico, mais difícil é processá-los. No entanto, como a biblioteca é, de facto, gráfica, eu tenho que aceitar o custo de desenhar e controlar cuidadosamente o que está sendo desenhado.
Em terceiro lugar, a função que mais consome recursos do programa é a de busca de extremos. Porém, não sei como implementá-la mais rapidamente. Por isso, considero que a atual implementação é ótima, por enquanto. De qualquer forma, esta função não é chamada com tanta frequência, apenas quando são traçadas linhas e outras formas úteis, e para já seu desempenho não otimizado pode ser ignorado .
Todas as outras funções são chamadas com muito menos frequência e funcionam rápido o suficiente para as tarefas deste aplicativo, e podemos não falar sobre elas.
Refatoração do código: a interconexão
A versão do código apresentada nos artigos anteriores assume que o aplicativo é monolítico e que suas partes não poderão ser utilizadas separadamente. É por isso que o código utilizava as configurações globais diretamente, enquanto alguns utilitários dependiam da classe do mouse.
Devido a isto, eu poderia escrever o código mais rapidamente, mas do ponto de vista da reutilização, era inconveniente. Se eu precisar anexar o arquivo de utilitário existente a um novo projeto que não utilize o mouse ou gráficos, ainda tenho que anexar o arquivo de configurações globais e a classe do mouse.
Isto é errado e desconfortável.
Por isso resolvi fazer uma pequena modificação no código. É por isso que decidi modificar ligeiramente o código. Todas as variáveis globais ainda são utilizadas. Não podemos removê-las, pois elas são as configurações.
Adicionei campos privados contendo cópias destas variáveis às classes principais. Para armazenar estes valores, é necessário adicionar funções especiais "públicas". Eles também são necessários para a leitura dos valores.
Ficam mais ou menos assim:
private: /* Fields */ //--- static int m_TrendLengthCoefficient; public: /* Methods */ //--- static int TrendLengthCoefficient(void) {return m_TrendLengthCoefficient;} //--- static void TrendLengthCoefficient(int _coefficient) {m_TrendLengthCoefficient=_coefficient;}
O processo parece longo e entediante, considerando o número de configurações existentes.
Mas a vantagem é grande. Em primeiro lugar, a classe se torna independente de arquivos externos. Se alguém quiser usar a classe, pode usar apenas as variáveis necessárias e definir seus valores conforme a necessidade.
Em segundo lugar, tais variáveis podem ser alteradas no momento da execução. Por exemplo, alguém quer escrever uma função que constrói um leque de linhas a partir de um único ponto. Cada linha é duas vezes mais longa que a anterior, e elas divergem em ângulos diferentes. Como fazer isso? Usando a implementação atual da classe CUtilites: antes de cada desenho, definimos o parâmetro descrito para o exemplo, TrendLengthCoefficient, colocando os pontos iniciais nas mesmas coordenadas, enquanto os pontos finais devem ser colocados em algum círculo de raio arbitrário.
Em terceiro lugar, os dados dentro da classe podem ser agrupados de qualquer forma conveniente. Podemos criar estruturas ou mesmo classes completas que armazenam separadamente, digamos, dados relativos aos retângulos, dados relativos às linhas diagonais e dados sobre os níveis. Do ponto de vista do usuário final, a interface (a maneira de acessar os dados) não foi alterada.
Em quarto lugar, os dados não devem necessariamente ser armazenados na RAM. Às vezes, as variáveis podem ser armazenadas nas variáveis globais do terminal, ou geralmente armazenadas em arquivos ou mesmo em um banco de dados. Alguns parâmetros podem ser calculados de imediato com base em outros parâmetros. Com esta disposição "correta" do acesso aos dados, como mostrado no exemplo acima, o usuário final pode reutilizar o código sem se preocupar com a implementação direta das estruturas de dados. De qualquer forma, é muito conveniente, apesar do esforço adicional necessário para escrever código excedente, chamar funções desnecessárias e ter que inicializar as variáveis necessárias cada vez que uma instância é criada.
Por isso, reescrevi tudo o que pude na versão atual da biblioteca para corresponder ao novo estilo, para que o arquivo de utilitários possa agora ser usado "como está" em qualquer projeto.
A classe do mouse originalmente continha todas as funções relacionadas, de modo que não havia nada a ser corrigido nela. A classe de desenho é inútil sem os utilitários. Entretanto, mudei todas as configurações externas nela para a nova forma de campos.
Assim, aqui está o que temos. O mouse e os utilitários são classes bastante independentes que podem ser usadas por conta própria ou em combinações. A classe de desenho usa ambas, mas é independente de outros arquivos externos, exceto o gerenciador que deve inicializar a classe. A classe que contém atalhos de teclado é a classe de gerenciamento, ou seja, o gerenciador que faz o código inteiro funcionar conforme necessário. Assim, a interconexão das classes é agora muito mais fraca, o que resulta nos benefícios acima mencionados.
Ferramenta fios
Na versão anterior da biblioteca, quando se desenhava uma linha de tendência, um fio cruzado era desenhado no final da linha para marcar o tempo e os níveis de preço. Para criá-lo, eu usava duas linhas simples, vertical e horizontal. Entretanto, para mostrá-lo num ponto arbitrário do gráfico, tinha que pressionar duas teclas, H e I. Às vezes era conveniente... mas, na verdade, queremos menos esforço... Por isso, acrescentei a ferramenta fio.
A ferramenta funciona de uma maneira comum. Se movermos o cursor para o local desejado e pressionarmos X, será gerado o fio. Abaixo está o código da função.
/* Graphics.mqh */ //+------------------------------------------------------------------+ //| Draws a crosshair at specified coordinates. If the coordinates | //| are not set, the mouse pointer coordinates are used. | //+------------------------------------------------------------------+ //| Parameters: | //| datetime _time - crosshair time | //| double _price - price level | //+------------------------------------------------------------------+ void CGraphics::DrawCross(datetime _time=-1,double _price=-1) { datetime time; double price; //--- if(_time==-1) { time=CMouse::Time(); } else { time=_time; } if(_price==-1) { price=CMouse::Price(); } else { price=NormalizeDouble(_price,Digits()); } DrawSimple(OBJ_HLINE,time,price); DrawSimple(OBJ_VLINE,time,price); }
Para aqueles familiarizados com o código da versão anterior, não há nada de especial desta vez. Em primeiro lugar, estabelecemos as coordenadas. Se estas coordenadas forem passadas usando os parâmetros, então são usados exatamente estes valores. Se os parâmetros padrão forem definidos, então usaremos as coordenadas do ponteiro do mouse.
Além disso, a linha é desenhada usando a função que foi descrita no segundo artigo.
Para que esta função funcione exatamente da maneira descrita, ela deve ser chamada a partir do arquivo Shortcuts.mqh quando ocorre um determinado evento - tecla X pressionada.
/* GlobalVariables.mqh */ // ... input string Cross_Key="X"; // Crosshair where the mouse was clicked // ... /* Shortcuts.mqh */ void CShortcuts::OnChartEvent( /* ... */ ) switch(id) { case CHARTEVENT_KEYDOWN: // ... //--- Draw a crosshair if(CUtilites::GetCurrentOperationChar(Cross_Key) == lparam) { m_graphics.DrawCross(); } }
Ferramenta: linha de tendência atravessando extremos arbitrários
A opção de criar uma linha de tendência que atravessa extremos com um certo número de barras à esquerda e à direita é prática. No entanto, às vezes pode ser conveniente traçar uma linha que cruza extremos arbitrários. Isto pode ser feito usando o comando Q.
O gif abaixo mostra um exemplo de como esta função funciona.
Como meu aplicativo de captura de tela tem algumas características específicas, eu tive que clicar no gráfico sempre antes de desenhar. Em condições reais, basta ativar o gráfico, e em seguida é possível desenhar tantas linhas quantas forem necessárias.
A linha é desenhada em duas etapas. No primeiro passo, você deve pressionarQ. Isto ativa o modo de desenho de linha arbitrário e marca o primeiro ponto, ficando assim claro que o comando foi executado.
Se não quisermos utilizar este ponto extremo (onde está o marcador), podemos pressionar Q mais uma vez - isto irá mudar o modo e cancelará o desenho. (Algum dia, provavelmente mudarei este comportamento ajustando a tecla Esc para cancelar, embora eu pessoalmente me sinta confortável na forma como funciona agora).
Se o primeiro ponto estiver correto, o próximo ponto será selecionado clicando perto do segundo extremo. Se isto resultar, o marcador não será mais necessário. Ele será apagado e a linha de tendência será desenhada.
Como os parâmetros da linha "arbitrária" não dependem do modo T, pode-se configurar, digamos, que T desenha uma linha em negrito de 4 px de largura, quatro vezes maior que o espaçamento entre os extremos, enquanto Q desenha uma linha fina que é duas vezes maior que o espaçamento.
Como de costume, o código é dividido em vários arquivos.
Vamos começar do final, do processamento do evento CHARTEVENT_KEYDOWN:
/* Shortcuts.mqh */ void CShortcuts::OnChartEvent( const int id, const long &lparam, const double &dparam, const string &sparam ) { //... switch(id) { //... case CHARTEVENT_KEYDOWN: if(CUtilites::GetCurrentOperationChar(Free_Line_Key) == lparam) { m_graphics.ToggleFreeLineMode(); if(m_graphics.IsFreeLineMode()){ m_graphics.DrawFreeLine(CMouse::Bar(),CMouse::Above()); } } //...
Caso o programa determine que a tecla Q é pressionada (a letra é armazenada na variável externa Free_Line_Key), ela muda o modo de desenho. Se após a comutação do modo se verificar que o modo está ligado, será gerado um comando para executar a função de desenho de linha.
O clique é processado no evento
/* Shortcuts.mqh */ //... case CHARTEVENT_CLICK: ChartClick_Handler(); break; //... } //+------------------------------------------------------------------+ //| Processing a click on a free chart field | //+------------------------------------------------------------------+ void CShortcuts::ChartClick_Handler() { //--- if(m_graphics.IsFreeLineMode()){ m_graphics.DrawFreeLine( CMouse::Bar(),CMouse::Above() ); } }
Mais uma vez, observe que quando a tecla é pressionada, o modo de desenho muda imediatamente, mesmo antes de qualquer desenho começar (o nome da minha função começa com Toggle). Este estado permanece até ser trocado novamente usando as teclas ou depois que a linha é desenhada. Ao clicar, o programa primeiro verifica se há algo a ser desenhado. Se houver, ele desenha e muda para o modo neutro.
A função ChartClick_Handler é implementada separadamente, pois pretendo adicionar mais modos que requerem cliques no gráfico. Por exemplo, o modo de exclusão de objetos complexos como fios cruzados ou níveis verticais que foram descritos no artigo anterior, às vezes pode exigir um clique no gráfico para cancelar o menu. Até agora, parece que uma implementação separada das funções de clique simplificará o desenvolvimento futuro. Mas todos estes recursos serão implementados mais tarde.
Por enquanto, vamos continuar a considerar como funciona o desenho.
/* Graphics.mqh */ //+------------------------------------------------------------------+ //| Draws a line by arbitrary specified extrema. In the current | //| implementation, the first extremum is set by a hot key | //| (Q by default), the second is set by clicking near the | //| required top | //+------------------------------------------------------------------+ //| Parameters: | //| int _bar - bar to start search at | //| bool _isUp - top or bottom? | //| int _fractalSizeRight - number of bars to the right of extr | //| int _fractalSizeLeft - number of bars to the left of extremum| //+------------------------------------------------------------------+ void CGraphics::DrawFreeLine( int _bar, bool _isUp, int _fractalSizeRight=1, int _fractalSizeLeft=1 ) { //--- Variables double selectedPrice,countedPrice,trendPrice1,trendPrice2; datetime selectedTime,countedTime,trendTime1,trendTime2; int selectedBar,countedBar; int bar1,bar2; string trendName="",trendDescription="p2;"; int fractalForFirstSearch = MathMax(_fractalSizeRight,_fractalSizeLeft)* 2; //--- Search for a bar that meets the extremum criteria selectedBar = CUtilites::GetNearesExtremumSearchAround( _bar, _isUp, _fractalSizeLeft, _fractalSizeRight ); //--- Building the starting marker if(0==m_Clicks_Count) { m_Clicks_Count=1; if(_isUp) { m_First_Point_Price=iHigh(NULL,PERIOD_CURRENT,selectedBar); } else { m_First_Point_Price=iLow(NULL,PERIOD_CURRENT,selectedBar); } m_First_Point_Time=iTime(NULL,PERIOD_CURRENT,selectedBar); //--- m_First_Point_Time=CUtilites::DeepPointSearch( m_First_Point_Time, _isUp, ENUM_TIMEFRAMES(Period()) ); //--- DrawFirstPointMarker(_isUp); } //--- Processing a click on the chart else { ObjectDelete(0,m_First_Point_Marker_Name); if(_isUp) { countedPrice=iHigh(NULL,PERIOD_CURRENT,selectedBar); } else { countedPrice=iLow(NULL,PERIOD_CURRENT,selectedBar); } countedTime=iTime(NULL,PERIOD_CURRENT,selectedBar); //--- Move a point in time on smaller timeframes countedTime=CUtilites::DeepPointSearch(countedTime,_isUp,ENUM_TIMEFRAMES(Period())); //--- The line is always drawn from left to right. //--- If it is not convenient, you can comment this part //--- up to the next comment if(countedTime<m_First_Point_Time) { trendTime1=countedTime; trendPrice1=countedPrice; trendTime2=m_First_Point_Time; trendPrice2=m_First_Point_Price; } else { trendTime2=countedTime; trendPrice2=countedPrice; trendTime1=m_First_Point_Time; trendPrice1=m_First_Point_Price; } //--- Set the description for future correction trendDescription+=TimeToString(trendTime2)+";"+DoubleToString(trendPrice2,Digits()); //selectedPrice=CUtilites::EquationDirect( // trendTime1,trendPrice1,trendTime2,trendPrice2,selectedTime // ); trendName=CUtilites::GetCurrentObjectName(allPrefixes[0],OBJ_TREND); TrendCreate( 0, // Chart ID trendName, // Line name 0, // Subwindow number trendTime1, // time of the first point trendPrice1, // price of the first point trendTime2, // time of the second point trendPrice2, // price of the second point CUtilites::GetTimeFrameColor( CUtilites::GetAllLowerTimeframes() ), // line color Trend_Line_Style, // line style Trend_Line_Width, // line width false, // background object true, // is the line selected true // ray to the right ); bar1=iBarShift(NULL,0,trendTime1); bar2=iBarShift(NULL,0,trendTime2); selectedTime = CUtilites::GetTimeInFuture( //iTime(NULL,PERIOD_CURRENT,0), trendTime1, (int)((bar1-bar2)*m_Free_Trend_Length_Coefficient), COUNT_IN_BARS ); selectedPrice= ObjectGetValueByTime(0,trendName,selectedTime); ObjectSetInteger(0,trendName,OBJPROP_RAY,IsRay()); ObjectSetInteger(0,trendName,OBJPROP_RAY_RIGHT,IsRay()); ObjectMove(0,trendName,1,selectedTime,selectedPrice); //--- m_Clicks_Count=0; ToggleFreeLineMode(); } ObjectSetString(0,trendName,OBJPROP_TEXT,trendDescription); ChartRedraw(); }
Como a função é bastante longa, provavelmente vou dividi-la em várias funções menores mais tarde. Espero que o destaque e os comentários ajudem a entender como funciona.
Nesta implementação, a função verifica ambos os sinais: o evento indicando que o desenho do primeiro ponto começou e a notificação de que o segundo ponto foi encontrado, e seu desenho começou. A variável m_Clicks_Count foi introduzida para distinguir estes eventos. A letra "m_" no início indica claramente que a variável é global para esta classe, e sua duração é igual à duração da instância objeto.
Se for a primeira chamada de função (ou seja, uma tecla foi pressionada), é necessário encontrar o primeiro ponto e desenhar o marcador.
Se for a segunda chamada, é necessário apagar o marcador, encontrar o segundo ponto e traçar uma linha. Estes foram os cinco blocos principais, enquanto todos os outros são necessários para sua implementação.
Na implementação atual, o preço futuro é determinado usando a própria linha reta. Geralmente, não é uma boa ideia, pois no momento de desenhar, o terminal tem que desenhar primeiro um raio, depois move o final da linha para o ponto desejado e decide se deseja desenhar o raio (dependendo das configurações externas). Normalmente, faço cálculos preliminares utilizando a famosa função de Igor Kim (Kim IV), que também está incluída na biblioteca. A parte rosa do código tem uma chamada comentada desta função. Entretanto, neste caso, se os pontos forem calculados por tempo, podemos ter um erro relacionado ao fim de semana, o que eu gostaria de evitar. Naturalmente, o erro poderia ser facilmente evitado calculando a linha por números de barras e depois recalculando os números em datas reais. Entretanto, a implementação atual me parece mais clara.
Assim, no código destacado em rosa, os extremos básicos já foram encontrados. O que precisamos fazer agora é traçar a linha. Primeiro, traçamos uma linha entre dois pontos extremos básicos - aqui devemos habilitar a propriedade "raio" para que a linha seja traçada no futuro (a função TrendCreate logo no início deste bloco).
Calculamos o tempo futuro necessário com base nas configurações:
selectedTime = CUtilites::GetTimeInFuture( //iTime(NULL,PERIOD_CURRENT,0), trendTime1, (int)((bar1-bar2)*m_Free_Trend_Length_Coefficient), COUNT_IN_BARS );
Em seguidad, obtemos o preço desejado usando a função padrão.
selectedPrice= ObjectGetValueByTime(0,trendName,selectedTime);
Depois disso, resta apenas mover o segundo ponto da linha para as coordenadas desejadas e definir a propriedade real do raio (lembre que esta propriedade é alternada por padrão com a tecla R - da palavra Ray).
Uma vez desenhada a linha, precisamos desabilitar o estado de espera do clique, que é o que as seguintes linhas fazem
m_Clicks_Count=0;
ToggleFreeLineMode();
Para o resto do código, em outros blocos desta função, a questão é um pouco mais complicada. Na verdade, adicionei algumas características relacionados ao trabalho conveniente com o uso de linhas retas.
A primeira característica está relacionada ao efeito de deslocamento das linhas nos tamframes inferiores. Se desenharmos linhas da maneira usual, quando alternarmos entre "janelas de tempo", será desenhado algo assim:
Pode-se ver que a borda esquerda da linha, que coincide exatamente com o extremo no período D1, é deslocada para a esquerda no período de 4 horas e não coincide com o extremo. Isto é um efeito bastante óbvio, porque o extremo do dia não precisa coincidir com seu início. Se quisermos mais precisão, ao desenhar à mão, podemos traçar uma linha aproximadamente, depois "descermos" para os timeframes inferiores e corrigir as extremidades.
Isso é bom para um, mas para dois gráficos... E se forem 20? Ou 100? Isso é irritante...
E, como a função de desenho automático já existe, por que não deixá-la fazer essa tarefa na hora de criar cada objeto?
Por causa disso foi criada a funçãoDeepPointSearch.
Função "associação profunda" DeepPointSearch
Na função de desenho de linha "livre", esta função é chamada duas vezes - uma para cada ponto. Ela está localizado no arquivo de utilitários e possui o seguinte código:
//+------------------------------------------------------------------+ //| Search for a given point on lower timeframes | //+------------------------------------------------------------------+ //| Parameters: | //| datetime _neededTime - start time on a higher timeframe | //| bool _isUp - search by highs or by lows | //| ENUM_TIMEFRAMES _higher_TF - the highest period | //+------------------------------------------------------------------+ //| Return value: | //| More accurate date (on the lowest possible timeframe) | //+------------------------------------------------------------------+ datetime CUtilites::DeepPointSearch( datetime _neededTime, bool _isUp, ENUM_TIMEFRAMES _higher_TF=PERIOD_CURRENT ) { //--- //--- As a result it gets the most accurate time available datetime deepTime=0; //--- current timeframe ENUM_TIMEFRAMES currentTF; //--- The number of the highest timeframe in the list of all available periods int highTFIndex = GetTimeFrameIndexByPeriod(_higher_TF); //--- The higher period in seconds int highTFSeconds = PeriodSeconds(_higher_TF); //--- Current interval in seconds int currentTFSeconds; //--- Counter int i; //--- Bar number on a higher timeframe int highBar=iBarShift(NULL,_higher_TF,_neededTime); //--- Bar number on the current timeframe int currentBar; //--- The total number of bars on the current timeframe int tfBarsCount; //--- How many bars of a lower TF fit into one bar of a higher TF int lowerBarsInHigherPeriod; //--- Maximum allowed number of bars in the terminal int terminalMaxBars = TerminalInfoInteger(TERMINAL_MAXBARS); //--- Loop sequentially through all timeframes for(i=0; i<highTFIndex; i++) { //--- Get a timeframe by a number in the list currentTF=GetTimeFrameByIndex(i); //--- Check if this timeframe has the required time. tfBarsCount=iBars(NULL,currentTF); if(tfBarsCount>terminalMaxBars-1) { tfBarsCount=terminalMaxBars-1; } deepTime=iTime(NULL,currentTF,tfBarsCount-1); //--- If it has, find it. if(deepTime>0 && deepTime<_neededTime) { currentTFSeconds=PeriodSeconds(currentTF); //--- Search for the required bar only within the higher TF candlestick lowerBarsInHigherPeriod=highTFSeconds/currentTFSeconds; currentBar = iBarShift(NULL,currentTF,_neededTime); if(_isUp) { currentBar = iHighest( NULL,currentTF,MODE_HIGH, lowerBarsInHigherPeriod+1, currentBar-lowerBarsInHigherPeriod+1 ); } else { currentBar = iLowest( NULL,currentTF,MODE_LOW, lowerBarsInHigherPeriod+1, currentBar-lowerBarsInHigherPeriod+1 ); } deepTime=iTime(NULL,currentTF,currentBar); //--- Once the required time is found, stop the search break; } } //--- If reached the end of the loop if(i==highTFIndex) { //--- then the required time is only available on the higher timeframe. deepTime=_neededTime; } //--- return (deepTime); }
Para mim, a principal dificuldade foi entender como deveria funcionar o fragmento de pesquisa principal. É claro que primeiro precisamos decidir se há um tempo no histórico de que precisamos. Afinal, é sabido que os timeframes inferiores muitas vezes não contêm o que está nos maiores. A função padrão iBars conta o número de barras no histórico. No entanto, isso não é suficiente, pois o terminal só pode exibir um número limitado. Primeiro, com o seguinte código, veremos a capacidade de exibição do terminal
//--- Maximum allowed number of bars in the terminal int terminalMaxBars = TerminalInfoInteger(TERMINAL_MAXBARS);
Se houver muitas barras no histórico, nos limitamos a apenas aquelas que são exibidas.
Em seguida, com a função iTime, definimos o tempo da última barra no histórico. Se este tempo for maior do que o desejado, não vale a pena procurar mais, já que a data mais alta disponível é a mais recente, portanto simplesmente mudamos para a próxima, o timeframe maior. Se o último candle disponível no terminal for anterior àquele que estamos procurando, é muito provável que tenhamos encontrado o lugar mais profundo onde este ponto ainda é significativo.
A rotina começa depois de todas as verificações. O ponto necessário será o mais extremo dentro do alcance do candle do timeframe maior. Só precisamos determinar quantos candles devem ser analisados. Depois disso, as funções padrão ajudam a determinar o extremo mais extremo que nos permite calcular o tempo e terminar o trabalho.
Na implementação atual da biblioteca, esta função só se aplica às linhas que são chamadas pela tecla T e Q. No entanto, na próxima versão, esta função estará disponível para todos os instrumentos. Além disso, pretendo torná-la personalizável para cada instrumento separadamente.
Correção de tempo
A segunda característica específica desta implementação é a correção das linhas por tempo. A animação abaixo explica o problema.
Preste atenção à contração do último retângulo. O final da linha, que estava a uma distância de mais de um dia do retângulo central, acabou se tornando muito próximo a ele. Consequentemente, os pontos acima também se deslocaram (observe o comportamento da linha reta próxima ao topo). Quando a linha contrai, surgem novas rupturas que podem afetar a estratégia de negociação.
Isto pode não ser tão crucial para o mercado forex, onde os picos podem ocorrer, digamos, uma vez por semana. Mas no mercado de ações, tais brechas de tempo podem ocorrer todos os dias, dependendo da bolsa, e isto muitas vezes acontece dentro de um dia.
É aqui que a automação vem a calhar!
Para que a peça funcione como desejado, devemos de alguma forma salvar as coordenadas "corretas" e depois ajustá-las conforme a necessidade.
Eu escolhi a descrição de uma linha reta para salvar coordenadas, já que a maioria dos traders não usa descrições ao criar objetos automáticos. Opcionalmente, podemos usar arquivos com uma lista de linhas ou variável global de terminal se houver muitas linhas.
/* Graphics.mqh */ void CGraphics::DrawFreeLine(//...) { //... string trendDescription="p2;"; //... trendDescription+=TimeToString(trendTime2)+";"+DoubleToString(trendPrice2,Digits()); //... ObjectSetString(0,trendName,OBJPROP_TEXT,trendDescription);
A seguir, aplicamos as ações descritas anteriormente às coordenadas na linha "física". Acho que o código abaixo é bastante claro.
/* Utilites.mqh */ //+------------------------------------------------------------------+ // | Adjusts the position of line end in the future in case of price | //| gaps | //+------------------------------------------------------------------+ //| Parameters: | //| string _line_name - the name of the line to be corrected | //+------------------------------------------------------------------+ void CUtilites::CorrectTrendFutureEnd(string _line_name) { //--- if(ObjectFind(0,_line_name)<0) { PrintDebugMessage(__FUNCTION__+" _line_name="+_line_name+": Object does not exist"); //--- If there is no object to search, there is nothing more to do. return; } //--- Get a description string line_text=ObjectGetString(0,_line_name,OBJPROP_TEXT); string point_components[]; // array for point description fragments string name_components[]; // array containing line name fragments string helpful_name="Helpful line"; // the name of the auxiliary line string vertical_name=""; // the name of the corresponding vertical from the crosshair //--- Get the point time and price in string form int point_components_count=StringSplit(line_text,StringGetCharacter(";",0),point_components); datetime time_of_base_point; // time of the basic point datetime time_first_point,time_second_point; // the time of the first and the second point datetime time_far_ideal; // estimated time in the future double price_of_base_point; // the price of the basic point double price_first_point,price_second_point; // the prices of the first and the second point int i; // counter //--- Check if the line is needed if(line_text=="" || point_components_count<3 || point_components[0]!="p2") { PrintDebugMessage(__FUNCTION__+" Error: the line cannot be used"); return; } //--- Get the coordinates of the "basic" point from the line description time_of_base_point=StringToTime(point_components[1]); price_of_base_point=StringToDouble(point_components[2]); if(time_of_base_point==0 || price_of_base_point==0) { PrintDebugMessage(__FUNCTION__+" Error: Unusable description"); return; } //--- Get the real coordinates of the line time_first_point = (datetime)ObjectGetInteger(0,_line_name,OBJPROP_TIME,0); time_second_point = (datetime)ObjectGetInteger(0,_line_name,OBJPROP_TIME,1); price_first_point = ObjectGetDouble(0,_line_name,OBJPROP_PRICE,0); price_second_point = ObjectGetDouble(0,_line_name,OBJPROP_PRICE,1); //--- Create an auxiliary line (from the starting point to the base one) MakeHelpfulLine( time_first_point, price_first_point, time_of_base_point, price_of_base_point ); //--- Calculate the correct time for the current situation time_far_ideal=ObjectGetTimeByValue(0,helpful_name,price_second_point); //--- if(time_second_point != time_far_ideal) { //--- move the free end of the trend line ObjectMove(0,_line_name,1,time_far_ideal,price_second_point); //--- and the corresponding vertical StringSplit(_line_name,StringGetCharacter("_",0),name_components); for(i=0; i<ObjectsTotal(0,-1,OBJ_VLINE); i++) { vertical_name = ObjectName(0,i,-1,OBJ_VLINE); if(name_components[0]==StringSubstr(vertical_name,0,StringFind(vertical_name,"_",0))) { if((datetime)ObjectGetInteger(0,vertical_name,OBJPROP_TIME,0)==time_second_point) { ObjectMove(0,vertical_name,0,time_far_ideal,price_second_point); break; } } } } // Delete the auxiliary line RemoveHelpfulLine(); }
Este código deve ser chamado em alguns intervalos. Eu o defino para o início de cada nova hora.
/* Shortcuts.mq5 */ int OnCalculate(/*...*/) { //... if(CUtilites::IsNewBar(First_Start_True,PERIOD_H1)) { for(i=0; i<all_lines_count; i++) { line_name=ObjectName(0,i,-1,OBJ_TREND); CUtilites::CorrectTrendFutureEnd(line_name); ChartRedraw(); } } //... }
Teclas usadas na implementação atual da biblioteca
Ação | Tecla | Do inglês |
---|---|---|
Move o timeframe para cima | U | Up |
Move o timeframe para baixo | D | Down |
Altera o nível Z (acima ou abaixo dos objetos) | Z | Z order |
Traça uma linha de tendência inclinada atravessando dois pontos extremos unidirecionais mais próximos do mouse | T | Trend line |
Muda para o modo de raio para novas retas | R key | Ray |
Traça uma linha vertical simples | I(i) | [Only visual vertical] |
Traça uma linha horizontal simples | H | Horizontal |
Traça um garfo de Andrews | P | Pitchfork |
Traça um leque de Fibonacci (VFun) | F key | Fun |
Traça um nível horizontal curto | S | Short |
Traça um nível horizontal alongado | L key | Long |
Traça uma linha vertical simples com marcas de níveis | V | Vertical |
Traça fios | X | [Only visual cross] |
Traça uma linha atravessando vértices arbitrários | Q | [No conformity... "L" and "T" is not free] |
Desenha um grupo de retângulos | B | Box |
Conclusão
Espero que você tenha achado este material útil. No entanto, ficarei feliz em receber críticas construtivas nos comentários.
Agora planejo tornar possível traçar linhas não apenas por vértices rígidos, mas também por tangentes.
Além disso, desejo prestar atenção aos canais. Por enquanto, estou pensando apenas em equidistantes, mas se alguém nos comentários ou em uma mensagem particular expressar o desejo de desenhar algo mais que esteja de acordo com os princípios da biblioteca, estou disposto a considerar essas sugestões.
A longo prazo, veremos como salvar as configurações num arquivo (em vez de, ou junto com, as configurações do indicador usando variáveis input) e adicionar uma interface gráfica para alterar as configurações na hora.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/9914
![Como se tornar um bom programador (Parte 6): 9 hábitos para desenvolver de maneira produtiva](https://c.mql5.com/2/43/coding_pro.png)
![Receitas MQL5: Calendário Econômico](https://c.mql5.com/2/43/mql5-recipes_calendar-4.png)
![Trabalhando com o tempo (Parte 2): funções](https://c.mql5.com/2/43/mql5-dealing-with-time__1.png)
![Quase-construtor para criar um Expert Advisor](https://c.mql5.com/2/43/carpenter-3572804_640.png)
![MQL5 - Linguagem para estratégias de negociação inseridas no terminal do cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
O arquivo "Utilites.mqh" apresenta 101 erros, sendo os seguintes erros:
'New_Bar_First_Start' - declaration without type Utilites.mqh 80 5
'mode' - comma expected Utilites.mqh 80 25
'New_Bar_First_Start' - declaration without type Utilites.mqh 1491 26
'mode' - comma expected Utilites.mqh 1491 46
'mode' - undeclared identifier Utilites.mqh 1511 18
'Is_Select_On_Create' - undeclared identifier Graphics.mqh 307 45
'Print_Warning_Messages' - undeclared identifier Graphics.mqh 249 8
'Print_Warning_Messages' - undeclared identifier Graphics.mqh 361 8
'CMouse' is not a class, struct or union Graphics.mqh 940 26
'Bar' - undeclared identifier Graphics.mqh 940 34
'Bar' - some operator expected Graphics.mqh 940 34
'(' - semicolon expected Graphics.mqh 940 37
')' - expression expected Graphics.mqh 940 38
'allPrefixes' - undeclared identifier Graphics.mqh 944 37
'[' - array required Graphics.mqh 944 48
'CMouse' is not a class, struct or union Graphics.mqh 959 6
'Below' - undeclared identifier Graphics.mqh 959 14
'Below' - some operator expected Graphics.mqh 959 14
'(' - unbalanced left parenthesis Graphics.mqh 959 5
')' - expression expected Graphics.mqh 959 20
')' - unexpected token Graphics.mqh 959 21
illegal 'else' without matching 'if' Graphics.mqh 968 3
variable 'tmp_price' not used Graphics.mqh 950 3
variable 'tmp_time' not used Graphics.mqh 954 3
'Simple_Vertical_Style' - undeclared identifier Graphics.mqh 420 26
'Simple_Vertical_Width' - undeclared identifier Graphics.mqh 422 26
'Simple_Horizontal_Style' - undeclared identifier Graphics.mqh 430 28
'Simple_Horizontal_Width' - undeclared identifier Graphics.mqh 432 28
'CMouse' is not a class, struct or union Graphics.mqh 434 14
'Window' - undeclared identifier Graphics.mqh 434 22
'Window' - some operator expected Graphics.mqh 434 22
')' - unexpected token Graphics.mqh 434 29
'Print_Warning_Messages' - undeclared identifier Graphics.mqh 439 10
'DEBUG_MESSAGE_PREFIX' - undeclared identifier Graphics.mqh 442 15
'allPrefixes' - undeclared identifier Graphics.mqh 451 9
'[' - array required Graphics.mqh 451 20
'Print_Warning_Messages' - undeclared identifier Graphics.mqh 462 10
'DEBUG_MESSAGE_PREFIX' - undeclared identifier Graphics.mqh 464 15
'CMouse' is not a class, struct or union Graphics.mqh 469 39
'Price' - undeclared identifier Graphics.mqh 469 47
'Price' - some operator expected Graphics.mqh 469 47
'(' - ':' colon sign expected Graphics.mqh 469 52
')' - expression expected Graphics.mqh 469 53
':' - unexpected token Graphics.mqh 469 55
'(' - expression expected Graphics.mqh 469 52
'=' - operand expected Graphics.mqh 469 24
expression has no effect Graphics.mqh 469 57
'CMouse' is not a class, struct or union Graphics.mqh 470 37
'Time' - undeclared identifier Graphics.mqh 470 45
'Time' - some operator expected Graphics.mqh 470 45
'(' - ':' colon sign expected Graphics.mqh 470 49
')' - expression expected Graphics.mqh 470 50
':' - unexpected token Graphics.mqh 470 52
'(' - expression expected Graphics.mqh 470 49
'=' - operand expected Graphics.mqh 470 23
expression has no effect Graphics.mqh 470 54
possible use of uninitialized variable 'Current_Object_Time' Graphics.mqh 478 5
possible use of uninitialized variable 'Current_Object_Price' Graphics.mqh 479 5
'Print_Warning_Messages' - undeclared identifier Graphics.mqh 516 8
'DEBUG_MESSAGE_PREFIX' - undeclared identifier Graphics.mqh 518 13
'DEBUG_MESSAGE_PREFIX' - undeclared identifier Graphics.mqh 573 11
'DEBUG_MESSAGE_PREFIX' - undeclared identifier Graphics.mqh 610 11
'allPrefixes' - undeclared identifier Graphics.mqh 672 37
'[' - array required Graphics.mqh 672 48
'allPrefixes' - undeclared identifier Graphics.mqh 674 39
'[' - array required Graphics.mqh 674 50
implicit conversion from 'unknown' to 'string' Graphics.mqh 674 39
'CMouse' is not a class, struct or union Graphics.mqh 676 19
'Bar' - undeclared identifier Graphics.mqh 676 27
'Bar' - some operator expected Graphics.mqh 676 27
'(' - semicolon expected Graphics.mqh 676 30
')' - expression expected Graphics.mqh 676 31
'CMouse' is not a class, struct or union Graphics.mqh 681 6
'Below' - undeclared identifier Graphics.mqh 681 14
'Below' - some operator expected Graphics.mqh 681 14
'(' - unbalanced left parenthesis Graphics.mqh 681 5
')' - expression expected Graphics.mqh 681 20
')' - unexpected token Graphics.mqh 681 21
illegal 'else' without matching 'if' Graphics.mqh 687 3
variable 'levels_descriptions' not used Graphics.mqh 666 10
variable 'time1' not used Graphics.mqh 675 12
variable 'time2' not used Graphics.mqh 675 19
'Pitchfork_Shiff_Color' - undeclared identifier Graphics.mqh 771 23
'Pitchfork_Shiff_Width' - undeclared identifier Graphics.mqh 772 23
'Pitchfork_Shiff_Style' - undeclared identifier Graphics.mqh 773 23
'Pitchfork_Reverce_Color' - undeclared identifier Graphics.mqh 779 25
'Pitchfork_Reverce_Width' - undeclared identifier Graphics.mqh 780 25
'Pitchfork_Reverce_Style' - undeclared identifier Graphics.mqh 781 25
'Pitchfork_Main_Color' - undeclared identifier Graphics.mqh 787 25
'Pitchfork_Main_Width' - undeclared identifier Graphics.mqh 788 25
'Pitchfork_Main_Style' - undeclared identifier Graphics.mqh 789 25
'CMouse' is not a class, struct or union Graphics.mqh 824 26
'Price' - undeclared identifier Graphics.mqh 824 34
'Price' - some operator expected Graphics.mqh 824 34
'(' - semicolon expected Graphics.mqh 824 39
')' - expression expected Graphics.mqh 824 40
'CMouse' is not a class, struct or union Graphics.mqh 825 21
'Bar' - undeclared identifier Graphics.mqh 825 29
'Bar' - some operator expected Graphics.mqh 825 29
'(' - semicolon expected Graphics.mqh 825 32
')' - expression expected Graphics.mqh 825 33
'CMouse' is not a class, struct or union Graphics.mqh 829 6
'Below' - undeclared identifier Graphics.mqh 829 14
'Below' - some operator expected Graphics.mqh 829 14
'(' - unbalanced left parenthesis Graphics.mqh 829 5
')' - expression expected Graphics.mqh 829 20
')' - unexpected token Graphics.mqh 829 21
illegal 'else' without matching 'if' Graphics.mqh 833 3
variable 'base' not used Graphics.mqh 827 19
'Short_Level_Length_In_Pixels' - undeclared identifier Graphics.mqh 1050 6
'Short_Level_Length_Pix' - undeclared identifier Graphics.mqh 1053 20
101 errors, 11 warnings 101 12