English Русский Español Deutsch 日本語 Português
preview
从新手到专家:报表 EA — 搭建工作流程

从新手到专家:报表 EA — 搭建工作流程

MetaTrader 5 — 示例 |
20 0
Clemence Benjamin
Clemence Benjamin

目录:



引言

这个想法的灵感来源于我开始通过电子邮件收到经纪商每天发送的交易确认信息。这些摘要提供了清晰、专业的交易活动概览,并突出了统计绩效分析的潜力。在探索 MetaTrader 5 终端时,我发现其当前版本包含了一个全面的报表工具,能够以 HTML 和 PDF 格式导出详细报表。然而,它缺乏自动报表发送和统一管理功能。在本次讨论中,我们的目标是探讨交易报表的作用,阐明其所涉及的关键术语,并强调它们对每位寻求明智决策的交易者的实际重要性。 

最终,我们计划与 Python 和其他外部工具合作,使用 MQL5 实现一个自定义报表系统。该解决方案将生成详细报表,确保跨多种文件格式的兼容性,并通过实用可靠的方法支持自动交付。

在检查了从 MetaTrader 5 导出的文件后,发现它们包含有价值的见解和预先计算好的指标,为交易者提供了对其表现的数学视角 — 这些信息可以直接影响交易决策和行为调整。

目前,MetaTrader 5 中的报表通常是通过手动导航获得的。然而,在使用 EA 时,我们有机会通过编程来控制这些报表的生成和发送。虽然一些经纪商已经能够自动发送这些报表,但我们在本次讨论中的目标是开发一个系统,使我们能够根据自己的偏好安排报表的发送 — 自定义发送频率和内容。

在我们努力解决这个问题时,我将以我个人的经历为例,探讨交易报表的实际重要性。我们还将研究这些报表中常见的术语,并讨论如何利用这些术语来改进手动交易策略和 EA 的性能评估。

在 MetaTrader 5 中,您可以通过从“视图”菜单中选择“报表”或使用快捷键 Alt+E 来访问交易报表。报表窗口以清晰、有序的格式展示您交易活动的各个方面。在此窗口中,您还可以选择将报表保存为 HTML 或 PDF 格式,如下图所示:

访问 MetaTrader 5 账户终端报表

在 MetaTrader 5 上访问报表

上图展示了交易报表中通常包含的一些关键组成部分。尽管这些信息极具价值,但许多交易者往往只关注图表和交易执行,而常常忽略定期回顾自己的业绩记录。清晰理解这些报表至关重要,因为它能通过总结过去活动的经验,强化纪律性,并培养更健康的交易心态。

在审阅了我的交易报表后,我主动研究了其中包含的关键术语,并编写了笔记,还附上了实际示例,以便更容易理解。在下一节中,你将找到五个结构清晰的要点,每个见解都提供了简洁而有意义的解释。这些见解将作为我们在本文后续部分探讨的实施蓝图的基础。在我们进入技术构建阶段之前,目标是确保对这些报表概念有扎实的掌握。如果你已经对这些概念很熟悉了,可以跳到实现阶段。


理解 MQL5 中的交易报表

1. 绩效总结

投资回报率:

  • 衡量账户余额相对于其初始资本的增长幅度。
  • 假设从 5000 美元开始,最终获得 12000 美元;这将带来 140% 的增长,这意味着每投入 1 美元,就能获得 2.40 美元。

回撤:

  • 捕捉从最高点至下一个低点之间百分比下降幅度最大的情况。
  • 想象一下,净值达到 12000 美元的峰值,然后跌至 9600 美元 — 20% 的跌幅凸显了复苏前最严重的倒退。

活动指标:

  • 反映交易节奏和一致性 — 每周交易次数、成功率和平均持仓时间。
  • 举例来说,在八周内完成 80 笔交易,相当于每周 10 笔;赢下其中 32 笔,成功率达到 40%;在市场中总共持有 160 天,平均持仓时间为 2 天。

夏普比率:

  • 表示每承受一个单位的波动性,就能获得多少超额收益。
  • 为了说明这一点,比较两个年收益率均为 15% 的系统:每日波动幅度为 ±2% 的系统,其夏普比率明显高于每日波动幅度为 ±6% 的系统,这反映出其净值走势更为平稳。

利润因子:

  • 表示总利润除以总亏损,用以显示盈利交易与亏损交易的效率对比。
  • 假设盈利交易额为 8000 美元,亏损交易额为 5000 美元,利润因子为 1.6,即每亏损 1 美元,就能获得 1.60 美元的收益。

