Оценка прибыли торговой операции: OrderCalcProfit

Одна из функций MQL5 API — OrderCalcProfit — позволяет заранее оценить финансовый результат торговой операции при выполнении предполагаемых условий. Например, с помощью неё можно узнать сумму прибыли при достижении уровня Take Profit и сумму убытка при срабатывании Stop Loss.

bool OrderCalcProfit(ENUM_ORDER_TYPE action, const string symbol, double volume,
  double openPrice, double closePrice, double &profit)

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

В параметре action указывается тип ордера, причем разрешено использовать только рыночные ордера ORDER_TYPE_BUY или ORDER_TYPE_SELL из перечисления ENUM_ORDER_TYPE. Название финансового инструмента и его объем передаются в параметрах symbol и volume. Цены входа и выхода из рынка задаются параметрами openPrice и closePrice, соответственно. Последним параметром по ссылке передается переменная profit, в которую будет записано значение прибыли.

Функция возвращает признак успеха (true) или ошибки (false).

Формула расчета финансового результата, применяемая внутри OrderCalcProfit, зависит от типа инструмента.

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

Формула

SYMBOL_CALC_MODE_FOREX

(ClosePrice - OpenPrice) * ContractSize * Lots

SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE

(ClosePrice - OpenPrice) * ContractSize * Lots

SYMBOL_CALC_MODE_CFD

(ClosePrice - OpenPrice) * ContractSize * Lots

SYMBOL_CALC_MODE_CFDINDEX

(ClosePrice - OpenPrice) * ContractSize * Lots

SYMBOL_CALC_MODE_CFDLEVERAGE

(ClosePrice - OpenPrice) * ContractSize * Lots

SYMBOL_CALC_MODE_EXCH_STOCKS

(ClosePrice - OpenPrice) * ContractSize * Lots

SYMBOL_CALC_MODE_EXCH_STOCKS_MOEX

(ClosePrice - OpenPrice) * ContractSize * Lots

SYMBOL_CALC_MODE_FUTURES

(ClosePrice - OpenPrice) * Lots * TickPrice / TickSize

SYMBOL_CALC_MODE_EXCH_FUTURES

(ClosePrice - OpenPrice) * Lots * TickPrice / TickSize

SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS

(ClosePrice - OpenPrice) * Lots * TickPrice / TickSize

SYMBOL_CALC_MODE_EXCH_BONDS

Lots * ContractSize * (ClosePrice * FaceValue + AccruedInterest)

SYMBOL_CALC_MODE_EXCH_BONDS_MOEX

Lots * ContractSize * (ClosePrice * FaceValue + AccruedInterest)

SYMBOL_CALC_MODE_SERV_COLLATERAL

Lots * ContractSize * MarketPrice * LiqudityRate

В формулах использованы следующие обозначения:

  • Lots — объем позиции в лотах (долях контракта);
  • ContractSize — размер контракта (одного лота, SYMBOL_TRADE_CONTRACT_SIZE);
  • TickPrice — стоимость тика (SYMBOL_TRADE_TICK_VALUE);
  • TickSize — размер тика (SYMBOL_TRADE_TICK_SIZE);
  • MarketPrice — последняя известная цена Bid/Ask в зависимости от типа сделки;
  • OpenPrice — цена открытия позиции;
  • ClosePrice — цена закрытия позиции;
  • FaceValue — номинальная цена облигации (SYMBOL_TRADE_FACE_VALUE);
  • LiqudityRate — коэффициент ликвидности (SYMBOL_TRADE_LIQUIDITY_RATE);
  • AccruedInterest — накопленный купонный доход (SYMBOL_TRADE_ACCRUED_INTEREST).

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

Чтобы обойти ограничение на применение функций OrderCalcProfit и OrderCalcMargin в индикаторах, был разработан набор функций, выполняющих расчеты по формулам из этого раздела, а также раздела Маржинальные требования. Функции находятся в заголовочном файле MarginProfitMeter.mqh, внутри общего пространства имен MPM (от "Margin Profit Meter").

В частности, для расчета финансового результата важно иметь стоимость одного пункта конкретного инструмента. В вышеприведенных формулах она опосредованно участвует в разности цен открытия и закрытия (ClosePrice - OpenPrice).

Вычислить стоимость одного ценового пункта позволяет функция PointValue.

namespace MPM
{
   double PointValue(const string symbolconst bool ask = false,
      const datetime moment = 0)
   {
      const double point = SymbolInfoDouble(symbolSYMBOL_POINT);
      const double contract = SymbolInfoDouble(symbolSYMBOL_TRADE_CONTRACT_SIZE);
      const ENUM_SYMBOL_CALC_MODE m =
         (ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbolSYMBOL_TRADE_CALC_MODE);
      ...

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

      double result = 0;
      switch(m)
      {
      case SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE:
      case SYMBOL_CALC_MODE_FOREX:
      case SYMBOL_CALC_MODE_CFD:
      case SYMBOL_CALC_MODE_CFDINDEX:
      case SYMBOL_CALC_MODE_CFDLEVERAGE:
      case SYMBOL_CALC_MODE_EXCH_STOCKS:
      case SYMBOL_CALC_MODE_EXCH_STOCKS_MOEX:
         result = point * contract;
         break;
   
      case SYMBOL_CALC_MODE_FUTURES:
      case SYMBOL_CALC_MODE_EXCH_FUTURES:
      case SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS:
         result = point * SymbolInfoDouble(symbolSYMBOL_TRADE_TICK_VALUE)
            / SymbolInfoDouble(symbolSYMBOL_TRADE_TICK_SIZE);
         break;
      default:
         PrintFormat("Unsupported symbol %s trade mode: %s"symbolEnumToString(m));
      }
      ...

Наконец, переводим сумму в валюту счета, если она отличается.

      string account = AccountInfoString(ACCOUNT_CURRENCY);
      string current = SymbolInfoString(symbolSYMBOL_CURRENCY_PROFIT);
   
      if(current != account)
      {
         if(!Convert(currentaccountaskresultmoment)) return 0;
      }
     
      return result;
   }
   ...
};

