English 中文 Español Deutsch 日本語 Português
Рецепты MQL5 – Получаем свойства открытой хеджевой позиции

Рецепты MQL5 – Получаем свойства открытой хеджевой позиции

MetaTrader 5Примеры | 10 сентября 2018, 11:30
5 947 8
Denis Kirichenko
Denis Kirichenko

Введение

Относительно недавно в торговом терминале MetaTrader 5 появилась возможность открывать разнонаправленные ордера. Такая система учёта ордеров называется «хеджинг». Наличие этой ордерной системы позволяет легко переносить торговые алгоритмы из MetaTrader 4 в пятую версию терминала, пользуясь всеми преимуществами последнего. Более подробно о хеджинге в MetaTrader 5 можно узнать в материале статьи "В MetaTrader 5 добавлена хеджинговая система учёта позиций".

В данной статье речь пойдёт о свойствах совокупной позиции, работа с которой идёт в системе «хеджинг».


1. Хеджевая позиция, типы

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

Для классификации совокупной позиции можно использовать несколько способов. Пожалуй, самым популярным является критерий, который различает позиции по типу рыночных ордеров, образующих ту или иную совокупную позицию. Итак, какими ордерами может быть заполнена такая позиция? Ниже в Таблице 1 представлены различные комбинации.

Тип Описание
1 Hedge buy Только покупки
2 Hedge netting buy "Чистая" покупка
3 Hedge sell Только продажи
4 Hedge netting sell "Чистая" продажа
5 Hedge locked Лок (полный хедж)

Таблица 1. Типы хеджа

Кратко поясним указанные разновидности. Если в совокупной позиции есть ордера (в терминах MetaTrader 4) только на покупку или только на продажу, то такую позицию будем считать либо hedge buy, либо hedge sell. Если в позиции есть смешанные ордера (как покупки, так и продажи), то будем смотреть, каких ордеров больше. Если больше ордеров на покупку, то позиция будет hedge netting buy. А если больше ордеров на продажу - hedge netting sell. Причём, если быть более точным, нужно говорить не о количестве ордеров, а об их объёмах. Допустим, что в позиции есть 1 ордер на покупку объёмом 1,25 лота и два ордера на продажу по 0,5 и 0,6 лота соответственно. В итоге позиция будет чистой позицией на покупку (hedge netting buy) в 0,15 лота:

1,25 – (0,5 + 0,6) = 0,15.

Особым видом смешанной позиции является лок, в котором покупки и продажи уравновешивают друг друга по торговому объёму.

Описанные виды хеджа формализуем в следующем перечислении:

//+------------------------------------------------------------------+
//| Hedge type                                                       |
//+------------------------------------------------------------------+
enum ENUM_HEDGE_TYPE
  {
   HEDGE_BUY=0,          // buy
   HEDGE_SELL=1,         // sell   
   HEDGE_NETTING_BUY=2,  // netting buy   
   HEDGE_NETTING_SELL=3, // netting sell
   HEDGE_LOCKED=4,       // lock
  };


Если обратиться к системе "неттинг", то там позиция, совокупная по определению, может относится только к одному из двух типов - либо buy, либо sell. Идентификатором служит одно из значений перечисления ENUM_POSITION_TYPE:

1) POSITION_TYPE_BUY;

2) POSITION_TYPE_SELL.

В системе же "хеджинг" получим 5 типов совокупной позиции.

В следующем разделе создадим класс для обработки свойств хеджевой позиции.


2. Класс CHedgePositionInfo

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

Воспользуемся ООП-средствами и создадим класс CHedgePositionInfo:

//+------------------------------------------------------------------+
//| Class CHedgePositionInfo                                         |
//| Purpose: Class for access to a hedge position info.              |  
//|              Derives from class CObject.                         |
//+------------------------------------------------------------------+
class CHedgePositionInfo : public CObject
  {
   //--- === Data members === --- 
private:
   ENUM_HEDGE_TYPE   m_type;
   double            m_volume;
   double            m_price;
   double            m_stop_loss;
   double            m_take_profit;
   ulong             m_magic;
   //--- objects
   CArrayLong        m_tickets;
   CSymbolInfo       m_symbol;
   CPositionInfo     m_pos_info;

   //--- === Methods === --- 
public:
   //--- constructor/destructor
   void              CHedgePositionInfo(void){};
   void             ~CHedgePositionInfo(void){};
   //--- initialization
   bool              Init(const string _symbol,const ulong _magic=0);
   //--- get methods
   CSymbolInfo      *Symbol(void)       {return GetPointer(m_symbol);};
   CArrayLong       *HedgeTickets(void) {return GetPointer(m_tickets);};
   CPositionInfo    *PositionInfo(void) {return GetPointer(m_pos_info);};
   ulong             Magic(void) const  {return m_magic;};
   //--- fast access methods to the integer hedge properties
   datetime          Time(void);
   ulong             TimeMsc(void);
   datetime          TimeUpdate(void);
   ulong             TimeUpdateMsc(void);
   ENUM_HEDGE_TYPE   HedgeType(void);
   //--- fast access methods to the double hedge properties
   double            Volume(double &_buy_volume,double &_sell_volume);
   double            PriceOpen(const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL);
   double            StopLoss(const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL);
   double            TakeProfit(const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL);
   double            PriceCurrent(const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL);
   double            Commission(const bool _full=false);
   double            Swap(void);
   double            Profit(void);
   double            Margin(void);
   //--- fast access methods to the string hedge properties
   string            TypeDescription(void);
   //--- info methods
   string            FormatType(string &_str,const uint _type) const;
   //--- select
   bool              Select(void);
   //--- state
   void              StoreState(void);
   bool              CheckState(void);

private:
   //--- calculation methods
   bool              AveragePrice(
                                  const SPositionParams &_pos_params,
                                  double &_avg_pr,
                                  double &_base_volume,
                                  double &_quote_volume
                                  );
   int               CheckLoadHistory(ENUM_TIMEFRAMES period,datetime start_date);
  };
