如何为您的 MT4 EA 添加新闻过滤器
EA(智能交易系统)如果在非农数据或利率决议公布时照常交易,跑的就不再是它的策略,而是在宽点差下掷硬币。新闻过滤器正是为此而设:在影响行情的数据公布前后几分钟暂停新开仓,其余时间策略照常运行。
MetaTrader 5 自带财经日历,MetaTrader 4 没有,EA 只能从外部获取新闻。本指南用的是免费的 ShowMyTrades 财经日历 API:无需账户,无需 API 密钥,在终端里放行一个网址,再把一个包含文件粘贴进任意 EA 即可。
🧭 整体思路
每 15 分钟
┌───────────────────
│ 下载未来 2 天新闻
│ (CSV,一次请求)
└───────────────────
│
▼
每个 tick
┌────────────────────────────────
│ 是否有数据公布落在当前时间的
│ [-30 分钟, +30 分钟] 范围内?
│ 是 → 不开新仓
│ 否 → 运行策略
└────────────────────────────────
| 设置项 | 代码中的默认值 | 原因 |
|---|---|---|
| 哪些货币 | EUR,USD | 即所交易货币对的两种货币。留空则包含全部九种主要货币 |
| 哪种影响级别 | high | 想过滤得更保守,就用 high,medium |
| 公布前暂停 | 30 分钟 | 数据公布前点差扩大、流动性变薄 |
| 公布后暂停 | 30 分钟 | 第一波行情和随后的反转都出现在这段时间 |
| 下载间隔 | 15 分钟 | 日历一天只变动几次;而且 API 本身就缓存 15 分钟 |
🔑 第 1 步:在 MetaTrader 中允许该网址
MQL4 用 WebRequest() 从网上下载数据,但它只访问您事先批准过的网址。在终端中打开工具 → 选项 → 智能交易系统(Tools → Options → Expert Advisors),勾选允许 WebRequest 访问下列 URL(Allow WebRequest for listed URL),然后添加:
https://showmytrades.com
漏了这一步,下载会返回 -1,错误代码 4060。这时包含文件会在「智能交易(Experts)」选项卡里打印一条提醒,免得您对着毫无动静的过滤器摸不着头脑。
📄 第 2 步:保存包含文件
把下面的代码保存为 SMT_NewsFilter.mqh,放进终端的 MQL4\Include 文件夹(文件 → 打开数据文件夹,File → Open Data Folder)。这就是 API 参考里给出的那个文件(注释和日志文字已译成中文),在 MQL5 中也能原样编译。
//+------------------------------------------------------------------+
//| SMT_NewsFilter.mqh - MT4 和 MT5 新闻过滤器
//| 数据:https://showmytrades.com/api/public/calendar/v1(免费)
//| 文档:https://showmytrades.com/docs/api/economic-calendar
//| 设置:工具 > 选项 > 智能交易系统 > 允许 WebRequest 访问
//| https://showmytrades.com
//+------------------------------------------------------------------+
#property strict
input string SMT_Currencies = "EUR,USD"; // 新闻:货币(逗号分隔,留空 = 全部)
input string SMT_Impact = "high"; // 新闻:影响级别(high | high,medium | all)
input int SMT_MinutesBefore = 30; // 新闻:公布前暂停(分钟)
input int SMT_MinutesAfter = 30; // 新闻:公布后暂停(分钟)
input int SMT_RefreshMinutes = 15; // 新闻:下载间隔(分钟,最少 15)
datetime smt_eventTime[];
string smt_eventTitle[];
datetime smt_nextDownload = 0;
long smt_serverTime = 0; // 上次下载时的服务器 UTC 时间(Unix 秒)
uint smt_syncTick = 0; // 当时的 GetTickCount()
bool smt_clockSynced = false;
//--- 真实的 UTC 时间:服务器时钟加上自下载以来经过的毫秒数,
//--- 用 GetTickCount() 计数,它不受时区、夏令时和电脑时钟校正的影响
//--- (uint 减法在计数器回绕后依然正确)。
//--- 首次下载之前退回使用 TimeGMT()。绝不用 TimeCurrent():
//--- 经纪商的 GMT 偏移未知,而且会随夏令时跳变。
datetime SMT_Now()
{
if(!smt_clockSynced) return TimeGMT();
uint elapsedMs = GetTickCount() - smt_syncTick;
return (datetime)(smt_serverTime + elapsedMs / 1000);
}
//--- 经纪商时钟与 UTC 的差值,单位为秒(7200 = GMT+2)。它本身就很有用:
//--- 大多数 EA 从来不知道这个值。TimeCurrent() 是最后一个 tick 的时间,
//--- 所以请在开市时读取,不要在周末读取。
int SMT_BrokerGmtOffsetSeconds()
{
return (int)(TimeCurrent() - SMT_Now());
}
//--- 第 n 个逗号之后的全部内容(CSV 的标题,标题本身可能含有逗号)
string SMT_AfterComma(const string line, const int n)
{
int pos = -1;
for(int i = 0; i < n; i++)
{
pos = StringFind(line, ",", pos + 1);
if(pos < 0) return "";
}
string rest = StringSubstr(line, pos + 1);
StringReplace(rest, "\"\"", "\"");
if(StringLen(rest) >= 2 && StringGetCharacter(rest, 0) == '"')
rest = StringSubstr(rest, 1, StringLen(rest) - 2);
return rest;
}
//--- 下载未来 2 天的数据(时间窗口也包含过去 6 小时)
bool SMT_Download()
{
string url = "https://showmytrades.com/api/public/calendar/v1?format=csv&days=2&impact=" + SMT_Impact;
if(StringLen(SMT_Currencies) > 0) url += "¤cy=" + SMT_Currencies;
char post[], result[];
string headers;
ResetLastError();
int status = WebRequest("GET", url, "", NULL, 10000, post, 0, result, headers);
if(status != 200)
{
Print("[SMT] 日历下载失败:HTTP ", status, ",错误 ", GetLastError(),
"。https://showmytrades.com 是否已添加到 工具 > 选项 > 智能交易系统 > 允许 WebRequest?");
smt_nextDownload = TimeCurrent() + 60; // 一分钟后重试
return false;
}
string body = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
string lines[];
int n = StringSplit(body, '\n', lines);
ArrayResize(smt_eventTime, 0);
ArrayResize(smt_eventTitle, 0);
for(int i = 1; i < n; i++) // 第 0 行是表头
{
// 最后一行 "# server_time=1760702400 server_time_iso=...":服务器的 UTC 时钟
if(StringFind(lines[i], "# server_time=") == 0)
{
long serverTime = StringToInteger(StringSubstr(lines[i], 14, 10));
if(serverTime > 0)
{
smt_serverTime = serverTime;
smt_syncTick = GetTickCount();
smt_clockSynced = true;
}
continue;
}
string f[];
if(StringSplit(lines[i], ',', f) < 8) continue;
int k = ArraySize(smt_eventTime);
ArrayResize(smt_eventTime, k + 1);
ArrayResize(smt_eventTitle, k + 1);
smt_eventTime[k] = (datetime)StringToInteger(f[0]); // Unix 秒,UTC
smt_eventTitle[k] = f[2] + " " + f[3] + ": " + SMT_AfterComma(lines[i], 7);
}
smt_nextDownload = TimeCurrent() + MathMax(SMT_RefreshMinutes, 15) * 60;
Print("[SMT] 日历:已加载 ", ArraySize(smt_eventTime), " 个事件。当前 UTC ",
TimeToString(SMT_Now(), TIME_DATE|TIME_MINUTES), ",经纪商时钟为 GMT",
(SMT_BrokerGmtOffsetSeconds() >= 0 ? "+" : ""), DoubleToString(SMT_BrokerGmtOffsetSeconds() / 3600.0, 1));
return true;
}
//--- 有数据公布处于暂停窗口内时返回 true;reason 说明是哪一条
bool SMT_IsNewsTime(string &reason)
{
reason = "";
if(MQLInfoInteger(MQL_TESTER)) return false; // 测试器中没有网络
if(TimeCurrent() >= smt_nextDownload) SMT_Download();
datetime now = SMT_Now();
for(int i = 0; i < ArraySize(smt_eventTime); i++)
{
if(now >= smt_eventTime[i] - SMT_MinutesBefore * 60 && now <= smt_eventTime[i] + SMT_MinutesAfter * 60)
{
reason = smt_eventTitle[i];
return true;
}
}
return false;
}
代码的流程是:按输入参数拼出请求,下载 CSV,每个事件只留一个时间戳和一个标签,最后回答 EA 唯一关心的问题:此刻是否处在某条数据公布的窗口内?
🔌 第 3 步:每次入场前先调用
在 EA 里包含这个文件,开仓前先检查一下:
#include <SMT_NewsFilter.mqh>
void OnTick()
{
string news;
if(SMT_IsNewsTime(news))
{
Comment("新闻过滤生效中:", news);
return; // 新闻窗口内不开新仓
}
Comment("");
// ... 您的入场逻辑 ...
}
这五个输入参数(SMT_Currencies、SMT_Impact 和三个分钟数)会和其他设置一起出现在 EA 的输入参数窗口里,每张图表都可以单独设置。
重新编译,把 EA 加载到图表上,再看「智能交易」选项卡:第一行应是 [SMT] 日历:已加载 N 个事件。没什么数据的日子里 N 可能是零,这属于正常,不是出错。
⏱️ 时间、时钟与测试器
过滤器能不能卡准时间触发,取决于三个细节。
| API 用 UTC,还会告诉您现在几点 | ts 是 UTC 的 Unix 时间(秒)。每次下载的末尾都带有服务器自己的时钟(# server_time=…),SMT_Now() 再用电脑的 tick 计数器接着往下走,这个计数器不受时区和夏令时影响。所以经纪商在哪个时区、哪里切换夏令时、VPS 的时区设得对不对,都无关紧要 |
绝不用 TimeCurrent() | 经纪商时间和 UTC 之间有个偏移,EA 无从知道,而且一年要变两次。SMT_BrokerGmtOffsetSeconds() 可以替您测出来,单独用也很方便;但过滤器从不依赖它 |
| 策略测试器不能联网 | 测试器里 WebRequest() 是禁用的,所以回测时这个函数总是返回「没有新闻」。想在历史数据上测试新闻过滤器,得准备一份本地的历史事件文件,那是另一回事了 |
🎛️ 调整设置
- 持仓。代码只拦新的入场。数据公布前要不要平仓、收紧或放宽止损,是策略层面的决定;什么时候动手,同样可以问
SMT_IsNewsTime()。 - 只看货币对的一边。如果您只在意美元新闻,把
SMT_Currencies设为USD。 - 加上中影响。
SMT_Impact = "high,medium"会让事件数量增加到大约三倍。适合剥头皮,对波段系统来说往往过多。 - 节假日。
impact=all会把市场节假日当作全天事件一并返回。多数过滤器用不上,默认设置不包含。
🔍 核对数据
这份日历也有网页版:财经日历。EA 提示因“USD high: Nonfarm Payrolls”暂停时,可以去那里找到这条事件,看到预期值,公布后还能看到公布值。
❓ 常见问题
需要 ShowMyTrades 账户吗? 不需要。财经日历 API 免费开放,不用密钥。想要一个经验证的公开页面的话,可以在 ShowMyTrades 上追踪您的账户,但新闻过滤器与此无关。
它会产生多少流量? 每张图表每 15 分钟请求一次,每次几 KB。一个终端跑十个 EA,每小时也就 40 个请求。上限是每个 IP 地址每分钟 600 个。
为什么不直接读取 Forex Factory?
可以,很多 EA 就是这么做的。但它的周数据文件每个 IP 地址每五分钟只能下载两次,一台 VPS 上开很多终端就会撞上限制,而且没有 actual(公布值)。ShowMyTrades 的端点在服务器端就按货币和影响级别筛好,只返回几 KB,本来就是为轮询设计的。
我的 VPS 时钟不准或时区设错了,过滤器还能准时触发吗? 能。每次下载时服务器都会告诉 EA 当前的 UTC 时间,之后 EA 用自己的计时接着走。VPS 时区设错,或者快慢几分钟,都没有影响。VPS 切换夏令时也一样:EA 计算经过的时间靠的是 tick 计数器,不是时钟。
如果下载失败怎么办?
过滤器会沿用上一次加载的日历,一分钟后重试。如果一次都没加载成功(刚启动、没联网),它会返回「没有新闻」,EA 就照常交易。不想这样的话,检查 ArraySize(smt_eventTime) == 0,自己决定怎么处理。
同一个文件能在 MT5 中使用吗? 能,原样可用。MT5 还有自带的日历,按需选用即可。
🔗 相关内容
- 财经日历 API 参考:所有参数、JSON 格式、用于 cTrader 的 WebSocket
- MT5 中的新闻过滤器:内置日历还是 API
- cTrader cBot 的新闻过滤器:同样的思路,用在无法使用 HTTP 的环境里
- 在销售页面上展示 EA 实时交易结果:EA 跑出成绩后,就把它展示出来
整个过滤器不过是一页代码加一个网址。如果您的 EA 已经在 ShowMyTrades 上追踪,它在新闻前后避开的那些交易,就会体现在少吃的那段回撤上。