根据品种和 EA 的 ORDER_MAGIC 分析余额/净值图形

Vladimir Karputov | 19 六月, 2017


内容


设置任务

随着对冲的引入, MetaTrader 5 提供了一个极佳的机会, 可以在一个交易账户内同时利用若干个专家交易系统 (或是策略) 进行交易。整个交易账户可以在 信号 服务中进行跟踪, 并具有接收统计数据的能力。然而, 仍然存在一个重要的问题: 如何直观各策略对余额和净值图形的贡献?

在交易中, 一个策略有盈利, 而第二个正在亏损的情形并不罕见。结果就是, 总体结果也许会徘徊在零值附近。在此情况下, 分别为每个交易策略构建余额和净值图形是十分有益的。


1. 佣金, 库存费, 利润

金融交易的总体结果通过汇总三个参数来定义:

 结果=成交佣金 +收盘时的累计库存费+ 成交利润

这些交易属性可使用 HistoryDealGetDouble() 函数并用以下 ID 为参数得到:

DEAL_COMMISSION

交易佣金

double

DEAL_SWAP

收盘时的累计库存费

double

DEAL_PROFIT

成交利润

double


HistoryDealGetTicket.mq5 脚本, 是在指定时间段内从交易历史中接收交易属性的示例。

脚本的操作结果 (排除交易的 DEAL_ENTRY_IN 类型, 因为它们没有财务结果):

...
  4: deal #36774600 at 2017.02.15 10:17:50 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 1.52 NZDUSD.m (order #47802989, position ID 47770449)
...
12: deal #36798157 at 2017.02.15 16:44:17 Entry out, buy vol: 0.01 comm: 0 swap: -0.01 profit: 2.98 EURUSD.m (order #47827771, position ID 47685190)
13: deal #36798161 at 2017.02.15 16:44:17 Entry out, buy vol: 0.01 comm: 0 swap: -0.02 profit: 5.99 EURUSD.m (order #47827785, position ID 47665575)
14: deal #36798176 at 2017.02.15 16:44:17 Entry out, buy vol: 0.01 comm: 0 swap: -0.02 profit: 5.93 EURUSD.m (order #47827805, position ID 47605603)
15: deal #36798185 at 2017.02.15 16:44:18 Entry out, buy vol: 0.01 comm: 0 swap: -0.03 profit: 5.98 EURUSD.m (order #47827821, position ID 47502789)
16: deal #36798196 at 2017.02.15 16:44:18 Entry out, buy vol: 0.01 comm: 0 swap: -0.03 profit: 8.96 EURUSD.m (order #47827832, position ID 47419515)
17: deal #36798203 at 2017.02.15 16:44:18 Entry out, buy vol: 0.01 comm: 0 swap: -0.06 profit: 8.92 EURUSD.m (order #47827835, position ID 47130461)
18: deal #36798212 at 2017.02.15 16:44:19 Entry out, sell vol: 0.01 comm: 0 swap: -0.48 profit: -21.07 EURUSD.m (order #47827845, position ID 46868574)
...
25: deal #36824799 at 2017.02.15 19:57:57 Entry out, sell vol: 0.01 comm: 0 swap: 0 profit: 2.96 NZDUSD.m (order #47855548, position ID 47817757)
26: deal #36824800 at 2017.02.15 19:57:58 Entry out, sell vol: 0.01 comm: 0 swap: 0 profit: 3.01 NZDUSD.m (order #47855549, position ID 47790966)
27: deal #36824801 at 2017.02.15 19:57:58 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 3.07 NZDUSD.m (order #47855550, position ID 47777495)
28: deal #36824802 at 2017.02.15 19:57:58 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 3 NZDUSD.m (order #47855551, position ID 47759307)
29: deal #36824803 at 2017.02.15 19:57:59 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 1.52 NZDUSD.m (order #47855552, position ID 47682775)
...
33: deal #36832775 at 2017.02.16 00:58:41 Entry out, sell vol: 0.01 comm: 0 swap: 0.05 profit: 2.96 NZDUSD.m (order #47863883, position ID 47826616)
34: deal #36832776 at 2017.02.16 00:58:41 Entry out, sell vol: 0.01 comm: 0 swap: 0.05 profit: 3.05 NZDUSD.m (order #47863884, position ID 47803010)
35: deal #36832777 at 2017.02.16 00:58:41 Entry out, sell vol: 0.01 comm: 0 swap: 0.05 profit: 2.98 NZDUSD.m (order #47863885, position ID 47792294)
36: deal #36832778 at 2017.02.16 00:58:42 Entry out, sell vol: 0.01 comm: 0 swap: 0.07 profit: 2.88 NZDUSD.m (order #47863886, position ID 47713741)
        ...

