市场模拟(第六部分):将信息从 MetaTrader 5 传输到 Excel·进阶篇
(2/3)· 非程序员最头疼的 MT5 与 Excel 互通,本篇拆解不需深啃 COM 的落地路径
很多交易者拿着 Excel 管风控,却被 MT5 没有原生导出功能卡住,要么硬学 COM 编程,要么放弃用表盘盯头寸。其实对持仓两三天、同时铺二十个多空仓的人来说,轻微延迟的自动接收就够用,没必要把计算搬出平台。
「为什么选服务而不是 EA 或指标」
拿到 MT5 想做轻量实时任务,路线很多:Python 拉实时报价、RTD/DDE 外挂都行,但纯 MQL5 里最快落地的其实是服务(Service)。它不挂图表、终端没开图也能常驻,这点区别于 EA、指标、脚本。
MT5 四类程序定位要先分清:EA 负责发单交易;指标只在图上画信息;脚本绑特定图表、跑完就退;服务不绑图表,启停快却可后台常活。四类各有用处,但只有服务脱离图表独立运行。
代码层面服务和脚本几乎同构,差别仅在一行属性声明。脚本里加了 #property service 就变服务,删掉就退回脚本(此时必绑图表)。本方案用服务,若你想改成脚本,去掉那行即可,逻辑不用动。
外汇/贵金属波动剧烈、杠杆高风险大,任何常驻程序上线前请在模拟盘验证稳定性。
class="macro">#class="kw">property service
把报价文件接进 Excel 的实操路径
MQL5 出于安全限制,只允许读写 MQL5 目录下 FILES 文件夹里的文件,不能随便碰系统其他路径。所以第一步要在 MetaEditor 导航窗格里定位到这个 FILES 目录,把后续要用的 QUOTE.CSV 先丢进去。 在 FILES 里建好 QUOTE.CSV 后,打开 Excel 新建空白表,用「从文本/CSV」导入(老版本藏在「获取数据」下面)。文件浏览器指到 MQL5\FILES\QUOTE.CSV,加载时选「加载到…」并放在默认高亮区域即可,图 05 就是刚导进来的静态样子。 关键点:没用 RTD 或 DDE,Excel 不会自己刷。选中表格切到「查询」选项卡点「属性」,把刷新间隔设成 60 秒、允许后台刷新,确认后 Excel 才会按文件变动约每 60 秒拉一次新值。 验证方法很直白——用记事本打开 QUOTE.CSV 改几行数值存盘,别关编辑器,等一分钟看 Excel 表是不是自动变成新内容。下面这段就是 CSV 里应有的格式样例,分号隔开标的与价格,外汇贵金属实盘接这思路同样高风险,行情断流或写盘失败都可能让表停更。
PETR4; VALE3; ITUB3; PETR4;class="num">30.80 VALE3;class="num">190.31 ITUB3;class="num">25.89
◍ 把报价喂给 Excel 的服务怎么写
在 MT5 里做服务比想象中轻量:不需要把资产显示在市场观察窗口,直接跑后台服务也不会拖慢终端。代码用类封装,方便以后把同样的逻辑挪去给其他外部程序传数据,不局限于 Excel。
第 2 行 #property service 是核心开关,告诉编译器这是服务而非脚本;删掉它就能当脚本挂到图表上跑,逻辑不受影响。第 8 行 input string user01 = "Quote.csv" 让用户指定共享文件名,必须和前一节建好的文件一致,否则系统起不来。
类内部从第 12 行 private: 起封装变量与日志函数,第 18 行 Message 统一在终端打印「Sharing service with Excel: […]」方便跟踪状态。构造函数第 25 行接收文件名参数,第 33 行尝试读文件,读不到就第 35–36 行报错返回;能读就按第 41 行用分号 ; 切出交易品种,第 45 行规范化大小写,这里故意不校验品种是否存在——出错时 Excel 会显示异常,人眼能秒查。
真循环从第 65 行开始,迭代间靠第 75 行 Sleep 按秒暂停,不会吃满 CPU。第 68–69 行拼好要写的内容,第 70 行用 FILE_SHARE_WRITE 标志开文件,解决生产者-消费者场景里「Excel 正读时 MT5 写」的冲突,第 72 行一次性写入、第 73 行立刻关文件。外汇与贵金属报价共享涉及实时外部读写,存在文件锁与延迟误差,属高风险操作,实盘前请在模拟环境验证。
想自己改推送字段,直接动第 69 行构建字符串那一段就行;比如塞进去 bid/ask 或持仓量,Excel 侧不用重连就能看到新列。
class="macro">#class="kw">property service class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property description "Quote sharing service between" class="macro">#class="kw">property description "MetaTrader class="num">5 and Excel" class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ input class="type">class="kw">string user01 = "Quote.csv"; class=class="str">"cmt">//FileName class=class="str">"cmt">//+------------------------------------------------------------------+ class C_ShareAtExcel { class="kw">private : class="type">class="kw">string szSymbol[], szFileName; class="type">int maxBuff; class="type">bool bError; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">void Message(const class="type">class="kw">string szMsg) { PrintFormat("Sharing service with Excel: [%s].", szMsg); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">public : class=class="str">"cmt">//+------------------------------------------------------------------+ C_ShareAtExcel(class="type">class="kw">string szArg) :bError(true),
「从 CSV 批量灌入交易标的」
这段逻辑负责把一个 ANSI 编码的 CSV 文件读进内存,逐行解析后把标的代码塞进 szSymbol 动态数组。文件打开失败会直接弹 Message("Failed") 并 return,不会让后续数组越界。 循环里用 FileIsEnding 判断收尾,每次 FileReadString 抓一整行;只有 StringSplit 按分号切出来的字段数大于 1 才认定是有效行,避免空行或脏数据进数组。 有效行取 szRet[0] 作为原始代码,ArrayResize 把 szSymbol 扩 1 格,写入后跑一次 StringToUpper 统一成大写,maxBuff 自增记录总量。外汇与贵金属标的最好先确认 CSV 里是 EURUSD 这类无斜杠写法,否则匹配 Symbol() 时可能漏掉。 开 MT5 把这段嵌进 EA 初始化,放一个两列以上的 test.csv 到 MQL5/Files,能直接看到 szSymbol 被填满,比手写十几个 Symbol 参数省事。
maxBuff(class="num">0), szFileName(szArg) { class="type">int file; class="type">class="kw">string sz0, szRet[]; if ((file = FileOpen(szFileName, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE) { Message("Failed"); class="kw">return; } while (!FileIsEnding(file)) { sz0 = FileReadString(file); if (StringSplit(sz0, &class="macro">#x27;;&class="macro">#x27;, szRet) > class="num">1) { ArrayResize(szSymbol, maxBuff + class="num">1); szSymbol[maxBuff] = szRet[class="num">0]; StringToUpper(szSymbol[maxBuff]); maxBuff++; } }