English 中文 Español Deutsch 日本語 Português
preview
Упрощаем торговлю на новостях (Часть 2): Управляем рисками

Упрощаем торговлю на новостях (Часть 2): Управляем рисками

MetaTrader 5Примеры | 25 октября 2024, 15:01
1 043 6
Kabelo Frans Mampa
Kabelo Frans Mampa

Введение

Для начала - краткое содержание предыдущей статьи серии "Упрощаем торговлю на новостях". В первой части мы рассмотрели концепцию летнего времени (Daylight Savings Time, DST) и различные версии для разных стран, которые меняют свои часовые пояса на час вперед и назад в течение финансового года. Это в свою очередь меняет графики торгов для соответствующих брокеров, использующих летнее время. Мы рассмотрели причины создания базы данных и ее преимущества. Была создана база данных для хранения новостей из Экономического календаря MQL5 с последующими изменениями данных о времени событий для отражения графика перехода брокера на летнее время для точного тестирования на истории в будущем. В файлах проекта были предоставлены результаты работы SQL-скрипта в формате Excel для всех уникальных событий, доступных через календарь MQL5 для различных стран.

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


Наследование

Типы наследования


Что такое наследование?

Наследование

Наследование - фундаментальная концепция объектно-ориентированного программирования (ООП), которая позволяет новому классу (называемому подклассом или производным классом) наследовать свойства и поведение (поля и методы) существующего класса (называемого суперклассом или базовым классом). Этот механизм позволяет создавать новый класс путем расширения или изменения поведения существующего класса, способствуя повторному использованию кода и созданию более логичной и иерархической структуры классов. 


Какова цель наследования?

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

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


Что такое модификаторы доступа?

Модификаторы доступа — это ключевые слова в объектно-ориентированных языках программирования, которые устанавливают доступность классов, методов/функций и других членов. Они контролируют видимость и доступность этих элементов из разных частей кода, тем самым обеспечивая инкапсуляцию и защищая целостность данных.

Типы модификаторов доступа

  • Публичные (Public)
  • Приватные (Private)
  • Защищенные (Protected)

1. Публичные

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

2. Приватные

Назначение: Ограничить доступ к членам класса, тем самым защищая целостность данных.

3. Защищенные

Назначение: Разрешить подклассам наследовать и получать доступ к членам, при этом ограничивая доступ со стороны объектов класса.


Пример наследования, связанный с MQL5

Сначала создадим диаграмму класса UML для примера визуализации классов, их взаимосвязей и атрибутов.

Диаграмма классов UML

Классы UnitedStates и Switzerland имеют единственное наследование от класса NewsData:

class NewsData
  {
private://Properties are only accessible from this class
   string            Country;//Private variable
   struct EventDetails//Private structure
     {
      int            EventID;
      string         EventName;
      datetime       EventDate;
     };
protected:
   //-- Protected Array Only accessible from this class and its children
   EventDetails      News[];
   //-- Proctected virtual void Function(to be expanded on via child classes)
   virtual void      SetNews();
   //-- Protected Function Only accessible from this class and its children
   void              SetCountry(string myCountry) {Country=myCountry;}
public:
   void              GetNews()//Public function to display 'News' array details
     {
      PrintFormat("+---------- %s ----------+",Country);
      for(uint i=0;i<News.Size();i++)
        {
         Print("ID: ",News[i].EventID," Name: ",News[i].EventName," Date: ",News[i].EventDate);
        }
     }
                     NewsData(void) {}//Class constructor
                    ~NewsData(void) {ArrayFree(News);}//Class destructor
  };

//+------------------------------------------------------------------+
//|(Subclass/Child) for 'NewsData'                                   |
//+------------------------------------------------------------------+
class UnitedStates:private NewsData
//private inheritance from NewsData,
//'UnitedStates' class's objects and children
//will not have access to 'NewsData' class's properties
  {
private:
   virtual void      SetNews()//private Function only Accessible in 'UnitedStates' class
     {
      ArrayResize(News,News.Size()+1,News.Size()+2);
      News[News.Size()-1].EventID = 1;
      News[News.Size()-1].EventName = "NFP(Non-Farm Payrolls)";
      News[News.Size()-1].EventDate = D'2024.01.03 14:00:00';
     }
public:
   void              myNews()//public Function accessible via class's object
     {
      SetCountry("United States");//Calling function from 'NewsData'
      GetNews();//Calling Function from private inherited class 'NewsData'
     }
                     UnitedStates(void) {SetNews();}//Class constructor
  };

//+------------------------------------------------------------------+
//|(Subclass/Child) for 'NewsData'                                   |
//+------------------------------------------------------------------+
class Switzerland: public NewsData
//public inheritance from NewsData
  {
public:
   virtual void      SetNews()//Public Function to set News data
     {
      ArrayResize(News,News.Size()+1,News.Size()+2);//Adjusting News structure array's size
      News[News.Size()-1].EventID = 0;//Setting event id to '0'
      News[News.Size()-1].EventName = "Interest Rate Decision";//Assigning event name
      News[News.Size()-1].EventDate = D'2024.01.06 10:00:00';//Assigning event date
     }
                     Switzerland(void) {SetCountry("Switerland"); SetNews();}//Class construct
  };

В этом примере:

NewsData выступает в качестве родительского/базового/суперкласса и любые приватные объявления будут доступны только этому классу. Приватные объявления будут недоступны для объектов и потомков класса. При этом защищенные объявления будут доступны как классу, так и его потомкам. Все приватные объявления будут доступны классу, его потомкам и объектам.

Таблица доступности для NewsData:

Свойства класса Класс Потомки Объекты
Переменная: Country(Private)
Структура: EventDetails(Private)
Переменная: News(Protected)
Функция: SetNews(Protected)
Функция: SetCountry(Protected)
Функция: GetNews(Public)

Конструктор: NewsData(Public)
Деструктор: ~NewsData(Public)
class NewsData
  {
private://Properties are only accessible from this class
   string            Country;//Private variable
   struct EventDetails//Private structure
     {
      int            EventID;
      string         EventName;
      datetime       EventDate;
     };
protected:
   //-- Protected Array Only accessible from this class and its children
   EventDetails      News[];
   //-- Proctected virtual void Function(to be expanded on via child classes)
   virtual void      SetNews();
   //-- Protected Function Only accessible from this class and its children
   void              SetCountry(string myCountry) {Country=myCountry;}
public:
   void              GetNews()//Public function to display 'News' array details
     {
      PrintFormat("+---------- %s ----------+",Country);
      for(uint i=0;i<News.Size();i++)
        {
         Print("ID: ",News[i].EventID," Name: ",News[i].EventName," Date: ",News[i].EventDate);
        }
     }
                     NewsData(void) {}//Class constructor
                    ~NewsData(void) {ArrayFree(News);}//Class destructor
  };

Свойства, видимые из объекта NewsData:  

Свойства, видимые из объекта           Публичная функция из объекта


Результат из функции GetNews в NewsData:

Вывод функции

Наследование реализовано с обоими оставшимися классами:

В дочернем/производном/субклассе UnitedStates, он приватно наследует родительский класс (NewsData).
Это означает, что субкласс (UnitedStates) может получить доступ к защищенным и открытым свойствам родительского класса (NewsData), но потомки для класса UnitedStates и его объекты не будут иметь доступа ни к каким свойствам класса родителя (NewsData). Если бы наследование модификатора доступа было защитным, потомки класса UnitedStates имели бы доступ к защитным и публичным свойствам родительского класса (NewsData), но объекты субкласса (UnitedStates) не имели бы доступа к свойствам родительского класса.

Таблица доступности для UnitedStates:

Свойства класса Класс Потомки Объекты
Переменная наследования (приватная): Country(Private)



Структура наследования (приватная): EventDetails(Private)



Переменная наследования (приватная): News(Protected)



Функция наследования (приватная): SetNews(Protected)



Функция наследования (приватная): SetCountry(Protected)



Функция наследования (приватная): GetNews(Public)



Конструктор наследования (приватный): NewsData(Public)



Деструктор наследования (приватный): ~NewsData(Public)



Функция: SetNews(Private)


Функция: myNews(Public)


Конструктор: UnitedStates(Public)


class UnitedStates:private NewsData
//private inheritance from NewsData,
//'UnitedStates' class's objects and children
//will not have access to 'NewsData' class's properties
  {
private:
   virtual void      SetNews()//private Function only Accessible in 'UnitedStates' class
     {
      ArrayResize(News,News.Size()+1,News.Size()+2);
      News[News.Size()-1].EventID = 1;
      News[News.Size()-1].EventName = "NFP(Non-Farm Payrolls)";
      News[News.Size()-1].EventDate = D'2024.01.03 14:00:00';
     }
public:
   void              myNews()//public Function accessible via class's object
     {
      SetCountry("United States");//Calling function from 'NewsData'
      GetNews();//Calling Function from private inherited class 'NewsData'
     }
                     UnitedStates(void) {SetNews();}//Class constructor
  };

Свойства, видимые из объекта UnitedStates:

Свойства, видимые из объекта        Приватная унаследованная функция из NewsData


Ошибка компиляции возникает из-за попытки получить доступ к функции GetNews, которая приватно унаследована от NewsData. Это предотвращает доступ к функции объекта UnitedStates.

Ошибка компиляции

В дочернем/производном/субклассе Switzerland.

Модификатор доступа к наследованию является публичным. Это обеспечивает потомкам субкласса (Switzerland) доступ к публичным и защитным свойствам родительского класса (NewsData), тогда как объекты класса Switzerland имеют доступ только к публичным свойствам всех связанных классов.

Таблица доступности для Switzerland:

Свойства класса Класс Потомки Объекты
Переменная наследования (публичная): Country(Private)



Структура наследования (публичная): EventDetails(Private)



Переменная наследования (публичная): News(Protected)



Функция наследования (публичная): SetNews(Protected)



Функция наследования (публичная): SetCountry(Protected)



Функция наследования (публичная): GetNews(Public)



Конструктор наследования (публичный): NewsData(Public)



Деструктор наследования (публичный): ~NewsData(Public)



Функция: SetNews(Public)


Конструктор: Switzerland(Public)


class Switzerland: public NewsData
//public inheritance from NewsData
  {
public:
   virtual void      SetNews()//Public Function to set News data
     {
      ArrayResize(News,News.Size()+1,News.Size()+2);//Adjusting News structure array's size
      News[News.Size()-1].EventID = 0;//Setting event id to '0'
      News[News.Size()-1].EventName = "Interest Rate Decision";//Assigning event name
      News[News.Size()-1].EventDate = D'2024.01.06 10:00:00';//Assigning event date
     }
                     Switzerland(void) {SetCountry("Switerland"); SetNews();}//Class construct
  };

Свойства, видимые из объекта Switzerland:

Свойства, видимые из объекта

Результаты:

Результаты объектов


Классы перехода на летнее время

В первой части серии "Упрощаем торговлю на новостях":

Диаграмма классов UML

Диаграмма классов UML (классы DaylightSavings) Часть 1

Файлы проекта:

Классы DaylightSavings Часть 1

В предыдущем коде у нас было три класса перехода на летнее время, а именно:

  • CDaylightSavings_AU
  • CDaylightSavings_UK
  • CDaylightSavings_US


В части 2:

Диаграмма классов UML

Диаграмма классов UML (классы перехода на летнее время) Часть 2

Файлы проекта:

Классы перехода на летнее время Часть 2

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

  • CDaylightSavings
  • CDaylightSavings_AU
  • CDaylightSavings_UK
  • CDaylightSavings_US


Зачем создавать еще один класс?

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


Что такое виртуальные функции?

В объектно-ориентированном программировании (ООП) виртуальная функция — это функция-член базового класса, которую можно переопределить в производном классе. Когда функция объявлена как виртуальная, она включает полиморфизм, позволяя производному классу предоставлять конкретную реализацию функции, которую можно вызывать через указатель или ссылку базового класса.

Для чего это делается?

  • Полиморфизм: Виртуальные функции допускают динамический полиморфизм. Это означает, что метод, который будет выполнен, определяется во время выполнения на основе фактического типа объекта, на который ссылаются, а не типа ссылки или указателя.
  • Гибкость: обеспечивается более гибкий и повторно используемый код, позволяя производным классам изменять или расширять поведение базового класса.
  • Разделение: виртуальные функции помогают разделить код, отделяя интерфейс от реализации, что упрощает изменение реализации без влияния на код, использующий интерфейс базового класса.


Класс CDaylightSavings

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

Класс CDaylightSavings имеет единственное наследование от класса CObject.

Класс CDaylightSavings имеет включения из классов:

  •  CArrayObj 
  •  CTimeManagement

//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+

#include <Object.mqh>

#include <Arrays\ArrayObj.mqh>
#include "../TimeManagement.mqh"

//+------------------------------------------------------------------+
//|DaylightSavings class                                             |
//+------------------------------------------------------------------+
class CDaylightSavings: public CObject
  {

protected:
   CTimeManagement   Time;
                     CDaylightSavings(datetime startdate,datetime enddate);
   CObject           *List() { return savings;}//Gets the list of Daylightsavings time
   datetime          StartDate;
   datetime          EndDate;
   CArrayObj         *savings;
   CArrayObj         *getSavings;
   CDaylightSavings      *dayLight;
   virtual void      SetDaylightSavings_UK();//Initialize UK Daylight Savings Dates into List
   virtual void      SetDaylightSavings_US();//Initialize US Daylight Savings Dates into List
   virtual void      SetDaylightSavings_AU();//Initialize AU Daylight Savings Dates into List

public:
                     CDaylightSavings(void);
                    ~CDaylightSavings(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 DaylightSavings Dates are available for a certain Year
   string            adjustDaylightSavings(datetime EventDate);//Will adjust the date's timezone depending on DaylightSavings
  };


//+------------------------------------------------------------------+
//|Constructor                                                       |
//+------------------------------------------------------------------+
CDaylightSavings::CDaylightSavings(void)
  {
  }

//+------------------------------------------------------------------+
//|Initialize variables                                              |
//+------------------------------------------------------------------+
CDaylightSavings::CDaylightSavings(datetime startdate,datetime enddate)
  {
   StartDate = startdate;//Assign class's global variable StartDate value from parameter variable startdate
   EndDate = enddate;//Assign class's global variable EndDate value from parameter variable enddate
  }

//+------------------------------------------------------------------+
//|checks if a given date falls within Daylight Savings Time         |
//+------------------------------------------------------------------+
bool CDaylightSavings::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;
  }

//+------------------------------------------------------------------+
//|Check if DaylightSavings Dates are available for a certain Year   |
//+------------------------------------------------------------------+
bool CDaylightSavings::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 DaylightSavings 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 DaylightSavings end dates in the List
        {
         endDate = dayLight.EndDate;
         endDateDetected = true;
        }
      if(startDateDetected&&endDateDetected)//Check if both DaylightSavings 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 DaylightSaving date is found
   endDate = D'1970.01.01 00:00:00';//Set a default end date if no DaylightSaving date is found
   return false;
  }

//+------------------------------------------------------------------+
//|Will adjust the date's timezone depending on DaylightSavings      |
//+------------------------------------------------------------------+
string CDaylightSavings::adjustDaylightSavings(datetime EventDate)
  {
   if(isDaylightSavings(TimeTradeServer()))//Check if the current tradeserver time is already within the DaylightSavings Period
     {
      if(isDaylightSavings(EventDate))//Checks if the event time is during daylight savings
        {
         return TimeToString(EventDate);//normal event time
        }
      else
        {
         return TimeToString((datetime)(EventDate-Time.HoursS()));//event time minus an hour for DST
        }
     }
   else
     {
      if(isDaylightSavings(EventDate))//Checks if the event time is during daylight savings
        {
         return TimeToString((datetime)(Time.HoursS()+EventDate));//event time plus an hour for DST
        }
      else
        {
         return TimeToString(EventDate);//normal event time
        }
     }
  }

//+------------------------------------------------------------------+
//|Destructor                                                        |
//+------------------------------------------------------------------+
CDaylightSavings::~CDaylightSavings(void)
  {
   delete savings;//Delete CArrayObj Pointer
   delete dayLight;//Delete CDaylightSavings Pointer
   delete getSavings;//Delete CArrayObj Pointer
  }
//+------------------------------------------------------------------+


Класс CDaylightSavings_AU

Здесь мы расширяем виртуальный void SetDaylightSavings_AU и приступаем к добавлению графика перехода на летнее время для Австралии.

Даты летнего времени для Австралии были найдены здесь.

Класс CDaylightSavings_AU имеет многоуровневое наследование от классов:

  • CDaylightSavings 
  • CObject

Класс CDaylightSavings_AU располагает иерархическим наследованием от классов:

  • CDaylightSavings
  • CArrayObj
  • CTimeManagement

//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+

#include "DaylightSavings.mqh"

//+------------------------------------------------------------------+
//|DaylightSavings_AU class                                          |
//+------------------------------------------------------------------+
class CDaylightSavings_AU: public CDaylightSavings
  {

public:

                     CDaylightSavings_AU(void);
  };

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

//+------------------------------------------------------------------+
//|Constructor                                                       |
//+------------------------------------------------------------------+
CDaylightSavings_AU::CDaylightSavings_AU(void)
  {
   SetDaylightSavings_AU();
  }
//+------------------------------------------------------------------+


Класс CDaylightSavings_UK

Здесь мы расширяем виртуальный void SetDaylightSavings_UK и приступаем к добавлению графика перехода на летнее время для Европы.

Даты летнего времени для Великобритании были найдены здесь.

Класс CDaylightSavings_UK имеет многоуровневое наследование от классов:

  • CDaylightSavings 
  • CObject

Класс CDaylightSavings_UK располагает иерархическим наследованием от классов:

  • CDaylightSavings
  • CArrayObj
  • CTimeManagement

//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+

#include "DaylightSavings.mqh"

//+------------------------------------------------------------------+
//|DaylightSavings_UK class                                          |
//+------------------------------------------------------------------+
class CDaylightSavings_UK: public CDaylightSavings
  {

public:

                     CDaylightSavings_UK(void);
  };

//+------------------------------------------------------------------+
//|Set Daylight Savings Schedule for Europe                          |
//+------------------------------------------------------------------+
void CDaylightSavings::SetDaylightSavings_UK()
  {
   savings = new CArrayObj();
//Daylight savings dates to readjust dates in the database for accurate testing in the strategy tester
   savings.Add(new CDaylightSavings(D'2007.03.25 02:00:00',D'2007.10.28 01:00:00'));
   savings.Add(new CDaylightSavings(D'2008.03.30 02:00:00',D'2008.10.26 01:00:00'));
   savings.Add(new CDaylightSavings(D'2009.03.29 02:00:00',D'2009.10.25 01:00:00'));
   savings.Add(new CDaylightSavings(D'2010.03.28 02:00:00',D'2010.10.31 01:00:00'));
   savings.Add(new CDaylightSavings(D'2011.03.27 02:00:00',D'2011.10.30 01:00:00'));
   savings.Add(new CDaylightSavings(D'2012.03.25 02:00:00',D'2012.10.28 01:00:00'));
   savings.Add(new CDaylightSavings(D'2013.03.31 02:00:00',D'2013.10.27 01:00:00'));
   savings.Add(new CDaylightSavings(D'2014.03.30 02:00:00',D'2014.10.26 01:00:00'));
   savings.Add(new CDaylightSavings(D'2015.03.29 02:00:00',D'2015.10.25 01:00:00'));
   savings.Add(new CDaylightSavings(D'2016.03.27 02:00:00',D'2016.10.30 01:00:00'));
   savings.Add(new CDaylightSavings(D'2017.03.26 02:00:00',D'2017.10.29 01:00:00'));
   savings.Add(new CDaylightSavings(D'2018.03.25 02:00:00',D'2018.10.28 01:00:00'));
   savings.Add(new CDaylightSavings(D'2019.03.31 02:00:00',D'2019.10.27 01:00:00'));
   savings.Add(new CDaylightSavings(D'2020.03.29 02:00:00',D'2020.10.25 01:00:00'));
   savings.Add(new CDaylightSavings(D'2021.03.28 02:00:00',D'2021.10.31 01:00:00'));
   savings.Add(new CDaylightSavings(D'2022.03.27 02:00:00',D'2022.10.30 01:00:00'));
   savings.Add(new CDaylightSavings(D'2023.03.26 02:00:00',D'2023.10.29 01:00:00'));
   savings.Add(new CDaylightSavings(D'2024.03.31 02:00:00',D'2024.10.27 01:00:00'));
   savings.Add(new CDaylightSavings(D'2025.03.30 02:00:00',D'2025.10.26 01:00:00'));
   savings.Add(new CDaylightSavings(D'2026.03.29 02:00:00',D'2026.10.25 01:00:00'));
   savings.Add(new CDaylightSavings(D'2027.03.28 02:00:00',D'2027.10.31 01:00:00'));
   savings.Add(new CDaylightSavings(D'2028.03.26 02:00:00',D'2028.10.29 01:00:00'));
   savings.Add(new CDaylightSavings(D'2029.03.25 02:00:00',D'2029.10.28 01:00:00'));
  }

//+------------------------------------------------------------------+
//|Constructor                                                       |
//+------------------------------------------------------------------+
CDaylightSavings_UK::CDaylightSavings_UK(void)
  {
   SetDaylightSavings_UK();
  }
//+------------------------------------------------------------------+


Класс CDaylightSavings_US

Здесь мы расширяем виртуальный void SetDaylightSavings_US и приступаем к добавлению графика перехода на летнее время для США.

Даты летнего времени для США были найдены здесь.

Класс CDaylightSavings_US имеет многоуровневое наследование от классов:

  • CDaylightSavings 
  • CObject

Класс CDaylightSavings_US располагает иерархическим наследованием от классов:

  • CDaylightSavings
  • CArrayObj
  • CTimeManagement

//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+

#include "DaylightSavings.mqh"

//+------------------------------------------------------------------+
//|DaylightSavings_US Class                                          |
//+------------------------------------------------------------------+
class CDaylightSavings_US: public CDaylightSavings
  {

public:

                     CDaylightSavings_US(void);
  };

//+------------------------------------------------------------------+
//|Set Daylight Savings Schedule for the United States               |
//+------------------------------------------------------------------+
void CDaylightSavings::SetDaylightSavings_US()
  {
   savings = new CArrayObj();
//Daylight savings dates to readjust dates in the database for accurate testing in the strategy tester
   savings.Add(new CDaylightSavings(D'2007.03.11 03:00:00',D'2007.11.04 01:00:00'));
   savings.Add(new CDaylightSavings(D'2008.03.09 03:00:00',D'2008.11.02 01:00:00'));
   savings.Add(new CDaylightSavings(D'2009.03.08 03:00:00',D'2009.11.01 01:00:00'));
   savings.Add(new CDaylightSavings(D'2010.03.14 03:00:00',D'2010.11.07 01:00:00'));
   savings.Add(new CDaylightSavings(D'2011.03.13 03:00:00',D'2011.11.06 01:00:00'));
   savings.Add(new CDaylightSavings(D'2012.03.11 03:00:00',D'2012.11.04 01:00:00'));
   savings.Add(new CDaylightSavings(D'2013.03.10 03:00:00',D'2013.11.03 01:00:00'));
   savings.Add(new CDaylightSavings(D'2014.03.09 03:00:00',D'2014.11.02 01:00:00'));
   savings.Add(new CDaylightSavings(D'2015.03.08 03:00:00',D'2015.11.01 01:00:00'));
   savings.Add(new CDaylightSavings(D'2016.03.13 03:00:00',D'2016.11.06 01:00:00'));
   savings.Add(new CDaylightSavings(D'2017.03.12 03:00:00',D'2017.11.05 01:00:00'));
   savings.Add(new CDaylightSavings(D'2018.03.11 03:00:00',D'2018.11.04 01:00:00'));
   savings.Add(new CDaylightSavings(D'2019.03.10 03:00:00',D'2019.11.03 01:00:00'));
   savings.Add(new CDaylightSavings(D'2020.03.08 03:00:00',D'2020.11.01 01:00:00'));
   savings.Add(new CDaylightSavings(D'2021.03.14 03:00:00',D'2021.11.07 01:00:00'));
   savings.Add(new CDaylightSavings(D'2022.03.13 03:00:00',D'2022.11.06 01:00:00'));
   savings.Add(new CDaylightSavings(D'2023.03.12 03:00:00',D'2023.11.05 01:00:00'));
   savings.Add(new CDaylightSavings(D'2024.03.10 03:00:00',D'2024.11.03 01:00:00'));
   savings.Add(new CDaylightSavings(D'2025.03.09 03:00:00',D'2025.11.02 01:00:00'));
   savings.Add(new CDaylightSavings(D'2026.03.08 03:00:00',D'2026.11.01 01:00:00'));
   savings.Add(new CDaylightSavings(D'2027.03.14 03:00:00',D'2027.11.07 01:00:00'));
   savings.Add(new CDaylightSavings(D'2028.03.12 03:00:00',D'2028.11.05 01:00:00'));
   savings.Add(new CDaylightSavings(D'2029.03.11 03:00:00',D'2029.11.04 01:00:00'));
  }

//+------------------------------------------------------------------+
//|Constructor                                                       |
//+------------------------------------------------------------------+
CDaylightSavings_US::CDaylightSavings_US(void)
  {
   SetDaylightSavings_US();
  }
//+------------------------------------------------------------------+


Класс свойств символа

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

Мы выберем комбинацию свойств, чтобы получить список, который может быть расширен. В противном случае список будет выглядеть так:

  • Ask Price (цена Ask)
  • Bid Price (цена Bid)
  • Contract Size (размер контракта)
  • Minimum Volume (минимальный объем)
  • Maximum Volume (максимальный объем)
  • Volume Step (шаг объема)
  • Volume Limit (лимит объема)
  • Spread (спред)
  • Stops Level (уровень стопов)
  • Freeze Level (уровень заморозки)
  • Symbol's Time (время символа)
  • Symbol's Normalized Price (нормализованная цена символа)
  • Symbol's Digits (количество знаков после запятой символа)
  • Symbol's Point (пункт символа)
  • Symbol's Trade Mode (режим торговли символом)
  • Sum of Symbol's Orders' Volume (суммарный объем ордеров символа)
  • Sum of Symbol's Positions' Volume (суммарный объем позиций символа)
  • Symbol's Currency Base (основная валюта символа)
  • Symbol's Currency Profit (валюта прибыли символа)
  • Symbol's Currency Margin (валюта маржи символа)
  • Symbol's Custom status (пользовательский статус символа)
  • Symbol's Background color (цвет фона символа)

Класс CSymbolProperties имеет включение из класса CSymbolInfo.