恢复因子:

  • 将净利润与经历的最大回撤进行比较。
  • 假设一项策略净赚 4000 美元,最差情况下亏损 1000 美元;恢复因子为 4 表示利润是最大损失的四倍。

2. 损益明细

毛利润和毛亏损:

  • 盈利交易与亏损交易的总和构成了扣除成本前的原始利润池。
  • 例如,盈利交易总额为 15000 美元,亏损交易总额为 6000 美元,则形成 9000 美元的毛收益差额。

费用和净利润:

  • 从毛利润中减去佣金、库存费和其他费用,才能得出真正的收益。
  • 换句话说,从 9000 美元的毛收益中扣除 1200 美元费用后,净利润为 7800 美元。

月度趋势:

  • 逐月绘制净结果图表,可以揭示业绩的变化情况。
  • 举例来说,1 月份盈利 4000 美元,2 月份盈利 3000 美元,3 月份盈利 800 美元,而 4 月份亏损 500 美元,这凸显了一种需要关注的下降趋势。

3. 风险分析

最大回撤:

  • 定义从峰值到下一个波谷的最大百分比跌幅。
  • 象一下,一个账户从 20,000 美元跌至 14,000 美元,然后反弹 — 这是最糟糕情况下 30% 的跌幅。

连续盈亏:

  • 最长的正负结果连串考验着系统的稳定性以及交易者的心理。
  • 想象一下,连续遭受七次亏损与连续五次盈利,这两种情况都会塑造交易者的信心、纪律和仓位规模。

最大有利波动(MFE):

  • 追踪交易期间达到的最高未实现利润。
  • 举例来说,某个仓位在触发任何平仓指令前,浮动盈利最高可能达到 600 美元。

最大不利波动(MAE):

  • 记录平仓前经历的最深未实现亏损。
  • 考虑同一笔交易在盈利平仓前可能会下跌 200 美元,这表明止损调整可能会有所帮助。

4. 工具/交易品种绩效

按资产分类的胜率:

  • 每个市场的成功率揭示了优势最强的领域。
  • 例如,EURUSD 可能在 48% 的交易中获胜,而 USDJPY 则有 60% 的胜率,这表明后者的信号更强。

利润贡献:

  • 按金融工具划分的净收益显示了收益的实际来源。
  • 例如,EURUSD 为总收益贡献了 9600 美元,而 XAUUSD 则拖累了 1100 美元,这有助于指导资源分配。

集中风险:

  • 突出显示与单一市场相关的资本比例或交易数量。
  • 假设 40% 的资金持有 EURUSD;欧元的突然波动可能会对整体业绩产生不成比例的影响。

每个交易品种的利润因子:

  • 比较每个市场的总盈利与总亏损,从而明确效率。
  • 举例来说,USDJPY 的利润因子可能高达 1.8,而 AUDCAD 的利润因子则为 0.9 — 这决定了应该优先考虑还是避免交易哪些货币对。

5. 活动与行为模式

交易分布:

  • 多空头仓位的平衡揭示了方向性偏好。
  • 例如,持有 70% 的多头仓位表明主要持看涨立场,但在横盘整理的市场中可能会动摇。

自动化与手动:

  • 将算法交易与人工交易进行比较,就能揭示真正的优势所在。
  • 净利润的 65% 来自自动入场,而手动入场仅占 35%,这凸显了该系统的强大之处。

时间分析:

  • 按小时和星期几细分结果,可以确定最佳和最脆弱的时间段。
  • 举例来说,大多数损失可能发生在格林威治标准时间 11:00 至 12:00 之间,这意味着最好避开午餐时间。

风格指标:

  • 平均持仓时间和每周交易量刻画了该交易风格。
  • 举例来说,平均持仓时间为 4 小时,每周交易次数约为 25 次,这就是中等节奏的日内交易策略的特点。


实现

在这个阶段,我们开始着手建立我们的工作流程。得益于上一节对交易报表术语的深入理解,你会发现这一部分更容易理解,因为不熟悉的概念更少了。

为了实现本文的目标,我们将开发一个 EA,用于处理数据导出并准备必要的日志。该 EA 将作为 MetaTrader 5(MQL5)和负责处理历史交易数据并以可移植格式生成最终报表的 Python 库之间的桥梁 — 类似于 MetaTrader 5 终端内置的报表工具。

我们将首先展示一个流程图,概述整个过程,然后详细介绍为使一切正常工作所需的工具和环境设置。本项目的所有组件均基于开源技术,确保任何人都可以使用。

报表 EA 流程图

流程图

