English 中文 Español Deutsch 日本語 Português
preview
Возможности Мастера MQL5, которые вам нужно знать (Часть 21): Тестирование с данными экономического календаря

Возможности Мастера MQL5, которые вам нужно знать (Часть 21): Тестирование с данными экономического календаря

MetaTrader 5Торговые системы | 18 октября 2024, 12:55
618 4
Stephen Njuki
Stephen Njuki

Введение

Я продолжаю серию статей, посвященную советникам, собранным в Мастере. Здесь мы рассмотрим, как можно интегрировать новости из экономического календаря в советник во время тестирования, чтобы подтвердить идею или построить более надежную торговую систему. Работа во многом основана на этой статье. Статья является первой частью серии и я рекомендую читателям ознакомиться со всей серией полностью. Здесь же мы сконцентрируемся на том, как советники, собранные с помощью Мастера, могут извлечь пользу из инструментов MQL5 IDE. Новички могут почитать вводные статьи о сборке советника в Мастере здесь и здесь.

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

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


Текущие ограничения и обходные пути

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

//+------------------------------------------------------------------+
//| Read Data
//+------------------------------------------------------------------+
double CSignalEconData::Read(string DB, datetime Time, string Event)
{  double _data = 0.0;
//--- create or open the database in the common terminal folder
   ResetLastError();
   int _db_handle = DatabaseOpen(DB, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE);
   if(_db_handle == INVALID_HANDLE)
   {  Print("DB: ", DB, " open failed with err: ", GetLastError());
      return(_data);
   }
   string _sql =
      "  SELECT ACTUAL " +
      "  FROM "   +
      "  ( "   +
      "  SELECT ACTUAL "   +
      "  FROM PRICES "  +
      "  WHERE DATE <= '" + TimeToString(Time) + "' "  +
      "  AND EVENT = '" + Event + "'  "  +
      "  ORDER BY DATE DESC   "  +
      "  LIMIT 1  "  +
      "  )  ";
   int _request = DatabasePrepare(_db_handle, _sql);
   if(_request == INVALID_HANDLE)
   {  Print("request failed with err: ", GetLastError());
      DatabaseClose(_db_handle);
      return(_data);
   }
   while(DatabaseRead(_request))
   {  //--- read the values of each field from the obtained entry
      ResetLastError();
      if(!DatabaseColumnDouble(_request, 0, _data))
      {  Print(" DatabaseRead() failed with err: ", GetLastError());
         DatabaseFinalize(_request);
         DatabaseClose(_db_handle);
      }
   }
   return(_data);
}

выдает ошибку 5601 с сообщением о том, что таблица, к которой я пытаюсь получить доступ, не существует! Однако запуск точного SQL-скрипта либо из среды разработки базы данных MetaEditor, либо из скрипта, прикрепленного к графику, не вызывает у меня таких проблем, поскольку возвращается ожидаемый результат. Таким образом, это может быть моей оплошностью (нужно включить дополнительный код для запуска), проблемой тестера стратегий ИЛИ же доступ к базам данных в тестере стратегий может быть не разрешен. Чат-бот службы поддержки помочь не смог!

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

В данном случае CSV-файлы используются в качестве обходного пути и имеют ряд проблем и ограничений, поэтому заменить базы данных они не могут. Меня могут спросить: "Почему бы просто не экспортировать данные непосредственно в файл CSV, не используя базу данных?"

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

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

Кроме того, базы данных имеют автоматизированные инструменты для резервного копирования и восстановления, чего нет в CSV. Базы данных поддерживают сложные запросы, которые используют объединения и манипуляции с SQL для тщательного анализа, в то время как файлам CSV для этого требуются сторонние скрипты. Базы данных соответствуют требованиям ACID, а CSV-файлы - нет.

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

Прежде чем экспортировать данные в CSV-файл для доступа тестера стратегий, нам следует создать базу данных и загрузить в нее экономические данные. Этим мы сейчас и займемся.


Создание базы данных SQLite

