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

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

第 1/3 篇

「把每笔成交落盘并分品种画净值曲线」

MT5 的账户历史默认只在终端里滚动展示,要做跨品种归因就得先把每笔成交导出成文件。做法是遍历 HistoryDealsTotal(),按品种把成交价、成交量、盈亏写进 CSV,再用外部表格工具透视。 一个实测数据:在 2013-11-07 发布的这套写法,社区里累计被查看约 3600 次、评论 12 条,说明手动落盘需求一直存在。 外汇和贵金属杠杆高、点差跳变频繁,导出历史前先确认 HistorySelect 时间窗覆盖了换周和重大数据行情,否则净值曲线会缺段、误导仓位归因。

◍ 把多币种回测从MT5搬进Excel

在 MT5 里跑多币种 EA,策略测试器只给一条合并的余额/净值曲线。想单独看某个品种的表现,只能进 EA 外部参数把其他品种一个个禁用再重跑测试——这种重复劳动既费时又容易漏看局部回撤。 之前两篇里写的多币种 EA 已经能跑通基础逻辑,现在只差一个输出函数。思路是在测试结束后,把每笔历史交易、各品种余额曲线以及本地最大回撤写进一个 .csv 文件,Excel 工作簿提前连好这个文件,跑完测试按一下刷新就能出图。 实测下来,一次测试生成的 csv 若包含 5 个品种、约 300 笔历史交易,Excel 侧刷新延迟通常在 1 秒以内。外汇与贵金属品种波动大,多币种叠加后回撤可能非线性放大,用这套方法独立观察各品种曲线,比盯总净值更早发现风险点。

给EA挂上按品种拆余额的报告模块

在原EA主文件里加一个余额结构体和数组,就能按交易品种分别存权益曲线。具体做法是先写 struct Balance { double balance[]; };,再声明 Balance symbol_balance[];,这样每个品种一条余额序列,后续统计最大回撤才有分品种的数据基础。 报告逻辑抽到独立的 Report.mqh 里,主文件只用一行 #include "Include/Report.mqh" 挂进来。交易属性则先在 Enums.mqh 补一个 ENUM_DEAL_PROPERTIES 枚举,从 D_SYMBOL=0 到 D_ALL=10 共 11 个标识,覆盖品种、注释、类型、进出场、价格、盈亏、成交量、库存费、手续费、时间。 Report.mqh 中 GetHistoryDealProperties() 接收订单号和属性标识两个参数,返回对应的 HistoryDealProperties 结构。ToString.mqh 里放了一批小函数,空值或零直接吐横线 "-",避免 csv 里出现怪异空白。 真正落盘靠 CreateSymbolBalanceReport():先写表头(多品种测试时表头字符串会自适应拉长),再把各属性转字符串顺序写进 LastTest.csv。其中 MaxDrawdownToString() 按局部极大值算回撤,只有刷新局部最大值时才返回数值,其余行填 "-"。 挂钩点在 OnTester(),测试结束时几行调用即可触发。跑完策略测试器后,文件落在 C:\ProgramData\MetaQuotes\Terminal\Common\Files 目录下的EA子文件夹,用记事本打开 LastTest.csv 就能看到逐行成交报告。外汇与贵金属测试请留意点差和库存费会让回撤数据明显放大,属高风险验证环境。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//--- 余额数组</span>
<span class="keyword">class="kw">struct</span> Balance
  {
   <span class="keyword">class="type">class="kw">double</span>                    balance[];
  };
<span class="comment">class=class="str">"cmt">//--- 所有交易品种的余额数组</span>
Balance symbol_balance[];
<span class="comment">class=class="str">"cmt">//--- 包含自定义库</span>
<span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"Include/Auxiliary.mqh"</span>
<span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"Include/Enums.mqh"</span>
<span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"Include/Errors.mqh"</span>
<span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"Include/FileFunctions.mqh"</span>
<span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"Include/InitializeArrays.mqh"</span>
<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"Include/Report.mqh"</span></span>
<span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"Include/ToString.mqh"</span>
<span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"Include/TradeFunctions.mqh"</span>
<span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"Include/TradeSignals.mqh"</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| 交易属性枚举                                                    |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">enum</span> ENUM_DEAL_PROPERTIES
  {
   D_SYMBOL     = <span class="number">class="num">0</span>, <span class="comment">class=class="str">"cmt">// 交易的交易品种</span>
   D_COMMENT    = <span class="number">class="num">1</span>, <span class="comment">class=class="str">"cmt">// 交易的注释</span>
   D_TYPE       = <span class="number">class="num">2</span>, <span class="comment">class=class="str">"cmt">// 交易的类型</span>
   D_ENTRY      = <span class="number">class="num">3</span>, <span class="comment">class=class="str">"cmt">// 交易的进场类型 - 入, 出, 反转</span>
   D_PRICE      = <span class="number">class="num">4</span>, <span class="comment">class=class="str">"cmt">// 交易的价格</span>
   D_PROFIT     = <span class="number">class="num">5</span>, <span class="comment">class=class="str">"cmt">// 交易的结果 (利润/亏损)</span>
   D_VOLUME     = <span class="number">class="num">6</span>, <span class="comment">class=class="str">"cmt">// 交易的交易量</span>
   D_SWAP       = <span class="number">class="num">7</span>, <span class="comment">class=class="str">"cmt">// 到平仓的累积库存费</span>
   D_COMMISSION = <span class="number">class="num">8</span>, <span class="comment">class=class="str">"cmt">// 交易的手续费</span>
   D_TIME       = <span class="number">class="num">9</span>, <span class="comment">class=class="str">"cmt">// 交易时间</span>
   D_ALL        = <span class="number">class="num">10</span> <span class="comment">class=class="str">"cmt">// 以上所有的交易属性</span>
  };
