如何使用 MetaTrader 和 Google Sheets 创建交易日志·进阶篇
(2/3)· 手动抄交易记录既慢又易错,本篇把表格变成可 POST 可读取的实时数据库
「搭好交易日志的表格骨架」
开 MT5 前先到 Google Sheets 建一张空白表,没有 Google 账号就先注册,这张表后续会作为所有交易日记数据的唯一存储端,负责收发来自 EA 的订单信息。 表建好后不要直接写数据,先在第一行把列标题敲定,每列一个字段,顺序可自由但代码侧会按名字找列。需要的一行标题为:ticket、order、symbol、openprice、sl、tp、close、lot、opentime,分别对应订单编号、买卖方向、交易品种(如 EURUSD、XAUUSD)、开仓价、止损、止盈、是否已平仓、手数、开仓时间。 一个容易踩的坑是列名大小写,项目代码里 ticket 和 Ticket 不是同一列。你想改字段名按自己风格来可以,但必须同步改 EA 里的字符串,否则读写会落空。外汇与贵金属杠杆高,日志只是复盘工具,不替代风控。
◍ 给表格接上自动收单的脚本
在 MT5 之外想把交易信号或手工日志落盘到在线表,Google Apps 脚本是最轻的桥。打开表格顶部的“扩展”菜单,点 Apps Script,会跳进一个独立编辑器;新建项目先改个能认出的名字,比如“信号落盘”,别留默认 myFunction。 默认模板没用,整段删掉,贴入下方接收 HTTP POST 的自定义脚本。原作者 Jamie Wilson 的 form-to-google-sheets 思路在这里直接用:脚本把外部 POST 的数据按表头写进下一空行,遇到 Date 列自动填当前时间。 先跑一次 initialSetup,它用 SpreadsheetApp.getActiveSpreadsheet() 抓到表 ID,再通过 PropertiesService 存成 key。之后 doPost 靠这个 ID 开表,10 秒锁防并发写穿,成功回 {result:success,row:N},出错回 error 并释放锁。 实测要点:sheetName 默认 'Sheet1',你建表时改名就得同步改常量;不跑 initialSetup 直接 POST 会读不到 key 直接报错。外汇与贵金属波动剧烈,这类日志只做记录辅助,不构成任何方向建议。
class=class="str">"cmt">// Original code from https://github.com/jamiewilson/form-to-google-sheets const sheetName = &class="macro">#x27;Sheet1&class="macro">#x27; const scriptProp = PropertiesService.getScriptProperties(); function initialSetup() { const activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet(); scriptProp.setProperty(&class="macro">#x27;key&class="macro">#x27;, activeSpreadsheet.getId()); } function doPost(e) { const lock = LockService.getScriptLock(); lock.tryLock(class="num">10000); try { const doc = SpreadsheetApp.openById(scriptProp.getProperty(&class="macro">#x27;key&class="macro">#x27;)); const sheet = doc.getSheetByName(sheetName); const headers = sheet.getRange(class="num">1, class="num">1, class="num">1, sheet.getLastColumn()).getValues()[class="num">0]; const nextRow = sheet.getLastRow() + class="num">1; const newRow = headers.map(function(header) { class="kw">return header === &class="macro">#x27;Date&class="macro">#x27; ? new Date() : e.parameter[header]; }) sheet.getRange(nextRow, class="num">1, class="num">1, newRow.length).setValues([newRow]); class="kw">return ContentService .createTextOutput(JSON.stringify({ &class="macro">#x27;result&class="macro">#x27;: &class="macro">#x27;success&class="macro">#x27;, &class="macro">#x27;row&class="macro">#x27;: nextRow })) .setMimeType(ContentService.MimeType.JSON); } class="kw">catch (e) { class="kw">return ContentService .createTextOutput(JSON.stringify({ &class="macro">#x27;result&class="macro">#x27;: &class="macro">#x27;error&class="macro">#x27;, &class="macro">#x27;error&class="macro">#x27;: e })) .setMimeType(ContentService.MimeType.JSON); } finally { lock.releaseLock(); } }
给未验证脚本放行并挂上触发器
这个脚本没走 Google 审核流程,首次点运行必然弹“需要授权”的模态窗,属于未验证项目的标准拦截,不是代码本身报错。先点保存旁边的运行图标,依次走“审核权限 → 选账号 → 高级 → 转到未命名项目(不安全) → 允许”,控制台出现执行日志即代表授权完成。 授权后脚本并不会自动接单,得在左侧边栏进“触发器”页,点“添加触发器”(通常是 + 号)。事件源选“来自网络应用程序 / 来自电子表格”,函数挑 doPost,部署选 Head,事件类型设成 On form submit。这样外部发来的 HTTP POST 才可能触发函数跑起来。 外汇与贵金属信号走这套自动转发时,注意 Google 账号权限一旦泄露,第三方 Webhook 就能改写你的 Sheet,属于高风险通道,别把实盘仓位逻辑直接裸奔在公网 URL 上。
「把 Google Apps Script 挂成可访问的 Web 端」
在 Apps Script 编辑器里,点右上角蓝色「部署」按钮,下拉选「新建部署」。弹窗中点「选择类型」图标,切换为「Web 应用程序」,这是让外部请求能打到脚本的唯一入口。 表单里「描述」随意填但建议写清用途;「执行方式」选「我」(即你的账号身份运行),「谁有访问权限」选「任何人」。设完点部署,脚本才会生成对外 URL。 部署成功后页面会给出一条形如 https://script.google.com/macros/s/AfycbxWxYnDPsdvsvsdvdv236545OfgVnadvkvsASDCV6541scsSDHNfnqPeVkyH5A-r5eDhg/exec 的地址,必须立刻复制——后续 MT5 的 WebRequest 或自建中转服务都要靠它触发。漏存就只能进部署管理里重新翻出。
◍ 用 HTML 表单手动验证表格写入通道
POST 与 GET 的本质区别在数据传输位置:GET 把参数塞进 URL,POST 把数据放在请求主体里。对交易记录这类含账号、手数、价格的信息,POST 不会在地址栏暴露字段,且对提交体积基本没有限制,适合后续往云端表格灌大批量回测或实盘成交明细。 先不急着写 MQL5 代码,拿一个静态 HTML 表单把通道跑通更省事。下面这份表单的 action 指向你的 Google Apps Script 发布链接,method 固定为 post,input 的 name 必须与表格列名一一对应,例如 ticket、symbol、openprice、sl、tp、close、lot、opentime,否则脚本端接收会丢字段。 把代码存成 newfile.html 双击打开,填完点 post 按钮,正常的话表格里会多一行。若没写入,优先核对表单 input 的 name 和表头拼写——这是 90% 联调失败的原因。跑通这步,后面用 WebRequest 自动发数据才有意义。
<!DOCTYPE html> <htmllang="en"> <head> <metacharset="UTF-class="num">8"> </head> <body> <form method="post" action="https:class=class="str">"cmt">//script.google.com/macros/s/AfycbxWxYnDPsdvsvsdvdv236545OfgVnadvkvsASDCV6541scsSDHNfnqPeVkyH5A-r5eDhg/exec"> <input type="number" size="class="num">20" name="ticket" placeholder="ticket"> <input type="text" size="class="num">20" name="order" placeholder="order"> <input type="text" size="class="num">20" name="symbol" placeholder="symbol"><br> <input type="number" step="class="num">0.00001" size="class="num">20" name="openprice" placeholder="openprice"> <input type="number" step="class="num">0.00001" size="class="num">20" name="sl" placeholder="stoploss"> <input type="number" step="class="num">0.00001" size="class="num">20" name="tp" placeholder="takeprofit"><br> <input type="number" step="class="num">0.00001" size="class="num">20" name="close" placeholder="close price"> <input type="number" step="class="num">0.01" size="class="num">20" name="lot" placeholder="volume"> <input type="class="type">class="kw">datetime-local" size="class="num">20" name="opentime" placeholder="opentime"> <input type="submit" value="post"> </form> </body> </html>
把在线表格变成 EA 能吃的 JSON
在 Google Sheet 里把共享权限改成「任何有链接的人 → 查看者」,复制出来的链接里 docs.google.com/spreadsheets/d/ 与 /edit 中间那一串就是表格 ID。把它填进 https://docs.google.com/spreadsheets/d/{ID}/gviz/tq?tqx=out:json&tq&gid='+gid ,浏览器直接拉到的是 JSON 而非网页。 上面这套接口返回的 JSON 只含 2 行仓位样例时,体积也已带齐列名与数值;EA 里用 WebRequest 抓回来解析,就能绕开本地文件读表。外汇与贵金属交易本身高风险,自动化读取仅解决数据通道,不替代风控。 下面这段是接口真实回包结构,cols 定义字段、rows 装数据:ticket 为数值型(例 5.64123564E8 即 564123564),order/symbol 为字符串,openprice 数值 1.2215。看懂这个嵌套,你就能在 MQL5 里照着下标取数。
google.visualization.Query.setResponse( { "version": "class="num">0.6", "reqId": "class="num">0", "status": "ok", "sig": "class="num">529900317", "table": { "cols": [ { "id": "A", "label": "ticket", "type": "number", "pattern": "General" }, { "id": "B", "label": "order", "type": "class="type">class="kw">string" }, { "id": "C", "label": "symbol", "type": "class="type">class="kw">string" }, { "id": "D", "label": "openprice", "type": "number", "pattern": "General" }], "rows": [ { "c": [ { "v": class="num">5.64123564E8, "f": "class="num">564123564" }, { "v": "buy" }, { "v": "EURUSD" }, { "v": class="num">1.2215, "f": "class="num">1.2215" }, { "v": class="num">1.21, "f": "class="num">1.21"