Desenvolvendo um EA multimoeda (Parte 26): Informador para instrumentos de negociação
Conteúdo
- Introdução
- Traçando o caminho
- Criação do projeto
- Repositório da parte da biblioteca
- Repositório do projeto
- Descrição do projeto
- Criando a primeira versão
- Testando o EA
- Considerações finais
Introdução
No artigo anterior, alcançamos finalmente o que já pode ser chamado de um sistema completo, capaz de organizar a geração automática, a partir de uma simples estratégia de negociação, de um EA completo que viabilize a execução paralela dessa estratégia em diferentes instrumentos e timeframes. Também foram abordadas questões relacionadas ao sistema de gerenciamento de capital e ao gerenciador de risco, que permite interromper a negociação diante de situações desfavoráveis ou, ao contrário, excessivamente favoráveis.
Durante quase todo o ciclo, trabalhamos com apenas uma estratégia de negociação simples, e somente nas últimas partes — quando as principais funcionalidades já haviam sido implementadas — é que analisamos o processo de adição de uma nova estratégia de negociação e sua utilização como principal. Esse exemplo demonstrou a possibilidade de tentar explorar o potencial de praticamente qualquer estratégia de negociação (se é que esse potencial realmente existe).
Mas ao atingir esse novo patamar, abre-se um campo ainda mais vasto para o trabalho futuro. As possibilidades são muitas, e escolher a próxima direção a seguir não é nada fácil. Para enfrentar isso, foi feita uma tentativa de mudar a abordagem de organização e armazenamento dos códigos-fonte deste projeto. Os primeiros passos foram dados já na Parte 23, onde separamos a maioria do código na chamada "parte da biblioteca", deixando o restante na "parte do projeto". Em seguida, voltamos nossa atenção para os recursos do novo repositório de código, apresentando os primeiros passos em um artigo separado Migrando para o MQL5 Algo Forge (Parte 1): Criando o repositório principal. Por ora, a estratégia de uso do novo repositório continua em fase de consolidação. De modo geral, a ideia é viabilizar o trabalho paralelo na parte da biblioteca em várias frentes ao mesmo tempo. Até que ponto isso será possível, veremos mais adiante.
Para isso, é preciso prática. Só assim é possível entender se as decisões arquiteturais tomadas foram de fato boas. Por isso, vamos praticar criando um novo projeto que utilize a biblioteca já desenvolvida, chamada Advisor. Não vamos começar logo com um grande projeto de desenvolvimento de EA baseado em alguma estratégia de negociação complexa. Na verdade, faremos o oposto. Vamos criar um projeto que nem mesmo tenha como objetivo o desenvolvimento de um EA de negociação.
Traçando o caminho
Um dos leitores levantou uma questão interessante relacionada à última estratégia adicionada, a SimpleCandles. Nessa estratégia, um dos parâmetros é a quantidade de velas consecutivas na mesma direção, dentro de um determinado timeframe. Por isso, seria útil termos uma forma de visualizar quais séries desse tipo de velas ocorrem em diferentes instrumentos e timeframes, para não deixarmos todo o trabalho de ajuste dos valores ideais apenas a cargo do otimizador.
De fato, mesmo para a otimização automática, ainda é necessário entender em que intervalo os parâmetros de entrada irão variar. É claro que se pode simplesmente definir um intervalo amplo, mas isso provavelmente reduzirá a eficiência da otimização. Afinal, o número total de combinações de parâmetros será maior, e a chance de encontrar uma combinação bem-sucedida será menor. É difícil dizer o quanto menor, mas só o entendimento básico dessa perda já é suficiente para nos motivar a buscar formas de aumentar a eficiência.
Além disso, reunir esse tipo de informação sobre o comportamento dos preços dos diferentes instrumentos ajudará a responder à seguinte pergunta: "É possível usar os mesmos intervalos de parâmetros para diferentes instrumentos?" Se a resposta for sim, isso facilita a organização da primeira etapa da otimização. Caso contrário, será necessário tornar um pouco mais complexo o processo de criação das tarefas de otimização nessa fase.
Resumindo, vamos tentar criar um EA auxiliar — ou uma parte de um EA — que nos permita exibir algum tipo de estatística por símbolo e timeframe. Pode ser que isso venha a ser útil futuramente, inclusive dentro das estratégias de negociação.
Mas essa é uma questão secundária. A primeira, com a qual vamos começar, é: como organizar o armazenamento do código-fonte de modo que ele possa ser reutilizado posteriormente em outros projetos.
Criação do projeto
À primeira vista, parece: o que poderia haver de complicado nisso? Já armazenamos o código de alguma forma no passado, por que não continuar do mesmo jeito? Mas há, sim, uma diferença. Uma coisa é manter todo o código do projeto em uma única pasta, e para cada novo projeto simplesmente criar uma nova pasta, copiando para ela o código do projeto anterior. Essa abordagem tem a vantagem da simplicidade e faz sentido em um processo de desenvolvimento estritamente sequencial, sem a necessidade de se preocupar com compatibilidade retroativa. Na fase inicial, quando as mudanças significativas ainda são frequentes, essa abordagem é muito mais conveniente. Mas é outra história quando o projeto começa a crescer, e já conseguimos identificar claramente partes distintas: algumas que não sofrerão alterações (ou serão modificadas raramente), e outras que poderão mudar significativamente ou até mesmo surgir do zero.
Nesse cenário, os pontos negativos de manter todo o código em uma única pasta, na nossa visão, começam a superar os positivos. Em um momento anterior, já havíamos movido a maior parte do código para a pasta MQL5/Include/antekov/Advisor, chamando esse conjunto de "parte da biblioteca". Mas agora, essa estrutura de localização para a parte da biblioteca não nos parece mais tão conveniente.
Vamos imaginar que dois projetos estão sendo desenvolvidos paralelamente, ambos utilizando a biblioteca Advisor. Em geral, as alterações afetam a parte do projeto, mas eventualmente alguns ajustes são feitos também na parte da biblioteca. Se os dois projetos fizerem referência à mesma instância da biblioteca, localizada no mesmo diretório (MQL5/Include/antekov/Advisor), é bem provável que surjam conflitos. Para evitá-los, ao alternar de um projeto para outro, seria necessário, no mínimo, trocar a versão da biblioteca para aquela correspondente, armazenada em outro branch do repositório. Embora isso não seja algo complicado, esse tipo de manipulação não é desejável. Pode-se acabar esquecendo de trocar o branch, e então ter que limpar alterações feitas no branch errado, transferindo-as para o correto.
Por isso, vamos tentar uma abordagem diferente. Cada projeto será um repositório separado. Dentro da pasta do projeto, haverá obrigatoriamente uma pasta Include, onde ficarão as subpastas com as partes da biblioteca. Atenção: não apenas uma parte da biblioteca, mas várias, organizadas em pastas distintas. Cada parte da biblioteca será um clone de um repositório de código separado.
Repositório da parte da biblioteca
Para a parte da biblioteca, criaremos um novo repositório no MQL5 Algo Forge ou em qualquer outro repositório GIT público. O nome Advisor, que usamos até agora para a biblioteca, nos pareceu genérico demais. Vamos torná-lo mais único, renomeando-a para Adwizard. É assim que passaremos a nos referir à nossa biblioteca daqui em diante.
Neste repositório, colocaremos todos os arquivos da parte da biblioteca. Ao criar o repositório, ele conterá apenas um branch com o nome main. Criamos um novo branch chamado develop, a partir do qual serão criadas branches para os artigos e para os novos recursos da biblioteca. Esses branches auxiliares serão encerrados após a implementação dos novos recursos, e as modificações serão incorporadas ao branch develop e, em seguida, ao main. Em geral, isso ocorrerá após a finalização do trabalho de mais um artigo.
Para garantir o funcionamento do código nesse repositório, ao cloná-lo para qualquer pasta, foi necessário fazer pequenos ajustes em alguns arquivos da biblioteca. As correções foram necessárias nos pontos em que, nas diretivas de inclusão #include, usávamos caminhos que levavam à pasta Include da biblioteca padrão. Após substituí-los por caminhos relativos, eliminamos a dependência de um local fixo da biblioteca dentro de MQL5/Include/antekov/Advisor.
Por exemplo, no arquivo Optimization.mqh, foi feita uma substituição como esta:
#include <antekov/Advisor/Optimization/Optimizer.mqh> #include "../Optimization/Optimizer.mqh"
No arquivo OptimizerTask.mqh, ainda utilizávamos um único arquivo de uma biblioteca externa do fxsaber. Esse arquivo também foi transferido para dentro da biblioteca, na pasta Utils:
#include <antekov/Advisor/Database/Database.mqh> #include <fxsaber/MultiTester/MTTester.mqh> // https://www.mql5.com/ru/code/26132 #include "../Database/Database.mqh" #include "../Utils/MTTester.mqh" // https://www.mql5.com/ru/code/26132
Essas alterações foram enviadas para o repositório da biblioteca.
Repositório do projeto
Para o projeto, criamos um novo repositório chamado SymbolsInformer. Nele, além da branch principal main, também criamos uma branch de desenvolvimento chamada develop. Caso esse projeto venha a ser abordado em vários artigos, o ideal é que as modificações relacionadas a artigos distintos sejam separadas em branches diferentes. Essas branches derivarão de develop e serão posteriormente mescladas de volta em develop e main , conforme forem finalizadas.
Criamos uma pasta para armazenar o diretório do projeto, por exemplo: MQL5/Experts/Article/17606. Clonamos o repositório para essa pasta escolhida e, dentro dela, criamos a pasta Include. Nessa pasta, vamos colocar os repositórios das outras bibliotecas das quais este projeto dependerá. Por enquanto, será apenas uma: a biblioteca Adwizard. Clonamos o repositório da biblioteca Adwizard dentro da pasta Include . Caso fosse necessária alguma outra biblioteca, ela também seria clonada nesta mesma pasta Include.
Após essas operações, teremos uma estrutura de pastas no diretório do terminal mais ou menos assim:

