English Español Deutsch 日本語
preview
Упрощаем торговлю на новостях (Часть 1): Создаем базу данных

Упрощаем торговлю на новостях (Часть 1): Создаем базу данных

MetaTrader 5Примеры | 14 августа 2024, 13:50
175 1
Kabelo Frans Mampa
Kabelo Frans Mampa

Введение

В этой статье мы научимся создавать базу данных, в которой будем хранить данные из экономического календаря MQL5. Эти данные будут использоваться в будущих статьях для торговли новостями. Мы также рассмотрим, как выполнять базовые SQL-запросы для извлечения определенной структурированной информации из базы данных. Вся работа будет выполняться в среде MQL5 IDE.

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


Для чего нужна база данных?

Пример таблицы базы данных


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

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

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


Валюты в экономическом календаре MQL5

Нет Символ Имя
 1. NZD Новозеландский доллар
 2. EUR Евро
 3. JPY Японская иена
 4. CAD Канадский доллар
 5. AUD Австралийский доллар
 6. CNY Китайский юань
 7. SGD Сингапурский доллар
 8. BRL Бразильский реал
 9. MXN Мексиканское песо
 10. ZAR Южноафриканский рэнд
 11. HKD Гонконгский доллар
 12. INR Индийская рупия
 13. NOK Норвежская крона
 14. USD Доллар США
 15. GBP Фунт стерлингов
 16. CHF Швейцарский франк
 17. KRW Южнокорейская вона
 18. SEK Шведская крона 

Таблица выше не имеет определенного порядка.

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


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

Переход на летнее и зимнее время

Прежде чем создавать базу данных, нам необходимо учесть переход на летнее время и то, как это повлияет на наше тестирование на истории. Переход на летнее время (Daylight saving time, DST) - практика перевода часов на один час вперед в теплые месяцы года, как правило, с весны до осени, для более эффективного использования дневного света и экономии энергии.

В регионах, где действует летнее время, местное время может меняться на один час в периоды весеннего и осеннего перехода. Это влияет на время открытия и закрытия финансовых рынков. Например, если начнется летнее время и часы будут переведены на один час вперед, рынок может открыться раньше по местному времени. Напротив, когда заканчивается летнее время и часы переводятся на час назад (по обычному времени), время открытия рынка может отображаться позже по местному времени. В разных регионах мира могут быть свои собственные правила перехода на летнее время в зависимости от времени года.

Например, в США есть собственное летнее время, которое обычно начинается во второе воскресенье марта каждого года и заканчивается в первое воскресенье ноября того же года. Европейские страны также имеют собственное летнее время, которое обычно начинается в последнее воскресенье марта каждого года и заканчивается в последнее воскресенье октября того же года. Австралия — еще одна страна, где практикуется летнее время. Оно обычно начинается в первое воскресенье октября и заканчивается в первое воскресенье апреля следующего года. 

Для учета корректировок, связанных с переходом на летнее время, некоторые биржи и финансовые рынки могут менять свои графики торгов или часовой пояс. Биржи публикуют скорректированные часы торговли с учетом изменений местного времени или меняют свой часовой пояс в соответствии с применяемым ими графиком перехода на летнее время, гарантируя участникам рынка единообразие и ясность.

По моим наблюдениям, если брокер, например, применяет летнее время в США, то часовой пояс до летнего времени будет GMT+2, а когда начнется летнее время в США, часовой пояс изменится на GMT+3. Вы не заметите разницы во времени между выпусками новостей США на сервере брокера до или во время летнего времени в США, поскольку они будут синхронизированы друг с другом. В качестве примера рассмотрим событие NFP (Non Farm Payrolls, количество рабочих мест в несельскохозяйственном секторе) в США. Допустим, NFP публикуется в 14:00 на сервере брокера до перехода на летнее время в США. Время останется прежним — 14:00 во время летнего времени в США, хотя часовой пояс изменится на GMT+3.


Объявление об изменении времени торговых сессий в летнее время в Великобритании


С другой стороны, предположим, что новости о занятости в Великобритании запланированы к выходу на 8 утра до начала летнего времени в Великобритании и США. Брокер меняет часовой пояс при начале летнего времени в США. При этом новости выходят до начала летнего времени в Великобритании (ЕС). В этом случае выход новостей о занятости в Великобритании происходит в 7 утра по времени брокера. Затем в Великобритании начинается летнее время. Оно совпадет с летним временем в США. Соответственно, новости о занятости в Великобритании снова начинают выходить в 8 утра. 

Всё это звучит довольно запутанно. Однако мы внедрим все эти изменения в нашу базу данных, чтобы иметь точные даты событий для последующего тестирования. Нам нужно будет определить, использует ли брокер DST в США, Великобритании (ЕС), Австралии или не использует его вообще. Мы также создадим таблицы экономического календаря для каждого типа летнего времени, чтобы иметь возможность изменять расписание для тестирования на истории в образовательных целях. 


Реализация

У нас будет три класса по переходу на летнее время:

  • DaylightSavings_AU
  • DaylightSavings_UK
  • DaylightSavings_US
В этой статье я буду работать только с одним из них, поскольку они очень похожи друг на друга. Все они будут включены в файлы проекта.

class CDaylightSavings_AU:CObject
  {

private:

   CTimeManagement   Time;
                     CDaylightSavings_AU(datetime startdate,datetime enddate);
   CObject           *List() { return savings;}//Gets the list of Daylight savings time
   datetime          StartDate;
   datetime          EndDate;
   CArrayObj         *savings;
   CArrayObj         *getSavings;
   CDaylightSavings_AU      *dayLight;

public:

                     CDaylightSavings_AU(void);
                    ~CDaylightSavings_AU(void);
   bool              isDaylightSavings(datetime Date);//This function checks if a given date falls within Daylight Savings Time.
   bool              DaylightSavings(int Year,datetime &startDate,datetime &endDate);//Check if Daylight Savings Dates are available for a certain Year
   void              adjustDaylightSavings(datetime EventDate,string &AdjustedDate);//Will adjust the date's time zone depending on Daylight Savings
  };


Создадим класс:

CDaylightSavings_AU

наследующий

CObject

Он будет создавать список, в котором будут храниться все даты перехода на летнее время с 2007 года, поскольку это самая ранняя дата, сохраненная в Экономический календарь MQL5

CDaylightSavings_AU::CDaylightSavings_AU(void)
  {
   savings = new CArrayObj();
//Daylight savings dates to readjust dates in the database for accurate testing in the strategy tester
   savings.Add(new CDaylightSavings_AU(D'2006.10.29 03:00:00',D'2007.03.25 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2007.10.28 03:00:00',D'2008.04.06 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2008.10.05 03:00:00',D'2009.04.05 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2009.10.04 03:00:00',D'2010.04.04 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2010.10.03 03:00:00',D'2011.04.03 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2011.10.02 03:00:00',D'2012.04.01 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2012.10.07 03:00:00',D'2013.04.07 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2013.10.06 03:00:00',D'2014.04.06 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2014.10.05 03:00:00',D'2015.04.05 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2015.10.04 03:00:00',D'2016.04.03 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2016.10.02 03:00:00',D'2017.04.02 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2017.10.01 03:00:00',D'2018.04.01 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2018.10.07 03:00:00',D'2019.04.07 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2019.10.06 03:00:00',D'2020.04.05 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2020.10.04 03:00:00',D'2021.04.04 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2021.10.03 03:00:00',D'2022.04.03 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2022.10.02 03:00:00',D'2023.04.02 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2023.10.01 03:00:00',D'2024.04.07 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2024.10.06 03:00:00',D'2025.04.06 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2025.10.05 03:00:00',D'2026.04.05 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2026.10.04 03:00:00',D'2027.04.04 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2027.10.03 03:00:00',D'2028.04.02 02:00:00'));
   savings.Add(new CDaylightSavings_AU(D'2028.10.01 03:00:00',D'2029.04.01 02:00:00'));
  }

Затем используем булеву функцию

bool              isDaylightSavings(datetime Date);//This function checks if a given date falls within Daylight Savings Time.

Чтобы определить, находится ли параметр даты в пределах дат летнего времени

bool CDaylightSavings_AU::isDaylightSavings(datetime Date)
  {
// Initialize a list to store daylight savings periods.
   getSavings = List();
// Iterate through all the periods in the list.
   for(int i=0; i<getSavings.Total(); i++)
     {
      // Access the current daylight savings period.
      dayLight = getSavings.At(i);
      // Check if the given date is within the current daylight savings period.
      if(Time.DateIsInRange(dayLight.StartDate,dayLight.EndDate,Date))
        {
         // If yes, return true indicating it is daylight savings time.
         return true;
        }
     }
// If no period matches, return false indicating it is not daylight savings time.
   return false;
  }

мы также будем использовать еще одну булеву функцию

bool              DaylightSavings(int Year,datetime &startDate,datetime &endDate);//Adjusts time when it is daylight savings an hour behind if outside

Чтобы определить, находится ли год (Year) в списке дат летнего времени, который мы инициализировали ранее, добавим начальную и конечную даты к переменным startDate и endDate.

bool CDaylightSavings_US::DaylightSavings(int Year,datetime &startDate,datetime &endDate)
  {
  // Initialize a list to store daylight savings periods.
   getSavings = List();
   bool startDateDetected=false,endDateDetected=false;
// Iterate through all the periods in the list.
   for(int i=0; i<getSavings.Total(); i++)
     {
      dayLight = getSavings.At(i);
      if(Year==Time.ReturnYear(dayLight.StartDate))//Check if a certain year's date is available within the Daylight Savings start dates in the List
        {
         startDate = dayLight.StartDate;
         startDateDetected = true;
        }
      if(Year==Time.ReturnYear(dayLight.EndDate))//Check if a certain year's date is available within the Daylight Savings end dates in the List
        {
         endDate = dayLight.EndDate;
         endDateDetected = true;
        }
      if(startDateDetected&&endDateDetected)//Check if both Daylight Savings start and end dates are found for a certain Year
        {
         return true;
        }
     }

   startDate = D'1970.01.01 00:00:00';//Set a default start date if no Daylight Saving date is found
   endDate = D'1970.01.01 00:00:00';//Set a default end date if no Daylight Saving date is found
   return false;
  }

