История сделок и библиотека функций для получения свойств позиции

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

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

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


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

Ниже на картинке показана ситуация, когда у позиции только одна сделка (точка входа):


Первая сделка позиции

На следующей картинке видно, как изменилась цена позиции после совершения второй сделки:

Вторая сделка позиции

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

История сделок счёта

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

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

//+------------------------------------------------------------------+
//| ПЕРЕЧИСЛЕНИЕ СВОЙСТВ ПОЗИЦИИ                                     |
//+------------------------------------------------------------------+
enum ENUM_PROP_POSITION
  {
   POS_TOTAL_DEALS   = 0,
   POS_COMMENT       = 1,
   POS_MAGIC         = 2,
   POS_PRICE_FDEAL   = 3,
   POS_PRICE_OPEN    = 4,
   POS_PRICE_CURRENT = 5,
   POS_PRICE_LDEAL   = 6,
   POS_SL            = 7,
   POS_TP            = 8,
   POS_SYMBOL        = 9,
   POS_TYPE          = 10,
   POS_VOLUME        = 11,
   POS_VOL_INIT      = 12,
   POS_COMMISSION    = 13,
   POS_SWAP          = 14,
   POS_PROFIT        = 15,
   POS_TIME          = 16,
   POS_LENGTH        = 17,
   POS_ID            = 18,
   POS_ALL           = 19
  };
//---

Комментарии к каждому свойству будут написаны в структуре, которую будем рассматривать чуть ниже.

Количество внешних параметров тоже пополним. Теперь можно будет указывать магический номер (MagicNumber), проскальзывание (Deviation), объём позиции (Lot). А также объём, на который будет увеличиваться объём позиции (Increase) и параметр, с помощью которого можно включить/отключить показ на графике информационной панели (InfoPanel). Вот, как это выглядит в коде:

//+------------------------------------------------------------------+
//| ВНЕШНИЕ ПАРАМЕТРЫ ЭКСПЕРТА                                       |
//+------------------------------------------------------------------+
sinput long   MagicNumber = 777;  // Magic Number
sinput int    Deviation   = 10;   // Deviation (p)
input  int    AmountBars  = 2;    // Amount Bear/Bull Bars For Buy/Sell
input  double TakeProfit  = 100;  // Take Profit (p)
input  double StopLoss    = 50;   // Stop Loss (p)
input  double TrailingSL  = 10;   // Trailing Stop (p)
input  bool   Reverse     = true; // On/Off Reverse
input  double Lot         = 0.1;  // Lot
input  double Increase    = 0.1;  // Increase Volume
sinput bool   InfoPanel   = true; // Info Panel
//---

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

Запрещённые для оптимизации параметры помечены серыми заглушками


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

Из Справки языка:

Структура является набором элементов произвольного типа (кроме типа void). Таким образом, структура объединяет логически связанные данные разных типов.

Обязательно ознакомьтесь с полным описанием этого типа данных в Справке.

Вот, что должно получиться в итоге:

//+------------------------------------------------------------------+
//| СТРУКТУРЫ                                                        |
//+------------------------------------------------------------------+
// Структура свойств позиции
struct propPosition
  {
   bool exist;    // Признак наличия/отсутствия открытой позиции
   //---
   uint
   total_deals,   // Количество сделок
   type;          // Tип позиции
   //---
   string
   symbol,        // Символ
   comment;       // Комментарий
   //---
   long
   magic,         // Магический номер
   length,        // Длительность позиции в секундах
   id;            // Идентификатор позиции
   //---
   double
   profit,        // Прибыль/убыток позиции
   swap,          // Своп
   commission,    // Комиссия
   price_fdeal,   // Цена первой сделки позиции
   price,         // Текущая цена позиции
   cprice,        // Текущая цена символа позиции
   price_ldeal,   // Цена последней сделки позиции
   volume,        // Текущий объём позиции
   vol_init,      // Начальный объём позиции
   sl,            // Stop Loss позиции
   tp;            // Take Profit позиции
   //---
   datetime time; // Время открытия позиции
  };