正如我们所见, 库存费和利润既有 "+" 亦有 "-" 符号。因此, 在总体财务结果方程中库存费和利润使用加法。


2. 计算历史余额和净值

通常操作原则包括形成 "持仓" 的清单。交易历史被划分为每 5-15 分钟一段 (此参数将在稍后定义)。那么我们应该先依次对每个一段执行以下操作:

  • 搜索离场交易。如果检测到, 我们应该根据这些交易的总体财务结果重新计算余额和净值图形的数值。修正 "持仓" 清单
  • 按照持仓计算净值图形数值。 

2.1. "持仓" 清单

我们需要成交类来考虑所选段落的持仓。我们会在 CHistoryDeal 类中实现它 — 在 HistoryDeal.mqh 包含文件中:

方法 数值
TicketDeal 取值/赋值 "Deal ticket" 属性
PosIDDeal 取值/赋值 "Position ID" 属性
SymbolDeal 取值/赋值 "Deal symbol" 属性
TypeDeal 取值/赋值 "Deal type" 属性, 数值取自 ENUM_DEAL_TYPE 枚举
EntryDeal 取值/赋值 "Trade direction" 属性, 数值取自 ENUM_DEAL_ENTRY 枚举
VolumeDeal 取值/赋值 "Trade volume" 属性
PriceDeal 取值/赋值 "Trade open price" 属性


2.2. 浮动盈利方程

盈利方程取自 ENUM_SYMBOL_CALC_MODE 枚举描述。在这样做时, 请始终记住计算品种利润的货币。可以获得利润货币:

SymbolInfoString(m_name,SYMBOL_CURRENCY_BASE);
  • 或是通过简单地打开终端中的品种规范:

rts 规范

图例. 1. RTS-3.17 规范

2.3. 避免难题: 是否所有品种均可用?

可能的难题: 

  • 在 "市场观察" 中缺失交易历史中的品种 (通过常用品种列表执行搜索);
  • 品种的利润货币不能重新计算转换到存款货币。换言之, 在 "市场观察" 中没有用于重新计算的品种。

考虑到在程序的 (而非终端的!) 全局变量 中可能的错误, 我们引入一个数组, 用来保存 "市场观察" 中不存在的 "损坏" 品种, 以及无法将利润货币重新计算转换为存款利率的那些:

string m_arr_defective_symbols[];                        // 有缺陷品种的数组

顺序通过检查 (所有阶段都通过 SearchDefectiveSymbols 函数):

  1. 为 "市场观察" 中存在的所有品种创建临时 (辅助) 数组;
  2. 为所有交易历史中的品种创建临时 (辅助) 数组 (在指定的日期段里: 从 ... 至 ... );
  3. 在 "市场观察" 中寻找交易历史中的品种。如果找不到任何品种, 则将其发送到 "损坏" 品种列表;
  4. 在 "市场观察" 中添加 (显示) 来自交易历史的品种。如果操作失败, 则将该品种发送到 "损坏" 品种列表;
  5. 从品种属性获取利润货币。如果品种的利润货币与存款货币不一致, 请转至 5.1 子步骤。
    1. 尝试找到一个 "市场观察" 品种重新计算。品种应符合 "存款货币" +"利润货币" 或 "利润货币" + "存款货币"。如果操作失败, 则将品种发送到 "损坏" 品种列表