Для создания нашей базы данных SQLite мы воспользуемся скриптом. Листинг приведен ниже:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
//| Sourced from: https://www.mql5.com/en/articles/7463#database_functions
//| and here: https://www.mql5.com/en/docs/database/databasebind
//+------------------------------------------------------------------+
void OnStart()
{
//--- create or open a database
   string _db_file = __currency + "_econ_cal.sqlite";
   int _db_handle = DatabaseOpen(_db_file, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE);
   if(_db_handle == INVALID_HANDLE)
   {  Print("DB: ", _db_file, " open failed with code ", GetLastError());
      return;
   }
   else
      Print("Database: ", _db_file, " opened successfully");
	...

        ...

}

Код в основном взят отсюда с некоторыми изменениями. Создание базы данных осуществляется с помощью хэндла, аналогично объявлению хэндла чтения или записи файла. Мы создаем базу данных для каждой валютной пары, что, я признаю, расточительно и очень громоздко. Лучшим подходом было бы собрать все экономические данные по валютам в одной базе данных, однако у меня не хватило на это должного усердия, за что прошу прощения. При необходимости вы можете внести свои правки. После создания нашего идентификатора нам нужно будет проверить его, прежде чем продолжить. Если он действителен, это означает, что у нас создана пустая база данных и мы можем приступить к созданию таблицы для размещения наших данных. В таблице мы указываем цены, поскольку в этой статье мы сосредоточимся только на экономическом секторе события типа sector prices (отраслевые цены). Это зонтичный сектор, который должен включать не только данные об уровне инфляции, но также индексы потребительских цен и цен производителей, а мы хотим разработать пользовательский класс сигналов, который основывает свои условия покупки и продажи на относительных темпах инфляции торгуемой валютной пары. При разработке длинных и коротких сигналов можно использовать множество альтернативных подходов. Выбранный здесь путь, вероятно, один из самых простых. При создании таблицы, как и в случае с большинством объектов базы данных, мы начнем с проверки ее существования. Если она существует, она будет удалена, чтобы мы могли создать таблицу, которую собираемся заполнить и использовать. Листинг представлен ниже:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
        ...


//--- if the PRICES table exists, delete it
   if(DatabaseTableExists(_db_handle, "PRICES"))
   {  //--- delete the table
      if(!DatabaseExecute(_db_handle, "DROP TABLE PRICES"))
      {  Print("Failed to drop table PRICES with code ", GetLastError());
         DatabaseClose(_db_handle);
         return;
      }
   }
//--- create the PRICES table
   if(!DatabaseExecute(_db_handle, "CREATE TABLE PRICES("
                       "DATE           TEXT            ,"
                       "FORECAST       REAL            ,"
                       "ACTUAL         REAL            ,"
                       "EVENT          TEXT);"))
   {  Print("DB: ", _db_file, " create table failed with code ", GetLastError());
      DatabaseClose(_db_handle);
      return;
   }
//--- display the list of all fields in the PRICES table
   if(DatabasePrint(_db_handle, "PRAGMA TABLE_INFO(PRICES)", 0) < 0)
   {  PrintFormat("DatabasePrint(\"PRAGMA TABLE_INFO(PRICES)\") failed, error code=%d at line %d", GetLastError(), __LINE__);
      DatabaseClose(_db_handle);
      return;
   }

        ...
}

Созданная нами таблица будет содержать четыре столбца - столбец DATE текстового типа, регистрирующий дату выхода экономических новостей, столбец FORECAST для прогнозируемой точки экономических данных, вещественного типа, столбец ACTUAL, который также будет вещественного типа и будет включать фактическую точку экономических данных на эту дату, и, наконец, столбец EVENT, который будет текстовым и поможет правильно маркировать эту точку данных, поскольку на любую дату для данной валюты у нас может быть несколько точек данных в категории цен сектора события. Таким образом, тип метки, используемой для каждой точки данных, будет соответствовать коду события данных. Это связано с тем, что при получении данных экономического календаря мы используем функцию CalendarValueHistoryByEvent для возврата значений новостей календаря, связанных с определенными событиями. Каждое из этих событий имеет строковый описательный код, и именно эти коды мы присваиваем нашим данным при сохранении в базе данных. Ниже приведен листинг функции Get, которая извлекает данные экономического календаря:

