English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Ограничения и проверки в экспертах

Ограничения и проверки в экспертах

MetaTrader 5Примеры | 2 августа 2010, 16:10
11 398 15
MetaQuotes
MetaQuotes

Введение

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

  • получить информацию о торговых сессиях;
  • проверить достаточность средств на открытие позиции;
  • наложить ограничение на общий торговый объем по символу;
  • наложить ограничение на общее количество ордеров;
  • посчитать потенциальный убыток между entry price и Stop Loss;
  • сделать проверку на наличие нового бара.

Торговые и котировочные сессии

Для получения информации о торговых сессиях используйте функцию SymbolInfoSessionTrade(), для котировочных сессий есть соответствующая функция SymbolInfoSessionQuote(). Обе функции работают аналогично: если для указанного дня недели существует сессия с указанным индексом (индексация сессий начинается с нуля), то функция возвращает true. Время начала и время окончания сессии записывается в четвертый и пятый параметры, переданные по ссылке.

//--- проверим наличие котировочной сессии с номером session_index
bool session_exist=SymbolInfoSessionQuote(symbol,day,session_index,start,finish);
Чтобы выяснить все сессии для указанного дня, вызовем эту функцию в цикле до тех пор, пока она не вернет false.
//+------------------------------------------------------------------+
//|  Вывести информацию о котировочных сессиях                       |
//+------------------------------------------------------------------+
void PrintInfoForQuoteSessions(string symbol,ENUM_DAY_OF_WEEK day)
  {
//--- начало и конец сессии
   datetime start,finish;
   uint session_index=0;
   bool session_exist=true;

//--- пройдемся по всем сессиям за этот день
   while(session_exist)
     {
      //--- проверим наличие котировочной сессии с номером session_index
      session_exist=SymbolInfoSessionQuote(symbol,day,session_index,start,finish);

      //--- если такая сессия есть
      if(session_exist)
        {
         //--- выведем день недели, номер сессии и время начала и окончания
         Print(DayToString(day),": session index=",session_index,"  start=",
               TimeToString(start,TIME_MINUTES),"    finish=",TimeToString(finish-1,TIME_MINUTES|TIME_SECONDS));
        }
      //--- увеличим счетчик сессий
      session_index++;
     }
  }

День недели выводится в строковом виде с помощью пользовательской функции DayToString(), которая принимает в качестве параметра значение перечисления ENUM_DAY_OF_WEEK.

//+------------------------------------------------------------------+
//| Получить строковое представление дня недели                      |
//+------------------------------------------------------------------+
string DayToString(ENUM_DAY_OF_WEEK day)
  {
   switch(day)
     {
      case SUNDAY:    return "Sunday";
      case MONDAY:    return "Monday";
      case TUESDAY:   return "Tuesday";
      case WEDNESDAY: return "Wednesday";
      case THURSDAY:  return "Thursday";
      case FRIDAY:    return "Friday";
      case SATURDAY:  return "Saturday";
      default:        return "Unknown day of week";
     }
   return "";
  }

Итоговый код скрипта SymbolInfoSession.mq5 прикреплен снизу статьи, здесь же приведем только его основную часть.

void OnStart()
  {
//--- массив, в котором хранятся дни недели
   ENUM_DAY_OF_WEEK days[]={SUNDAY,MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY};
   int size=ArraySize(days);

//---
   Print("Котировочные сессии");
//--- пробежим по всем дням недели
   for(int d=0;d<size;d++)
     {
      PrintInfoForQuoteSessions(Symbol(),days[d]);
     }

//---
   Print("Торговые сессии");
//--- пробежим по всем дням недели
   for(int d=0;d<size;d++)
     {
      PrintInfoForTradeSessions(Symbol(),days[d]);
     }
  }


Проверка маржи

Выяснить размер залоговых средств, требуемых на открытие или наращивание позиции, можно функцией OrderCalcMargin(), которой первым параметром передается значение из перечисления ENUM_ORDER_TYPE. Для операции покупки необходимо вызывать с параметром ORDER_TYPE_BUY, для продажи - с параметром ORDER_TYPE_SELL. Функция возвращает размер маржи в зависимости от количества лотов и цены открытия.