//---
// Структура свойств символа
struct propSymbol
  {
   int
   dgt,         // Количество знаков в цене после запятой
   spread,      // Размер спреда в пунктах
   stops_level; // Ограничитель установки Stop ордеров
   //---
   double
   point,       // Значение одного пункта
   ask,         // Цена ask
   bid,         // Цена bid
   vol_min,     // Минимальный объем для заключения сделки
   vol_max,     // Максимальный объем для заключения сделки
   vol_lmt,     // Максимально допустимый объём для позиции и ордеров в одном направлении
   vol_step,    // Минимальный шаг изменения объема для заключения сделки
   pp_filter,   // Отступ от максимально возможной цены для операции
   up_level,    // Цена верхнего уровня stop level
   dw_level;    // Цена нижнего уровня stop level
  };
//---

Теперь, чтобы получить доступ к тому или иному элементу структуры, нужно создать переменную. Это делается также, как создание объекта для торгового класса, который рассматривался в статье "Изучение свойств позиции в тестере MetaTrader 5".

Ниже показано создание переменных для выше созданных структур propPosition и propSymbol:

//---
propPosition pos; // Создадим переменную pos типа структуры propPosition
propSymbol smb; // Создадим переменную smb типа структуры propSymbol
//---

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

Ещё один важный момент. Так как мы сейчас модифицируем эксперта и заменили в нём практически все глобальные переменные, которые использовались во многих функциях, то нужно теперь заменить их на элементы структуры. Вручную делать это долго и утомительно, но всё можно автоматизировать с помощью средств редактора MetaEditor. Например, глобальная переменная isPos, которая использовалась для сохранения признака существования/отсутствия позиции, заменилась элементом структуры exist. Поэтому теперь везде, где использовалась переменная isPos нужно написать pos.exist.

На панели инструментов редактора MetaEditor есть (или можно установить в настройках) значок "Замена заданного текста другим":

Значок опции в MetaEditor - Замена заданного текста другим

Окно с настройками этой опции можно также вызвать с помощью комбинации горячих клавиш Ctrl+H.  Ниже показан скриншот окна с настройками этой опции:

Окно настроек опции в MetaEditor - Замена заданного текста другим

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

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

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

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

//+------------------------------------------------------------------+
//| ОТКРЫТИЕ ПОЗИЦИИ                                                 |
//+------------------------------------------------------------------+
void OpenPosition(double lot,
                  ENUM_ORDER_TYPE type_ord,
                  double oprice,
                  double sl,
                  double tp,
                  string comment)
  {
   trd.SetExpertMagicNumber(MagicNumber); // Установим номер мэджика в торговую структуру
   trd.SetDeviationInPoints(DgtMlt(Deviation)); // Установим размер проскальзывания в пунктах
//---
// Если позиция не открылась, вывести сообщение об этом
   if(!trd.PositionOpen(_Symbol,type_ord,lot,oprice,sl,tp,comment))
     { Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDesc(GetLastError())); }
  }
//---

Осталось внести небольшие изменения в код главной торговой функции эксперта TradingBlock(). Ниже приведена только та часть кода функции, где были произведены изменения:

//---
// Если позиции нет
   if(!pos.exist)
     {
      lot=GetLot(Lot); // Скорректируем объём
      //---
      // Откроем позицию
      OpenPosition(lot,(ENUM_ORDER_TYPE)type_ord,oprice,sl,tp,comment);
     }
   else // Если позиция есть
     {
      // Получим тип позиции
      GetPropPosition(POS_TYPE);
      //---
      // Если позиция противоположна сигналу и включен переворот позиции
      if(pos.type==opptype_pos && Reverse)
        {
         // Получим объём позиции
         GetPropPosition(POS_VOLUME);
         //---
         // Скорректируем объём
         lot=ND(pos.volume+GetLot(Lot),2);
         //---
         // Перевернём позицию
         OpenPosition(lot,(ENUM_ORDER_TYPE)type_ord,oprice,sl,tp,comment);
         //---
         return;
        }
      //---
      // Если сигнал по направлению позиции и 
      // включено наращивание объёма, то увеличим объём позиции
      if(pos.type==type_ord && Increase>0)
        {
         lot=GetLot(Increase); // Скорректируем объём
         //---
         // Увеличим объём позиции
         OpenPosition(lot,(ENUM_ORDER_TYPE)type_ord,oprice,sl,tp,comment);
         //---
         return;
        }
     }
//---

В коде выше был добавлен блок, начиная со строки 499 (выделена), в котором проверяется направление текущей позиции и сигнала. Если они в одном направлении и во внешних параметрах включено наращивание объёма позиции (значение параметра Increase больше нуля), то проверяем/корректируем установленный лот и отправляем приказ. Для отправки приказа на открытие, переворота и увеличение объёма теперь достаточно написать одну строку кода.