//+------------------------------------------------------------------+
//| Get Currency Events
//+------------------------------------------------------------------+
bool Get(string Currency, datetime Start, datetime Stop, ENUM_CALENDAR_EVENT_SECTOR Sector, string &Data[][4])
{  ResetLastError();
   MqlCalendarEvent _event[];
   int _events = CalendarEventByCurrency(Currency, _event);
   printf(__FUNCSIG__ + " for Currency: " + Currency + " events are: " + IntegerToString(_events));
//
   MqlCalendarValue _value[];
   int _rows = 1;
   ArrayResize(Data, __COLS * _rows);
   for(int e = 0; e < _events; e++)
   {  int _values = CalendarValueHistoryByEvent(_event[e].id, _value, Start, Stop);
      //
      if(_event[e].sector != Sector)
      {  continue;
      }
      printf(__FUNCSIG__ + " Calendar Event code: " + _event[e].event_code + ", belongs to sector: " + EnumToString(_event[e].sector));
      //
      _rows += _values;
      ArrayResize(Data, __COLS * _rows);
      for(int v = 0; v < _values; v++)
      {  //
         printf(__FUNCSIG__ + " Calendar Event code: " + _event[e].event_code + ", for value: " + TimeToString(_value[v].period) + " on: " + TimeToString(_value[v].time) + ", has... ");
         //
         Data[_rows - _values + v - 1][0] = TimeToString(_value[v].time);
         //
         if(_value[v].HasForecastValue())
         {  Data[_rows - _values + v - 1][1] = DoubleToString(_value[v].GetForecastValue());
         }
         if(_value[v].HasActualValue())
         {  Data[_rows - _values + v - 1][2] = DoubleToString(_value[v].GetActualValue());
         }
         //
         Data[_rows - _values + v - 1][3] = _event[e].event_code;
      }
   }
   return(true);
}

Мы используем многомерный строковый массив с именем _data для извлечения данных экономического календаря, а его второе измерение совпадает с количеством столбцов в таблице PRICES, которую мы будем использовать для хранения данных, что означает, что его строки по количеству равны строкам данных, которые мы вставим в таблицу PRICES. Чтобы ускорить загрузку данных из нашего массива в таблицу, мы сначала используем функции DatabaseTransactionBegin() и DatabaseTransactionCommit() для соответственно начала и завершения операций записи данных. В статье, на которую я уже ссылался выше, указывается на большую эффективность такого подхода. Во-вторых, мы используем функцию привязки данных для фактической записи данных нашего массива в базу данных. Поскольку наши столбцы данных соответствуют целевой таблице данных, этот процесс также относительно прост и очень эффективен, несмотря на свою относительную длительность:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
//| Sourced from: https://www.mql5.com/en/articles/7463#database_functions
//| and here: https://www.mql5.com/en/docs/database/databasebind
//+------------------------------------------------------------------+
void OnStart()
{

        ...

//--- create a parametrized _sql_request to add _points to the PRICES table
   string _sql = "INSERT INTO PRICES (DATE,FORECAST,ACTUAL,EVENT)"
                " VALUES (?1,?2,?3,?4);"; // _sql_request parameters
   int _sql_request = DatabasePrepare(_db_handle, _sql);
   if(_sql_request == INVALID_HANDLE)
   {  PrintFormat("DatabasePrepare() failed with code=%d", GetLastError());
      Print("SQL _sql_request: ", _sql);
      DatabaseClose(_db_handle);
      return;
   }
//--- go through all the _points and add them to the PRICES table
   string _data[][__COLS];
   Get(__currency, __start_date, __stop_date, __event_sector, _data);
   int _points = int(_data.Size() / __COLS);
   bool _request_err = false;
   DatabaseTransactionBegin(_db_handle);
   for(int i = 0; i < _points; i++)
   {  //--- set the values of the parameters before adding a data point
      ResetLastError();
      string _date = _data[i][0];
      if(!DatabaseBind(_sql_request, 0, _date))
      {  PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError());
         _request_err = true;
         break;
      }
      //--- if the previous DatabaseBind() call was successful, set the next parameter
      if(!DatabaseBind(_sql_request, 1, _data[i][1]))
      {  PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError());
         _request_err = true;
         break;
      }
      if(!DatabaseBind(_sql_request, 2, _data[i][2]))
      {  PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError());
         _request_err = true;
         break;
      }
      if(!DatabaseBind(_sql_request, 3, _data[i][3]))
      {  PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError());
         _request_err = true;
         break;
      }
      //--- execute a _sql_request for inserting the entry and check for an error
      if(!DatabaseRead(_sql_request) && (GetLastError() != ERR_DATABASE_NO_MORE_DATA))
      {  PrintFormat("DatabaseRead() failed with code=%d", GetLastError());
         DatabaseFinalize(_sql_request);
         _request_err = true;
         break;
      }
      else
         PrintFormat("%d: added data for %s", i + 1, _date);
      //--- reset the _sql_request before the next parameter update
      if(!DatabaseReset(_sql_request))
      {  PrintFormat("DatabaseReset() failed with code=%d", GetLastError());
         DatabaseFinalize(_sql_request);
         _request_err = true;
         break;
      }
   } //--- done going through all the data points