//+------------------------------------------------------------------+


Несколько слов о членах-данные класса.

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

Во-вторых, можно в качестве фильтра нужных ордеров задавать магик (m_magic). Таким образом, можно создать позицию, которую обслуживает какой-то один торговый советник. Тогда на одном символе можно создавать несколько хеджей.

Ещё есть динамический массив для учёта ордеров (m_tickets). В него будут попадать тикеты ордеров хедж-позиции.

Функции по получению свойств какой-то одной выбранной позиции (рыночного ордера в терминах MetaTrader 4) возложены на экземпляр класса CPositionInfo (m_pos_info).

Остальные свойства хеджа нужны для оценки его состояния:

  • тип (m_type);
  • объём (m_volume);
  • цена открытия (m_price);
  • цена стоп-лосса (m_stop_loss);
  • цена тэйк-профита (m_take_profit).

Стоит отметить, что за основу конструирования класса была взята логика класса CPositionInfo. И это вполне естественно. Поэтому в новом классе есть методы, возвращающие целочисленные свойства, double-свойства и пр. Но конечно есть и методы, которые будут специфическими.


2.1 Метод инициализации

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

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
bool CHedgePositionInfo::Init(const string _symbol,const ulong _magic=0)
  {
//--- account margin mode
   ENUM_ACCOUNT_MARGIN_MODE margin_mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   if(margin_mode!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
     {
      Print(__FUNCTION__+": no retail hedging!");
      return false;
     }
   if(!m_symbol.Name(_symbol))
     {
      Print(__FUNCTION__+": a symbol not selected!");
      return false;
     }
   ENUM_SYMBOL_CALC_MODE  symbol_calc_mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(_symbol,SYMBOL_TRADE_CALC_MODE);
   if(symbol_calc_mode!=SYMBOL_CALC_MODE_FOREX)
     {
      Print(__FUNCTION__+": only for Forex mode!");
      return false;
     }
   m_magic=_magic;
//---
   return true;
  }
//+------------------------------------------------------------------+

Данный метод является обязательным для последующего использования возможностей хеджевого класса. Отмечу, что в методе проверяется режим расчета маржи. Если он не соответствует "хеджингу", то метод вернёт ложь. Также проверяется способ вычисления стоимости контракта. Работать будем только с Форекс-контрактами.


2.2 Целочисленные свойства

Доступ к целочисленным свойствам осуществляется посредством методов:

  1. datetime                   Time(void);
  2. ulong                        TimeMsc(void);
  3. datetime                   TimeUpdate(void);
  4. ulong                        TimeUpdateMsc(void);
  5. ENUM_HEDGE_TYPE   HedgeType(void).

Посмотрим, к примеру, на код метода CHedgePositionInfo::Time():

//+------------------------------------------------------------------+
//| Get  the hedge open time                                         |
//+------------------------------------------------------------------+
datetime CHedgePositionInfo::Time(void)
  {
   datetime hedge_time=WRONG_VALUE;
   int hedge_pos_num=m_tickets.Total();
//--- if any positions
   if(hedge_pos_num>0)
     {
      //--- find the first opened position
      for(int pos_idx=0;pos_idx<hedge_pos_num;pos_idx++)
        {
         ulong curr_pos_ticket=m_tickets.At(pos_idx);
         if(curr_pos_ticket<LONG_MAX)
            if(m_pos_info.SelectByTicket(curr_pos_ticket))
              {
               datetime curr_pos_time=m_pos_info.Time();
               if(curr_pos_time>0)
                 {
                  if(hedge_time==0)
                     hedge_time=curr_pos_time;
                  else
                    {
                     if(curr_pos_time<hedge_time)
                        hedge_time=curr_pos_time;
                    }
                 }
              }
        }
     }
//---
   return hedge_time;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Get  the hedge update time                                       |
//+------------------------------------------------------------------+
datetime CHedgePositionInfo::TimeUpdate(void)
  {
   datetime hedge_time_update=0;
   int hedge_pos_num=m_tickets.Total();
//--- if any positions
   if(hedge_pos_num>0)
     {
      //--- find the first opened position
      for(int pos_idx=0;pos_idx<hedge_pos_num;pos_idx++)
        {
         ulong curr_pos_ticket=m_tickets.At(pos_idx);
         if(curr_pos_ticket<LONG_MAX)
            if(m_pos_info.SelectByTicket(curr_pos_ticket))
              {
               //--- get the current position update time
               datetime curr_pos_time_update=m_pos_info.TimeUpdate();
               if(curr_pos_time_update>0)
                  if(curr_pos_time_update>hedge_time_update)
                     hedge_time_update=curr_pos_time_update;
              }
        }
     }
//---
   return hedge_time_update;
  }
//+------------------------------------------------------------------+

Метод определения типа хеджа закодирован следующим образом:

//+------------------------------------------------------------------+
//| Get  the hedge type                                              |
//+------------------------------------------------------------------+
ENUM_HEDGE_TYPE CHedgePositionInfo::HedgeType(void)
  {
   ENUM_HEDGE_TYPE curr_hedge_type=WRONG_VALUE;
   int hedge_pos_num=m_tickets.Total();
//--- if any positions
   if(hedge_pos_num>0)
     {
      //--- get the volumes      
      double total_vol,buy_volume,sell_volume;
      buy_volume=sell_volume=0.;
      total_vol=this.Volume(buy_volume,sell_volume);
      //--- define a hedge type
      if(buy_volume>0. && sell_volume>0.)
        {
         if(buy_volume>sell_volume)
            curr_hedge_type=HEDGE_NETTING_BUY;
         else if(buy_volume<sell_volume)
            curr_hedge_type=HEDGE_NETTING_SELL;
         else
            curr_hedge_type=HEDGE_LOCKED;
        }
      else if(buy_volume>0. && sell_volume==0.)
         curr_hedge_type=HEDGE_BUY;
      else if(buy_volume==0. && sell_volume>0.)
         curr_hedge_type=HEDGE_SELL;
     }
//---
   return curr_hedge_type;
  };
//+------------------------------------------------------------------+

Тип хеджа зависит от разницы объёмов покупок и продаж. Сначала проверяем, действительно ли хедж является таковым в узком смысле. Если объёмы покупок и продаж равны, то хедж полный. Если есть неравенство, то мы имеем дело с частичным хеджем.

Затем проверяем, не представлен ли хедж либо только покупками, либо только продажами.


2.3 Double-свойства

Доступ к double-свойствам осуществляется посредством методов:

  1.    double            Volume(double &_buy_volume,double &_sell_volume);
  2.    double            PriceOpen(void);
  3.    double            StopLoss(void);
  4.    double            TakeProfit(void);
  5.    double            PriceCurrent(void);
  6.    double            Commission(void);
  7.    double            Swap(void);
  8.    double            Profit(void);
  9.    double            Margin(void).

Легко заметить, что метод определения объёма хеджа имеет параметры в виде ссылок. Такая реализация позволяет сразу получить как величину объёма самого хеджа, так и его составляющих (покупок и продаж).

//+------------------------------------------------------------------+
//| Get  the hedge volume                                            |
//+------------------------------------------------------------------+
double CHedgePositionInfo::Volume(double &_buy_volume,double &_sell_volume)
  {
   double total_vol=0.;
   int hedge_pos_num=m_tickets.Total();
//--- if any positions
   if(hedge_pos_num>0)
     {
      _buy_volume=_sell_volume=0.;
      //--- get the buy\sell volumes      
      for(int pos_idx=0;pos_idx<hedge_pos_num;pos_idx++)
        {
         ulong curr_pos_ticket=m_tickets.At(pos_idx);
         if(curr_pos_ticket<LONG_MAX)
            if(m_pos_info.SelectByTicket(curr_pos_ticket))
              {
               ENUM_POSITION_TYPE curr_pos_type=m_pos_info.PositionType();
               double curr_pos_vol=m_pos_info.Volume();
               if(curr_pos_vol>0.)
                 {
                  //--- for a buy position
                  if(curr_pos_type==POSITION_TYPE_BUY)
                     _buy_volume+=curr_pos_vol;
                  //--- else for a sell position
                  else if(curr_pos_type==POSITION_TYPE_SELL)
                     _sell_volume+=curr_pos_vol;
                 }
              }
        }
      total_vol=_buy_volume-_sell_volume;
     }
//---
   return total_vol;
  }
//+------------------------------------------------------------------+

Для работы с ценовыми свойствами был создан вспомогательный метод CHedgePositionInfo::AveragePrice(). Приведу блок кода, где он считает среднюю цену хеджа в зависимости от типа ценового уровня:

//--- if the hedge volumes calculated
if(hedge_base_volume!=0. && hedge_quote_volume!=0.)
  {
   _avg_pr=fabs(hedge_quote_volume/hedge_base_volume);
   _base_volume=hedge_base_volume;
   _quote_volume=hedge_quote_volume;
   return true;
  }

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

Позже посмотрим на конкретный расчёт при работе с примером.

Методы получения свопа и профита суммируют соответствующие показатели каждой позиции хеджа, а затем возвращают итоговое значение. Метод получения комиссии анализирует сделки, которые участвовали в открытии позиции. Причём можно выбрать через параметр, как рассчитывать размер комиссии. Если хотим получить его только по сделкам входа, то оставляем параметр по умолчанию. А если нужно подсчитать комиссию за вход и выход, то параметр должен быть равен true. Отмечу, что последний способ носит примерный характер по нескольким причинам. Во-первых, мы можем закрыть выбранные позиции встречными. Тогда появятся сделки типа DEAL_ENTRY_OUT_BY. А за них комиссия не взимается. А во-вторых, если валюта счёта не совпадает с базовой валютой, то при изменении курсов стоимость входа и выхода могут отличаться.

//+------------------------------------------------------------------+
//| Get  the hedge commission                                        |
//+------------------------------------------------------------------+
double CHedgePositionInfo::Commission(const bool _full=false)
  {
   double hedge_commission=0.;
   int hedge_pos_num=m_tickets.Total();
//--- if any positions
   if(hedge_pos_num>0)
      for(int pos_idx=0;pos_idx<hedge_pos_num;pos_idx++)
        {
         ulong curr_pos_ticket=m_tickets.At(pos_idx);
         if(curr_pos_ticket<LONG_MAX)
            if(m_pos_info.SelectByTicket(curr_pos_ticket))
              {
               long curr_pos_id=m_pos_info.Identifier();
               if(curr_pos_id>0)
                  //--- retrieve the history of deals associated with the selected position 
                  if(HistorySelectByPosition(curr_pos_id))
                    {
                     CDealInfo curr_deal;
                     int deals_num=HistoryDealsTotal();
                     for(int deal_idx=0;deal_idx<deals_num;deal_idx++)
                        if(curr_deal.SelectByIndex(deal_idx))
                          {
                           ENUM_DEAL_ENTRY curr_deal_entry=curr_deal.Entry();
                           if(curr_deal_entry==DEAL_ENTRY_IN)
                             {
                              double curr_deal_commission=NormalizeDouble(curr_deal.Commission(),2);
                              if(curr_deal_commission!=0.)
                                {
                                 double fac=1.;
                                 if(_full) fac=2.;
                                 hedge_commission+=(fac*curr_deal_commission);
                                }
                             }
                          }

                    }
              }
        }
//---
   return hedge_commission;
  }
//+------------------------------------------------------------------+

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


2.3.1 Залог для хеджевой позиции

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

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

  1. Для неперекрытого объема;
  2. Для перекрытого объема (если указан размер хеджированной маржи);
  3. Для отложенных ордеров.

Маржа в данной статье рассчитывается для модели Retail Forex, Futures. Расчёт маржи для отложенных ордеров не рассматривается.

Для полноценного расчёта маржи для хеджевой позиции нужна будет информация по следующим параметрам:

  1. Валюта депозита. Как правило, счёта номинированы в долларах США (USD), евро (EUR), фунтах (GBP), франках (CHF).
  2. Валюта маржи. Как правило, это базовая валюта символа. Например, для пары EURUSD такой будет евро (EUR), а для кросса AUDNZD — австралийский доллар (AUD).
  3. Размер плеча.

Кроме того замечу, что в строке баланса на вкладке «Торговля» терминала значение маржи будет указано в валюте депозита. Поэтому результатом расчёта должно стать значение маржи именно в валюте депозита.

А так как валюта маржи может не совпадать с валютой депозита, то есть несколько вариантов алгоритма расчёта:

  1. Когда валюта депозита присутствует в символе хеджевой позиции в виде базовой валюты. Допустим, торгуете USDCHF на долларовом счёте.
  2. Когда валюта депозита присутствует в символе хеджевой позиции в виде котируемой валюты. Допустим, торгуете EUR USD на долларовом счёте.
  3. Когда валюты депозита нет в символе хеджевой позиции. Допустим, торгуете AUDNZD на долларовом счёте.

Первый вариант будет самым простым для расчёта, а последний – самым сложным. Займёмся бухгалтерией и рассмотрим примеры для каждого варианта.

Первый вариант

Пусть есть долларовый счёт (депозит), на котором открыто 5 позиций по символу USDCHF (Рис.1).


Рыночные позиции по паре USDCHF

Рис.1 Рыночные позиции по паре USDCHF


Базовые параметры:

Валюта счёта - USD.

Валюта маржи - USD.

Размер плеча - 1:100.

Есть 3 позиции на покупку. Общий объём позиций равен 5,55 лота, что в стоимостном выражении составляет $555 000. И ещё есть 2 позиции на продажу. Общий объём позиций равен 7,5 лота, что в стоимостном выражении составляет $750 000.

А) расчёт для неперекрытого объёма

Неперекрытый объём равен 1,95 лота или $195 000. Относится он к продажам, т.к. продали больше чем купили. Хотя в данном случае это совсем неважно – покупки или продажи, ведь не нужно расчитывать средневзвешенную цену.

Маржа от этой суммы берётся с учётом плеча:

$195 000 / 100 = $1 950.

Б) расчёт для перекрытого объёма

