Estimación del beneficio de una operación de trading: OrderCalcProfit

Una de las funciones de la API de MQL5, OrderCalcProfit, permite preevaluar el resultado financiero de una operación de trading si se cumplen las condiciones previstas. Por ejemplo, utilizando esta función puede averiguar la cantidad de beneficios al alcanzar el nivel Take Profit, y la cantidad de pérdidas cuando se activa Stop Loss.

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

La función calcula el beneficio o la pérdida en la divisa de la cuenta para el entorno de mercado actual basándose en los parámetros pasados.

El tipo de orden se especifica en el parámetro action. Sólo se permiten órdenes de mercado ORDER_TYPE_BUY u ORDER_TYPE_SELL de la enumeración ENUM_ORDER_TYPE. El nombre del instrumento financiero y su volumen se pasan en los parámetros symbol y volume. Los precios de entrada y salida del mercado se fijan mediante los parámetros openPrice y closePrice, respectivamente. La variable profit se pasa por referencia como último parámetro, y en ella se escribirá el valor del beneficio.

La función devuelve un indicador de éxito (true) o de error (false).

La fórmula de cálculo del resultado financiero utilizada en OrderCalcProfit depende del tipo de símbolo.

Identificador

Formula

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

En las fórmulas se utiliza la siguiente notación:

  • Lotes - volumen de la posición en lotes (acciones del contrato)
  • ContractSize - tamaño del contrato (un lote, SYMBOL_TRADE_CONTRACT_SIZE)
  • TickPrice - precio del tick (SYMBOL_TRADE_TICK_VALUE)
  • TickSize - tamaño del tick (SYMBOL_TRADE_TICK_SIZE)
  • MarketPrice - último precio de oferta/compra (Bid/Ask) conocido según el tipo de transacción
  • OpenPrice - precio de apertura de la posición
  • ClosePrice - precio de cierre de la posición
  • FaceValue - valor nominal del bono (SYMBOL_TRADE_FACE_VALUE)
  • LiqudityRate - ratio de liquidez (SYMBOL_TRADE_LIQUIDITY_RATE)
  • AccruedInterest - ingresos por cupones acumulados (SYMBOL_TRADE_ACCRUED_INTEREST)

La función OrderCalcProfit sólo puede utilizarse en Asesores Expertos y scripts. Para calcular los beneficios/pérdidas potenciales en los indicadores es necesario aplicar un método alternativo, por ejemplo, cálculos independientes mediante fórmulas.

Para eludir la restricción de uso de las funciones OrderCalcProfit y OrderCalcMargin en indicadores, hemos desarrollado un conjunto de funciones que realizan cálculos utilizando las fórmulas de esta sección, así como la sección Requisitos de margen. Las funciones se encuentran en el archivo de encabezado MarginProfitMeter.mqh, dentro del espacio de nombres común MPM (de «Margin Profit Meter»).

En concreto, para calcular el resultado financiero, es importante disponer del valor de un punto de un instrumento concreto. En las fórmulas anteriores, participa indirectamente en la diferencia entre los precios de apertura y cierre (ClosePrice - OpenPrice).

La función calcula el valor de un punto de precio 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);
      ...

Al principio de la función solicitamos todas las propiedades de los símbolos necesarias para el cálculo. A continuación, en función del tipo de símbolo, se obtiene el beneficio/pérdida en la divisa del beneficio de este instrumento. Tenga en cuenta que aquí no hay bonos cuyas fórmulas tengan en cuenta el precio nominal y los ingresos por cupones.

      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));
      }
      ...

Por último, convertimos el importe a la divisa de la cuenta, si difiere.

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

La función de ayuda Convert se utiliza para convertir importes. A su vez, depende de la función FindExchangeRate, que busca entre todos los símbolos disponibles uno que contenga el tipo de cambio de la divisa current a la moneda 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;
   }

La función FindExchangeRate busca caracteres en Observación de Mercado y devuelve el nombre del primer símbolo de Forex que coincida, si hay varios, en el parámetro result. Si la cotización corresponde al orden directo de las divisas «current/account», la función devolverá +1, y si es al contrario, será «account/current», es decir, -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;
   }

El código completo de las funciones se encuentra en el archivo adjunto MarginProfitMeter.mqh.

Vamos a comprobar el rendimiento de la función OrderCalcProfit y del grupo de funciones MPM con un script de prueba ProfitMeter.mq5: calcularemos la estimación de ganancias/pérdidas de las operaciones virtuales para todos los símbolos de Observación de Mercado, y lo haremos utilizando dos métodos: el integrado y el nuestro.

En los parámetros de entrada del script, puede seleccionar el tipo de operación Action (compra o venta), el tamaño del lote Lot y el tiempo de mantenimiento de la posición en barras Duration. El resultado financiero se calcula para las cotizaciones de las últimas barras Duration del marco temporal actual.

#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)

En el cuerpo del script, conectamos los archivos de encabezado y mostramos el encabezado con los parámetros.

#include <MQL5Book/MarginProfitMeter.mqh>
#include <MQL5Book/Periods.mqh>
   
void OnStart()
{
   // guarantee that the operation will only be a buy or a sell
   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));
   ...

A continuación, en un bucle a través de símbolos, realizamos los cálculos de dos maneras e imprimimos los resultados para compararlos.

   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 adopted variables
      
      // standard method 
      if(!OrderCalcProfit(typesymbolLotenterexitprofit1))
      {
         PrintFormat("OrderCalcProfit(%s) failed: %d"symbol_LastError);
         continue;
      }
      
      // our own method 
      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;
      
      // output to the log for comparison
      PrintFormat("%s: %f %f"symbolprofit1profit2);
   }
}

Pruebe a ejecutar el script para diferentes cuentas y conjuntos de instrumentos.

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

Lo ideal es que los números de cada línea coincidan.