首先,请确保您的系统上已安装 MetaTrader 5。接下来需要设置 Python 环境,稍后我会详细解释。请参阅下表,了解本工作流程中使用的简明要求和工具列表。

组件 开源工具 成本 实现说明
数据提取 MetaTrader 5 EA(ReporterEA.mq5) 免费 自定义 CSV 导出功能,支持日期范围筛选
调度引擎 MetaTrader 5 定时器事件(OnTimer()) 免费 MetaTrader 5 终端内部调度
处理 Python 3.10+(Pandas、Matplotlib) 免费 由 EA 通过 ShellExecute 触发
报表交付 smtplib + Gmail SMTP 免费 Python 脚本每天发送多封免费邮件
维护 EA 自动清理功能 免费 MQL5/Python 中的文件轮换和错误处理


开发报表 EA

在这个阶段,我们将按计划开始开发 EA。我将引导您了解代码的每个组成部分,并解释它们如何协同工作以形成一个完整且功能齐全的系统。我们的目标是构建一个稳健的 EA,使其能够无缝地发挥其预期作用。请跟随我们,我们将开发过程分解为清晰、可管理的步骤,确保每个部分都易于理解,并对最终产品做出有意义的贡献。

在 MetaEditor 5 中,打开一个新文件并选择 “EA 交易” 模板。我建议删除自动生成的代码中与我们当前开发目标无关的部分,这样我们就可以专注于真正重要的事情。在开始逐步构建 EA 时,请按照以下编号的步骤进行。

1. 文件元数据和编译指令

文件顶部的 #property 指令声明了 EA 的元数据。这些属性包括版权和链接信息(显示在 MetaTrader 5 终端的“关于”框中),以及版本号和严格编译模式。严格模式强制执行更严格的类型检查和编译规则,通过禁止隐式类型转换并要求显式类型转换来帮助防止出现细微的错误。

#property copyright "Clemence Benjamin"
#property link      "https://www.mql5.com/go?link=https://www.mql5.com/en/users/billionaire2024/seller"
#property version   "1.00"
#property strict

2. 常量和 Windows API 导入

#define 语句为常用常量值创建符号名称(SW_HIDE 用于隐藏控制台窗口,INVALID_FILE_ATTRIBUTES 用于错误检查)。随后,代码通过 #import 导入两个 Windows 系统库:kernel32.dll 用于文件属性函数 (GetFileAttributesW),shell32.dll 用于执行外部进程 (ShellExecuteW)。通过调用这些原生 DLL 函数,EA 扩展了 MetaTrader 5 的内置功能,以验证文件是否存在并启动 Python 解释器。

#define SW_HIDE                 0
#define INVALID_FILE_ATTRIBUTES 0xFFFFFFFF

//--- Windows API imports
#import "kernel32.dll"
uint   GetFileAttributesW(string lpFileName);
#import

#import "shell32.dll"
int    ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);
#import

3. 配置的用户输入参数

输入声明在 EA 的设置对话框中展示了可自定义的参数。用户可以指定 Python 可执行文件的绝对路径(PythonPath)、要运行的 Python 脚本(ScriptPath)、每日报表应执行的小时和分钟(ReportHour、ReportMinute)、是否发送推送通知(EnableEmail)以及是否在启动时进行初始测试运行(TestOnInit)。将这些作为输入参数公开,可以让非程序员在不编辑源代码的情况下调整行为。

//--- User inputs
input string PythonPath   = "C:\\Users\\BTA24\\AppData\\Local\\Programs\\Python\\Python312\\python.exe";
input string ScriptPath   = "C:\\Users\\BTA24\\Documents\\BENJC_TRADE_ADVISOR\\Scripts\\processor.py";
input int    ReportHour   = 15;    // Hour (24h) to run report
input int    ReportMinute = 55;    // Minute after the hour
input bool   EnableEmail  = true;  // Send push notification
input bool   TestOnInit   = true;  // Immediately run export+Python on init

4. 全局状态管理

一个名为 lastRunTime 的全局变量存储了最近一次成功执行报表的时间戳。通过比较 TimeCurrent() 与 lastRunTime,即使定时器回调的检查频率更高,EA 也能确保报表每 24 小时只运行一次。

//--- Globals
datetime lastRunTime = 0;

5. 初始化逻辑(OnInit)

OnInit() 函数执行所有启动例程。首先,它会将状态消息打印到专家日志中。它会检查 Python 可执行文件和脚本的文件属性,如果它们缺失,则会打印警告信息。然后,它会在 MQL5\Files 目录中创建、写入、关闭和删除一个虚拟文件来测试写入权限。接下来,它会通过 EventSetTimer(30) 函数设置一个每30秒重复一次的定时器事件。最后,如果 TestOnInit 为 true,它会立即调用 RunDailyExport() 来验证完整的导出和 Python 工作流程,并将当前时间记录在 lastRunTime 中。

