如何使用 MetaTrader 和 Google Sheets 创建交易日志·进阶篇
📊

如何使用 MetaTrader 和 Google Sheets 创建交易日志·进阶篇

(2/3)· 手动抄交易记录既慢又易错,本篇把表格变成可 POST 可读取的实时数据库

偏理论进阶 第 2/3 篇
很多交易者把成交截图贴进表格,隔天复盘才发现漏了滑点和手续费。靠手填的日志永远慢半拍,回测和情绪分析都建立在失真数据上。

「搭好交易日志的表格骨架」

开 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 直接报错。外汇与贵金属波动剧烈,这类日志只做记录辅助,不构成任何方向建议。

MQL5 / C++
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 自动发数据才有意义。

MQL5 / C++
<!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 里照着下标取数。

MQL5 / C++
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"
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到订单与持仓的自动归集,你不必自己搭 Web 应用也能先看清盘口节奏。

常见问题

通常选“仅本人”并执行者设为脚本所有者,避免外部随意写入你的交易日志,外汇贵金属本身高风险,日志泄露会放大隐私暴露。
检查脚本里 appendRow 的数组顺序与 EA 端发送的 JSON 键名是否一一对应,顺序错位会导致盈亏列被填成时间。
小布目前内置的是品种页维度的持仓与信号归集,完整自定义表格回写仍需按本篇自建脚本,但可把小布数据作为核对基准。
读取频率控制在秒级以上、单次返回行数限制后影响很小,过度轮询才可能让 EA 在报价tick上出现轻微延迟。