void OnStart()
  {
//--- переменная для получения значения маржи
   double margin;
//--- для получения данных последнего тика
   MqlTick last_tick;
//--- попытаемся получить значения из последнего тика
   if(SymbolInfoTick(Symbol(),last_tick))
     {
      //--- сбросим код последней ошибки
      ResetLastError();
      //--- вычислим значение маржи
      bool check=OrderCalcMargin(type,Symbol(),lots,last_tick.ask,margin);
      if(check)
        {
         PrintFormat("Для операции %s  %s %.2f lot at %G требуется %.2f %s",OrderTypeToString(type),
                     Symbol(),lots,last_tick.ask,margin,AccountInfoString(ACCOUNT_CURRENCY));
        }
     }
   else
     {
      Print("Неудачное выполнение функции SymbolInfoTick(), ошибка ",GetLastError());
     }
  }

Следует отметить, что функция OrderCalcMargin() позволяет вычислять размер маржи не только для рыночных ордеров, но и для отложенных. Можете проверить возвращаемые значения для всех типов ордеров с помощью приложенного скрипта Check_Money.mq5.

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

Идентификатор

Описание

Тип свойства

SYMBOL_MARGIN_LONG

Коэффициент взимания маржи по длинным позициям

double

SYMBOL_MARGIN_SHORT

Коэффициент взимания маржи по коротким позициям

double

SYMBOL_MARGIN_LIMIT

Коэффициент взимания маржи по Limit ордерам

double

SYMBOL_MARGIN_STOP

Коэффициент взимания маржи по Stop ордерам

double

SYMBOL_MARGIN_STOPLIMIT

Коэффициент взимания маржи по Stop Limit ордерам

double


Получить значения этих коэффициентов можно с помощью простого кода:

//--- вычислим коэффициенты для взимания маржи под различные типы ордеров
   PrintFormat("Коэффициент взимания маржи по длинным позициям равен %G",SymbolInfoDouble(Symbol(),SYMBOL_MARGIN_LONG));
   PrintFormat("Коэффициент взимания маржи по коротким  позициям равен %G",SymbolInfoDouble(Symbol(),SYMBOL_MARGIN_SHORT));
   PrintFormat("Коэффициент взимания маржи по Limit ордерам равен %G",SymbolInfoDouble(Symbol(),SYMBOL_MARGIN_LIMIT));
   PrintFormat("Коэффициент взимания маржи по Stop ордерам равен %G",SymbolInfoDouble(Symbol(),SYMBOL_MARGIN_STOP));
   PrintFormat("Коэффициент взимания маржи по Stop Limit ордерам равен %G",SymbolInfoDouble(Symbol(),SYMBOL_MARGIN_STOPLIMIT));

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

Результаты работы скрипта Check_Money.mq5.

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

Учет возможных прибылей и убытков

При выставлении защитного стопа необходимо быть готовым к тому, что он сработает. Риск потенциального получения убытка необходимо учитывать в денежном выражении, и для этого существует функция OrderCalcProfit(). Она очень похожа на уже рассмотренную функцию OrderCalcMargin(), но требует для вычисления не только цену открытия, но и цену закрытия.

Укажите первым параметром одно из двух значений перечисления ENUM_ORDER_TYPE - ORDER_TYPE_BUY или ORDER_TYPE_SELL, другие типы ордеров приведут к ошибке. Последним параметром необходимо передать по ссылке переменную, в которую функция OrderCalcProfit() запишет значение прибыли/убытка в случае успешного выполнения.

Пример использования показан в функции CalculateProfitOneLot(), которая вычисляет прибыль или убыток при закрытии длинной позиции с указанными уровнями входа и выхода:

//+------------------------------------------------------------------+
//| Вычислить потенциальные прибыль/убыток для покупки 1 лота        |
//+------------------------------------------------------------------+
double CalculateProfitOneLot(double entry_price,double exit_price)
  {
//--- в эту переменную получим значение прибыли
   double profit=0;
   if(!OrderCalcProfit(ORDER_TYPE_BUY,Symbol(),1.0,entry_price,exit_price,profit))
     {
      Print(__FUNCTION__,"  Не удалось вычислить OrderCalcProfit(). Ошибка ",GetLastError());
     }
//---
   return(profit);
  }

На рисунке показан результат вычисления этой функции.

Пример вычисления и отображения на графике потенциального убытка с помощью функции OrderCalcProfit().

Полный код можно посмотреть в приложенном эксперте CalculateProfit_EA.mq5.

Проверка появления нового бара

При разработке многих торговых систем предполагается, что торговые сигналы вычисляются только при появлении нового бара, и все торговые действия производятся только один раз. Для проверки подобных автоматических систем торговли в тестере стратегий терминала MetaTrader 5 хорошо подходит режим тестирования "Только цены открытия".

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

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

Получить время открытия последнего бара можно функцией SeriesInfoInteger(), которой необходимо указать имя символа, таймфрейм и свойство SERIES_LASTBAR_DATE. Сравнивая каждый раз время открытия текущего бара с тем, что хранится в переменной, можно легко определить момент появления нового бара. Это позволяет создать свою функцию isNewBar(), которая может выглядеть так:

//+------------------------------------------------------------------+
//| Возвращает true, если появился новый бар для пары символ/период  |
//+------------------------------------------------------------------+
bool isNewBar()
  {
//--- в статической переменной будем помнить время открытия последнего бара
   static datetime last_time=0;
//--- текущее время
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

//--- если это первый вызов функции
   if(last_time==0)
     {
      //--- установим время и выйдем 
      last_time=lastbar_time;
      return(false);
     }

//--- если время отличается
   if(last_time!=lastbar_time)
     {
      //--- запомним время и вернем true
      last_time=lastbar_time;
      return(true);
     }
//--- дошли до этого места - значит бар не новый, вернем false
   return(false);
  }

Пример использования функции приведен в приложенном эксперте CheckLastBar.mq5.

Сообщения советника CheckLastBar о появлении новых баров на минутном таймфрейме.

Ограничение на количество отложенных ордеров

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

//+------------------------------------------------------------------+
//| проверяет - можно ли открыть выставить еще один ордер            |
//+------------------------------------------------------------------+
bool IsNewOrderAllowed()
  {
//--- получим количество разрешенных на счете отложенных ордеров
   int max_allowed_orders=(int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);

//--- если ограничения нет - вернем true, можно отослать ордер
   if(max_allowed_orders==0) return(true);

//--- если дошли до этого места, значит ограничение есть, узнаем сколько уже ордеров действует
   int orders=OrdersTotal();

//--- вернем результат сравнения
   return(orders<max_allowed_orders);
  }

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

Ограничение на количество лотов по одному символу

Для того чтобы получить размер объема открытой позиции по заданному символу, необходимо предварительно выбрать позицию с помощью функции PositionSelect(). И только после этого запросить объем выбранной позиции с помощью функции PositionGetDouble(), которая возвращает различные свойства выбранной позиции, имеющие тип double.  Для получения объема позиции по данному символу напишем функцию PositionVolume().

//+------------------------------------------------------------------+
//| Возвращает размер позиции по указанному символу                  |
//+------------------------------------------------------------------+
double PositionVolume(string symbol)
  {
//--- попробуем выбрать позицию по символу
   bool selected=PositionSelect(symbol);
//--- позиция существует
   if(selected)
      //--- вернем объем позиции
      return(PositionGetDouble(POSITION_VOLUME));
   else
     {
      //--- сообщим о неудачной попытке выбрать позицию
      Print(__FUNCTION__," Не удалось выполнить PositionSelect() для символа ",
            symbol," Ошибка ",GetLastError());
      return(-1);
     }
  }

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

double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT);
if(max_volume==0) volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);

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