int OnInit()
{
   Print(">> Reporting EA initializing…");

   // Verify Python executable
   if(GetFileAttributesW(PythonPath) == INVALID_FILE_ATTRIBUTES)
      Print("!! Python executable not found at: ", PythonPath);
   else
      Print("✔ Found Python at: ", PythonPath);

   // Verify Python script
   if(GetFileAttributesW(ScriptPath) == INVALID_FILE_ATTRIBUTES)
      Print("!! Python script not found at:   ", ScriptPath);
   else
      Print("✔ Found script at:   ", ScriptPath);

   // Test write permission
   int h = FileOpen("test_perm.txt", FILE_WRITE|FILE_COMMON|FILE_ANSI);
   if(h==INVALID_HANDLE)
      Print("!! Cannot write to MQL5\\Files directory!");
   else
   {
      FileWrite(h, "OK");
      FileClose(h);
      FileDelete("test_perm.txt");
      Print("✔ Write permission confirmed.");
   }

   // Set timer
   EventSetTimer(30);
   Print(">> Timer set to 30 seconds.");

   // Test run on init
   if(TestOnInit)
   {
      Print(">> Test mode: running initial export.");
      RunDailyExport();
      lastRunTime = TimeCurrent();
   }

   return(INIT_SUCCEEDED);
}

6.去初始化逻辑(OnDeinit)

当 EA 被移除或平台关闭时,会调用 OnDeinit() 函数。该函数的唯一职责是通过终止定时器(EventKillTimer())和记录去初始化消息来进行清理。正确释放定时器资源可以防止出现孤立的回调函数和潜在的崩溃问题。

void OnDeinit(const int reason)
{
   EventKillTimer();
   Print(">> Reporting EA deinitialized.");
}

7.调度定时器回调函数(OnTimer)

每隔 30 秒,OnTimer() 函数就会运行一次,并通过 MqlDateTime 结构检索当前的小时和分钟。它会检查当前时间是否等于或超过配置的报表时间(ReportHour,ReportMinute),以及自上次运行时间(lastRunTime)以来是否已过去至少 86400秒(24 小时)。这种双重检查确保报表在计划分钟或之后每天运行一次。

void OnTimer()
{
   MqlDateTime dt; TimeToStruct(TimeCurrent(), dt);

   if(dt.hour==ReportHour && dt.min>=ReportMinute && (TimeCurrent()-lastRunTime)>86400)
   {
      RunDailyExport();
      lastRunTime = TimeCurrent();
   }
}

8.主要工作流程:导出和 Python 调用(RunDailyExport)

  1. 该函数概括了 EA 报表功能的核心步骤。
  2. 使用 TerminalInfoString 计算 MetaTrader 5 的 MQL5\Files 目录的绝对路径。
  3. 通过格式化当前日期并去掉点号,为 CSV 导出文件和日志文件生成带日期戳的文件名。
  4. 调用 ExportHistoryToCSV() 将最近 30 天的交易历史记录写入 CSV 文件。如果此操作失败,则函数中止。
  5. 构建一个命令字符串,该字符串使用脚本和 CSV 文件名调用 Python 解释器,并将标准输出和标准错误重定向到日志文件中。字符串格式化和 StringFormat 可确保对包含空格的路径进行正确的引用。
  6. 调用 EA 的 ShellExecute() 包装器来启动 cmd.exe /c <pythonCmd>,捕获整数返回代码以进行诊断日志记录。
  7. 使用 Sleep(3000) 暂停 3 秒钟,以允许外部 Python 进程完成。
  8. 检查日志文件是否存在,然后逐行读取其内容并将其打印到专家日志中。
  9. 最后,编写一条通知消息,总结 CSV 路径,如果启用,则通过 SendNotification() 将其发送到用户的移动终端。

