借助PythonAnywhere对接MetaTrader与谷歌表格:安全数据传输指南
引言
对MetaTrader交易者来说,获取并分析交易数据至关重要,有助于规避交易失误并做出更优的后续决策。平台自带数据导出工具与函数,但都无法直接把数据传输到云端服务。把图表、历史记录与日志数据自动上传至云端,能够极大辅助交易者的决策流程。
市面上有多种方案与工具,可以把MetaTrader的数据传输到谷歌表格这类云端平台:部分方案免费,但安全性不足;另一些安全性较好,但需要付费或者存在访问限制。交易者希望借助历史交易数据分析来提升收益,但要找到一个低成本的云端数据导出方案,往往并非易事。
MetaTrader本身缺少云端自动备份功能,交易者经常丢失重要交易记录和各类有效信息,同时也难以充分利用云端现成的数据可视化与数据分析工具。本文旨在讲解如何以零额外成本、高安全性地实现MetaTrader到谷歌表格的数据自动传输,借助这些唾手可得的工具,从而更好地辅助未来交易决策。
本文可为这类希望将数据传输到云端平台或云存储的交易者和开发者提供参考。文中将介绍如何使用免费且安全的工具,例如谷歌服务账号密钥,以及部署在PythonAnywhere(一款免费、安全、基于网页的云平台)上的代理服务器。
为什么选择谷歌表格与服务账号密钥?
谷歌表格是十分理想的选择,它基于云端,存储在其中的数据可随时随地访问。交易者可以读取导出到谷歌表格的交易数据,随时随地展开分析,指导后续交易。我们可以使用谷歌表格自带的数据分析工具,还能将它对接谷歌数据工作室或TradingView等其他平台。其他交易者或者协作人员也可以在表格上协同编辑,分享各自的分析结论与观点。
我已经在MQL5市场发布了一款名为Info Exporter MT5工具的应用,可免费下载使用。该工具借助google's apps script实现MetaTrader到谷歌表格的数据传输。
虽然应用脚本免费且易于使用,但安全性不足。部署后的URL是公开可访问的,任何拿到该链接的人都可能通过该脚本接口读取或写入表格数据。还有很多其他方案,例如工作负载身份联合(Workload Identity Federation)与谷歌表格API,但是要么配置复杂,要么收费,或是仅提供有限免费额度。
服务账号密钥安全性更高、免费,并且搭建门槛更低。
您可以观看视频在代理服务器中使用服务账号密钥访问谷歌表格,详细了解如何配置谷歌服务账号密钥,并在程序中调用。
为什么选用PythonAnywhere.com搭建代理服务器?
MetaTrader无法直接对接谷歌表格及其API。因此,代理服务器是向谷歌表格传输数据的一个较好方案。使用PythonAnywhere这类云端平台,能够保障服务的可用性、可扩展性、安全性与稳定性。可用性:本地电脑离线时,服务依旧在线;便捷共享性:只要提供有效的表格ID,任何持有链接的用户都可以使用;安全性:平台经过充分测试,由专业工程师维护;可靠性:服务稳定可用(免费版同样具备)。
PythonAnywhere.com为用户提供一个相当实用的免费套餐,我们可以用它搭建这类项目所需的简易代理服务器。免费版代理服务器的URL有效期为三个月,但只要在到期前手动续期,就可以持续使用。

这项免费服务会提供一个可访问的URL,我们可以将其作为代理服务器的API接口,用来访问谷歌表格,还可实现读取表格、展示静态信息页面等各类功能。平台提供控制台,可以执行终端命令,主要用于安装依赖库,以及处理文件、网络相关任务,例如修改错误日志文件等。
我们还可以使用MySQL数据库,配合API服务完成信息存储或者数据缓存。平台支持定时任务功能,可以创建Cron定时任务,使其在每日指定时间自动运行,无需手动执行。同时还具备文件新建、编辑、删除工具,可通过网页界面查看错误日志、配置网站安全、创建Python虚拟环境,以及使用其他各类工具。
我在视频中尽可能完整讲解了在PythonAnywhere.com上搭建、修改、部署代理服务器所需的必要内容,视频中搭建的代理服务器和本文所使用的完全一致。
数据流图
整个数据传输流程原理通俗易懂。MetaTrader作为数据源,保存着用于深度分析、辅助制定盈利交易决策的各类数据;数据会发送至部署在pythonanywhere.com云端平台的代理服务器。代理服务器借助谷歌认证工具包,以及由谷歌服务账号密钥生成的身份凭证,将收到的数据写入已授权的谷歌表格。以下示意图可以更直观地展示整个流程。