//+------------------------------------------------------------------+
//| Возвращает размер позиции по указанному символу                  |
//+------------------------------------------------------------------+
double PositionVolume(string symbol)
  {
//--- попробуем выбрать позицию по символу
   bool selected=PositionSelect(symbol);
//--- позиция существует
   if(selected)
      //--- вернем объем позиции
      return(PositionGetDouble(POSITION_VOLUME));
   else
     {
      //--- вернем ноль при отсутствии позиции
      return(0);
     }
  }

С учетом объема открытой позиции и объема в отложенных ордерах, окончательная проверка будет выглядеть так:

//+------------------------------------------------------------------+
//|  Возвращает максимально допустимый объем для ордера по символу   |
//+------------------------------------------------------------------+
double NewOrderAllowedVolume(string symbol)
  {
   double allowed_volume=0;
//--- получим ограничение на максимальный объем в ордере
   double symbol_max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);
//--- получим ограничение по символу на объем
   double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT);

//--- получим объем открытой позиции по символу
   double opened_volume=PositionVolume(symbol);
   if(opened_volume>=0)
     {
      //--- если мы уже исчерпали объем
      if(max_volume-opened_volume<=0)
         return(0);

      //--- объем открытой позиции не превышает max_volume
      double orders_volume_on_symbol=PendingsVolume(symbol);
      allowed_volume=max_volume-opened_volume-orders_volume_on_symbol;
      if(allowed_volume>symbol_max_volume) allowed_volume=symbol_max_volume;
     }
   return(allowed_volume);
  }

Полный код эксперта Check_Order_And_Volume_Limits.mq5, который содержит рассмотренные в этом разделе функции, прилагается к статье.

Пример проверок ограничений экспертом Check_Order_And_Volume_Limits на счете участника Чемпионата Automated Trading Championship 2010.

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

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

Идентификатор

Описание

Тип свойства

SYMBOL_VOLUME_MIN

Минимальный объем для заключения сделки

double

SYMBOL_VOLUME_MAX

Максимальный объем для заключения сделки

double

SYMBOL_VOLUME_STEP

Минимальный шаг изменения объема для заключения сделки

double


Для такой проверки мы можем написать собственную функцию CheckVolumeValue():

//+------------------------------------------------------------------+
//|  Проверяет объем ордера на корректность                           |
//+------------------------------------------------------------------+
bool CheckVolumeValue(double volume,string &description)
  {
//--- минимально допустимый объем для торговых операций
   double min_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   if(volume<min_volume)
     {
      description=StringFormat("Объем меньше минимально допустимого SYMBOL_VOLUME_MIN=%.2f",min_volume);
      return(false);
     }

//--- максимально допустимый объем для торговых операций
   double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);
   if(volume>max_volume)
     {
      description=StringFormat("Объем больше максимально допустимого SYMBOL_VOLUME_MAX=%.2f",max_volume);
      return(false);
     }

//--- получим минимальную градацию объема
   double volume_step=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP);

   int ratio=(int)MathRound(volume/volume_step);
   if(MathAbs(ratio*volume_step-volume)>0.0000001)
     {
      description=StringFormat("Объем не является кратным минимальной градации SYMBOL_VOLUME_STEP=%.2f, ближайший корректный объем %.2f",
                               volume_step,ratio*volume_step);
      return(false);
     }
   description="Корректное значение объема";
   return(true);
  }

Проверить работу этой функции вы можете с помощью приложенного к статье скрипта CheckVolumeValue.mq5.


Сообщения из скрипта CheckVolumeValue.mq5, проверяющего объем на корректность.

Заключение

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

Прикрепленные файлы |
check_money.mq5 (3.37 KB)
checklastbar.mq5 (1.77 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (15)
Rashid Umarov
Rashid Umarov | 19 авг. 2010 в 15:46
gumgum:

'SYMBOL_VOLUME_LIMIT' - undeclared identifier.............

'SymbolInfoDouble' - no one of the overloads can be applied to the function call............

Это только у меня так?

Как только выйдет новый билд - все станет нормально. Ждем билда.
Mario
Mario | 19 авг. 2010 в 23:53
Rosh:

а так можно?

lot_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);

 