void RunDailyExport()
{
   string filesDir = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\";
   
   string dateStr = TimeToString(TimeCurrent(), TIME_DATE);
   StringReplace(dateStr, ".", "");
   
   string csvName = "History_" + dateStr + ".csv";
   string logName = "ProcLog_" + dateStr + ".txt";
   
   string csvFull  = filesDir + csvName;
   string logFull  = filesDir + logName;

   // 3) Export CSV
   if(!ExportHistoryToCSV(csvName))
   {
      Print("!! CSV export failed: ", csvFull);
      return;
   }
   Print("✔ CSV exported: ", csvFull);

   // 4) Build Python command
   string pythonCmd = StringFormat(
      "\"%s\" \"%s\" \"%s\" >> \"%s\" 2>&1",
      PythonPath,
      ScriptPath,
      csvFull,
      logFull
   );
   string fullCmd = "/c " + pythonCmd;
   PrintFormat("→ Launching: cmd.exe %s", fullCmd);

   // 5) Execute
   int result = ShellExecute(" " + fullCmd);
   PrintFormat("← ShellExecute returned: %d", result);

   // 6) Wait
   Sleep(3000);

   // 7) Read log
   if(GetFileAttributesW(logFull) == INVALID_FILE_ATTRIBUTES)
      Print("!! Log file not created: ", logFull);
   else
   {
      Print("=== Python Log Start ===");
      int fh = FileOpen(logName, FILE_READ|FILE_COMMON|FILE_TXT);
      while(fh!=INVALID_HANDLE && !FileIsEnding(fh))
         Print("PY: ", FileReadString(fh));
      if(fh!=INVALID_HANDLE) FileClose(fh);
      Print("=== Python Log End ===");
   }

   // 8) Notification
   string msg = "Report & log generated: " + csvFull;
   Print(msg);
   if(EnableEmail) SendNotification(msg);
}

9.CSV 生成(ExportHistoryToCSV)

此辅助函数可将交易历史自动提取到 CSV 文件中。它选择过去 30 天内的所有历史记录(HistorySelect),遍历每个交易单,使用 HistoryDealGet* 函数检索属性(时间、类型、交易品种、交易量、价格、利润、佣金、库存费),并通过 FileWrite 将这些属性以逗号分隔的值格式写入文件。在输出标题行后,循环使用 DoubleToString 和 TimeToString 构建每一行,确保数值精度和时间戳格式的一致性。对 FileOpen 进行适当的错误检查,以防止出现无提示失败。

bool ExportHistoryToCSV(string filename)
{
   datetime end   = TimeCurrent();
   datetime start = end - 2592000; // last 30 days

   HistorySelect(start, end);
   int total = HistoryDealsTotal();

   int fh = FileOpen(filename, FILE_WRITE|FILE_CSV|FILE_COMMON|FILE_ANSI, ",");
   if(fh==INVALID_HANDLE)
   {
      Print("!! FileOpen failed for: ", filename);
      return(false);
   }

   FileWrite(fh, "Ticket,Time,Type,Symbol,Volume,Price,Profit,Commission,Swap");

   for(int i=0; i<total; i++)
   {
      ulong deal = HistoryDealGetTicket(i);
      if(deal==0) continue;

      long    tr = HistoryDealGetInteger(deal, DEAL_TIME);
      datetime t = (datetime)tr;
      int     tp = (int)HistoryDealGetInteger(deal, DEAL_TYPE);

      string  sym = HistoryDealGetString (deal, DEAL_SYMBOL);
      double  vol = HistoryDealGetDouble (deal, DEAL_VOLUME);
      double  prc = HistoryDealGetDouble (deal, DEAL_PRICE);
      double  pf  = HistoryDealGetDouble (deal, DEAL_PROFIT);
      double  cm  = HistoryDealGetDouble (deal, DEAL_COMMISSION);
      double  sw  = HistoryDealGetDouble (deal, DEAL_SWAP);

      FileWrite(fh,
         deal,
         TimeToString(t, TIME_DATE|TIME_SECONDS),
         tp,
         sym,
         DoubleToString(vol,2),
         DoubleToString(prc,5),
         DoubleToString(pf,2),
         DoubleToString(cm,2),
         DoubleToString(sw,2)
      );
   }

   FileClose(fh);
   return(true);
}

10.Shell 命令包装器 (ShellExecute)

ShellExecute 函数是对导入的 ShellExecuteW API 调用进行简单封装。通过标准化 cmd.exe 的调用,它隐藏了原生 API 的复杂性,并始终使用 SW_HIDE 来隐藏控制台窗口。返回整数结果代码使EA能够检测并记录启动外部命令时可能出现的错误。

int ShellExecute(string command)
{
   return(ShellExecuteW(0, "open", "cmd.exe", command, NULL, SW_HIDE));
}


开发 Python 处理器脚本

我们首先要设置 Python 并安装所需的库。首先,打开命令提示符并运行必要的安装命令。之后,您可以使用文本编辑器编写您的 Python 脚本。我个人比较喜欢开源工具 Notepad++,但你可以自由选择任何你喜欢的 Python IDE。

设置