Наша последняя публичная функция в этом классе

void              adjustDaylightSavings(datetime EventDate,string &AdjustedDate);//Will adjust the date's time -zone depending on Daylight Savings

изменит строковую переменную AdjustedDate по ссылке с EventDate

void CDaylightSavings_AU::adjustDaylightSavings(datetime EventDate,string &AdjustedDate)
  {
   if(isDaylightSavings(TimeTradeServer()))//Check if the current trade server time is already within the Daylight Savings Period
     {
      if(isDaylightSavings(EventDate))//Checks if the event time is during daylight savings
        {
         AdjustedDate = TimeToString(EventDate);//storing normal event time
        }
      else
        {
         AdjustedDate = TimeToString((datetime)(EventDate-Time.HoursS()));//storing event time and removing an hour for DST
        }
     }
   else
     {
      if(isDaylightSavings(EventDate))//Checks if the event time is during daylight savings
        {
         AdjustedDate = TimeToString((datetime)(Time.HoursS()+EventDate));//storing event time and adding an hour for DST
        }
      else
        {
         AdjustedDate = TimeToString(EventDate);//storing normal event time
        }
     }
  }


Теперь перейдем к следующему заголовочному файлу в этом проекте, посвященному манипуляциям со временем

CTimeManagement

который является классом в этом же проекте

class CTimeManagement
  {

private:

   MqlDateTime       today;//private variable
   MqlDateTime       timeFormat;//private variable

public:

   bool              DateIsInRange(datetime FirstTime,datetime SecondTime,datetime compareTime);//Checks if a date is within two other dates
   bool              DateIsInRange(datetime Start,datetime End,datetime CompareStart,datetime CompareEnd);//Check if two dates(Start&End) are within CompareStart & CompareEnd
   bool              DateisToday(datetime TimeRepresented);//Checks if a date is within the current day
   int               SecondsS(int multiple=1);//Returns seconds
   int               MinutesS(int multiple=1);//Returns Minutes in seconds
   int               HoursS(int multiple=1);//Returns Hours in seconds
   int               DaysS(int multiple=1);//Returns Days in seconds
   int               WeeksS(int multiple=1);//Returns Weeks in seconds
   int               MonthsS(int multiple=1);//Returns Months in seconds
   int               YearsS(int multiple=1);//Returns Years in seconds
   int               ReturnYear(datetime time);//Returns the Year for a specific date
   datetime          TimeMinusOffset(datetime standardtime,int timeoffset);//Will return a datetime type of a date with an subtraction offset in seconds
   datetime          TimePlusOffset(datetime standardtime,int timeoffset);//Will return a datetime type of a date with an addition offset in seconds
  };


Ранее вызванная функция 

Time.DateIsInRange(dayLight.StartDate,dayLight.EndDate,Date)

является простой функцией, которая проверяет, находится ли одна дата между двумя другими датами

bool CTimeManagement::DateIsInRange(datetime FirstTime,datetime SecondTime,datetime compareTime)
  {
   if(FirstTime<=compareTime&&SecondTime>compareTime)
     {
      return true;
     }
   return false;
  }

Одно и то же имя функции с разными параметрами

bool		DateIsInRange(datetime Start,datetime End,datetime CompareStart,datetime CompareEnd);

Функция проверяет, находятся ли две даты между двумя другими датами

bool CTimeManagement::DateIsInRange(datetime Start,datetime End,datetime CompareStart,datetime CompareEnd)
  {
   if(Start<=CompareStart&&CompareEnd<End)
     {
      return true;
     }
   return false;
  }


Нам понадобится функция, которая будет знать, наступила ли сегодня определенная дата, поэтому я и объявил

bool              DateisToday(datetime TimeRepresented);//Checks if a date is within the current day

который будет сравнивать год, месяц и день текущего времени и datetime TimeRepresented

bool CTimeManagement::DateisToday(datetime TimeRepresented)
  {
   MqlDateTime TiM;
   TimeToStruct(TimeRepresented,TiM);
   TimeCurrent(today);
   if(TiM.year==today.year&&TiM.mon==today.mon&&TiM.day==today.day)
     {
      return true;
     }
   return false;
  }


Чтобы получить дату и время со смещением, у нас есть две функции

datetime          TimeMinusOffset(datetime standardtime,int timeoffset);//Will return a datetime type of a date with an subtraction offset in seconds

и

datetime          TimePlusOffset(datetime standardtime,int timeoffset);//Will return a datetime type of a date with an addition offset in seconds


когда мы хотим получить дату с отрицательным смещением в час (или любую другую единицу времени), мы будем использовать

datetime CTimeManagement::TimeMinusOffset(datetime standardtime,int timeoffset)
  {
   standardtime-=timeoffset;
   return standardtime;
  }

С другой стороны, если нам нужна дата с положительным смещением, мы будем использовать

datetime CTimeManagement::TimePlusOffset(datetime standardtime,int timeoffset)
  {
   standardtime+=timeoffset;
   return standardtime;
  }



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

Этот включаемый файл называется CommonVariables и будет хранить общие переменные

string broker=AccountInfoString(ACCOUNT_COMPANY);//Getting brokers name via AccountInfoString
int Str = StringReplace(broker," ","");//Removing or replacing any spaces in the broker's name with an empty string
int Str1 = StringReplace(broker,".","");//Removing or replacing any dots in the broker's name with an empty string
int Str2 = StringReplace(broker,",","");//Removing or replacing any commas in the broker's name with an empty string
#define BROKER_NAME                    broker//Broker's Name
#define NEWS_TRADING_FOLDER            "NewsTrading"//Name of main folder in common/files
#define NEWS_CALENDAR_FOLDER           StringFormat("%s\\NewsCalendar",NEWS_TRADING_FOLDER)//name of subfolder in NewsTrading
#define NEWS_CALENDAR_BROKER_FOLDER    StringFormat("%s\\%s",NEWS_CALENDAR_FOLDER,BROKER_NAME)//Name of subfolder in NewsCalendar
#define NEWS_DATABASE_FILE             StringFormat("%s\\Calendar.sqlite",NEWS_CALENDAR_BROKER_FOLDER)//Name of sqlite file in subfolder in "Broker's Name"
#define NEWS_TEXT_FILE                 StringFormat("%s\\CalendarOpen.txt",NEWS_CALENDAR_BROKER_FOLDER)//Name of text file to indicate Calendar is open.

struct Calendar
  {
   ulong             EventId;//Event Id
   string            CountryName;//Event Country
   string            EventName;//Event Name
   string            EventType;//Event Type
   string            EventImportance;//Event Importance
   string            EventDate;//Event Date
   string            EventCurrency;//Event Currency
   string            EventCode;//Event Code
   string            EventSector;//Event Sector
   string            EventForecast;//Event Forecast Value
   string            EventPreval;//Event Previous Value
   string            EventImpact;//Event Impact
   string            EventFrequency;//Event Frequency
  };

enum DST_type
  {
   US_DST,//US Daylight Savings
   UK_DST,//UK(EU) Daylight Savings
   AU_DST,//AU Daylight Savings
   DST_NONE//No Daylight Savings Available
  };



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

Итак, я создал класс

class CFolders

что создаст папки в папке Common files в последовательности NewsTrading/NewsCalendar/Broker. Эту задачу мы поручим выполнить конструктору класса.

class CFolders
  {
private:
   bool              CreateFolder(string FolderPath);//Will create a folder with the FolderPath string parameter

public:
                     CFolders(void);//Class's constructor
  };

в конструкторе

CFolders::CFolders(void)
  {
   if(CreateFolder(NEWS_TRADING_FOLDER))//Will create the NewsTrading Folder
     {
      if(CreateFolder(NEWS_CALENDAR_FOLDER))//Will create the NewsCalendar Folder
        {
         if(!CreateFolder(NEWS_CALENDAR_BROKER_FOLDER))//Will create the Broker Folder
           {
            Print("Something went wrong with creating folder: ",NEWS_CALENDAR_BROKER_FOLDER);
           }
        }
     }
  }

Функция, вызываемая в конструкторе для создания отдельных папок

bool CFolders::CreateFolder(string FolderPath)
  {
//--- attempt to create a folder relative to the MQL5\Files path
   if(FolderCreate(FolderPath,FILE_COMMON))
     {
      //--- successful execution
      return true;
     }
   else
     {
      PrintFormat("Failed to create the folder %s. Error code %d",FolderPath,GetLastError());
     }
//--- execution failed
   return false;
  }



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

class CCandleProperties

Теперь мы объявим функции в этом классе.

class CCandleProperties
  {
private:
   CTimeManagement   Time;

public:
   double            Open(int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,string SYMBOL=NULL);//Retrieve Candle OpenPrice
   double            Close(int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,string SYMBOL=NULL);//Retrieve Candle ClosePrice
   double            High(int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,string SYMBOL=NULL);//Retrieve Candle HighPrice
   double            Low(int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,string SYMBOL=NULL);//Retrieve Candle LowPrice
   bool              IsLargerThanPreviousAndNext(datetime CandleTime,int Offset,string SYMBOL);//Determine if one candle is larger than two others
  };

Мы кратко рассмотрим