Перекрытый объём равен 5,55 лота или $555 000.

Маржа от этой суммы берётся с учётом плеча:

$555 000 / 100 = $5 550.

Тогда валовая маржа рассчитывается как сумма маржи для неперекрытого объёма и маржи для перекрытого объёма:

$1 950 + $5 550 = $7 500.

Именно это значение мы и видим в торговом терминале для показателя "Маржа".

Второй вариант

Пусть есть долларовый счёт (депозит), на котором открыто 5 позиций по символу EURUSD (Рис.2).


Рыночные позиции по паре EURUSD

Рис.2 Рыночные позиции по паре EURUSD

Базовые параметры:

  • Валюта счёта - USD.
  • Валюта маржи - EUR.
  • Размер плеча - 1:300.

Есть 3 позиции на покупку. Общий объём позиций равен 5,55 лота, что в стоимостном выражении составляет €555 000 или $645 617.20. Средневзвешенная цена покупок равна $1.163274.

Есть 2 позиции на продажу. Общий объём позиций равен 7,50 лота, что в стоимостном выражении составляет €750 000 или $872 409. Средневзвешенная цена продаж равна $1.163212.

Все позиции представлены в Таблице 2.

Type Volume Price Value, $
buy 1.75 1.16329 203 575.75
buy 2.55 1.16329 296 638.95
buy 1.25 1.16322 145 402.50
sell 3.00 1.16323 348 969.00
sell 4.50 1.16320 523 440.00
Total 13.05 1.1632385 1 518 026.20