1.要准备 Python 端,首先需要安装最新的 Python 3.x 解释器(例如 3.10 或 3.12)。在项目文件夹中创建并激活虚拟环境:

python -m venv venv  

source venv/Scripts/activate    # Windows  

# or  

source venv/bin/activate        # macOS/Linux  

2.激活后,使用以下命令安装所需软件包:

pip install pandas fpdf

  • pandas 负责 CSV 解析和数据分析,
  • FPDF(或您选择的其他 PDF 库)生成报表。

3.如果你打算发送电子邮件提醒,还需安装一个 SMTP 库,如 yagmail,或使用 Python 内置的 smtplib。 

现在是时候编写 Python 脚本了。我们将按以下步骤逐一实现每个部分。

1. 脚本头和导入

脚本以一个 Unix 风格的 shebang 开头,以便在支持它的系统上执行,随后导入关键库:

  • sys、os 和 traceback 用于与操作系统交互、处理参数和打印错误;
  • pandas 用于数据加载和操作;
  • datetime 和 timedelta 用于日期计算;
  • fpdf 包中的 FPDF 用于生成 PDF 报表。

#!/usr/bin/env python

import sys, os, traceback

import pandas as pd

from datetime import datetime, timedelta

from fpdf import FPDF

2. 主要工作流程:参数验证和文件检查

main(csv_path) 函数是协调器。它会打印正在处理的 CSV 文件,并立即验证该文件是否存在,如果不存在则引发 FileNotFoundError 错误。这与 MQL5 EA 自身对 Python 可执行文件和脚本路径的预检相呼应。

def main(csv_path):

    print(f"Processing CSV: {csv_path}")

    if not os.path.isfile(csv_path):

        raise FileNotFoundError(f"CSV not found: {csv_path}")

3. 加载和解析 CSV 文件

脚本使用 pandas.read_csv 加载 EA 生成的交易历史 CSV 文件。然后,它使用 pd.to_datetime 将 “Time” 列转换为 datetime 对象,以确保后续基于时间的计算准确无误。这与 EA 使用 TimeToString 函数对时间进行格式化的方式类似。

# 1. Load & parse

    df = pd.read_csv(csv_path)

    df['Time'] = pd.to_datetime(df['Time'])

4. 计算摘要分析

脚本将关键绩效指标汇总到一个报表字典中:

  • 'date' :今天的日期(字符串形式);
  • 'net_profit' :利润列的总和;
  • 'trade_count' :DataFrame 中的总行数;
  • 'top_symbol' :使用 groupby 和 idxmax 计算累计利润最高的交易品种。

这些指标与 EA 的 CSV 内容相匹配,并允许 PDF 准确地总结导出的内容。

 # 2. Analytics

    report = {

        'date'       : datetime.now().strftime("%Y-%m-%d"),

        'net_profit' : df['Profit'].sum(),

        'trade_count': len(df),

        'top_symbol' : df.groupby('Symbol')['Profit'].sum().idxmax()

    }

5. 生成 PDF 报表

脚本在与 CSV 文件相同的 MQL5\Files 目录中构建输出路径,并按日期命名 PDF 文件。然后调用 generate_pdf(report, pdf_file)。这与 EA 记录 Python 输出以及预期任何工件(CSV 和 PDF)都落在公共文件文件夹中相吻合。

# 3. Generate PDF

    dirpath = os.path.dirname(csv_path)

    pdf_file = os.path.join(dirpath, f"Report_{report['date']}.pdf")

    generate_pdf(report, pdf_file)


    print(f"PDF written: {pdf_file}")

    return 0

6.使用 FPDF 构建 PDF

generate_pdf 函数使用 FPDF 的简单 API:创建文档、添加页面、设置字体,并为每个指标编写行。ln=True 参数会自动移到下一行。这个模块化辅助函数将 PDF 格式设置与数据逻辑分离。

def generate_pdf(report, output_path):

    pdf = FPDF()

    pdf.add_page()

    pdf.set_font("Arial", size=12)

    pdf.cell(0, 10, f"Report Date: {report['date']}", ln=True)

    pdf.cell(0, 10, f"Total Trades: {report['trade_count']}", ln=True)

    pdf.cell(0, 10, f"Net Profit:    ${report['net_profit']:.2f}", ln=True)

    pdf.cell(0, 10, f"Top Symbol:    {report['top_symbol']}", ln=True)

    pdf.output(output_path)

7.维护:清理旧报表

