English 中文 Español Deutsch 日本語 Português
Спать или не спать?

Спать или не спать?

MetaTrader 4Примеры | 6 ноября 2008, 12:51
5 207 11
Sergey Gridnev
Sergey Gridnev

Введение

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

Sleep() или не Sleep()?

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

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

Рассмотрим реализацию десятисекундной паузы в программе на языке MQL4. Ниже представлен вариант с использованием функции Sleep().

if ( /* условие, при выполнение которого требуется выдержать паузу */ )
   Sleep(10000); // остановка на 10 секунд

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

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

Вариант №1 (с использованием цикла while).

int _time_waiting=0;
// ...
if ( ... ) // условие, при выполнение которого требуется выдержать паузу
   _time_waiting = TimeLocal() + 10; // окончание паузы через 10 секунд от текущего локального времени
while ( TimeLocal() < _time_waiting )
{ // программный блок, выполнение которого производится во время ожидания времени окончания паузы циклически
   // ...
}
// программный блок, выполнение которого произойдет не ранее времени окончания паузы
// ...

Вариант №2 (с использованием цикличности вызова функции start()).

static int _time_waiting=0;
// ...
if ( ... ) // условие, при выполнение которого требуется выдержать паузу
   _time_waiting = TimeLocal() + 10; // окончание паузы через 10 секунд от текущего локального времени
if ( TimeLocal() >= _time_waiting )
{ // программный блок, выполнение которого не произойдет ранее времени окончания паузы
   // ...
}
// программный блок, выполнение которого происходит на каждом тике и не связано с ожиданием времени окончания паузы
// ...

Основное отличие представленных вариантов заключается в том, что вариант №1 гарантирует исполнение отложенного на время паузы программного блока, а вариант №2 не гарантирует. Связано это с тем, что поток тиков может быть прерван по каким-либо причинам и не возобновлен. Кроме того, в варианте №2 переменная _time_waiting описана как static, это гарантирует сохранность ее значения между вызовами функции start(), что не требуется варианте №1.

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

1) под каждую группу условий использовать собственную переменную для хранения времени окончания соответствующей паузы;

2) описать _time_waiting в виде массива.

Мне представляется не лишенным смысла создание небольшой библиотеки с реализацией функций таймера. Такой "таймер" должен содержать достаточное количество переменных-счетчиков и иметь функции инициализации, добавления и удаления контролируемых выдержек. Кроме того, можно реализовать не только обратный отсчет (таймер ожидания), но и прямой (секундомер). Пример реализации такой библиотеки прикреплен к статье (файл с исходным текстом - "ExLib_Timer.mq4"), ниже представлен заголовочный файл с прототипами реализованных функций.

#import "ExLib_Timer.ex4"
//
// Примечание.
// Идентификатор счетчика <CounterID> может принимать любое значение кроме "0",
// т.к. значение "0" зарезервировано для обозначения незадействованных счетчиков
// в общем массиве.
//
void timer_Init();
// начальная инициализация
//
int timer_NumberTotal();
// запрос общего количества счетчиков таймера
// Возврат:
//    общее количество счетчиков
//
int timer_NumberUsed();
// запрос количества задействованных счетчиков
// Возврат:
//    количество задействованных счетчиков
//
int timer_NumberFree();
// запрос количества незадействованных (свободных) счетчиков
// Возврат:
//    количество свободных счетчиков
//
void timer_ResetAll();
// сброс (обнуление) всех счетчиков
//
bool timer_Reset(int CounterID);
// сброс (обнуление) счетчика по идентификатору
// Параметры:
//    <CounterID> - идентификатор счетчика
// Возврат:
//    true  - сбос произведен
//    false - счетчик с заданным идентификатором не обнаружен
//
void timer_DeleteAll();
// удаление всех счетчиков
//
bool timer_Delete(int CounterID);
// удаление счетчика по идентификатороу
// Параметры:
//    <CounterID> - идентификатор счетчика
// Возврат:
//    true  - удаление произведено
//    false - счетчик с заданным идентификатором не обнаружен
//
bool timer_StartWaitable(int CounterID, int timeslice);
// старт счетчика типа "таймер ожидания"
// Параметры:
//    <CounterID> - идентификатор счетчика (в случае отсутствия, счетчик добавляется)
//    <timeslice> - периода ожидания (секунд)
// Возврат:
//    true  - старт произведен
//    false - нет свободных счетчиков
//
bool timer_StartStopwatch(int CounterID);
// старт счетчика типа "секундомер"
// Параметры:
//    <CounterID> - идентификатор счетчика (в случае отсутствия, счетчик добавляется)
// Возврат:
//    true  - старт произведен
//    false - нет свободных счетчиков
//
int timer_GetCounter(int CounterID);
// запрос показаний счетчика с идентификатором <CounterID>
// Возврат:
//    для счетчика типа "секундомер" - количество секунд, прошедших от начала старта
//    для счетчика типа "таймер ожидания" - количество секунд, оставшихся до окончания периода ожидания
// Примечание:
//    в случае отсутствия счетчика с идентификатором <CounterID> возвращается -1
//
int timer_GetType(int CounterID);
// запрос типа счетчика с идентификатором <CounterID>
// Параметры:
//    <CounterID> - идентификатор счетчика
// Возврат:
//     1 - счетчик типа "секундомер"
//    -1 - счетчик типа "таймер ожидания"
//     0 - счетчик с заданным идентификатором не обнаружен
//
int timer_GetCounterIDbyPOS(int CounterPOS);
// запрос идентификатора счетчика по его позиции среди задействованных
// Параметры:
//    <CounterPOS>   - номер позиции счетчика среди задействованных
// Возврат:
//    идентификатор счетчика или "0", если задана несуществующая позиция
// Примечание:
//    <CounterPOS> может принимать значения от 0 до количества задействованных счетчиков,
//    возвращаемого функцией timer_NumberUsed(), в противном случае возвращается 0
//
#import

