MQL5 Cookbook: 把交易历史写入文件以及为每个交易品种在Excel中创建余额图表·进阶篇
把成交记录拆成可读字符串
在 MT5 的历史成交回放里,裸枚举值对人眼不友好。下面这段把成交品种和类型映射成中文短串,方便后续直接打印或写 CSV。 品种名若为空的场合,函数返回横线占位,避免日志出现连续空白导致对齐错乱;这个细节在批量扫描几十个品种时尤其影响可读性。 交易类型覆盖买、卖、余额、信用、收费、更正、奖励、手续费八类。DEAL_TYPE_BUY 映射为“买”,DEAL_TYPE_SELL 为“卖”,其余非开平仓类(如 DEAL_TYPE_BALANCE 余额)也一并转串,账户流水审计时不会再对着宏发懵。 复制下面的函数进 EA 或脚本,编译后调用 DealTypeToString() 即可在专家日志里看到中文成交类型,省掉每次翻文档。
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) 就能跑出可读成交清单,比终端默认英文报表省眼。
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_CSV | FILE_WRITE | FILE_ANSI | FILE_COMMON 打开文件。表头写死 10 列:TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,PRICE,SWAP($),PROFIT($),DRAWDOWN(%),BALANCE;若 SYMBOLS_COUNT>1 会循环把每个品种名追加到表头末尾。 |
|---|
外汇和贵金属杠杆高、点差跳空频繁,导出后建议先筛掉 SWAP 为 '-' 的异常行再算期望收益,否则样本偏差可能让结论失真。
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 跑一遍,可能比终端自带报表更贴合你自己的统计口径。
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">// 考虑库存费和手续费