为防止磁盘空间膨胀,clean_old_reports 会删除超过可配置天数(默认 30 天)的任何 PDF 文件。它仅在 EA 在星期日(weekday() == 6)调用脚本并在 sys.argv[1] 中收到 CSV 路径时运行,以确保它指向正确的目录。维护与 EA 自身的基于日期的命名和 24 小时一次的逻辑相一致。

def clean_old_reports(days=30):

    now = datetime.now()

    cutoff = now - timedelta(days=days)

    dirpath = os.path.dirname(sys.argv[1])

    for fname in os.listdir(dirpath):

        if fname.startswith("Report_") and fname.endswith(".pdf"):

            full = os.path.join(dirpath, fname)

            if datetime.fromtimestamp(os.path.getmtime(full)) < cutoff:

                os.remove(full)

                print(f"Deleted old report: {full}")

8.脚本入口点和错误处理

if __name__ == "__main__": 代码块强制使用一个参数(CSV 的完整路径)。它将对 main 的调用包装在 try/except 中,以捕获任何异常,打印回溯,并以非零代码退出 — 就像 EA 捕获 Python 的 stdout/stderr 并将任何错误显示到自己的日志中一样。退出前每周都会进行可选维护。

if __name__ == "__main__":

    if len(sys.argv) != 2:

        print("Usage: processor.py <full_csv_path>")

        sys.exit(1)

    try:

        ret = main(sys.argv[1])

        # Optional maintenance

        if datetime.now().weekday() == 6:

            clean_old_reports(30)

        sys.exit(ret)

    except Exception as e:

        print("ERROR:", e)

        traceback.print_exc()

        sys.exit(1)

MetaTrader 5 EA 与 Python 脚本之间的交互

  1. 文件名命名规则:EA 会生成一个日期格式的 CSV 文件名(History_YYYYMMDD.csv),而 Python 脚本需要一个指向该文件的参数。
  2. 目录对齐:这两个组件都在 MetaTrader 5 的 MQL5\Files 文件夹中运行,从而确保能够顺利找到并处理相关文件。。
  3. 错误传播:EA 将 Python 的标准输出和标准错误重定向到带时间戳的日志文件;脚本中的任何异常(例如,缺少 CSV、解析错误)都会被 EA 的日志读取器捕获并打印到专家日志中。
  4. 调度:EA 的 24 小时定时器逻辑决定 Python 何时运行;脚本本身除了处理其输入之外保持无状态,依靠 EA 在正确的时间调用它。
  5. 维护协调:每周对 PDF 文件的清理由脚本触发,但仅在周日调用时执行 — 如果延长,则与 EA 的每周节奏检查相匹配。


测试

在本节中,我们将报表 EA 部署到 MetaTrader 5 图表上。在 Windows 电脑上,必须启用 DLL 导入才能执行外部进程。在测试过程中,EA 成功实现了其目标:将交易历史记录导出为 CSV 文件,并触发负责处理数据的 Python 脚本。然后,脚本会生成所需的报表指标,并将其导出为精美的 PDF 文档,以便通过电子邮件发送或存档以供审查。

部署报表 EA

部署报表 EA

EA 专家报表日志:

2025.07.24 20:44:57.061 Reporting EA (GBPJPY.0,M1)      >> Reporting EA initializing…
2025.07.24 20:44:57.061 Reporting EA (GBPJPY.0,M1)      !! Python executable not found at: C:\path_to\python.exe
2025.07.24 20:44:57.061 Reporting EA (GBPJPY.0,M1)      !! Python script not found at:   C:\path_to\reports_processor.py
2025.07.24 20:44:57.062 Reporting EA (GBPJPY.0,M1)      ✔ Write permission confirmed.
2025.07.24 20:44:57.062 Reporting EA (GBPJPY.0,M1)      >> Timer set to 30 seconds.
2025.07.24 20:44:57.062 Reporting EA (GBPJPY.0,M1)      >> Test mode: running initial export.
2025.07.24 20:44:57.063 Reporting EA (GBPJPY.0,M1)      ✔ CSV exported: C:\TERMINAL_PATH\MQL5\Files\History_20250724.csv
2025.07.24 20:44:57.063 Reporting EA (GBPJPY.0,M1)      → Launching: cmd.exe /c "C:\path_to\python.exe" "C:\path_to\reports_processor.py" "C:\TERMINAL_PATH\MQL5\Files\History_20250724.csv" >> "C:\TERMINAL_PATH\MQL5\Files\ProcLog_20250724.txt" 2>&1
2025.07.24 20:44:57.124 Reporting EA (GBPJPY.0,M1)      ← ShellExecute returned: 42
2025.07.24 20:45:00.124 Reporting EA (GBPJPY.0,M1)      !! Log file not created: C:\Users\TERMINAL_PATH\MQL5\Files\ProcLog_20250724.txt
2025.07.24 20:45:00.124 Reporting EA (GBPJPY.0,M1)      Report & log generated: C:\Users\TERMINAL_PATH\Files\History_20250724.csv

