如何使用 MetaTrader 和 Google Sheets 创建交易日志·综合运用
(3/3)· 把订单流自动写进表格并回读分析,告别手工记账的时间损耗与录入偏差
◍ 交易记录 JSON 里的字段排列
上面这段是某笔订单在表格数据源里的原始 JSON 片段,用 c 数组承载一列列单元格值,顺序固定为: ticket、类型、品种、开仓价、止损价、止盈价、浮动盈亏……
以 USDJPY 那笔为例,v 数值分别为 147.22、147.44、146.0,对应开仓卖单挂在 147.22,止损 147.44,止盈 146.0,距离开仓 122 点和 222 点。外汇与贵金属杠杆高,这类止损止盈间距若碰上央行讲话可能瞬间被打穿。
前面那一串 null 是预留的扩展列(如手续费、魔术码、注释),实际渲染时会被前端忽略;最后那个 {"v": null} 通常表示该行已平仓但盈亏字段尚未回写。打开 MT5 的自定义报表导出,对照这种结构能快速定位哪一段是订单主体、哪一段是垃圾填充。
{
"c": [
{"v": class="num">123123.0, "f": "class="num">123123"},
{"v": "sell"},
{"v": "USDJPY"},
{"v": class="num">147.22, "f": "class="num">147.22"},
{"v": class="num">147.44, "f": "class="num">147.44"},
{"v": class="num">146.0, "f": "class="num">146"},
{"v": class="num">0.0, "f": "class="num">0"}
]
}「谷歌表格数据体里的手数与时点残片」
这段 JSON 是某次 MT5 信号推送接口导出的表格数据残片,核心只塞了两列有效信息:手数 0.02,以及一笔标注为 2024-09-04 20:43 的时点记录。 其余从第三个数组元素开始全是 null,一直排到 parsedNumHeaders 显示表头数为 1,说明这张表原本设计了很多列,但实际只落了极少量字段。 在写 EA 接收第三方信号时,这种稀疏结构很常见——你不能假设每行都有值,遍历数组必须先用 if(v!=null) 过滤,否则容易把空手数当 0 手下单,在外汇和贵金属上引发非预期市价单,风险偏高。 直接把上面这段贴进 MT5 的 JSON 解析函数做反序列化,验证 null 跳过逻辑是否生效,比盲目信任信号源更稳妥。
},
{
"v": class="num">0.02,
"f": "class="num">0.02"
},
{
"v": "Date(class="num">2024,class="num">8,class="num">4,class="num">20,class="num">43,class="num">0)",
"f": "class="num">2024-class="num">09-class="num">04 class="num">20:class="num">43"
}, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
{
"v": null
}]
}],
"parsedNumHeaders": class="num">1
}
});用 EA 把 MT5 订单推到云表格
在 MT5 里落地一套跨账户交易日志,核心不是表格本身,而是让终端主动把成交数据往外推。Google Sheet 只承担云数据库角色,前面用 Apps Script 开了 HTTP 入口,这一步在早先的搭建里已经跑通,本次不重复。 新活落在 MetaTrader 侧:写一个自定义 EA,借助 WebRequest 走 HTTP POST,把 symbol、volume、order type 这些字段实时发到 Sheet。这样多账户、异地登录时,持仓与注释都能在同一张表里被监控和共享,外汇与贵金属品种波动大、滑点频发,跨账户核对前先确认券商允许外部 POST 请求。 EA 里关键就一行触发逻辑——订单成交事件里调 WebRequest 拼 JSON 出去,比手动导出 csv 更不容易漏单。
◍ 用 EA 把持仓与挂单推到在线表格
想在 MT5 里实时把账户里的未平仓头寸和挂单甩到 Google Sheet,得靠一个 EA 来干脏活。核心逻辑拆成两块就够了:一块负责把当前账户的仓位和待成交单捞出来,另一块负责把这批数据通过 HTTP 推走。 第一块建议写成独立函数,比如命名为 OrderList,它只做一件事——遍历账户,把每张单的子号、品种、方向、手数、开价、止损止盈捞成结构化数组。第二块命名为 UpdateDatabase,调用 WebRequest 把 OrderList 产出的内容 POST 到表格接口。 外汇与贵金属杠杆高,推送频率若设太密,WebRequest 超时可能漏单,实盘前先在策略测试器用模拟账户验证连通性。
「遍历终端里的活单与挂单」
要把 MT4/MT5 当前所有未平仓位和挂单抓出来,核心就是按总数倒序循环,避免删除或平仓导致索引错位。MQL5 用 PositionsTotal() 拿到持仓数,MQL4 用 OrdersTotal() 覆盖订单与持仓,两者都从 n-1 跑到 0。 MQL5 段先用 PositionGetTicket 取票据号,再用 PositionSelectByTicket 锁定,失败就 continue 跳过;随后用 PositionGetString / Integer / Double 把品种、类型、手数、开价、时间、SL、TP 读出来。MQL4 段则是 OrderSelect(conter, SELECT_BY_POS, MODE_TRADES) 选中后直接 OrderXxx() 系列取字段。 下面两段代码可直接贴进 EA 测:MQL5 版本在 0 号位起循环到 PositionsTotal()-1,MQL4 版本用 OrdersTotal()-1 同样倒序。跑起来后,若 UpdateDatabase 换成 Print 输出,能在专家日志看到每张活单的 ticket 与开仓价,外汇与贵金属杠杆高,活单暴露的 SL/TP 距离可能瞬间被扫,验证时先用模拟盘。
class="type">void OrderList { for(class="type">int conter = PositionsTotal() - class="num">1; conter >= class="num">0; conter -= class="num">1) { const class="type">class="kw">ulong ticket = PositionGetTicket(conter); if(ticket == class="num">0 || !PositionSelectByTicket(ticket)) class="kw">continue; class="type">class="kw">string symbol = PositionGetString(POSITION_SYMBOL); class="type">int order_type = PositionGetInteger(POSITION_TYPE); class="type">class="kw">double volume = PositionGetDouble(POSITION_VOLUME); class="type">class="kw">double open_price = PositionGetDouble(POSITION_PRICE_OPEN); class="type">class="kw">datetime open_time = PositionGetInteger(POSITION_TIME); class="type">class="kw">double stop_loss = PositionGetDouble(POSITION_SL); class="type">class="kw">double take_profit = PositionGetDouble(POSITION_TP); class=class="str">"cmt">// Send trade details via WebRequest UpdateDatabase(symbol, order_type, volume, open_price, stop_loss, take_profit, ticket, open_time); } } class="type">void OrderList { class=class="str">"cmt">// Loop through all open trades for(class="type">int conter = OrdersTotal() - class="num">1; conter >= class="num">0; conter -= class="num">1) { if(OrderSelect(conter, SELECT_BY_POS, MODE_TRADES)) { class=class="str">"cmt">// Collect trade data class="type">int ticket = OrderTicket(); class="type">class="kw">string symbol = OrderSymbol(); class="type">int order_type = OrderType(); class="type">class="kw">double volume = OrderLots(); class="type">class="kw">double open_price = OrderOpenPrice(); class="type">class="kw">datetime open_time = OrderOpenTime(); class="type">class="kw">double stop_loss = OrderStopLoss(); class="type">class="kw">double take_profit = OrderTakeProfit(); class=class="str">"cmt">// Send trade details via WebRequest UpdateDatabase(symbol, order_type, volume, open_price, stop_loss, take_profit, ticket, opentime); } } }
把持仓快照推到 Google Sheet
拿到 OrderList 返回的逐单明细后,真正往外发数据的是另一个函数:它把交易品种、订单类型、手数、开仓价、止损、止盈、票号和开仓时间收齐,拼成 HTTP POST 体再送出。 URL 填的是 Google Apps Script 发布的公开表单接口,每人必须换成自己的脚本地址,否则数据会写进别人表里。 下面这段是核心发送逻辑。注意 datastring 用 & 拼接键值对,DoubleToString 按品种精度 digit 或固定 2 位格式化价格与手数;StringToCharArray 转 UTF-8 字节数组,WebRequest 走 POST,返回码 -1 代表请求层失败,需打印 GetLastError 排查。 实盘里每笔新单触发一次该函数,Sheet 会多一行记录;外汇与贵金属杠杆高,接口若卡顿可能漏单,建议先在模拟盘验证回写延迟。
class="type">void SendTradeData(class="type">class="kw">string orderSymbol, class="type">int orderType, class="type">class="kw">double orderVolume, class="type">class="kw">double openPrice, class="type">class="kw">double stopLoss, class="type">class="kw">double takeProfit, class="type">class="kw">ulong orderTicket, class="type">class="kw">datetime openTime) { class=class="str">"cmt">// Database url. Replace with your own url class="type">class="kw">string url = "https:class=class="str">"cmt">//script.google.com/macros/s/AfycbxWxYnDPsdvsvsdvdv236545OfgVnadvkvsASDCV6541scsSDHNfnqPeVkyH5A-r5eDhg/exec"; class="type">int digit = (class="type">int)SymbolInfoInteger(orderSymbol,SYMBOL_DIGITS); class="type">class="kw">string datastring = "?" +"&ticket="+class="type">class="kw">string(orderTicket) +"&order="+class="type">class="kw">string(orderType) +"&symbol="+class="type">class="kw">string(orderSymbol) +"&openprice="+DoubleToString(openPrice,digit) +"&sl="+DoubleToString(stopLoss,digit) +"&tp="+DoubleToString(takeProfit,digit) +"&close=class="num">0" +"&lot="+DoubleToString(orderVolume,class="num">2) +"&opentime="+(class="type">class="kw">string)openTime; class="type">int res; class=class="str">"cmt">// To receive the operation execution result class="type">char data[]; class=class="str">"cmt">// Data array to send POST requests class="type">char result[]; class=class="str">"cmt">//--- Create the body of the POST request for authorization ArrayResize(data,StringToCharArray(datastring,data,class="num">0,WHOLE_ARRAY,CP_UTF8)-class="num">1); ResetLastError(); res=WebRequest("POST", url, NULL, class="num">0, data, result, str); class="type">class="kw">string responsestring = CharArrayToString(result, class="num">0, class="num">0, CP_UTF8); Print(responsestring); if(res == -class="num">1) Print("Error in WebRequest: ", GetLastError()); else Print("Trade data sent: ", data); }
◍ 用表格把每笔单子拆开算
MT5 每次成交,把开仓时间、品种、买卖方向、手数、平仓盈亏写进一张在线表,表里的图表就能实时反映你的全部历史仓位。 能算的东西很直接:买单数、卖单数、总手数、盈利单与亏损单各自的数量、平均盈利额、平均亏损额,以及任何你临时想到的切片维度。外汇与贵金属杠杆高,样本量太小的时候这些均值很容易失真,先看清楚分母再下结论。 我们做好的那张日记模板里,详细列字段都带了公式——点开链接看「列公式」那一栏,就能知道原始成交清单是怎么被汇总成分析结果的。复制一份改成自己的数据源,比从零搭表省事得多。
「记住这一条就够了」
把 MT5 里的成交明细自动推到 Google Sheets,本质就是 EA 里发一个 HTTP POST,靠 Google Apps Script 接住写表。原文给的 mq4 与 mq5 两个版本压缩包分别是 10.03 KB 和 9.38 KB,直接拖进对应终端就能跑,比手工记日志省事,也少抄错。
有读者在 5.00 build 5572 上编译片段报过 lvalue expected 的错,作者回应说文章里贴的是教学截断码,得用文末完整版才不会炸。外汇和贵金属杠杆高、滑点狠,这套日志只管记录不替你管控风险,别以为有了表就能乱开仓。
真要验证,就开 MT5 把文末 mq5 的 WebRequest 那行单独拎出来看:
res=WebRequest("POST", url, NULL, 0, data, result, str);
这里 str 是出参容器,老版本终端或截断代码最容易在这翻车,更新到当前正式版再试一次最稳。
res=WebRequest("POST", url, NULL, class="num">0, data, result, str);