Перейти к содержанию

(MQL) 2.5 Время. Класс «ETime».

время

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

Класс «ETime». Держим время под контролем

Потому мы создаём отдельный класс «ETime», который будет заниматься обработкой времени. Данный класс прописывается в одноимённом файле, который располагается в папке «Date» нашего проекта.

ETime

В начале подключаем файл «Prepare.mqh».

include "..\..\Helpers\Prepare.mqh"

Теперь создаём класс «ETime».

class ETime
{

}
// Сразу после создания класса напишем следующую строку 
ETime time;

Такая запись позволяет создать объект «ETime» как переменную и использовать её во всех последующих классах и функциях. То есть выводит «time» на глобальный уровень.

Небольшое лирическое отступление

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

Когда я только начинал знакомство с языком MQL5, то я не следил за использованием оперативной памяти. Почему? Потому, что у меня не было опыта работы с языком C++, а язык MQL4, на тот момент, представлял собой набор функций.

Те, кто работает с языком C++ прекрасно знает его возможности. Но у этого языка есть один недостаток — отсутствие контроля за использованием оперативной памяти. Программист обязан сам следить за тем как используется оперативная память компьютера и освобождать её по мере необходимости. Язык MQL5 является C-подобным языком. Он многое позаимствовал у языка C++. В том числе и его главный недостаток.

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

Если он работал в обычном режиме, то ничего подобного не происходило. Но как только включался тестер стратегий начинались проблемы. В этот момент расход оперативной памяти достигал огромных значений (до 2 гигабайт). А всё из-за того, что в этот момент создавались тысячи объектов. Чем больше был период тестирования, тем больше создавалось объектов.

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

Возвращаясь к классу «ETime» стоит отметить, что объявление

ETime time;

избавляет нас от необходимости удалять переменную «time» в ручную. Это делается автоматически после деактивации программы(торгового робота / индикатора / скрипта).

Продолжаем разбор класса «ETime»

Мы создали пустой класс «ETime». Теперь откроем файл «Structure.mqh» и пропишем в нём две структуры:

// Смещение времени, с которым мы работаем на данный момент относительно времени GMT
 
struct TimeOffset
{
 // смещение в секундах
 ulong inSeconds;

 // смещение в минутах
 ulong inMinutes;

 // смещение в часах
 ulong inHours;
};

Чтобы мы могли работать с конкретным временем, необходимо знать сколько секунд прошло от даты 1970:01:01 до интересующего нас момента времени.

struct DataTime
{
 // время в секундах, прошедшее с 00:00 1 января 1970 года
 datetime time; 
 
 // количество секунд, прошедших от 1970:01:01. Фактически это дублирование "time". Иногда могут возникнуть ситуации, когда нужно будет сохранить новое значение времени, но при этом продолжать работу с результатами обработки предыдущего значения. Как только эти результаты потеряют свою актуальность, можно будет обработать новое значение времени
 ulong seconds;
 
 // количество минут, прошедших от 1970:01:01
 ulong minutes; 

 // количество часов, прошедших от 1970:01:01
 ulong hours; 

 // количество дней, прошедших от 1970:01:01
 ulong days; 

 // количество недель, прошедших от 1970:01:01
 ulong weeks; 
 
 // количество месяцев, прошедших от 1970:01:01
 ulong months; 

 // количество лет, прошедших от 1970:01:01
 ulong years; 

 // время начала текущей минуты (указано в секундах)
 ulong lastMinute;

 // время начала текущего часа (указано в секундах)
 ulong lastHour; 

 // время начала текущего дня (указано в секундах)
 ulong lastDay; 

 // время начала текущей недели (указано в секундах)
 ulong lastWeek; 

 // время начала текущего месяца (указано в секундах)
 ulong lastMonth; 

 // время начала текущего года (указано в секундах)
 ulong lastYear; 

 // Стандартная структура даты
 MqlDateTime mqlDateTime;

 // смещение времени относительно GMT
 TimeOffset offset; 
};

Возвращаемся к классу «ETime». В его теле объявим переменную «eTimeCustom» с типом данных «DataTime» и спецификатором доступа «private»:

private:
  
 DataTime eTimeCustom;

Теперь приступим к описанию методов данного класса:

public:
     // конструктор класса ETime. Будет описан ниже
     ETime();
     // деструктор класса ETime
    ~ETime();

// Получаем (в секундах) последнее сохранённое  время 
datetime Time() const{return (eTimeCustom.time);}

// Сохраняем (в секундах) новое значение времени
void Time(datetime set){eTimeCustom.time = set;}

// Получаем количество секунд, прошедших от 1970:01:01
ulong Seconds()const{return (eTimeCustom.seconds);}