double            Open(int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,string SYMBOL=NULL);//Retrieve Candle Open Price

В этой функции мы вернем цену открытия свечи на основе целочисленного параметра CandleIndex и таймфрейма свечи, а также символа свечи.

double CCandleProperties::Open(int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,string SYMBOL=NULL)
  {
   return iOpen(((SYMBOL==NULL)?Symbol():SYMBOL),Period,CandleIndex);//return candle open price
  }

Чтобы проверить, произошло ли экономическое событие в конкретную дату, поскольку брокеры могут менять часовые пояса. Мы получим дату экономического события и с этой датой посмотрим на свечу M15 за это время, затем рассчитаем высоту свечи (iHigh-iLow) и сравним эту высоту со свечой M15 за час до события и через час после. Поскольку экономические события обычно приводят к волатильности рыночных цен, мы должны заметить очень длинную свечу, когда событие действительно произойдет. Мы будем использовать свечу M15, чтобы дать рынку достаточно времени для формирования этой длинной свечи. Если брокер изменил свой часовой пояс, дата события, сохраненная в экономическом календаре MQL5, может не совпадать с волатильностью свечи в определенное время на свече брокера, но эта волатильность может присутствовать и на час позже или на час раньше указанной даты. Функция, которую мы будем использовать для этой проверки, будет следующей:

bool              IsLargerThanPreviousAndNext(datetime CandleTime,int Offset,string SYMBOL);//Determine if one candle is larger than two others

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

bool CCandleProperties::IsLargerThanPreviousAndNext(datetime CandleTime,int Offset,string SYMBOL)
  {
   int CandleIndex = iBarShift(SYMBOL,PERIOD_M15,CandleTime);//Assign candle index of candletime
   int CandleIndexMinusOffset = iBarShift(SYMBOL,PERIOD_M15,Time.TimeMinusOffset(CandleTime,Offset));//Assign candle index of candletime minus time offset 
   int CandleIndexPlusOffset = iBarShift(SYMBOL,PERIOD_M15,Time.TimePlusOffset(CandleTime,Offset));//Assign candle index of candletime plus time offset
   double CandleHeight = High(CandleIndex,PERIOD_M15,SYMBOL)-Low(CandleIndex,PERIOD_M15,SYMBOL);//Assign height of M15 candletime in pips
   double CandleHeightMinusOffset = High(CandleIndexMinusOffset,PERIOD_M15,SYMBOL)-Low(CandleIndexMinusOffset,PERIOD_M15,SYMBOL);//Assign height of M15 candletime  minus offset in Pips
   double CandleHeightPlusOffset = High(CandleIndexPlusOffset,PERIOD_M15,SYMBOL)-Low(CandleIndexPlusOffset,PERIOD_M15,SYMBOL);//Assign height of M15 candletime plus offset in Pips
   //--Determine if candletime height is greater than candletime height minus offset and candletime height plus offset
   if(CandleHeight>CandleHeightMinusOffset&&CandleHeight>CandleHeightPlusOffset)
     {
      return true;//Candletime is likely when the news event occurred
     }
   return false;//Candletime is unlikely when the real news data was released
  }



Перейдем к классу новостей. Он будет получать значения напрямую из экономического календаря MQL5 и хранить

значения в базе данных для тестирования и, в конечном итоге, торговли. 

class CNews
  {
   //Private Declarations Only accessible by this class/header file
private:
   CTimeManagement   Time;//TimeManagement Object declaration
   CDaylightSavings_UK  Savings_UK;//DaylightSavings Object for the UK and EU
   CDaylightSavings_US  Savings_US;//DaylightSavings Object for the US
   CDaylightSavings_AU  Savings_AU;//DaylightSavings Object for the AU
   CCandleProperties Candle;//CandleProperties Object
   string            CurrencyBase,CurrencyProfit,EURUSD;//String variables declarations for working with EURUSD
   bool              EurusdIsSelected,EurusdIsFound,is_Custom;//Boolean variables declarations for working with EURUSD
   bool              timeIsShifted;//Boolean variable declaration will be used to determine if the broker changes it's time zone
   datetime          DaylightStart,DaylightEnd;//Datetime variables declarations for start and end dates for Daylight Savings
   //Structure Declaration for DST
   struct DST
     {
      bool           result;
      datetime       date;
     };
   bool              AutoDetectDST(DST_type &dstType);//Function will determine Broker DST
   DST_type          DSTType;//variable of DST_type enumeration declared in the CommonVariables class/header file
   bool              InsertIntoTable(int db,DST_type Type,Calendar &Evalues[]);//Function for inserting Economic Data in to a database's table
   void              CreateAutoDST(int db);//Function for creating and inserting Recommend DST for the Broker into a table
   bool              CreateTable(int db,string tableName,bool &tableExists);//Function for creating a table in a database
   void              CreateRecords(int db);//Creates a table to store records of when last the Calendar database was updated/created
   bool              UpdateRecords();//Checks if the main Calendar database needs an update or not
   void              EconomicDetails(Calendar &NewsTime[]);//Gets values from the MQL5 economic Calendar

   //Public declarations accessable via a class's Object
public:
                    ~CNews(void);//Deletes a text file created when the Calendar database is being worked on
   void              CreateEconomicDatabase();//Creates the Calendar database for a specific Broker
   datetime          GetLastestNewsDate();//Gets the latest/newest date in the Calendar database
  };


Здесь есть много того, что нужно разобрать. Сначала рассмотрим 

bool              AutoDetectDST(DST_type &dstType);//Function will determine Broker DST

Как следует из названия функции, ее основная цель — получить расписание DST, которое использует брокер. Мы получим эту информацию через перечисление

enum DST_type
  {
   US_DST,//US Daylight Savings
   UK_DST,//UK(EU) Daylight Savings
   AU_DST,//AU Daylight Savings
   DST_NONE//No Daylight Savings Available
  };

переданная по ссылке в функции AutoDectectDST. Функция вернет false, если расписание перехода на летнее время брокера неизвестно или не может быть распознано.