//--- transactions status
   if(_request_err)
   {  PrintFormat("Table PRICES: failed to add %s data", _points);
      DatabaseTransactionRollback(_db_handle);
      DatabaseClose(_db_handle);
      return;
   }
   else
   {  DatabaseTransactionCommit(_db_handle);
      PrintFormat("Table PRICES: added %d data", _points);
   }


        ...

}

После вставки данных в таблицу PRICE нам нужно создать CSV-файл из нашей базы данных, поскольку доступ при использовании тестера стратегий затруднен. То есть наша функция Read(), содержащая SQL, используемый для чтения базы данных, может прекрасно работать в MetaEditor, как показано на рисунке ниже:

m_ed_sql

Кроме того, если мы прикрепим скрипт sql_read (полный исходный код приведен ниже) к любому графику с аналогичными входными данными времени и выполним запрос к базе данных USD, мы получим тот же результат, что означает, что проблем с базой данных в среде MetaEditor IDE или в среде терминала MetaTrader 5 нет. Взгляните на сообщение на вкладке "Эксперты":

t_lg_sql

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


Экспорт данных экономического календаря

Для экспорта данных в CSV мы используем одну из встроенных функций DatabaseExport(), как показано в коде ниже:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{

...

//--- save the PRICES table to a CSV file
   string _csv_file = "PRICES.csv";
   if(DatabaseExport(_db_handle, "SELECT * FROM PRICES", _csv_file,
                     DATABASE_EXPORT_HEADER | DATABASE_EXPORT_INDEX | DATABASE_EXPORT_QUOTED_STRINGS, ";"))
      Print("Database: table PRICES saved in ", _csv_file);
   else
      Print("Database: DatabaseExport(\"SELECT * FROM PRICES\") failed with code", GetLastError());
//--- close the database file and inform of that
   DatabaseClose(_db_handle);
   PrintFormat("Database: %s created and closed", _db_file);
}


Этот подход является наименее интенсивным по коду. Если бы мы, например, сначала выбрали данные в объект (например, массив), а затем перебрали все значения массива и сохранили их в разделенной запятыми строке, которую затем экспортировали, это дало бы тот же результат, но я почти уверен, что даже без учета излишнего кода, наш подход имеет гораздо меньшее время выполнения, чем подход с циклом for. Это может быть связано с тем, что SQLite — это библиотека на языке C, а MQL5 также во многом основан на C.

В нашей структуре таблицы базы данных для PRICES отсутствует явный первичный ключ, а эти ключи с большими наборами данных важны для создания индексов, которые делают базы данных быстрым и мощным инструментом. В качестве модификации этой таблицы можно либо добавить столбец с автоинкрементом, который будет служить первичным ключом, либо объединить столбцы EVENT и DATE, чтобы они оба стали первичными ключами, поскольку с точки зрения разработки объединенные значения в обоих столбцах будут уникальными для всех строк данных. Неоднозначность кодов, принятых для маркировки событий, хранящихся в столбце EVENT, требует особой осмотрительности, чтобы убедиться, что интересующая вас точка данных — это именно то, что вы действительно извлекаете.