//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+
#include <Trade/SymbolInfo.mqh>
//+------------------------------------------------------------------+
//|SymbolProperties class                                            |
//+------------------------------------------------------------------+
class CSymbolProperties
  {
private:
   double            ASK;//Store Ask Price
   double            BID;//Store Bid Price
   double            LOTSMIN;//Store Minimum Lotsize
   double            LOTSMAX;//Store Maximum Lotsize
   double            LOTSSTEP;//Store Lotsize Step
   double            LOTSLIMIT;//Store Lotsize Limit(Maximum sum of Volume)
   long              SPREAD;//Store Spread value
   long              STOPLEVEL;//Store Stop level
   long              FREEZELEVEL;//Store Freeze level
   long              TIME;//Store time
   long              DIGITS;//Store Digits
   double            POINT;//Store Point
   double            ORDERSVOLUME;//Store Orders volume
   double            POSITIONSVOLUME;//Store Positions volume
   long              CUSTOM;//Store if Symbol is Custom
   long              BACKGROUND_CLR;//Store Symbol's background color

protected:
   CSymbolInfo       CSymbol;//Creating class CSymbolInfo's Object
   bool              SetSymbolName(string SYMBOL)
     {
      //-- If Symbol's name was successfully set.
      if(!CSymbol.Name((SYMBOL==NULL)?Symbol():SYMBOL))
        {
         Print("Invalid Symbol: ",SYMBOL);
         return false;
        }
      return true;
     }

   //-- Retrieve Symbol's name
   string            GetSymbolName()
     {
      return CSymbol.Name();
     }

public:
                     CSymbolProperties(void);//Constructor
   double            Ask(string SYMBOL=NULL);//Retrieve Ask Price
   double            Bid(string SYMBOL=NULL);//Retrieve Bid Price
   double            ContractSize(string SYMBOL=NULL);//Retrieve Contract Size
   double            LotsMin(string SYMBOL=NULL);//Retrieve Min Volume
   double            LotsMax(string SYMBOL=NULL);//Retrieve Max Volume
   double            LotsStep(string SYMBOL=NULL);//Retrieve Volume Step
   double            LotsLimit(string SYMBOL=NULL);//Retrieve Volume Limit
   int               Spread(string SYMBOL=NULL);//Retrieve Spread
   int               StopLevel(string SYMBOL=NULL);//Retrieve Stop Level
   int               FreezeLevel(string SYMBOL=NULL);//Retrieve Freeze Level
   datetime          Time(string SYMBOL=NULL);//Retrieve Symbol's Time
   //-- Normalize Price
   double            NormalizePrice(const double price,string SYMBOL=NULL);
   int               Digits(string SYMBOL=NULL);//Retrieve Symbol's Digits
   double            Point(string SYMBOL=NULL);//Retrieve Symbol's Point
   ENUM_SYMBOL_TRADE_MODE TradeMode(string SYMBOL=NULL);//Retrieve Symbol's Trade Mode
   double            OrdersVolume(string SYMBOL=NULL);//Retrieve Symbol's Orders Volume
   double            PositionsVolume(string SYMBOL=NULL);//Retrieve Symbol's Positions Volume
   string            CurrencyBase(string SYMBOL=NULL);//Retrieve Symbol's Currency Base
   string            CurrencyProfit(string SYMBOL=NULL);//Retrieve Symbol's Currency Profit
   string            CurrencyMargin(string SYMBOL=NULL);//Retrieve Symbol's Currency Margin
   bool              Custom(string SYMBOL=NULL);//Retrieve Symbol's Custom status
   color             SymbolBackground(string SYMBOL=NULL,bool allow_black=false);//Retrieve Symbol's Background color
  };

//+------------------------------------------------------------------+
//|Constructor                                                       |
//+------------------------------------------------------------------+
//Initializing Variables
CSymbolProperties::CSymbolProperties(void):ASK(0.0),BID(0.0),
   LOTSMIN(0.0),LOTSMAX(0.0),
   LOTSSTEP(0.0),LOTSLIMIT(0.0),DIGITS(0),
   SPREAD(0),STOPLEVEL(0),ORDERSVOLUME(0.0),
   FREEZELEVEL(0),TIME(0),POINT(0.0),POSITIONSVOLUME(0.0),
   CUSTOM(0),BACKGROUND_CLR(0)
  {
  }

//+------------------------------------------------------------------+
//|Retrieve Ask Price                                                |
//+------------------------------------------------------------------+
double CSymbolProperties::Ask(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_ASK,ASK))
        {
         return ASK;
        }
     }
   Print("Unable to retrieve Symbol's Ask Price");
   return 0.0;
  }

//+------------------------------------------------------------------+
//|Retrieve Bid Price                                                |
//+------------------------------------------------------------------+
double CSymbolProperties::Bid(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_BID,BID))
        {
         return BID;
        }
     }
   Print("Unable to retrieve Symbol's Bid Price");
   return 0.0;
  }

//+------------------------------------------------------------------+
//|Retrieve Contract Size                                            |
//+------------------------------------------------------------------+
double CSymbolProperties::ContractSize(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh())
        {
         return CSymbol.ContractSize();
        }
     }
   Print("Unable to retrieve Symbol's Contract size");
   return 0.0;
  }

//+------------------------------------------------------------------+
//|Retrieve Min Volume                                               |
//+------------------------------------------------------------------+
double CSymbolProperties::LotsMin(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_VOLUME_MIN,LOTSMIN))
        {
         return LOTSMIN;
        }
     }
   Print("Unable to retrieve Symbol's LotsMin");
   return 0.0;
  }

//+------------------------------------------------------------------+
//|Retrieve Max Volume                                               |
//+------------------------------------------------------------------+
double CSymbolProperties::LotsMax(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_VOLUME_MAX,LOTSMAX))
        {
         return LOTSMAX;
        }
     }
   Print("Unable to retrieve Symbol's LotsMax");
   return 0.0;
  }

//+------------------------------------------------------------------+
//|Retrieve Volume Step                                              |
//+------------------------------------------------------------------+
double CSymbolProperties::LotsStep(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_VOLUME_STEP,LOTSSTEP))
        {
         return LOTSSTEP;
        }
     }
   Print("Unable to retrieve Symbol's LotsStep");
   return 0.0;
  }

//+------------------------------------------------------------------+
//|Retrieve Volume Limit                                             |
//+------------------------------------------------------------------+
double CSymbolProperties::LotsLimit(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_VOLUME_LIMIT,LOTSLIMIT))
        {
         return LOTSLIMIT;
        }
     }
   Print("Unable to retrieve Symbol's LotsLimit");
   return 0.0;
  }

//+------------------------------------------------------------------+
//|Retrieve Spread                                                   |
//+------------------------------------------------------------------+
int CSymbolProperties::Spread(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_SPREAD,SPREAD))
        {
         return int(SPREAD);
        }
     }
   Print("Unable to retrieve Symbol's Spread");
   return 0;
  }

//+------------------------------------------------------------------+
//|Retrieve Stop Level                                               |
//+------------------------------------------------------------------+
int CSymbolProperties::StopLevel(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_TRADE_STOPS_LEVEL,STOPLEVEL))
        {
         return int(STOPLEVEL);
        }
     }
   Print("Unable to retrieve Symbol's StopLevel");
   return 0;
  }

//+------------------------------------------------------------------+
//|Retrieve Freeze Level                                             |
//+------------------------------------------------------------------+
int CSymbolProperties::FreezeLevel(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_TRADE_FREEZE_LEVEL,FREEZELEVEL))
        {
         return int(FREEZELEVEL);
        }
     }
   Print("Unable to retrieve Symbol's FreezeLevel");
   return 0;
  }

//+------------------------------------------------------------------+
//|Retrieve Symbol's Time                                            |
//+------------------------------------------------------------------+
datetime CSymbolProperties::Time(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_TIME,TIME))
        {
         return datetime(TIME);
        }
     }
   Print("Unable to retrieve Symbol's Time");
   TIME=0;
   return datetime(TIME);
  }

//+------------------------------------------------------------------+
//|Normalize Price                                                   |
//+------------------------------------------------------------------+
double CSymbolProperties::NormalizePrice(const double price,string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh()&&CSymbol.RefreshRates())
        {
         return CSymbol.NormalizePrice(price);
        }
     }
   Print("Unable to Normalize Symbol's Price");
   return price;
  }

//+------------------------------------------------------------------+
//|Retrieve Symbol's Digits                                          |
//+------------------------------------------------------------------+
int CSymbolProperties::Digits(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_DIGITS,DIGITS))
        {
         return int(DIGITS);
        }
     }
   Print("Unable to retrieve Symbol's Digits");
   return 0;
  }

//+------------------------------------------------------------------+
//|Retrieve Symbol's Point                                           |
//+------------------------------------------------------------------+
double CSymbolProperties::Point(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_POINT,POINT))
        {
         return POINT;
        }
     }
   Print("Unable to retrieve Symbol's Point");
   return 0.0;
  }

//+------------------------------------------------------------------+
//|Retrieve Symbol's Trade Mode                                      |
//+------------------------------------------------------------------+
ENUM_SYMBOL_TRADE_MODE CSymbolProperties::TradeMode(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh())
        {
         return CSymbol.TradeMode();
        }
     }
   Print("Unable to retrieve Symbol's TradeMode");
   return SYMBOL_TRADE_MODE_DISABLED;
  }

//+------------------------------------------------------------------+
//|Retrieve Symbol's Orders Volume                                   |
//+------------------------------------------------------------------+
double CSymbolProperties::OrdersVolume(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      for(int i=0; i<OrdersTotal(); i++)
        {
         if(OrderSelect(OrderGetTicket(i)))
           {
            if(OrderGetString(ORDER_SYMBOL)==GetSymbolName())
              {
               ORDERSVOLUME+=OrderGetDouble(ORDER_VOLUME_CURRENT);
              }
           }
        }
     }
   else
     {
      Print("Unable to retrieve Symbol's OrdersVolume");
      return 0.0;
     }
   return ORDERSVOLUME;
  }

//+------------------------------------------------------------------+
//|Retrieve Symbol's Positions Volume                                |
//+------------------------------------------------------------------+
double CSymbolProperties::PositionsVolume(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      for(int i=0; i<PositionsTotal(); i++)
        {
         if(PositionGetTicket(i)>0)
           {
            if(PositionGetString(POSITION_SYMBOL)==GetSymbolName())
              {
               POSITIONSVOLUME+=PositionGetDouble(POSITION_VOLUME);
              }
           }
        }
     }
   else
     {
      Print("Unable to retrieve Symbol's PositionsVolume");
      return 0.0;
     }
   return POSITIONSVOLUME;
  }

//+------------------------------------------------------------------+
//|Retrieve Symbol's Currency Base                                   |
//+------------------------------------------------------------------+
string CSymbolProperties::CurrencyBase(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh())
        {
         return CSymbol.CurrencyBase();
        }
     }
   Print("Unable to retrieve Symbol's CurrencyBase");
   return "";
  }
//+------------------------------------------------------------------+
//|Retrieve Symbol's Currency Profit                                 |
//+------------------------------------------------------------------+
string CSymbolProperties::CurrencyProfit(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh())
        {
         return CSymbol.CurrencyProfit();
        }
     }
   Print("Unable to retrieve Symbol's CurrencyProfit");
   return "";
  }
//+------------------------------------------------------------------+
//|Retrieve Symbol's Currency Margin                                 |
//+------------------------------------------------------------------+
string CSymbolProperties::CurrencyMargin(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh())
        {
         return CSymbol.CurrencyMargin();
        }
     }
   Print("Unable to retrieve Symbol's CurrencyMargin");
   return "";
  }

//+------------------------------------------------------------------+
//|Retrieve Symbol's Custom status                                   |
//+------------------------------------------------------------------+
bool CSymbolProperties::Custom(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_CUSTOM,CUSTOM))
        {
         return bool(CUSTOM);
        }
     }
   Print("Unable to retrieve if Symbol is Custom");
   return false;
  }

//+------------------------------------------------------------------+
//|Retrieve Symbol's Background color                                |
//+------------------------------------------------------------------+
color CSymbolProperties::SymbolBackground(string SYMBOL=NULL,bool allow_black=false)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_BACKGROUND_COLOR,BACKGROUND_CLR))
        {
         /*Avoid any Symbol black background color */
         BACKGROUND_CLR = ((ColorToString(color(BACKGROUND_CLR))=="0,0,0"||
                            color(BACKGROUND_CLR)==clrBlack)&&!allow_black)?
                          long(StringToColor("236,236,236")):BACKGROUND_CLR;
         return color(BACKGROUND_CLR);
        }
     }
   Print("Unable to retrieve Symbol's Background color");
   return color(StringToColor("236,236,236"));//Retrieve a lightish gray color
  }
//+------------------------------------------------------------------+

В конструкторе класса ниже мы инициализируем переменные, которые мы объявили ранее, такие как double-переменная ASK. Мы устанавливаем для ASK значение 0,0, поскольку у нас еще нет цены Ask для Symbol.

//Initializing Variables
CSymbolProperties::CSymbolProperties(void):ASK(0.0),BID(0.0),
   LOTSMIN(0.0),LOTSMAX(0.0),
   LOTSSTEP(0.0),LOTSLIMIT(0.0),DIGITS(0),
   SPREAD(0),STOPLEVEL(0),ORDERSVOLUME(0.0),
   FREEZELEVEL(0),TIME(0),POINT(0.0),POSITIONSVOLUME(0.0),
   CUSTOM(0),BACKGROUND_CLR(0)
  {
  }

В приведенном ниже коде мы сделаем ряд шагов, чтобы в конечном итоге получить цену Ask символа.

Цена Ask

1. Во-первых, у нас есть необязательный параметр, с помощью которого мы можем решить ввести или отредактировать имя символа в переменной SYMBOL. 

2. Затем мы устанавливаем имя символа в соответствии со значением параметра. Если значение параметра по-прежнему равно значению по умолчанию NULL, мы предполагаем, что нам нужны свойства для текущего символа графика - Symbol().

3. Если мы не сможем найти имя символа, выведем сообщение об ошибке, чтобы уведомить пользователя о том, что цена Ask символа не может быть получена, и вернем 0,0.

4. Как только мы сможем задать имя символа, мы получим цену Ask для этого конкретного символа и вернем значение.

double CSymbolProperties::Ask(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))
     {
      if(CSymbol.InfoDouble(SYMBOL_ASK,ASK))
        {
         return ASK;
        }
     }
   Print("Unable to retrieve Symbol's Ask Price");
   return 0.0;
  }
   bool              SetSymbolName(string SYMBOL)
     {
      //-- If Symbol's name was successfully set.
      if(!CSymbol.Name((SYMBOL==NULL)?Symbol():SYMBOL))
        {
         Print("Invalid Symbol: ",SYMBOL);
         return false;
        }
      return true;
     }

Функция ниже извлекает цену Bid символа на основе переменной SYMBOL.

Цена Bid

double CSymbolProperties::Bid(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_BID,BID))
        {
         return BID;
        }
     }
   Print("Unable to retrieve Symbol's Bid Price");
   return 0.0;
  }

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

Размер контракта

double CSymbolProperties::ContractSize(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh())
        {
         return CSymbol.ContractSize();
        }
     }
   Print("Unable to retrieve Symbol's Contract size");
   return 0.0;
  }

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

Минимальный объем

double CSymbolProperties::LotsMin(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_VOLUME_MIN,LOTSMIN))
        {
         return LOTSMIN;
        }
     }
   Print("Unable to retrieve Symbol's LotsMin");
   return 0.0;
  }

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

Максимальный объем


double CSymbolProperties::LotsMax(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_VOLUME_MAX,LOTSMAX))
        {
         return LOTSMAX;
        }
     }
   Print("Unable to retrieve Symbol's LotsMax");
   return 0.0;
  }

Функция ниже извлекает шаг объема символа/размера лота. Это означает, что размер лота должен иметь интервал указанного значения. Например, если шаг объема равен 1, трейдер не может выбрать размер лота/объем 1,5. 

Шаг объема


double CSymbolProperties::LotsStep(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_VOLUME_STEP,LOTSSTEP))
        {
         return LOTSSTEP;
        }
     }
   Print("Unable to retrieve Symbol's LotsStep");
   return 0.0;
  }

Функция ниже извлекает лимит объема символа/размера лота. Это сумма объемов/размеров лотов, которые разрешено размещать до того, как на счет трейдера будут наложены ограничения для конкретного символа.

Лимит объема
double CSymbolProperties::LotsLimit(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_VOLUME_LIMIT,LOTSLIMIT))
        {
         return LOTSLIMIT;
        }
     }
   Print("Unable to retrieve Symbol's LotsLimit");
   return 0.0;
  }

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

Спред

int CSymbolProperties::Spread(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_SPREAD,SPREAD))
        {
         return int(SPREAD);
        }
     }
   Print("Unable to retrieve Symbol's Spread");
   return 0;
  }

Функция ниже извлекает уровень стопов символа. Это ограничение на минимальное расстояние между ценой открытия и стоп-лоссом или тейк-профитом, а также на минимальное расстояние между текущей ценой Ask или Bid и ценой открытия ордера.

Уровень стопов

int CSymbolProperties::StopLevel(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_TRADE_STOPS_LEVEL,STOPLEVEL))
        {
         return int(STOPLEVEL);
        }
     }
   Print("Unable to retrieve Symbol's StopLevel");
   return 0;
  }

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

int CSymbolProperties::FreezeLevel(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_TRADE_FREEZE_LEVEL,FREEZELEVEL))
        {
         return int(FREEZELEVEL);
        }
     }
   Print("Unable to retrieve Symbol's FreezeLevel");
   return 0;
  }

Функция ниже извлекает время символа.

Время

datetime CSymbolProperties::Time(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_TIME,TIME))
        {
         return datetime(TIME);
        }
     }
   Print("Unable to retrieve Symbol's Time");
   TIME=0;
   return datetime(TIME);
  }

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

Например, цена Ask для EURUSD составляет 1,07735, и вы пытаетесь открыть сделку на покупку по цене 1,077351. Вы можете получить ошибку "Недопустимая цена", так как количество цифр после запятой превышает допустимое число, например 5 цифр. Эта функция возьмет цену из 6 цифр и преобразует ее в 5 цифр, тем самым нормализуя цену.

double CSymbolProperties::NormalizePrice(const double price,string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh()&&CSymbol.RefreshRates())
        {
         return CSymbol.NormalizePrice(price);
        }
     }
   Print("Unable to Normalize Symbol's Price");
   return price;
  }

Функция ниже извлекает цифры символа. Цифры представлены в виде десятичных знаков цены символа.

Количество знаков в котировках

Число знаков после запятой в символе равно 3.

int CSymbolProperties::Digits(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_DIGITS,DIGITS))
        {
         return int(DIGITS);
        }
     }
   Print("Unable to retrieve Symbol's Digits");
   return 0;
  }

Функция ниже извлекает пункт символа.

double CSymbolProperties::Point(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoDouble(SYMBOL_POINT,POINT))
        {
         return POINT;
        }
     }
   Print("Unable to retrieve Symbol's Point");
   return 0.0;
  }

Функция ниже извлекает режим торговли символа.

Торговый режим

ENUM_SYMBOL_TRADE_MODE CSymbolProperties::TradeMode(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh())
        {
         return CSymbol.TradeMode();
        }
     }
   Print("Unable to retrieve Symbol's TradeMode");
   return SYMBOL_TRADE_MODE_DISABLED;
  }

Функция ниже извлекает суммарный объем/количество лотов ордеров символа.

double CSymbolProperties::OrdersVolume(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      for(int i=0; i<OrdersTotal(); i++)
        {
         if(OrderSelect(OrderGetTicket(i)))
           {
            if(OrderGetString(ORDER_SYMBOL)==GetSymbolName())
              {
               ORDERSVOLUME+=OrderGetDouble(ORDER_VOLUME_CURRENT);
              }
           }
        }
     }
   else
     {
      Print("Unable to retrieve Symbol's OrdersVolume");
      return 0.0;
     }
   return ORDERSVOLUME;
  }

Функция ниже извлекает суммарный объем/количество лотов позиций символа.

double CSymbolProperties::PositionsVolume(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      for(int i=0; i<PositionsTotal(); i++)
        {
         if(PositionGetTicket(i)>0)
           {
            if(PositionGetString(POSITION_SYMBOL)==GetSymbolName())
              {
               POSITIONSVOLUME+=PositionGetDouble(POSITION_VOLUME);
              }
           }
        }
     }
   else
     {
      Print("Unable to retrieve Symbol's PositionsVolume");
      return 0.0;
     }
   return POSITIONSVOLUME;
  }

Функция ниже извлекает базовую валюту символа.

string CSymbolProperties::CurrencyBase(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh())
        {
         return CSymbol.CurrencyBase();
        }
     }
   Print("Unable to retrieve Symbol's CurrencyBase");
   return "";
  }

Функция ниже извлекает валюту прибыли символа.

Валюта прибыли

string CSymbolProperties::CurrencyProfit(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh())
        {
         return CSymbol.CurrencyProfit();
        }
     }
   Print("Unable to retrieve Symbol's CurrencyProfit");
   return "";
  }

Функция ниже извлекает валюту маржи символа.

Валюта маржи

string CSymbolProperties::CurrencyMargin(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.Refresh())
        {
         return CSymbol.CurrencyMargin();
        }
     }
   Print("Unable to retrieve Symbol's CurrencyMargin");
   return "";
  }

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

bool CSymbolProperties::Custom(string SYMBOL=NULL)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_CUSTOM,CUSTOM))
        {
         return bool(CUSTOM);
        }
     }
   Print("Unable to retrieve if Symbol is Custom");
   return false;
  }

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

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

Черный фон

color CSymbolProperties::SymbolBackground(string SYMBOL=NULL,bool allow_black=false)
  {
   if(SetSymbolName(SYMBOL))//Set Symbol
     {
      if(CSymbol.InfoInteger(SYMBOL_BACKGROUND_COLOR,BACKGROUND_CLR))
        {
        /*Avoid any Symbol black background color */
         BACKGROUND_CLR = ((ColorToString(color(BACKGROUND_CLR))=="0,0,0"||
                            color(BACKGROUND_CLR)==clrBlack)&&!allow_black)?
                          long(StringToColor("236,236,236")):BACKGROUND_CLR;
         return color(BACKGROUND_CLR);
        }
     }
   Print("Unable to retrieve Symbol's Background color");
   return color(StringToColor("236,236,236"));//Retrieve a lightish gray color
  }


Класс управления временем

В этом классе демонстрируются новые функции. Назначение класса - управление данными о времени.