bool CNews::AutoDetectDST(DST_type &dstType)
  {
   MqlCalendarValue values[];//Single array of MqlCalendarValue type
   string eventtime[];//Single string array variable to store NFP(Nonfarm Payrolls) dates for the 'United States' from the previous year
   int lastyear = Time.ReturnYear(Time.TimeMinusOffset(iTime(Symbol(),PERIOD_CURRENT,0),Time.YearsS()));//Will store the previous year into an integer
   datetime lastyearstart = StringToTime(StringFormat("%s.01.01 00:00:00",(string)lastyear));//Will store the start date for the previous year
   datetime lastyearend = StringToTime(StringFormat("%s.12.31 23:59:59",(string)lastyear));//Will store the end date for the previous year

   if(CalendarValueHistory(values,lastyearstart,lastyearend,"US"))//Getting last year's calendar values for CountryCode = 'US'
     {
      for(int x=0; x<(int)ArraySize(values); x++)
        {
         if(values[x].event_id==840030016)//Get only NFP Event Dates
           {
            ArrayResize(eventtime,eventtime.Size()+1,eventtime.Size()+2);//Increasing the size of eventtime array by 1
            eventtime[eventtime.Size()-1] = TimeToString(values[x].time);//Storing the dates in an array of type string
           }
        }
     }

   datetime ShiftStart=D'1970.01.01 00:00:00',ShiftEnd=D'1970.01.01 00:00:00';//datetime variables to store the broker's time zone shift(change)
   DST previousresult,currentresult;//Variables of structure type DST declared at the beginning of the class

   EURUSD="";//String variable assigned empty string
   EurusdIsSelected = false;//Boolean variable assigned value false
   EurusdIsFound = false;//Boolean variable assigned value false

   for(int i=0;i<SymbolsTotal(true);i++)//Will loop through all the Symbols inside the Market Watch
     {
      string SymName = SymbolName(i,true);//Assign the Symbol Name of index 'i' from the list of Symbols inside the Market Watch
      CurrencyBase = SymbolInfoString(SymName,SYMBOL_CURRENCY_BASE);//Assign the Symbol's Currency Base
      CurrencyProfit = SymbolInfoString(SymName,SYMBOL_CURRENCY_PROFIT);//Assign the Symbol's Currency Profit
      SymbolExist(SymName,is_Custom);//Get the boolean value into 'is_Custom' for whether the Symbol Name is a Custom Symbol(Is not from the broker)

      //-- Check if the Symbol outside the Market Watch has a SYMBOL_CURRENCY_BASE of EUR
      //-- and a SYMBOL_CURRENCY_PROFIT of USD, and this Symbol is not a Custom Symbol(Is not from the broker)
      if(CurrencyBase=="EUR"&&CurrencyProfit=="USD"&&!is_Custom)
        {
         EURUSD = SymName;//Assigning the name of the EURUSD Symbol found inside the Market Watch
         EurusdIsFound = true;//EURUSD Symbol was found in the Trading Terminal for your Broker
         break;//Will end the for loop
        }
     }

   if(!EurusdIsFound)//Check if EURUSD Symbol was already Found in the Market Watch
     {
      for(int i=0; i<SymbolsTotal(false); i++)//Will loop through all the available Symbols outside the Market Watch
        {
         string SymName = SymbolName(i,false);//Assign the Symbol Name of index 'i' from the list of Symbols outside the Market Watch
         CurrencyBase = SymbolInfoString(SymName,SYMBOL_CURRENCY_BASE);
         CurrencyProfit = SymbolInfoString(SymName,SYMBOL_CURRENCY_PROFIT);
         SymbolExist(SymName,is_Custom);//Get the boolean value into 'is_Custom' for whether the Symbol Name is a Custom Symbol(Is not from the broker)

         //-- Check if the Symbol outside the Market Watch has a SYMBOL_CURRENCY_BASE of EUR
         //-- and a SYMBOL_CURRENCY_PROFIT of USD, and this Symbol is not a Custom Symbol(Is not from the broker)
         if(CurrencyBase=="EUR"&&CurrencyProfit=="USD"&&!is_Custom)
           {
            EurusdIsSelected = SymbolSelect(SymName,true);//Adding the EURUSD Symbol to the Market Watch
            if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
              {
               EURUSD = SymName;//Assigning the name of the EURUSD Symbol found outside the Market Watch
               EurusdIsFound = true;//EURUSD Symbol was found in the Trading Terminal for your Broker
               break;//Will end the for loop
              }
           }
        }
     }

   if(!EurusdIsFound)//Check if EURUSD Symbol was Found in the Trading Terminal for your Broker
     {
      Print("Cannot Find EURUSD!");
      Print("Cannot Create Database!");
      Print("Server DST Cannot be Detected!");
      dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
      return false;//Returning False, Broker's DST schedule was not found
     }

   for(uint i=0;i<eventtime.Size();i++)
     {
      currentresult.result = Candle.IsLargerThanPreviousAndNext((datetime)eventtime[i],Time.HoursS(),EURUSD);//Store the result of if the event date is the larger candlestick
      currentresult.date = (datetime)eventtime[i];//Store the eventdate from eventtime[i]
      timeIsShifted = ((currentresult.result!=previousresult.result&&i>0)?true:false);//Check if there is a difference between the previous result and the current result

      //--- Print Event Dates and if the event date's candle is larger than the previous candle an hour ago and the next candle an hour ahead
      Print("Date: ",eventtime[i]," is Larger: ",Candle.IsLargerThanPreviousAndNext((datetime)eventtime[i],Time.HoursS(),EURUSD)," Shifted: ",timeIsShifted);

      if(timeIsShifted)//Check if the Larger candle has shifted from the previous event date to the current event date in eventtime[i] array
        {
         if(ShiftStart==D'1970.01.01 00:00:00')//Check if the ShiftStart variable has not been assigned a relevant value yet
           {
            ShiftStart=currentresult.date;//Store the event date for when the time shift began
           }
         ShiftEnd=previousresult.date;//Store the event date timeshift
        }
      previousresult.result = currentresult.result;//Store the previous result of if the event date is the larger candlestick
      previousresult.date = currentresult.date;//Store the event date from eventtime[i]
     }

   if(ShiftStart==D'1970.01.01 00:00:00'&&eventtime.Size()>0)//Check if the ShiftStart variable has not been assigned a relevant value and the event dates are more than zero
     {
      Print("Broker ServerTime unchanged!");
      dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
      return true;//Returning True, Broker's DST schedule was found successfully
     }

   if(Savings_AU.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
     {
      if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
        {
         Print("Broker ServerTime Adjusted For AU DST");
         if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
           {
            SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
           }
         dstType = AU_DST;//Assigning enumeration value AU_DST, Broker has AU DST(Daylight Savings Time)
         return true;//Returning True, Broker's DST schedule was found successfully
        }
     }
   else
     {
      Print("Something went wrong!");
      Print("Cannot Find Daylight-Savings Date For AU");
      Print("Year: %d Cannot Be Found!",lastyear);
      if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
        {
         SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
        }
      dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
      return false;//Returning False, Broker's DST schedule was not found
     }

   if(Savings_UK.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
     {
      if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
        {
         Print("Broker ServerTime Adjusted For UK DST");
         if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
           {
            SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
           }
         dstType = UK_DST;//Assigning enumeration value UK_DST, Broker has UK/EU DST(Daylight Savings Time)
         return true;//Returning True, Broker's DST schedule was found successfully
        }
     }
   else
     {
      Print("Something went wrong!");
      Print("Cannot Find Daylight-Savings Date For UK");
      Print("Year: %d Cannot Be Found!",lastyear);
      if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
        {
         SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
        }
      dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
      return false;//Returning False, Broker's DST schedule was not found
     }

   if(Savings_US.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
     {
      if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
        {
         Print("Broker ServerTime Adjusted For US DST");
         if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
           {
            SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
           }
         dstType = US_DST;//Assigning enumeration value US_DST, Broker has US DST(Daylight Savings Time)
         return true;//Returning True, Broker's DST schedule was found successfully
        }
     }
   else
     {
      Print("Something went wrong!");
      Print("Cannot Find Daylight-Savings Date For US");
      Print("Year: %d Cannot Be Found!",lastyear);
      if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
        {
         SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
        }
      dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
      return false;//Returning False, Broker's DST schedule was not found
     }

   if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
     {
      SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
     }
   Print("Cannot Detect Broker ServerTime Configuration!");
   dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
   return false;//Returning False, Broker's DST schedule was not found
  }


Функция получилась длинной. Давайте разберем ее. В первой части мы собираем даты NFP предыдущего года. Поэтому, если текущий год — 2024, мы соберем все даты на 2023 год, поскольку нам необходимо проанализировать весь год, чтобы успешно получить вероятный график перехода брокера на летнее время. Эти даты будут сохранены в нашем едином строковом массиве eventtime. 

MqlCalendarValue values[];//Single array of MqlCalendarValue type
   string eventtime[];//Single string array variable to store NFP(Nonfarm Payrolls) dates for the 'United States' from the previous year
   int lastyear = Time.ReturnYear(Time.TimeMinusOffset(iTime(Symbol(),PERIOD_CURRENT,0),Time.YearsS()));//Will store the previous year into an integer
   datetime lastyearstart = StringToTime(StringFormat("%s.01.01 00:00:00",(string)lastyear));//Will store the start date for the previous year
   datetime lastyearend = StringToTime(StringFormat("%s.12.31 23:59:59",(string)lastyear));//Will store the end date for the previous year

   if(CalendarValueHistory(values,lastyearstart,lastyearend,"US"))//Getting last year's calendar values for CountryCode = 'US'
     {
      for(int x=0; x<(int)ArraySize(values); x++)
        {
         if(values[x].event_id==840030016)//Get only NFP Event Dates
           {
            ArrayResize(eventtime,eventtime.Size()+1,eventtime.Size()+2);//Increasing the size of event time array by 1
            eventtime[eventtime.Size()-1] = TimeToString(values[x].time);//Storing the dates in an array of type string
           }
        }
     }


Затем нам нужно будет использовать эти даты для символа EURUSD, поскольку XAUUSD (золото) и другие индексы, такие как US30 (Dow Jones), очень волатильны даже без новостей. EURUSD в основном стабилен и становится по-настоящему волатильным во время экономических событий, что упрощает определение того, когда произошло экономическое событие, поскольку рынок, скорее всего, сформирует ценовой выброс в связи с событием. По этой же причине мы сосредоточимся на событиях NFP, поскольку они регулярно вызывают скачки цен на рынке. Итак, после того, как мы получили эти данные, нам понадобится символ EURUSD. Сначала мы проверим все доступные символы у брокера, чтобы найти EURUSD и выбрать его. Если EURUSD не найден, вернем false. 

   EURUSD="";//String variable assigned empty string
   EurusdIsSelected = false;//Boolean variable assigned value false
   EurusdIsFound = false;//Boolean variable assigned value false

   for(int i=0;i<SymbolsTotal(true);i++)//Will loop through all the Symbols inside the Market Watch
     {
      string SymName = SymbolName(i,true);//Assign the Symbol Name of index 'i' from the list of Symbols inside the Market Watch
      CurrencyBase = SymbolInfoString(SymName,SYMBOL_CURRENCY_BASE);//Assign the Symbol's Currency Base
      CurrencyProfit = SymbolInfoString(SymName,SYMBOL_CURRENCY_PROFIT);//Assign the Symbol's Currency Profit
      SymbolExist(SymName,is_Custom);//Get the boolean value into 'is_Custom' for whether the Symbol Name is a Custom Symbol(Is not from the broker)

      //-- Check if the Symbol outside the Market Watch has a SYMBOL_CURRENCY_BASE of EUR
      //-- and a SYMBOL_CURRENCY_PROFIT of USD, and this Symbol is not a Custom Symbol(Is not from the broker)
      if(CurrencyBase=="EUR"&&CurrencyProfit=="USD"&&!is_Custom)
        {
         EURUSD = SymName;//Assigning the name of the EURUSD Symbol found inside the Market Watch
         EurusdIsFound = true;//EURUSD Symbol was found in the Trading Terminal for your Broker
         break;//Will end the for loop
        }
     }

   if(!EurusdIsFound)//Check if EURUSD Symbol was already Found in the Market Watch
     {
      for(int i=0; i<SymbolsTotal(false); i++)//Will loop through all the available Symbols outside the Market Watch
        {
         string SymName = SymbolName(i,false);//Assign the Symbol Name of index 'i' from the list of Symbols outside the Market Watch
         CurrencyBase = SymbolInfoString(SymName,SYMBOL_CURRENCY_BASE);
         CurrencyProfit = SymbolInfoString(SymName,SYMBOL_CURRENCY_PROFIT);
         SymbolExist(SymName,is_Custom);//Get the boolean value into 'is_Custom' for whether the Symbol Name is a Custom Symbol(Is not from the broker)

         //-- Check if the Symbol outside the Market Watch has a SYMBOL_CURRENCY_BASE of EUR
         //-- and a SYMBOL_CURRENCY_PROFIT of USD, and this Symbol is not a Custom Symbol(Is not from the broker)
         if(CurrencyBase=="EUR"&&CurrencyProfit=="USD"&&!is_Custom)
           {
            EurusdIsSelected = SymbolSelect(SymName,true);//Adding the EURUSD Symbol to the Market Watch
            if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
              {
               EURUSD = SymName;//Assigning the name of the EURUSD Symbol found outside the Market Watch
               EurusdIsFound = true;//EURUSD Symbol was found in the Trading Terminal for your Broker
               break;//Will end the for loop
              }
           }
        }
     }

   if(!EurusdIsFound)//Check if EURUSD Symbol was Found in the Trading Terminal for your Broker
     {
      Print("Cannot Find EURUSD!");
      Print("Cannot Create Database!");
      Print("Server DST Cannot be Detected!");
      dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
      return false;//Returning False, Broker's DST schedule was not found
     }


После обнаружения EURUSD пройдемся по всем датам NFP и найдем свечи M15 для каждой отдельной даты, а затем сравним высоту свечи со смещенными датами за час до и после события, чтобы с большой вероятностью определить, произошло ли событие. Если даты событий не соответствуют волатильности в свечах, мы сохраним первую дату события, когда возникло это несоответствие, и дату его окончания в переменных ShiftStart и ShiftEnd.

for(uint i=0;i<eventtime.Size();i++)
     {
      currentresult.result = Candle.IsLargerThanPreviousAndNext((datetime)eventtime[i],Time.HoursS(),EURUSD);//Store the result of if the event date is the larger candlestick
      currentresult.date = (datetime)eventtime[i];//Store the event date from eventtime[i]
      timeIsShifted = ((currentresult.result!=previousresult.result&&i>0)?true:false);//Check if there is a difference between the previous result and the current result

      //--- Print Event Dates and if the event date's candle is larger than the previous candle an hour ago and the next candle an hour ahead
      Print("Date: ",eventtime[i]," is Larger: ",Candle.IsLargerThanPreviousAndNext((datetime)eventtime[i],Time.HoursS(),EURUSD)," Shifted: ",timeIsShifted);

      if(timeIsShifted)//Check if the Larger candle has shifted from the previous event date to the current event date in eventtime[i] array
        {
         if(ShiftStart==D'1970.01.01 00:00:00')//Check if the ShiftStart variable has not been assigned a relevant value yet
           {
            ShiftStart=currentresult.date;//Store the event date for when the time shift began
           }
         ShiftEnd=previousresult.date;//Store the event date timeshift
        }
      previousresult.result = currentresult.result;//Store the previous result of if the event date is the larger candlestick
      previousresult.date = currentresult.date;//Store the event date from eventtime[i]
     }


Получив даты ShiftStart и ShiftEnd, мы проверим, соответствуют ли они датам начала и окончания летнего времени. Если совпадение есть, мы назначим расписание летнего времени в переменной dstType и вернем значение true. Если у нас нет даты ShiftStart date(ShiftStart=D'1970.01.01 00:00:00') и размер массива eventtime больше нуля, мы знаем, что брокер не следует никакому расписанию перехода на летнее время.

if(ShiftStart==D'1970.01.01 00:00:00'&&eventtime.Size()>0)//Check if the ShiftStart variable has not been assigned a relevant value and the event dates are more than zero
     {
      Print("Broker ServerTime unchanged!");
      dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
      return true;//Returning True, Broker's DST schedule was found successfully
     }

   if(Savings_AU.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
     {
      if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
        {
         Print("Broker ServerTime Adjusted For AU DST");
         if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
           {
            SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
           }
         dstType = AU_DST;//Assigning enumeration value AU_DST, Broker has AU DST(Daylight Savings Time)
         return true;//Returning True, Broker's DST schedule was found successfully
        }
     }
   else
     {
      Print("Something went wrong!");
      Print("Cannot Find Daylight-Savings Date For AU");
      Print("Year: %d Cannot Be Found!",lastyear);
      if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
        {
         SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
        }
      dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
      return false;//Returning False, Broker's DST schedule was not found
     }

   if(Savings_UK.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
     {
      if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
        {
         Print("Broker ServerTime Adjusted For UK DST");
         if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
           {
            SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
           }
         dstType = UK_DST;//Assigning enumeration value UK_DST, Broker has UK/EU DST(Daylight Savings Time)
         return true;//Returning True, Broker's DST schedule was found successfully
        }
     }
   else
     {
      Print("Something went wrong!");
      Print("Cannot Find Daylight-Savings Date For UK");
      Print("Year: %d Cannot Be Found!",lastyear);
      if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
        {
         SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
        }
      dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
      return false;//Returning False, Broker's DST schedule was not found
     }

   if(Savings_US.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
     {
      if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
        {
         Print("Broker ServerTime Adjusted For US DST");
         if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
           {
            SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
           }
         dstType = US_DST;//Assigning enumeration value US_DST, Broker has US DST(Daylight Savings Time)
         return true;//Returning True, Broker's DST schedule was found successfully
        }
     }
   else
     {
      Print("Something went wrong!");
      Print("Cannot Find Daylight-Savings Date For US");
      Print("Year: %d Cannot Be Found!",lastyear);
      if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
        {
         SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
        }
      dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
      return false;//Returning False, Broker's DST schedule was not found
     }

   if(EurusdIsSelected)//Check if this program added EURUSD Symbol to Market Watch
     {
      SymbolSelect(EURUSD,false);//Remove EURUSD Symbol from Market Watch
     }
   Print("Cannot Detect Broker ServerTime Configuration!");
   dstType = DST_NONE;//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
   return false;//Returning False, Broker's DST schedule was not found
  }


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

void              CreateAutoDST(int db);//Function for creating and inserting Recommend DST for the Broker into a table

В таблице AutoDST будет сохранена одна запись.

void CNews::CreateAutoDST(int db)
  {
   bool failed=false;//boolean variable

   if(!DatabaseTableExists(db,"AutoDST"))//Checks if the table 'AutoDST' exists in the databse 'Calendar'
     {
      //--- create the table AutoDST
      if(!DatabaseExecute(db,"CREATE TABLE AutoDST(DST STRING NOT NULL);"))//Will attempt to create the table 'AutoDST'
        {
         Print("DB: create the AutoDST table failed with code ", GetLastError());
         DatabaseClose(db);//Close the database
         return;//Exits the function if creating the table failed
        }
     }
   else
     {
      return;//Exits the function if the table AutoDST table already exists
     }

//Sql query/request to insert the recommend DST for the Broker using the DSTType variable to determine which string data to insert
   string request_text=StringFormat("INSERT INTO 'AutoDST'(DST) VALUES ('%s')",((DSTType==US_DST)?"Data_US":
                                    (DSTType==UK_DST)?"Data_UK":(DSTType==AU_DST)?"Data_AU":"Data_None"));

   if(!DatabaseExecute(db, request_text))//Will attempt to run this sql request/query
     {
      Print(GetLastError());
      PrintFormat("INSERT INTO 'AutoDST'(DST) VALUES ('%s')",((DSTType==US_DST)?"Data_US":
                  (DSTType==UK_DST)?"Data_UK":(DSTType==AU_DST)?"Data_AU":"Data_None"));//Will print the sql query if failed
      failed=true;//assign true if the request failed
     }

   if(failed)
     {
      //--- roll back all transactions and unlock the database
      DatabaseTransactionRollback(db);
      PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError());
     }
  }


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

bool              CreateTable(int db,string tableName,bool &tableExists);//Function for creating a table in a database

В этой функции CreateTable мы просто создадим таблицы 'Data_UK' для DST Великобритании ,'Data_US' для DST США, 'Data_AU' для DST Австралии и 'Data_None' при отсутствии DST. Строка tableName определяет суффикс имени таблицы, например, 'US'.

bool CNews::CreateTable(int db,string tableName,bool &tableExists)
  {
   if(DatabaseTableExists(db,StringFormat("Data_%s",tableName)))//Checks if a table 'Data_%s' exists in the database 'Calendar'
     {
      tableExists=true;//Assigns true to tableExists variable

      if(!DatabaseExecute(db,StringFormat("DROP TABLE Data_%s",tableName)))//We will drop the table if the table already exists
        {
         //If the table failed to be dropped/deleted
         PrintFormat("Failed to drop table Data_%s with code %d",tableName,GetLastError());
         DatabaseClose(db);//Close the database
         return false;//will terminate execution of the rest of the code below and return false, when the table cannot be dropped
        }
     }

   if(!DatabaseTableExists(db,StringFormat("Data_%s",tableName)))//If the database table 'Data_%s' doesn't exist
     {
      //--- create the table 'Data' with the following columns
      if(!DatabaseExecute(db,StringFormat("CREATE TABLE Data_%s("
                                          "ID INT NOT NULL,"
                                          "EVENTID  INT   NOT NULL,"
                                          "COUNTRY  STRING   NOT NULL,"
                                          "EVENTNAME   STRING   NOT NULL,"
                                          "EVENTTYPE   STRING   NOT NULL,"
                                          "EVENTIMPORTANCE   STRING   NOT NULL,"
                                          "EVENTDATE   STRING   NOT NULL,"
                                          "EVENTCURRENCY  STRING   NOT NULL,"
                                          "EVENTCODE   STRING   NOT NULL,"
                                          "EVENTSECTOR STRING   NOT NULL,"
                                          "EVENTFORECAST  STRING   NOT NULL,"
                                          "EVENTPREVALUE  STRING   NOT NULL,"
                                          "EVENTIMPACT STRING   NOT NULL,"
                                          "EVENTFREQUENCY STRING   NOT NULL,"
                                          "PRIMARY KEY(ID));",tableName)))//Checks if the table was successfully created
        {
         Print("DB: create the Calendar table failed with code ", GetLastError());
         DatabaseClose(db);//Close the database
         return false;//Function returns false if creating the table failed
        }
     }
   return true;//Function returns true if creating the table was successful
  }


Теперь нам нужно вставить данные в созданные нами таблицы, но сначала нам нужно получить эти данные. Наша следующая функция

void              EconomicDetails(Calendar &NewsTime[]);//Gets values from the MQL5 economic Calendar

Извлекаем все доступные экономические события по ссылке в массиве NewsTime Calendar

void CNews::EconomicDetails(Calendar &NewsTime[])
  {
   int Size=0;//to keep track of the size of the events in the NewsTime array
   MqlCalendarCountry countries[];
   int count=CalendarCountries(countries);//Get the array of country names available in the Calendar
   string Country_code="";

   for(int i=0; i<count; i++)
     {
      MqlCalendarValue values[];
      datetime date_from=0;//Get date from the beginning
      datetime date_to=(datetime)(Time.MonthsS()+iTime(Symbol(),PERIOD_D1,0));//Date of the next month from the current day

      if(CalendarValueHistory(values,date_from,date_to,countries[i].code))
        {
         for(int x=0; x<(int)ArraySize(values); x++)
           {
            MqlCalendarEvent event;
            ulong event_id=values[x].event_id;//Get the event id

            if(CalendarEventById(event_id,event))
              {
               ArrayResize(NewsTime,Size+1,Size+2);//Readjust the size of the array to +1 of the array size
               StringReplace(event.name,"'","");//Removing or replacing single quotes(') from event name with an empty string

               NewsTime[Size].CountryName = countries[i].name;//storing the country's name from the specific event
               NewsTime[Size].EventName = event.name;//storing the event's name
               NewsTime[Size].EventType = EnumToString(event.type);//storing the event type from (ENUM_CALENDAR_EVENT_TYPE) to a string
               NewsTime[Size].EventImportance = EnumToString(event.importance);//storing the event importance from (ENUM_CALENDAR_EVENT_IMPORTANCE) to a string
               NewsTime[Size].EventId = event.id;//storing the event id
               NewsTime[Size].EventDate = TimeToString(values[x].time);//storing normal event time
               NewsTime[Size].EventCurrency = countries[i].currency;//storing event currency
               NewsTime[Size].EventCode = countries[i].code;//storing event code
               NewsTime[Size].EventSector = EnumToString(event.sector);//storing event sector from (ENUM_CALENDAR_EVENT_SECTOR) to a string

               if(values[x].HasForecastValue())//Checks if the event has a forecast value
                 {
                  NewsTime[Size].EventForecast = (string)values[x].forecast_value;//storing the forecast value into a string
                 }
               else
                 {
                  NewsTime[Size].EventForecast = "None";//storing 'None' as the forecast value
                 }

               if(values[x].HasPreviousValue())//Checks if the event has a previous value
                 {
                  NewsTime[Size].EventPreval = (string)values[x].prev_value;//storing the previous value into a string
                 }
               else
                 {
                  NewsTime[Size].EventPreval = "None";//storing 'None' as the previous value
                 }

               NewsTime[Size].EventImpact =  EnumToString(values[x].impact_type);//storing the event impact from (ENUM_CALENDAR_EVENT_IMPACT) to a string
               NewsTime[Size].EventFrequency =  EnumToString(event.frequency);//storing the event frequency from (ENUM_CALENDAR_EVENT_FREQUENCY) to a string
               Size++;//incrementing the Calendar array NewsTime
              }
           }
        }
     }
  }


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

bool              InsertIntoTable(int db,DST_type Type,Calendar &Evalues[]);//Function for inserting Economic Data in to a database's table

Выполнить эту задачу поможет функция InsertIntoTable. Она имеет три параметра.

1. Этот входной параметр представляет собой целочисленное значение базы данных.

int db

2. Этот параметр - график DST.

DST_type Type

3. Эта ссылка на массив представляет собой входные данные событий календаря, которые мы могли бы получить из нашей предыдущей функции EconomicDetails.

Calendar &Evalues[]

 В этой функции мы изменим даты отдельных событий. Если они попадают в график летнего времени, мы добавим час к дате события. Если текущее время сервера попадает в график летнего времени, мы уберем час из даты события. Сохраним все данные об экономических событиях в таблице календаря Data_%s.

bool CNews::InsertIntoTable(int db,DST_type Type,Calendar &Evalues[])
  {
   string tableName;//will store the table name suffix
   for(uint i=0; i<Evalues.Size(); i++)//Looping through all the Economic Events
     {
      string Date;//Will store the date for the economic event
      switch(Type)//Switch statement to check all possible 'case' scenarios for the variable Type
        {
         case DST_NONE://if(Type==DST_NONE) then run code below
            Date = Evalues[i].EventDate;//Assign the normal Economic EventDate
            tableName = "None";//Full table name will be 'Data_None'
            break;//End switch statement
         case US_DST://if(Type==US_DST) then run code below
            Savings_US.adjustDaylightSavings(StringToTime(Evalues[i].EventDate),Date);//Assign by Reference the Economic EventDate adjusted for US DST(Daylight Savings Time)
            tableName = "US";//Full table name will be 'Data_US'
            break;//End switch statement
         case UK_DST://if(Type==UK_DST) then run code below
            Savings_UK.adjustDaylightSavings(StringToTime(Evalues[i].EventDate),Date);//Assign by Reference the Economic EventDate adjusted for UK DST(Daylight Savings Time)
            tableName = "UK";//Full table name will be 'Data_UK'
            break;//End switch statement
         case AU_DST://if(Type==AU_DST) then run code below
            Savings_AU.adjustDaylightSavings(StringToTime(Evalues[i].EventDate),Date);//Assign by Reference the Economic EventDate adjusted for AU DST(Daylight Savings Time)
            tableName = "AU";//Full table name will be 'Data_AU'
            break;//End switch statement
         default://if(Type==(Unknown value)) then run code below
            Date = Evalues[i].EventDate;//Assign the normal Economic EventDate
            tableName = "None";//Full table name will be 'Data_None'
            break;//End switch statement
        }

      string request_text =
         StringFormat("INSERT INTO 'Data_%s'(ID,EVENTID,COUNTRY,EVENTNAME,EVENTTYPE,EVENTIMPORTANCE,EVENTDATE,EVENTCURRENCY,EVENTCODE,"
                      "EVENTSECTOR,EVENTFORECAST,EVENTPREVALUE,EVENTIMPACT,EVENTFREQUENCY)"
                      "VALUES (%d,%d,'%s','%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s')",
                      tableName,
                      i,
                      Evalues[i].EventId,
                      Evalues[i].CountryName,
                      Evalues[i].EventName,
                      Evalues[i].EventType,
                      Evalues[i].EventImportance,
                      Date,
                      Evalues[i].EventCurrency,
                      Evalues[i].EventCode,
                      Evalues[i].EventSector,
                      Evalues[i].EventForecast,
                      Evalues[i].EventPreval,
                      Evalues[i].EventImpact,
                      Evalues[i].EventFrequency);//Inserting all the columns for each event record

      if(!DatabaseExecute(db, request_text))//Checks whether the event was inserted into the table 'Data_%s'
        {
         Print(GetLastError());
         PrintFormat("INSERT INTO 'Data_%s'(ID,EVENTID,COUNTRY,EVENTNAME,EVENTTYPE,EVENTIMPORTANCE,EVENTDATE,EVENTCURRENCY,EVENTCODE,"
                      "EVENTSECTOR,EVENTFORECAST,EVENTPREVALUE,EVENTIMPACT,EVENTFREQUENCY)"
                      "VALUES (%d,%d,'%s','%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s')",
                      tableName,
                      i,
                      Evalues[i].EventId,
                      Evalues[i].CountryName,
                      Evalues[i].EventName,
                      Evalues[i].EventType,
                      Evalues[i].EventImportance,
                      Date,
                      Evalues[i].EventCurrency,
                      Evalues[i].EventCode,
                      Evalues[i].EventSector,
                      Evalues[i].EventForecast,
                      Evalues[i].EventPreval,
                      Evalues[i].EventImpact,
                      Evalues[i].EventFrequency);//Will print the sql query to check for any errors or possible defaults in the query/request

         return false;//Will end the loop and return false, as values failed to be inserted into the table
        }
     }
   return true;//Will return true, all values were inserted into the table successfully
  }


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

void              CreateRecords(int db);//Creates a table to store records of when last the Calendar database was updated/created

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

void CNews::CreateRecords(int db)
  {
   bool failed=false;

   if(!DatabaseTableExists(db,"Records"))//Checks if the table 'Records' exists in the databse 'Calendar'
     {
      //--- create the table
      if(!DatabaseExecute(db,"CREATE TABLE Records(RECORDEDTIME INT NOT NULL);"))//Will attempt to create the table 'Records'
        {
         Print("DB: create the Records table failed with code ", GetLastError());
         DatabaseClose(db);//Close the database
         return;//Exits the function if creating the table failed
        }
     }

//Sql query/request to insert the current time into the 'RECORDEDTIME' column in the table 'Records'
   string request_text=StringFormat("INSERT INTO 'Records'(RECORDEDTIME) VALUES (%d)",(int)TimeCurrent());

   if(!DatabaseExecute(db, request_text))//Will attempt to run this sql request/query
     {
      Print(GetLastError());
      PrintFormat("INSERT INTO 'Records'(RECORDEDTIME) VALUES (%d)",(int)TimeCurrent());
      failed=true;//assign true if the request failed
     }

   if(failed)
     {
      //--- roll back all transactions and unlock the database
      DatabaseTransactionRollback(db);
      PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError());
     }
  }

 

Наши функции почти готовы. Осталось еще три, на которых я хочу сосредоточиться. Первая

bool              UpdateRecords();//Checks if the main Calendar database needs an update or not

Вернет false, когда максимальная запись в таблице Records находится в пределах текущей даты (это означает, что таблицы календаря уже были созданы или обновлены в текущий день и сегодня не нужно выполнять никаких новых обновлений). Функция вернет true, если таблицы календаря не существуют, или нет базы данных, или максимальная запись в таблице Records не попадает в текущую дату (день).

bool CNews::UpdateRecords()
  {
//--- open/create
   int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READONLY|DATABASE_OPEN_COMMON);//try to open database Calendar

   if(db==INVALID_HANDLE)//Checks if the database was able to be opened
     {
      //if opening the database failed
      if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))//Checks if the database Calendar exists in the common folder
        {
         return true;//Returns true when the database was failed to be opened and the file doesn't exist in the common folder
        }
     }

   if(!DatabaseTableExists(db,"Records"))//If the database table 'Records' doesn't exist
     {
      DatabaseClose(db);
      return true;
     }

   int recordtime=0;//will store the maximum date recorded in the database table 'Records'
   string request_text="SELECT MAX(RECORDEDTIME) FROM Records";//Sql query to determine the lastest or maximum date recorded
   int request=DatabasePrepare(db,request_text);//Creates a handle of a request, which can then be executed using DatabaseRead()
   if(request==INVALID_HANDLE)//Checks if the request failed to be completed
     {
      Print("DB: ",NEWS_DATABASE_FILE, " request failed with code ", GetLastError());
      DatabaseClose(db);
      return true;
     }

   for(int i=0; DatabaseRead(request); i++)//Will read all the results from the sql query/request
     {
      if(!DatabaseColumnInteger(request, 0, recordtime))//Will assign the first column value to the variable 'recordtime'
        {
         Print(i, ": DatabaseRead() failed with code ", GetLastError());
         DatabaseFinalize(request);//Removes a request created in DatabasePrepare()
         DatabaseClose(db);//Closes the database
         return true;
        }
     }

   DatabaseFinalize(request);//Removes a request created in DatabasePrepare()
   DatabaseClose(db);//Closes the database

   if(!Time.DateisToday((datetime)recordtime))//Checks if the recorded time/date is today(current day)
     {
      return true;
     }

   return false;
  }