Например, в этой статье мы сосредоточимся на паре GBPUSD, а это значит, что нас будут интересовать две валюты - GBP и USD. (Обратите внимание, что мы обошли стороной евро, учитывая многочисленные точки данных не только по еврозоне, но и по странам-членам!) Если мы посмотрим на коды событий для данных по инфляции для этих менее неоднозначных валют, для GBP у нас будет cpi-yy, а для USD — core-pce-price-index-yy. Имейте в виду, что существуют и другие годовые индексы потребительской инфляции для USD, которые мы не будем рассматривать, поэтому следует тщательно обдумать свой выбор. Кроме того, эта кодировка сама по себе не является стандартной, а это значит, что через несколько лет или даже раньше она может быть пересмотрена, и всем автоматизированным системам также потребуется обновить свой код. Это наталкивает на идею иметь собственное обозначение с функцией проверки информации на основе календарных данных, помогающей гарантировать, что нужные данные будут правильно закодированы, но, как уже упоминалось, время от времени потребуется проверка, поскольку кодировка может быть изменена в любой момент.


Класс сигналов MQL5

Как уже упоминалось, мы используем CSV-файлы и хотя процесс кажется относительно простым, форматирование ANSI и UTF8 может вызвать некоторые трудности при чтении данных, если не знать об их различиях. Мы применили стандартную функцию чтения CSV-файлов отсюда для чтения экспортированных CSV-данных, и они загружаются в функцию, которая инициализирует индикаторы для каждой валюты - маржи (GBP) и прибыли (USD). При этом обязательно будут ограничения на чтение больших CSV-файлов, поскольку они перегружают оперативную память. Потенциальным решением этой проблемы может быть разбиение CSV-файла по времени, так что при инициализации загружается только один из файлов, а когда его последняя точка данных становится слишком старой для текущего времени в тестере стратегий, этот файл "освобождается" и загружается более новый CSV-файл.

Все эти обходные пути решают проблемы, которых бы не было, если бы был возможен доступ к базе данных в тестере стратегий. Таким образом, наш класс сигнала будет просто принимать в качестве входных данных имена CSV-файлов как для валюты маржи, так и для валюты прибыли поскольку он не считывает данные из базы данных. В нашем классе сигнала единственным последовательным буфером, который мы будем использовать, будет класс m_time. Строго говоря, нам даже не нужен буфер, поскольку текущего времени достаточно, однако здесь он используется для получения времени по нулевому индексу. Извлечение значений данных календаря из загруженного CSV-файла выполняется функцией Read, код которой приведен ниже:

//+------------------------------------------------------------------+
//| Read Data
//+------------------------------------------------------------------+
double CSignalEconData::Read(datetime Time, SLine &Data[])
{  double _data = 0.0;
   int _rows = ArraySize(Data);
   _data = StringToDouble(Data[0].field[1]);
   for(int i = 0; i < _rows; i++)
   {  if(Time >= StringToTime(Data[i].field[0]))
      {  _data = StringToDouble(Data[i].field[1]);
      }
      else if(Time < StringToTime(Data[i].field[0]))
      {  break;
      }
   }
   return(_data);
}

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

Функция read вызывается как для валюты маржи, так и для валюты прибыли и возвращает последние показатели инфляции. Наш сигнал просто генерируется на основе относительного размера этих показателей. Если валюта маржи имеет более высокий уровень инфляции, чем валюта прибыли, то мы будем продавать пару. Если же уровень инфляции валюты маржи ниже, мы будем ее покупать. Эта логика показана ниже как часть функции LongCondition():

