估算交易操作的利润: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 参数中指定。仅允许使用 ENUM_ORDER_TYPE 枚举中的市场订单 ORDER_TYPE_BUY 或 ORDER_TYPE_SELL。金融工具的名称及其交易量在 symbolvolume参数中传递。市场进入和退出价格分别由 openPriceclosePrice 参数设置。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 - 最后已知 买入/卖出 价格(取决于交易类型)
  • OpenPrice - 仓位开盘价
  • ClosePrice - 仓位收盘价
  • FaceValue - 债券的面值 (SYMBOL_TRADE_FACE_VALUE)
  • LiqudityRate - 流动性比率 (SYMBOL_TRADE_LIQUIDITY_RATE)
  • AccruedInterest - 累积息票收入 (SYMBOL_TRADE_ACCRUED_INTEREST)

OrderCalcProfit 函数只能在 EA 交易和脚本中使用。要计算指标中的潜在利润/损失,需要实施替代方法,例如,使用公式的独立计算。

为了绕过在指标中使用 OrderCalcProfitOrderCalcMargin 函数的限制,我们开发了一组函数,这些含税使用本节以及 保证金要求一节的公式执行计算。些函数位于头文件 MarginProfitMeter.mqh 中,在公共名称空间 MPM 内(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 函数在 Market Watch 中查找字符,并在 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 中找到。

我们用一个测试脚本 ProfitMeter.mq5 来检查 OrderCalcProfit 函数和函数组 MPM 的性能:我们将为市场报价的所有交易品种计算虚拟交易的利润/损失估算,可使用两种方法来完成该计算:内置方法和我们自己编写的方法。

在该脚本的输入参数中,可以选择操作类型 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()
{
   // 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));
   ...

然后,在对交易品种迭代中,以两种方式执行计算,并打印结果以供比较。

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

尝试为不同的账户和金融工具集运行该脚本。

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

理想情况下,每行中的数字应匹配。