Вторая функция, на которой я хочу сосредоточиться, это

datetime          GetLastestNewsDate();//Gets the lastest/newest date in the Calendar database

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

datetime CNews::GetLastestNewsDate()
  {
//--- open the database 'Calendar' in the common folder
   int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READONLY|DATABASE_OPEN_COMMON);

   if(db==INVALID_HANDLE)//Checks if 'Calendar' failed to be opened
     {

      if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))//Checks if 'Calendar' database exists
        {
         return 0;//Will return the earliest date which is 1970.01.01 00:00:00
        }
     }

   string eventtime="1970.01.01 00:00:00";//string variable with the first/earliest possible date in MQL5
//Sql query to determine the lastest or maximum recorded time from which the database was updated.
   string request_text="SELECT MAX(RECORDEDTIME) FROM Records";
   int request=DatabasePrepare(db,request_text);

   if(request==INVALID_HANDLE)
     {

      Print("DB: ",NEWS_DATABASE_FILE, " request failed with code ", GetLastError());
      DatabaseClose(db);
      return true;

     }

   for(int i=0; DatabaseRead(request); i++)//Will read all the results from the sql query/request
     {
      if(!DatabaseColumnText(request, 0,eventtime))//Will assign the first column(column 0) value to the variable 'eventtime'
        {

         Print(i, ": DatabaseRead() failed with code ", GetLastError());
         DatabaseFinalize(request);//Finalize request
         DatabaseClose(db);//Closes the database 'Calendar'
         return 0;//Will end the for loop and will return the earliest date which is 1970.01.01 00:00:00
        }
     }

   DatabaseFinalize(request);
   DatabaseClose(db);//Closes the database 'Calendar'

   return StringToTime(eventtime);//Returns the string eventtime converted to datetime
  }


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

