Рецепты MQL5 - Как получить свойства позиции?

Anatoli Kazharski | 26 февраля, 2013

Введение

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

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


Процесс разработки скрипта

Начало программы примерно такое же, как и в предыдущей статье (смотрите код ниже). Сначала идут свойства программы. Далее следует строка с директивой #define, после которой переменной SCRIPT_NAME с помощью функции MQLInfoString() и указанной в ней константы MQL_PROGRAM_NAME, присваивается имя программы. В Справочнике MQL5 вы можете подробнее ознакомиться со всеми возможными значениями для функции MQLInfoString().

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

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

#property copyright   "Copyright 2012, http://tol64.blogspot.com"
#property link        "http://tol64.blogspot.com"
#property description "email: hello.tol64@gmail.com"
#property version     "1.0"
#property script_show_inputs
//---
#define SCRIPT_NAME MQLInfoString(MQL_PROGRAM_NAME) // Имя скрипта
//---
// ПЕРЕЧИСЛЕНИЕ РЕЖИМОВ
enum ENUM_SYMBOLS_MODE
  {
   CURRENT_SYMBOL =0,                     // Текущий символ
   ALL_SYMBOLS    =1                      // Все символы
  };
//---
// ВХОДНЫЕ ПАРАМЕТРЫ
input ENUM_SYMBOLS_MODE mode=CURRENT_SYMBOL;     // Режим

Далее по коду следуют глобальные переменные. Глобальные переменные доступны в любом месте программы и для этого должны быть расположены вне функций (обычно - в самом начале программы).

// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
long                 pos_magic=0;         // Магический номер
string               pos_symbol="";       // Символ
string               pos_comment="";      // Комментарий
double               pos_swap=0.0;        // Своп
double               pos_commission=0.0;  // Комиссия
double               pos_price=0.0;       // Текущая цена позиции
double               pos_cprice=0.0;      // Текущая цена позиции
double               pos_profit=0.0;      // Прибыль/убыток позиции
double               pos_volume=0.0;      // Объём позиции
double               pos_sl=0.0;          // Stop Loss позиции
double               pos_tp=0.0;          // Take Profit позиции
datetime             pos_time=NULL;       // Время открытия позиции
long                 pos_id=0;            // Идентификатор позиции
ENUM_POSITION_TYPE   pos_type=NULL;       // Тип позиции
//---

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

//+------------------------------------------------------------------+
//| ГЛАВНАЯ ФУНКЦИЯ                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   PrintPositionProperties();
  }

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

//+------------------------------------------------------------------+
//| ОТКРЫВАЕТ ДИАЛОГОВОЕ ОКНО С ДАННЫМИ СИМВОЛА                      |
//+------------------------------------------------------------------+
void PrintPositionProperties()
  {
   int err=0; // Переменная для обработки ошибок
//---
// Если нужно получить свойства позиции только на текущем символе
   if(mode==CURRENT_SYMBOL)
     {
 
     }
//---
// Если нужно получить свойства позиций на всех символах
   if(mode==ALL_SYMBOLS)
     {
 
     }
  }

Всего лишь две ветки, в начале функции также объявлена локальная переменная err для обработки ошибок. Теперь нужно для каждого варианта написать свой сценарий. Будем идти по порядку и займёмся первым, т.е. "Если нужно получить свойства позиции только на текущем символе".

Всё просто. Сначала нужно проверить, есть ли позиция на текущем символе. Для этой проверки в MQL5 есть функция PositionSelect(). Ей в качестве единственного параметра передаётся имя символа. Чтобы передать имя текущего символа? нужно воспользоваться или функцией Symbol() или предопределённой переменной _Symbol, которая уже содержит имя текущего символа. Функция PositionSelect() возвратит положительный результат, если позиция на этом символе есть, или отрицательный результат, если позиции нет или была ошибка.

Код с подробными комментариями для первого варианта можно посмотреть ниже:

//---
      // Если есть позиция, то...
      if(PositionSelect(_Symbol))
        {
         // ...получим её свойства
         GetPositionProperties();
         //---
         // Откроем диалоговое окно и выведем в нём все полученные данные
         MessageBox("Symbol        : "+pos_symbol+"\n"+
                    "Comment       : "+pos_comment+"\n"+
                    "Magic Number  : "+IntegerToString(pos_magic)+"\n"+
                    "Price Open    : "+DoubleToString(pos_price,_Digits)+"\n"+
                    "Current Price : "+DoubleToString(pos_cprice,_Digits)+"\n"+
                    "Stop Loss     : "+DoubleToString(pos_sl,_Digits)+"\n"+
                    "Take Profit   : "+DoubleToString(pos_tp,_Digits)+"\n"+
                    "Type          : "+PositionTypeToString(pos_type)+"\n"+
                    "Volume        : "+DoubleToString(pos_volume,2)+"\n"+
                    "Commission    : "+DoubleToString(pos_commission,2)+"\n"+
                    "Swap          : "+DoubleToString(pos_swap,2)+"\n"+
                    "Profit        : "+DoubleToString(pos_profit,2)+"\n"+
                    "Time          : "+TimeToString(pos_time)+"\n"+
                    "Identifier    : "+IntegerToString(pos_id)+"",
                    //---
                    "Message Box",MB_ICONASTERISK);
         //---
         return;
        }
      // Если же позиции нет или была ошибка, сообщим об этом
      else
        {
         err=GetLastError(); // Получим код последней зафиксированной ошибки
         //---
         if(err>0) // Если ошибка есть
           {
            // Выведем сообщение об этом
            MessageBox("Ошибка ("+IntegerToString(err)+") при выборе позиции ("+_Symbol+") !\n\n"+
                       "Возможно, что на этом символе нет позиции. Если это не так, попробуйте ещё раз.",
                       "Error",
                       MB_ICONWARNING);
            //---
            return; // Выйдем из функции
           }
        }
      //---

В коде выше можно увидеть ещё две пользовательские функции GetPositionProperties() и PositionTypeToString(). Так как придется получать свойства не в одном месте программы, то имеет смысл создать отдельную функцию, чтобы уменьшить объем кода и, тем самым, улучшить его восприятие. Код этой функции ниже. Не забывайте также посмотреть в Справке дополнительную информацию об используемых внутри GetPositionProperties() функциях и идентификаторах языка MQL5.