// Получаем количество минут, прошедших от 1970:01:01
ulong Minutes()const{return (eTimeCustom.minutes);}

// Получаем количество часов, прошедших от 1970:01:01
ulong Hours()const{return (eTimeCustom.hours);}

// Получаем количество дней, прошедших от 1970:01:01
ulong Days()const{return (eTimeCustom.days);}

// Получаем количество недель, прошедших от 1970:01:01
ulong Weeks()const{return (eTimeCustom.weeks);}

// Получаем количество месяцев, прошедших от 1970:01:01
ulong Months()const{return (eTimeCustom.months);}

// Получаем количество лет, прошедших от 1970:01:01
ulong Years()const{return (eTimeCustom.years);}

// Получаем время начала текущей минуты (указано в секундах)
ulong MinuteLast()const{return (eTimeCustom.lastMinute);}

// Получаем время начала текущего часа (указано в секундах)
ulong HourLast()const{return (eTimeCustom.lastHour);}

// Получаем время начала текущего дня (указано в секундах)
ulong DayLast()const{return (eTimeCustom.lastDay);}

// Получаем время начала текущей недели (указано в секундах)
ulong WeekLast()const{return (eTimeCustom.lastWeek);}

// Получаем время начала текущего месяца (указано в секундах)
ulong MonthLast()const{return (eTimeCustom.lastMonth);}

// Получаем время начала текущего года (указано в секундах)
ulong YearLast()const{return (eTimeCustom.lastYear);}

// Получаем значение текущего года (1983, 2022 и так далее)
int Year()const{return eTimeCustom.mqlDateTime.year;}

// Получаем порядковый номер текущего месяца (5, 11, 3 и так далее)
int Month()const{return eTimeCustom.mqlDateTime.mon;}

// Получаем порядковый номер дня месяца (1, 5, 30, 21 и так далее)
int DayOfMonth()const{return eTimeCustom.mqlDateTime.day;}

// Получаем порядковый номер дня недели (0-воскресенье, 1-понедельник, … ,6-суббота)
int DayOfWeek()const{return eTimeCustom.mqlDateTime.day_of_week;}

// Получаем порядковый номер дня в году (1 января имеет номер 0)
int DayOfYear()const{return eTimeCustom.mqlDateTime.day_of_year;}

// Получаем значение часа (1, 23, 16 и так далее)
int Hour()const{return eTimeCustom.mqlDateTime.hour;}

// // Получаем значение минуты (1, 32, 55 и так далее)
int MinutesOfHour()const{return eTimeCustom.mqlDateTime.min;}

// Получаем значение секунды (1, 11, 44 и так далее)
int SecondsOfMinute()const{return eTimeCustom.mqlDateTime.sec;}


private:
// Заметьте методы Init() и Calculate() 
// Сохраняем новое значение времени (в секундах)
bool Init(datetime tm)
{
 if(tm>0)Time(tm);
 // если время сохранилось, то возвращается "true"
 if(eTimeCustom.time==tm)return(true); 
 return(false);
}

 // Этот метод получает значение времени (в секундах), затем проводит ряд расчётов. Полное его описание будет дано ниже
 bool Calculate(datetime tm=0);


public:
// Данный метод рассчитывает смещение времени относительно GMT 
void Offset();

// Данный метод запускает метод "Calculate()" и метод "Offset()"
bool Prepare(datetime tm); 

// Это метод возвращает структуру типа "DataTime" со всеми значениями.  
DataTime StructCustom(){return eTimeCustom;}

// Строковое представление рассчитанных значений
string TimeStructToString(string modificator="");

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

ETime::ETime()
{
 // Так как все переменные числовые, то их инициализация довольно проста  
 eTimeCustom.seconds = 0;
 eTimeCustom.minutes = 0;
 eTimeCustom.hours = 0; 
 eTimeCustom.days = 0;
 eTimeCustom.weeks = 0;
 eTimeCustom.months = 0;
 eTimeCustom.years = 0;
 eTimeCustom.mqlDateTime.sec = 0;
 eTimeCustom.mqlDateTime.min = 0;
 eTimeCustom.mqlDateTime.hour = 0;
 eTimeCustom.mqlDateTime.day = 0;
 eTimeCustom.mqlDateTime.day_of_week = 0;
 eTimeCustom.mqlDateTime.day_of_year = 0;
 eTimeCustom.mqlDateTime.mon = 0;
 eTimeCustom.mqlDateTime.year = 0;
 eTimeCustom.offset.inSeconds = 0;
 eTimeCustom.offset.inMinutes = 0;
 eTimeCustom.offset.inHours = 0;
 eTimeCustom.lastDay = 0;
 eTimeCustom.lastHour = 0;
 eTimeCustom.lastMinute = 0;
 eTimeCustom.lastMonth = 0;
 eTimeCustom.lastWeek = 0;
 eTimeCustom.lastYear = 0;

}

