Обсуждение статьи "Разрабатываем мультивалютный советник (Часть 17): Дальнейшая подготовка к реальной торговле"

 

Опубликована статья Разрабатываем мультивалютный советник (Часть 17): Дальнейшая подготовка к реальной торговле:

Сейчас наш советник использует базу данных для получения строк инициализации одиночных экземпляров торговых стратегий. Однако база данных является достаточно объёмной и содержит много информации, ненужной при реальной работе советника. Попробуем обеспечить работоспособность советника без обязательного подключения к базе данных.

В одной из предыдущих статей мы уже обратили своё внимание на доработки советника, необходимые для работы на реальных счетах. До этого наши усилия были сосредоточены в основном на получении приемлемых результатов работы советника в тестере стратегий. Но работы в направлении движения к реальной торговле не были завершены. Тут ещё много над чем можно потрудиться.

Кроме восстановления работы советника после перезапуска терминала, возможности использовать немного другие названия торговых инструментов и автоматического завершения торговли при достижении заданных показателей, перед нами стоит ещё и такая проблема: для формирования строки инициализации мы используем информацию, получаемую напрямую из базы данных, в которой хранятся все результаты оптимизаций экземпляров торговых стратегий и их групп.

Для запуска советника необходимо наличие файла с базой данных в общей папке терминалов. Размер базы данных уже сейчас составляет несколько гигабайт, а в дальнейшем он будет только расти. Так что делать базу данных составной частью советника не рационально — для запуска необходима только очень малая часть из хранящейся там информации. Поэтому необходимо реализовать механизм извлечения и использования в советнике этой информации.

Разрабатываем мультивалютный советник (Часть 17): Дальнейшая подготовка к реальной торговле

Автор: Yuriy Bykov

 

Юрий здравствуйте! Изучаю Ваш код и немного не понятно.. В файле SimpleVolumesStrategy.mqh в конструкторе у Вас указан параметр:

         // Регистрируем обработчик события нового бара на минимальном таймфрейме
         IsNewBar(m_symbol, PERIOD_M1);

Зачем Вы это делаете? Почему именно период М1, а не текущий на котором запускается этот экземпляр стратегии? Как я понимаю в этом случае советник будет работать не по открытиям баров заданного ТФ а просто по м1. Или я что-то не правильно понимаю?

И второй момент - в функции SignalForOpen() вот здесь:

      // Если текущий объем превысил заданный уровень, то
      if(m_volumes[0] > avrVolume * (1 + m_signalDeviation + m_ordersTotal * m_signaAddlDeviation)) {
         // если цена открытия свечи меньше текущей цены (закрытия), то
         if(iOpen(m_symbol, m_timeframe, 0) < iClose(m_symbol, m_timeframe, 0)) {
            signal = 1; // сигнал на покупку
         } else {
            signal = -1; // иначе - сигнал на продажу
         }
      }

У Вас указан в вычислениях бар 0 (текущий), хотя как мне кажется там должен стоять бар 1 (последний закрытый). Просто у бара 0 на первом тике (если мы работаем по ценам открытия) нет никакого тела, и соответственно цена открытия равна всегда цене закрытия. Да и объёма у него не может быть тк он ещё не сформировался к этому моменту. Я может что-то не правильно понимаю, ведь оно как-то работает... Хотелось бы понять почему так по этим моментам?

И ещё вот если я хочу допустим прокинуть какой либо параметр (новый) в эту стратегию, то мне его надо создать в советнике SimpleVolumesStage1.mq5 в настройках и передать в строковую переменную там, а потом в этом классе (SimpleVolumesStrategy.mqh) в конструкторе разобрать в правильном порядке и всё? Или где-то ещё надо его прописывать?

 

Здравствуйте, Виктор.

Зачем Вы это делаете? Почему именно период М1, а не текущий на котором запускается этот экземпляр стратегии? Как я понимаю в этом случае советник будет работать не по открытиям баров заданного ТФ а просто по м1. Или я что-то не правильно понимаю?

Работа советника состоит из двух частей: открытие виртуальных позиций и синхронизация открытых виртуальных позиций с реальными. Заданный ТФ используется только в первой части для определения сигнала к открытию. А синхронизация должна в идеале выполняться на каждом тике или хотя бы на каждом новом баре минимального таймфрейма M1, так как в любой момент у виртуальной позиции может быть достигнут TP или SL.

В методе VirtualAdvisor::Tick() в начале есть проверка на наступление нового бара на всех отслеживаемых символах и таймфреймах, в том числе и на M1. Если он не наступил, то советник не выполняет больше никаких действий. Делать что-то ещё он будет только при наступлении нового бара на M1. В этом случае можно оптимизировать в режиме OHLC на М1 и получать при работе советника на графике (где есть все тики) почти такие же результаты. А оптимизация так проходит гораздо быстрее. Упомянутая вами строка кода просто подстраховка на тот случай, если в стратегии нам не понадобилось отслеживать новый бар на M1. Так он гарантированно будет отслеживаться хотя бы на одном символе.