Теперь создадим функции для получения свойств позиции, которые можно получить только при поднятии истории сделок. Начнём с функции GetTotalDealsCurrPos(), которая возвращает количество сделок в текущей позиции. Ниже представлен её код:

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ КОЛИЧЕСТВО СДЕЛОК ТЕКУЩЕЙ ПОЗИЦИИ                     |
//+------------------------------------------------------------------+
uint GetTotalDealsCurrPos()
  {
   int
   total=0, // Всего сделок в списке выбранной истории
   count=0; // Счётчик сделок по символу позиции
   ulong ticket=0; // Тикет сделки
   string deal_symbol=""; // символ сделки
//---
// Если история позиции получена
   if(HistorySelect(pos.time,TimeCurrent()))
     {
      // Получим количество сделок в полученном списке
      total=HistoryDealsTotal();
      //---
      // Пройдем в цикле по всем сделкам в полученном списке
      // от первой сделки позиции к последней
      for(int i=0; i<total; i++)
        {
         // Если тикет сделки по её позиции в списке получен, то...
         if((ticket=HistoryDealGetTicket(i))>0)
           {
            deal_symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); // получим символ сделки
            //---
            // Если символ сделки и текущий символ совпадают, увеличим счётчик
            if(deal_symbol==_Symbol) { count++; }
           }
        }
     }
//---
   return(count);
  }
//---

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

Историю той или иной позиции можно выбрать и по её идентификатору с помощью функции HistorySelectByPosition(), но тогда нужно учитывать, что при перевороте позиции, как это и происходит иногда в этом эксперте, идентификатор у позиции не меняется. А вот время открытия позиции при перевороте меняется, поэтому такой вариант проще реализовать. Но если нужно работать с историей сделок, которая относится не только к той позиции, которая открыта в текущий момент, то нужно использовать идентификаторы. К истории сделок ещё не раз вернёмся в будущих статьях.

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

Далее создадим функцию GetPriceFirstDealCurrPos(), которая возвращает цену самой первой сделки позиции. То есть, цену той сделки, с которой позиция была открыта. Принцип такой же, как и у предыдущей функции. Получаем историю от точки открытия позиции и на каждой итерации проверяем время сделки и время открытия позиции. При получении свойства времени сделки заодно получаем такие свойства, как имя символа и цену сделки. Как только время сделки и позиции совпали, то это значит, что найдена самая первая сделка, и так как её цена уже присвоена соответствующей переменной, то осталось только вернуть это значение. Фактически цикл заканчивается сразу же на первой итерации, так как он начинается именно с самой первой сделки позиции, по времени которой и выбирается список.

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ ЦЕНУ ПЕРВОЙ СДЕЛКИ ТЕКУЩЕЙ ПОЗИЦИИ                    |
//+------------------------------------------------------------------+
double GetPriceFirstDealCurrPos()
  {
   int total=0; // Всего сделок в списке выбранной истории
   ulong ticket=0; // Тикет сделки
   string deal_symbol=""; // символ сделки
   double deal_price=0.0; // Цена сделки
   datetime deal_time=NULL; // Время сделки
//---
// Если история позиции получена
   if(HistorySelect(pos.time,TimeCurrent()))
     {
      // Получим количество сделок в полученном списке
      total=HistoryDealsTotal();
      //---
      // Пройдем в цикле по всем сделкам в полученном списке
      // от первой сделки позиции к последней
      for(int i=0; i<total; i++)
        {
         // Если тикет сделки по её позиции в списке получен, то...
         if((ticket=HistoryDealGetTicket(i))>0)
           {
            deal_symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); // получим символ сделки
            deal_price=HistoryDealGetDouble(ticket,DEAL_PRICE); // получим цену сделки
            deal_time=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); // получим время сделки
            //---
            // Если время сделки и 
            // время открытия позиции равны, а также,
            // символ сделки и текущий символ равны, остановим цикл
            if(deal_time==pos.time && deal_symbol==_Symbol) { break; }
           }
        }
     }
//---
   return(deal_price);
  }
//---

