
Разрабатываем мультивалютный советник (Часть 26): Информер для торговых инструментов
Содержание
- Введение
- Намечаем путь
- Создание проекта
- Репозиторий библиотечной части
- Репозиторий проекта
- Описание проекта
- Делаем первую версию
- Тестирование советника
- Заключение
Введение
В прошлой статье мы наконец-то подошли к тому, что уже можно назвать целостной системой, позволяющей организовать автоматическое получение из одной простой стратегии торговли полноценного советника, обеспечивающего параллельную работу этой стратегии на разных инструментах и таймфреймах. Не остались без внимания и вопросы, связанные с системой управления капиталом и риск-менеджером, позволяющим останавливать торговлю при возникновении неблагоприятных или, наоборот, слишком благоприятных ситуаций.
На протяжении почти всего цикла мы работали только с одной простой торговой стратегией, и только в последних частях, когда в целом были уже реализованы основные возможности, был рассмотрен процесс добавления новой торговой стратегии и использование её в качестве основной. На этом примере была продемонстрирована возможность попробовать раскрыть потенциал практически любой торговой стратегии (если он, конечно, реально есть).
Но поднявшись на текущую высоту, нам открывается ещё более широкое поле для дальнейшей работы. Глаза разбегаются, и выбор дальнейшего направления является достаточно сложным. Для преодоления этого, была предпринята попытка изменить подход к организации и хранению исходных кодов этого проекта. Первые шаги были сделаны ещё в части 23, в которой мы выделили большую часть кода в так называемую "библиотечную часть", оставив остальной код в "проектной части". Затем, мы обратили внимание на возможности нового репозитория кода, рассказав про первые шаги в отдельной статье Переходим на MQL5 Algo Forge (Часть 1): Создание основного репозитория. Пока ещё стратегия использования возможностей нового репозитория находится в процессе становления. В целом, хотелось бы организовать возможность параллельной работы над библиотечной частью сразу по нескольким направлениям. Насколько это получится сделать, будет видно позднее.
Для этого нужна практика. Только так удаётся понять, были ли хороши принятые архитектурные решения. Поэтому, попробуем попрактиковаться в создании нового проекта, использующего разработанную библиотеку, названную Advisor. Не будем затевать сразу работу над большим проектом по разработке советника, использующего какую-то сложную торговую стратегию. Даже наоборот. Сделаем проект, который вообще не ставит в качестве цели разработку торгового советника.
Намечаем путь
Один из читателей поднял интересный вопрос, касающийся последней добавленной стратегии SimpleCandles. В этой стратегии один из параметров — это количество идущих подряд однонаправленных свечей на заданном таймфрейме. Поэтому, неплохо было бы получить возможность посмотреть, какие серии таких свечей бывают на разных инструментах и разных таймфреймах, чтобы не перекладывать полностью подбор подходящих значений на плечи оптимизатора.
Действительно, даже для автоматической оптимизации всё равно надо понимать, в каких пределах будут изменяться входные параметры. Можно, конечно, поставить просто какой-то широкий диапазон, но это должно сильно снижать эффективность оптимизации. Ведь общее количество комбинаций параметров будет больше, и вероятность попасть на удачную комбинацию будет ниже. Насколько ниже — сказать трудно, но достаточно и простого понимания снижения, чтобы попробовать предпринять некоторые усилия по повышению эффективности.
Также сбор подобной информации о поведении цен разных инструментов поможет ответить на вопрос: "Можно ли использовать одинаковые диапазоны параметра для разных инструментов?" Если да, то это упрощает организацию проведения первого этапа оптимизации, а если нет, то придётся несколько усложнить процесс создания заданий оптимизации на этом этапе.
В общем, попробуем создать вспомогательный советник, или часть советника, который сможет нам в том или ином виде показывать некоторую статистику по символам и таймфреймам. Может быть, он нам пригодится и в дальнейшем для использования в торговых стратегиях.
Но это будет второй вопрос. А первый, с которого начнём, — как нам организовать хранение исходного кода с прицелом на его дальнейшее использования в других проектах.
Создание проекта
С первого взгляда кажется: ну что тут может быть сложного? Хранили же мы как-то раньше код, можно так и продолжить. Но отличие всё-таки есть. Одно дело, когда мы полностью храним код проекта в одной папке, а для каждого следующего проекта делаем просто новую папку, копируя в неё код из предыдущего проекта. Такой подход хорош своей простотой и вполне оправдан при строго последовательной разработке, без необходимости заботиться об обратной совместимости. На начальном этапе, когда существенные изменения идут достаточно часто, он гораздо удобнее. И другое дело, когда наш проект начинает разрастаться, и теперь в нём уже отчётливо выделяются отдельные части, которые не будут изменяться (или будут, но достаточно редко), и остальные части, которые могут или сильно изменяться, или вообще возникать с нуля.
В этом случае, минусы хранения всего кода в одной папке, на наш взгляд, начинают перевешивать плюсы такого подхода. Немного ранее мы уже вынесли большую часть кода в папку MQL5/Include/antekov/Advisor, назвав эту часть библиотечной. Но теперь использование данного расположения для библиотечной части кажется не совсем удобным.
Представим себе, что ведётся параллельная работа над двумя проектами, в которых используется библиотека Advisor. В основном, изменения затрагивают проектную часть, но кое-какие правки вносятся и в библиотечную часть. Если оба проекта ссылаются на библиотечную часть, расположенную в одном и том же месте (MQL5/Include/antekov/Advisor), то вполне возможны конфликты. Чтобы их не было, придётся, при переходе от работы над одним проектом к другому, как минимум, переключать библиотечную часть на другую соответствующую версию, хранящуюся в другой ветви репозитория. Хотя в этом нет ничего сложного, необходимость таких манипуляций не может приветствоваться. Можно когда-нибудь забыть переключиться, а затем вычищать уже сделанные не в той ветке правки, перенося их в другую ветку.
Так что попробуем изменить подход. Каждый проект будет отдельным репозиторием. Внутри папки проекта будет обязательно присутствовать папка Include, в которой будут располагаться папки с библиотечными частями. Обратите внимание: не с одной библиотечной частью, а с несколькими, разнесёнными по разным папкам. Каждая библиотечная часть будет являться клоном отдельного репозитория кода.
Репозиторий библиотечной части
Для библиотечной части создадим новый репозиторий на MQL5 Algo Forge или любом другом публичном хранилище репозиториев GIT. Название Advisor, которое мы использовали для библиотеки, показалось нам слишком общим. Добавим уникальности, переименовав её в Adwizard. Так и будем впредь называть нашу библиотеку.
Разместим в этом репозитории все файлы библиотечной части. При создании репозитория в нём присутствует единственная ветка с именем main. Создаём новую ветку develop, от которой будут порождаться ветки для статей и новых возможностей библиотеки. Эти вспомогательные ветки будут закрываться после реализации новых возможностей, и правки будут вливаться в ветку develop и затем в main. Обычно это будет происходить при завершении работы над очередной статьёй.
Для обеспечения работоспособности кода в этом репозитории, при клонировании его в любую папку, нам понадобилось внести небольшие исправления в некоторые файлы библиотеки. Они понадобились в тех местах, где в директивах подключения #include мы использовали пути, ведущие в папку Include со стандартной библиотекой. После замены их на относительные пути, мы убрали привязку к конкретному расположению библиотеки в папке MQL5/Include/antekov/Advisor.
Например, в файле Optimization.mqh была сделана такая замена:
#include <antekov/Advisor/Optimization/Optimizer.mqh> #include "../Optimization/Optimizer.mqh"
В файле OptimizerTask.mqh у нас ещё использовался единственный файл сторонней библиотеки от fxsaber. Его мы тоже перенесли внутрь библиотеки в папку 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
Эти правки были отправлены в репозиторий библиотеки.
Репозиторий проекта
Для проекта создадим новый репозиторий SymbolsInformer. В нём, помимо основной ветки main, тоже сделаем ветку для разработки с названием develop. Если этому проекту будет посвящено несколько статей, то правки, относящиеся к разным статьям, желательно разнести по разным веткам. Они будут порождаться от ветки develop и вливаться обратно в develop и main по мере готовности.
Создадим папку для размещения папки проекта, например, MQL5/Experts/Article/17606. Клонируем этот репозиторий в выбранную папку и создадим в ней папку Include. В этой папке мы разместим репозитории других библиотек, от которых будет зависеть данный проект. Пока что это будет только одна библиотека Adwizard. Клонируем в папку Include репозиторий библиотеки Adwizard. Если бы понадобилась ещё другая библиотека, то клонировали бы её в эту же папку Include.
После данных операций, мы получим примерно такую структуру папок в папке терминала:
В клонированной папке репозитория Adwizard переключимся на ветку develop. Она будет являться общей для всех статей. Если в процессе работы над этим проектом мы не будем вносить изменения в библиотеку Adwizard, то будем оставаться в ветке develop, обновляя её при наличии новых правок, сделанных при работе над другими статьями. Если же понадобится что-то исправить в этой библиотеке, в связи с работой над текущим проектом, то тогда создадим новую ветку.
После этого, в репозитории проекта создаём ветку для работы над данной статьёй и приступаем к разработке в ней. Здесь мы дали сжатое описание процесса создания нового проекта, более детальному будет посвящена отдельная статья.
Описание проекта
Попробуем сформулировать краткое техническое задание на разработку требуемого инструмента. Реализован он будет в виде советника, так как в нём не будет каких-либо расчётных параметров, требующих периодического пересчёта и показа изменяющихся значений на протяжении некоторого периода времени.
Прежде всего, для подсчёта количества серий однонаправленных свечей нам надо задать некоторый временной промежуток, на котором мы будем собирать эту статистику. Сделать это можно по-разному. Например, можно задать количество дней от текущей даты, или две разные даты, указывающие на начало и конец. Наверное, для начала сделаем только расчёт статистики для интервала, начинающегося с текущей даты. А длительность можно задать, выбрав таймфрейм (например, дневной) и количество свечей этого таймфрейма. Назовём его основным таймфреймом.
Далее понадобится как-то указать, для каких торговых инструментов (символов) и на каких таймфреймах мы хотим провести вычисления. Можно, конечно, проводить расчёты только для того символа и таймфрейма, на котором будет запущен разрабатываемый советник. Но лучше всё-таки сразу закладывать возможность выполнить расчёты для нескольких символов и таймфреймов.
На основе сказанного, составим список входных параметров советника:
- Основной таймфрейм
- Количество свечей основного таймфрейма
- Список символов
- Список таймфреймов
Этот набор параметров может пополниться в дальнейшем. Списки символов и таймфреймов будут содержать их имена, разделённые запятыми. Имена таймфреймов будем задавать так, как они названы в терминале, например M5, M15, H1 и так далее.
Для каждого символа и таймфрейма будем вычислять следующие значения:
- Средние размеры свечей:
- бычьих ("свеча вверх" или "buy", у которой цена закрытия не ниже цены открытия);
- медвежьих ("свеча вниз" или "sell", у которой цена закрытия не выше цены открытия);
- всех (и бычьих и медвежьих);
- Среднюю длину серии (серией будет считаться последовательность двух и более одинаковых по направлению свечей);
- Количество серий с длиной
- 2
- 3
- ...
- 8
- 9
Этот список тоже является открытым, то есть, при желании, мы можем добавить в него новые расчётные величины.
Делаем первую версию
Для начала, попробуем сделать максимально простой вариант. Расчётные значения будем сохранять в глобальных массивах, а результаты в каком-то виде будем выводить в лог и на график в виде комментария. Мы пока точно не знаем, какие данные нам окажутся полезными, а какие будут бессмысленны. Поэтому, первая версия нужна прежде всего для определения, что же нам всё-таки нужно. В ней мы не будем уделять большого внимания проверке непротиворечивости входных параметров и организации хранения информации.
Входные параметры, согласно составленного списка, можно задать таким образом:
//+------------------------------------------------------------------+ //| Входные параметры | //+------------------------------------------------------------------+ input group "::: Период для расчётов" sinput ENUM_TIMEFRAMES mainTimeframe_ = PERIOD_D1; // Основной таймфрейм input int mainLength_ = 30; // Количество свечей основного таймфрейма input group "::: Символы и таймфреймы" sinput string symbols_ = ""; // Символы (через запятую) sinput string timeframes_ = ""; // Таймфреймы (напр. M5,H1,H4)
Поскольку во входных параметрах символы и таймфреймы задаются по несколько в одной строке, то нам понадобятся массивы, хранящие отдельные значения каждого имени символа и каждого таймфрейма.
Для хранения расчётных значений создадим двумерные массивы. Первый индекс в них будет связан с символом, а второй — с таймфреймом. Поскольку при объявлении двумерных массивов, надо обязательно указать количество элементов по второму измерению, то введём константу TFN, в которой укажем количество всех существующих на данный момент стандартных таймфреймов. Их оказалось 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
Для преобразований между символьными константами таймфреймов (типа ENUM_TIMEFRAMES), их строковыми названиями и индексами в массиве всех таймфреймов сделаем вспомогательные функции. С их помощью можно будет решать три задачи:
- по строковому названию получать символьную константу (StringToTimeframe)
- из символьной константы получать строковое название таймфрейма без префикса "PERIOD_" (TimeframeToString)
- из символьной константы получать индекс массива всех таймфреймов (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; }
Расчёт всех величин будет выполняться внутри функции Calculate(). Организуем в ней двойной цикл, перебирающий все комбинации символов и таймфреймов. Внутри них будем проверять, наступил ли новый бар именно по этому символу и таймфрейму. Если да, то вызываем вспомогательные функции расчёта. Также можно передать через параметр force указание о необходимости немедленного расчёта всех величин без ожидания нового бара. Такой режим будет использоваться при запуске советника, чтобы мы сразу смогли увидеть результаты.
//+------------------------------------------------------------------+ //| Расчёт всех величин | //+------------------------------------------------------------------+ 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); } } } }
Непосредственные расчёты мы разместили в двух вспомогательных функциях. Каждая из них выполняет расчёт только для одного символа на одном таймфрейме. Помимо этих двух параметров, мы также передаём третий параметр — количество свечей, на котором будет происходить расчёт.
Расчёт средних размеров свечей выполняется в функции CalculateAvrSizes(). В начале мы по названию символа и таймфрейму определяем индексы s и t того элемента в двумерных массивах, которые предназначены для хранения этого результата. Свечи, у которых цена открытия равна цене закрытия, будут считаться и свечой вверх и свечой вниз. Посчитанные средние значения будут округляться до целых пунктов.
//+------------------------------------------------------------------+ //| Расчёт средних размеров свечей | //+------------------------------------------------------------------+ 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]); } }
Расчёт длин серий выполняется похожим образом в функции CalculateSeries(). В ней использован вспомогательный массив seriesLens, с размером 100 элементов. Индекс элемента этого массива соответствует длине серии, а сам элемент — количеству серий такой длины. Таким образом, мы будем считать, что подавляющая часть серий имеет длину менее ста свечей. Показывать же мы будем вообще только количества серий с длиной менее 10 свечей. Количество серий именно с такими длинами мы в конце функции переписываем из массива seriesLens в соответствующие элементы массивов для результатов с именами вида 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]; } }
За показ результатов будет отвечать функция Show(). В первой версии мы пока ограничимся выводом в лог терминала и в виде комментария на графике, на котором будет запущен советник. Таким образом, нам достаточно представить результаты в виде текста. Этим представлением будет заниматься отдельная функция TextComment().
//+------------------------------------------------------------------+ //| Показ результатов | //+------------------------------------------------------------------+ void Show() { // Получаем результаты в виде текста string text = TextComment(); // Показываем его в комментарии и в логе Comment(text); Print(text); }
В функции инициализации советника нам остаётся обработать входные параметры, разделив перечисленные названия символов и таймфреймов на отдельные значения, и подготовить массивы для записи результатов нужных размеров. После чего, можно вызвать функцию расчёта и показа результатов:
//+------------------------------------------------------------------+ //| Инициализация советника | //+------------------------------------------------------------------+ 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); }
В приведённой выше функции мы использовали новый макрос SPLIT. Он был добавлен в файл Utils/Macros.mqh библиотеки Adwizard. Это пока единственное добавление в библиотеку, которое понадобилось для данного проекта.
Сам макрос предназначен для разделения строки на части по двум возможным символам разделителям: запятой и точке с запятой.
#define SPLIT(V, A) { string s=V; StringReplace(s, ";", ","); StringSplit(s, ',', A); }
Посмотрим теперь на результаты работы разработанного советника.
Тестирование советника
Запустим советник с параметрами по умолчанию на каком-нибудь графике. В результате можно увидеть примерно такую картину:
Рис. 1. Результаты работы советника с параметрами по умолчанию на AUDCAD H1
Поскольку при выводе комментария на график, используется не моноширинный шрифт, то рассматривать полученные значения на графике не особо удобно. В логе терминала шрифт моноширинный, поэтому там всё выглядит получше. Посмотрим, как будут смотреться результаты для нескольких символов и таймфреймов.
Запустим советник с такими входными параметрами:
И посмотрим на результат.
Рис. 2. Результаты работы советника с для нескольких символов и таймфреймов
Расчёты для нескольких символов и таймфреймов были успешно выполнены. Результаты показаны в виде таблицы. Да, пользоваться ими пока что не особо удобно, но для беглого предварительного анализа такого отображения вполне достаточно.
Заключение
Итак, мы сделали первую версию вспомогательного советника-информера, показывающего информацию о средних размерах свечей в пунктах и длинах серий однонаправленных свечей. На первый взгляд это имеет несколько опосредованное отношение к нашему главному проекту создания системы автоматической оптимизации и запуска мультивалютных советников, реализующих разнообразные простые стратегии. Это действительно так, поэтому продолжение работы над этим советником будет продолжено уже вне рамок этого цикла статей. Но в процессе работы мы будем пробовать и обкатывать реализацию многих вещей, которые затем надеемся успешно применить и в главном проекте.
Сейчас мы уже сделали не мало. Выбор другой, более оптимальной структуры организации кода позволит распараллелить работу по разным направлениям дальнейшего развития библиотеки Adwizard. А этих направлений уже просматривается несколько штук. Одно из них — это построение визуального интерфейса управления работой итоговых советников. И рассмотренный в этой статье проект поможет нам, не погрязая в реализации очень сложных вещей, попробовать различные возможные подходы. Исследовав их плюсы и минусы и выбрав самый подходящий, мы сможем уже более целенаправленно заняться развитием основного проекта.
Спасибо за внимание, до встречи!
Важное предупреждение
Все результаты, изложенные в этой статье и всех предшествующих статьях цикла, основываются только на данных тестирования на истории и не являются гарантией получения хоть какой-то прибыли в будущем. Работа в рамках данного проекта носит исследовательский характер. Все опубликованные результаты могут быть использованы всеми желающими на свой страх и риск.
Содержание архива
# | Имя | Версия | Описание | Последние изменения |
---|---|---|---|---|
SymbolsInformer | Рабочая папка проекта | |||
1 | SymbolsInformer.mq5 | 1.00 | Советник для вывода информации о длинах серий однонаправленных свечей | Часть 26 |
SymbolsInformer/Include/Adwizard/Base | Базовые классы, от которых наследуются другие классы проекта | |||
2 | Advisor.mqh | 1.04 | Базовый класс эксперта | Часть 10 |
3 | Factorable.mqh | 1.05 | Базовый класс объектов, создаваемых из строки | Часть 24 |
4 | FactorableCreator.mqh | 1.00 | Класс создателей, связывающих названия и статические конструкторы классов-наследников CFactorable | Часть 24 |
5 | Interface.mqh | 1.01 | Базовый класс визуализации различных объектов | Часть 4 |
6 | Receiver.mqh | 1.04 | Базовый класс перевода открытых объемов в рыночные позиции | Часть 12 |
7 | Strategy.mqh | 1.04 | Базовый класс торговой стратегии | Часть 10 |
SymbolsInformer/Include/Adwizard/Database | Файлы для работы со всеми типами баз данных, используемых советниками проекта | |||
8 | Database.mqh | 1.12 | Класс для работы с базой данных | Часть 25 |
9 | db.adv.schema.sql | 1.00 | Схема базы данных итогового советника | Часть 22 |
10 | db.cut.schema.sql | 1.00 | Схема урезанной базы данных оптимизации | Часть 22 |
11 | db.opt.schema.sql | 1.05 | Схема базы данных оптимизации | Часть 22 |
12 | Storage.mqh | 1.01 | Класс работы с хранилищем Key-Value для итогового советника в базе данных эксперта | Часть 23 |
SymbolsInformer/Include/Adwizard/Experts | Файлы с общими частями используемых советников разного типа | |||
13 | Expert.mqh | 1.22 | Библиотечный файл для итогового советника. Параметры групп могут браться базы данных эксперта | Часть 23 |
14 | Optimization.mqh | 1.04 | Библиотечный файл для советника, управляющего запуском задач оптимизации | Часть 23 |
15 | Stage1.mqh | 1.19 | Библиотечный файл для советника оптимизации одиночного экземпляра торговой стратегии (Этап 1) | Часть 23 |
16 | Stage2.mqh | 1.04 | Библиотечный файл для советника оптимизации группы экземпляров торговых стратегий (Этап 2) | Часть 23 |
17 | Stage3.mqh | 1.04 | Библиотечный файл для советника, сохраняющего сформированную нормированную группу стратегий в базу данных эксперта с заданным именем. | Часть 23 |
SymbolsInformer/Include/Adwizard/Optimization | Классы, отвечающие за работу автоматической оптимизации | |||
18 | OptimizationJob.mqh | 1.00 | Класс для работы этапа проекта оптимизации | Часть 25 |
19 | OptimizationProject.mqh | 1.00 | Класс для проекта оптимизации | Часть 25 |
20 | OptimizationStage.mqh | 1.00 | Класс для этапа проекта оптимизации | Часть 25 |
21 | OptimizationTask.mqh | 1.00 | Класс для задачи оптимизации (для создания) | Часть 25 |
22 | Optimizer.mqh | 1.03 | Класс для менеджера автоматической оптимизации проектов | Часть 22 |
23 | OptimizerTask.mqh | 1.03 | Класс для задачи оптимизации (для конвейера) | Часть 22 |
SymbolsInformer/Include/Adwizard/Strategies | Примеры торговых стратегий, используемые для демонстрации работы проекта | |||
24 | HistoryStrategy.mqh | 1.00 | Класс торговой стратегии воспроизведения истории сделок | Часть 16 |
25 | SimpleVolumesStrategy.mqh | 1.11 | Класс торговой стратегии с использованием тиковых объемов | Часть 22 |
SymbolsInformer/Include/Adwizard/Utils | Вспомогательные утилиты, макросы для сокращения кода | |||
26 | ExpertHistory.mqh | 1.00 | Класс для экспорта истории сделок в файл | Часть 16 |
27 | Macros.mqh | 1.07 | Полезные макросы для операций с массивами | Часть 26 |
28 | NewBarEvent.mqh | 1.00 | Класс определения нового бара для конкретного символа | Часть 8 |
29 | SymbolsMonitor.mqh | 1.00 | Класс получения информации о торговых инструментах (символах) | Часть 21 |
SymbolsInformer/Include/Adwizard/Virtual | Классы для создания различных объектов, объединённых использованием системы виртуальных торговых ордеров и позиций | |||
30 | Money.mqh | 1.01 | Базовый класс управления капиталом | Часть 12 |
31 | TesterHandler.mqh | 1.07 | Класс для обработки событий оптимизации | Часть 23 |
32 | VirtualAdvisor.mqh | 1.10 | Класс эксперта, работающего с виртуальными позициями (ордерами) | Часть 24 |
33 | VirtualChartOrder.mqh | 1.01 | Класс графической виртуальной позиции | Часть 18 |
34 | VirtualHistoryAdvisor.mqh | 1.00 | Класс эксперта воспроизведения истории сделок | Часть 16 |
35 | VirtualInterface.mqh | 1.00 | Класс графического интерфейса советника | Часть 4 |
36 | VirtualOrder.mqh | 1.09 | Класс виртуальных ордеров и позиций | Часть 22 |
37 | VirtualReceiver.mqh | 1.04 | Класс перевода открытых объемов в рыночные позиции (получатель) | Часть 23 |
38 | VirtualRiskManager.mqh | 1.05 | Класс управления риском (риск-менеждер) | Часть 24 |
39 | VirtualStrategy.mqh | 1.09 | Класс торговой стратегии с виртуальными позициями | Часть 23 |
40 | VirtualStrategyGroup.mqh | 1.03 | Класс группы торговых стратегий или групп торговых стратегий | Часть 24 |
41 | VirtualSymbolReceiver.mqh | 1.00 | Класс символьного получателя | Часть 3 |
Также исходный код доступен в публичных репозиториях SymbolsInformer и Adwizard




- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Просто позор(
Завтра я изучу ваш информативный отзыв
@Rashid Umarov
Hi
Are you following this thread closely and able to implement everything and optimize?
Can you please help me doing thst?
Cheers