Различия между использованием вызова Sleep() и альтернативным вариантом реализации паузы наглядного демонстрирует посредством вывода сообщений в журнал небольшой эксперт, текст которого приведен ниже. Вариант реализации паузы задается входным параметром Use_Sleep - "true" для использования Sleep(), "false" для отказа от использования Sleep().

#include "include/ExLib_Timer.mqh"
// --
//---- input parameters
extern bool Use_Sleep = false;
// =true  - использование функции "Sleep()"
// =false - использование таймера
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
{
   timer_Init();
   if ( Use_Sleep )
      Print("init(). * Использование функции Sleep() *");
   else
      Print("init(). * Использование таймера *");
   timer_StartWaitable(101, 10);
   timer_StartStopwatch(102);
   return(0);
}
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
{
   timer_DeleteAll();
   Comment("");
   return(0);
}
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
{
   Comment("Waitable:",timer_GetCounter(101)," / Stopwatch:",timer_GetCounter(102));
   Print("start() - начальный блок -");
// --
   if ( Use_Sleep )
   {
      //  - использование функции "Sleep()" -
      Sleep( 10000 ); // пауза 10 секунд
      // программный блок, выполнение которого произойдет по истечению времени паузы
      Print("start() - исполняемый после паузы блок -");
   }
   else
   {
      //  - использование таймера -
      if ( timer_GetType(101) == 0 )
         timer_StartWaitable(101, 10); // пауза 10 секунд
         
      if ( timer_GetCounter(101) == 0 )
      {
         // программный блок, выполнение которого произойдет не ранее истечения времени паузы
         timer_Delete(101);
         Print("start() - исполняемый после паузы блок -");
      }
   }
// --
   Print("start() - конечный блок -");
   return(0);
}

Кроме приведенного выше, к статье прикреплены два демонстрационных эксперта:
Ex_Timer_OrderLimits_TrailByTime.mq4 - демонстрация реализации трейлинга SL и TP ордеров по времени
Ex_Timer_OrderSend_wMinTimeLimit.mq4 - демонстрация выставления ордеров не чаще 1 раза за период, установленный на текущем графике

Несущественные недостатки или "ложка дёгтя"?

К сожалению, представленная библиотека не может заменить Sleep() в полной мере. Есть два негативных момента, на которые следует обратить внимание читателя.

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

Во-вторых, функция Sleep() позволяет задавать паузу с шагом 0.1 сек, а представленный таймер - 1 сек.

Выводы

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

Состав прикрепленных файлов:

Библиотека функций:

  • ExLib_Timer.mqh - заголовочный файл;
  • ExLib_Timer.mq4 - исходный текст.

Примеры использования:

  • Ex_Timer_SleepOrNot.mq4 - эксперт, наглядно демонстрирующий различия в вариантах реализации паузы;
  • Ex_Timer_OrderLimits_TrailByTime.mq4 - эксперт, демонстрирующий трейлинг SL и TP ордеров по времени;
  • Ex_Timer_OrderSend_wMinTimeLimit.mq4 - эксперт, демонстрирующий выставление ордеров не чаще 1 раза за период, установленный на текущем графике.