Na pasta clonada do repositório Adwizard, vamos alternar para o branch develop. Esse será o branch comum a todos os artigos. Se, durante o desenvolvimento deste projeto, não houver necessidade de alterar a biblioteca Adwizard, permaneceremos no branch develop, atualizando-o apenas quando surgirem novas alterações feitas em outros artigos. Se, por outro lado, for preciso fazer alguma modificação na biblioteca relacionada ao trabalho atual, então criaremos um novo branch.
Em seguida, no repositório do projeto, criamos um branch específico para o desenvolvimento do artigo atual e começamos a trabalhar nele. Aqui fornecemos uma descrição resumida do processo de criação de um novo projeto; uma explicação mais detalhada será abordada em um artigo separado.
Descrição do projeto
Vamos tentar formular uma especificação técnica resumida para o desenvolvimento da ferramenta desejada. Ela será implementada na forma de um EA, já que não haverá parâmetros de cálculo que exijam recálculo periódico e exibição de valores variáveis ao longo do tempo.
Antes de tudo, para contar a quantidade de séries de velas consecutivas na mesma direção, precisamos definir um certo intervalo de tempo no qual coletaremos essa estatística. Isso pode ser feito de várias formas. Por exemplo, é possível definir uma quantidade de dias a partir da data atual, ou estabelecer duas datas distintas, indicando início e fim. Inicialmente, faremos apenas o cálculo da estatística para um intervalo que começa a partir da data atual. A duração será determinada pela escolha de um timeframe (por exemplo, diário) e uma quantidade de velas desse timeframe. Vamos chamá-lo de timeframe principal.
Depois, será necessário indicar para quais instrumentos de negociação (símbolos) e quais timeframes desejamos realizar os cálculos. Claro, poderíamos limitar os cálculos apenas ao símbolo e timeframe no qual o EA estiver sendo executado. Mas é melhor desde já prever a possibilidade de realizar os cálculos para vários símbolos e vários timeframes.
Com base nisso, elaboramos a lista de parâmetros de entrada do EA:
- Timeframe principal
- Quantidade de velas do timeframe principal
- Lista de símbolos
- Lista de timeframes
Esse conjunto de parâmetros poderá ser ampliado no futuro. As listas de símbolos e timeframes conterão seus nomes separados por vírgulas. Os nomes dos timeframes serão definidos conforme aparecem no terminal, por exemplo: M5, M15, H1 e assim por diante.
Para cada símbolo e timeframe, vamos calcular os seguintes valores:
- Tamanhos médios das velas:
- bullish ("vela para cima" ou "buy", cuja cotação de fechamento não é inferior à de abertura);
- bearish ("vela para baixo" ou "sell", cuja cotação de fechamento não é superior à de abertura);
- todas (tanto bullish quanto bearish);
- Comprimento médio da série (série será considerada uma sequência de duas ou mais velas consecutivas com a mesma direção);
- Quantidade de séries com comprimento
- 2
- 3
- ...
- 8
- 9
Essa lista também é aberta, ou seja, podemos adicionar novos valores calculados conforme desejarmos.
Criando a primeira versão
Para começar, vamos desenvolver a versão mais simples possível. Os valores calculados serão armazenados em arrays globais, e os resultados serão exibidos de alguma forma no log e no gráfico, como comentário. Ainda não sabemos exatamente quais dados nos serão úteis e quais serão irrelevantes. Portanto, essa primeira versão servirá principalmente para determinar o que realmente precisamos. Nela, não daremos muita atenção à verificação da consistência dos parâmetros de entrada nem à organização do armazenamento das informações.
Os parâmetros de entrada, conforme a lista elaborada, podem ser definidos da seguinte forma:
//+------------------------------------------------------------------+ //| Входные параметры | //+------------------------------------------------------------------+ input group "::: Период для расчётов" sinput ENUM_TIMEFRAMES mainTimeframe_ = PERIOD_D1; // Основной таймфрейм input int mainLength_ = 30; // Количество свечей основного таймфрейма input group "::: Символы и таймфреймы" sinput string symbols_ = ""; // Символы (через запятую) sinput string timeframes_ = ""; // Таймфреймы (напр. M5,H1,H4)
Como nos parâmetros de entrada os símbolos e timeframes são especificados múltiplos por linha, vamos precisar de arrays que armazenem individualmente cada nome de símbolo e de timeframe.
Para armazenar os valores calculados, criaremos arrays bidimensionais. O primeiro índice estará associado ao símbolo, e o segundo, ao timeframe. Como ao declarar arrays bidimensionais é obrigatório especificar a quantidade de elementos no segundo índice, vamos definir uma constante chamada TFN, na qual indicaremos a quantidade total de timeframes padrão existentes no momento. Foram identificados 21.
// Количество существующих таймфреймов #define TFN (21) // Глобальные переменные string g_symbols[]; // Массив всех используемых символов ENUM_TIMEFRAMES g_timeframes[]; // Массив всех используемых таймфреймов // Массивы расчётных значений. // Первый индекс - символ, второй индекс - таймфрейм double symbolAvrCandleSizes[][TFN]; // Массив средних размеров всех свечей double symbolAvrBuyCandleSizes[][TFN]; // Массив средних размеров свечей вверх double symbolAvrSellCandleSizes[][TFN]; // Массив средних размеров свечей вниз double symbolAvrSeriesLength[][TFN]; // Массив средних длин серий int symbolCountSeries2[][TFN]; // Массив количества серий длины 2 int symbolCountSeries3[][TFN]; // Массив количества серий длины 3 int symbolCountSeries4[][TFN]; // Массив количества серий длины 4 int symbolCountSeries5[][TFN]; // Массив количества серий длины 5 int symbolCountSeries6[][TFN]; // Массив количества серий длины 6 int symbolCountSeries7[][TFN]; // Массив количества серий длины 7 int symbolCountSeries8[][TFN]; // Массив количества серий длины 8 int symbolCountSeries9[][TFN]; // Массив количества серий длины 9
Para fazer conversões entre as constantes simbólicas dos timeframes (do tipo ENUM_TIMEFRAMES), seus nomes em string e os índices nos arrays de timeframes, criaremos funções auxiliares. Com elas, poderemos resolver três tarefas:
- obter a constante simbólica a partir do nome em string (StringToTimeframe)
- obter o nome do timeframe (sem o prefixo "PERIOD_") a partir da constante simbólica (TimeframeToString)
- obter o índice no array de timeframes a partir da constante simbólica (TimeframeToIndex)
// Массив всех таймфреймов ENUM_TIMEFRAMES tfValues[] = { PERIOD_M1, PERIOD_M2, PERIOD_M3, PERIOD_M4, PERIOD_M5, PERIOD_M6, PERIOD_M10, PERIOD_M12, PERIOD_M15, PERIOD_M20, PERIOD_M30, PERIOD_H1, PERIOD_H2, PERIOD_H3, PERIOD_H4, PERIOD_H6, PERIOD_H8, PERIOD_H12, PERIOD_D1, PERIOD_W1, PERIOD_MN1 }; //+------------------------------------------------------------------+ //| Преобразование строкового названия в таймфрейм | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES StringToTimeframe(string s) { // Если в строке есть символ "_", то оставляем только символы, идущие после него int pos = StringFind(s, "_"); if(pos != -1) { s = StringSubstr(s, pos + 1); } // Переводим в верхний регистр StringToUpper(s); // Массивы соответствующих строковых названий таймфреймов string keys[] = {"M1", "M2", "M3", "M4", "M5", "M6", "M10", "M12", "M15", "M20", "M30", "H1", "H2", "H3", "H4", "H6", "H8", "H12", "D1", "W1", "MN1" }; // Ищем соответствие и возвращаем, если нашли FOREACH(keys) { if(keys[i] == s) return tfValues[i]; } return PERIOD_CURRENT; } //+------------------------------------------------------------------+ //| Преобразование таймфрейма в строковое название | //+------------------------------------------------------------------+ string TimeframeToString(ENUM_TIMEFRAMES tf) { // Получаем название таймфрейма вида 'PERIOD_*' string s = EnumToString(tf); // Возвращаем часть названия после символа '_' return StringSubstr(s, StringFind(s, "_") + 1); } //+------------------------------------------------------------------+ //| Преобразование таймфрейма в индекс в массиве всех таймфреймов | //+------------------------------------------------------------------+ int TimeframeToIndex(ENUM_TIMEFRAMES tf) { // Ищем соответствие и возвращаем, если нашли FOREACH(tfValues) { if(tfValues[i] == tf) return i; } return WRONG_VALUE; }
O cálculo de todos os valores será feito dentro da função Calculate(). Nela, vamos organizar um laço duplo que percorre todas as combinações possíveis de símbolos e timeframes. Em cada iteração, verificaremos se um novo candle se formou especificamente para aquele símbolo e timeframe. Se sim, chamaremos as funções auxiliares de cálculo. Também será possível passar, por meio do parâmetro force , uma instrução para forçar o cálculo imediato de todos os valores, sem aguardar a formação de um novo candle. Esse modo será utilizado na inicialização do EA, para que possamos ver os resultados imediatamente.
//+------------------------------------------------------------------+ //| Расчёт всех величин | //+------------------------------------------------------------------+ void Calculate(bool force = false) { string symbol; ENUM_TIMEFRAMES tf; // Для каждого символа и таймфрейма FOREACH_AS(g_symbols, symbol) { FOREACH_AS(g_timeframes, tf) { // Если для данного символа и таймфрейма наступил новый бар, то if(IsNewBar(symbol, tf) || force) { // Находим количество свечей для расчёта int n = PeriodSeconds(mainTimeframe_) * mainLength_ / PeriodSeconds(tf); // Рассчитываем средние размеры свечей CalculateAvrSizes(symbol, tf, n); // Рассчитываем длины серий свечей CalculateSeries(symbol, tf, n); } } } }
Os cálculos propriamente ditos foram organizados em duas funções auxiliares. Cada uma realiza o cálculo para apenas um símbolo e um timeframe. Além desses dois parâmetros, também passamos um terceiro: a quantidade de velas sobre a qual será feito o cálculo.
O cálculo dos tamanhos médios das velas é feito na função CalculateAvrSizes(). No início, usamos o nome do símbolo e o timeframe para determinar os índices s e t do elemento correspondente nos arrays bidimensionais onde os resultados serão armazenados. Velas cuja cotação de abertura seja igual à de fechamento serão consideradas tanto como velas para cima quanto para baixo. Os valores médios calculados serão arredondados para o número inteiro mais próximo em pontos.
//+------------------------------------------------------------------+ //| Расчёт средних размеров свечей | //+------------------------------------------------------------------+ void CalculateAvrSizes(string symbol, ENUM_TIMEFRAMES tf, int n) { // Находим индекс, который используется для нужного символа int s; FIND(g_symbols, symbol, s); // Находим индекс, который используется для нужного таймфрейма int t = TimeframeToIndex(tf); // Массив для свечей MqlRates rates[]; // Копируем нужное количество свечей в массив int res = CopyRates(symbol, tf, 1, n, rates); // Если всё скопировалось, то if(res == n) { // Количество свечей вверх и вниз int nBuy = 0, nSell = 0; // Обнуляем элементы для расчётных средних значений symbolAvrCandleSizes[s][t] = 0; symbolAvrBuyCandleSizes[s][t] = 0; symbolAvrSellCandleSizes[s][t] = 0; // Для всех свечей FOREACH(rates) { // Находим размер свечи double size = rates[i].high - rates[i].low; // Добавляем его в суммарный размер всех свечей symbolAvrCandleSizes[s][t] += size; // Если это свеча вверх, то учитываем её if(IsBuyRate(rates[i])) { symbolAvrBuyCandleSizes[s][t] += size; nBuy++; } // Если это свеча вниз, то учитываем её if(IsSellRate(rates[i])) { symbolAvrSellCandleSizes[s][t] += size; nSell++; } } // Получаем размер одного пункта для символа double point = SymbolInfoDouble(symbol, SYMBOL_POINT); // Находим средние значения в пунктах symbolAvrCandleSizes[s][t] /= n * point; symbolAvrBuyCandleSizes[s][t] /= nBuy * point; symbolAvrSellCandleSizes[s][t] /= nSell * point; // Округляем их до целых пунктов symbolAvrCandleSizes[s][t] = MathRound(symbolAvrCandleSizes[s][t]); symbolAvrBuyCandleSizes[s][t] = MathRound(symbolAvrBuyCandleSizes[s][t]); symbolAvrSellCandleSizes[s][t] = MathRound(symbolAvrSellCandleSizes[s][t]); } }
O cálculo dos comprimentos das séries é feito de forma semelhante na função CalculateSeries(). Nela, é utilizado um array auxiliar chamado seriesLens, com 100 elementos. O índice de cada elemento desse array corresponde ao comprimento da série, e o valor do elemento indica a quantidade de séries com esse comprimento. Assim, estamos assumindo que a imensa maioria das séries terá menos de cem velas. Na prática, exibiremos apenas a quantidade de séries com comprimento inferior a 10 velas. No final da função, os valores correspondentes a essas séries são transferidos do array seriesLens para os elementos apropriados dos arrays de resultados, com nomes do tipo symbolCountSeries*.
//+------------------------------------------------------------------+ //| Расчёт длин серий свечей | //+------------------------------------------------------------------+ void CalculateSeries(string symbol, ENUM_TIMEFRAMES tf, int n) { // Находим индекс, который используется для нужного символа int s; FIND(g_symbols, symbol, s); // Находим индекс, который используется для нужного таймфрейма int t = TimeframeToIndex(tf); // Массив для свечей MqlRates rates[]; // Копируем нужное количество свечей в массив int res = CopyRates(symbol, tf, 1, n, rates); // Если всё скопировалось, то if(res == n) { // Текущая длина серии int curLen = 0; // Направление предыдущей свечи bool prevIsBuy = false; bool prevIsSell = false; // Массив количеств серий разной длины (индекс = длина серии) int seriesLens[]; // Устанавливаем размер и инициализируем ArrayResize(seriesLens, 100); ArrayInitialize(seriesLens, 0); // Для всех свечей FOREACH(rates) { // Определяем направление свечи bool isBuy = IsBuyRate(rates[i]); bool isSell = IsSellRate(rates[i]); // Если направление совпадает с предыдущим, то if((isBuy && prevIsBuy) || (isSell && prevIsSell)) { // Увеличиваем длину серии curLen++; } else { // Иначе если длина попадает в требуемый диапазон, то if(curLen > 1 && curLen < 100) { // Увеличивем счётчик серий этой длины seriesLens[curLen]++; } // Сбрасываем текущую длину серии curLen = 1; } // Запоминаем направление текущей свечи как предыдущее prevIsBuy = isBuy; prevIsSell = isSell; } // Инициализируем элемент массива для средней длины серии symbolAvrSeriesLength[s][t] = 0; int count = 0; // Для всех длин серий находим сумму и количество FOREACH(seriesLens) { symbolAvrSeriesLength[s][t] += seriesLens[i] * i; count += seriesLens[i]; } // Вычисляем среднюю длину серий свечей symbolAvrSeriesLength[s][t] /= (count > 0 ? count : 1); // Копируем значения длин серий в итоговые массивы symbolCountSeries2[s][t] = seriesLens[2]; symbolCountSeries3[s][t] = seriesLens[3]; symbolCountSeries4[s][t] = seriesLens[4]; symbolCountSeries5[s][t] = seriesLens[5]; symbolCountSeries6[s][t] = seriesLens[6]; symbolCountSeries7[s][t] = seriesLens[7]; symbolCountSeries8[s][t] = seriesLens[8]; symbolCountSeries9[s][t] = seriesLens[9]; } }
A exibição dos resultados fica a cargo da função Show(). Nesta primeira versão, vamos apenas apresentar as informações no log do terminal e como comentário no gráfico onde o EA estiver em execução. Sendo assim, basta representar os resultados em forma de texto. Essa formatação textual será feita por uma função separada chamada TextComment().
//+------------------------------------------------------------------+ //| Показ результатов | //+------------------------------------------------------------------+ void Show() { // Получаем результаты в виде текста string text = TextComment(); // Показываем его в комментарии и в логе Comment(text); Print(text); }
Na função de inicialização do EA, resta processar os parâmetros de entrada, dividindo os nomes de símbolos e timeframes listados em valores individuais, e preparar os arrays de resultados com os tamanhos adequados. Depois disso, já podemos chamar a função de cálculo e exibição dos resultados:
//+------------------------------------------------------------------+ //| Инициализация советника | //+------------------------------------------------------------------+ int OnInit(void) { // Наполняем массив символов для расчётов из входных параметров SPLIT(symbols_, g_symbols); // Если символы не указаны, используем один текущий символ if(ArraySize(g_symbols) == 0) { APPEND(g_symbols, Symbol()); } // Количество символов для расчётов int nSymbols = ArraySize(g_symbols); // Инициализируем массивы для расчётных значений Initialize(nSymbols); // Наполняем массив названий таймфреймов из входных параметров string strTimeframes[]; SPLIT(timeframes_, strTimeframes); ArrayResize(g_timeframes, 0); // Если таймфреймы не указаны, используем один текущий таймфрейм if(ArraySize(strTimeframes) == 0) { APPEND(strTimeframes, TimeframeToString(Period())); } // Наполняем массив таймфреймов из массива названий таймфреймов FOREACH(strTimeframes) { ENUM_TIMEFRAMES tf = StringToTimeframe(strTimeframes[i]); if(tf != PERIOD_CURRENT) { APPEND(g_timeframes, tf); } } // Выполняем принудительный перерасчёт Calculate(true); // Показываем результаты Show(); return(INIT_SUCCEEDED); }
Na função acima, utilizamos um novo macro chamado SPLIT. Ele foi adicionado ao arquivo Utils/Macros.mqh da biblioteca Adwizard. Esse foi, até o momento, o único acréscimo à biblioteca necessário para este projeto.
O macro em si tem como propósito dividir uma string em partes, considerando dois possíveis separadores: vírgula e ponto e vírgula.
#define SPLIT(V, A) { string s=V; StringReplace(s, ";", ","); StringSplit(s, ',', A); }
Vamos agora observar os resultados da execução do EA desenvolvido.
Testando o EA
Executamos o EA com os parâmetros padrão em algum gráfico. Como resultado, obtemos algo semelhante a isto:

Fig. 1. Resultados do Expert Advisor com parâmetros padrão no AUDCAD H1
Como o comentário exibido no gráfico utiliza uma fonte proporcional (não monoespaçada), analisar os valores diretamente no gráfico não é muito prático. Já no log do terminal, onde a fonte é monoespaçada, a visualização é mais clara. Vamos ver como os resultados se apresentam ao utilizarmos múltiplos símbolos e timeframes.
Executamos o EA com os seguintes parâmetros de entrada:

E conferimos o resultado.

Fig. 2. Resultados do EA para vários símbolos e timeframes
Os cálculos para os diferentes símbolos e timeframes foram executados com sucesso. Os resultados foram exibidos em forma de tabela. É verdade que ainda não é muito confortável trabalhar com esses dados, mas, para uma análise preliminar rápida, esse formato de exibição já cumpre bem o seu papel.
Considerações finais
E assim, finalizamos a primeira versão do EA auxiliar-informador, que exibe informações sobre os tamanhos médios das velas em pontos e os comprimentos das séries de velas consecutivas na mesma direção. À primeira vista, isso pode parecer ter uma relação apenas indireta com o nosso projeto principal de criação de um sistema de otimização automática e execução de EAs multimoeda, baseados em diversas estratégias simples. E de fato é isso mesmo. Por isso, o desenvolvimento deste EA será continuado fora do escopo deste ciclo de artigos. No entanto, ao longo desse processo, vamos testar e validar a implementação de muitos recursos que, esperamos, possam ser aplicados com sucesso também no projeto principal.
Já avançamos bastante até aqui. A escolha de uma estrutura mais adequada para organização do código permitirá distribuir paralelamente o trabalho entre diferentes frentes de desenvolvimento da biblioteca Adwizard. E essas frentes já começam a se delinear em várias direções. Uma delas é a criação de uma interface visual para controle da execução dos EAs finais. O projeto apresentado neste artigo nos ajudará a explorar diferentes abordagens possíveis, sem que precisemos nos aprofundar logo de início em implementações complexas. Depois de avaliarmos os prós e contras de cada abordagem e escolhermos a mais adequada, poderemos focar o desenvolvimento do projeto principal com mais objetividade.
Obrigado pela atenção e até a próxima!
Aviso importante
Todos os resultados apresentados neste artigo e nos anteriores deste ciclo baseiam-se exclusivamente em testes realizados com dados históricos e não constituem garantia de qualquer tipo de rentabilidade futura. O trabalho desenvolvido neste projeto tem caráter de pesquisa. Todos os resultados publicados podem ser utilizados livremente, por conta e risco dos próprios usuários.
Conteúdo do arquivo compactado
| # | Nome | Versão | Descrição | Últimas alterações |
|---|---|---|---|---|
| SymbolsInformer | Pasta de trabalho do projeto | |||
| 1 | SymbolsInformer.mq5 | 1.00 | EA para exibir informações sobre os comprimentos de séries de velas consecutivas na mesma direção | Parte 26 |
| SymbolsInformer/Include/Adwizard/Base | Classes base das quais outras classes do projeto herdam | |||
| 2 | Advisor.mqh | 1.04 | Classe base do EA | Parte 10 |
| 3 | Factorable.mqh | 1.05 | Classe base de objetos criados a partir de uma string | Parte 24 |
| 4 | FactorableCreator.mqh | 1.00 | Classe de criadores que associam nomes a construtores estáticos de classes derivadas de CFactorable | Parte 24 |
| 5 | Interface.mqh | 1.01 | Classe base para visualização de diversos objetos | Parte 4 |
| 6 | Receiver.mqh | 1.04 | Classe base para conversão de volumes abertos em posições de mercado | Parte 12 |
| 7 | Strategy.mqh | 1.04 | Classe base para estratégia de negociação | Parte 10 |
| SymbolsInformer/Include/Adwizard/Database | Arquivos para trabalhar com todos os tipos de bancos de dados utilizados pelos EAs do projeto | |||
| 8 | Database.mqh | 1.12 | Classe para trabalhar com banco de dados | Parte 25 |
| 9 | db.adv.schema.sql | 1.00 | Esquema do banco de dados do EA final | Parte 22 |
| 10 | db.cut.schema.sql | 1.00 | Esquema do banco de dados reduzido de otimização | Parte 22 |
| 11 | db.opt.schema.sql | 1.05 | Esquema do banco de dados de otimização | Parte 22 |
| 12 | Storage.mqh | 1.01 | Classe para trabalhar com armazenamento do tipo Key-Value para o EA final no banco de dados do expert | Parte 23 |
| SymbolsInformer/Include/Adwizard/Experts | Arquivos com partes comuns dos EAs utilizados, de diferentes tipos | |||
| 13 | Expert.mqh | 1.22 | Arquivo de biblioteca para o EA final. Os parâmetros de grupos podem ser obtidos do banco de dados do expert | Parte 23 |
| 14 | Optimization.mqh | 1.04 | Arquivo de biblioteca para o EA que gerencia a execução de tarefas de otimização | Parte 23 |
| 15 | Stage1.mqh | 1.19 | Arquivo de biblioteca para o EA de otimização de uma instância individual de estratégia de negociação (Etapa 1) | Parte 23 |
| 16 | Stage2.mqh | 1.04 | Arquivo de biblioteca para o EA de otimização de um grupo de instâncias de estratégias de negociação (Etapa 2) | Parte 23 |
| 17 | Stage3.mqh | 1.04 | Arquivo de biblioteca para o EA que grava o grupo de estratégias normalizadas formado no banco de dados do Expert com o nome especificado. | Parte 23 |
| SymbolsInformer/Include/Adwizard/Optimization | Classes responsáveis pelo funcionamento da otimização automática | |||
| 18 | OptimizationJob.mqh | 1.00 | Classe para a etapa de execução do projeto de otimização | Parte 25 |
| 19 | OptimizationProject.mqh | 1.00 | Classe para o projeto de otimização | Parte 25 |
| 20 | OptimizationStage.mqh | 1.00 | Classe para a etapa do projeto de otimização | Parte 25 |
| 21 | OptimizationTask.mqh | 1.00 | Classe para a tarefa de otimização (para criação) | Parte 25 |
| 22 | Optimizer.mqh | 1.03 | Classe para o gerenciador da otimização automática de projetos | Parte 22 |
| 23 | OptimizerTask.mqh | 1.03 | Classe para a tarefa de otimização (para o pipeline) | Parte 22 |
| SymbolsInformer/Include/Adwizard/Strategies | Exemplos de estratégias de negociação usadas para demonstrar o funcionamento do projeto | |||
| 24 | HistoryStrategy.mqh | 1.00 | Classe da estratégia de negociação para reprodução do histórico de operações | Parte 16 |
| 25 | SimpleVolumesStrategy.mqh | 1.11 | Classe da estratégia de negociação com uso de volumes de ticks | Parte 22 |
| SymbolsInformer/Include/Adwizard/Utils | Utilitários auxiliares e macros para reduzir o código | |||
| 26 | ExpertHistory.mqh | 1.00 | Classe para exportar o histórico de ordens para arquivo | Parte 16 |
| 27 | Macros.mqh | 1.07 | Macros úteis para operações com arrays | Parte 26 |
| 28 | NewBarEvent.mqh | 1.00 | Classe para detectar novo candle para um símbolo específico | Parte 8 |
| 29 | SymbolsMonitor.mqh | 1.00 | Classe para obtenção de informações sobre instrumentos de negociação (símbolos) | Parte 21 |
| SymbolsInformer/Include/Adwizard/Virtual | Classes para criação de diferentes objetos reunidos pelo uso do sistema de ordens e posições virtuais de negociação | |||
| 30 | Money.mqh | 1.01 | Classe base para gerenciamento de capital | Parte 12 |
| 31 | TesterHandler.mqh | 1.07 | Classe para tratamento de eventos de otimização | Parte 23 |
| 32 | VirtualAdvisor.mqh | 1.10 | Classe do expert que trabalha com posições (ordens) virtuais | Parte 24 |
| 33 | VirtualChartOrder.mqh | 1.01 | Classe da posição virtual gráfica | Parte 18 |
| 34 | VirtualHistoryAdvisor.mqh | 1.00 | Classe do expert para reprodução do histórico de ordens | Parte 16 |
| 35 | VirtualInterface.mqh | 1.00 | Classe da interface gráfica do EA | Parte 4 |
| 36 | VirtualOrder.mqh | 1.09 | Classe de ordens e posições virtuais | Parte 22 |
| 37 | VirtualReceiver.mqh | 1.04 | Classe que converte volumes abertos em posições de mercado (receptor) | Parte 23 |
| 38 | VirtualRiskManager.mqh | 1.05 | Classe de gerenciamento de risco (risk manager) | Parte 24 |
| 39 | VirtualStrategy.mqh | 1.09 | Classe da estratégia de negociação com posições virtuais | Parte 23 |
| 40 | VirtualStrategyGroup.mqh | 1.03 | Classe de grupo de estratégias de negociação ou grupos de estratégias | Parte 24 |
| 41 | VirtualSymbolReceiver.mqh | 1.00 | Classe do receptor simbólico | Parte 3 |
O código-fonte também está disponível nos repositórios públicos SymbolsInformer e Adwizard
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/17606
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Redes neurais em trading: Otimização de LSTM para fins de previsão de séries temporais multivariadas (DA-CG-LSTM)
Simulação de mercado: A união faz a força (II)
Algoritmo de otimização caótica — Chaos optimization algorithm (COA)
Do básico ao intermediário: Sobrecarga de operadores (III)
- 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
É uma pena(
Vou dar uma olhada em sua análise informativa amanhã
@Rashid Umarov
Hi
Você está acompanhando este tópico de perto e é capaz de implementar tudo e otimizar?
Pode me ajudar a fazer isso?
Obrigado