Деструктор класса выглядит так:

ETime::~ETime()
{
}

Теперь рассмотрим метод «Calculate()«. Он принимает, в качестве аргумента, значение времени (в секундах)

bool ETime::Calculate(datetime tm)
{
//+---
if(!Init(tm))return false;
//+---
// Так как в методе Init() уже осуществляется проверка eTimeCustom.time,то далее можно смело работать с этой переменной  

// Рассчитываем количество секунд, прошедших от 1970:01:01
eTimeCustom.seconds = toULong(eTimeCustom.time);

// Рассчитываем количество минут, прошедших от 1970:01:01. Общее количество секунд делим на 60, так как в одной минуте 60 секунд
if(eTimeCustom.seconds!=0)eTimeCustom.minutes = toULong((eTimeCustom.seconds - eTimeCustom.seconds%60)/60);

// Рассчитываем количество часов, прошедших от 1970:01:01. Общее количество минут делим на 60, так как в одном часе 60 минут
if(eTimeCustom.minutes!=0)eTimeCustom.hours = toULong((eTimeCustom.minutes - eTimeCustom.minutes%60)/60);

// Рассчитываем количество дней, прошедших от 1970:01:01. Общее количество часов делим на 24, так как в одном дне 24 часа
if(eTimeCustom.hours!=0)eTimeCustom.days = toULong((eTimeCustom.hours - eTimeCustom.hours%24)/24);

// Рассчитываем количество недель, прошедших от 1970:01:01. Общее количество дней делим на 7, так как в одной неделе 7 дней
if(eTimeCustom.days!=0)eTimeCustom.weeks = toULong((eTimeCustom.days - eTimeCustom.days%7)/7);

// Рассчитываем количество месяцев, прошедших от 1970:01:01. Общее количество недель делим на 4.333, так как в одном месяце 4 полных недель и одна неполная
if(eTimeCustom.weeks!=0)eTimeCustom.months = toULong ((eTimeCustom.weeks - eTimeCustom.weeks%4)/4.3333);

// Рассчитываем количество лет, прошедших от 1970:01:01. Общее количество месяцев делим на 12, так как один год содержит из 12 месяцев
if(eTimeCustom.months!=0)eTimeCustom.years = toULong((eTimeCustom.months - eTimeCustom.months%12)/12);

// Ищем точку начала последней минуты анализируемого времени. Для этого делим количество секунд на 60, так как в одной минуте 60 секунд. Если результат получается без остатка, значит текущее время и есть точка отсчёта последней минуты. В противном случае мы отнимаем от анализируемого времени остаток от деления и получаем интересующий нас результат
if(eTimeCustom.seconds!=0)eTimeCustom.lastMinute = toDateTime(eTimeCustom.seconds - eTimeCustom.seconds%60);

// Ищем точку начала последнего часа анализируемого времени. В одном часе 3600 секунд
if(eTimeCustom.seconds!=0)eTimeCustom.lastHour = 
toDateTime(eTimeCustom.seconds - eTimeCustom.seconds%3600);

// Ищем точку начала последнего дня анализируемого времени.
if(eTimeCustom.seconds!=0)eTimeCustom.lastDay = 
toDateTime(eTimeCustom.seconds - eTimeCustom.seconds%(24*3600)); 

// Ищем точку начала последней недели анализируемого времени.
if(eTimeCustom.seconds!=0)eTimeCustom.lastWeek = toDateTime(eTimeCustom.seconds - eTimeCustom.seconds%(7*24*3600)); 

// Ищем точку начала последнего месяца анализируемого времени.
if(eTimeCustom.seconds!=0)eTimeCustom.lastMonth = toDateTime (eTimeCustom.seconds - eTimeCustom.seconds%(4*7*24*3600));

// Ищем точку начала последнего года анализируемого времени.
if(eTimeCustom.seconds!=0)eTimeCustom.lastYear = toDateTime(eTimeCustom.seconds - eTimeCustom.seconds%(12*4*7*24*3600));

// Переводим анализируемое время в переменную типа MqlDateTime.
TimeToStruct(eTimeCustom.time,eTimeCustom.mqlDateTime);

return true;
}

Следующий метод «Offset()» рассчитывает смещение анализируемого времени относительно GMT:

void ETime::Offset()
{
 if(eTimeCustom.time==0)return;
 
 // Время GMT сохраняется в секундах
 datetime gmt = TimeGMT();

 // Рассчитываем смещение в секундах
 if(gmt>0)eTimeCustom.offset.inSeconds = eTimeCustom.seconds - gmt;
 
// Рассчитываем смещение в минутах
 if(gmt>0)eTimeCustom.offset.inMinutes = eTimeCustom.minutes -   ulong((gmt-gmt%60)/60);

// Рассчитываем смещение в часах
 if(gmt>0)eTimeCustom.offset.inHours = eTimeCustom.hours - ulong((gmt-gmt%3600)/3600);

}