//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|TimeManagement class                                              |
//+------------------------------------------------------------------+
class CTimeManagement
  {

private:

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

public:

   //-- Checks if a date is within two other dates
   bool              DateIsInRange(datetime FirstTime,datetime SecondTime,datetime compareTime);
   //-- Check if two dates(Start&End) are within CompareStart & CompareEnd
   bool              DateIsInRange(datetime Start,datetime End,datetime CompareStart,datetime 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
   int               ReturnMonth(datetime time);//Returns the Month for a specific date
   int               ReturnDay(datetime time);//Returns the Day for a specific date
   //-- Will return a datetime type of a date with an subtraction offset in seconds
   datetime          TimeMinusOffset(datetime standardtime,int timeoffset);
   //-- Will return a datetime type of a date with an addition offset in seconds
   datetime          TimePlusOffset(datetime standardtime,int timeoffset);
  };

//+------------------------------------------------------------------+
//|Checks if a date is within two other dates                        |
//+------------------------------------------------------------------+
bool CTimeManagement::DateIsInRange(datetime FirstTime,datetime SecondTime,datetime compareTime)
  {
   return(FirstTime<=compareTime&&SecondTime>compareTime);
  }

//+------------------------------------------------------------------+
//|Check if two dates(Start&End) are within CompareStart & CompareEnd|
//+------------------------------------------------------------------+
bool CTimeManagement::DateIsInRange(datetime Start,datetime End,datetime CompareStart,datetime CompareEnd)
  {
   return(Start<=CompareStart&&CompareEnd<End);
  }

//+------------------------------------------------------------------+
//|Checks if a date is within the current day                        |
//+------------------------------------------------------------------+
bool CTimeManagement::DateisToday(datetime TimeRepresented)
  {
   MqlDateTime TiM;
   TimeToStruct(TimeRepresented,TiM);
   TimeCurrent(today);
   return(TiM.year==today.year&&TiM.mon==today.mon&&TiM.day==today.day);
  }

//+------------------------------------------------------------------+
//|Returns seconds                                                   |
//+------------------------------------------------------------------+
int CTimeManagement::SecondsS(int multiple=1)
  {
   return (1*multiple);
  }

//+------------------------------------------------------------------+
//|Returns Minutes in seconds                                        |
//+------------------------------------------------------------------+
int CTimeManagement::MinutesS(int multiple=1)
  {
   return (SecondsS(60)*multiple);
  }

//+------------------------------------------------------------------+
//|Returns Hours in seconds                                          |
//+------------------------------------------------------------------+
int CTimeManagement::HoursS(int multiple=1)
  {
   return (MinutesS(60)*multiple);
  }

//+------------------------------------------------------------------+
//|Returns Days in seconds                                           |
//+------------------------------------------------------------------+
int CTimeManagement::DaysS(int multiple=1)
  {
   return (HoursS(24)*multiple);
  }

//+------------------------------------------------------------------+
//|Returns Weeks in seconds                                          |
//+------------------------------------------------------------------+
int CTimeManagement::WeeksS(int multiple=1)
  {
   return (DaysS(7)*multiple);
  }

//+------------------------------------------------------------------+
//|Returns Months in seconds                                         |
//+------------------------------------------------------------------+
int CTimeManagement::MonthsS(int multiple=1)
  {
   return (WeeksS(4)*multiple);
  }

//+------------------------------------------------------------------+
//|Returns Years in seconds                                          |
//+------------------------------------------------------------------+
int CTimeManagement::YearsS(int multiple=1)
  {
   return (MonthsS(12)*multiple);
  }

//+------------------------------------------------------------------+
//|Returns the Year for a specific date                              |
//+------------------------------------------------------------------+
int CTimeManagement::ReturnYear(datetime time)
  {
   TimeToStruct(time,timeFormat);
   return timeFormat.year;
  }

//+------------------------------------------------------------------+
//|Returns the Month for a specific date                             |
//+------------------------------------------------------------------+
int CTimeManagement::ReturnMonth(datetime time)
  {
   TimeToStruct(time,timeFormat);
   return timeFormat.mon;
  }

//+------------------------------------------------------------------+
//|Returns the Day for a specific date                               |
//+------------------------------------------------------------------+
int CTimeManagement::ReturnDay(datetime time)
  {
   TimeToStruct(time,timeFormat);
   return timeFormat.day;
  }

//+------------------------------------------------------------------+
//|Will return a datetime type of a date with an subtraction offset  |
//|in seconds                                                        |
//+------------------------------------------------------------------+
datetime CTimeManagement::TimeMinusOffset(datetime standardtime,int timeoffset)
  {
   standardtime-=timeoffset;
   return standardtime;
  }

//+------------------------------------------------------------------+
//|Will return a datetime type of a date with an addition offset     | 
//|in seconds                                                        |
//+------------------------------------------------------------------+
datetime CTimeManagement::TimePlusOffset(datetime standardtime,int timeoffset)
  {
   standardtime+=timeoffset;
   return standardtime;
  }
//+------------------------------------------------------------------+


Класс свойств графика

Назначение свойств графика - сохранение его свойств до внесения изменений. После удаления советника с графика деструктор класса восстановит его состояние до внесения изменений.

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

CChartProperties имеет единственное наследование от класса CSymbolProperties.

CChartProperties имеет иерархическое наследование от классов:

  • CSymbolProperties
  • CSymbolInfo
//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+
#include "SymbolProperties.mqh"
//+------------------------------------------------------------------+
//|ChartProperties class                                             |
//+------------------------------------------------------------------+
class CChartProperties : public CSymbolProperties
  {
private:
   struct ChartFormat
     {
      ulong             CHART_MODE;//Chart Candle Mode
      ulong             CHART_COLOR_BACKGROUND;//Chart Background Color
      ulong             CHART_COLOR_FOREGROUND;//Chart Foreground Color
      ulong             CHART_COLOR_CHART_LINE;//Chart Line Color
      ulong             CHART_COLOR_CANDLE_BEAR;//Chart Bear Candle Color
      ulong             CHART_COLOR_CHART_DOWN;//Chart Down Candle Color
      ulong             CHART_COLOR_CANDLE_BULL;//Chart Bull Candle Color
      ulong             CHART_COLOR_CHART_UP;//Chart Up Candle Color
      ulong             CHART_COLOR_ASK;//Chart Ask Color
      ulong             CHART_COLOR_BID;//Chart Bid Color
      ulong             CHART_COLOR_STOP_LEVEL;//Chart Stoplevel Color
      ulong             CHART_SHOW_PERIOD_SEP;//Chart Show Period Separator
      ulong             CHART_SCALE;//Chart Scale
      ulong             CHART_FOREGROUND;//Chart Show Foreground
      ulong             CHART_SHOW_ASK_LINE;//Chart Show Ask Line
      ulong             CHART_SHOW_BID_LINE;//Chart Show Bid Line
      ulong             CHART_SHOW_TRADE_LEVELS;//Chart Show Trade Levels
      ulong             CHART_SHOW_OHLC;//Chart Show Open-High-Low-Close
      ulong             CHART_SHOW_GRID;//Chart Show Grid
      ulong             CHART_SHOW_VOLUMES;//Chart Show Volumes
      ulong             CHART_AUTOSCROLL;//Chart Auto Scroll
      double            CHART_SHIFT_SIZE;//Chart Shift Size
      ulong             CHART_SHIFT;//Chart Shift
      ulong             CHART_SHOW_ONE_CLICK;//Chart One Click Trading
     };
   ulong             ChartConfig[65];//Array To Store Chart Properties
   void              ChartSet();//Apply Chart format
   void              ChartConfigure();//Set Chart Values
   ChartFormat       Chart;//Variable of type ChartFormat

public:
                     CChartProperties(void);//Constructor
                    ~CChartProperties(void);//Destructor
   void              ChartRefresh() {ChartConfigure();}
  };

//+------------------------------------------------------------------+
//|Constructor                                                       |
//+------------------------------------------------------------------+
CChartProperties::CChartProperties(void)//Class Constructor
  {
   for(int i=0;i<65;i++)//Iterating through ENUM_CHART_PROPERTY_INTEGER Elements
     {
      ChartGetInteger(0,(ENUM_CHART_PROPERTY_INTEGER)i,0,ChartConfig[i]);//Storing Chart values into ChartConfig array
     }
   ChartConfigure();
  }

//+------------------------------------------------------------------+
//|Destructor                                                        |
//+------------------------------------------------------------------+
CChartProperties::~CChartProperties(void)
  {
   for(int i=0;i<65;i++)//Iterating through ENUM_CHART_PROPERTY_INTEGER Elements
     {
      ChartSetInteger(0,(ENUM_CHART_PROPERTY_INTEGER)i,0,ChartConfig[i]);//Restoring Chart values from ChartConfig array
     }
  }

//+------------------------------------------------------------------+
//|Set Chart Properties                                              |
//+------------------------------------------------------------------+
void CChartProperties::ChartSet()
  {
   ChartSetInteger(0,CHART_MODE,Chart.CHART_MODE);//Set Chart Candle Mode
   ChartSetInteger(0,CHART_COLOR_BACKGROUND,Chart.CHART_COLOR_BACKGROUND);//Set Chart Background Color
   ChartSetInteger(0,CHART_COLOR_FOREGROUND,Chart.CHART_COLOR_FOREGROUND);//Set Chart Foreground Color
   ChartSetInteger(0,CHART_COLOR_CHART_LINE,Chart.CHART_COLOR_CHART_LINE);//Set Chart Line Color
   ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,Chart.CHART_COLOR_CANDLE_BEAR);//Set Chart Bear Candle Color
   ChartSetInteger(0,CHART_COLOR_CHART_DOWN,Chart.CHART_COLOR_CHART_DOWN);//Set Chart Down Candle Color
   ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,Chart.CHART_COLOR_CANDLE_BULL);//Set Chart Bull Candle Color
   ChartSetInteger(0,CHART_COLOR_CHART_UP,Chart.CHART_COLOR_CHART_UP);//Set Chart Up Candle Color
   ChartSetInteger(0,CHART_COLOR_ASK,Chart.CHART_COLOR_ASK);//Set Chart Ask Color
   ChartSetInteger(0,CHART_COLOR_BID,Chart.CHART_COLOR_BID);//Set Chart Bid Color
   ChartSetInteger(0,CHART_COLOR_STOP_LEVEL,Chart.CHART_COLOR_STOP_LEVEL);//Set Chart Stop Level Color
   ChartSetInteger(0,CHART_FOREGROUND,Chart.CHART_FOREGROUND);//Set if Chart is in Foreground Visibility
   ChartSetInteger(0,CHART_SHOW_ASK_LINE,Chart.CHART_SHOW_ASK_LINE);//Set Chart Ask Line Visibility
   ChartSetInteger(0,CHART_SHOW_BID_LINE,Chart.CHART_SHOW_BID_LINE);//Set Chart Bid Line Visibility
   ChartSetInteger(0,CHART_SHOW_PERIOD_SEP,Chart.CHART_SHOW_PERIOD_SEP);//Set Chart Period Separator Visibility
   ChartSetInteger(0,CHART_SHOW_TRADE_LEVELS,Chart.CHART_SHOW_TRADE_LEVELS);//Set Chart Trade Levels Visibility
   ChartSetInteger(0,CHART_SHOW_OHLC,Chart.CHART_SHOW_OHLC);//Set Chart Open-High-Low-Close Visibility
   ChartSetInteger(0,CHART_SHOW_GRID,Chart.CHART_SHOW_GRID);//Set Chart Grid Visibility
   ChartSetInteger(0,CHART_SHOW_VOLUMES,Chart.CHART_SHOW_VOLUMES);//Set Chart Volumes Visibility
   ChartSetInteger(0,CHART_SCALE,Chart.CHART_SCALE);//Set Chart Scale Value
   ChartSetInteger(0,CHART_AUTOSCROLL,Chart.CHART_AUTOSCROLL);//Set Chart Auto Scroll Option
   ChartSetDouble(0,CHART_SHIFT_SIZE,Chart.CHART_SHIFT_SIZE);//Set Chart Shift Size Value
   ChartSetInteger(0,CHART_SHIFT,Chart.CHART_SHIFT);//Set Chart Shift Option
   ChartSetInteger(0,CHART_SHOW_ONE_CLICK,Chart.CHART_SHOW_ONE_CLICK);//Set Chart One Click Trading
  }

//+------------------------------------------------------------------+
//|Initialize Chart Properties                                       |
//+------------------------------------------------------------------+
void CChartProperties::ChartConfigure(void)
  {
   Chart.CHART_MODE=(ulong)CHART_CANDLES;//Assigning Chart Mode of CHART_CANDLES
   Chart.CHART_COLOR_BACKGROUND=ulong(SymbolBackground());//Assigning Chart Background Color of Symbol's Background color
   Chart.CHART_COLOR_FOREGROUND=(ulong)clrBlack;//Assigning Chart Foreground Color of clrBalck(Black color)
   Chart.CHART_COLOR_CHART_LINE=(ulong)clrBlack;//Assigning Chart Line Color of clrBlack(Black color)
   Chart.CHART_COLOR_CANDLE_BEAR=(ulong)clrBlack;//Assigning Chart Bear Candle Color of clrBlack(Black color)
   Chart.CHART_COLOR_CHART_DOWN=(ulong)clrBlack;//Assigning Chart Down Candle Color of clrBlack(Black color)
   Chart.CHART_COLOR_CANDLE_BULL=(ulong)clrWhite;//Assigning Chart Bull Candle Color of clrWhite(White color)
   Chart.CHART_COLOR_CHART_UP=(ulong)clrBlack;//Assigning Chart Up Candle Color of clrBlack(Black color)
   Chart.CHART_COLOR_ASK=(ulong)clrBlack;//Assigning Chart Ask Color of clrBlack(Black color)
   Chart.CHART_COLOR_BID=(ulong)clrBlack;//Assigning Chart Bid Color of clrBlack(Black color)
   Chart.CHART_COLOR_STOP_LEVEL=(ulong)clrBlack;//Assigning Chart Stop Level Color of clrBlack(Black color)
   Chart.CHART_FOREGROUND=(ulong)false;//Assigning Chart Foreground Boolean Value of 'false'
   Chart.CHART_SHOW_ASK_LINE=(ulong)true;//Assigning Chart Ask Line Boolean Value of 'true'
   Chart.CHART_SHOW_BID_LINE=(ulong)true;//Assigning Chart Bid Line Boolean Value of 'true'
   Chart.CHART_SHOW_PERIOD_SEP=(ulong)true;//Assigning Chart Period Separator Boolean Value of 'true'
   Chart.CHART_SHOW_TRADE_LEVELS=(ulong)true;//Assigning Chart Trade Levels Boolean Value of 'true'
   Chart.CHART_SHOW_OHLC=(ulong)false;//Assigning Chart Open-High-Low-Close Boolean Value of 'false'
   Chart.CHART_SHOW_GRID=(ulong)false;//Assigning Chart Grid Boolean Value of 'false'
   Chart.CHART_SHOW_VOLUMES=(ulong)false;//Assigning Chart Volumes Boolean Value of 'false'
   Chart.CHART_SCALE=(ulong)3;//Assigning Chart Scale Boolean Value of '3'
   Chart.CHART_AUTOSCROLL=(ulong)true;//Assigning Chart Auto Scroll Boolean Value of 'true'
   Chart.CHART_SHIFT_SIZE=30;//Assigning Chart Shift Size Value of '30'
   Chart.CHART_SHIFT=(ulong)true;//Assigning Chart Shift Boolean Value of 'true'
   Chart.CHART_SHOW_ONE_CLICK=ulong(false);//Assigning Chart One Click Trading a value of 'false'
   ChartSet();//Calling Function to set chart format
  }
//+------------------------------------------------------------------+

В объявленной нами структуре ChartFormat, мы храним различные переменные графика, которые мы будем изменять на текущем графике, на котором находится советник.

   struct ChartFormat
     {
      ulong             CHART_MODE;//Chart Candle Mode
      ulong             CHART_COLOR_BACKGROUND;//Chart Background Color
      ulong             CHART_COLOR_FOREGROUND;//Chart Foreground Color
      ulong             CHART_COLOR_CHART_LINE;//Chart Line Color
      ulong             CHART_COLOR_CANDLE_BEAR;//Chart Bear Candle Color
      ulong             CHART_COLOR_CHART_DOWN;//Chart Down Candle Color
      ulong             CHART_COLOR_CANDLE_BULL;//Chart Bull Candle Color
      ulong             CHART_COLOR_CHART_UP;//Chart Up Candle Color
      ulong             CHART_COLOR_ASK;//Chart Ask Color
      ulong             CHART_COLOR_BID;//Chart Bid Color
      ulong             CHART_COLOR_STOP_LEVEL;//Chart Stoplevel Color
      ulong             CHART_SHOW_PERIOD_SEP;//Chart Show Period Separator
      ulong             CHART_SCALE;//Chart Scale
      ulong             CHART_FOREGROUND;//Chart Show Foreground
      ulong             CHART_SHOW_ASK_LINE;//Chart Show Ask Line
      ulong             CHART_SHOW_BID_LINE;//Chart Show Bid Line
      ulong             CHART_SHOW_TRADE_LEVELS;//Chart Show Trade Levels
      ulong             CHART_SHOW_OHLC;//Chart Show Open-High-Low-Close
      ulong             CHART_SHOW_GRID;//Chart Show Grid
      ulong             CHART_SHOW_VOLUMES;//Chart Show Volumes
      ulong             CHART_AUTOSCROLL;//Chart Auto Scroll
      double            CHART_SHIFT_SIZE;//Chart Shift Size
      ulong             CHART_SHIFT;//Chart Shift
      ulong             CHART_SHOW_ONE_CLICK;//Chart One Click Trading
     };

Массив ChartConfig будет хранить все свойства графика до того, как мы внесем в него изменения.

ulong             ChartConfig[65];//Array To Store Chart Properties

В функции SetBackground мы получаем цвет фона MarketWatch текущего символа:

MarketWatch

и устанавливаем цвет фона текущего графика на тот, что получили:

Цвет фона графика

В конструкторе класса мы получаем все свойства графика типа Integer и сохраняем их в массиве ChartConfig. 

CChartProperties::CChartProperties(void)//Class Constructor
  {
   for(int i=0;i<65;i++)//Iterating through ENUM_CHART_PROPERTY_INTEGER Elements
     {
      ChartGetInteger(0,(ENUM_CHART_PROPERTY_INTEGER)i,0,ChartConfig[i]);//Storing Chart values into ChartConfig array
     }
   ChartConfigure();
  }

Мы также инициализируем переменную Chart, которая имеет упомянутый ранее тип структуры ChartFormat, и присваиваем ей соответствующие значения, чтобы настроить график по своему вкусу в функции ChartConfigure.

void CChartProperties::ChartConfigure(void)
  {
   Chart.CHART_MODE=(ulong)CHART_CANDLES;//Assigning Chart Mode of CHART_CANDLES
   Chart.CHART_COLOR_BACKGROUND=ulong(SymbolBackground());//Assigning Chart Background Color of Symbol's Background color
   Chart.CHART_COLOR_FOREGROUND=(ulong)clrBlack;//Assigning Chart Foreground Color of clrBalck(Black color)
   Chart.CHART_COLOR_CHART_LINE=(ulong)clrBlack;//Assigning Chart Line Color of clrBlack(Black color)
   Chart.CHART_COLOR_CANDLE_BEAR=(ulong)clrBlack;//Assigning Chart Bear Candle Color of clrBlack(Black color)
   Chart.CHART_COLOR_CHART_DOWN=(ulong)clrBlack;//Assigning Chart Down Candle Color of clrBlack(Black color)
   Chart.CHART_COLOR_CANDLE_BULL=(ulong)clrWhite;//Assigning Chart Bull Candle Color of clrWhite(White color)
   Chart.CHART_COLOR_CHART_UP=(ulong)clrBlack;//Assigning Chart Up Candle Color of clrBlack(Black color)
   Chart.CHART_COLOR_ASK=(ulong)clrBlack;//Assigning Chart Ask Color of clrBlack(Black color)
   Chart.CHART_COLOR_BID=(ulong)clrBlack;//Assigning Chart Bid Color of clrBlack(Black color)
   Chart.CHART_COLOR_STOP_LEVEL=(ulong)clrBlack;//Assigning Chart Stop Level Color of clrBlack(Black color)
   Chart.CHART_FOREGROUND=(ulong)false;//Assigning Chart Foreground Boolean Value of 'false'
   Chart.CHART_SHOW_ASK_LINE=(ulong)true;//Assigning Chart Ask Line Boolean Value of 'true'
   Chart.CHART_SHOW_BID_LINE=(ulong)true;//Assigning Chart Bid Line Boolean Value of 'true'
   Chart.CHART_SHOW_PERIOD_SEP=(ulong)true;//Assigning Chart Period Separator Boolean Value of 'true'
   Chart.CHART_SHOW_TRADE_LEVELS=(ulong)true;//Assigning Chart Trade Levels Boolean Value of 'true'
   Chart.CHART_SHOW_OHLC=(ulong)false;//Assigning Chart Open-High-Low-Close Boolean Value of 'false'
   Chart.CHART_SHOW_GRID=(ulong)false;//Assigning Chart Grid Boolean Value of 'false'
   Chart.CHART_SHOW_VOLUMES=(ulong)false;//Assigning Chart Volumes Boolean Value of 'false'
   Chart.CHART_SCALE=(ulong)3;//Assigning Chart Scale Boolean Value of '3'
   Chart.CHART_AUTOSCROLL=(ulong)true;//Assigning Chart Auto Scroll Boolean Value of 'true'
   Chart.CHART_SHIFT_SIZE=30;//Assigning Chart Shift Size Value of '30'
   Chart.CHART_SHIFT=(ulong)true;//Assigning Chart Shift Boolean Value of 'true'
   Chart.CHART_SHOW_ONE_CLICK=ulong(false);//Assigning Chart One Click Trading a value of 'false'
   ChartSet();//Calling Function to set chart format
  }

В функции ChartSet установим значения выбранных свойств графика из переменной Chart типа структуры ChartFormat.

void CChartProperties::ChartSet()
  {
   ChartSetInteger(0,CHART_MODE,Chart.CHART_MODE);//Set Chart Candle Mode
   ChartSetInteger(0,CHART_COLOR_BACKGROUND,Chart.CHART_COLOR_BACKGROUND);//Set Chart Background Color
   ChartSetInteger(0,CHART_COLOR_FOREGROUND,Chart.CHART_COLOR_FOREGROUND);//Set Chart Foreground Color
   ChartSetInteger(0,CHART_COLOR_CHART_LINE,Chart.CHART_COLOR_CHART_LINE);//Set Chart Line Color
   ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,Chart.CHART_COLOR_CANDLE_BEAR);//Set Chart Bear Candle Color
   ChartSetInteger(0,CHART_COLOR_CHART_DOWN,Chart.CHART_COLOR_CHART_DOWN);//Set Chart Down Candle Color
   ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,Chart.CHART_COLOR_CANDLE_BULL);//Set Chart Bull Candle Color
   ChartSetInteger(0,CHART_COLOR_CHART_UP,Chart.CHART_COLOR_CHART_UP);//Set Chart Up Candle Color
   ChartSetInteger(0,CHART_COLOR_ASK,Chart.CHART_COLOR_ASK);//Set Chart Ask Color
   ChartSetInteger(0,CHART_COLOR_BID,Chart.CHART_COLOR_BID);//Set Chart Bid Color
   ChartSetInteger(0,CHART_COLOR_STOP_LEVEL,Chart.CHART_COLOR_STOP_LEVEL);//Set Chart Stop Level Color
   ChartSetInteger(0,CHART_FOREGROUND,Chart.CHART_FOREGROUND);//Set if Chart is in Foreground Visibility
   ChartSetInteger(0,CHART_SHOW_ASK_LINE,Chart.CHART_SHOW_ASK_LINE);//Set Chart Ask Line Visibility
   ChartSetInteger(0,CHART_SHOW_BID_LINE,Chart.CHART_SHOW_BID_LINE);//Set Chart Bid Line Visibility
   ChartSetInteger(0,CHART_SHOW_PERIOD_SEP,Chart.CHART_SHOW_PERIOD_SEP);//Set Chart Period Separator Visibility
   ChartSetInteger(0,CHART_SHOW_TRADE_LEVELS,Chart.CHART_SHOW_TRADE_LEVELS);//Set Chart Trade Levels Visibility
   ChartSetInteger(0,CHART_SHOW_OHLC,Chart.CHART_SHOW_OHLC);//Set Chart Open-High-Low-Close Visibility
   ChartSetInteger(0,CHART_SHOW_GRID,Chart.CHART_SHOW_GRID);//Set Chart Grid Visibility
   ChartSetInteger(0,CHART_SHOW_VOLUMES,Chart.CHART_SHOW_VOLUMES);//Set Chart Volumes Visibility
   ChartSetInteger(0,CHART_SCALE,Chart.CHART_SCALE);//Set Chart Scale Value
   ChartSetInteger(0,CHART_AUTOSCROLL,Chart.CHART_AUTOSCROLL);//Set Chart Auto Scroll Option
   ChartSetDouble(0,CHART_SHIFT_SIZE,Chart.CHART_SHIFT_SIZE);//Set Chart Shift Size Value
   ChartSetInteger(0,CHART_SHIFT,Chart.CHART_SHIFT);//Set Chart Shift Option
   ChartSetInteger(0,CHART_SHOW_ONE_CLICK,Chart.CHART_SHOW_ONE_CLICK);//Set Chart One Click Trading
  }

Восстановим целочисленные значения предыдущего графика в деструкторе.

CChartProperties::~CChartProperties(void)
  {
   for(int i=0;i<65;i++)//Iterating through ENUM_CHART_PROPERTY_INTEGER Elements
     {
      ChartSetInteger(0,(ENUM_CHART_PROPERTY_INTEGER)i,0,ChartConfig[i]);//Restoring Chart values from ChartConfig array
     }
  }


Класс свойств свечей

CCandleProperties имеет многоуровневое наследование от классов:

  • CChartProperties
  • CSymbolProperties

CCandleProperties имеет включение из класса CTimeManagement.

CCandleProperties располагает иерархическим наследованием от классов:

  • CSymbolProperties
  • CSymbolInfo

//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+
#include "TimeManagement.mqh"
#include "ChartProperties.mqh"
//+------------------------------------------------------------------+
//|CandleProperties class                                            |
//+------------------------------------------------------------------+
class CCandleProperties : public CChartProperties
  {
private:
   CTimeManagement   Time;

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

//+------------------------------------------------------------------+
//|Retrieve Candle Open-Price                                        |
//+------------------------------------------------------------------+
double CCandleProperties::Open(int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,string SYMBOL=NULL)
  {
   return (SetSymbolName(SYMBOL))?iOpen(GetSymbolName(),Period,CandleIndex):0;//return candle open price
  }

//+------------------------------------------------------------------+
//|Retrieve Candle Close-Price                                       |
//+------------------------------------------------------------------+
double CCandleProperties::Close(int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,string SYMBOL=NULL)
  {
   return (SetSymbolName(SYMBOL))?iClose(GetSymbolName(),Period,CandleIndex):0;//return candle close price
  }

//+------------------------------------------------------------------+
//|Retrieve Candle High-Price                                        |
//+------------------------------------------------------------------+
double CCandleProperties::High(int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,string SYMBOL=NULL)
  {
   return (SetSymbolName(SYMBOL))?iHigh(GetSymbolName(),Period,CandleIndex):0;//return candle high price
  }

//+------------------------------------------------------------------+
//|Retrieve Candle Low-Price                                         |
//+------------------------------------------------------------------+
double CCandleProperties::Low(int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,string SYMBOL=NULL)
  {
   return (SetSymbolName(SYMBOL))?iLow(GetSymbolName(),Period,CandleIndex):0;//return candle low price
  }

//+------------------------------------------------------------------+
//|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
//--Assign candle index of candletime minus time offset
   int CandleIndexMinusOffset = iBarShift(SYMBOL,PERIOD_M15,Time.TimeMinusOffset(CandleTime,Offset));
//--Assign candle index of candletime plus time offset
   int CandleIndexPlusOffset = iBarShift(SYMBOL,PERIOD_M15,Time.TimePlusOffset(CandleTime,Offset));
//--Assign height of M15 candletime in pips
   double CandleHeight = High(CandleIndex,PERIOD_M15,SYMBOL)-Low(CandleIndex,PERIOD_M15,SYMBOL);
//--Assign height of M15 candletime  minus offset in Pips
   double CandleHeightMinusOffset = High(CandleIndexMinusOffset,PERIOD_M15,SYMBOL)-Low(CandleIndexMinusOffset,PERIOD_M15,SYMBOL);
//--Assign height of M15 candletime plus offset in Pips
   double CandleHeightPlusOffset = High(CandleIndexPlusOffset,PERIOD_M15,SYMBOL)-Low(CandleIndexPlusOffset,PERIOD_M15,SYMBOL);
//--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 occured
     }
   return false;//Candletime is unlikely when the real news data was released
  }
//+------------------------------------------------------------------+


Класс свойств объекта

Класс будет отвечать за создание и удаление объектов графика.

CObjectProperties имеет многоуровневое наследование от классов:

  • CChartProperties
  • CSymbolProperties
CObjectProperties имеет иерархическое наследование от классов:

  • CSymbolProperties
  • CSymbolInfo
//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+
#include "ChartProperties.mqh"
//+------------------------------------------------------------------+
//|ObjectProperties class                                            |
//+------------------------------------------------------------------+
class CObjectProperties:public CChartProperties
  {
private:
   //Simple  chart objects structure
   struct ObjStruct
     {
      long           ChartId;
      string         Name;
     } Objects[];//ObjStruct variable array

   //-- Add chart object to Objects array
   void              AddObj(long chart_id,string name)
     {
      ArrayResize(Objects,Objects.Size()+1,Objects.Size()+2);
      Objects[Objects.Size()-1].ChartId=chart_id;
      Objects[Objects.Size()-1].Name=name;
     }

public:
                     CObjectProperties(void) {}//Class constructor

   //-- Create Rectangle chart object
   void              Square(long chart_ID,string name,int x_coord,int y_coord,int width,int height,ENUM_ANCHOR_POINT Anchor);

   //-- Create text chart object
   void              TextObj(long chartID,string name,string text,int x_coord,int y_coord,
                             ENUM_BASE_CORNER Corner=CORNER_LEFT_UPPER,int fontsize=10);

   //-- Create Event object
   void               EventObj(long chartID,string name,string description,datetime eventdate);

   //-- Class destructor removes all chart objects created previously
                    ~CObjectProperties(void)
     {
      for(uint i=0;i<Objects.Size();i++)
        {
         ObjectDelete(Objects[i].ChartId,Objects[i].Name);
        }
     }
  };