这是将MetaTrader数据传输至谷歌表格的免费且安全方案之一。无论是本地部署,还是部署在PythonAnywhere这类经过验证的安全云平台,通过这种方式,代理服务器只会访问已获得授权的表格。
创建服务账号密钥的流程
谷歌云控制台提供了许多免费且实用的功能。谷歌服务账号就是其中之一。服务账号位于IAM与管理员板块,如下图所示:

进入服务账号管理页面前,请先新建项目,或者选择一个已有的项目。服务账号页面如下图所示,页面顶部有创建服务账号按钮。

在创建服务账号的步骤中填写所需信息,完成后会生成一个服务账号邮箱,后续需要在谷歌表格的共享设置中,将该服务帐号邮箱加入共享名单。此时,密钥ID一栏为空,需要进入刚创建好的服务账号详情页面生成密钥,如下图所示:

点击添加密钥按钮,即可选择创建密钥,并下载JSON或P12格式的身份证明文件。选择JSON格式,文件会自动下载,该文件将由代理服务器用于向Google API进行身份验证。
下一步是为项目启用表格API。该选项位于API和服务板块,找到谷歌表格API并点击启用即可。


在前面“为什么选择谷歌表格与服务账号密钥”章节中,我附上了讲解该整套流程的详细视频。
代理服务器程序
如果您看过上一章节“为什么选用PythonAnywhere搭建代理服务器”里分享的视频,就能更好理解以下将要讲解的代码。我们将编写一个简易Flask应用,借助谷歌认证库函数以及服务账号密钥,实现谷歌表格的数据更新。
首先,我们需要在程序中导入所需的库函数。
from flask import Flask, request, jsonify from google.oauth2 import service_account from googleapiclient.discovery import build
如果系统或您的虚拟环境中尚未安装上述库,则需要使用pip命令进行安装。相关操作我也在视频里做过讲解。
接下来需要创建Flask应用实例,这样后续才能在程序里创建API端点(GET、POST、PUT、DELETE)、路由以及其他功能。用来完成实例化的代码如下:
app = Flask(__name__)
接下来,指定创建服务账号密钥时下载的JSON文件所在路径。我们还可以定义权限范围,将访问权限限定在指定资源,也就是谷歌表格。随后利用密钥文件和权限范围生成密钥对象,初始化谷歌API客户端时会用到该对象。
SERVICE_ACCOUNT_FILE = 'service_account.json' SCOPES = ['https://www.googleapis.com/auth/spreadsheets'] credentials = service_account.Credentials.from_service_account_file( SERVICE_ACCOUNT_FILE, scopes=SCOPES )
下一步,定义一个名为"update-sheet"的POST接口。
@app.route('/update-sheet', methods=['POST'])
def update_sheet(): 我们通常使用POST请求向服务器发送数据。同时还会传递请求头,请求头内包含数据相关信息,方便服务器解析数据。
data = request.json sheet_id = data['sheet_id'] data_range = data['range'] values = data['values']
在上述代码中,request是Flask对象,保存客户端在POST请求中传入的全部信息,其中就包含JSON数据。以上代码用于提取表格ID、表格数据范围,以及待写入表格的数据内容。
接下来,传入前面创建好的密钥对象,初始化谷歌API客户端库。
service = build('sheets', 'v4', credentials=credentials)
通过service对象,我们就可以更新上面提取到的sheet_id所对应的电子表格。
result = service.spreadsheets().values().update(spreadsheetId=sheet_id, range=data_range, valueInputOption='RAW', body={'values': values}).execute()
上述代码指示谷歌API客户端服务,用传入的值更新指定范围内的电子表格内容。文中附带了包含追加接口的完整代码,供深入研究。完整代码中还包含追加接口、检查工作表名称,以及如果对应工作表不存在则自动创建的逻辑。您也可以按需新增更多接口,例如删除接口等。
MetaTrader程序
此mq5主程序可以导出终端信息、账户信息、历史成交记录与历史订单,还提供输入配置面板,让用户选择需要传输哪一类数据。这里仅讲解账户信息相关代码。以下函数通过引入AccountInfo.mqh头文件来读取账户数据,并将其格式化为字符串数组,再传入StringToCharArray函数转为字符数组,以满足网络请求函数WebRequest的参数要求。
#include <Trade\AccountInfo.mqh> string GetAccountInfo() { CAccountInfo accountInfo; string account_info = "[\"symbol\", \"name\", \"currency\", \"company\", \"balance\", \"credit\", " + "\"profit\", \"equity\", \"margin\", \"login\", \"trade_mode\", \"leverage\", \"limit_orders\", \"margin_mode\"]"; StringAdd(account_info, StringFormat( ", [\"%s\", \"%s\", \"%s\", \"%s\", \"%f\", \"%f\", \"%f\", \"%f\", \"%f\", \"%d\", \"%d\", \"%d\", \"%d\", \"%d\"]", Symbol(), accountInfo.Name(), accountInfo.Currency(), accountInfo.Company(), accountInfo.Balance(), accountInfo.Credit(), accountInfo.Profit(), accountInfo.Equity(), accountInfo.Margin(), accountInfo.Login(), accountInfo.TradeMode(), accountInfo.Leverage(), accountInfo.LimitOrders(), accountInfo.MarginMode() )); return account_info; }
上述函数仅负责准备账户信息相关数据。附件里的完整代码文件ProxyExport.mq5还包含另外三个函数,分别用于准备终端信息、历史成交单以及历史挂单的数据。数据准备完成后,程序会调用下面的WriteToGoogleSheet函数,在该函数内部会进行额外的字符串处理,并调用SheetExporter(表格导出器)。
void WriteToGoogleSheet(string terminalInfos, string historicalDeals, string historicalOrders, string accountInfos) { if(terminalInfos != NULL) { StringReplace(terminalInfos, "\\", "/"); SheetExporter(terminalInfoName, terminalInfos); } if(historicalDeals != NULL) { SheetExporter(historyDealsName, historicalDeals); } if(historicalOrders != NULL) { SheetExporter(historyOrdersName, historicalOrders); } if(accountInfos != NULL) { SheetExporter(accountInfoName, accountInfos); } } void SheetExporter(string sheetName, string data) { string headers = "Content-Type: application/json\r\n"; char postData[], result[]; string responseHeaders; string jsonDataWithSheetName = StringFormat("{\"spreadSheetId\": \"%s\", \"sheetName\": \"%s\", \"data\": [%s]}", spreadSheetId, sheetName, data); // Convert to char array StringToCharArray(jsonDataWithSheetName, postData, 0, StringLen(jsonDataWithSheetName), CP_UTF8); // Send POST request int res = WebRequest("POST", proxyServerUrl, headers, 5000, postData, result, responseHeaders); // Check response if(res != 200) { Alert("Account Infos not exported to Google Sheets returned error ", res, " and get last error is ", GetLastError()); } }
SheetExporter函数会在待发送至代理服务器的JSON字符串中,除业务数据外,额外加入电子表格ID与工作表名称。请注意,您需要在MetaTrader的【工具】→【选项】菜单下,找到“允许访问以下URL的WebRequest”配置项,填入代理服务器地址。同时,该代理服务器地址也需要粘贴到以下输入框内。

数据流演示
遵循上文“创建服务账号密钥的流程”章节操作,生成服务账号密钥并下载JSON文件,代理服务器依靠这份密钥获得目标谷歌表格的写入授权,MetaTrader的数据就可以导出到该表格。之后您可以自行部署代理服务器,或者使用pythonanywhere.com,也可以选用其他您熟悉的平台运行service_accounts_proxy.py代码,对外提供更新数据的API接口。您可以根据自身需求修改代码。
最后一步是编译 ProxyExport.mq5,并将生成的ex5挂载到图表上运行。程序会弹出选项,供您选择需要导出的数据类型,同时还有API地址与电子表格ID的输入框;缺少这两项配置,数据导出功能无法生效。
文中完整演示了创建服务账号密钥、启用谷歌表格API、以及把谷歌表格共享给服务账号邮箱的全部操作。

以下动图演示了在PythonAnywhere平台需要完成的全部必要操作。

同时,我也演示了在MetaEditor与MetaTrader中,让数据导出功能正常运行所需的全部操作步骤。以下动图实时展示MetaTrader数据自动传输至代理服务器、再写入谷歌表格的完整过程。

借助谷歌表格分析交易
我已经将如下交易历史数据导出至谷歌表格,仅展示9行记录。

其中,点数(Pips)计算公式:(平仓价 - 开仓价) × 换算系数。日元货币对换算系数为100,其余品种为10000;盈利计算公式:点数 × 手数 × 10;累计盈利计算公式:上一行累计盈利 + 当前这笔订单盈利。
根据代码设定,上述工作表命名为History_Deals。您可以按需修改工作表名称。接下来,我们新建一个工作表,命名为matrices,并按下图添加标签与计算公式。
| 标签 | 公式 |
|---|---|
| 总交易笔数 | =COUNTA(History_Deals!A2:A) |
| 盈利交易数 | =COUNTIF(History_Deals!K2:K,">0") |
| 亏损交易数 | =COUNTIF(History_Deals!K2:K,"<0") |
| 盈利率% | =FIXED(B2/A2 * 100, 2) |
| 亏损率% | =FIXED(C2/A2 * 100, 2) |
| 平均盈利 | =FIXED(AVERAGEIF(History_Deals!K2:K,">0"), 2) |
| 平均亏损 | =FIXED(AVERAGEIF(History_Deals!K2:K,"<0"), 2) |
| 盈亏比 | =FIXED(F2/ABS(G2), 2) |
| 期望收益 | =FIXED((D2/100*F2)-(E2/100*G2), 2) |
| 总盈利 | =SUM(History_Deals!K2:K) |
| 盈利因子 | =SUMIF(History_Deals!K2:K,">0")/ABS(SUMIF(History_Deals!K2:K,"<0")) |
| 最大回撤 | =MIN(History_Deals!L2:L)-MAX(FILTER(History_Deals!L2:L,History_Deals!L2:L<MIN(History_Deals!L2:L))) |
让我来简要说明一下上表用到的各项评估指标。
- 总交易笔数是指全部已完成交易的总次数。总样本量越大,统计结果就越可靠和准确。
- 盈利交易数是指平仓后获得盈利的交易数量。该值代表您做出正确交易决策的次数。
- 亏损交易数是指平仓后产生亏损的交易数量。该值代表您做出错误交易决策的次数。
- 盈利率是指盈利交易占比;亏损率是指亏损交易占比。请牢记,高胜率并不代表策略一定能盈利。即便盈利率只有30%至40%,只要把控好盈亏比,交易者依旧可以实现盈利。
- 平均盈利与平均亏损代表交易判断正确时平均能赚多少,判断失误时平均会亏多少。我们的策略设计目标之一,就是让平均亏损始终保持在较低水平。
- 盈亏比是平均盈利除以平均亏损。交易者的目标应当让该数值始终大于1。行业黄金标准是指盈亏比保持在1.5:1到2:1之间。
- 期望收益是每笔交易的平均收益。例如,如果该值为20,那么100笔交易下来总共可以盈利2000。
- 总盈利是指所有盈利与亏损相加的合计结果。
- 盈利因子是衡量策略整体盈利能力的指标。交易者应当尽量让该指标高于1.5。
- 最大回撤是累计盈利从峰值回落的最大幅度。交易者需要尽可能降低最大回撤。
填入全部公式后,得到指标汇总表如下。
| 总交易笔数 | 盈利交易数 | 亏损交易数 | 盈利率% | 亏损率% | 平均盈利 | 平均亏损 | 盈亏比 | 期望收益 | 总盈利 | 盈利因子 | 最大回撤 |
| 9 | 7 | 2 | 77.78 | 22.22 | 285.71 | -400.00 | 0.71 | 311.11 | 1200 | 2.5 | 200 |
我们可以绘制累计盈利折线图,以此判断交易的健康度与稳定性。操作路径为插入 → 图表 → 折线图,纵轴选择累计盈利,横轴选择平仓日期,效果如下。

我们还可以通过饼状图对比盈利订单与亏损订单,效果如下:

我们也可以通过柱状图查看各个交易品种的表现,如下图所示:

在谷歌表格中,我们还能完成很多这类数据分析工作。
结论
交易者可以使用这套方案,轻松、安全地将MetaTrader数据传输至谷歌表格,并借助谷歌表格可视化工具挖掘数据价值。本文介绍了两种代理服务器部署方式:自建代理服务器,或是使用pythonanywhere.com这类云端平台托管。云端方案具备良好的安全性与可扩展性。文中对MetaTrader程序进行模块化设计,方便使用者按需选择功能,也便于后续新增功能,例如导出策略测试器日志数据用于进一步分析等。
总而言之,本文提供的这套工具可供交易者与开发者传输MetaTrader数据做后续分析,辅助制定更优的交易决策。
文中已包含代理服务器源码以及MetaTrader脚本源码。
| 文件名 | 描述 |
|---|---|
| service_accounts_proxy.py | 一款简易的Flask应用,提供更新与追加两类API接口。 |
| ProxyExport.mq5 | 一款MetaTrader应用程序,支持用户自主选择需要导出的数据类型。 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/19175
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
新手在交易中的10个基本错误
从新手到专家:使用 Reporting EA 打造详尽的交易报告
快来看看这篇新文章:《MetaTrader 与 Google 表格通过 Pythonanywhere 实现集成:安全数据流指南》。
作者:Ramesh Maharjan
既然可以用MQL实现相同的功能,为什么还要用Python呢?
我们能否在MQL中使用像我在Python服务器中那样使用的Google库?如果可以的话,请分享相关链接,说明如何在MQL中实现。如果能有这种新方法,我将不胜感激。