void              CreateEconomicDatabase();//Creates the Calendar database for a specific Broker

Это функция, которую мы будем вызывать для создания нашей базы данных, после того как советник будет присоединен к графику.

void CNews::CreateEconomicDatabase()
  {
   Print("Please wait...");

   if(FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))//Check if the database exists
     {
      if(!UpdateRecords())//Check if the database is up to date
        {
         return;//will terminate execution of the rest of the code below
        }
     }
   else
     {
      if(!AutoDetectDST(DSTType))//Check if AutoDetectDST went through all the right procedures
        {
         return;//will terminate execution of the rest of the code below
        }
     }

   if(FileIsExist(NEWS_TEXT_FILE,FILE_COMMON))//Check if the database is open
     {
      return;//will terminate execution of the rest of the code below
     }

   Calendar Evalues[];//Creating a Calendar array variable
   bool failed=false,tableExists=false;
   int file=INVALID_HANDLE;
   datetime lastestdate=D'1970.01.01 00:00:00';
//--- open/create the database 'Calendar'
   int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE| DATABASE_OPEN_COMMON);//will try to open/create in the common folder

   if(db==INVALID_HANDLE)//Checks if the database 'Calendar' failed to open/create
     {
      Print("DB: ",NEWS_DATABASE_FILE, " open failed with code ", GetLastError());
      return;//will terminate execution of the rest of the code below
     }
   else
     {
      file=FileOpen(NEWS_TEXT_FILE,FILE_WRITE|FILE_ANSI|FILE_TXT|FILE_COMMON);//try to create a text file 'NewsDatabaseOpen' in common folder
      if(file==INVALID_HANDLE)
        {
         DatabaseClose(db);//Closes the database 'Calendar' if the News text file failed to be created
         return;//will terminate execution of the rest of the code below
        }
     }

   DatabaseTransactionBegin(db);//Starts transaction execution
   Print("Please wait...");