Таблица.2 Рыночные позиции по паре EURUSD


Всего есть 5 позиций. Общий объём позиций равен 13,05 лота, что в стоимостном выражении составляет €1 305 000 или $1 518 026.20. Средневзвешенная цена позиций равна $1.16324.

А) расчёт для неперекрытого объёма

Неперекрытый объём равен 1,95 лота или €195 000. Относится он к продажам, т.к. продали больше чем купили. Поэтому для определения стоимости этого объёма берём средневзвешенную цену продаж:

$1.163212 * €195 000 = $226 826.34.

Маржа от этой суммы берётся с учётом плеча:

$226 826.34 / 300 = $756.09.

Б) расчёт для перекрытого объёма

Перекрытый объём равен 5,55 лота или €555 000. Для определения стоимости этого объёма берём средневзвешенную цену всех позиций:

$1.1632385 * €555 000 = $645 597.35.

Маржа от этой суммы берётся с учётом плеча:

$645 597.35 / 300 = $2 151,99.

Тогда, по идее, вся валовая маржа должна составить:

$756.09 + $2 151.99 = $2 908.08.

Однако в терминале мы видим значение $1 832.08.

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

Стоимость перекрытого объёма = $1.1632385 * €555 000 / (100 000 / 50 000) = $322 798,67.