Важно! Эксперты "Ex_Timer_OrderLimits_TrailByTime" и "Ex_Timer_OrderSend_wMinTimeLimit" расчитаны на работу только на демо счетах!

Последние комментарии | Перейти к обсуждению на форуме трейдеров (11)
Andrey Khatimlianskii
Andrey Khatimlianskii | 17 нояб. 2008 в 15:46

Похожая статья - Пауза между торговыми операциями.

И стоило упомянуть, что Sleep() имеет встроенную проверку на IsStopped() - иногда это может быть важно.

Andrey Khatimlianskii
Andrey Khatimlianskii | 17 нояб. 2008 в 15:47
tadbor:

Мне лично больше нравятся скрипты в while цикле чем советники зависимые от тиков.

Советника тоже можно зациклить ;)

[Удален] | 18 мая 2009 в 19:55
static int _time_waiting=0;
// ...
if ( ... ) // условие, при выполнение которого требуется выдержать паузу
   _time_waiting = TimeLocal() + 10; // окончание паузы через 10 секунд от текущего локального времени
if ( TimeLocal() >= _time_waiting )
{ // программный блок, выполнение которого не произойдет ранее времени окончания паузы
   // ...
}

Для тех кто в поезде, обясните плз.

1) Где обнуление _time_waiting`a?

На каком тике исполнится условие:

      _time_waiting = TimeLocal() + 10; // окончание паузы через 10 секунд от текущего локального времени
      if ( TimeLocal() >= _time_waiting )

ведь как я понял, каждый тик, переменная _time_waiting будет на 10сек больше текущего времени.

сенк.

Andrey Khatimlianskii
Andrey Khatimlianskii | 18 мая 2009 в 22:36
iSeq:

Для тех кто в поезде, обясните плз.

1) Где обнуление _time_waiting`a?

На каком тике исполнится условие:

      _time_waiting = TimeLocal() + 10; // окончание паузы через 10 секунд от текущего локального времени
      if ( TimeLocal() >= _time_waiting )

ведь как я понял, каждый тик, переменная _time_waiting будет на 10сек больше текущего времени.

Обнуление не нужно.

Строка " _time_waiting = TimeLocal() + 10;" выполнится только внутри if ( условие, при выполнение которого требуется выдержать паузу ).

sHD
sHD | 21 дек. 2012 в 04:50

Здравствуйте. При попытке реализовать паузу в пользовательском индикаторе терминал жёстко зависает. Этот же код без директивы  #property indicator_chart_window, после компиляции в эксперт работает нормально. Почему?

#property indicator_chart_window
int init()
  {
   return(0);
  }
int deinit()
  {
   return(0);
  }
int start()
  {
   bool AlertSignal = false;
   Alert ("Текущая цена" + Bid);   
   int _time_waiting=0;
   AlertSignal = true;
   if (AlertSignal == true)
   _time_waiting = TimeLocal() + 20; // окончание паузы через 20 секунд от текущего локального времени
   while ( TimeLocal() < _time_waiting )
         { 
          Comment("пауза");
         }
  return(0);
 }
Программная папка клиентского терминала MetaTrader 4 Программная папка клиентского терминала MetaTrader 4
В статье сделано описание содержимого программной папки клиентского терминала MetaTrader 4. Статья будет полезной прежде всего тем, кто уже немного разобрался с работой клиентского терминала.
Визуальное тестирование прибыльности индикаторов и сигналов Визуальное тестирование прибыльности индикаторов и сигналов
Выбор индикатора торговых сигналов или просто методики их расчета обычно проверяют в тестере при прогонах экспертов, использующих эти сигналы. Однако не всегда бывает возможно/нужно/целесообразно писать эксперт под каждый индикатор. Оперативно просчитать прибыльность торговли по сигналам других индикаторов можно с помощью специального индикатора, который сам собирает их сигналы и рисует картину идеальной торговли по ним. С его помощью Вы можете не только визуально оценить результаты, но и быстро подобрать наиболее оптимальные параметры.
Рецепты нейросетей Рецепты нейросетей
Статья для начинающих кулинаров в приготовлении "слоёных" пирогов
Лень - двигатель прогресса. Полуавтоматическая разметка шаблона Лень - двигатель прогресса. Полуавтоматическая разметка шаблона
Среди множества приемов работы с графиками есть способ ручной разметки шаблона. На график наносятся линии тренда, каналы, уровни поддержки и сопротивления и т.д. Естественно, что для этой работы есть и специальные программы. Какой способ использовать, каждый решает для себя сам. В данной статье я предлагаю рассмотреть способы ручной разметки и затем автоматизировать некоторые элементы рутинного повторения некоторых действий по разметке.