При желании можно, конечно, такой режим работы отключить через переменную useOnlyNewBars_ = false. Тогда советник будет на каждом доступном тике проверять и синхронизировать позиции.

Просто у бара 0 на первом тике (если мы работаем по ценам открытия) нет никакого тела, и соответственно цена открытия равна всегда цене закрытия. Да и объёма у него не может быть тк он ещё не сформировался к этому моменту. Я может что-то не правильно понимаю, ведь оно как-то работает...

Открытие нового бара M1 может происходить внутри бара более старшего таймфрейма. Обратите внимание, что в SignalForOpen() используется текущий таймфрейм, который обычно H1, M30 или M15. Поэтому там уже не будет совпадения цен открытия и закрытия текущего таймфрейма. Ко всему прочему, до этой проверки дело доходит только когда тиковый объём текущего бара на текущем таймфрейме заметно превысил типичный тиковый объём одного бара. Это не может случиться на первом тике, когда тиковый объем равен всего лишь 1.

И ещё вот если я хочу допустим прокинуть какой либо параметр (новый) в эту стратегию, то мне его надо создать в советнике SimpleVolumesStage1.mq5 в настройках и передать в строковую переменную там, а потом в этом классе (SimpleVolumesStrategy.mqh) в конструкторе разобрать в правильном порядке и всё? Или где-то ещё надо его прописывать?

Абсолютно верно.

 
Yuriy Bykov #:

Работа советника состоит из двух частей: открытие виртуальных позиций и синхронизация открытых виртуальных позиций с реальными. Заданный ТФ используется только в первой части для определения сигнала к открытию. А синхронизация должна в идеале выполняться на каждом тике или хотя бы на каждом новом баре минимального таймфрейма M1, так как в любой момент у виртуальной позиции может быть достигнут TP или SL.

В методе VirtualAdvisor::Tick() в начале есть проверка на наступление нового бара на всех отслеживаемых символах и таймфреймах, в том числе и на M1. Если он не наступил, то советник не выполняет больше никаких действий. Делать что-то ещё он будет только при наступлении нового бара на M1. В этом случае можно оптимизировать в режиме OHLC на М1 и получать при работе советника на графике (где есть все тики) почти такие же результаты. А оптимизация так проходит гораздо быстрее. Упомянутая вами строка кода просто подстраховка на тот случай, если в стратегии нам не понадобилось отслеживать новый бар на M1. Так он гарантированно будет отслеживаться хотя бы на одном символе.

При желании можно, конечно, такой режим работы отключить через переменную useOnlyNewBars_ = false. Тогда советник будет на каждом доступном тике проверять и синхронизировать позиции.

Понятно. А можно к примеру сделать чтобы синхронизация позиций работала на каждом тике, а открытие виртуальных (новых) позиций происходило при наступлении нового бара на заданном в стратегии ТФ (m15,m30,h1) ?

Yuriy Bykov #:
Открытие нового бара M1 может происходить внутри бара более старшего таймфрейма. Обратите внимание, что в SignalForOpen() используется текущий таймфрейм, который обычно H1, M30 или M15. Поэтому там уже не будет совпадения цен открытия и закрытия текущего таймфрейма. Ко всему прочему, до этой проверки дело доходит только когда тиковый объём текущего бара на текущем таймфрейме заметно превысил типичный тиковый объём одного бара. Это не может случиться на первом тике, когда тиковый объем равен всего лишь 1.