//-- attempt to create the calendar tables
   if(!CreateTable(db,"None",tableExists)||!CreateTable(db,"US",tableExists)
      ||!CreateTable(db,"UK",tableExists)||!CreateTable(db,"AU",tableExists))
     {
      FileClose(file);//Closing the file 'NewsDatabaseOpen.txt'
      FileDelete(NEWS_TEXT_FILE,FILE_COMMON);//Deleting the file 'NewsDatabaseOpen.txt'
      return;//
     }

   EconomicDetails(Evalues);//Retrieving the data from the Economic Calendar

   if(tableExists)//Checks if there is an existing table within the Calendar Database
     {
      //if there is an existing table we will notify the user that we are updating the table.
      PrintFormat("Updating %s",NEWS_DATABASE_FILE);
     }
   else
     {
      //if there isn't an existing table we will notify the user that we about to create one
      PrintFormat("Creating %s",NEWS_DATABASE_FILE);
     }

//-- attempt to insert economic event data into the calendar tables
   if(!InsertIntoTable(db,DST_NONE,Evalues)||!InsertIntoTable(db,US_DST,Evalues)
      ||!InsertIntoTable(db,UK_DST,Evalues)||!InsertIntoTable(db,AU_DST,Evalues))
     {
      failed=true;//Will assign true if inserting economic vaules failed in any of the Data tables
     }

   if(failed)//Checks if the event/s failed to be recorded/inserted into the database table 'Data_%s'
     {
      //--- roll back all transactions and unlock the database
      DatabaseTransactionRollback(db);
      PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError());
      FileClose(file);//Close the text file 'NEWS_TEXT_FILE'
      FileDelete(NEWS_TEXT_FILE,FILE_COMMON);//Delete the text file, as we are reverted/rolled-back the database
      ArrayRemove(Evalues,0,WHOLE_ARRAY);//Removes the values in the array

     }
   else//if all the events were recorded or inserted into the tables 'Data_%s'
     {
      if(tableExists)
        {
         //Let the user/trader know that the database was updated
         PrintFormat("%s Updated",NEWS_DATABASE_FILE);
        }
      else
        {
         //Let the user/trader know that the database was created
         PrintFormat("%s Created",NEWS_DATABASE_FILE);
        }

      CreateRecords(db);//Will create the 'Records' table and insert the  current time
      CreateAutoDST(db);//Will create the 'AutoDST' table and insert the broker's DST schedule
      FileClose(file);//Close the text file 'NEWS_TEXT_FILE'
      FileDelete(NEWS_TEXT_FILE,FILE_COMMON);//Delete the text file, as we are about to close the database
      ArrayRemove(Evalues,0,WHOLE_ARRAY);//Removes the values in the array
     }
//--- all transactions have been performed successfully - record changes and unlock the database
   DatabaseTransactionCommit(db);
   DatabaseClose(db);//Close the database
  }



Теперь перейдем к советнику, который будет запускать весь код, созданный нами во всех различных классах и файлах, созданных в проекте NewsTrading.