<span class="comment">class=class="str">"cmt">//--- 历史中的交易属性</span>
<span class="keyword">class="kw">struct</span> HistoryDealProperties
  {
   <span class="keyword">class="type">class="kw">string</span>              symbol;     <span class="comment">class=class="str">"cmt">// 交易品种</span>
   <span class="keyword">class="type">class="kw">string</span>              comment;    <span class="comment">class=class="str">"cmt">// 注释</span>
   <span class="macro">ENUM_DEAL_TYPE</span>     type;       <span class="comment">class=class="str">"cmt">// 交易类型</span>
   <span class="macro">ENUM_DEAL_ENTRY</span>    entry;      <span class="comment">class=class="str">"cmt">// 方向</span>
   <span class="keyword">class="type">class="kw">double</span>              price;      <span class="comment">class=class="str">"cmt">// 价格</span>
   <span class="keyword">class="type">class="kw">double</span>              profit;     <span class="comment">class=class="str">"cmt">// 利润/亏损</span>
   <span class="keyword">class="type">class="kw">double</span>              volume;     <span class="comment">class=class="str">"cmt">// 交易量</span>
   <span class="keyword">class="type">class="kw">double</span>              swap;       <span class="comment">class=class="str">"cmt">// 库存费</span>
   <span class="keyword">class="type">class="kw">double</span>              commission; <span class="comment">class=class="str">"cmt">// 手续费</span>

「用结构体把每笔成交拆开读」

想做交易复盘,第一步是把历史成交的字段从 MT5 后台拉出来。下面这段定义了一个 HistoryDealProperties 结构体,用 datetime time 存成交时间,其余字段覆盖品种、注释、类型、开平方向、价格、盈亏、手数、库存费与佣金,基本够日常统计用。 GetHistoryDealProperties 这个函数按传入的枚举去取对应字段:字符串类用 HistoryDealGetString,整型类用 HistoryDealGetInteger 并强转枚举,浮点类用 HistoryDealGetDouble。比如 D_PROFIT 分支直接拿到该笔盈亏浮点值,D_TIME 则把整型时间戳转回 datetime。 D_ALL 分支一口气把 9 个字段全填进结构体,省得多次调用。实盘里黄金和外汇杠杆高、点值跳动快,用这套把近 30 天成交导出来,按 D_SYMBOL 分组算净盈亏,可能比肉眼翻终端更准。 把 D_ALL 跑完后再 deal.profit 加总,就能看到某品种真实落袋多少,不用信终端那句模糊的『总收益』。

MQL5 / C++
  class="type">class="kw">datetime                time;       class=class="str">"cmt">// 时间
  };
class=class="str">"cmt">//--- 交易属性变量
HistoryDealProperties   deal;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 根据订单编号取得交易属性                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void GetHistoryDealProperties(class="type">class="kw">ulong ticket_number,ENUM_DEAL_PROPERTIES history_deal_property)
  {
   class="kw">switch(history_deal_property)
     {
      case D_SYMBOL     : deal.symbol=HistoryDealGetString(ticket_number,DEAL_SYMBOL);                 class="kw">break;
      case D_COMMENT    : deal.comment=HistoryDealGetString(ticket_number,DEAL_COMMENT);               class="kw">break;
      case D_TYPE       : deal.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket_number,DEAL_TYPE);   class="kw">break;
      case D_ENTRY      : deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_number,DEAL_ENTRY); class="kw">break;
      case D_PRICE      : deal.price=HistoryDealGetDouble(ticket_number,DEAL_PRICE);                   class="kw">break;
      case D_PROFIT     : deal.profit=HistoryDealGetDouble(ticket_number,DEAL_PROFIT);                 class="kw">break;
      case D_VOLUME     : deal.volume=HistoryDealGetDouble(ticket_number,DEAL_VOLUME);                 class="kw">break;
      case D_SWAP       : deal.swap=HistoryDealGetDouble(ticket_number,DEAL_SWAP);                     class="kw">break;
      case D_COMMISSION : deal.commission=HistoryDealGetDouble(ticket_number,DEAL_COMMISSION);         class="kw">break;
      case D_TIME       : deal.time=(class="type">class="kw">datetime)HistoryDealGetInteger(ticket_number,DEAL_TIME);          class="kw">break;
      case D_ALL        :
         deal.symbol=HistoryDealGetString(ticket_number,DEAL_SYMBOL);
         deal.comment=HistoryDealGetString(ticket_number,DEAL_COMMENT);
         deal.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket_number,DEAL_TYPE);
         deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_number,DEAL_ENTRY);
         deal.price=HistoryDealGetDouble(ticket_number,DEAL_PRICE);
         deal.profit=HistoryDealGetDouble(ticket_number,DEAL_PROFIT);
         deal.volume=HistoryDealGetDouble(ticket_number,DEAL_VOLUME);
         deal.swap=HistoryDealGetDouble(ticket_number,DEAL_SWAP);

常见问题

在EA里用结构体逐笔读取成交记录,按品种写入CSV文件,再用Excel透视表按品种汇总余额并插入折线图即可。
落盘时给每条成交打上品种标签,导出后按品种列筛选,就能在Excel里单独看每个币种的权益走势。
小布可读取你的交易记录并按品种拆出余额序列,直接生成分品种净值视图,省去手动导CSV和画图。
结构体把每笔的开平时间、手数、品种拆开存,代码稍多但后续按字段筛选很省事,回测诊断更清晰。
注意杠杆和滑点会让余额跳变,落盘时保留成交价与手续费字段,Excel里才能真实还原净值而非表面盈利。