//+------------------------------------------------------------------+
//| ПОЛУЧАЕТ СВОЙСТВА СИМВОЛА                                        |
//+------------------------------------------------------------------+
void GetPositionProperties()
  {
   pos_symbol     =PositionGetString(POSITION_SYMBOL);
   pos_comment    =PositionGetString(POSITION_COMMENT);
   pos_magic      =PositionGetInteger(POSITION_MAGIC);
   pos_price      =PositionGetDouble(POSITION_PRICE_OPEN);
   pos_cprice     =PositionGetDouble(POSITION_PRICE_CURRENT);
   pos_sl         =PositionGetDouble(POSITION_SL);
   pos_tp         =PositionGetDouble(POSITION_TP);
   pos_type       =(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
   pos_volume     =PositionGetDouble(POSITION_VOLUME);
   pos_commission =PositionGetDouble(POSITION_COMMISSION);
   pos_swap       =PositionGetDouble(POSITION_SWAP);
   pos_profit     =PositionGetDouble(POSITION_PROFIT);
   pos_time       =(datetime)PositionGetInteger(POSITION_TIME);
   pos_id         =PositionGetInteger(POSITION_IDENTIFIER);
  }

А пользовательская функция PositionTypeToString() конвертирует тип позиции, который возвращается в виде целого числа, в строковой читаемый вид. Смотрим код ниже:

//+------------------------------------------------------------------+
//| ПРЕОБРАЗУЕТ ТИП ПОЗИЦИИ В СТРОКУ                                 |
//+------------------------------------------------------------------+
string PositionTypeToString(int position_type)
  {
   string str="";
//---
   if(position_type==0) { str="buy";  }
   if(position_type==1) { str="sell"; }
//---
   return(str);
  }

Для первого варианта, когда нужно показать свойства позиции только на текущем символе, код готов. Его даже можно уже прямо сейчас протестировать, если вы делали все сразу, читая статью. Откройте позицию в торговом терминале MetaTrader 5, используя штатные средства. Для этого нажмите клавишу F9: откроется окно Ордер, в котором есть все необходимые опции для настройки свойств позиции перед её открытием:

Рис. 1. Окно "Ордер" в клиентском терминале MetaTrader 5.

Рис. 1. Окно "Ордер" в клиентском терминале MetaTrader 5.

Настройте все свойства и нажмите Sell или Buy, а после запустите скрипт двойным щелчком или перетащив на график. Откроется окно скрипта. По умолчанию нужное значение (Текущий символ) параметра Режим уже установлено. Нажатие кнопки OK откроет диалоговое окно, в котором будут показаны все свойства позиции на текущем символе:

Рис. 2. Диалоговое окно со свойствами текущего символа.

Рис. 2. Диалоговое окно со свойствами текущего символа.

Если же позиции на текущем символе нет, то выйдет диалоговое окно с предупреждением об этом:

Рис. 3. Диалоговое окно с предупреждением.

Рис. 3. Диалоговое окно с предупреждением.

Всё работает так, как было задумано и реализовано в коде.

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

//---
      int digits=0; // Количество десятичных знаков символа после запятой
      int mb_res=-1; // Переменная с результатом выбора в диалоговом окне
      int pos_total=PositionsTotal(); // Количество открытых позиций в терминале
      //---
      // Последовательно в цикле просмотрим свойства всех позиций
      for(int i=0; i<pos_total; i++)
        {
         ResetLastError(); // Обнулим последнюю ошибку
         //---
         pos_symbol=PositionGetSymbol(i); // Получим имя символа
         digits=(int)SymbolInfoInteger(pos_symbol,SYMBOL_DIGITS); // Получим количество знаков в цене
         //---
         // Если позиция по этому символу есть, то...
         if(PositionSelect(pos_symbol))
           {
            // ...получим её свойства
            GetPositionProperties();
            //---
            // Откроем диалоговое окно и выведем в нём все полученные свойства позиции
            mb_res=MessageBox("Total Positions/Current: "+IntegerToString(pos_total)+"/"+IntegerToString(i+1)+"\n"+
                              "---------------------------------\n"+
                              "Symbol: "        +pos_symbol+"\n"+
                              "Comment: "       +pos_comment+"\n"+
                              "Magic Number: "  +IntegerToString(pos_magic)+"\n"+
                              "Price Open: "    +DoubleToString(pos_price,digits)+"\n"+
                              "Current Price: " +DoubleToString(pos_cprice,digits)+"\n"+
                              "Stop Loss: "     +DoubleToString(pos_sl,digits)+"\n"+
                              "Take Profit: "   +DoubleToString(pos_tp,digits)+"\n"+
                              "Type: "          +PositionTypeToString(pos_type)+"\n"+
                              "Volume: "        +DoubleToString(pos_volume,2)+"\n"+
                              "Commission: "    +DoubleToString(pos_commission,2)+"\n"+
                              "Swap: "          +DoubleToString(pos_swap,2)+"\n"+
                              "Profit: "        +DoubleToString(pos_profit,2)+"\n"+
                              "Time: "          +TimeToString(pos_time)+"\n"+
                              "Identifier: "    +IntegerToString(pos_id)+"",
                              //---
                              "Message Box",MB_CANCELTRYCONTINUE|MB_ICONASTERISK);
            //---
            if(mb_res==IDCANCEL) // Если нажали кнопку Отмена или Закрыть
              { Print("Программа ("+SCRIPT_NAME+") была прервана пользователем!"); return; } // Выйдем из функции
            //---
            // Если нажали кнопку Повторить   
            if(mb_res==IDTRYAGAIN) { i--; } // Отмотаем счётчик назад для повтора
           }
         else // Если же позиции нет или была ошибка, сообщим об этом
           {
            err=GetLastError(); // Получим код последней зафиксированной ошибки
            //---
            if(err>0) // Если ошибка есть
              {
               // Выведем сообщение об этом
               MessageBox("Ошибка ("+IntegerToString(err)+") при выборе позиции ("+pos_symbol+") !\n\n"+
                          "Возможно, на этом символе нет позиции. Если это не так, попробуйте ещё раз.",
                          "Error",
                          MB_ICONWARNING);
              }
           }
        }
      //---

Осталось протестировать и этот вариант. Например, откроем позиции на двух символах (AUDUSD и EURUSD). Запустив скрипт, выбрав во внешних параметрах в выпадающем списке вариант Все символы и нажав кнопку OK, откроется вот такое диалоговое окно:

Рис. 4. Диалоговое окно со свойствами позиции для второго варианта.

Рис. 4. Диалоговое окно со свойствами позиции для второго варианта.


Заключение

Как видно на рисунке выше, у диалогового окна три кнопки. Если нажать кнопку Повторить, счётчик цикла будет отмотан назад и свойства позиции символа которого уже отображены в текущий момент будут обновлены в диалоговом окне. Если же нажать кнопку Продолжить, программа перейдёт к следующему символу. Кнопка Отмена завершает работу программы.

Также можно заметить, что в первой строке над списком свойств позиции отображается общее количество открытых позиций (Total Positions) и текущий номер счётчика позиций (Current).

На этом всё. Ниже можно скачать файл с исходным кодом, который нужно скомпилировать в редакторе MetaEditor.