Маржа для перекрытого объёма = $322 798,67 / 300 = $1 076.00.

В сумме: $756.09 + $1 076.00 = $1 832.08. Именно это значение соответствует терминальному.

Третий вариант

Пусть есть долларовый счёт (депозит), на котором открыто 5 позиций по символу AUDNZD (Рис.3).


Рыночные позиции по кроссу AUDNZD

Рис.3 Рыночные позиции по кроссу AUDNZD

Базовые параметры:

  • Валюта счёта - USD.
  • Валюта маржи - AUD.
  • Размер плеча - 1:300.

Есть 3 позиции на покупку. Общий объём позиций равен 5,55 лота, что в стоимостном выражении составляет A$555 000 или $400 442.35. Средневзвешенная цена покупок равна $0.7215178.

Есть 2 позиции на продажу. Общий объём позиций равен 7,50 лота, что в стоимостном выражении составляет A$750 000 или $541 035.00. Средневзвешенная цена продаж равна $0.72138.

Все позиции представлены в Таблице 3.

Type Volume Price Value, $
buy 1.75 0.72152 126 266.00
buy 2.55 0.72152 183 987.60
buy 1.25 0.72151 90 188.75
sell 3.00 0.72144 216 432.00
sell 4.50 0.72134 324 603.00
Total 13.05 0.72144 941 477.35