//+------------------------------------------------------------------+
//|Create Rectangle chart object                                     |
//+------------------------------------------------------------------+
void CObjectProperties::Square(long chart_ID,string name,int x_coord,int y_coord,int width,int height,ENUM_ANCHOR_POINT Anchor)
  {
   const int              sub_window=0;             // subwindow index
   const int              x=x_coord;                // X coordinate
   const int              y=y_coord;                // Y coordinate
   const color            back_clr=clrBlack;        // background color
   const ENUM_BORDER_TYPE border=BORDER_SUNKEN;     // border type
   const color            clr=clrRed;               // flat border color (Flat)
   const ENUM_LINE_STYLE  style=STYLE_SOLID;        // flat border style
   const int              line_width=0;             // flat border width
   const bool             back=false;               // in the background
   const bool             selection=false;          // highlight to move
   const bool             hidden=true;              // hidden in the object list

   ObjectDelete(chart_ID,name);//Delete previous object with the same name and chart id
   if(ObjectCreate(chart_ID,name,OBJ_RECTANGLE_LABEL,sub_window,0,0))//create rectangle object label
     {
      AddObj(chart_ID,name);//Add object to array
      ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x);//Set x Distance/coordinate
      ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y);//Set y Distance/coordinate
      ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,width);//Set object's width/x-size
      ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,height);//Set object's height/y-size
      ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,back_clr);//Set object's background color
      ObjectSetInteger(chart_ID,name,OBJPROP_BORDER_TYPE,border);//Set object's border type
      ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,Anchor);//Set objects anchor point
      ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);//Set object's color
      ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);//Set object's style
      ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,line_width);//Set object's flat border width
      ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);//Set if object is in foreground or not
      ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);//Set if object is selectable/dragable
      ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);//Set if object is Selected
      ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden);//Set if object is hidden in object list
      ChartRedraw(chart_ID);
     }
   else
     {
      Print("Failed to create object: ",name);
     }
  }

//+------------------------------------------------------------------+
//|Create text chart object                                          |
//+------------------------------------------------------------------+
void CObjectProperties::TextObj(long chartID,string name,string text,int x_coord,int y_coord,
                                ENUM_BASE_CORNER Corner=CORNER_LEFT_UPPER,int fontsize=10)
  {
   ObjectDelete(chartID,name);//Delete previous object with the same name and chart id
   if(ObjectCreate(chartID,name,OBJ_LABEL,0,0,0))//Create object label
     {
      AddObj(chartID,name);//Add object to array
      ObjectSetInteger(chartID,name,OBJPROP_XDISTANCE,x_coord);//Set x Distance/coordinate
      ObjectSetInteger(chartID,name,OBJPROP_YDISTANCE,y_coord);//Set y Distance/coordinate
      ObjectSetInteger(chartID,name,OBJPROP_CORNER,Corner);//Set object's corner anchor
      ObjectSetString(chartID,name,OBJPROP_TEXT,text);//Set object's text
      ObjectSetInteger(chartID,name,OBJPROP_COLOR,SymbolBackground());//Set object's color
      ObjectSetInteger(chartID,name,OBJPROP_FONTSIZE,fontsize);//Set object's font-size
     }
   else
     {
      Print("Failed to create object: ",name);
     }
  }

//+------------------------------------------------------------------+
//|Create Event object                                               |
//+------------------------------------------------------------------+
void CObjectProperties::EventObj(long chartID,string name,string description,datetime eventdate)
  {
   ObjectDelete(chartID,name);//Delete previous object with the same name and chart id
   if(ObjectCreate(chartID,name,OBJ_EVENT,0,eventdate,0))//Create object event
     {
      AddObj(chartID,name);//Add object to array
      ObjectSetString(chartID,name,OBJPROP_TEXT,description);//Set object's text
      ObjectSetInteger(chartID,name,OBJPROP_COLOR,clrBlack);//Set object's color
     }
   else
     {
      Print("Failed to create object: ",name);
     }
  }
//+------------------------------------------------------------------

Переменная массива Objects будет хранить все объекты графика, созданные в классе CObjectProperties.

struct ObjStruct
     {
      long           ChartId;
      string         Name;
     } Objects[];//ObjStruct variable array

Функция AddObj добавляет идентификатор объекта графика и имя объекта в массив Objects.

   //-- Add chart object to Objects array
   void              AddObj(long chart_id,string name)
     {
      ArrayResize(Objects,Objects.Size()+1,Objects.Size()+2);
      Objects[Objects.Size()-1].ChartId=chart_id;
      Objects[Objects.Size()-1].Name=name;
     }

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

void CObjectProperties::Square(long chart_ID,string name,int x_coord,int y_coord,int width,int height,ENUM_ANCHOR_POINT Anchor)
  {
   const int              sub_window=0;             // subwindow index
   const int              x=x_coord;                // X coordinate
   const int              y=y_coord;                // Y coordinate
   const color            back_clr=clrBlack;        // background color
   const ENUM_BORDER_TYPE border=BORDER_SUNKEN;     // border type
   const color            clr=clrRed;               // flat border color (Flat)
   const ENUM_LINE_STYLE  style=STYLE_SOLID;        // flat border style
   const int              line_width=0;             // flat border width
   const bool             back=false;               // in the background
   const bool             selection=false;          // highlight to move
   const bool             hidden=true;              // hidden in the object list

   ObjectDelete(chart_ID,name);//Delete previous object with the same name and chart id
   if(ObjectCreate(chart_ID,name,OBJ_RECTANGLE_LABEL,sub_window,0,0))//create rectangle object label
     {
      AddObj(chart_ID,name);//Add object to array
      ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x);//Set x Distance/coordinate
      ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y);//Set y Distance/coordinate
      ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,width);//Set object's width/x-size
      ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,height);//Set object's height/y-size
      ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,back_clr);//Set object's background color
      ObjectSetInteger(chart_ID,name,OBJPROP_BORDER_TYPE,border);//Set object's border type
      ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,Anchor);//Set objects anchor point
      ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);//Set object's color
      ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);//Set object's style
      ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,line_width);//Set object's flat border width
      ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);//Set if object is in foreground or not
      ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);//Set if object is selectable/dragable
      ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);//Set if object is Selected
      ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden);//Set if object is hidden in object list
      ChartRedraw(chart_ID);
     }
   else
     {
      Print("Failed to create object: ",name);
     }
  }

Функция TextObj создает текстовые объекты на графике. 

void CObjectProperties::TextObj(long chartID,string name,string text,int x_coord,int y_coord,
                                ENUM_BASE_CORNER Corner=CORNER_LEFT_UPPER,int fontsize=10)
  {
   ObjectDelete(chartID,name);//Delete previous object with the same name and chart id
   if(ObjectCreate(chartID,name,OBJ_LABEL,0,0,0))//Create object label
     {
      AddObj(chartID,name);//Add object to array
      ObjectSetInteger(chartID,name,OBJPROP_XDISTANCE,x_coord);//Set x Distance/coordinate
      ObjectSetInteger(chartID,name,OBJPROP_YDISTANCE,y_coord);//Set y Distance/coordinate
      ObjectSetInteger(chartID,name,OBJPROP_CORNER,Corner);//Set object's corner anchor
      ObjectSetString(chartID,name,OBJPROP_TEXT,text);//Set object's text
      ObjectSetInteger(chartID,name,OBJPROP_COLOR,SymbolBackground());//Set object's color
      ObjectSetInteger(chartID,name,OBJPROP_FONTSIZE,fontsize);//Set object's font-size
     }
   else
     {
      Print("Failed to create object: ",name);
     }
  }

Функция EventObj создает объекты событий на графике для отображения ожидаемых или произошедших экономических событий.

void CObjectProperties::EventObj(long chartID,string name,string description,datetime eventdate)
  {
   ObjectDelete(chartID,name);//Delete previous object with the same name and chart id
   if(ObjectCreate(chartID,name,OBJ_EVENT,0,eventdate,0))//Create object event
     {
      AddObj(chartID,name);//Add object to array
      ObjectSetString(chartID,name,OBJPROP_TEXT,description);//Set object's text
      ObjectSetInteger(chartID,name,OBJPROP_COLOR,clrBlack);//Set object's color
     }
   else
     {
      Print("Failed to create object: ",name);
     }
  }


Класс новостей

Календарные таблицы в Части 1:

Таблицы баз данных в Части 1

База данных календаря Часть 1

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

Таблицы:

  • Data_AU
  • Data_None
  • Data_UK
  • Data_US

хранят схожие новостные данные, отличающиеся лишь временем.

Новая структура:

Содержание календаря в Части 2:

Содержимое базы данных

База данных календаря Часть 2

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


С учетом новой структуры содержимое базы данных будет следующим:

  • AutoDST Table
  • Calendar_AU View
  • Calendar_NONE View
  • Calendar_UK View
  • Calendar_US View
  • MQL5Calendar Table
  • Record Table
  • TimeSchedule Table
  • OnlyOne_AutoDST Trigger
  • OnlyOne_Record Trigger

Мы собираемся нормализовать таблицы из предыдущей базы данных. Это таблицы Data_AU, Data_None, Data_UK и Data_US.

Что такое нормализация базы данных?

Нормализация базы данных — это организация базы данных в таблицы и столбцы с целью минимизации избыточности и зависимости. Главные цели — исключить избыточные данные (например, хранение одних и тех же данных в нескольких таблицах) и обеспечить разумность зависимостей данных (хранение в таблице только связанных данных). В результате получается набор таблиц, с которыми легче работать. К тому же, снижается вероятность возникновения аномалий данных.

UML

Мы также создадим триггеры (triggers), чтобы гарантировать, что в таблицах AutoDST и Record хранится только одна запись. Кроме того, мы создадим представления (views) для каждого DST, чтобы отображать новости за последний обновленный день. В идеале это позволит легко ориентироваться в том, какие новостные события были актуальны, без необходимости многократно выполнять запросы к таблицам с тысячами записей. 


Итак, что такое триггер?

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

Что такое представление?

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

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

В SQLite есть таблица SQLITE_MASTER/SQLITE_SCHEMA, в которой хранятся метаданные базы данных, включая информацию обо всех объектах в базе данных и SQL, используемый для их определения. Это самый важный системный каталог в SQLite. Запрос ниже используется для получения всей информации из базы данных.

SELECT * FROM SQLITE_MASTER;

 Результат:

type    name		    		tbl_name        rootpage        sql
table   Data_None       		Data_None       2       	CREATE TABLE Data_None(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))
index   sqlite_autoindex_Data_None_1    Data_None       3       
table   Data_US 			Data_US 	4       	CREATE TABLE Data_US(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))
index   sqlite_autoindex_Data_US_1      Data_US 	5       
table   Data_UK 			Data_UK 	6       	CREATE TABLE Data_UK(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))
index   sqlite_autoindex_Data_UK_1      Data_UK 	7       
table   Data_AU 			Data_AU 	8       	CREATE TABLE Data_AU(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))
index   sqlite_autoindex_Data_AU_1      Data_AU 	9       
table   Records 			Records 	38774   	CREATE TABLE Records(RECORDEDTIME INT NOT NULL)
table   AutoDST 			AutoDST 	38775   	CREATE TABLE AutoDST(DST STRING NOT NULL)

Мы видим, что у нас появилась новая сущность под названием "индекс" (index), которую мы ранее не создавали.

Что такое индекс?

Индекс в SQLite - объект базы данных, который позволяет повысить производительность запросов, ускоряя извлечение записей из таблицы. Индексы особенно полезны для ускорения операций поиска, сортировки и объединения за счет создания отсортированной структуры данных (обычно B-дерева), которая позволяет ядру базы данных быстрее находить строки.

Зачем был создан индекс?

Если таблица в SQLite имеет первичный ключ, для нее автоматически создается индекс.

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

CNews имеет многоуровневое наследование от классов:

  • CCandleProperties
  • CChartProperties
  • CSymbolProperties

CNews имеет включения из классов:

  • CDaylightSavings_UK
  • CDaylightSavings_US
  • CDaylightSavings_AU

CNews имеет включение из заголовочного файла CommonVariables.mqh

CNews имеет иерархическое наследование от классов:

  • CSymbolProperties
  • CSymbolInfo
  • CCandleProperties
  • CTimeManagement
//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+
#include "CommonVariables.mqh"
#include "DayLightSavings/DaylightSavings_UK.mqh"
#include "DayLightSavings/DaylightSavings_US.mqh"
#include "DayLightSavings/DaylightSavings_AU.mqh"
#include "CandleProperties.mqh"

//+------------------------------------------------------------------+
//|News class                                                        |
//+------------------------------------------------------------------+
class CNews : private CCandleProperties
  {
   //Private Declarations Only accessable by this class/header file
private:

   //-- To keep track of what is in our database
   enum CalendarComponents
     {
      AutoDST_Table,//AutoDST Table
      CalendarAU_View,//View for DST_AU
      CalendarNONE_View,//View for DST_NONE
      CalendarUK_View,//View for DST_UK
      CalendarUS_View,//View for DST_US
      Record_Table,// Record Table
      TimeSchedule_Table,//TimeSchedule Table
      MQL5Calendar_Table,//MQL5Calendar Table
      AutoDST_Trigger,//Table Trigger for AutoDST
      Record_Trigger//Table Trigger for Record
     };

   //-- structure to retrieve all the objects in the database
   struct SQLiteMaster
     {
      string         type;//will store object's type
      string         name;//will store object's name
      string         tbl_name;//will store table name
      int            rootpage;//will store rootpage
      string         sql;//Will store the sql create statement
     } DBContents[];//Array of type SQLiteMaster

   //--  MQL5CalendarContents inherits from SQLiteMaster structure
   struct MQL5CalendarContents:SQLiteMaster
     {
      CalendarComponents  Content;
      string         insert;//Will store the sql insert statement
     } CalendarContents[10];//Array to Store objects in our database

   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

   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              InsertIntoTables(int db,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              CreateCalendarTable(int db,bool &tableExists);//Function for creating a table in a database
   bool              CreateTimeTable(int db,bool &tableExists);//Function for creating a table in a database
   void              CreateCalendarViews(int db);//Function for creating a view in a database
   void              CreateRecordTable(int db);//Creates a table to store the record 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
   string            DropRequest;//Variable for dropping tables in the database

   //-- Function for retrieving the MQL5CalendarContents structure for the enumartion type CalendarComponents
   MQL5CalendarContents CalendarStruct(CalendarComponents Content)
     {
      MQL5CalendarContents Calendar;
      for(uint i=0;i<CalendarContents.Size();i++)
        {
         if(CalendarContents[i].Content==Content)
           {
            return CalendarContents[i];
           }
        }
      return Calendar;
     }

   //Public declarations accessable via a class's Object
public:
                     CNews(void);
                    ~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          GetLatestNewsDate();//Gets the lastest/newest date in the Calendar database
  };

//+------------------------------------------------------------------+
//|Constructor                                                       |
//+------------------------------------------------------------------+
CNews::CNews(void):DropRequest("PRAGMA foreign_keys = OFF; "
                                  "PRAGMA secure_delete = ON; "
                                  "Drop %s IF EXISTS %s; "
                                  "Vacuum; "
                                  "PRAGMA foreign_keys = ON;")//Sql drop statement
  {
//-- initializing properties for the AutoDST table
   CalendarContents[0].Content = AutoDST_Table;
   CalendarContents[0].name = "AutoDST";
   CalendarContents[0].sql = "CREATE TABLE AutoDST(DST TEXT NOT NULL DEFAULT 'DST_NONE')STRICT;";
   CalendarContents[0].tbl_name = "AutoDST";
   CalendarContents[0].type = "table";
   CalendarContents[0].insert = "INSERT INTO 'AutoDST'(DST) VALUES ('%s');";

   string views[] = {"UK","US","AU","NONE"};
   string view_sql = "CREATE VIEW IF NOT EXISTS Calendar_%s "
                     "AS "
                     "SELECT C.Eventid,C.Eventname,C.Country,T.DST_%s as Time,C.EventCurrency,C.Eventcode from MQL5Calendar C,Record R "
                     "Inner join TimeSchedule T on C.ID=T.ID "
                     "Where DATE(REPLACE(T.DST_%s,'.','-'))=R.Date "
                     "Order by T.DST_%s Asc;";

//-- Sql statements for creating the table views
   for(uint i=1;i<=views.Size();i++)
     {
      CalendarContents[i].Content = (CalendarComponents)i;
      CalendarContents[i].name = StringFormat("Calendar_%s",views[i-1]);
      CalendarContents[i].sql = StringFormat(view_sql,views[i-1],views[i-1],views[i-1],views[i-1]);
      CalendarContents[i].tbl_name = StringFormat("Calendar_%s",views[i-1]);
      CalendarContents[i].type = "view";
     }

//-- initializing properties for the Record table
   CalendarContents[5].Content = Record_Table;
   CalendarContents[5].name = "Record";
   CalendarContents[5].sql = "CREATE TABLE Record(Date TEXT NOT NULL)STRICT;";
   CalendarContents[5].tbl_name="Record";
   CalendarContents[5].type = "table";
   CalendarContents[5].insert = "INSERT INTO 'Record'(Date) VALUES (Date(REPLACE('%s','.','-')));";

//-- initializing properties for the TimeSchedule table
   CalendarContents[6].Content = TimeSchedule_Table;
   CalendarContents[6].name = "TimeSchedule";
   CalendarContents[6].sql = "CREATE TABLE TimeSchedule(ID INT NOT NULL,DST_UK   TEXT   NOT NULL,DST_US   TEXT   NOT NULL,"
                             "DST_AU   TEXT   NOT NULL,DST_NONE   TEXT   NOT NULL,FOREIGN KEY (ID) REFERENCES MQL5Calendar (ID))STRICT;";
   CalendarContents[6].tbl_name="TimeSchedule";
   CalendarContents[6].type = "table";
   CalendarContents[6].insert = "INSERT INTO 'TimeSchedule'(ID,DST_UK,DST_US,DST_AU,DST_NONE) "
                                "VALUES (%d,'%s','%s', '%s', '%s');";

//-- initializing properties for the MQL5Calendar table
   CalendarContents[7].Content = MQL5Calendar_Table;
   CalendarContents[7].name = "MQL5Calendar";
   CalendarContents[7].sql = "CREATE TABLE MQL5Calendar(ID INT NOT NULL,EVENTID  INT   NOT NULL,COUNTRY  TEXT   NOT NULL,"
                             "EVENTNAME   TEXT   NOT NULL,EVENTTYPE   TEXT   NOT NULL,EVENTIMPORTANCE   TEXT   NOT NULL,"
                             "EVENTCURRENCY  TEXT   NOT NULL,EVENTCODE   TEXT   NOT NULL,EVENTSECTOR TEXT   NOT NULL,"
                             "EVENTFORECAST  TEXT   NOT NULL,EVENTPREVALUE  TEXT   NOT NULL,EVENTIMPACT TEXT   NOT NULL,"
                             "EVENTFREQUENCY TEXT   NOT NULL,PRIMARY KEY(ID))STRICT;";
   CalendarContents[7].tbl_name="MQL5Calendar";
   CalendarContents[7].type = "table";
   CalendarContents[7].insert = "INSERT INTO 'MQL5Calendar'(ID,EVENTID,COUNTRY,EVENTNAME,EVENTTYPE,EVENTIMPORTANCE,EVENTCURRENCY,EVENTCODE,"
                                "EVENTSECTOR,EVENTFORECAST,EVENTPREVALUE,EVENTIMPACT,EVENTFREQUENCY) "
                                "VALUES (%d,%d,'%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s');";

//-- Sql statement for creating the AutoDST table's trigger
   CalendarContents[8].Content = AutoDST_Trigger;
   CalendarContents[8].name = "OnlyOne_AutoDST";
   CalendarContents[8].sql = "CREATE TRIGGER IF NOT EXISTS OnlyOne_AutoDST "
                             "BEFORE INSERT ON AutoDST "
                             "BEGIN "
                             "Delete from AutoDST; "
                             "END;";
   CalendarContents[8].tbl_name="AutoDST";
   CalendarContents[8].type = "trigger";

//-- Sql statement for creating the Record table's trigger
   CalendarContents[9].Content = Record_Trigger;
   CalendarContents[9].name = "OnlyOne_Record";
   CalendarContents[9].sql = "CREATE TRIGGER IF NOT EXISTS OnlyOne_Record "
                             "BEFORE INSERT ON Record "
                             "BEGIN "
                             "Delete from Record; "
                             "END;";
   CalendarContents[9].tbl_name="Record";
   CalendarContents[9].type = "trigger";
  }

//+------------------------------------------------------------------+
//|Destructor                                                        |
//+------------------------------------------------------------------+
CNews::~CNews(void)
  {
   if(FileIsExist(NEWS_TEXT_FILE,FILE_COMMON))//Check if the news database open text file exists
     {
      FileDelete(NEWS_TEXT_FILE,FILE_COMMON);
     }
  }

//+------------------------------------------------------------------+
//|Gets values from the MQL5 economic Calendar                       |
//+------------------------------------------------------------------+
void CNews::EconomicDetails(Calendar &NewsTime[])
  {
   int Size=0;//to keep track of the size of the events in the NewsTime array
   MqlCalendarCountry countries[];
   string Country_code="";

   for(int i=0,count=CalendarCountries(countries); 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
               //-- storing the event importance from (ENUM_CALENDAR_EVENT_IMPORTANCE) to a string
               NewsTime[Size].EventImportance = EnumToString(event.importance);
               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
                 }
               //-- storing the event impact from (ENUM_CALENDAR_EVENT_IMPACT) to a string
               NewsTime[Size].EventImpact =  EnumToString(values[x].impact_type);
               //-- storing the event frequency from (ENUM_CALENDAR_EVENT_FREQUENCY) to a string
               NewsTime[Size].EventFrequency =  EnumToString(event.frequency);
               Size++;//incrementing the Calendar array NewsTime
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//|Checks if the main Calendar database needs an update or not       |
//+------------------------------------------------------------------+
bool CNews::UpdateRecords()
  {
//initialize variable to true
   bool perform_update=true;
//--- open/create
//-- try to open database Calendar
   int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE| DATABASE_OPEN_COMMON);
   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 perform_update;//Returns true when the database was failed to be opened and the file doesn't exist in the common folder
        }
     }

   int MasterRequest = DatabasePrepare(db,"select * from sqlite_master where type<>'index';");
   if(MasterRequest==INVALID_HANDLE)
     {
      Print("DB: ",NEWS_DATABASE_FILE, " request failed with code ", GetLastError());
     }
   else
     {
      SQLiteMaster ReadContents;
      //Assigning values from the sql query into DBContents array
      for(int i=0; DatabaseReadBind(MasterRequest,ReadContents); i++)
        {
         ArrayResize(DBContents,i+1,i+2);
         DBContents[i].type = ReadContents.type;
         DBContents[i].name = ReadContents.name;
         DBContents[i].tbl_name = ReadContents.tbl_name;
         DBContents[i].rootpage = ReadContents.rootpage;
         /*Check if the end of the sql string has a character ';' if not add this character to the string*/
         DBContents[i].sql = (StringFind(ReadContents.sql,";",StringLen(ReadContents.sql)-1)==
                              (StringLen(ReadContents.sql)-1))?ReadContents.sql:ReadContents.sql+";";;
        }

      uint contents_exists = 0;
      for(uint i=0;i<DBContents.Size();i++)
        {
         bool isCalendarContents = false;
         for(uint x=0;x<CalendarContents.Size();x++)
           {
            /*Store Sql query from CalendarContents without string ' IF NOT EXISTS'*/
            string CalendarSql=CalendarContents[x].sql;
            StringReplace(CalendarSql," IF NOT EXISTS","");
            //-- Check if the Db object is in our list
            if(DBContents[i].name==CalendarContents[x].name&&
               (DBContents[i].sql==CalendarSql||
                DBContents[i].sql==CalendarContents[x].sql)&&
               CalendarContents[x].type==DBContents[i].type&&
               CalendarContents[x].tbl_name==DBContents[i].tbl_name)
              {
               contents_exists++;
               isCalendarContents = true;
              }
           }
         if(!isCalendarContents)
           {
            //-- Print DBcontent's name if it does not match with CalendarContents
            PrintFormat("DBContent: %s is not needed!",DBContents[i].name);
            //-- We will drop the table if it is not neccessary
            DatabaseExecute(db,StringFormat(DropRequest,DBContents[i].type,DBContents[i].name));
            Print("Attempting To Clean Database...");
           }
        }
      /*If not all the CalendarContents exist in the Calendar Database before an update */
      if(contents_exists!=CalendarContents.Size())
        {
         return perform_update;
        }
     }
   if(!DatabaseTableExists(db,CalendarStruct(Record_Table).name))//If the database table 'Record' doesn't exist
     {
      DatabaseClose(db);
      return perform_update;
     }

//-- Sql query to determine the lastest or maximum date recorded
   /* If the last recorded date data in the 'Record' table is not equal to the current day, perform an update! */
   string request_text=StringFormat("SELECT Date FROM %s where Date=Date(REPLACE('%s','.','-'))",
                                    CalendarStruct(Record_Table).name,TimeToString(TimeTradeServer()));
   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 perform_update;
     }

   if(DatabaseRead(request))//Will be true if there are results from the sql query/request
     {
      DatabaseFinalize(request);//Removes a request created in DatabasePrepare()
      DatabaseClose(db);//Closes the database
      perform_update=false;
      return perform_update;
     }
   else
     {
      DatabaseFinalize(request);//Removes a request created in DatabasePrepare()
      DatabaseClose(db);//Closes the database
      return perform_update;
     }
  }

//+------------------------------------------------------------------+
//|Creates the Calendar database for a specific Broker               |
//+------------------------------------------------------------------+
void CNews::CreateEconomicDatabase()
  {
   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
        }
     }
   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;
//--- open/create the database 'Calendar'
//-- will try to open/create in the common folder
   int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE| DATABASE_OPEN_COMMON);
   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
     {
      //-- try to create a text file 'NewsDatabaseOpen' in common folder
      file=FileOpen(NEWS_TEXT_FILE,FILE_WRITE|FILE_ANSI|FILE_TXT|FILE_COMMON);
      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 MQL5Calendar and TimeSchedule tables
   if(!CreateCalendarTable(db,tableExists)||!CreateTimeTable(db,tableExists))
     {
      FileClose(file);//Closing the file 'NewsDatabaseOpen.txt'
      FileDelete(NEWS_TEXT_FILE,FILE_COMMON);//Deleting the file 'NewsDatabaseOpen.txt'
      return;//will terminate execution of the rest of the code below
     }

   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(!InsertIntoTables(db,Evalues))
     {
      //-- Will assign true if inserting economic vaules failed in the MQL5Calendar and TimeSchedule tables
      failed=true;
     }

   if(failed)
     {
      //--- 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
     {
      CreateCalendarViews(db);
      CreateRecordTable(db);//Will create the 'Record' 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
      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);
        }
     }