"SearchDefectiveSymbols" 函数完成后, 来自交易历史的 "损坏" 品种数组业已形成。"损坏" 品种不会在未来的计算中使用 (余额和净值图形)。

2.4. 考虑历史中的开仓和平仓

所有余额变化均显示在 m_arr_balance_equity 二维数组中。

请求在 "从日期" 和 "至日起" 日期 (EA 输入) 之间的历史数据, 并以 "计时器 (分钟)" 期间的周期数将其切分。此操作是在 "CreateBalanceEquity" 函数中完成的。所有交易均被写入 "ArrOpenPositions" 动态指针数组中。随后的数组操作是添加和删除交易。

根据 "交易方向" 参数 — ENUM_DEAL_ENTRY, 余额和净值图表的计算方式不同。如果您想查看对冲和净持账户中开仓、平仓、部分平仓或反转持仓的行为, 您可以启动 HistorySelect.mq5 脚本。

  • ‌DEAL_ENTRY_IN — 入场。将一笔交易写入 ArrOpenPositions 数组
  • DEAL_ENTRY_OUT — 离场。ArrOpenPositions 数组中搜索一笔 DEAL_ENTRY_IN 类型, 且 DEAL_POSITION_ID 相同的交易
    • 如果搜索返回 "false", 则在余额和净值图形中不进行任何更改
    • 如果 'true', 验证交易量 (以手数)
      • 如果交易量相同, 从 ArrOpenPositions 数组里删除一笔交易, 并在余额图形数组 (m_arr_balance_equity) 里进行修改
      • 如果来自 ArrOpenPositions 数组里的交易量较高, 调整在 ArrOpenPositions 数组里的交易量, 并在 余额图形 (m_arr_balance_equity 数组) 里进行修改
      • 如果来自 ArrOpenPositions 数组里的交易量较低, 从 ArrOpenPositions 数组里删除一笔交易, 并在 余额图形 (m_arr_balance_equity 数组) 里进行修改
  • DEAL_ENTRY_INOUT — 反转。在 ArrOpenPositions 数组搜索 DEAL_ENTRY_IN 类型且 DEAL_POSITION_ID 相同的一笔交易
    • 如果搜索返回 "false", 则在余额和净值图形中不进行任何更改
    • 如果 'true', 在 ArrOpenPositions 数组里调整交易量和类型, 并在余额图形 (m_arr_balance_equity 数组) 里进行修改
  • DEAL_ENTRY_OUT_BY — 由相反仓位平仓。在 ArrOpenPositions 数组里搜索 DEAL_ENTRY_IN 类型且 DEAL_POSITION_ID 相同的一笔交易
    • 如果交易量相同, 从 ArrOpenPositions 数组里删除一笔交易, 并在余额图形 (m_arr_balance_equity 数组) 里进行修改
    • 如果交易量不同, 在 ArrOpenPositions 数组里调整交易量, 并在余额图形 (m_arr_balance_equity 数组) 里进行修改

2.5. 如何计算浮盈 (净值)

有必要沿着整个 ArrOpenPositions 数组计算数组中所有交易的浮动利润。如果一笔交易已在 ArrOpenPositions 数组, 每笔交易我们都有一些如下的最重要数据:

  • 品种
  • 类型 (买入或卖出)
  • 交易量
  • 成交价格,

以及所请求的交易历史的最终日期。直到所请求的交易历史, 唯一缺少的数据是该笔交易最终日期的品种价格。价格在 CalculationFloatingProfit 里使用 CopyTicks 进行接收。如上所述, 盈利方程取自 ENUM_SYMBOL_CALC_MODE 枚举描述。 

在分析时, CopyTicks 方法变得非常耗时, 尤其当 CalculationFloatingProfit 数组在一个品种上包含多笔交易时。在这种情况下, 由于品种和日期一样, 相同的数据被请求多次。我们已实现了 结果缓存 (CopyTicks) 来加速这一进程: 