Таблица.3 Рыночные позиции по кроссу AUDNZD


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

Всего есть 5 позиций. Общий объём позиций равен 13,05 лота, что в стоимостном выражении составляет A$1 305 000 или $941 477.35. Средневзвешенная цена позиций равна $0.72144.

А) расчёт для неперекрытого объёма

Неперекрытый объём равен 1,95 лота или A$195 000. Относится он к продажам, т.к. продали больше чем купили. Поэтому для определения стоимости этого объёма берём средневзвешенную цену продаж:

$0.72138 * A$195 000 = $140 669.10.

Маржа от этой суммы берётся с учётом плеча:

$140 669.10 / 300 = $468.90.

Б) расчёт для перекрытого объёма

Перекрытый объём равен 5,55 лота или A$555 000. Для определения стоимости этого объёма берём средневзвешенную цену всех позиций с учётом значения параметра "Хеджированная маржа":

$0.72144 * A$555 000 / (100 000 / 50 000) = $200 199.21.

Маржа от этой суммы берётся с учётом плеча:

$200 199.21/ 300 = $667.33.

В сумме: $468.90 + $667.33 = $1 136.23. Проверяем согласно Рис.3: оно совпадает с терминальным.


2.4 Прочие свойства

В классе ещё есть методы, которые работают с состоянием хеджа: StoreState() и CheckState(). Так же как и с обычной позицией, оно определяется значениями типа, объёма, цены открытия, цены стоп-лосса и тэйк-профита.

Единственный метод текстового свойства TypeDescription() возвращает тип хеджа в виде строки.

Особо стоит отметить метод выбора хеджевой позиции Select(). Код метода представлен ниже:

//+------------------------------------------------------------------+
//| Selects hedge positions                                          |
//+------------------------------------------------------------------+
bool CHedgePositionInfo::Select(void)
  {
   string hedge_symbol=m_symbol.Name();
//--- clear all positions 
   m_tickets.Shutdown();
//--- collect positions
   int pos_num=PositionsTotal();
   for(int pos_idx=0;pos_idx<pos_num;pos_idx++)
      if(m_pos_info.SelectByIndex(pos_idx))
        {
         string curr_pos_symbol=m_pos_info.Symbol();
         //--- select by symbol
         if(!StringCompare(hedge_symbol,curr_pos_symbol))
           {
            //--- if to select by magic
            bool is_the_same_magic=true;
            if(m_magic>0)
              {
               long curr_pos_magic=m_pos_info.Magic();
               if(m_magic!=curr_pos_magic)
                  is_the_same_magic=false;
              }
            if(is_the_same_magic)
              {
               ulong curr_pos_ticket=m_pos_info.Ticket();
               if(curr_pos_ticket>0)
                  if(!m_tickets.Add(curr_pos_ticket))
                    {
                     PrintFormat(__FUNCTION__+": failed to add #%d ticket!",curr_pos_ticket);
                     return false;
                    }
              }
           }
        }
//---
   return m_tickets.Total()>0;
  }
//+------------------------------------------------------------------+

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


3. Примеры

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


3.1 Тестовый скрипт

Для учебных целей был создан скрипт Test_hedge_properties.mq5, который выводит в журнал на вкладке "Эксперты" информацию о свойствах хеджа.

В первом варианте расчёта залога у нас было 5 позиций по символу USDCHF (Рис.1). Запустим скрипт и получим в журнале такую информацию:

2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       ---== Hedge properties==---
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Symbol: USDCHF
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Positions total = 5
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)               1) #293972991 buy 1.75 USDCHF 0.97160000
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)               2) #293974150 buy 2.55 USDCHF 0.97142000
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)               3) #293974889 sell 3.00 USDCHF 0.97157000
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)               4) #293975329 sell 4.50 USDCHF 0.97164000
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)               5) #293976289 buy 1.25 USDCHF 0.97205000
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Magic: 0
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Time: 2018.08.29 17:15:44
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Time in msc: 1535562944628
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Update time: 2018.08.29 17:20:35
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Update time in msc: 1535563235034
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Type: HEDGE_NETTING_SELL
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Type description: hedge netting sell
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Volume: -1.95
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Buy volume: 5.55
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Sell volume: 7.50
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Open price: 0.97159
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Sl-price: -1.00000
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Tp-price: -1.00000
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Current price: 0.96956
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Commission: 0.00
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Swap: -35.79
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Profit: 409.77
2018.09.03 18:51:37.078 Test_hedge_properties (AUDNZD,H1)       Margin: 7500.00

Во втором варианте обрабатывали свойства позиций по EURUSD (Рис.2). После запуска скрипта получили такую информацию:

2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       ---== Hedge properties==---
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Symbol: EURUSD
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Positions total = 5
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)               1) #119213986 buy 1.75 EURUSD 1.16329000
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)               2) #119214003 buy 2.55 EURUSD 1.16329000
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)               3) #119214004 buy 1.25 EURUSD 1.16322000
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)               4) #119214011 sell 3.00 EURUSD 1.16323000
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)               5) #119214021 sell 4.50 EURUSD 1.16320000
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Magic: 0
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Time: 2018.08.31 16:38:10
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Time in msc: 1535733490531
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Update time: 2018.08.31 16:38:49
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Update time in msc: 1535733529678
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Type: HEDGE_NETTING_SELL
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Type description: hedge netting sell
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Volume: -1.95
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Buy volume: 5.55
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Sell volume: 7.50
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Open price: 1.16303
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Sl-price: -1.00000
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Tp-price: -1.00000
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Current price: 1.16198
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Commission: 0.00
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Swap: -37.20
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Profit: 206.60
2018.09.03 18:55:09.469 Test_hedge_properties (AUDNZD,H1)       Margin: 1832.08

В третьем варианте работа шла с позициями по символу AUDNZD (Рис.3). В журнал скрипт вывел такую информацию:

2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       ---== Hedge properties==---
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Symbol: AUDNZD
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Positions total = 5
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)               1) #119214062 buy 1.75 AUDNZD 1.08781000
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)               2) #119214068 buy 2.55 AUDNZD 1.08783000
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)               3) #119214071 buy 1.25 AUDNZD 1.08785000
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)               4) #119214083 sell 3.00 AUDNZD 1.08773000
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)               5) #119214092 sell 4.50 AUDNZD 1.08757000
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Magic: 0
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Time: 2018.08.31 16:39:41
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Time in msc: 1535733581113
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Update time: 2018.08.31 16:40:07
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Update time in msc: 1535733607241
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Type: HEDGE_NETTING_SELL
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Type description: hedge netting sell
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Volume: -1.95
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Buy volume: 5.55
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Sell volume: 7.50
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Open price: 1.08708
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Sl-price: -1.00000
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Tp-price: -1.00000
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Current price: 1.09314
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Commission: 0.00
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Swap: -21.06
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Profit: -779.45
2018.09.03 18:47:25.369 Test_hedge_properties (EURUSD,H1)       Margin: 1136.23

Код скрипта Test_hedge_properties.mq5 можно будет скачать из архива файлов.


3.1 Панель свойств хеджа

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

Для этих целей создадим класс CHedgeDialog, который будет являться потомком стандартного класса CAppDialog. С помощью последнего избавимся от необходимости программировать типичные задачи, например: минимизация и восстановление окна панели, обработка изменений в элементах панели и т.д.

//+------------------------------------------------------------------+
//| Class CHedgeDialog                                               |
//| Purpose: Class for displaying a hedge position info.             |  
//|              Derives from class CAppDialog.                      |
//+------------------------------------------------------------------+
class CHedgeDialog : private CAppDialog
  {
   //--- === Data members === --- 
private:
   CArrayString      m_symbols_arr;
   //--- controls
   CLabel            m_labels[FIELDS_NUM+1];
   CEdit             m_edits[FIELDS_NUM];
   CComboBox         m_combo;
   bool              m_to_refresh;
   //--- === Methods === --- 
public:
   //--- constructor/destructor
   void              CHedgeDialog(void) {};
   void             ~CHedgeDialog(void) {};
   //--- initialization
   bool              Init(void);
   void              Deinit(const int _reason);
   //--- processing
   void              OnChartEvent(const int _id,
                                  const long &_lparam,
                                  const double &_dparam,
                                  const string &_sparam);
   void              OnTradeEvent(void);
   //---
private:
   int               HedgeSymbols(void);
   void              RefreshPanel(void);
  };
//+------------------------------------------------------------------+

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

Панель свойств хеджевой позиции представлена на Рис.4.

Панель свойств хеджевой позиции

Рис.4 Панель свойств хеджевой позиции

Одним из основных методов является CHedgeDialog::RefreshPanel(). Это, так сказать, "рабочая лошадка". При необходимости он обновляет информационные поля панели. Некоторую сложность при кодировании и тестировании вызвала ситуация, когда изменялось число хеджей. В таком случае нужно изменить уникальные символы в выпадающем списке и не попасть в бесконечный цикл вызовов обработчика OnChartEvent(). Для этого был использован предел для последовательных вызовов обработчика протяжённостью в 1 сек.

//--- check the limit for refreshing
if(!m_to_refresh)
  {
   uint last_cnt=GetTickCount();
   static uint prev_cnt=0;
   uint msc_elapsed=last_cnt-prev_cnt;
   prev_cnt=last_cnt;
   if(msc_elapsed>1000)
      m_to_refresh=true;
   else
      return;
  }