Yedelkin
Yedelkin | 31 авг. 2010 в 20:07

Ещё один вопрос по поводу функции SeriesInfoInteger().

При создании темы "AccountInfo... Актуальность информации"  узнал, что есть функции, которые используют данные, "кешируемые в программном окружении", и есть функции, всегда имеющие "прямой доступ к актуальным данным".

К какой категории функций относится  функция SeriesInfoInteger() ?

Максим
Максим | 24 окт. 2010 в 17:54

В функции CheckVolumeValue используется следующий код:

int ratio=(int)MathRound(volume/volume_step);

if(MathAbs(ratio*volume_step-volume)>0.0000001)

{

description=StringFormat("Объем не является кратным минимальной градации SYMBOL_VOLUME_STEP=%.2f, ближайший корректный объем %.2f",

volume_step,ratio*volume_step);

return(false);

}

 Но правильней будет: 

int ratio = (int)MathRound((volume-min_volume)/volume_step);

if (MathAbs(ratio*volume_step+min_volume-volume)>0.0000001)

{

description=StringFormat("Объем не является кратным минимальной градации SYMBOL_VOLUME_STEP=%.2f, ближайший корректный объем %.2f",

  volume_step,ratio*volume_step+min_volume);

return(false);

}

Поскольку минимальный шаг изменения объёма должен отсчитываться от минимального значения.

Максим
Максим | 24 окт. 2010 в 19:43

И, пожалуй, фрагмент кода:

//--- вычислим значение маржи

bool check=OrderCalcMargin(type,Symbol(),lots,last_tick.ask,margin);

 нужно заменить на:

//--- вычислим значение маржи

double price = (type == ORDER_TYPE_BUY || type == ORDER_TYPE_BUY_LIMIT || type == ORDER_TYPE_BUY_STOP || type == ORDER_TYPE_BUY_STOP_LIMIT) ? last_tick.ask : last_tick.bid;

bool check=OrderCalcMargin(type,Symbol(),lots,price,margin);

 

Исследование паттернов (моделей) японских свечей Исследование паттернов (моделей) японских свечей
Построение графиков японских свечей и анализ свечных моделей — удивительное направление технического анализа. Преимущество японских свечей в том, что они представляют данные таким образом, что появляется возможность увидеть динамику внутри данных. В данной статье мы рассмотрим типы свечей, классификацию свечных моделей и напишем индикатор, распознающий свечные паттерны.
Цветные индикаторы - создание и применение Цветные индикаторы - создание и применение
Речь в данной статье пойдет о возможностях для создания цветных индикаторов и раскрашивания индикаторов уже существующих. С переходом на MQL5 появилась возможность представлять информацию в удобном для глаза виде. Теперь не обязательно накидывать кучу графиков с разными индикаторами и с линейкой высматривать уровни RSI и Stochastic, можно просто раскрасить свечи в разные цвета в зависимости от показаний индикаторов.
Как создать свой Trailing Stop Как создать свой Trailing Stop
Основное правило трейдера - дай прибыли расти, обрезай убытки! В статье рассматривается один из основных технических приемов, позволяющий следовать этому правилу - перемещение уровня защитной остановки (уровня Stoploss) вслед за растущей прибылью позиции, другими словами - скользящий стоп или трейлинг стоп (trailingstop). Приводится пошаговая процедура создания класса для трейлинг стопа на индикаторах SAR и NRTR, который каждый желающий сможет за 5 минут встроить в своего эксперта или использовать независимо для управления позициями на своем счете.
Интервью с Николаем Косициным: мультивалютные эксперты менее рискованны (ATC 2010) Интервью с Николаем Косициным: мультивалютные эксперты менее рискованны (ATC 2010)
Мы побеседовали с Николаем Косициным о его разработках. Он считает мультивалютники перспективным направлением и занимается разработкой именно таких экспертов. На чемпионатах Николай также выступает только с мультивалютниками. Именно его советник стал единственным мультивалютником, который вышел в победители Чемпионата за все время проведения соревнования.