Метод «Prepare()» объединяет в себе оба предыдущих метода:

bool ETime::Prepare(datetime tm)
{
 // true - обработка прошла успешно, false - обработка невозможна
 if(!Calculate(tm))return false;
   Offset();
 return true;
}

Последний метод «TimeStructToString()» возвращает строковое представление рассчитанных переменных, с учётом значения модификатора.

string ETime::TimeStructToString(string modificator = "")
{
if(eTimeCustom.time==0)return "0";

if(modificator == "s")return IntegerToString(eTimeCustom.mqlDateTime.sec);
if(modificator == "m")return IntegerToString(eTimeCustom.mqlDateTime.min);
if(modificator == "h")return IntegerToString(eTimeCustom.mqlDateTime.hour);
if(modificator == "dw")return IntegerToString(eTimeCustom.mqlDateTime.day_of_week);
if(modificator == "dmn")return IntegerToString(eTimeCustom.mqlDateTime.day);
if(modificator == "dy")return IntegerToString( eTimeCustom.mqlDateTime.day_of_year);
if(modificator == "mn")return IntegerToString(eTimeCustom.mqlDateTime.mon);
if(modificator == "y")return IntegerToString(eTimeCustom.mqlDateTime.year);

// Использование модификатора типа "*..." возвращает строковое представление значений времени (в секундах), прошедшего с момента  1970:01:01
if(modificator == "*s")return IntegerToString(eTimeCustom.seconds);
if(modificator == "*m")return IntegerToString(eTimeCustom.minutes);
if(modificator == "*h")return IntegerToString(eTimeCustom.hours);
if(modificator == "*d")return IntegerToString(eTimeCustom.days);
if(modificator == "*w")return IntegerToString(eTimeCustom.weeks);
if(modificator == "*mn")return IntegerToString(eTimeCustom.months);
if(modificator == "*y")return IntegerToString(eTimeCustom.years);

// Использование модификатора типа ":..." возвращает строковое представление значений последних отрезков времени
if(modificator == ":m")return IntegerToString(eTimeCustom.lastMinute);
if(modificator == ":h")return IntegerToString(eTimeCustom.lastHour);
if(modificator == ":d")return IntegerToString(eTimeCustom.lastDay);
if(modificator == ":w")return IntegerToString(eTimeCustom.lastWeek);
if(modificator == ":mn")return IntegerToString(eTimeCustom.lastMonth);
if(modificator == ":y")return IntegerToString(eTimeCustom.lastYear);

// Использование модификатора типа "_..." возвращает строковое представление смещения времени относительно GMT 
if(modificator == "_s")return IntegerToString(eTimeCustom.offset.inSeconds); 
if(modificator == "_m")return IntegerToString(eTimeCustom.offset.inMinutes);
if(modificator == "_h")return IntegerToString(eTimeCustom.offset.inHours);

return "0";
}

На этом разработка класса «ETime» окончена. Так как он объявляется на глобальном уровне, то для его использования необходимо подключить файл «ETime.mqh» нашего проекта. Затем вызывается метод «Prepare()», которому передаётся интересующее вас время. Оно выражается в количестве секунд, прошедших от даты «1970:01:01».

Для примера я создал файл скрипта:

//+------------------------------------------------------------------+
//|                                                         ETime.mq5|
//|                                                                  |
//|                                                                  |
//+------------------------------------------------------------------+
property copyright "Copyright 2021"
property link ""
property version "1.00"

// Полный путь к файлу ETime.mqh
include "..\Shared Projects\Trade Framework\Classes\Date\ETime.mqh"

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

 // TimeLocal() - время компьютера (в секундах)
 // time - объект ETime, представленный в виде переменной. 
 // Так как этот объект был объявлен и проинициализирован в файле "ETime.mqh", то нам достаточно вызвать метод "Prepare()" 
 time.Prepare(TimeLocal());

  Print("День недели -> "+time.TimeStructToString("dw"));
  
  Print("Количество минут, прошедших от даты 1970:01:01 -> "+time.TimeStructToString("*m"));
  
  Print("Время начала последнего дня -> "+time.TimeStructToString(":d"));
  
  Print("Смещение времени относительно GMT (в часах) -> "+time.TimeStructToString("_h"));
}
//+------------------------------------------------------------------+

После завершения работы объект переменная «time» удалится автоматически.

Опубликовано в рубрикеОснову для торговых роботов (MQL)2. Хелперы