//+------------------------------------------------------------------+
//|                                                  NewsTrading.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include "News.mqh"
CNews NewsObject;
#include "TimeManagement.mqh"
CTimeManagement CTM;
#include "WorkingWithFolders.mqh"
CFolders Folder();//Calling the class's constructor
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!MQLInfoInteger(MQL_TESTER))//Checks whether the program is in the strategy tester
     {
      //Checks whether the database file exists and whether it has been modified in the current date
      if((!CTM.DateisToday((datetime)FileGetInteger(NEWS_DATABASE_FILE,FILE_MODIFY_DATE,true)))||(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON)))
        {
         /*
         In the Do while loop below, the code will check if the terminal is connected to the internet.
         If the the program is stopped the loop will break, if the program is not stopped and the terminal
         is connected to the internet the function CreateEconomicDatabase will be called from the News.mqh header file's
         object called NewsObject and the loop will break once called.
         */
         bool done=false;
         do
           {
            if(IsStopped())
              {
               done=true;
              }

            if(!TerminalInfoInteger(TERMINAL_CONNECTED))
              {
               Print("Waiting for connection...");
               Sleep(500);
               continue;
              }
            else
              {
               Print("Connection Successful!");
               NewsObject.CreateEconomicDatabase();//calling the database create function
               done=true;
              }
           }
         while(!done);
        }
     }
   else
     {
      //Checks whether the database file exists
      if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))
        {
         Print("Necessary Files Do not Exist!");
         Print("Run Program outside of the Strategy Tester");
         Print("Necessary Files Should be Created First");
         return(INIT_FAILED);
        }
      //Checks whether the lastest database date includes the time and date being tested
      datetime lastestdate = CTM.TimePlusOffset(NewsObject.GetLastestNewsDate(),CTM.DaysS());//Day after the lastest recorded time in the database
      if(lastestdate<TimeCurrent())
        {
         Print("Necessary Files OutDated!");
         Print("Database Dates End at: ",lastestdate);
         Print("Dates after %s will not be available for backtest",lastestdate);
         Print("To Update Files:");
         Print("Run Program outside of the Strategy Tester");
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

 Теперь осталось только скомпилировать файлы и советник, добавить советник на график и начать анализ.

И самое главное, посмотреть, что может предложить экономический календарь.

Это наши файлы проекта

Программные файлы проекта




Разбивка экономического календаря MQL5

Окно навигации


После того, как все скомпилировано, откроем торговый терминал, найдем окно "Навигатор" и откроем папку NewsTrading.


Добавление советника на график

Добавим советник на нужный нам график. Опцию "Разрешить автоматическую торговлю" включать не будем. Торговать мы пока не собираемся. Жмем ОК и добавляем советник.



Сообщения советника

После присоединения советника к графику на вкладке "Эксперты" должно появиться сообщение об успешном создании базы данных.


CommonFolderIDE


Нажмите кнопку IDE на верхней панели.

Нажмите кнопку "Файл" в новом окне и выберите "Открыть общую папку данных".


Файловый каталог 

Ваш файловый каталог должен выглядеть примерно так, как показано на изображении выше.


Папка NewsTrading

Папка NewsCalendar

Папка Broker

Вы увидите, что была создана папка NewsTrading и многие другие.


Директория

Файл базы данных календаря

После того, как все папки будут открыты, вы увидите отдельную базу данных SQLite календаря вашего брокера.

Вернемся к среде разработки MQL5.


Иконка с папкой

Выберите иконку с изображением папки в левом верхнем углу, чтобы открыть общую папку MetaQoutes.


Файл базы данных

Найдите базу данных Calendar SQLite и откройте ее.



Открытая база данных

Вы должны увидеть содержимое базы данных в окне "Навигатор". Щелкните правой кнопкой мыши по первой таблице и выберите "Открыть таблицу".



Таблица AutoDST

Откроется таблица и отобразятся все записи (*) из AutoDST. В этой таблице всегда будет только одна запись, поскольку это будет рекомендуемая нашим брокером таблица DST.


Data_AU Table

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



Объединение таблиц календаря

SQL-запрос:

SELECT  None.EVENTNAME, DATE(REPLACE(None.EVENTDATE,'.','-')) as Date, TIME(REPLACE(None.EVENTDATE,'.','-')) as Time_None,

TIME(REPLACE(US.EVENTDATE,'.','-')) as Time_US, TIME(REPLACE(UK.EVENTDATE,'.','-')) as Time_UK, TIME(REPLACE(AU.EVENTDATE,'.','-')) as Time_AU

FROM 'Data_None' None

INNER JOIN 'Data_US' US on US.ID=None.ID

INNER JOIN 'Data_UK' UK on UK.ID=None.ID

INNER JOIN 'Data_AU' AU on AU.ID=None.ID

WHERE Date BETWEEN '2023-01-01' AND '2024-01-01' AND None.EVENTID='840100001';

Назначение: Запрос отображает название события с идентификатором события = '840100001', дату события - как Date, а время - как Time_None между '2023-01-01' и '2024-01-01' из таблицы Data_None, а также выбирает тот же идентификатор события из таблиц Data_UK, Data_US, Data_AU, но отображает только время событий из этих таблиц как Time_UK, Time_US и Time_AU. В этом запросе мы хотим показать разницу во времени между разными календарными таблицами для одного и того же события за весь 2023 год.

В приведенном выше SQL-запросе мы даем таблице Data_None псевдоним None, таблице Data_US — псевдоним US, таблице Data_UK — псевдоним UK, а таблице Data_AU — псевдоним AU.

Мы выбираем (SELECT)

None.EVENTNAME

из таблицы Data_None.


В SQL Function

REPLACE(None.EVENTDATE,'.','-')

Точки ('.') в значении из None.EVENTDATE заменяются дефисами ('-'). Это потому, что функция SQL

DATE()

принимает только даты в datetime с дефисом в качестве разделителя символов и не принимает даты в datetime с точками. Функция преобразует datetime в дату.


TIME()

Эта функция преобразует datetime во время.



Отдельные страны

SQL-запрос: 

SELECT Distinct(COUNTRY) FROM 'Data_None';

Назначение: Запрос предоставляет нам все уникальные страны, записи о которых имеются в таблице календаря Data_None.



ExportResults

Если вы хотите сохранить результаты ваших SQL-запросов для последующего анализа, щелкните правой кнопкой мыши по одному из результатов и экспортируйте CSV-файл, который можно преобразовать в файл Excel

для более подробного анализа. 


Новая Зеландия

SQL-запрос: 

SELECT * FROM 'Data_None' where COUNTRY='New Zealand';

Назначение: Запрос предоставляет все записи по определенной стране. В данном случае это Новая Зеландия.



Уникальные идентификаторы событий

SQL-запрос: 

SELECT Distinct(EVENTID), COUNTRY, EVENTNAME,EVENTTYPE, EVENTSECTOR, EVENTIMPORTANCE, EVENTFREQUENCY,
EVENTCURRENCY, EVENTCODE FROM 'Data_None' 
where COUNTRY='New Zealand';

Назначение: Запрос извлекает все уникальные идентификаторы событий для определенной страны и выбирает определенные столбцы, такие как " EVENTNAME ", " EVENTSECTOR " и так далее.

В их число не входит "EVENTDATE", поскольку у всех "EVENTID" разные "EVENTDATE". Попытка включить в запрос "EVENTDATE" приведет к ошибкам.



Определенный EventSector

SQL-запрос: 

SELECT Distinct(EVENTID), COUNTRY, EVENTNAME, EVENTTYPE, EVENTIMPORTANCE FROM 'Data_None' 
where COUNTRY='New Zealand' and EVENTSECTOR='CALENDAR_SECTOR_PRICES';

Цель: Запрос извлекает все уникальные идентификаторы событий для определенных "COUNTRY" и "EVENTSECTOR"



События с высокой важностью

SQL-запрос:

SELECT COUNTRY,EVENTNAME, EVENTIMPORTANCE FROM 'Data_None' 
where EVENTIMPORTANCE='CALENDAR_IMPORTANCE_HIGH'
UNION
SELECT COUNTRY,EVENTNAME, EVENTIMPORTANCE FROM 'Data_None' 
where EVENTIMPORTANCE='CALENDAR_IMPORTANCE_HIGH';

Цель: Запрос извлекает все уникальные записи для важности события "CALENDAR_IMPORTANCE_HIGH"



Заключение

Из этой статьи можно извлечь много полезного. Надеюсь, вы узнали что-то новое и почерпнули новые идеи. Мы ответили на вопрос, почему использование базы данных - это правильное решение. Мы также рассмотрели все валюты, предоставляемые Экономическим календарем MQL5, и переход на летнее время (DST). Кроме того, мы создали различные необходимые файлы для создания базы данных календаря и проверки необходимости обновления или создания базы данных.

Наконец, мы осуществили поиск файлов базы данных после их создания и извлекли определенные данные с помощью базовых SQL-запросов для управления таблицей. В следующей статье мы рассмотрим управление рисками.



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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
amrali
amrali | 22 апр. 2024 в 09:27

Отличная статья и хорошо продуманный код.

Мне понравился подход, который вы использовали для получения типа DST брокера через реакцию цены на NFP США на графиках EURUSD, он работает безупречно.

Я также протестировал корректировку dst на события Non-farm payrolls (NFP) США на нескольких брокерах (DST_NONE, DST_UK и DST_US) и показал, что они правильно рассчитываются внутри метода CDaylightSavings_AU::adjustDaylightSavings() и его братьев и сестер в двух других классах. Однако производительность процесса определения времени суток можно значительно повысить, если напрямую вычислять время переключения dst с помощью математических уравнений вместо линейного поиска по CArrayObj() жестко закодированных значений. См. здесь.

Также обратите внимание, что SymbolInfoString(SymName,SYMBOL_CURRENCY_BASE) может не сработать у некоторых брокеров, которые неправильно настраивают это свойство на своих серверах (они"ошибочно" устанавливают базовую валюту EURUSD в USD вместо EUR), поэтому безопаснее использовать StringSubstr(SymName,0,3).

Спасибо.

Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Методы Уильяма Ганна (Часть I): Создаем индикатор углов Ганна Методы Уильяма Ганна (Часть I): Создаем индикатор углов Ганна
В чем суть теории Ганна? Как строятся углы Ганна? Создаем индикатор углов Ганна для MetaTrader 5.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Алгоритм миграции животных — Animal Migration Optimization (AMO) Алгоритм миграции животных — Animal Migration Optimization (AMO)
Статья посвящена алгоритму AMO, который моделирует процесс сезонной миграции животных в поисках оптимальных условий для жизни и размножения. Основные особенности AMO включают использование топологического соседства и вероятностный механизм обновления, что делает его простым в реализации и гибким для различных оптимизационных задач.