上面显示的专家日志揭示了报表 EA 的初始化尝试,其中系统未能找到指定的 Python 可执行文件和脚本。这是因为代码中故意重命名了文件路径(例如,C:\path_to\python.exe 和 C:\path_to\reports_processor.py),以作演示或占位符使用。因此,EA 无法执行 Python 脚本或生成预期的日志输出 (ProcLog_20250724.txt)。尽管如此,EA 还是成功确认了写入权限,并将交易历史记录导出为 CSV 文件。

该测试突显了在 EA 中正确配置文件路径的重要性 — 指向实际的 Python 解释器和处理脚本 — 以确保无缝端到端报表生成。请务必仔细检查并使用与本地系统设置相匹配的有效绝对路径,以避免此类问题,并解锁报表工具的全部功能。


结论

本次讨论主要聚焦于理解交易报表、建立功能工作流程以及开发以可移植文档格式(PDF)交付定制交易报表所需的工具。为避免本演示文稿内容过于冗长,通过电子邮件发送生成的 PDF 文件的最后阶段将留待后续文章中介绍。不过,基于导出 CSV 文件的 PDF 生成过程已成功使用 Python 库实现。进一步的增强功能,如包含图表和高级报表功能,将在下次讨论中介绍。

既然 EA 及其相应的 Python 脚本都已经完成,我们就有条件去实现更多目标。总之,本项目解决了接收定期、可定制且强调清晰呈现的特性的交易报表的难题,这些特征有助于用户理解。正如记账对于任何企业都至关重要一样,定期报表在交易中也是至关重要的 — 它有助于提升交易者的业绩意识、纪律性和心理成长。


关键经验

经验 描述
1.模块化设计 将 CSV 导出逻辑 (MQL5) 与报表生成 (Python) 分离,可以提高可维护性,并允许每个组件独立发展。
2.稳健的错误处理 通过验证文件路径、于 Python 中捕获异常以及记录故障信息,可确保系统在出现故障时能够优雅降级,并维持其可调试性。
3.跨语言集成 通过 ShellExecute 调用外部脚本,可以展示交易平台如何利用强大的外部库和生态系统。
4.自动调度 在 MQL5 中使用定时器回调来触发每日导出,可确保报表无需人工干预即可运行,从而提高一致性和可靠性。
5.集中式日志记录 将日志记录到 MetaTrader 5 的“专家”选项卡和外部文本文件中,可以清晰查看工作流程的每个步骤。
6.环境验证 在初始化时检查 Python 和脚本的可用性,可以避免运行时的意外情况,并指导用户满足设置要求。
7.开源工具 依靠免费、广泛采用的库(pandas、FPDF)和标准 API 可以降低准入门槛,并鼓励社区合作。
8.用户可配置参数 将路径、计划时间和通知开关作为输入参数,使 EA 具有灵活性和适应性,能够适应不同的交易环境。


附件

文件名 版本 描述
reporting_processor.py
Python 脚本,用于加载交易历史的 CSV 文件,计算汇总分析数据(净利润、交易次数、最佳交易品种),通过 FPDF 生成 PDF 报表,并可选择性地清理旧报表。
Reporting EA.mq5 1.0 MetaTrader 5 EA,可将最近 30 天的交易历史记录导出到 CSV 文件,调用 processor.py,捕获其退出代码,检查生成的 PDF 文件,并发送推送通知。

返回目录

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18882

附加的文件 |
Reporting_EA.mq5 (15.06 KB)
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
交易中的神经网络:智能预测流程(Time-MoE) 交易中的神经网络:智能预测流程(Time-MoE)
诚邀大家一起来研究现代Time-MoE框架,该框架已针对时间序列预测任务完成适配。本文将分步实现该架构的核心组件,并同步提供原理讲解与实操示例。该方法使得您不仅能够理解模型的运行机制,还可以将相关原理应用于真实交易场景。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
交易中的神经网络:智能预测流程(结论) 交易中的神经网络:智能预测流程(结论)
本文深入介绍 SwiGLU 嵌入层如何挖掘隐藏的市场规律,以及Decoder-Only Transformer 架构内的稀疏混合专家模型,如何在合理的计算开销下提升预测精度。本文详解将 Time‑MoE 框架集成至 MQL5 与 OpenCL 的实现方法,并提供模型配置与训练的分步教程。