Полный код советника HedgePropertiesEA.mq5 представлен в архиве файлов.


Заключение

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

Надеюсь, что данная статья вызовет интерес у тех, кто желает заняться переводом своих стратегий из MetaTrader 4 в MetaTrader 5.


Прикрепленные файлы |
Hedge.ZIP (15.3 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (8)
Denis Kirichenko
Denis Kirichenko | 10 сент. 2018 в 23:41
fxsaber:

...Нет, судил по коду. Откройте позицию, а затем закройте ее часть. По Вашему коду комиссия текущей позиции не изменится.

Более того, удвоение - это благое намерение, которое создает проблему. Дело в том, что Вам нужно, чтобы После закрытия всех позиций Balance стал равен Equity + PositionCommission. Но тогда никакого удвоения быть не должно, т.к. MT5 на открытии позиций уже в балансе отразил комиссию открытия.

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

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

А почему Вы решили, что мне это нужно? Я просто показал один из способов расчёта таких накладных расходов как комиссия. 


Ну и при подсчете комиссии очень дорого проводить нормализацию на каждом шаге цикла. Важно при Оптимизации.

Ну разве что для оптимизации. Ну так никто же не мешает внести свои изменения и учесть их в коде. 

fxsaber
fxsaber | 10 сент. 2018 в 23:53
Denis Kirichenko:

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

К примеру, есть ещё такой тип сделки как DEAL_ENTRY_OUT_BY. Там вообще нет комиссии, по крайней мере у моего брокера.

CloseBy не облагается комиссией по определению.

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

А почему Вы решили, что мне это нужно? Я просто показал один из способов расчёта таких накладных расходов как комиссия. 

Потому что на языке того же MT4 Equity = Balance + Profit + Swap + Commission. Это самая распространенная логика. Но у Вас, конечно, может быть своя.

Ну разве что для оптимизации. Ну так никто же не мешает внести свои изменения и учесть их в коде. 

Разработчики распинаются, чтобы сделать Оптимизатор быстрее. А в Статьях допускается медленный код. Выглядит странно.

Denis Kirichenko
Denis Kirichenko | 11 сент. 2018 в 11:05
fxsaber:

Потому что на языке того же MT4 Equity = Balance + Profit + Swap + Commission. Это самая распространенная логика. Но у Вас, конечно, может быть своя.

Не поверите, я так же считаю относительно эквити :-))

Сделаю выбор для метода комиссии, как считать. Внесу изменения в код...

...Разработчики распинаются, чтобы сделать Оптимизатор быстрее. А в Статьях допускается медленный код. Выглядит странно.

Как бы это сказать языком дипломатов... Вы всё ещё кипятите? - Я давно тестирую всё в облаке и не парюсь, что где-то есть лишние нормировки и нет многострочных макросов...

fxsaber
fxsaber | 11 сент. 2018 в 11:15
Denis Kirichenko:

давно тестирую всё в облаке и не парюсь, что где-то есть лишние нормировки и нет многострочных макросов...

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

Andrey Khatimlianskii
Andrey Khatimlianskii | 11 сент. 2018 в 23:10
fxsaber:

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

Поколение гигагерц и гигабайт.

Мониторинг торгового счета - необходимый инструмент трейдера Мониторинг торгового счета - необходимый инструмент трейдера
Мониторинг торгового счета — это подробный отчет по всем совершенным сделкам. Вся торговая статистика собирается автоматически и предоставляется вам в виде понятных диаграмм и графиков.
Модель продолжения движения - поиск на графике и статистика исполнения Модель продолжения движения - поиск на графике и статистика исполнения
В данной статье я хочу описать программное определение одной из моделей продолжения движения. В основе работы лежит определение двух волн — основной волны и коррекционной волны. В качестве экстремумов будут использованы фракталы, а также, как я их называю, потенциальные фракталы - экстремумы, которые как фракталы еще не сформировались.
Методы дистанционного управления работой советников Методы дистанционного управления работой советников
Основным преимуществом торговых роботов является безустанная работа 24 часа в сутки на удаленном VPS сервере. Но иногда необходимо вмешаться в их работу в ручном режиме, а прямого доступа к серверу сейчас нет. Возможно ли управлять работой советника дистанционно? В данной статье предлагается один из вариантов управления роботами через внешние команды.
Использование индикаторов для RealTime оптимизации советников Использование индикаторов для RealTime оптимизации советников
Ни для кого не секрет, что успешность работы любого торгового робота зависит от правильного подбора его параметров (его оптимизации). Но оптимальные для одного временного интервала параметры не всегда оказываются наилучшими на другом участке истории. А зачастую советники, прибыльные на тестировании, оказываются убыточными в реальном времени. И здесь возникает вопрос о необходимости постоянной оптимизации. А там где появляется много рутинной работы человек ищет пути ее автоматизации. В данной статье я предлагаю свой нестандартный подход к решению данной задачи.