//--- all transactions have been performed successfully - record changes and unlock the database
   DatabaseTransactionCommit(db);
   DatabaseClose(db);//Close the database
  }


//+------------------------------------------------------------------+
//|Function for creating a table in a database                       |
//+------------------------------------------------------------------+
bool CNews::CreateCalendarTable(int db,bool &tableExists)
  {
//-- Checks if a table 'MQL5Calendar' exists
   if(DatabaseTableExists(db,CalendarStruct(MQL5Calendar_Table).name))
     {
      tableExists=true;//Assigns true to tableExists variable
      //-- Checks if a table 'TimeSchedule' exists in the database 'Calendar'
      if(DatabaseTableExists(db,CalendarStruct(TimeSchedule_Table).name))
        {
         //-- We will drop the table if the table already exists
         if(!DatabaseExecute(db,StringFormat("Drop Table %s",CalendarStruct(TimeSchedule_Table).name)))
           {
            //If the table failed to be dropped/deleted
            PrintFormat("Failed to drop table %s with code %d",CalendarStruct(TimeSchedule_Table).name,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
           }
        }
      //--We will drop the table if the table already exists
      if(!DatabaseExecute(db,StringFormat("Drop Table %s",CalendarStruct(MQL5Calendar_Table).name)))
        {
         //If the table failed to be dropped/deleted
         PrintFormat("Failed to drop table %s with code %d",CalendarStruct(MQL5Calendar_Table).name,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 the database table 'MQL5Calendar' doesn't exist
   if(!DatabaseTableExists(db,CalendarStruct(MQL5Calendar_Table).name))
     {
      //--- create the table 'MQL5Calendar'
      if(!DatabaseExecute(db,CalendarStruct(MQL5Calendar_Table).sql))//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
  }

//+------------------------------------------------------------------+
//|Function for creating a table in a database                       |
//+------------------------------------------------------------------+
bool CNews::CreateTimeTable(int db,bool &tableExists)
  {
//-- If the database table 'TimeSchedule' doesn't exist
   if(!DatabaseTableExists(db,CalendarStruct(TimeSchedule_Table).name))
     {
      //--- create the table 'TimeSchedule'
      if(!DatabaseExecute(db,CalendarStruct(TimeSchedule_Table).sql))//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
  }

//+------------------------------------------------------------------+
//|Function for creating views in a database                         |
//+------------------------------------------------------------------+
void CNews::CreateCalendarViews(int db)
  {
   for(uint i=1;i<=4;i++)
     {
      if(!DatabaseExecute(db,CalendarStruct((CalendarComponents)i).sql))//Checks if the view was successfully created
        {
         Print("DB: create the Calendar view failed with code ", GetLastError());
        }
     }
  }

//+------------------------------------------------------------------+
//|Function for inserting Economic Data in to a database's table     |
//+------------------------------------------------------------------+
bool CNews::InsertIntoTables(int db,Calendar &Evalues[])
  {
   for(uint i=0; i<Evalues.Size(); i++)//Looping through all the Economic Events
     {
      string request_insert_into_calendar =
         StringFormat(CalendarStruct(MQL5Calendar_Table).insert,
                      i,
                      Evalues[i].EventId,
                      Evalues[i].CountryName,
                      Evalues[i].EventName,
                      Evalues[i].EventType,
                      Evalues[i].EventImportance,
                      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_insert_into_calendar))//Check if insert query into calendar was successful
        {
         string request_insert_into_time =
            StringFormat(CalendarStruct(TimeSchedule_Table).insert,
                         i,
                         //-- Economic EventDate adjusted for UK DST(Daylight Savings Time)
                         Savings_UK.adjustDaylightSavings(StringToTime(Evalues[i].EventDate)),
                         //-- Economic EventDate adjusted for US DST(Daylight Savings Time)
                         Savings_US.adjustDaylightSavings(StringToTime(Evalues[i].EventDate)),
                         //-- Economic EventDate adjusted for AU DST(Daylight Savings Time)
                         Savings_AU.adjustDaylightSavings(StringToTime(Evalues[i].EventDate)),
                         Evalues[i].EventDate//normal Economic EventDate
                        );//Inserting all the columns for each event record
         if(!DatabaseExecute(db,request_insert_into_time))
           {
            Print(GetLastError());
            //-- Will print the sql query to check for any errors or possible defaults in the query/request
            Print(request_insert_into_time);
            return false;//Will end the loop and return false, as values failed to be inserted into the table
           }
        }
      else
        {
         Print(GetLastError());
         //-- Will print the sql query to check for any errors or possible defaults in the query/request
         Print(request_insert_into_calendar);
         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
  }

//+------------------------------------------------------------------+
//|Creates a table to store the record of when last the Calendar     |
//|database was updated/created                                      |
//+------------------------------------------------------------------+
void CNews::CreateRecordTable(int db)
  {
   bool failed=false;
   if(!DatabaseTableExists(db,CalendarStruct(Record_Table).name))//Checks if the table 'Record' exists in the databse 'Calendar'
     {
      //--- create the table
      if(!DatabaseExecute(db,CalendarStruct(Record_Table).sql))//Will attempt to create the table 'Record'
        {
         Print("DB: create the Records table failed with code ", GetLastError());
         DatabaseClose(db);//Close the database
         return;//Exits the function if creating the table failed
        }
      else//If Table was created Successfully then Create Trigger
        {
         DatabaseExecute(db,CalendarStruct(Record_Trigger).sql);
        }
     }
   else
     {
      DatabaseExecute(db,CalendarStruct(Record_Trigger).sql);
     }
//Sql query/request to insert the current time into the 'Date' column in the table 'Record'
   string request_text=StringFormat(CalendarStruct(Record_Table).insert,TimeToString(TimeTradeServer()));
   if(!DatabaseExecute(db, request_text))//Will attempt to run this sql request/query
     {
      Print(GetLastError());
      PrintFormat(CalendarStruct(Record_Table).insert,TimeToString(TimeTradeServer()));
      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());
     }
  }

//+------------------------------------------------------------------+
//|Function for creating and inserting Recommend DST for the Broker  |
//|into a table                                                      |
//+------------------------------------------------------------------+
void CNews::CreateAutoDST(int db)
  {
   bool failed=false;//boolean variable
   if(!AutoDetectDST(DSTType))//Check if AutoDetectDST went through all the right procedures
     {
      return;//will terminate execution of the rest of the code below
     }

   if(!DatabaseTableExists(db,CalendarStruct(AutoDST_Table).name))//Checks if the table 'AutoDST' exists in the databse 'Calendar'
     {
      //--- create the table AutoDST
      if(!DatabaseExecute(db,CalendarStruct(AutoDST_Table).sql))//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//If Table was created Successfully then Create Trigger
        {
         DatabaseExecute(db,CalendarStruct(AutoDST_Trigger).sql);
        }
     }
   else
     {
      //Create trigger if AutoDST table exists
      DatabaseExecute(db,CalendarStruct(AutoDST_Trigger).sql);
     }
//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(CalendarStruct(AutoDST_Table).insert,EnumToString(DSTType));
   if(!DatabaseExecute(db, request_text))//Will attempt to run this sql request/query
     {
      Print(GetLastError());
      PrintFormat(CalendarStruct(AutoDST_Table).insert,EnumToString(DSTType));//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());
     }
  }

//+------------------------------------------------------------------+
//|Gets the latest/newest date in the Calendar database              |
//+------------------------------------------------------------------+
datetime CNews::GetLatestNewsDate()
  {
//--- 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
        {
         Print("Could not find Database!");
         return 0;//Will return the earliest date which is 1970.01.01 00:00:00
        }
     }
   string latest_record="1970.01.01";//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 REPLACE(Date,'-','.') FROM 'Record'";
   int request=DatabasePrepare(db,request_text);
   if(request==INVALID_HANDLE)
     {
      Print("DB: ",NEWS_DATABASE_FILE, " request failed with code ", GetLastError());
      DatabaseClose(db);//Close Database
      return 0;
     }
   if(DatabaseRead(request))//Will read the one record in the 'Record' table
     {
      //-- Will assign the first column(column 0) value to the variable 'latest_record'
      if(!DatabaseColumnText(request,0,latest_record))
        {
         Print("DatabaseRead() failed with code ", GetLastError());
         DatabaseFinalize(request);//Finalize request
         DatabaseClose(db);//Closes the database 'Calendar'
         return D'1970.01.01';//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 (datetime)latest_record;//Returns the string latest_record converted to datetime
  }

//+------------------------------------------------------------------+
//|Function will determine Broker DST                                |
//+------------------------------------------------------------------+
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
//-- Will store the previous year into an integer
   int lastyear = Time.ReturnYear(Time.TimeMinusOffset(iTime(Symbol(),PERIOD_CURRENT,0),Time.YearsS()));
//-- Will store the start date for the previous year
   datetime lastyearstart = StringToTime(StringFormat("%s.01.01 00:00:00",(string)lastyear));
//-- Will store the end date for the previous year
   datetime lastyearend = StringToTime(StringFormat("%s.12.31 23:59:59",(string)lastyear));
//-- Getting last year's calendar values for CountryCode = 'US'
   if(CalendarValueHistory(values,lastyearstart,lastyearend,"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 variables to store the broker's timezone shift(change)
   datetime ShiftStart=D'1970.01.01 00:00:00',ShiftEnd=D'1970.01.01 00:00:00';
   string   EURUSD="";//String variables declarations for working with EURUSD
   bool     EurusdIsFound=false;//Boolean variables declarations for working with EURUSD
   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
      //-- 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(SymName)=="EUR"&&CurrencyProfit(SymName)=="USD")||
          (StringFind(SymName,"EUR")>-1&&CurrencyProfit(SymName)=="USD"))&&!Custom(SymName))
        {
         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
         //-- 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(SymName)=="EUR"&&CurrencyProfit(SymName)=="USD")||
             (StringFind(SymName,"EUR")>-1&&CurrencyProfit(SymName)=="USD"))&&!Custom(SymName))
           {
            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
     }

   struct DST
     {
      bool           result;
      datetime       date;
     } previousresult,currentresult;

   bool timeIsShifted;//Boolean variable declaration will be used to determine if the broker changes it's timezone
   for(uint i=0;i<eventtime.Size();i++)
     {
      //-- Store the result of if the eventdate is the larger candlestick
      currentresult.result = IsLargerThanPreviousAndNext((datetime)eventtime[i],Time.HoursS(),EURUSD);
      currentresult.date = (datetime)eventtime[i];//Store the eventdate from eventtime[i]
      //-- Check if there is a difference between the previous result and the current result
      timeIsShifted = ((currentresult.result!=previousresult.result&&i>0)?true:false);

      //-- Check if the Larger candle has shifted from the previous event date to the current event date in eventtime[i] array
      if(timeIsShifted)
        {
         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 eventdate for when the timeshift began
           }
         ShiftEnd=previousresult.date;//Store the eventdate timeshift
        }
      previousresult.result = currentresult.result;//Store the previous result of if the eventdate is the larger candlestick
      previousresult.date = currentresult.date;//Store the eventdate from eventtime[i]
     }
//-- Check if the ShiftStart variable has not been assigned a relevant value and the eventdates are more than zero
   if(ShiftStart==D'1970.01.01 00:00:00'&&eventtime.Size()>0)
     {
      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
     }

   datetime DaylightStart,DaylightEnd;//Datetime variables declarations for start and end dates for DaylightSavings
   if(Savings_AU.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
     {
      if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
        {
         Print("Broker ServerTime Adjusted For AU DST");
         dstType = DST_AU;//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);
      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");
         dstType = DST_UK;//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);
      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");
         dstType = DST_US;//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);
      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
     }
   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
  }
//+------------------------------------------------------------------+

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

 enum CalendarComponents
     {
      AutoDST_Table,//AutoDST Table
      CalendarAU_View,//View for DST_AU
      CalendarNONE_View,//View for DST_NONE
      CalendarUK_View,//View for DST_UK
      CalendarUS_View,//View for DST_US
      Record_Table,// Record Table
      TimeSchedule_Table,//TimeSchedule Table
      MQL5Calendar_Table,//MQL5Calendar Table
      AutoDST_Trigger,//Table Trigger for AutoDST
      Record_Trigger//Table Trigger for Record
     };

Назначение структуры SQLiteMaster - хранение текущих свойств объекта базы данных, таких как тип, имя и т. д. Таким образом, мы можем отслеживать все объекты в массиве DBContents.

//-- structure to retrieve all the objects in the database
   struct SQLiteMaster
     {
      string         type;//will store object type
      string         name;//will store object's name
      string         tbl_name;//will store table name
      int            rootpage;//will store rootpage
      string         sql;//Will store the sql create statement
     } DBContents[];//Array of type SQLiteMaster

В структуре MQL5CalendarContents мы будем хранить дополнительные свойства - переменные Content и insert.

Наша строковая переменная insert сохранит SQL-операторы insertion для наших объектов SQL. Так как переменная CalendarComponents Content будет хранить значение перечисления для нашего объекта SQL как форму идентификации, мы можем распознать объекты SQL, как только все свойства объекта будут сохранены в массиве структуры CalendarContents.

//--  MQL5CalendarContents inherits from SQLiteMaster structure
   struct MQL5CalendarContents:SQLiteMaster
     {
      CalendarComponents  Content;
      string         insert;//Will store the sql insert statement
     } CalendarContents[10];//Array to Store objects in our database

Функция CalendarStruct вернет структуру MQL5CalendarContents value, при которой параметр Content равен значению перечисления в переменной Content структуры массива CalendarContents.

//-- Function for retrieving the MQL5CalendarContents structure for the enumartion type CalendarComponents
   MQL5CalendarContents CalendarStruct(CalendarComponents Content)
     {
      MQL5CalendarContents Calendar;
      for(uint i=0;i<CalendarContents.Size();i++)
        {
         if(CalendarContents[i].Content==Content)
           {
            return CalendarContents[i];
           }
        }
      return Calendar;
     }

Строковая переменная DropRequest будет отвечать за удаление объектов базы данных, которые нам больше не нужны. В SQL-запросе мы используем операторы PRAGMA.

Что такое оператор PRAGMA?

Оператор PRAGMA в SQLite - специальная команда, используемая для изменения работы библиотеки SQLite или для запроса внутреннего состояния ядра базы данных. Операторы PRAGMA не являются частью стандартного SQL, они специфичны для SQLite. Они предоставляют возможность управлять различными параметрами среды и поведением базы данных.


Назначение операторов PRAGMA

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

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

Во втором операторе PRAGMA мы включаем secure_delete, который контролирует, будет ли удаленный контент обнуляться перед удалением из файла базы данных. В этом случае база данных перезапишет удаленное содержимое нулями только в том случае, если это не приведет к увеличению объема ввода-вывода.

В третьем операторе мы удалим объект SQL, если он существует. Затем мы используем команду Vacuum, которая перестроит файл базы данных, переупаковав его так, чтобы он занимал минимальный объем дискового пространства. Это может помочь оптимизировать производительность базы данных и освободить неиспользуемое пространство.

Наконец, снова включаем ограничения внешнего ключа.

CNews::CNews(void):DropRequest("PRAGMA foreign_keys = OFF; "
                                  "PRAGMA secure_delete = ON; "
                                  "Drop %s IF EXISTS %s; "
                                  "Vacuum; "
                                  "PRAGMA foreign_keys = ON;")//Sql drop statement

Мы сохраним свойства нашей таблицы AutoDST в первом индексе массива CalendarContents. Здесь, в переменной Content, мы присваиваем значение перечисления AutoDST_Table.

Затем присвоим имя, наименование таблицы, тип и оператор insert. В операторе SQL для создания таблицы мы присваиваем столбцу DST значение по умолчанию DST_NONE и завершаем оператор ключевым словом STRICT.

Ключевое слово STRICT гарантирует, что данные, вставленные в столбец, должны соответствовать объявленному типу этого столбца. Это обеспечивает большую предсказуемость и единообразие типов хранимых данных.

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

//-- initializing properties for the AutoDST table
   CalendarContents[0].Content = AutoDST_Table;
   CalendarContents[0].name = "AutoDST";
   CalendarContents[0].sql = "CREATE TABLE AutoDST(DST TEXT NOT NULL DEFAULT 'DST_NONE')STRICT;";
   CalendarContents[0].tbl_name = "AutoDST";
   CalendarContents[0].type = "table";
   CalendarContents[0].insert = "INSERT INTO 'AutoDST'(DST) VALUES ('%s');";

Теперь мы инициализируем свойства представлений календаря для UK, US, AU и NONE. 

В переменной view_sql хранится наш оператор SQL для создания отдельных представлений. В нашем SQL-операторе мы выбираем Eventid, Eventname, Country, EventCurrency и Eventcode из таблицы MQL5Calendar.

ID      EVENTID 	COUNTRY 	EVENTNAME       				EVENTTYPE       	EVENTIMPORTANCE 		EVENTCURRENCY   EVENTCODE       EVENTSECTOR     		EVENTFORECAST   EVENTPREVALUE   EVENTIMPACT     		EVENTFREQUENCY
18742   999020002       European Union  Eurogroup Meeting       			CALENDAR_TYPE_EVENT     CALENDAR_IMPORTANCE_MODERATE    EUR     	EU      	CALENDAR_SECTOR_GOVERNMENT      None    	None    	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_NONE
18746   999010020       European Union  ECB Executive Board Member Lane Speech  	CALENDAR_TYPE_EVENT     CALENDAR_IMPORTANCE_MODERATE    EUR     	EU      	CALENDAR_SECTOR_MONEY   	None    	None    	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_NONE
34896   392010004       Japan   	Coincident Index        			CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	JPY     	JP      	CALENDAR_SECTOR_BUSINESS        113900000       113900000       CALENDAR_IMPACT_NEGATIVE        CALENDAR_FREQUENCY_MONTH
34897   392010005       Japan   	Leading Index   				CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	JPY     	JP      	CALENDAR_SECTOR_BUSINESS        111400000       111400000       CALENDAR_IMPACT_POSITIVE        CALENDAR_FREQUENCY_MONTH
34898   392010011       Japan   	Coincident Index m/m    			CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	JPY     	JP      	CALENDAR_SECTOR_BUSINESS        None    	2400000 	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
34899   392010012       Japan   	Leading Index m/m       			CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	JPY     	JP      	CALENDAR_SECTOR_BUSINESS        None    	-700000 	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
55462   156010014       China   	Industrial Profit YTD y/y       		CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	CNY     	CN      	CALENDAR_SECTOR_BUSINESS        None    	4300000 	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
72568   276030001       Germany 	Ifo Business Expectations       		CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_MODERATE    EUR     	DE      	CALENDAR_SECTOR_BUSINESS        92000000        89900000        CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
72569   276030002       Germany 	Ifo Current Business Situation  		CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_MODERATE    EUR     	DE      	CALENDAR_SECTOR_BUSINESS        88800000        88900000        CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
72570   276030003       Germany 	Ifo Business Climate    			CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_HIGH        EUR     	DE      	CALENDAR_SECTOR_BUSINESS        89900000        89400000        CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
72571   276050007       Germany 	Bbk Executive Board Member Mauderer Speech      CALENDAR_TYPE_EVENT     CALENDAR_IMPORTANCE_MODERATE    EUR     	DE      	CALENDAR_SECTOR_MONEY   	None    	None    	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_NONE
78850   250020001       France  	3-Month BTF Auction     			CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	EUR     	FR      	CALENDAR_SECTOR_MARKET  	None    	3746000 	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_NONE
78851   250020002       France  	6-Month BTF Auction     			CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	EUR     	FR      	CALENDAR_SECTOR_MARKET  	None    	3657000 	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_NONE
78852   250020003       France  	12-Month BTF Auction    			CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	EUR     	FR      	CALENDAR_SECTOR_MARKET  	None    	3467000 	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_NONE
84771   76020007        Brazil  	BCB Bank Lending m/m    			CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	BRL     	BR      	CALENDAR_SECTOR_MONEY   	400000  	1200000 	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
84772   76020001        Brazil  	BCB Focus Market Report 			CALENDAR_TYPE_EVENT     CALENDAR_IMPORTANCE_MODERATE    BRL     	BR      	CALENDAR_SECTOR_MONEY   	None    	None    	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_NONE
94938   344020004       Hong Kong       Exports y/y     				CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	HKD     	HK      	CALENDAR_SECTOR_TRADE   	18100000        4700000 	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
94939   344020005       Hong Kong       Imports y/y     				CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	HKD     	HK      	CALENDAR_SECTOR_TRADE   	15000000        5300000 	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
94940   344020006       Hong Kong       Trade Balance   				CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_MODERATE    HKD     	HK      	CALENDAR_SECTOR_TRADE   	-29054000       -45000000       CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
102731  578020001       Norway  	Unemployment Rate       			CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_MODERATE    NOK     	NO      	CALENDAR_SECTOR_JOBS    	3700000 	4000000 	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
102732  578020020       Norway  	General Public Domestic Loan Debt y/y   	CALENDAR_TYPE_INDICATOR CALENDAR_IMPORTANCE_LOW 	NOK     	NO      	CALENDAR_SECTOR_MONEY   	3300000 	3500000 	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_MONTH
147163  840031004       United States   Memorial Day    				CALENDAR_TYPE_HOLIDAY   CALENDAR_IMPORTANCE_NONE        USD     	US      	CALENDAR_SECTOR_HOLIDAYS        None    	None    	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_NONE
162245  826090005       United Kingdom  Spring Bank Holiday     			CALENDAR_TYPE_HOLIDAY   CALENDAR_IMPORTANCE_NONE        GBP     	GB      	CALENDAR_SECTOR_HOLIDAYS        None    	None    	CALENDAR_IMPACT_NA      	CALENDAR_FREQUENCY_NONE

Затем выбираем столбец DST для соответствующего графика из TimeSchedule.

ID      DST_UK  		DST_US  		DST_AU  		DST_NONE
18742   2024.05.27 02:00        2024.05.27 02:00        2024.05.27 02:00        2024.05.27 02:00
18746   2024.05.27 14:00        2024.05.27 14:00        2024.05.27 14:00        2024.05.27 14:00
34896   2024.05.27 07:00        2024.05.27 07:00        2024.05.27 07:00        2024.05.27 07:00
34897   2024.05.27 07:00        2024.05.27 07:00        2024.05.27 07:00        2024.05.27 07:00
34898   2024.05.27 07:00        2024.05.27 07:00        2024.05.27 07:00        2024.05.27 07:00
34899   2024.05.27 07:00        2024.05.27 07:00        2024.05.27 07:00        2024.05.27 07:00
55462   2024.05.27 03:30        2024.05.27 03:30        2024.05.27 03:30        2024.05.27 03:30
72568   2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30
72569   2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30
72570   2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30
72571   2024.05.27 15:30        2024.05.27 15:30        2024.05.27 15:30        2024.05.27 15:30
78850   2024.05.27 14:50        2024.05.27 14:50        2024.05.27 14:50        2024.05.27 14:50
78851   2024.05.27 14:50        2024.05.27 14:50        2024.05.27 14:50        2024.05.27 14:50
78852   2024.05.27 14:50        2024.05.27 14:50        2024.05.27 14:50        2024.05.27 14:50
84771   2024.05.27 13:30        2024.05.27 13:30        2024.05.27 13:30        2024.05.27 13:30
84772   2024.05.27 13:30        2024.05.27 13:30        2024.05.27 13:30        2024.05.27 13:30
94938   2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30
94939   2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30
94940   2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30        2024.05.27 10:30
102731  2024.05.27 08:00        2024.05.27 08:00        2024.05.27 08:00        2024.05.27 08:00
102732  2024.05.27 08:00        2024.05.27 08:00        2024.05.27 08:00        2024.05.27 08:00
147163  2024.05.27 02:00        2024.05.27 02:00        2024.05.27 02:00        2024.05.27 02:00
162245  2024.05.27 02:00        2024.05.27 02:00        2024.05.27 02:00        2024.05.27 02:00

Присоединяемся к двум таблицам MQL5Calendar и TimeSchedule по тому же идентификатору.

Сортируем список с Date из таблицы Record.

Date
27-05-2024

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

   string views[] = {"UK","US","AU","NONE"};
   string view_sql = "CREATE VIEW IF NOT EXISTS Calendar_%s "
                     "AS "
                     "SELECT C.Eventid,C.Eventname,C.Country,T.DST_%s as Time,C.EventCurrency,C.Eventcode from MQL5Calendar C,Record R "
                     "Inner join TimeSchedule T on C.ID=T.ID "
                     "Where DATE(REPLACE(T.DST_%s,'.','-'))=R.Date "
                     "Order by T.DST_%s Asc;";

//-- Sql statements for creating the table views
   for(uint i=1;i<=views.Size();i++)
     {
      CalendarContents[i].Content = (CalendarComponents)i;
      CalendarContents[i].name = StringFormat("Calendar_%s",views[i-1]);
      CalendarContents[i].sql = StringFormat(view_sql,views[i-1],views[i-1],views[i-1],views[i-1]);
      CalendarContents[i].tbl_name = StringFormat("Calendar_%s",views[i-1]);
      CalendarContents[i].type = "view";
     }

Давайте рассмотрим одно из представлений и посмотрим, что выдаст результат запроса:

SELECT * FROM 'Calendar_UK';

Результат:

EVENTID 	EVENTNAME       				COUNTRY 	Time    		EVENTCURRENCY   EVENTCODE
999020002       Eurogroup Meeting       			European Union  2024.05.27 02:00        EUR     	EU
840031004       Memorial Day    				United States   2024.05.27 02:00        USD     	US
826090005       Spring Bank Holiday     			United Kingdom  2024.05.27 02:00        GBP     	GB
156010014       Industrial Profit YTD y/y       		China   	2024.05.27 03:30        CNY     	CN
392010004       Coincident Index        			Japan   	2024.05.27 07:00        JPY     	JP
392010005       Leading Index   				Japan   	2024.05.27 07:00        JPY     	JP
392010011       Coincident Index m/m    			Japan   	2024.05.27 07:00        JPY     	JP
392010012       Leading Index m/m       			Japan   	2024.05.27 07:00        JPY     	JP
578020001       Unemployment Rate       			Norway  	2024.05.27 08:00        NOK     	NO
578020020       General Public Domestic Loan Debt y/y   	Norway  	2024.05.27 08:00        NOK     	NO
276030001       Ifo Business Expectations       		Germany 	2024.05.27 10:30        EUR     	DE
276030002       Ifo Current Business Situation  		Germany 	2024.05.27 10:30        EUR     	DE
276030003       Ifo Business Climate    			Germany 	2024.05.27 10:30        EUR     	DE
344020004       Exports y/y     				Hong Kong       2024.05.27 10:30        HKD     	HK
344020005       Imports y/y     				Hong Kong       2024.05.27 10:30        HKD     	HK
344020006       Trade Balance   				Hong Kong       2024.05.27 10:30        HKD     	HK
76020007        BCB Bank Lending m/m    			Brazil  	2024.05.27 13:30        BRL     	BR
76020001        BCB Focus Market Report 			Brazil  	2024.05.27 13:30        BRL     	BR
999010020       ECB Executive Board Member Lane Speech  	European Union  2024.05.27 14:00        EUR     	EU
250020001       3-Month BTF Auction     			France  	2024.05.27 14:50        EUR     	FR
250020002       6-Month BTF Auction     			France  	2024.05.27 14:50        EUR     	FR
250020003       12-Month BTF Auction    			France  	2024.05.27 14:50        EUR     	FR
276050007       Bbk Executive Board Member Mauderer Speech      Germany 	2024.05.27 15:30        EUR     	DE

Технически у нас есть новая таблица Record. Она заменит нашу предыдущую таблицу Records, так как теперь мы будем хранить только одну запись. Наша таблица будет иметь тип данных TEXT и имя столбца Date, которое не следует путать с функцией Date в SQLite.

//-- initializing properties for the Record table
   CalendarContents[5].Content = Record_Table;
   CalendarContents[5].name = "Record";
   CalendarContents[5].sql = "CREATE TABLE Record(Date TEXT NOT NULL)STRICT;";
   CalendarContents[5].tbl_name="Record";
   CalendarContents[5].type = "table";
   CalendarContents[5].insert = "INSERT INTO 'Record'(Date) VALUES (Date(REPLACE('%s','.','-')));";

Наша таблица TimeSchedule будет хранить все временные данные отдельных событий и будет использовать ссылку на внешний ключ ID для связи таблицы (создания связи) с таблицей MQL5Calendar.

//-- initializing properties for the TimeSchedule table
   CalendarContents[6].Content = TimeSchedule_Table;
   CalendarContents[6].name = "TimeSchedule";
   CalendarContents[6].sql = "CREATE TABLE TimeSchedule(ID INT NOT NULL,DST_UK   TEXT   NOT NULL,DST_US   TEXT   NOT NULL,"
                             "DST_AU   TEXT   NOT NULL,DST_NONE   TEXT   NOT NULL,FOREIGN KEY (ID) REFERENCES MQL5Calendar (ID))STRICT;";
   CalendarContents[6].tbl_name="TimeSchedule";
   CalendarContents[6].type = "table";
   CalendarContents[6].insert = "INSERT INTO 'TimeSchedule'(ID,DST_UK,DST_US,DST_AU,DST_NONE) "
                                "VALUES (%d,'%s','%s', '%s', '%s');";

Таблица MQL5Calendar будет иметь первичный ключ с именем "ID", который будет уникальным для каждой записи новостного события в таблице.

//-- initializing properties for the MQL5Calendar table
   CalendarContents[7].Content = MQL5Calendar_Table;
   CalendarContents[7].name = "MQL5Calendar";
   CalendarContents[7].sql = "CREATE TABLE MQL5Calendar(ID INT NOT NULL,EVENTID  INT   NOT NULL,COUNTRY  TEXT   NOT NULL,"
                             "EVENTNAME   TEXT   NOT NULL,EVENTTYPE   TEXT   NOT NULL,EVENTIMPORTANCE   TEXT   NOT NULL,"
                             "EVENTCURRENCY  TEXT   NOT NULL,EVENTCODE   TEXT   NOT NULL,EVENTSECTOR TEXT   NOT NULL,"
                             "EVENTFORECAST  TEXT   NOT NULL,EVENTPREVALUE  TEXT   NOT NULL,EVENTIMPACT TEXT   NOT NULL,"
                             "EVENTFREQUENCY TEXT   NOT NULL,PRIMARY KEY(ID))STRICT;";
   CalendarContents[7].tbl_name="MQL5Calendar";
   CalendarContents[7].type = "table";
   CalendarContents[7].insert = "INSERT INTO 'MQL5Calendar'(ID,EVENTID,COUNTRY,EVENTNAME,EVENTTYPE,EVENTIMPORTANCE,EVENTCURRENCY,EVENTCODE,"
                                "EVENTSECTOR,EVENTFORECAST,EVENTPREVALUE,EVENTIMPACT,EVENTFREQUENCY) "
                                "VALUES (%d,%d,'%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s');";

Создадим триггер под названием OnlyOne_AutoDST. Триггер активируется, когда мы пытаемся вставить значение в AutoDST и удалить все записи из AutoDST перед вставкой новой записи.

//-- Sql statement for creating the AutoDST table's trigger
   CalendarContents[8].Content = AutoDST_Trigger;
   CalendarContents[8].name = "OnlyOne_AutoDST";
   CalendarContents[8].sql = "CREATE TRIGGER IF NOT EXISTS OnlyOne_AutoDST "
                             "BEFORE INSERT ON AutoDST "
                             "BEGIN "
                             "Delete from AutoDST; "
                             "END;";
   CalendarContents[8].tbl_name="AutoDST";
   CalendarContents[8].type = "trigger";

То же самое можно сказать и об OnlyOne_Record, но этот триггер связан с таблицей Record.

//-- Sql statement for creating the Record table's trigger
   CalendarContents[9].Content = Record_Trigger;
   CalendarContents[9].name = "OnlyOne_Record";
   CalendarContents[9].sql = "CREATE TRIGGER IF NOT EXISTS OnlyOne_Record "
                             "BEFORE INSERT ON Record "
                             "BEGIN "
                             "Delete from Record; "
                             "END;";
   CalendarContents[9].tbl_name="Record";
   CalendarContents[9].type = "trigger";

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

Изменения этой функции по сравнению с предыдущей в части 1 заключаются в следующем:

1. Мы прочитаем все объекты, которые не являются индексами, присутствующими в базе данных, с помощью SQL-запроса "select * from sqlite_master where type<>'index' ; ".

2. Мы сохраним все атрибуты объекта в массиве DBContents, и если в конце SQL-оператора нет точки с запятой, мы ее добавим.

3. Мы сравним объекты, найденные в нашей базе данных, и объекты, которые мы инициализируем в нашем массиве CalendarContents. Мы удалим ' IF NOT EXISTS' из CalendarContents.

4. Если совпадений между DBContents и CalendarContents не обнаружено, удалим объект из индекса DBcontents.

5. Если совпадения объектов SQL не равны размеру CalendarContents, мы выполним обновление. 

bool CNews::UpdateRecords()
  {
//initialize variable to true
   bool perform_update=true;
//--- open/create
//-- try to open database Calendar
   int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE| DATABASE_OPEN_COMMON);
   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 perform_update;//Returns true when the database was failed to be opened and the file doesn't exist in the common folder
        }
     }

   int MasterRequest = DatabasePrepare(db,"select * from sqlite_master where type<>'index';");
   if(MasterRequest==INVALID_HANDLE)
     {
      Print("DB: ",NEWS_DATABASE_FILE, " request failed with code ", GetLastError());
     }
   else
     {
      SQLiteMaster ReadContents;
      //Assigning values from the sql query into DBContents array
      for(int i=0; DatabaseReadBind(MasterRequest,ReadContents); i++)
        {
         ArrayResize(DBContents,i+1,i+2);
         DBContents[i].type = ReadContents.type;
         DBContents[i].name = ReadContents.name;
         DBContents[i].tbl_name = ReadContents.tbl_name;
         DBContents[i].rootpage = ReadContents.rootpage;
         /*Check if the end of the sql string has a character ';' if not add this character to the string*/
         DBContents[i].sql = (StringFind(ReadContents.sql,";",StringLen(ReadContents.sql)-1)==
                              (StringLen(ReadContents.sql)-1))?ReadContents.sql:ReadContents.sql+";";;
        }

      uint contents_exists = 0;
      for(uint i=0;i<DBContents.Size();i++)
        {
         bool isCalendarContents = false;
         for(uint x=0;x<CalendarContents.Size();x++)
           {
            /*Store Sql query from CalendarContents without string ' IF NOT EXISTS'*/
            string CalendarSql=CalendarContents[x].sql;
            StringReplace(CalendarSql," IF NOT EXISTS","");
            //-- Check if the Db object is in our list
            if(DBContents[i].name==CalendarContents[x].name&&
               (DBContents[i].sql==CalendarSql||
                DBContents[i].sql==CalendarContents[x].sql)&&
               CalendarContents[x].type==DBContents[i].type&&
               CalendarContents[x].tbl_name==DBContents[i].tbl_name)
              {
               contents_exists++;
               isCalendarContents = true;
              }
           }
         if(!isCalendarContents)
           {
            //-- Print DBcontent's name if it does not match with CalendarContents
            PrintFormat("DBContent: %s is not needed!",DBContents[i].name);
            //-- We will drop the table if it is not neccessary
            DatabaseExecute(db,StringFormat(DropRequest,DBContents[i].type,DBContents[i].name));
            Print("Attempting To Clean Database...");
           }
        }
      /*If not all the CalendarContents exist in the Calendar Database before an update */
      if(contents_exists!=CalendarContents.Size())
        {
         return perform_update;
        }
     }
   if(!DatabaseTableExists(db,CalendarStruct(Record_Table).name))//If the database table 'Record' doesn't exist
     {
      DatabaseClose(db);
      return perform_update;
     }

//-- Sql query to determine the lastest or maximum date recorded
   /* If the last recorded date data in the 'Record' table is not equal to the current day, perform an update! */
   string request_text=StringFormat("SELECT Date FROM %s where Date=Date(REPLACE('%s','.','-'))",
                                    CalendarStruct(Record_Table).name,TimeToString(TimeTradeServer()));
   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 perform_update;
     }

   if(DatabaseRead(request))//Will be true if there are results from the sql query/request
     {
      DatabaseFinalize(request);//Removes a request created in DatabasePrepare()
      DatabaseClose(db);//Closes the database
      perform_update=false;
      return perform_update;
     }
   else
     {
      DatabaseFinalize(request);//Removes a request created in DatabasePrepare()
      DatabaseClose(db);//Closes the database
      return perform_update;
     }
  }

В функции CreateCalendarTable мы проверим, существует ли уже таблица MQL5Calendar в базе данных календаря, а также таблица TimeSchedule, и попытаемся удалить каждую таблицу, если они существуют. Поскольку TimeSchedule требует MQL5Calendar, мы не можем удалить MQL5Calendar, не удалив сначала TimeSchedule. 

Если MQL5Calendar не существует, мы создадим его таблицу.

bool CNews::CreateCalendarTable(int db,bool &tableExists)
  {
//-- Checks if a table 'MQL5Calendar' exists
   if(DatabaseTableExists(db,CalendarStruct(MQL5Calendar_Table).name))
     {
      tableExists=true;//Assigns true to tableExists variable
      //-- Checks if a table 'TimeSchedule' exists in the database 'Calendar'
      if(DatabaseTableExists(db,CalendarStruct(TimeSchedule_Table).name))
        {
         //-- We will drop the table if the table already exists
         if(!DatabaseExecute(db,StringFormat("Drop Table %s",CalendarStruct(TimeSchedule_Table).name)))
           {
            //If the table failed to be dropped/deleted
            PrintFormat("Failed to drop table %s with code %d",CalendarStruct(TimeSchedule_Table).name,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
           }
        }
      //--We will drop the table if the table already exists
      if(!DatabaseExecute(db,StringFormat("Drop Table %s",CalendarStruct(MQL5Calendar_Table).name)))
        {
         //If the table failed to be dropped/deleted
         PrintFormat("Failed to drop table %s with code %d",CalendarStruct(MQL5Calendar_Table).name,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 the database table 'MQL5Calendar' doesn't exist
   if(!DatabaseTableExists(db,CalendarStruct(MQL5Calendar_Table).name))
     {
      //--- create the table 'MQL5Calendar'
      if(!DatabaseExecute(db,CalendarStruct(MQL5Calendar_Table).sql))//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
  }

В функции CreateTimeTable проверяем, существует ли таблица в базе данных Calendar. Если нет, создаем ее. 

bool CNews::CreateTimeTable(int db,bool &tableExists)
  {
//-- If the database table 'TimeSchedule' doesn't exist
   if(!DatabaseTableExists(db,CalendarStruct(TimeSchedule_Table).name))
     {
      //--- create the table 'TimeSchedule'
      if(!DatabaseExecute(db,CalendarStruct(TimeSchedule_Table).sql))//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
  }

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

void CNews::CreateCalendarViews(int db)
  {
   for(uint i=1;i<=4;i++)
     {
      if(!DatabaseExecute(db,CalendarStruct((CalendarComponents)i).sql))//Checks if the view was successfully created
        {
         Print("DB: create the Calendar view failed with code ", GetLastError());
        }
     }
  }

В функции InsertIntoTables вставим каждую запись из массива Evalues в таблицы MQL5Calendar и TimeSchedule соответственно. Даты событий будут скорректированы с учетом различных графиков DST в TimeSchedule.

bool CNews::InsertIntoTables(int db,Calendar &Evalues[])
  {
   for(uint i=0; i<Evalues.Size(); i++)//Looping through all the Economic Events
     {
      string request_insert_into_calendar =
         StringFormat(CalendarStruct(MQL5Calendar_Table).insert,
                      i,
                      Evalues[i].EventId,
                      Evalues[i].CountryName,
                      Evalues[i].EventName,
                      Evalues[i].EventType,
                      Evalues[i].EventImportance,
                      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_insert_into_calendar))//Check if insert query into calendar was successful
        {
         string request_insert_into_time =
            StringFormat(CalendarStruct(TimeSchedule_Table).insert,
                         i,
                         //-- Economic EventDate adjusted for UK DST(Daylight Savings Time)
                         Savings_UK.adjustDaylightSavings(StringToTime(Evalues[i].EventDate)),
                         //-- Economic EventDate adjusted for US DST(Daylight Savings Time)
                         Savings_US.adjustDaylightSavings(StringToTime(Evalues[i].EventDate)),
                         //-- Economic EventDate adjusted for AU DST(Daylight Savings Time)
                         Savings_AU.adjustDaylightSavings(StringToTime(Evalues[i].EventDate)),
                         Evalues[i].EventDate//normal Economic EventDate
                        );//Inserting all the columns for each event record
         if(!DatabaseExecute(db,request_insert_into_time))
           {
            Print(GetLastError());
            //-- Will print the sql query to check for any errors or possible defaults in the query/request
            Print(request_insert_into_time);
            return false;//Will end the loop and return false, as values failed to be inserted into the table
           }
        }
      else
        {
         Print(GetLastError());
         //-- Will print the sql query to check for any errors or possible defaults in the query/request
         Print(request_insert_into_calendar);
         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
  }

В функции CreateRecordTable проверяем, существует ли уже таблица Record. Если нет, создаем таблицу. Как только таблица Record будет создана, создадим для нее триггер и продолжим добавлять текущую дату сервера.


Зачем использовать TimeTradeServer вместо TimeCurrent?

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

void CNews::CreateRecordTable(int db)
  {
   bool failed=false;
   if(!DatabaseTableExists(db,CalendarStruct(Record_Table).name))//Checks if the table 'Record' exists in the databse 'Calendar'
     {
      //--- create the table
      if(!DatabaseExecute(db,CalendarStruct(Record_Table).sql))//Will attempt to create the table 'Record'
        {
         Print("DB: create the Records table failed with code ", GetLastError());
         DatabaseClose(db);//Close the database
         return;//Exits the function if creating the table failed
        }
      else//If Table was created Successfully then Create Trigger
        {
         DatabaseExecute(db,CalendarStruct(Record_Trigger).sql);
        }
     }
   else
     {
      DatabaseExecute(db,CalendarStruct(Record_Trigger).sql);
     }
//Sql query/request to insert the current time into the 'Date' column in the table 'Record'
   string request_text=StringFormat(CalendarStruct(Record_Table).insert,TimeToString(TimeTradeServer()));
   if(!DatabaseExecute(db, request_text))//Will attempt to run this sql request/query
     {
      Print(GetLastError());
      PrintFormat(CalendarStruct(Record_Table).insert,TimeToString(TimeTradeServer()));
      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());
     }
  }
В функции CreateAutoDST мы проверяем расписание перехода на летнее время брокера. Если нам удалось получить расписание перехода на летнее время, мы проверяем, существует ли таблица AutoDST в базе данных Calendar. Если таблица AutoDST не существует, она будет создана. После создания таблицы AutoDST мы создаем ее триггер и пытаемся вставить график DST, преобразованный из перечисления в строку. 
void CNews::CreateAutoDST(int db)
  {
   bool failed=false;//boolean variable
   if(!AutoDetectDST(DSTType))//Check if AutoDetectDST went through all the right procedures
     {
      return;//will terminate execution of the rest of the code below
     }

   if(!DatabaseTableExists(db,CalendarStruct(AutoDST_Table).name))//Checks if the table 'AutoDST' exists in the databse 'Calendar'
     {
      //--- create the table AutoDST
      if(!DatabaseExecute(db,CalendarStruct(AutoDST_Table).sql))//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//If Table was created Successfully then Create Trigger
        {
         DatabaseExecute(db,CalendarStruct(AutoDST_Trigger).sql);
        }
     }
   else
     {
      //Create trigger if AutoDST table exists
      DatabaseExecute(db,CalendarStruct(AutoDST_Trigger).sql);
     }
//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(CalendarStruct(AutoDST_Table).insert,EnumToString(DSTType));
   if(!DatabaseExecute(db, request_text))//Will attempt to run this sql request/query
     {
      Print(GetLastError());
      PrintFormat(CalendarStruct(AutoDST_Table).insert,EnumToString(DSTType));//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());
     }
  }


Класс управления рисками

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

В этой статье понятия "лот", "размер лота" и "объем" используются как синонимы. Рассматривайте их как одно и то же в контексте управления рисками. 


Список профилей риска

  • Минимальный размер лота
  • Максимальный размер лота
  • Процент от баланса
  • Процент свободной маржи
  • Риск в сумме на баланс
  • Риск в сумме на свободную маржу
  • Размер лота на баланс
  • Размер лота на свободную маржу
  • Пользовательский размер лота
  • Процент риска

Минимальный размер лота:

В этом варианте мы будем использовать минимально допустимый размер лота для текущего символа.

Максимальный размер лота:

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

Процент от баланса:

Здесь мы сначала получим размер риска.

amount_of_risk = Balance*Percent;

Допустим, что Percent = 5%, а баланс счета равен 10 000.

amount_of_risk = 10000*(5/100);
amount_of_risk = 500;

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

OrderCalcProfit(ORDER_TYPE,Symbol(),Minimum_lotsize,OpenPrice,ClosePrice,Minimum_risk);

При возврате Minimum_risk воспользуемся следующим уравнением, чтобы получить требуемый размер лота для amount_of_risk

required_lotsize = (amount_of_risk/Minimum_risk)*Minimum_lotsize;

Допустим, что Minimum_risk = 100, а Minimum_lotsize = 0.1;

required_lotsize = (500/100)*0.1;
required_lotsize = 5*0.1;
required_lotsize = 0.5;


Процент свободной маржи:

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

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


Риск в сумме на баланс:

Здесь нам нужно сначала получить частное суммарного риска (делитель) и баланса (делимое).

risk = Balance/Risk_in_Amount;

Тогда пусть Balance = 10 000, а Risk_in_Amount = 800;

В этом случае мы, по сути, хотим рисковать USD 800 за сделку на каждые USD 10 000 на балансе счета трейдера.

risk = 10000/800;
risk = 12.5;

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

amount_of_risk = AccountBalance/risk;

Допустим, что AccountBalance = 5000;

amount_of_risk = 5000/12.5;
amount_of_risk = 400;

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


Риск в сумме на свободную маржу:

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

risk = FreeMargin/Risk_in_Amount;

Допустим, что FreeMargin = 150, а Risk_in_Amount = 1;

В этом случае мы будем рисковать USD 1 на каждые USD 150 FreeMargin.

risk = 150/1;
risk = 150;

amount_of_risk = AccountFreeMargin/risk;

//-- Let AccountFreeMargin = 750

amount_of_risk = 750/150;
amount_of_risk = 5;

Получив сумму риска, мы рассчитаем требуемый размер лота для данной конкретной сделки, чтобы покрыть риск в USD 5.


Размер лота на баланс:

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

required_lotsize = (AccountBalance/Balance)*lotsize;

Здесь AccountBalance - фактический баланс на счете трейдера, Balance и lotsize - входные значения, предоставленные трейдером.

Допустим AccountBalance = 10 000, Balance = 350, а lotsize = 0,01

В этом случае трейдер хочет рискнуть 0,01 лота на каждые USD 350 на балансе своего счета. 

required_lotsize = (10000/350)*0.01;
required_lotsize = 0.285;

required_lotsize равен 0,285, фактическое значение - намного больше. Предположим, что шаг объема для конкретного символа, по которому мы хотим открыть сделку, составляет 0,01. Попытка открыть сделку с размером лота 0,285 при шаге объема 0,01 приведет к ошибке.

Чтобы предотвратить это, мы нормализуем размер лота. По сути, мы отформатируем размер лота в соответствии с шагом объема.

required_lotsize = Volume_Step*MathFloor(0.285/Volume_Step);
requred_lotsize = 0.01*MathFloor(0.285/0.01);
required_lotsize = 0.01*MathFloor(28.5);
required_lotsize = 0.01*28;
required_lotsize = 0.28;


Размер лота на свободную маржу:

Этот вариант аналогичен размеру лота на баланс. Приведем еще один пример.

required_lotsize = (AccountFreeMargin/FreeMargin)*lotsize;

Допустим, что AccountFreeMargin = 134 560, FreeMargin = 1622, а lot-size = 0,0056

В данном случае:

required_lotsize = (134560/1622)*0.0056;
required_lotsize = 0.464;

Предположим, что шаг Volume равен 0,02.

Нормализуем required_lotsize в соответствии с шагом объема (Volume step).

//-- normalize for Volume Step
required_lotsize = Volume_Step*MathFloor(0.464/Volume_Step);
requred_lotsize = 0.02*MathFloor(0.464/0.02);
required_lotsize = 0.02*MathFloor(23.2);
required_lotsize = 0.02*23;
required_lotsize = 0.46;


Пользовательский размер лота:

В этом варианте мы будем использовать размер входного лота, предоставленный трейдером.


Процент риска:

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

Класс CRiskManagement имеет многоуровневое наследование от классов:

  • CSymbolProperties
  • CChartProperties

Класс CRiskManagement имеет включение из класса CAccountInfo.

Класс CRiskManagement располагает иерархическим наследованием от классов:

  • CSymbolProperties
  • CSymbolInfo
//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+
#include "ChartProperties.mqh"
#include <Trade/AccountInfo.mqh>
CAccountInfo      Account;

//-- Enumeration declaration for Risk options
enum RiskOptions
  {
   MINIMUM_LOT,//MINIMUM LOTSIZE
   MAXIMUM_LOT,//MAXIMUM LOTSIZE
   PERCENTAGE_OF_BALANCE,//PERCENTAGE OF BALANCE
   PERCENTAGE_OF_FREEMARGIN,//PERCENTAGE OF FREE-MARGIN
   AMOUNT_PER_BALANCE,//AMOUNT PER BALANCE
   AMOUNT_PER_FREEMARGIN,//AMOUNT PER FREE-MARGIN
   LOTSIZE_PER_BALANCE,//LOTSIZE PER BALANCE
   LOTSIZE_PER_FREEMARGIN,//LOTSIZE PER FREE-MARGIN
   CUSTOM_LOT,//CUSTOM LOTSIZE
   PERCENTAGE_OF_MAXRISK//PERCENTAGE OF MAX-RISK
  } RiskProfileOption;//variable for Risk options

//-- Enumeration declaration for Risk floor
enum RiskFloor
  {
   RiskFloorMin,//MINIMUM LOTSIZE
   RiskFloorMax,//MAX-RISK
   RiskFloorNone//NONE
  } RiskFloorOption;//variable for Risk floor

//-- Enumeration declaration for Risk ceiling(Maximum allowable risk in terms of lot-size)
enum RiskCeil
  {
   RiskCeilMax,//MAX LOTSIZE
   RiskCeilMax2,//MAX LOTSIZE(x2)
   RiskCeilMax3,//MAX LOTSIZE(x3)
   RiskCeilMax4,//MAX LOTSIZE(x4)
   RiskCeilMax5,//MAX LOTSIZE(x5)
  } RiskCeilOption;//variable for Risk ceiling

//-- Structure declaration for Risk options (AMOUNT PER BALANCE and AMOUNT PER FREE-MARGIN)
struct RISK_AMOUNT
  {
   double            RiskAmountBoF;//store Balance or Free-Margin
   double            RiskAmount;//store risk amount
  } Risk_Profile_2;//variable for Risk options (AMOUNT PER BALANCE and AMOUNT PER FREE-MARGIN)

//-- Structure declaration for Risk options (LOTSIZE PER BALANCE and LOTSIZE PER FREE-MARGIN)
struct RISK_LOT
  {
   double            RiskLotBoF;//store Balance or Free-Margin
   double            RiskLot;//store lot-size
  } Risk_Profile_3;//variable for Risk options (LOTSIZE PER BALANCE and LOTSIZE PER FREE-MARGIN)


double            RiskFloorPercentage;//variable for RiskFloorMax
double            Risk_Profile_1;//variable for Risk options (PERCENTAGE OF BALANCE and PERCENTAGE OF FREE-MARGIN)
double            Risk_Profile_4;//variable for Risk option (CUSTOM LOTSIZE)
double            Risk_Profile_5;//variable for Risk option (PERCENTAGE OF MAX-RISK)

//+------------------------------------------------------------------+
//|RiskManagement class                                              |
//+------------------------------------------------------------------+
class CRiskManagement : public CChartProperties
  {

private:
   double            Medium;//variable to store actual Account (Balance or Free-Margin)
   double            RiskAmount,MinimumAmount;
   double            Lots;//variable to store Lot-size to open trade
   const double      max_percent;//variable to store percentage for Maximum risk

   //-- enumeration for dealing with account balance/free-margin
   enum RiskMedium
     {
      BALANCE,
      MARGIN
     };

   //-- calculations for Risk options (PERCENTAGE OF BALANCE and PERCENTAGE OF FREE-MARGIN)
   double              RiskProfile1(const RiskMedium R_Medium);
   //-- calculations for Risk options (AMOUNT PER BALANCE and AMOUNT PER FREE-MARGIN)
   double              RiskProfile2(const RiskMedium R_Medium);
   //-- calculations for Risk options (LOTSIZE PER BALANCE and LOTSIZE PER FREE-MARGIN)
   double              RiskProfile3(const RiskMedium R_Medium);
   //-- calculations for Maximum allowable Risk
   double              MaxRisk(const double percent);
   //-- Store Trade's Open-price
   double              OpenPrice;
   //-- Store Trade's Close-price
   double              ClosePrice;
   //-- Store Ordertype between (ORDER_TYPE_BUY or ORDER_TYPE_SELL) for risk calaculations
   ENUM_ORDER_TYPE     ORDERTYPE;
   //-- Set Medium variable value
   void                SetMedium(const RiskMedium R_Medium) {Medium = (R_Medium==BALANCE)?Account.Balance():Account.FreeMargin();}
   //-- Get Minimum Risk for a Trade using Minimum Lot-size
   bool                GetMinimumRisk()
     {
      return OrderCalcProfit(ORDERTYPE,Symbol(),LotsMin(),OpenPrice,ClosePrice,MinimumAmount);
     }
   //-- Retrieve Risk amount based on Risk inputs
   double            GetRisk(double Amount)
     {
      if(!GetMinimumRisk()||Amount==0)
         return 0.0;
      return ((Amount/MinimumAmount)*LotsMin());
     }

protected:
   //-- Application of Lot-size limits
   void              ValidateLotsize(double &Lotsize);
   //-- Set ORDERTYPE variable to (ORDER_TYPE_BUY or ORDER_TYPE_SELL) respectively
   void              SetOrderType(ENUM_ORDER_TYPE Type)
     {
      if(Type==ORDER_TYPE_BUY||Type==ORDER_TYPE_BUY_LIMIT||Type==ORDER_TYPE_BUY_STOP)
        {
         ORDERTYPE = ORDER_TYPE_BUY;
        }
      else
         if(Type==ORDER_TYPE_SELL||Type==ORDER_TYPE_SELL_LIMIT||Type==ORDER_TYPE_SELL_STOP)
           {
            ORDERTYPE = ORDER_TYPE_SELL;
           }
     }

public:

                     CRiskManagement();//Class's constructor
   //-- Retrieve user's Risk option
   string            GetRiskOption()
     {
      switch(RiskProfileOption)
        {
         case  MINIMUM_LOT://MINIMUM LOTSIZE - Risk Option
            return "MINIMUM LOTSIZE";
            break;
         case MAXIMUM_LOT://MAXIMUM LOTSIZE - Risk Option
            return "MAXIMUM LOTSIZE";
            break;
         case PERCENTAGE_OF_BALANCE://PERCENTAGE OF BALANCE - Risk Option
            return "PERCENTAGE OF BALANCE";
            break;
         case PERCENTAGE_OF_FREEMARGIN://PERCENTAGE OF FREE-MARGIN - Risk Option
            return "PERCENTAGE OF FREE-MARGIN";
            break;
         case AMOUNT_PER_BALANCE://AMOUNT PER BALANCE - Risk Option
            return "AMOUNT PER BALANCE";
            break;
         case AMOUNT_PER_FREEMARGIN://AMOUNT PER FREE-MARGIN - Risk Option
            return "AMOUNT PER FREE-MARGIN";
            break;
         case LOTSIZE_PER_BALANCE://LOTSIZE PER BALANCE - Risk Option
            return "LOTSIZE PER BALANCE";
            break;
         case LOTSIZE_PER_FREEMARGIN://LOTSIZE PER FREE-MARGIN - Risk Option
            return "LOTSIZE PER FREE-MARGIN";
            break;
         case CUSTOM_LOT://CUSTOM LOTSIZE - Risk Option
            return "CUSTOM LOTSIZE";
            break;
         case PERCENTAGE_OF_MAXRISK://PERCENTAGE OF MAX-RISK - Risk Option
            return "PERCENTAGE OF MAX-RISK";
            break;
         default:
            return "";
            break;
        }
     }
   //-- Retrieve user's Risk Floor Option
   string            GetRiskFloor()
     {
      switch(RiskFloorOption)
        {
         case RiskFloorMin://MINIMUM LOTSIZE for Risk floor options
            return "MINIMUM LOTSIZE";
            break;
         case RiskFloorMax://MAX-RISK for Risk floor options
            return "MAX-RISK";
            break;
         case RiskFloorNone://NONE for Risk floor options
            return "NONE";
            break;
         default:
            return "";
            break;
        }
     }
   //-- Retrieve user's Risk Ceiling option
   string            GetRiskCeil()
     {
      switch(RiskCeilOption)
        {
         case  RiskCeilMax://MAX LOTSIZE for Risk ceiling options
            return "MAX LOTSIZE";
            break;
         case RiskCeilMax2://MAX LOTSIZE(x2) for Risk ceiling options
            return "MAX LOTSIZE(x2)";
            break;
         case RiskCeilMax3://MAX LOTSIZE(x3) for Risk ceiling options
            return "MAX LOTSIZE(x3)";
            break;
         case RiskCeilMax4://MAX LOTSIZE(x4) for Risk ceiling options
            return "MAX LOTSIZE(x4)";
            break;
         case RiskCeilMax5://MAX LOTSIZE(x5) for Risk ceiling options
            return "MAX LOTSIZE(x5)";
            break;
         default:
            return "";
            break;
        }
     }

   double            Volume();//Get risk in Volume
   //Apply fixes to lot-size where applicable
   void              NormalizeLotsize(double &Lotsize);
  };

//+------------------------------------------------------------------+
//|Constructor                                                       |
//+------------------------------------------------------------------+
//Initialize values
CRiskManagement::CRiskManagement(void):Lots(0.0),max_percent(100),
   ORDERTYPE(ORDER_TYPE_BUY),OpenPrice(Ask()),
   ClosePrice(NormalizePrice(Ask()+Ask()*0.01))

  {
  }

//+------------------------------------------------------------------+
//|Get risk in Volume                                                |
//+------------------------------------------------------------------+
double CRiskManagement::Volume()
  {
   switch(RiskProfileOption)
     {
      case  MINIMUM_LOT://MINIMUM LOTSIZE - Risk Option
         return LotsMin();
         break;
      case MAXIMUM_LOT://MAXIMUM LOTSIZE - Risk Option
         Lots = LotsMax();
         break;
      case PERCENTAGE_OF_BALANCE://PERCENTAGE OF BALANCE - Risk Option
         Lots = RiskProfile1(BALANCE);
         break;
      case PERCENTAGE_OF_FREEMARGIN://PERCENTAGE OF FREE-MARGIN - Risk Option
         Lots = RiskProfile1(MARGIN);
         break;
      case AMOUNT_PER_BALANCE://AMOUNT PER BALANCE - Risk Option
         Lots = RiskProfile2(BALANCE);
         break;
      case AMOUNT_PER_FREEMARGIN://AMOUNT PER FREE-MARGIN - Risk Option
         Lots = RiskProfile2(MARGIN);
         break;
      case LOTSIZE_PER_BALANCE://LOTSIZE PER BALANCE - Risk Option
         Lots =  RiskProfile3(BALANCE);
         break;
      case LOTSIZE_PER_FREEMARGIN://LOTSIZE PER FREE-MARGIN - Risk Option
         Lots = RiskProfile3(MARGIN);
         break;
      case CUSTOM_LOT://CUSTOM LOTSIZE - Risk Option
         Lots = Risk_Profile_4;
         break;
      case PERCENTAGE_OF_MAXRISK://PERCENTAGE OF MAX-RISK - Risk Option
         Lots = MaxRisk(Risk_Profile_5);
         break;
      default:
         Lots = 0.0;
         break;
     }
   ValidateLotsize(Lots);//Check/Adjust Lotsize Limits
   NormalizeLotsize(Lots);//Normalize Lotsize
   return Lots;
  }

//+------------------------------------------------------------------+
//|calculations for Risk options                                     |
//|(PERCENTAGE OF BALANCE and PERCENTAGE OF FREE-MARGIN)             |
//+------------------------------------------------------------------+
//-- calculations for Risk options (PERCENTAGE OF BALANCE and PERCENTAGE OF FREE-MARGIN)
double CRiskManagement::RiskProfile1(const RiskMedium R_Medium)
  {
   SetMedium(R_Medium);
   RiskAmount = Medium*(Risk_Profile_1/100);
   return GetRisk(RiskAmount);
  }

//+------------------------------------------------------------------+
//|calculations for Risk options                                     |
//|(AMOUNT PER BALANCE and AMOUNT PER FREE-MARGIN)                   |
//+------------------------------------------------------------------+
//-- calculations for Risk options (AMOUNT PER BALANCE and AMOUNT PER FREE-MARGIN)
double CRiskManagement::RiskProfile2(const RiskMedium R_Medium)
  {
   SetMedium(R_Medium);
   double risk = (Risk_Profile_2.RiskAmountBoF/Risk_Profile_2.RiskAmount);
   risk = (risk<1)?1:risk;

   if(Medium<=0)
      return 0.0;

   RiskAmount = Medium/risk;
   return GetRisk(RiskAmount);
  }

//+------------------------------------------------------------------+
//|calculations for Risk options                                     |
//|(LOTSIZE PER BALANCE and LOTSIZE PER FREE-MARGIN)                 |
//+------------------------------------------------------------------+
//-- calculations for Risk options (LOTSIZE PER BALANCE and LOTSIZE PER FREE-MARGIN)
double CRiskManagement::RiskProfile3(const RiskMedium R_Medium)
  {
   SetMedium(R_Medium);
   return (Medium>0)?((Medium/Risk_Profile_3.RiskLotBoF)*Risk_Profile_3.RiskLot):0.0;
  }

//+------------------------------------------------------------------+
//|calculations for Maximum allowable Risk                           |
//+------------------------------------------------------------------+
//-- calculations for Maximum allowable Risk
double CRiskManagement::MaxRisk(const double percent)
  {
   double margin=0.0,max_risk=0.0;
//--- checks
   if(percent<0.01 || percent>100)
     {
      Print(__FUNCTION__," invalid parameters");
      return(0.0);
     }
//--- calculate margin requirements for 1 lot
   if(!OrderCalcMargin(ORDERTYPE,Symbol(),1.0,OpenPrice,margin) || margin<0.0)
     {
      Print(__FUNCTION__," margin calculation failed");
      return(0.0);
     }
//--- calculate maximum volume
   max_risk=Account.FreeMargin()*(percent/100.0)/margin;
//--- return volume
   return(max_risk);
  }

//+------------------------------------------------------------------+
//|Apply fixes to lot-size where applicable                          |
//+------------------------------------------------------------------+
void CRiskManagement::NormalizeLotsize(double &Lotsize)
  {
   if(Lotsize<=0.0)
      return;

//-- Check if the is a Volume limit for the current Symbol
   if(LotsLimit()>0.0)
     {
      if((Lots+PositionsVolume()+OrdersVolume())>LotsLimit())
        {
         //-- calculation of available lotsize remaining
         double remaining_avail_lots = (LotsLimit()-(PositionsVolume()+OrdersVolume()));
         if(remaining_avail_lots>=LotsMin())
           {
            if(RiskFloorOption==RiskFloorMin)//Check if Risk floor option is MINIMUM LOTSIZE
              {
               Print("Warning: Volume Limit Reached, minimum Lotsize selected.");
               Lotsize = LotsMin();
              }
            else
               if(RiskFloorOption==RiskFloorMax)//Check if Risk floor option is MAX-RISK
                 {
                  Print("Warning: Volume Limit Reached, Lotsize Reduced.");
                  Lotsize = ((remaining_avail_lots*(RiskFloorPercentage/100))>LotsMin())?
                            (remaining_avail_lots*(RiskFloorPercentage/100)):LotsMin();
                 }
           }
         else
           {
            Print("Volume Limit Reached!");
            Lotsize=0.0;
            return;
           }
        }
     }

//Check if there is a valid Volume Step for the current Symbol
   if(LotsStep()>0.0)
      Lotsize=LotsStep()*MathFloor(Lotsize/LotsStep());
  }

//+------------------------------------------------------------------+
//|Application of Lot-size limits                                    |
//+------------------------------------------------------------------+
void CRiskManagement::ValidateLotsize(double &Lotsize)
  {
   switch(RiskFloorOption)
     {
      case RiskFloorMin://MINIMUM LOTSIZE for Risk floor options
         //-- Check if lot-size is not less than Minimum lot or more than maximum allowable risk
         if(Lotsize<LotsMin()||Lotsize>MaxRisk(max_percent))
           {
            Lotsize=LotsMin();
           }
         break;
      case RiskFloorMax://MAX-RISK for Risk floor options
         //-- Check if lot-size is more the maximum allowable risk
         if(Lotsize>MaxRisk(max_percent))
           {
            Lotsize=(MaxRisk(RiskFloorPercentage)>LotsMin())?MaxRisk(RiskFloorPercentage):LotsMin();
           }
         else
            if(Lotsize<LotsMin())//Check if lot-size is less than Minimum lot
              {
               Lotsize=LotsMin();
              }
         break;
      case RiskFloorNone://NONE for Risk floor options
         //Check if lot-size is less than Minimum lot
         if(Lotsize<LotsMin())
           {
            Lotsize=0.0;
           }
         break;
      default:
         Lotsize=0.0;
         break;
     }

   switch(RiskCeilOption)
     {
      case  RiskCeilMax://MAX LOTSIZE for Risk ceiling options
         //Check if lot-size is more than Maximum lot
         if(Lotsize>LotsMax())
            Lotsize=LotsMax();
         break;
      case RiskCeilMax2://MAX LOTSIZE(x2) for Risk ceiling options
         //Check if lot-size is more than Maximum lot times two
         if(Lotsize>(LotsMax()*2))
            Lotsize=(LotsMax()*2);
         break;
      case RiskCeilMax3://MAX LOTSIZE(x3) for Risk ceiling options
         //Check if lot-size is more than Maximum lot times three
         if(Lotsize>(LotsMax()*3))
            Lotsize=(LotsMax()*3);
         break;
      case RiskCeilMax4://MAX LOTSIZE(x4) for Risk ceiling options
         //Check if lot-size is more than Maximum lot times four
         if(Lotsize>(LotsMax()*4))
            Lotsize=(LotsMax()*4);
         break;
      case RiskCeilMax5://MAX LOTSIZE(x5) for Risk ceiling options
         //Check if lot-size is more than Maximum lot times five
         if(Lotsize>(LotsMax()*5))
            Lotsize=(LotsMax()*5);
         break;
      default:
         break;
     }
  }
//+------------------------------------------------------------------+

Переменная RiskProfileOption типа перечисления RiskOptions будет хранить параметр профиля риска пользователя/трейдера, который послужит входным параметром советника.  

//-- Enumeration declaration for Risk options
enum RiskOptions
  {
   MINIMUM_LOT,//MINIMUM LOTSIZE
   MAXIMUM_LOT,//MAXIMUM LOTSIZE
   PERCENTAGE_OF_BALANCE,//PERCENTAGE OF BALANCE
   PERCENTAGE_OF_FREEMARGIN,//PERCENTAGE OF FREE-MARGIN
   AMOUNT_PER_BALANCE,//AMOUNT PER BALANCE
   AMOUNT_PER_FREEMARGIN,//AMOUNT PER FREE-MARGIN
   LOTSIZE_PER_BALANCE,//LOTSIZE PER BALANCE
   LOTSIZE_PER_FREEMARGIN,//LOTSIZE PER FREE-MARGIN
   CUSTOM_LOT,//CUSTOM LOTSIZE
   PERCENTAGE_OF_MAXRISK//PERCENTAGE OF MAX-RISK
  } RiskProfileOption;//variable for Risk options

Переменная RiskFloorOption типа перечисления RiskFloor будет хранить минимально допустимое значение риска пользователя/трейдера, которое послужит входным параметром советника.

//-- Enumeration declaration for Risk floor
enum RiskFloor
  {
   RiskFloorMin,//MINIMUM LOTSIZE
   RiskFloorMax,//MAX-RISK
   RiskFloorNone//NONE
  } RiskFloorOption;//variable for Risk floor

Переменная RiskCeilOption типа перечисления RiskCeil будет хранить максимально допустимое значение риска пользователя/трейдера, которое послужит входным параметром советника.

//-- Enumeration declaration for Risk ceiling(Maximum allowable risk in terms of lot-size)
enum RiskCeil
  {
   RiskCeilMax,//MAX LOTSIZE
   RiskCeilMax2,//MAX LOTSIZE(x2)
   RiskCeilMax3,//MAX LOTSIZE(x3)
   RiskCeilMax4,//MAX LOTSIZE(x4)
   RiskCeilMax5,//MAX LOTSIZE(x5)
  } RiskCeilOption;//variable for Risk ceiling

Баланс счета или свободная маржа пользователя/трейдера будут сохранены в double-переменной RiskAmountBoF, а double-переменная RiskAmount будет хранить значение риска. Risk_Profile_2 будет использоваться для хранения свойств профилей риска AMOUNT PER BALANCE (сумма на баланс) и AMOUNT PER FREE-MARGIN (сумма на свободную маржу).

//-- Structure declaration for Risk options (AMOUNT PER BALANCE and AMOUNT PER FREE-MARGIN)
struct RISK_AMOUNT
  {
   double            RiskAmountBoF;//store Balance or Free-Margin
   double            RiskAmount;//store risk amount
  } Risk_Profile_2;//variable for Risk options (AMOUNT PER BALANCE and AMOUNT PER FREE-MARGIN)

Переменная Risk_Profile_3 структуры типа RISK_LOT будет хранить свойства профилей риска LOTSIZE PER BALANCE (размер лота на баланс) и LOTSIZE PER FREE-MARGIN (размер лота на свободную маржу).

//-- Structure declaration for Risk options (LOTSIZE PER BALANCE and LOTSIZE PER FREE-MARGIN)
struct RISK_LOT
  {
   double            RiskLotBoF;//store Balance or Free-Margin
   double            RiskLot;//store lot-size
  } Risk_Profile_3;//variable for Risk options (LOTSIZE PER BALANCE and LOTSIZE PER FREE-MARGIN)

Переменная RiskFloorPercentage будет хранить процент максимального риска для опции Riskfloor под названием RiskFloorMax.

double            RiskFloorPercentage;//variable for RiskFloorMax

Переменная Risk_Profile_1 будет хранить процент риска для вариантов PERCENTAGE OF BALANCE (процент баланса) или PERCENTAGE OF FREE-MARGIN (процент свободной маржи).

double            Risk_Profile_1;//variable for Risk options (PERCENTAGE OF BALANCE and PERCENTAGE OF FREE-MARGIN)

Переменная Risk_Profile_4 будет хранить пользовательский размер лота для варианта CUSTOM LOTSIZE (пользовательский размер лота).

double            Risk_Profile_4;//variable for Risk option (CUSTOM LOTSIZE)

Переменная Risk_Profile_5 будет хранить процент максимального риска для варианта PERCENTAGE OF MAX-RISK (процент максимального риска).

double            Risk_Profile_5;//variable for Risk option (PERCENTAGE OF MAX-RISK)

В функции GetRiskOption мы получим параметр риска пользователя/трейдера в строковом типе данных.

   //-- Retrieve user's Risk option
   string            GetRiskOption()
     {
      switch(RiskProfileOption)
        {
         case  MINIMUM_LOT://MINIMUM LOTSIZE - Risk Option
            return "MINIMUM LOTSIZE";
            break;
         case MAXIMUM_LOT://MAXIMUM LOTSIZE - Risk Option
            return "MAXIMUM LOTSIZE";
            break;
         case PERCENTAGE_OF_BALANCE://PERCENTAGE OF BALANCE - Risk Option
            return "PERCENTAGE OF BALANCE";
            break;
         case PERCENTAGE_OF_FREEMARGIN://PERCENTAGE OF FREE-MARGIN - Risk Option
            return "PERCENTAGE OF FREE-MARGIN";
            break;
         case AMOUNT_PER_BALANCE://AMOUNT PER BALANCE - Risk Option
            return "AMOUNT PER BALANCE";
            break;
         case AMOUNT_PER_FREEMARGIN://AMOUNT PER FREE-MARGIN - Risk Option
            return "AMOUNT PER FREE-MARGIN";
            break;
         case LOTSIZE_PER_BALANCE://LOTSIZE PER BALANCE - Risk Option
            return "LOTSIZE PER BALANCE";
            break;
         case LOTSIZE_PER_FREEMARGIN://LOTSIZE PER FREE-MARGIN - Risk Option
            return "LOTSIZE PER FREE-MARGIN";
            break;
         case CUSTOM_LOT://CUSTOM LOTSIZE - Risk Option
            return "CUSTOM LOTSIZE";
            break;
         case PERCENTAGE_OF_MAXRISK://PERCENTAGE OF MAX-RISK - Risk Option
            return "PERCENTAGE OF MAX-RISK";
            break;
         default:
            return "";
            break;
        }
     }

В функции GetRiskFloor мы получим значение минимального уровня риска пользователя/трейдера в строковом типе данных.

   //-- Retrieve user's Risk Floor Option
   string            GetRiskFloor()
     {
      switch(RiskFloorOption)
        {
         case RiskFloorMin://MINIMUM LOTSIZE for Risk floor options
            return "MINIMUM LOTSIZE";
            break;
         case RiskFloorMax://MAX-RISK for Risk floor options
            return "MAX-RISK";
            break;
         case RiskFloorNone://NONE for Risk floor options
            return "NONE";
            break;
         default:
            return "";
            break;
        }
     }

В функции GetRiskCeil мы получим параметр Risk Ceiling (потолок риска) пользователя/трейдера в строковом типе данных.

   //-- Retrieve user's Risk Ceiling option
   string            GetRiskCeil()
     {
      switch(RiskCeilOption)
        {
         case  RiskCeilMax://MAX LOTSIZE for Risk ceiling options
            return "MAX LOTSIZE";
            break;
         case RiskCeilMax2://MAX LOTSIZE(x2) for Risk ceiling options
            return "MAX LOTSIZE(x2)";
            break;
         case RiskCeilMax3://MAX LOTSIZE(x3) for Risk ceiling options
            return "MAX LOTSIZE(x3)";
            break;
         case RiskCeilMax4://MAX LOTSIZE(x4) for Risk ceiling options
            return "MAX LOTSIZE(x4)";
            break;
         case RiskCeilMax5://MAX LOTSIZE(x5) for Risk ceiling options
            return "MAX LOTSIZE(x5)";
            break;
         default:
            return "";
            break;
        }
     }

В конструкторе класса управления рисками инициализируем ранее объявленные переменные значением по умолчанию. Значением по умолчанию для переменной ORDERTYPE является ORDER_TYPE_BUY, поэтому для рисковых опционов, требующих тип ордера для расчета риска, тип ордера будет установлен этой переменной и будет использоваться для моделирования расчетов риска при открытии сделок. Цена открытия по умолчанию будет сохранена в переменной OpenPrice и будет ценой Ask для нашего ORDERTYPE. Цена закрытия по умолчанию будет иметь отклонение в 1% от цены Ask, сохраненной в переменной ClosePrice.

//Initialize values
CRiskManagement::CRiskManagement(void):Lots(0.0),max_percent(100),
   ORDERTYPE(ORDER_TYPE_BUY),OpenPrice(Ask()),
   ClosePrice(NormalizePrice(Ask()+Ask()*0.01))

  {
  }

Функция Volume извлекает размер лота для профиля риска пользователя/трейдера и корректирует размер лота на основе выбранного параметра риска в соответствии с параметрами Risk Ceiling и Risk Floor, выбранными пользователем/трейдером.

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

double CRiskManagement::Volume()
  {
   switch(RiskProfileOption)
     {
      case  MINIMUM_LOT://MINIMUM LOTSIZE - Risk Option
         return LotsMin();
         break;
      case MAXIMUM_LOT://MAXIMUM LOTSIZE - Risk Option
         Lots = LotsMax();
         break;
      case PERCENTAGE_OF_BALANCE://PERCENTAGE OF BALANCE - Risk Option
         Lots = RiskProfile1(BALANCE);
         break;
      case PERCENTAGE_OF_FREEMARGIN://PERCENTAGE OF FREE-MARGIN - Risk Option
         Lots = RiskProfile1(MARGIN);
         break;
      case AMOUNT_PER_BALANCE://AMOUNT PER BALANCE - Risk Option
         Lots = RiskProfile2(BALANCE);
         break;
      case AMOUNT_PER_FREEMARGIN://AMOUNT PER FREE-MARGIN - Risk Option
         Lots = RiskProfile2(MARGIN);
         break;
      case LOTSIZE_PER_BALANCE://LOTSIZE PER BALANCE - Risk Option
         Lots =  RiskProfile3(BALANCE);
         break;
      case LOTSIZE_PER_FREEMARGIN://LOTSIZE PER FREE-MARGIN - Risk Option
         Lots = RiskProfile3(MARGIN);
         break;
      case CUSTOM_LOT://CUSTOM LOTSIZE - Risk Option
         Lots = Risk_Profile_4;
         break;
      case PERCENTAGE_OF_MAXRISK://PERCENTAGE OF MAX-RISK - Risk Option
         Lots = MaxRisk(Risk_Profile_5);
         break;
      default:
         Lots = 0.0;
         break;
     }
   ValidateLotsize(Lots);//Check/Adjust Lotsize Limits
   NormalizeLotsize(Lots);//Normalize Lotsize
   return Lots;
  }

Функция SetMedium присвоит переменной Medium значение баланса счета пользователя/трейдера или свободной маржи счета на основе переменной перечисления R_Medium.

//-- Set Medium variable value
   void                SetMedium(const RiskMedium R_Medium) {Medium = (R_Medium==BALANCE)?Account.Balance():Account.FreeMargin();}

Функция GetMinimumRisk присвоит переменной MinimumAmount минимально допустимый риск для конкретной сделки с минимальным размером лота.

//-- Get Minimum Risk for a Trade using Minimum Lot-size
   bool                GetMinimumRisk()
     {
      return OrderCalcProfit(ORDERTYPE,Symbol(),LotsMin(),OpenPrice,ClosePrice,MinimumAmount);
     }

Функция GetRisk получит требуемый размер лота для указанной суммы риска в переменном аргументе Amount. При установке MinimumAmount(минимальная сумма риска для конкретной сделки) Amount делится на MinimumAmount, чтобы получить частное, кратное минимальному размеру лота для получения требуемого размера лота для Amount

//-- Retrieve Risk amount based on Risk inputs
   double            GetRisk(double Amount)
     {
      if(!GetMinimumRisk()||Amount==0)
         return 0.0;
      return ((Amount/MinimumAmount)*LotsMin());
     }

В функции RiskProfile1 рассчитываем и возвращаем размер лота для вариантов PERCENTAGE OF BALANCE или PERCENTAGE OF FREE-MARGIN.

//-- calculations for Risk options (PERCENTAGE OF BALANCE and PERCENTAGE OF FREE-MARGIN)
double CRiskManagement::RiskProfile1(const RiskMedium R_Medium)
  {
   SetMedium(R_Medium);
   RiskAmount = Medium*(Risk_Profile_1/100);
   return GetRisk(RiskAmount);
  }

В функции RiskProfile2 рассчитываем и возвращаем размер лота для вариантов AMOUNT PER BALANCE или AMOUNT PER FREE-MARGIN.

//-- calculations for Risk options (AMOUNT PER BALANCE and AMOUNT PER FREE-MARGIN)
double CRiskManagement::RiskProfile2(const RiskMedium R_Medium)
  {
   SetMedium(R_Medium);
   double risk = (Risk_Profile_2.RiskAmountBoF/Risk_Profile_2.RiskAmount);
   risk = (risk<1)?1:risk;

   if(Medium<=0)
      return 0.0;

   RiskAmount = Medium/risk;
   return GetRisk(RiskAmount);
  }

В функции RiskProfile3 рассчитываем и возвращаем размер лота для вариантов LOTSIZE PER BALANCE или LOTSIZE PER FREE-MARGIN.

//-- calculations for Risk options (LOTSIZE PER BALANCE and LOTSIZE PER FREE-MARGIN)
double CRiskManagement::RiskProfile3(const RiskMedium R_Medium)
  {
   SetMedium(R_Medium);
   return (Medium>0)?((Medium/Risk_Profile_3.RiskLotBoF)*Risk_Profile_3.RiskLot):0.0;
  }

В функции ValidateLotsize редактируем переменную Lotsize, переданную по ссылке. 

В первом операторе Switch RiskFloorOption:

  • В случае RiskFloorMin: Проверяем, выходит ли переменная Lotsize за пределы установленных лимитов и устанавливаем ей значение минимального размера лота текущего символа. Мы проверяем нижний лимит, то есть, меньше ли значение переменной минимального размера лота. Верхний предел - превышение переменной Lotsize максимально возможного риска.
  • В случае RiskFloorMax: Сначала проверяем, превышает ли переменная Lotsize максимально возможный риск. Если да, проверяем, превышает ли максимально допустимый минимальный риск (maximum desired minimum risk) минимальный размер лота. Если да, присваиваем Lotsize с максимально допустимым минимальным риском сделке. Если нет, присваиваем минимальный размер лота. Если Lotsize изначально меньше максимально возможного риска и меньше минимального размера лота, присваиваем минимальный размер лота. 
Во втором операторе Switch RiskCeilOption для каждого случая мы проверяем, превышает ли Lotsize любой максимальный риск, основанный на размере лота, и устанавливаем значение Lotsize лимиту размера лота, если он достигнут.

void CRiskManagement::ValidateLotsize(double &Lotsize)
  {
   switch(RiskFloorOption)
     {
      case RiskFloorMin://MINIMUM LOTSIZE for Risk floor options
         //-- Check if lot-size is not less than Minimum lot or more than maximum allowable risk
         if(Lotsize<LotsMin()||Lotsize>MaxRisk(max_percent))
           {
            Lotsize=LotsMin();
           }
         break;
      case RiskFloorMax://MAX-RISK for Risk floor options
         //-- Check if lot-size is more the maximum allowable risk
         if(Lotsize>MaxRisk(max_percent))
           {
            Lotsize=(MaxRisk(RiskFloorPercentage)>LotsMin())?MaxRisk(RiskFloorPercentage):LotsMin();
           }
         else
            if(Lotsize<LotsMin())//Check if lot-size is less than Minimum lot
              {
               Lotsize=LotsMin();
              }
         break;
      case RiskFloorNone://NONE for Risk floor options
         //Check if lot-size is less than Minimum lot
         if(Lotsize<LotsMin())
           {
            Lotsize=0.0;
           }
         break;
      default:
         Lotsize=0.0;
         break;
     }

   switch(RiskCeilOption)
     {
      case  RiskCeilMax://MAX LOTSIZE for Risk ceiling options
         //Check if lot-size is more than Maximum lot
         if(Lotsize>LotsMax())
            Lotsize=LotsMax();
         break;
      case RiskCeilMax2://MAX LOTSIZE(x2) for Risk ceiling options
         //Check if lot-size is more than Maximum lot times two
         if(Lotsize>(LotsMax()*2))
            Lotsize=(LotsMax()*2);
         break;
      case RiskCeilMax3://MAX LOTSIZE(x3) for Risk ceiling options
         //Check if lot-size is more than Maximum lot times three
         if(Lotsize>(LotsMax()*3))
            Lotsize=(LotsMax()*3);
         break;
      case RiskCeilMax4://MAX LOTSIZE(x4) for Risk ceiling options
         //Check if lot-size is more than Maximum lot times four
         if(Lotsize>(LotsMax()*4))
            Lotsize=(LotsMax()*4);
         break;
      case RiskCeilMax5://MAX LOTSIZE(x5) for Risk ceiling options
         //Check if lot-size is more than Maximum lot times five
         if(Lotsize>(LotsMax()*5))
            Lotsize=(LotsMax()*5);
         break;
      default:
         break;
     }
  }

В функции NormalizeLotsize мы проверяем, находится ли размер лота в пределах объема символа и соответствует ли размер лота шагу объема.

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

  • RiskFloorMin: Присваиваем переменной Lotsize минимальный размер лота.
  • RiskFloorMax: Если RiskFloorPercentage оставшегося размера лота больше минимального размера лота, присваиваем это значение переменной Lotsize. Если значение RiskFloorPercentage оставшегося размера лота меньше или равно минимальному размеру лота, присваиваем переменной Lotsize минимальное значение лота. 

void CRiskManagement::NormalizeLotsize(double &Lotsize)
  {
   if(Lotsize<=0.0)
      return;

//-- Check if the is a Volume limit for the current Symbol
   if(LotsLimit()>0.0)
     {
      if((Lots+PositionsVolume()+OrdersVolume())>LotsLimit())
        {
         //-- calculation of available lotsize remaining
         double remaining_avail_lots = (LotsLimit()-(PositionsVolume()+OrdersVolume()));
         if(remaining_avail_lots>=LotsMin())
           {
            if(RiskFloorOption==RiskFloorMin)//Check if Risk floor option is MINIMUM LOTSIZE
              {
               Print("Warning: Volume Limit Reached, minimum Lotsize selected.");
               Lotsize = LotsMin();
              }
            else
               if(RiskFloorOption==RiskFloorMax)//Check if Risk floor option is MAX-RISK
                 {
                  Print("Warning: Volume Limit Reached, Lotsize Reduced.");
                  Lotsize = ((remaining_avail_lots*(RiskFloorPercentage/100))>LotsMin())?
                            (remaining_avail_lots*(RiskFloorPercentage/100)):LotsMin();
                 }
           }
         else
           {
            Print("Volume Limit Reached!");
            Lotsize=0.0;
            return;
           }
        }
     }

//Check if there is a valid Volume Step for the current Symbol
   if(LotsStep()>0.0)
      Lotsize=LotsStep()*MathFloor(Lotsize/LotsStep());
  }


Общий графический класс (Common Graphics Class)

Общий графический класс будет отображать общие свойства текущего символа и некоторые параметры риска, установленные трейдером.

CommonGraphics имеет многоуровневое наследование от классов:

  • CObjectProperties
  • CChartProperties
  • CSymbolProperties

CommonGraphics имеет включение из класса CRiskManagement.

CommonGraphics располагает иерархическим наследованием от классов:

  • CSymbolProperties
  • CSymbolInfo

//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+
#include "ObjectProperties.mqh"
#include "RiskManagement.mqh"
//+------------------------------------------------------------------+
//|CommonGraphics class                                              |
//+------------------------------------------------------------------+
class CCommonGraphics:CObjectProperties
  {
private:
   CRiskManagement   CRisk;//Risk management class object

public:
                     CCommonGraphics(void);//class constructor
                    ~CCommonGraphics(void) {}//class destructor
   void              GraphicsRefresh();//will create the chart objects
  };

//+------------------------------------------------------------------+
//|Constructor                                                       |
//+------------------------------------------------------------------+
CCommonGraphics::CCommonGraphics(void)
  {
   GraphicsRefresh();//calling GraphicsRefresh function
  }

//+------------------------------------------------------------------+
//|Specify Chart Objects                                             |
//+------------------------------------------------------------------+
void CCommonGraphics::GraphicsRefresh()
  {
//-- Will create the rectangle object
   Square(0,"Symbol Properties",2,20,330,183,ANCHOR_LEFT_UPPER);
//-- Will create the text object for the Symbol's name
   TextObj(0,"Symbol Name",Symbol(),5,23);
//-- Will create the text object for the contract size
   TextObj(0,"Symbol Contract Size","Contract Size: "+string(ContractSize()),5,40,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the Symbol's Minimum lotsize
   TextObj(0,"Symbol MinLot","Minimum Lot: "+string(LotsMin()),5,60,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the Symbol's Maximum lotsize
   TextObj(0,"Symbol MaxLot","Max Lot: "+string(LotsMax()),5,80,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the Symbol's Volume Step
   TextObj(0,"Symbol Volume Step","Volume Step: "+string(LotsStep()),5,100,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the Symbol's Volume Limit
   TextObj(0,"Symbol Volume Limit","Volume Limit: "+string(LotsLimit()),5,120,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the trader's Risk Option
   TextObj(0,"Risk Option","Risk Option: "+CRisk.GetRiskOption(),5,140,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the trader's Risk Floor
   TextObj(0,"Risk Floor","Risk Floor: "+CRisk.GetRiskFloor(),5,160,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the trader's Risk Ceiling
   TextObj(0,"Risk Ceil","Risk Ceiling: "+CRisk.GetRiskCeil(),5,180,CORNER_LEFT_UPPER,9);
  }
//+------------------------------------------------------------------+

В функции GraphicsRefresh мы задаем свойства графических объектов.

Видимые объекты графика

Объекты графика

void CCommonGraphics::GraphicsRefresh()
  {
//-- Will create the rectangle object
   Square(0,"Symbol Properties",2,20,330,183,ANCHOR_LEFT_UPPER);
//-- Will create the text object for the Symbol's name
   TextObj(0,"Symbol Name",Symbol(),5,23);
//-- Will create the text object for the contract size
   TextObj(0,"Symbol Contract Size","Contract Size: "+string(ContractSize()),5,40,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the Symbol's Minimum lotsize
   TextObj(0,"Symbol MinLot","Minimum Lot: "+string(LotsMin()),5,60,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the Symbol's Maximum lotsize
   TextObj(0,"Symbol MaxLot","Max Lot: "+string(LotsMax()),5,80,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the Symbol's Volume Step
   TextObj(0,"Symbol Volume Step","Volume Step: "+string(LotsStep()),5,100,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the Symbol's Volume Limit
   TextObj(0,"Symbol Volume Limit","Volume Limit: "+string(LotsLimit()),5,120,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the trader's Risk Option
   TextObj(0,"Risk Option","Risk Option: "+CRisk.GetRiskOption(),5,140,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the trader's Risk Floor
   TextObj(0,"Risk Floor","Risk Floor: "+CRisk.GetRiskFloor(),5,160,CORNER_LEFT_UPPER,9);
//-- Will create the text object for the trader's Risk Ceiling
   TextObj(0,"Risk Ceil","Risk Ceiling: "+CRisk.GetRiskCeil(),5,180,CORNER_LEFT_UPPER,9);
  }


Советник

И снова в этой статье мы не будем открывать никаких сделок.
//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+
//--- width and height of the canvas (used for drawing)
#define IMG_WIDTH  200
#define IMG_HEIGHT 100
//--- enable to set color format
ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_XRGB_NOALPHA;
//--- drawing array (buffer)
uint ExtImg[IMG_WIDTH*IMG_HEIGHT];

#include "News.mqh"
CNews NewsObject;//Class CNews Object 'NewsObject'
#include "TimeManagement.mqh"
CTimeManagement CTM;//Class CTimeManagement Object 'CTM'
#include "WorkingWithFolders.mqh"
CFolders Folder();//Calling Class's Constructor
#include "ChartProperties.mqh"
CChartProperties CChart;//Class CChartProperties Object 'CChart'
#include "RiskManagement.mqh"
CRiskManagement CRisk;//Class CRiskManagement Object 'CRisk'
#include "CommonGraphics.mqh"
CCommonGraphics CGraphics();//Calling Class's Constructor

enum iSeparator
  {
   Delimiter//__________________________
  };

sinput group "+--------| RISK MANAGEMENT |--------+";
input RiskOptions RISK_Type=MINIMUM_LOT;//SELECT RISK OPTION
input RiskFloor RISK_Mini=RiskFloorMin;//RISK FLOOR
input double RISK_Mini_Percent=75;//MAX-RISK [100<-->0.01]%
input RiskCeil  RISK_Maxi=RiskCeilMax;//RISK CEILING
sinput iSeparator iRisk_1=Delimiter;//__________________________
sinput iSeparator iRisk_1L=Delimiter;//PERCENTAGE OF [BALANCE | FREE-MARGIN]
input double Risk_1_PERCENTAGE=3;//[100<-->0.01]%
sinput iSeparator iRisk_2=Delimiter;//__________________________
sinput iSeparator iRisk_2L=Delimiter;//AMOUNT PER [BALANCE | FREE-MARGIN]
input double Risk_2_VALUE=1000;//[BALANCE | FREE-MARGIN]
input double Risk_2_AMOUNT=10;//EACH AMOUNT
sinput iSeparator iRisk_3=Delimiter;//__________________________
sinput iSeparator iRisk_3L=Delimiter;//LOTSIZE PER [BALANCE | FREE-MARGIN]
input double Risk_3_VALUE=1000;//[BALANCE | FREE-MARGIN]
input double Risk_3_LOTSIZE=0.1;//EACH LOTS(VOLUME)
sinput iSeparator iRisk_4=Delimiter;//__________________________
sinput iSeparator iRisk_4L=Delimiter;//CUSTOM LOTSIZE
input double Risk_4_LOTSIZE=0.01;//LOTS(VOLUME)
sinput iSeparator iRisk_5=Delimiter;//__________________________
sinput iSeparator iRisk_5L=Delimiter;//PERCENTAGE OF MAX-RISK
input double Risk_5_PERCENTAGE=1;//[100<-->0.01]%

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//Initializing CRiskManagement variable for Risk options
   RiskProfileOption = RISK_Type;
//Initializing CRiskManagement variable for Risk floor
   RiskFloorOption = RISK_Mini;
//Initializing CRiskManagement variable for RiskFloorMax
   RiskFloorPercentage = (RISK_Mini_Percent>100)?100:
                         (RISK_Mini_Percent<0.01)?0.01:RISK_Mini_Percent;//Percentage cannot be more than 100% or less than 0.01%
//Initializing CRiskManagement variable for Risk ceiling
   RiskCeilOption = RISK_Maxi;
//Initializing CRiskManagement variable for Risk options (PERCENTAGE OF BALANCE and PERCENTAGE OF FREE-MARGIN)
   Risk_Profile_1 = (Risk_1_PERCENTAGE>100)?100:
                    (Risk_1_PERCENTAGE<0.01)?0.01:Risk_1_PERCENTAGE;//Percentage cannot be more than 100% or less than 0.01%
//Initializing CRiskManagement variables for Risk options (AMOUNT PER BALANCE and AMOUNT PER FREE-MARGIN)
   Risk_Profile_2.RiskAmountBoF = Risk_2_VALUE;
   Risk_Profile_2.RiskAmount = Risk_2_AMOUNT;
//Initializing CRiskManagement variables for Risk options (LOTSIZE PER BALANCE and LOTSIZE PER FREE-MARGIN)
   Risk_Profile_3.RiskLotBoF = Risk_3_VALUE;
   Risk_Profile_3.RiskLot = Risk_3_LOTSIZE;
//Initializing CRiskManagement variable for Risk option (CUSTOM LOTSIZE)
   Risk_Profile_4 = Risk_4_LOTSIZE;
//Initializing CRiskManagement variable for Risk option (PERCENTAGE OF MAX-RISK)
   Risk_Profile_5 = (Risk_5_PERCENTAGE>100)?100:
                    (Risk_5_PERCENTAGE<0.01)?0.01:Risk_5_PERCENTAGE;//Percentage cannot be more than 100% or less than 0.01%

   CChart.ChartRefresh();//Load chart configurations
   CGraphics.GraphicsRefresh();//-- Create/Re-create chart objects

   if(!MQLInfoInteger(MQL_TESTER))//Checks whether the program is in the strategy tester
     {
      //--- create OBJ_BITMAP_LABEL object for drawing
      ObjectCreate(0,"STATUS",OBJ_BITMAP_LABEL,0,0,0);
      ObjectSetInteger(0,"STATUS",OBJPROP_XDISTANCE,5);
      ObjectSetInteger(0,"STATUS",OBJPROP_YDISTANCE,22);
      //--- specify the name of the graphical resource
      ObjectSetString(0,"STATUS",OBJPROP_BMPFILE,"::PROGRESS");
      uint   w,h;          // variables for receiving text string sizes
      uint    x,y;          // variables for calculation of the current coordinates of text string anchor points

      /*
      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
        {
         //--- clear the drawing buffer array
         ArrayFill(ExtImg,0,IMG_WIDTH*IMG_HEIGHT,0);

         if(!TerminalInfoInteger(TERMINAL_CONNECTED))
           {
            //-- integer dots used as a loading animation
            static int dots=0;
            //--- set the font
            TextSetFont("Arial",-150,FW_EXTRABOLD,0);
            TextGetSize("Waiting",w,h);//get text width and height values
            //--- calculate the coordinates of the 'Waiting' text
            x=10;//horizontal alignment
            y=IMG_HEIGHT/2-(h/2);//alignment for the text to be centered vertically
            //--- output the 'Waiting' text to ExtImg[] buffer
            TextOut("Waiting",x,y,TA_LEFT|TA_TOP,ExtImg,IMG_WIDTH,IMG_HEIGHT,ColorToARGB(CChart.SymbolBackground()),clr_format);
            //--- calculate the coordinates for the dots after the 'Waiting' text
            x=w+13;//horizontal alignment
            y=IMG_HEIGHT/2-(h/2);//alignment for the text to be centered vertically
            TextSetFont("Arial",-160,FW_EXTRABOLD,0);
            //--- output of dots to ExtImg[] buffer
            TextOut(StringSubstr("...",0,dots),x,y,TA_LEFT|TA_TOP,ExtImg,IMG_WIDTH,IMG_HEIGHT,ColorToARGB(CChart.SymbolBackground()),clr_format);
            //--- update the graphical resource
            ResourceCreate("::PROGRESS",ExtImg,IMG_WIDTH,IMG_HEIGHT,0,0,IMG_WIDTH,clr_format);
            //--- force chart update
            ChartRedraw();
            dots=(dots==3)?0:dots+1;
            //-- Notify user that program is waiting for connection
            Print("Waiting for connection...");
            Sleep(500);
            continue;
           }
         else
           {
            //--- set the font
            TextSetFont("Arial",-120,FW_EXTRABOLD,0);
            TextGetSize("Getting Ready",w,h);//get text width and height values
            x=20;//horizontal alignment
            y=IMG_HEIGHT/2-(h/2);//alignment for the text to be centered vertically
            //--- output the text 'Getting Ready...' to ExtImg[] buffer
            TextOut("Getting Ready...",x,y,TA_LEFT|TA_TOP,ExtImg,IMG_WIDTH,IMG_HEIGHT,ColorToARGB(CChart.SymbolBackground()),clr_format);
            //--- update the graphical resource
            ResourceCreate("::PROGRESS",ExtImg,IMG_WIDTH,IMG_HEIGHT,0,0,IMG_WIDTH,clr_format);
            //--- force chart update
            ChartRedraw();
            //-- Notify user that connection is successful
            Print("Connection Successful!");
            NewsObject.CreateEconomicDatabase();//calling the database create function
            done=true;
           }
        }
      while(!done&&!IsStopped());
      //-- Delete chart object
      ObjectDelete(0,"STATUS");
      //-- force chart to update
      ChartRedraw();
     }
   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);
        }
      else
        {
         //Checks whether the lastest database date includes the time and date being tested
         datetime latestdate = CTM.TimePlusOffset(NewsObject.GetLatestNewsDate(),CTM.DaysS());//Day after the lastest recorded time in the database
         if(latestdate<TimeCurrent())
           {
            Print("Necessary Files outdated!");
            Print("To Update Files: Run Program outside of the Strategy Tester");
           }
         Print("Database Dates End at: ",latestdate);
         PrintFormat("Dates after %s will not be available for backtest",TimeToString(latestdate));
        }
     }
//-- the volume calculations and the risk type set by the trader
   Print("Lots: ",CRisk.Volume()," || Risk type: ",CRisk.GetRiskOption());
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

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

Файлы проекта


Советник


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

График US30

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

Теперь добавим советник.

Добавляем советник

Сначала мы можем настроить входные переменные управления рисками. Для начала я оставлю значения по умолчанию.

Параметры советника

Если вы впервые запускаете NewsTrading 2.00 и ранее не запускали NewsTrading 1.00 у своего брокера, а база данных календаря отсутствует в общей папке, ваш график будет выглядеть следующим образом. Цвета графиков могут отличаться у разных брокеров.

График советника 1

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

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


Если терминал не может установить соединение.


Если во время первого запуска советника NewsTrading 2.00 база данных календаря была создана из NewsTrading 1.00, NewsTrading 2.00 удалит все таблицы из NewsTrading 1.00.

Советник запущен, база данных календаря взята из предыдущей версии


Заключение

В этой статье мы рассмотрели, как работает наследование, и привели пример для наглядности. Мы создали новый класс перехода на летнее время, который будет выступать в качестве родительского для различных классов графиков DST. Мы создали класс свойств Symbol для извлечения свойств символа из всех классов, которые его наследуют. Мы также создали класс свойств Chart для настройки графика. Мы рассмотрели различные методологии SQLite и простой способ повышения общей эффективности базы данных. Мы создали класс свойств Object для создания и удаления объектов графика, затем мы создали класс управления рисками для обслуживания различных вариантов управления рисками. Наконец, мы создали последний класс Common Graphics для отображения свойств Symbol на графике вместе с вариантами риска для трейдера. В следующей статье, "Упрощаем торговлю на новостях (Часть 3): Совершаем сделки", мы наконец начнем открывать сделки.


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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
Kabelo Frans Mampa
Kabelo Frans Mampa | 11 июл. 2024 в 19:13
Jermaine Wedderburn #:

Я пытался протестировать его, но не знаю, куда поместить все файлы, поэтому newstrading не компилируется.


Шаг 1: Переместите папку NewsTrading в папку Experts.

Шаг 1


Шаг 2: Откройте файл проекта NewsTrading.

Шаг 2


Шаг 3: Нажмите на файл NewsTrading mq5 и откройте его.

Шаг 3


Шаг 4: Скомпилируйте приложение.

Шаг 4

Christian Edward Bannard
Christian Edward Bannard | 25 окт. 2024 в 00:50

При переключении Stop Loss на 0 происходит вот что:


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

Kabelo Frans Mampa
Kabelo Frans Mampa | 25 окт. 2024 в 06:15
Christian Edward Bannard #:

При переключении Stop Loss на 0 происходит вот что:


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

Привет, Кристиан, спасибо за добрые слова. Эта проблема была замечена и решена в моих последующих статьях, которые все еще находятся в процессе публикации. Что касается комиссии, вы предлагаете эксперту корректировать комиссионные при расчете риска?
Christian Edward Bannard
Christian Edward Bannard | 28 окт. 2024 в 06:01
Kabelo Frans Mampa #:
Привет, Кристиан, спасибо за добрые слова. Этот вопрос был замечен и решен в моих последующих статьях, которые все еще находятся в процессе публикации. Что касается комиссии, вы предлагаете, чтобы эксперт учитывал комиссионные при расчете риска?

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

Если сделка находится в убытке или открыта в течение длительного времени, то цена, просто вернувшись к точке входа, скорее всего, не покроет расходы.

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

Kabelo Frans Mampa
Kabelo Frans Mampa | 30 окт. 2024 в 04:54
Christian Edward Bannard # :

I believe that accounting for fees is an important aspect of profitability and risk calculation, otherwise traders using automated systems may close trades thinking they are in profit, when in fact profit minus expenses = net profit, which reflects the real world.

If a trade is in the red or has been open for a long time, the price simply returning to the entry point will most likely not cover the costs.

Of course, a demo account is fine for testing purposes, but most brokers charge some commission, so breakeven will never be back to your entry point, it will always be a few pips above/below your original entry point depending on whether the broker charges entry and exit fees or charges a percentage. Swaps must also be factored into any breakeven point.

Thanks for the feedback, it is appreciated!

Методы оптимизации библиотеки ALGLIB (Часть I) Методы оптимизации библиотеки ALGLIB (Часть I)
В статье познакомимся с методами оптимизации библиотеки ALGLIB для MQL5. Статья включает простые и наглядные примеры применения ALGLIB для решения задач оптимизации, что сделает процесс освоения методов максимально доступным. Мы подробно рассмотрим подключение таких алгоритмов, как BLEIC, L-BFGS и NS, и на их основе решим простую тестовую задачу.
Разрабатываем мультивалютный советник (Часть 19): Создаём этапы, реализованные на Python Разрабатываем мультивалютный советник (Часть 19): Создаём этапы, реализованные на Python
Пока что мы рассматривали автоматизацию запуска последовательных процедур оптимизации советников исключительно в штатном тестере стратегий. Но что делать, если между такими запусками нам хотелось бы выполнить некоторую обработку уже полученных данных, используя другие средства? Попробуем добавить возможность создания новых этапов оптимизации, выполняемых программами, написанными на Python.
Разработка системы репликации (Часть 54): Появление первого модуля Разработка системы репликации (Часть 54): Появление первого модуля
В этой статье мы рассмотрим, как собрать первый из действительно функциональных модулей для использования в системе репликации/моделирования, который также будет иметь общее назначение, чтобы служить и другим целям. Мы говорим о модуле индикатора мыши.
Разработка системы репликации (Часть 53): Всё усложняется (V) Разработка системы репликации (Часть 53): Всё усложняется (V)
В этой статье мы рассмотрим важную тему, которую мало кто понимает: Пользовательские события. Опасности. Преимущества и ошибки, вызванные такими элементами. Данная тема является ключевой для тех, кто хочет стать профессиональным программистом на MQL5 или любом другом языке. Поэтому мы сосредоточимся на MQL5 и MetaTrader 5.