//--- 缓存结果 "CopyTicks"
   static string arr_name[];
   static double arr_ask[];
   static double arr_bid[];
   static datetime prev_time=0;
   int number=-1;
   if(time>prev_time)
     {
      prev_time=time;
      ArrayFree(arr_name);
      ArrayFree(arr_ask);
      ArrayFree(arr_bid);
     }
   found=false;
   size=ArraySize(arr_name);
   if(size>0)
      for(int i=0;i<size;i++)
        {
         if(name==arr_name[i])
           {
            number=i;
            found=true;
            break;
           }
        }
   if(found)
     {
      ArrayResize(ticks_array,1);
      ticks_array[0].ask=arr_ask[number];
      ticks_array[0].bid=arr_bid[number];
     }
   else
     {
      //---
      int copy_ticks=-1;
      int count=0;
      while(copy_ticks==-1 && count<5)
        {
         copy_ticks=CopyTicks(name,ticks_array,COPY_TICKS_INFO,1000*(ulong)time,1);
         //if(copy_ticks==-1)

供给价和采购价结果将保存到本地数组: 如果在该时间段内已接收到品种的即时报价, 则这些即时报价将简单地从本地数组里获取。这种方法可将进程加速 10-15 倍。 


3. 将 EA 集成到对话框 (面板)

现在, EA 的基础已经准备就绪了, 到了让界面更加友好的时候了。考虑到文章的名称 "根据品种和 EA 的 ORDER MAGIC 分析余额/净值图形", 有必要提供几种过滤功能:

  • 显示所有/一个或几个选定的品种;
  • 显示所有魔幻数字/由一个或几个选定的品种。

我们已经有 m_arr_all_trade_symbols 数组来参考所有交易品种。它是在全局程序层面上声明的。我们来引入另外一个数组 m_arr_all_magics 用来参考所有的魔幻数字。为此, 请升级 FillArrayTradeSymbols函数: 现在, m_arr_all_magics 数组也将会填充。

3.1. 对话框的正常视图

面板

图例. 2. 面板的一般视图

在 "损坏" 品种、所有的交易品种和魔幻数字数组形成之后, 面板 (基于 CComboBox 类的元素) 上的这两个列表被填入: 左侧列表中填充所有的交易品种, 右侧的一个充满了所有的魔幻数字。在列表中, 首先选择所有的品种和魔幻数字:

面板组合框

图例. 3. 下拉列表

面板 EA 操作逻辑如下: 仅在按下 "开始" 按钮后, 系统检查两个下拉列表中的选择项。根据所选参数, 余额/净值图形根据交易历史记录绘制在列表中。

3.2. 与对话框交互

我决定将整个 EA 代码转移到面板类 (APHDialog.mqh), 这将花费太多时间。替代解决方案: 在面板类中引入 m_ready, m_symbolm_magic 内部变量:

//+------------------------------------------------------------------+
//| 类 CAPHDialog                                                    |
//| 用法: 控件应用的主要对话框                                           |
//+------------------------------------------------------------------+
class CAPHDialog : public CAppDialog
  {
private:
   CLabel            m_label_symbols;                 // 标签对象
   CComboBox         m_combo_box_symbols;             // 组合框对象
   CLabel            m_label_magics;                  // 标签对象
   CComboBox         m_combo_box_magics;              // 组合框对象
   CButton           m_button_start;                  // 按钮对象
   //---
   bool              m_ready;                         // true -> 您可以构建图形
   string            m_symbol;
   ulong             m_magic;

public:

当处理 开始 按钮时, 会在 m_ready 变量状态基础上进行决策:

//+------------------------------------------------------------------+
//| 事件处理器                                                         |
//+------------------------------------------------------------------+
void CAPHDialog::OnClickButtonStart(void)
  {
   if(m_combo_box_symbols.Select()!=" " && m_combo_box_magics.Select()!=" ")
      m_ready=true;
   else
      m_ready=false;
//Comment(__FUNCTION__+" ButtonStartclick"+"\n"+
//        "品种: "+"\""+m_combo_box_symbols.Select()+"\""+"\n"+
//        "魔幻数字: "+"\""+m_combo_box_magics.Select()+"\""+"\n"+
//        "m_ready: "+IntegerToString(m_ready));
  }

注意, 高亮字符串: 一旦创建并填充下拉列表 (在我们的案例中这些是 m_combo_box_symbols and m_combo_box_magics 元素) 之后, 带有 " " 值 (空格) 的元素设置在列表里。

在处理相应下拉列表上的点击时, 会在 m_symbol 和 m_magic 变量状态基础上做出决策:

//+------------------------------------------------------------------+
//| 事件处理器                                                         |
//+------------------------------------------------------------------+
void CAPHDialog::OnChangeComboBoxSymbols(void)
  {
//Comment(__FUNCTION__+" \""+m_combo_box_symbols.Select()+"\"");
   if(m_combo_box_symbols.Select()=="所有品种")
      m_symbol="";
   else
      m_symbol=m_combo_box_symbols.Select();
  }
//+------------------------------------------------------------------+
//| 事件处理器                                                         |
//+------------------------------------------------------------------+
void CAPHDialog::OnChangeComboBoxMagics(void)
  {
//Comment(__FUNCTION__+" \""+m_combo_box_magics.Select()+"\"");
   if(m_combo_box_magics.Select()=="所有魔幻数字")
      m_magic=-1;
   else
      m_magic=StringToInteger(m_combo_box_magics.Select());
  }

所以, 当单击开始按钮时, 将填充三个变量 m_ready, m_symbol 和 m_magic。现在, 所有要做的就是通知 EA 已在面板中选择了参数。解决方案很简单: 在 EA 中, 我们应该以三秒钟的间隔启动定时器。定时对面板进行调查。为此, 我们在面板中写下 CAPHDialog::IsReady 方法。

//+------------------------------------------------------------------+
//| 在面板上有所选择的参数                                               |
//+------------------------------------------------------------------+
bool CAPHDialog::IsReady(string &symbol,ulong &magic)
  {
   if(m_ready)
     {
      symbol=m_symbol;
      magic=m_magic;
      m_ready=false;
      return(true);
     }
   else
      return(false);
  }

在此方法中, 我们将内部变量的值写入通过引用传递的变量里, 并重置 m_ready 内部变量。

3.3. 小调整 - 考虑所选的魔幻数字

根据指定条件选择交易: 在 GetHistory 里根据品种, 或是所有品种和魔幻数字, 或是所有魔幻数字:

//--- 所有成交 
   for(int i=0;i<deals;i++)
     {
      deal_ticket          = HistoryDealGetTicket(i);
      deal_position_ID     = HistoryDealGetInteger(deal_ticket,DEAL_POSITION_ID);
      deal_symbol          = HistoryDealGetString(deal_ticket,DEAL_SYMBOL);
      deal_type            = (ENUM_DEAL_TYPE)HistoryDealGetInteger(deal_ticket,DEAL_TYPE);
      deal_entry           = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket,DEAL_ENTRY);
      deal_volume          = HistoryDealGetDouble(deal_ticket,DEAL_VOLUME);
      deal_price           = HistoryDealGetDouble(deal_ticket,DEAL_PRICE);
      deal_commission      = HistoryDealGetDouble(deal_ticket,DEAL_COMMISSION);
      deal_swap            = HistoryDealGetDouble(deal_ticket,DEAL_SWAP);
      deal_profit          = HistoryDealGetDouble(deal_ticket,DEAL_PROFIT);
      deal_magic           = HistoryDealGetInteger(deal_ticket,DEAL_MAGIC);
      if(sSymbol!="")
         if(deal_symbol!=sSymbol)
            continue;
      if(uMagic!=ULONG_MAX)
         if(deal_magic!=uMagic)
            continue;
      //--- 仅买入或卖出

请注意, 对于 uMagic 变量, ULONG_MAX 意味着 "所有魔幻数字", 而对于 sSymbol 变量, "" 意味着 "所有品种"。

根据交易历史绘制余额和浮动利润 (首先, 对于所有品种, 然后 — 仅一个):


视频

4. MFE 和 MAE 分布图形

最大盈利 (MFE) 和最大亏损 (MAE) 值是在每笔开单的生存期之间录得的。每笔平仓订单的附加表征参数, 最大未实现潜力和最大允许风险值。MFE/盈利 和 MAE/盈利 分布图将每笔订单显示为沿 X 轴绘制的盈利/亏损值的点数, 而最大潜在利润 (MFE) 和潜在亏损(MAE) 则沿 Y 轴绘制。

4.1. MFE 和 MAE 会计原则

用于仓位记账的 m_arr_pos_id 数组, 以及参考 MFE 的 m_arr_mfe_mae数组, 总体财务结果和 MAE 在全局程序层面上声明: 

long   m_arr_pos_id[];
double m_arr_mfe_mae[][3];  // [][0] - mfe, [][1] - 最终财务结果,[][2] - mae

随后, 基于 m_arr_mfe_mae 数组绘制 MFE 和 MAE 分布点数图形。

用于仓位记账的 m_arr_pos_id 数组, 以及参考 MFE 的 m_arr_mfe_mae 在第一维里总有一个大小 — 所以, "i" 位置 (m_arr_pos_id[i]) 总是清晰的匹配 m_arr_mfe_mae[i][][][]。这两个数组的大小设置在 GetHistory 中:

         if(deal_symbol=="")
            DebugBreak();
         ArrOpenPositions.Add(HistoryDeal);
         //--- mfe, mae
         int size=ArraySize(m_arr_pos_id);
         ArrayResize(m_arr_pos_id,size+1,10);
         ArrayResize(m_arr_mfe_mae,size+1,10);
         m_arr_pos_id[size]=deal_position_ID;
         // [][0] - mfe, [][1] - 最终财务结果,[][2] - mae
         m_arr_mfe_mae[size][0]=0.0;
         m_arr_mfe_mae[size][1]=0.0;
         m_arr_mfe_mae[size][2]=0.0;
         continue;
        }
      //--- 
      if(deal_entry==DEAL_ENTRY_OUT)

函数负责记录每笔仓位的最大盈利和亏损:

//+------------------------------------------------------------------+
//| 加入 Mfe Mae 结果                                                  |
//+------------------------------------------------------------------+
void AddResultMfeMae(const long pos_id,const double floating_profit,const double financial_result)
  {
// [][0] - mfe (盈利), [][1] - 最终财务结果,[][2] - mae (亏损)
//--- 搜索 pos_id
   int position=-1;
   int size=ArraySize(m_arr_pos_id);
   for(int i=0;i<size;i++)
      if(m_arr_pos_id[i]==pos_id)
        {
         position=i;
         break;
        }
   if(position==-1)
      return;

//---
   if(floating_profit==0.0)
      return;

   if(floating_profit>0.0) // profit
     {
      if(m_arr_mfe_mae[position][0]<floating_profit)
         m_arr_mfe_mae[position][0]=floating_profit;
     }
   else // loss
     {
      if(m_arr_mfe_mae[position][2]>floating_profit)
         m_arr_mfe_mae[position][2]=floating_profit;
     }
   m_arr_mfe_mae[position][1]=financial_result;
  }

所有三个参数都应传递。换言之, 如果我们的虚拟仓位仍然位于 ArrOpenPositions 持仓列表, 且其浮盈为 -20.2, 则看起来如下:

    AddResultMfeMae(pos_id,-20.2,0.0);

如果我们的虚拟仓位仍然位于 ArrOpenPositions 持仓列表, 且其浮盈为 +5.81, 则看起来如下:

    AddResultMfeMae(pos_id,5.81,0.0);

如果我们的虚拟仓位仍然位于 ArrOpenPositions 持仓列表, 且其浮盈为 -3.06, 则看起来如下:

    AddResultMfeMae(pos_id,0.0,-3.06);

换言之, 如果有浮盈, 总体财务结果为 0。如果仓位已平仓, 浮动利润等于 0。

4.2. 处理仓位

  • ‌DEAL_ENTRY_IN - 入场。写入一笔交易至 ArrOpenPositions 数组。我们在 m_arr_pos_idm_arr_mfe_mae 数组里创建一个新元素。
  • DEAL_ENTRY_OUT - 离场。在 ArrOpenPositions 数组里搜索一笔 DEAL_ENTRY_IN 类型的交易, 且 DEAL_POSITION_ID 相同
    • 如果搜索返回 "false", 则在余额和净值图形中不进行任何更改
    • 如果 'true', 验证交易量 (以手数)
      • 如果交易量相同, ArrOpenPositions 数组里删除一笔交易, 并修改余额图形 (m_arr_balance_equity)。 写入 [浮盈 0.0][总体财务结果] 至 m_arr_mfe_mae 数组。
      • 如果来自 ArrOpenPositions 数组的交易量较高, ArrOpenPositions 数组里调整交易量, 并修改 m_arr_balance_equity 数组 (余额图形)。 写入 [浮盈 0.0][总体财务结果] 至 m_arr_mfe_mae 数组。
      • 如果来自 ArrOpenPositions 数组的交易量较低, ArrOpenPositions 数组里删除一笔交易, 并修改 m_arr_balance_equity 数组 (余额图形)。 写入 [浮盈 0.0][总体财务结果] 至 m_arr_mfe_mae 数组。
  • DEAL_ENTRY_INOUT - 反转。在 ArrOpenPositions 数组里搜索一笔 DEAL_ENTRY_IN 类型的交易, 且 DEAL_POSITION_ID 相同
    • 如果搜索返回 "false", 则在余额和净值图形中不进行任何更改
    • 若为 'true', ArrOpenPositions 数组里调整交易量和类型 , 并修改 m_arr_balance_equity 数组 (余额图表)。 写入 [浮盈 0.0][总体财务结果] 至 m_arr_mfe_mae 数组。
  • DEAL_ENTRY_OUT_BY - 由相反仓位平仓。在 ArrOpenPositions 数组里搜索一笔 DEAL_ENTRY_IN 类型的交易, 且 DEAL_POSITION_ID 相同
    • 如果交易量相同, ArrOpenPositions 数组里删除一笔交易, 并修改余额图形 (m_arr_balance_equity)。 写入 [浮盈 0.0][总体财务结果] 至 m_arr_mfe_mae 数组。
    • 如果交易量不同, 在 ArrOpenPositions 数组里调整交易量, 并修改 m_arr_balance_equity 数组 (余额图形)。 写入 [浮盈 0.0][总体财务结果] 至 m_arr_mfe_mae 数组。

此外, 如果您打算重新计算每笔仓位的浮盈, 在 GetHistory 里, 写入 [浮盈][总体财务结果 0.0] 至 m_arr_mfe_mae 数组。

4.3. 修改对话框

为了显示余额/净值, 和 MFE/MAE 图形, 对话框应稍作修改:

面板 2

图例. 4. 修改对话框

MFE (最大盈利) 和 MAE (最大亏损) 使用两个坐标构建: 分别对应 X - 仓位的总体财务结果, Y - MFE 或 MAE 数值。

MFE

图例. 5. MFE

MAE

图例. 6. MAE

结论

现在, 即使在对冲账户里同时使用几个 EA, 您也可以查看每个品种和魔幻数字的余额和净值图形。换言之, 您可以直观地定义每个 EA (ORDER_MAGIC) 对总体余额的贡献, 以及每个 EA 的回撤。

文章中使用的程序:

#
 名称
类型
描述
1
HistoryDeal.mqh 函数库  在所选段落上分析持仓的类
2
HistoryDealGetTicket.mq5 EA  在指定时间段内从交易历史中接收交易属性的示例
3
APHDialog.mqh 函数库  EA 对话框类
4
Accounting_positions_on_history.mq5 EA  文章中描述的主要 EA
5
MQL5.zip 存档  包含主要 EA 及其包含文件的存档。