MQL5 Cookbook: 把交易历史写入文件以及为每个交易品种在Excel中创建余额图表·进阶篇
📘

MQL5 Cookbook: 把交易历史写入文件以及为每个交易品种在Excel中创建余额图表·进阶篇

第 2/3 篇

把成交记录拆成可读字符串

在 MT5 的历史成交回放里,裸枚举值对人眼不友好。下面这段把成交品种和类型映射成中文短串,方便后续直接打印或写 CSV。 品种名若为空的场合,函数返回横线占位,避免日志出现连续空白导致对齐错乱;这个细节在批量扫描几十个品种时尤其影响可读性。 交易类型覆盖买、卖、余额、信用、收费、更正、奖励、手续费八类。DEAL_TYPE_BUY 映射为“买”,DEAL_TYPE_SELL 为“卖”,其余非开平仓类(如 DEAL_TYPE_BALANCE 余额)也一并转串,账户流水审计时不会再对着宏发懵。 复制下面的函数进 EA 或脚本,编译后调用 DealTypeToString() 即可在专家日志里看到中文成交类型,省掉每次翻文档。

MQL5 / C++
class="type">class="kw">string DealSymbolToString(class="type">class="kw">string deal_symbol)
  {
   class="kw">return(deal_symbol=="" ? "-" : deal_symbol);
  }