Для конвертации сумм используется вспомогательная функция Convert. Она, в свою очередь, зависит от функции FindExchangeRate, производящей поиск среди всех доступных символов такого, который содержит курс из валюты current в валюту account.

   bool Convert(const string currentconst string account,
      const bool askdouble &marginconst datetime moment = 0)
   {
      string rate;
      int dir = FindExchangeRate(currentaccountrate);
      if(dir == +1)
      {
         margin *= moment == 0 ?
            SymbolInfoDouble(rateask ? SYMBOL_BID : SYMBOL_ASK) :
            GetHistoricPrice(ratemomentask);
      }
      else if(dir == -1)
      {
         margin /= moment == 0 ?
            SymbolInfoDouble(rateask ? SYMBOL_ASK : SYMBOL_BID) :
            GetHistoricPrice(ratemomentask);
      }
      else
      {
         static bool once = false;
         if(!once)
         {
            Print("Can't convert "current" -> "account);
            once = true;
         }
      }
      return true;
   }

Функция FindExchangeRate просматривает символы в Обзоре рынка и возвращает название первого подходящего Forex-символа, если их несколько, в параметре result. Если котировка соответствует прямому порядку валют "current/account", функция вернет +1, а если обратному — "account/current" — -1.

   int FindExchangeRate(const string currentconst string accountstring &result)
   {
      for(int i = 0i < SymbolsTotal(true); i++)
      {
         const string symbol = SymbolName(itrue);
         const ENUM_SYMBOL_CALC_MODE m =
            (ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbolSYMBOL_TRADE_CALC_MODE);
         if(m == SYMBOL_CALC_MODE_FOREX || m == SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE)
         {
            string base = SymbolInfoString(symbolSYMBOL_CURRENCY_BASE);
            string profit = SymbolInfoString(symbolSYMBOL_CURRENCY_PROFIT);
            if(base == current && profit == account)
            {
               result = symbol;
               return +1;
            }
            else
            if(base == account && profit == current)
            {
               result = symbol;
               return -1;
            }
         }
      }
      return 0;
   }

С полным кодом функций можно ознакомиться в прилагаемом файле MarginProfitMeter.mqh.

Проверим работу функции OrderCalcProfit и группы функций MPM с помощью тестового скрипта ProfitMeter.mq5: вычислим оценку прибыли/убытка для виртуальных трейдов по всем символам Обзора рынка, причем сделаем это двумя способами — встроенным и собственным.

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

#property script_show_inputs
   
input ENUM_ORDER_TYPE Action = ORDER_TYPE_BUY// Action (only Buy/Sell allowed)
input float Lot = 1;
input int Duration = 20// Duration (bar number in past)

В теле скрипта подключаем заголовочные файлы и выводим "шапку" с параметрами.

#include <MQL5Book/MarginProfitMeter.mqh>
#include <MQL5Book/Periods.mqh>
   
void OnStart()
{
   // гарантируем, что операцией будет только покупка или продажа
   ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)(Action % 2);
   const string text[] = {"buying""selling"};
   PrintFormat("Profits/Losses for %s %s lots"
      " of %d symbols in Market Watch on last %d bars %s",
      text[type], (string)LotSymbolsTotal(true),
      DurationPeriodToString(_Period));
   ...

Затем в цикле по символам выполняем расчеты двумя способами и печатаем результаты для сравнения.

   for(int i = 0i < SymbolsTotal(true); i++)
   {
      const string symbol = SymbolName(itrue);
      const double enter = iClose(symbol_PeriodDuration);
      const double exit = iClose(symbol_Period0);
      
      double profit1profit2// 2 примемных переменных
      
      // стандартный способ
      if(!OrderCalcProfit(typesymbolLotenterexitprofit1))
      {
         PrintFormat("OrderCalcProfit(%s) failed: %d"symbol_LastError);
         continue;
      }
      
      // собственный способ
      const int points = (int)MathRound((exit - enter)
         / SymbolInfoDouble(symbolSYMBOL_POINT));
      profit2 = Lot * points * MPM::PointValue(symbol);
      profit2 = NormalizeDouble(profit2,
         (int)AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS));
      if(type == ORDER_TYPE_SELLprofit2 *= -1;
      
      // выводим в лог для сравнения
      PrintFormat("%s: %f %f"symbolprofit1profit2);
   }
}

Попробуйте запустить скрипт для разных счетов и наборов инструментов.

Profits/Losses for buying 1.0 lots of 13 symbols in Market Watch on last 20 bars H1
EURUSD: 390.000000 390.000000
GBPUSD: 214.000000 214.000000
USDCHF: -254.270000 -254.270000
USDJPY: -57.930000 -57.930000
USDCNH: -172.570000 -172.570000
USDRUB: 493.360000 493.360000
AUDUSD: 84.000000 84.000000
NZDUSD: 13.000000 13.000000
USDCAD: -97.480000 -97.480000
USDSEK: -682.910000 -682.910000
XAUUSD: -1706.000000 -1706.000000
SP500m: 5300.000000 5300.000000
XPDUSD: -84.030000 -84.030000

В идеале числа в каждой строке должны совпасть.