Здесь я Вас немного не понял. Да в SignalForOpen() используется ТФ заданный в настройках текущего экземпляра виртуальной стратегии, это я вижу. Но вот к примеру если я хочу чтобы советник работал строго по закрытым последним барам, то вот здесь

      if(m_volumes[0] > avrVolume * (1 + m_signalDeviation + m_ordersTotal * m_signaAddlDeviation)) {
         // если цена открытия свечи меньше текущей цены (закрытия), то
         if(iOpen(m_symbol, m_timeframe, 0) < iClose(m_symbol, m_timeframe, 0)) {

я должен указать вместо нулей единицы ? Правильно я понимаю ?

 
Viktor Kudriavtsev #:
А можно к примеру сделать чтобы синхронизация позиций работала на каждом тике, а открытие виртуальных (новых) позиций происходило при наступлении нового бара на заданном в стратегии ТФ (m15,m30,h1) ?

Да, так и будет, если useOnlyNewBars_ = false. Эта переменная не используется стратегиями, они сами определяют, когда проверять сигнал на открытие и когда при поступившем ранее сигнале открывать позиции. Например, только при наступлении нового бара на H1. В этом случае вы тогда должны еще модифицировать код, чтобы полученный в середине бара сигнал доживал до начала следующего бара. Сейчас полученный сигнал используется сразу (приводит к открытию виртуальных позиций), поэтому он не сохраняется нигде.

Здесь я Вас немного не понял. Да в SignalForOpen() используется ТФ заданный в настройках текущего экземпляра виртуальной стратегии, это я вижу. Но вот к примеру если я хочу чтобы советник работал строго по закрытым последним барам, то вот здесь я должен указать вместо нулей единицы ? Правильно я понимаю ?

Если под словами "советник работал строго по закрытым последним барам" подразумевается, что при превышении тиковым объёмом порогового значения на текущем баре для определения направления сигнала на открытие мы возьмём предыдущий бар, и посмотрим на его направление, то вы всё поняли правильно.

 

Юрий здравствуйте. У меня при выполнение советника SimpleVolumesStage3.mq5 и сохранения им в базу информации вылетает ошибка:

2024.09.11 21:02:09.909 Core 1  2024.09.06 23:54:59   
2024.09.11 21:02:09.909 Core 1  2024.09.06 23:54:59   database error, FOREIGN KEY constraint failed
2024.09.11 21:02:09.909 Core 1  2024.09.06 23:54:59   CDatabase::Execute | ERROR: 5619 in query
2024.09.11 21:02:09.909 Core 1  2024.09.06 23:54:59   INSERT INTO strategy_groups VALUES(0, 'EA_EG_EU (H1, M30, M15, 9x16 items)')
2024.09.11 21:02:09.909 Core 1  final balance 24603.99 USD

Что это значит и как её исправить? Таблицу добавил в базу с помощью Вашего запроса со статьи.

 

Юрий я разбирался в Вашем коде и вижу что ошибка происходит чуть раньше в функции CDatabase::Insert, в лог пишет вот такое:

2024.09.12 20:14:11.248 Core 1  2024.09.06 23:54:59   CDatabase::Insert | ERROR: Reading row for request 
2024.09.12 20:14:11.248 Core 1  2024.09.06 23:54:59   INSERT INTO passes VALUES (NULL, 0, 0, 10000.00,0.00,11096.20,21542.31,-10446.11,92.51,-63.35,630.89,39.00,444.04,53.00,-376.27,52.00,-376.27,52.00,9430.69,569.31,5.69,5.69,569.31,9325.11,683.96,6.83,6.83,683.96,2.15,2.06,16.22,3.44,3736.76,8435.00,5170.00,3042.00,2128.00,2766.00,2404.00,1706.00,1336.00,6.00,4.00,99.11,8122.90,'class CVirtualStrategyGroup([
2024.09.12 20:14:11.248 Core 1  2024.09.06 23:54:59           class CVirtualStrategyGroup([
2024.09.12 20:14:11.248 Core 1  2024.09.06 23:54:59           class CVirtualStrategyGroup([
2024.09.12 20:14:11.248 Core 1  2024.09.06 23:54:59           class CSimpleVolumesStrategy("CADCHF",16385,220,1.40,1.70,150,2200.00,200.00,46000,24)
2024.09.12 20:14:11.248 Core 1  2024.09.06 23:54:59          ],66.401062),class CVirtualStrategyGroup([

.....

2024.09.12 20:38:26.905	Core 1	2024.09.06 23:54:59          ],13.365410),class CVirtualStrategyGroup([
2024.09.12 20:38:26.905	Core 1	2024.09.06 23:54:59           class CSimpleVolumesStrategy("CADJPY",15,132,0.40,1.90,0,7200.00,600.00,45000,27)
2024.09.12 20:38:26.905	Core 1	2024.09.06 23:54:59          ],13.365410),
2024.09.12 20:38:26.905	Core 1	2024.09.06 23:54:59          ],2.970797),
2024.09.12 20:38:26.905	Core 1	2024.09.06 23:54:59          ],1.462074)',
2024.09.12 20:38:26.905	Core 1	2024.09.06 23:54:59   '',
2024.09.12 20:38:26.905	Core 1	2024.09.06 23:54:59   '2024.09.06 23:54:59') RETURNING rowid;
2024.09.12 20:38:26.905	Core 1	2024.09.06 23:54:59   failed with code 5039

Не может выполнить 

      if(DatabaseReadBind(request, row)) {

С чем это может быть связано? Второй этап пройден и сам тест проходит (советник торгует и проходы с БД подгружаются).

 

Здравствуйте, Виктор.

Скоро вернусь к продолжению работы над этим проектом и постараюсь разобраться с найденными ошибками. Спасибо, что вы их обнаружили. Часть ошибок, про которые вы писали раньше, у меня тоже получилось воспроизвести. Они оказались связаны с тем, что в более поздних частях вносились правки, которые были направлены на одно, но вдобавок оказывали влияние и на другое, что в очередной статье не рассматривалось. Это влияние порождало ошибки. В следующей статье будет снова пройден путь всех этапов автоматизированной оптимизации с устранением всех обнаруженных ошибок.