//+------------------------------------------------------------------+
//| "Voting" that price will grow.                                   |
//+------------------------------------------------------------------+
int CSignalEconData::LongCondition(void)
{  int result = 0;
   m_time.Refresh(-1);
   double _m = Read(m_time.GetData(0), m_margin_data);
   double _p = Read(m_time.GetData(0), m_profit_data);
   if(_m < _p)
   {  result = int(100.0 * ((_p - _m) / _p));
   }
   return(result);
}


Если после сборки с помощью Мастера и компиляции мы запустим советник без оптимизации, используя самые первые настройки по умолчанию, то мы получим следующие результаты:

s1


r1


c1

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


Заключение

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


Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/14993

Прикрепленные файлы |
ed_r1.mq5 (7.01 KB)
db_calendar_r1.mq5 (8.06 KB)
SignalWZ_21_r1.mqh (10.79 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Stanislav Korotky
Stanislav Korotky | 18 окт. 2024 в 19:57

Кэширование встроенного экономического календаря для тестирования и оптимизации советников было описано в книге по алготрейдингу.

Ваша реализация чтения событий (CSignalEconData::Read) неэффективна и вряд ли целесообразна.

PS. Для работы с SQLite из тестера необходимо создать/поместить базу данных в общую папку и открыть ее с флагом DATABASE_OPEN_COMMON.

Aleksey Vyazmikin
Aleksey Vyazmikin | 19 окт. 2024 в 20:34
Stanislav Korotky #:

Ваша реализация чтения событий (CSignalEconData::Read) неэффективна и вряд ли целесообразна.

Что скрывается под этой фразой? Как измеряется эффективность?

Stanislav Korotky
Stanislav Korotky | 20 окт. 2024 в 00:11
Aleksey Vyazmikin #:

Что скрывается под этой фразой? Как измеряется эффективность?

Поиск конкретного времени даты реализован через прямой цикл по всему массиву событий при каждом вызове, что загружает процессор в геометрической прогрессии. Кроме того, на каждой итерации вызываются StringToTime и StringToDouble.

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

Aleksey Vyazmikin
Aleksey Vyazmikin | 20 окт. 2024 в 02:49
Stanislav Korotky #:

Поиск конкретного времени даты реализован через прямой цикл по всему массиву событий при каждом вызове, что загружает процессор в геометрической прогрессии. Кроме того, на каждой итерации вызываются StringToTime и StringToDouble.

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

Спасибо за пояснения.

Разработка системы репликации (Часть 49): Все усложняется (I) Разработка системы репликации (Часть 49): Все усложняется (I)
В этой статье мы немного усложним ситуацию. Используя то, что было показано в предыдущих статьях, мы начнем открывать доступ к файлу шаблона, чтобы пользователь мог использовать свой собственный шаблон. Однако я буду вносить изменения постепенно, так как также буду дорабатывать индикатор, чтобы снизить нагрузку на MetaTrader 5.
Нейронная сеть на практике: Первый нейрон Нейронная сеть на практике: Первый нейрон
В этой статье мы начнем создавать нечто простое и скромное: нейрон. Мы запрограммируем его с помощью очень небольшого кода на MQL5. Нейрон прекрасно работал в тех тестах, которые я проводил. Вернемся немного назад в этой серии статей о нейронных сетях, чтобы понять, о чем я говорю.
Нейросети в трейдинге: Контрастный Трансформер паттернов Нейросети в трейдинге: Контрастный Трансформер паттернов
Контрастный Transformer паттернов осуществляет анализ рыночных ситуаций, как на уровне отдельных свечей, так и целых паттернов. Что способствует повышению качества моделирования рыночных тенденций. А применение контрастного обучения для согласования представлений свечей и паттернов ведет к саморегуляции и повышению точности прогнозов.
Нейросети в трейдинге: Анализ рыночной ситуации с использованием Трансформера паттернов Нейросети в трейдинге: Анализ рыночной ситуации с использованием Трансформера паттернов
В анализе рыночной ситуации нашими моделями ключевым элементом является свеча. Тем не менее давно известно, что свечные паттерны могут помочь в прогнозировании будущих ценовых движений. И в этой статье мы познакомимся с методом, который позволяет интегрировать оба этих подхода.