class="type">class="kw">string DealTypeToString(ENUM_DEAL_TYPE deal_type)
  {
   class="type">class="kw">string str="";
   class="kw">switch(deal_type)
     {
      case DEAL_TYPE_BUY                 : str="买";      class="kw">break;
      case DEAL_TYPE_SELL                : str="卖";      class="kw">break;
      case DEAL_TYPE_BALANCE             : str="余额";    class="kw">break;
      case DEAL_TYPE_CREDIT              : str="信用";    class="kw">break;
      case DEAL_TYPE_CHARGE              : str="收费";    class="kw">break;
      case DEAL_TYPE_CORRECTION          : str="更正";    class="kw">break;
      case DEAL_TYPE_BONUS               : str="奖励";    class="kw">break;
      case DEAL_TYPE_COMMISSION          : str="手续费";  class="kw">break;

「成交记录里的类型与方向字符串映射」

在 MT5 的成交(deal)历史解析里,把枚举值转成中文标签是最容易被忽略但极实用的细节。下面这段 switch 把 DEAL_TYPE 系列枚举直接映射到手续费、利息、取消等中文描述,覆盖每日/每月手续费及代理手续费共 5 种类型,外加买取消、卖取消与未知兜底。 交易方向用 ENUM_DEAL_ENTRY 区分:IN 为建仓入、OUT 为平仓出、INOUT 为同笔内开平、STATE 仅是状态记录。做成交明细导出时,这四个值能让你一眼看穿一笔记录到底在干什么,而不是对着枚举数字发呆。 成交量与价格的格式化也顺手做了:成交量小于等于 0 时返回“-”,否则用 DoubleToString 保留 2 位小数;价格则交给带 digits 参数的函数按品种精度处理。外汇与贵金属杠杆高,账户若开多品种,digits 不传对就会把 XAUUSD 的 3 位报价截歪,建议直接取 SymbolInfoInteger 的 SYMBOL_DIGITS 传入。 复制下面代码到 MT5 脚本里,挂上 HistorySelect 后循环 OrderGetInteger(ORDER_DEAL_TYPE) 就能跑出可读成交清单,比终端默认英文报表省眼。

MQL5 / C++
case DEAL_TYPE_COMMISSION_DAILY               : str="每日手续费";        class="kw">break;
case DEAL_TYPE_COMMISSION_MONTHLY              : str="每月手续费";        class="kw">break;
case DEAL_TYPE_COMMISSION_AGENT_DAILY          : str="每日代理手续费";   class="kw">break;
case DEAL_TYPE_COMMISSION_AGENT_MONTHLY        : str="每月代理手续费"; class="kw">break;
case DEAL_TYPE_INTEREST                        : str="利息";             class="kw">break;
case DEAL_TYPE_BUY_CANCELED                    : str="买取消";           class="kw">break;
case DEAL_TYPE_SELL_CANCELED                   : str="卖取消";           class="kw">break;
class=class="str">"cmt">//--- 未知交易类型
class="kw">default : str="未知";
   }
class=class="str">"cmt">//---
   class="kw">return(str);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 把交易方向转换为字符串                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string DealEntryToString(ENUM_DEAL_ENTRY deal_entry)
  {
   class="type">class="kw">string str="";
class=class="str">"cmt">//---
   class="kw">switch(deal_entry)
     {
      case DEAL_ENTRY_IN     : str="入";       class="kw">break;
      case DEAL_ENTRY_OUT    : str="出";       class="kw">break;
      case DEAL_ENTRY_INOUT  : str="入/出";    class="kw">break;
      case DEAL_ENTRY_STATE  : str="状态记录"; class="kw">break;
      class=class="str">"cmt">//--- 未知方向类型
      class="kw">default : str="未知";
     }
class=class="str">"cmt">//---
   class="kw">return(str);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 把交易量转换为字符串                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string DealVolumeToString(class="type">class="kw">double deal_volume)
  {
   class="kw">return(deal_volume<=class="num">0 ? "-" : DoubleToString(deal_volume,class="num">2));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 把价格转换为字符串                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string DealPriceToString(class="type">class="kw">double deal_price,class="type">int digits)
  {

◍ 把成交记录导出成可分析的 CSV

做复盘最怕手工抄交易记录。下面这套 MQL5 函数把每笔成交的价格、库存费、盈亏和回撤直接落盘成 LastTest.csv,开 MT5 跑一遍就能在 Excel 里透视。 价格字段用 deal_price<=0 做防护:无成交返回 '-',否则按品种 digits 精度用 DoubleToString 格式化;库存费同理只保留正数两小数,负值一律记 '-',避免报表出现无意义的 0 行。

CreateSymbolBalanceReport 先在通用目录建文件夹,再以 FILE_CSVFILE_WRITEFILE_ANSIFILE_COMMON 打开文件。表头写死 10 列:TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,PRICE,SWAP($),PROFIT($),DRAWDOWN(%),BALANCE;若 SYMBOLS_COUNT>1 会循环把每个品种名追加到表头末尾。

外汇和贵金属杠杆高、点差跳空频繁,导出后建议先筛掉 SWAP 为 '-' 的异常行再算期望收益,否则样本偏差可能让结论失真。

MQL5 / C++
  class="kw">return(deal_price<=class="num">0 ? "-" : DoubleToString(deal_price,digits));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 把交易结果转换为字符串                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string DealProfitToString(class="type">class="kw">string deal_symbol,class="type">class="kw">double deal_profit)
  {
   class="kw">return((deal_profit==class="num">0 || deal_symbol=="") ? "-" : DoubleToString(deal_profit,class="num">2));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 把库存费转换为字符串                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string DealSwapToString(class="type">class="kw">double deal_swap)
  {
   class="kw">return(deal_swap<=class="num">0 ? "-" : DoubleToString(deal_swap,class="num">2));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建 .csv 格式的交易测试报告                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CreateSymbolBalanceReport()
  {
   class="type">int    file_handle =INVALID_HANDLE; class=class="str">"cmt">// 文件句柄
   class="type">class="kw">string path        ="";             class=class="str">"cmt">// 文件路径
class=class="str">"cmt">//---如果在创建/读取文件夹时出错,退出
   if((path=CreateInputParametersFolder())=="")
      class="kw">return;
class=class="str">"cmt">//--- 在终端的通用文件夹下创建写入数据的文件
   file_handle=FileOpen(path+"\\LastTest.csv",FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON);
class=class="str">"cmt">//--- 如果句柄有效 (文件已创建/打开)
   if(file_handle>class="num">0)
     {
      class="type">int    digits           =class="num">0;    class=class="str">"cmt">// 价格中的小数点位数
      class="type">int    deals_total      =class="num">0;    class=class="str">"cmt">// 制定历史中的交易数量
      class="type">class="kw">ulong  ticket           =class="num">0;    class=class="str">"cmt">// 交易订单号
      class="type">class="kw">double drawdown_max     =class="num">0.0; class=class="str">"cmt">// 最大回撤
      class="type">class="kw">double balance         =class="num">0.0; class=class="str">"cmt">// 余额
      class=class="str">"cmt">//---
      class="type">class="kw">string delimeter       =","; class=class="str">"cmt">// 分隔符
      class="type">class="kw">string string_to_write ="";  class=class="str">"cmt">// 生成写下的字符串
      class=class="str">"cmt">//--- 生成抬头字符串
      class="type">class="kw">string headers="TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,PRICE,SWAP($),PROFIT($),DRAWDOWN(%),BALANCE";
      class=class="str">"cmt">//--- 如果选了多个交易品种, 修改抬头字符串
      if(SYMBOLS_COUNT>class="num">1)
        {
         for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
            StringAdd(headers,","+InputSymbols[s]);
        }
      class=class="str">"cmt">//--- 写报告抬头

把逐笔成交落盘成可复盘报表

实盘复盘最怕历史数据散在终端里翻不出来。这段逻辑直接把 MT5 已平仓成交按时间序写进文件:先 HistorySelect(0, TimeCurrent()) 拉全量历史,再用 HistoryDealsTotal() 拿到成交笔数 deals_total,循环里逐笔取 ticket 并读属性。 写入前按 SYMBOLS_COUNT 给余额数组 ArrayResize,多品种时每个 symbol_balance[s].balance 也扩到 deals_total 长度,避免越界。单笔处理时 balance += deal.profit + deal.swap + deal.commission,把浮动盈亏、库存费、手续费并成净权益变动,这和外盘贵金属隔夜费跳点高风险直接相关,读数前务必用 SymbolInfoInteger 取 SYMBOL_DIGITS 控精度。 StringConcatenate 把时间、品种、买卖方向、成交量、成交价、库存费、利润、回撤和累计余额拼成一行;若 SYMBOLS_COUNT>1 且 deal.symbol==InputSymbols[s] 且 profit!=0,才补写该品种分项余额。复制去 MT5 跑一遍,可能比终端自带报表更贴合你自己的统计口径。

MQL5 / C++
  FileWrite(file_handle,headers);
  class=class="str">"cmt">//--- 取得完整历史
  HistorySelect(class="num">0,TimeCurrent());
  class=class="str">"cmt">//--- 取得交易数量
  deals_total=HistoryDealsTotal();
  class=class="str">"cmt">//---根据交易品种数量修改余额数组大小
  ArrayResize(symbol_balance,SYMBOLS_COUNT);
  class=class="str">"cmt">//--- 修改每个交易品种的交易数组大小
  for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
     ArrayResize(symbol_balance[s].balance,deals_total);
  class=class="str">"cmt">//--- 在循环中迭代并写入数据
  for(class="type">int i=class="num">0; i<deals_total; i++)
    {
     class=class="str">"cmt">//--- 取得交易订单号码
     ticket=HistoryDealGetTicket(i);
     class=class="str">"cmt">//--- 取得所有的交易属性
     GetHistoryDealProperties(ticket,D_ALL);
     class=class="str">"cmt">//--- 取得价格中的小数位数
     digits=(class="type">int)SymbolInfoInteger(deal.symbol,SYMBOL_DIGITS);
     class=class="str">"cmt">//--- 计算总体余额
     balance+=deal.profit+deal.swap+deal.commission;
     class=class="str">"cmt">//--- 通过连接生成用于写入的字符串
     StringConcatenate(string_to_write,
                  deal.time,delimeter,
                  DealSymbolToString(deal.symbol),delimeter,
                  DealTypeToString(deal.type),delimeter,
                  DealEntryToString(deal.entry),delimeter,
                  DealVolumeToString(deal.volume),delimeter,
                  DealPriceToString(deal.price,digits),delimeter,
                  DealSwapToString(deal.swap),delimeter,
                  DealProfitToString(deal.symbol,deal.profit),delimeter,
                  MaxDrawdownToString(i,balance,max_drawdown),delimeter,
                  DoubleToString(balance,class="num">2));
     class=class="str">"cmt">//--- 如果选择了多个交易品种,写下它们的余额
     if(SYMBOLS_COUNT>class="num">1)
       {
        class=class="str">"cmt">//--- 迭代所有交易品种
        for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
          {
           class=class="str">"cmt">//--- 如果交易品种相同而交易结果不等于零
           if(deal.symbol==InputSymbols[s] && deal.profit!=class="num">0)
             {
              class=class="str">"cmt">//--- 显示对应交易品种的交易余额
              class=class="str">"cmt">//   考虑库存费和手续费

常见问题

在EA里用文件函数把每笔成交的币种、方向、开平、手数、价格、时间写成 CSV,再用 Excel 按品种分列作图即可逐品种看权益曲线。
建一个类型映射数组,把枚举值转成「买/卖/开/平」等字符串再写入文件,避免后期在表格里手动替换。
可以,小布盯盘的 AIGC 能按品种拉取成交并生成 CSV 与余额图表,你打开对应品种页就能直接看,不用自己写落盘代码。
导出时按品种分文件或加品种列,再用数据透视按品种筛选,就能画出每条货币对独立的余额曲线。
统一用服务器时间并写明时区,Excel 里再转本地时区,能避免跨时区复盘时顺序错乱。