Двигаемся дальше. Иногда может понадобится получить цену последней сделки текущей позиции, поэтому сделаем функцию GetPriceLastDealCurrPos(), которая предназначена для этого.

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ ЦЕНУ ПОСЛЕДНЕЙ СДЕЛКИ ТЕКУЩЕЙ ПОЗИЦИИ                 |
//+------------------------------------------------------------------+
double GetPriceLastDealCurrPos()
  {
   int total=0; // Всего сделок в списке выбранной истории
   ulong ticket=0; // Тикет сделки
   string deal_symbol=""; // Символ сделки 
   double deal_price=0.0; // Цена
//---
// Если история позиции получена
   if(HistorySelect(pos.time,TimeCurrent()))
     {
      // Получим количество сделок в полученном списке
      total=HistoryDealsTotal();
      //---
      // Пройдем в цикле по всем сделкам в полученном списке
      // от последней сделки в списке к первой
      for(int i=total-1; i>=0; i--)
        {
         // Если тикет сделки по её позиции в списке получен, то...
         if((ticket=HistoryDealGetTicket(i))>0)
           {
            deal_symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); // получим символ сделки
            deal_price=HistoryDealGetDouble(ticket,DEAL_PRICE); // получим цену сделки
            //---
            // Если символ сделки и текущий символ равны, остановим цикл
            if(deal_symbol==_Symbol) { break; }
           }
        }
     }
//---
   return(deal_price);
  }
//---

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

Текущий объём позиции можно получить с помощью стандартного идентификатора POSITION_VOLUME. А вот, чтобы узнать начальный объём позиции (объём первой сделки) создадим функцию GetVolumeInitialCurrPos(). Ниже представлен код этой функции:

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ НАЧАЛЬНЫЙ ОБЪЁМ ТЕКУЩЕЙ ПОЗИЦИИ                       |
//+------------------------------------------------------------------+
double GetVolumeInitialCurrPos()
  {
   int total=0; // Всего сделок в списке выбранной истории
   ulong ticket=0; // Тикет сделки
   int ldeal_entry=-1; // Способ изменения позиции
   double cnt_dvol=0.0; // Счётчик совокупного объёма всех сделок кроме первой
   double ldeal_vol=0.0; // Объём сделки
   string deal_symbol=""; // Символ сделки 
   datetime ldeal_tmstp=NULL; // Время совершения сделки
//---
// Если история позиции получена
   if(HistorySelect(pos.time,TimeCurrent()))
     {
      // Получим количество сделок в полученном списке
      total=HistoryDealsTotal();
      //---
      // Пройдем в цикле по всем ордерам в полученном списке
      // от последней сделки в списке к первой
      for(int i=total-1; i>=0; i--)
        {
         // Если тикет ордера по его позиции в списке получен, то...
         if((ticket=HistoryDealGetTicket(i))>0)
           {
            ldeal_vol=HistoryDealGetDouble(ticket,DEAL_VOLUME); // получим объём ордера
            //---
            // получим способ изменения позиции
            ldeal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY);
            ldeal_tmstp=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); // получим время установки ордера
            deal_symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); // получим символ сделки
            //---
            // Если время установки ордера и 
            // время открытия позиции меньше или равны, остановим цикл
            if(ldeal_tmstp<=pos.time) { break; }
            else
              {// иначе считаем совокупный объём сделок по символу позиции, кроме первой
               if(deal_symbol==_Symbol) { cnt_dvol+=ldeal_vol; }
              }
           }
        }
     }
//---
// Если способ изменения позиции Переворот
   if(ldeal_entry==DEAL_ENTRY_INOUT)
     {
      // Если объём позиции увеличивался/уменьшался
      // То есть, сделок больше одной
      if(fabs(cnt_dvol)>0)
        {
         // Текущий объём минус совокупный объём сделок кроме первой
         double rvol=pos.volume-cnt_dvol;
         //---
         // Если итог больше нуля, вернём итог
         if(rvol>0) { ldeal_vol=rvol; }
         //---
         // Если равен нулю, вернём текущий объём позиции
         if(rvol==0) { ldeal_vol=pos.volume; }
        }
      //---
      // Если сделок кроме входа больше не было,
      if(cnt_dvol==0) { ldeal_vol=pos.volume; } // вернём текущий объём позиции
     }
//---
// Вернём начальный объём позиции
   return(ND(ldeal_vol,2));
  }
//---

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

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

//+------------------------------------------------------------------+
//| ПЕРЕЧИСЛЕНИЕ ДЛИТЕЛЬНОСТЕЙ                                       |
//+------------------------------------------------------------------+
enum ENUM_MODE_TIMELENGTH
  {
   TIME_DAYS  = 0, // Дни
   TIME_HOURS = 1, // Часы
   TIME_MINS  = 2, // Минуты
   TIME_SECS  = 3  // Секунды
  };
//---

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

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ ДЛИТЕЛЬНОСТЬ ТЕКУЩЕЙ ПОЗИЦИИ                          |
//+------------------------------------------------------------------+
long GetLengthCurrPos(int mode)
  {
   long
   rval=0, // Итоговый результат
   cnt_sec=0; // Количество секунд
   datetime curr_time=0; // Текущее время
//---
   curr_time=TimeCurrent(); // Получим текущее время
   cnt_sec=curr_time-pos.time; // Вычислим длительность позиции в секундах
//---
   switch(mode)
     {
      case TIME_DAYS  : rval=((cnt_sec/60)/60)/24; break; // Посчитаем кол-во дней
      case TIME_HOURS : rval=(cnt_sec/60)/60;      break; // Посчитаем кол-во часов
      case TIME_MINS  : rval=cnt_sec/60;           break; // Посчитаем кол-во минут
      case TIME_SECS  : rval=cnt_sec;              break; // Без расчётов (кол-во секунд)
      //---
      default : Print(__FUNCTION__,"(): Передан неизвестный идентификатор длительности!");
     }
//---
   return(rval); // Вернём результат
  }
//---

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

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ СТРОКУ ДЛИТЕЛЬНОСТИ ПОЗИЦИИ                           |
//+------------------------------------------------------------------+
string StrLengthCurrPos(long seconds)
  {
   string str="-"; // Прочерк в случае отсутствия позиции
//---
   if(pos.exist) // Если есть позиция
     {
      // Сформируем формат для строки
      string timef="%02i d : %02i h : %02i min";
      //---
      double // Вспомогательные переменные и ...
      cnt_d=0.0,cnt_h=0.0,cnt_m=0.0,
      //---
      // ...переменные для результата расчётов
      d=0.0,h=0.0,m=0.0;
      //---
      cnt_m=seconds/60.0; // Получим количество минут
      cnt_h=cnt_m/60.0;   // Получим количество часов
      cnt_d=cnt_h/24.0;   // Получим количество дней
      //---
      // Если ещё не прошло целого часа, то...
      if((int)cnt_h==0) { m=cnt_m; } // ...не производим дополнительных вычислений с минутами
      else // Если же уже есть один целый час или более
        {
         h=(int)cnt_h; // Отбросим остаток приведением к целому
         m=cnt_m-(h*60); // Посчитаем текущее количество минут в часе
        }
      //---
      //  Если ещё не прошло целого дня, то...
      if((int)cnt_d==0) { h=cnt_h; } // ...не производим дополнительных вычислений с часами
      else // Если же уже есть один целый день или более
        {
         d=(int)cnt_d; // Отбросим остаток приведением к целому
         h=cnt_h-(d*24); // Посчитаем текущее количество часов в дне
        }
      //---
      // Сформируем строку в указанном формате
      str=StringFormat(timef,(int)d,(int)h,(int)m);
     }
//---
   return(str); // Вернём результат
  }
//---

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

Информационная панель должна получиться такой, как на рисунке ниже:

Демонстрация работы всех функций свойств позиции на инфо-панели

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

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

Настройки тестера установим так, как на рисунке ниже:

Настройки тестера для оптимизации параметров


Настройки внешних параметров эксперта установим с широкими диапазонами:

Настройки параметров эксперта для оптимизации

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

Сортировка по максимальной прибыли

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

Результат по максимальной прибыли

..., посмотрите потом результат на других символах с теми же параметрами. Например, результат выше был получен на символе EURUSD. А результат ниже на символе EURJPY:

Результат на другом символе с теми же параметрами

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

Сортировка по максимальному фактору восстановления

Даже с учётом того, что оптимизация проводилась на EURUSD, на многих символах можно увидеть положительный результат с такими же параметрами:

Результат на символе EURUSD:

Результат на символе EURUSD

Результат на символе AUDUSD:

Результат на символе AUDUSD

Результат на символе NZDUSD:

Результат на символе NZDUSD

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

Ниже можно скачать исходный код эксперта и сет настроек для внешних параметров.

Успехов!




Скачать эксперт expGPPfinal.mq5
Скачать сет с настройками эксперта.


Комментариев нет :

Отправить комментарий