让新闻交易轻松上手(第六部分):执行交易(3)·综合运用
🛠️

让新闻交易轻松上手(第六部分):执行交易(3)·综合运用

(3/3)·从新闻筛选配置到多模式入场,把前三篇的性能与执行碎片拼成可跑的交易系统

实战向进阶 第 3/3 篇
很多人在新闻EA最后组装阶段把筛选条件和入场方式混在一起调,结果回测跑飞还找不到是哪层配置冲突。分开验证新闻配置文件与止损单偏差,是收尾时最容易漏的一步。

◍ 在财经日历事件窗口内抓取信号的循环逻辑

这段 MQL5 片段展示了一个遍历财经日历数组、并在事件触发前后时间窗内返回标记的典型写法。循环从 i=0 扫到 CalendarArray.Size(),对每个事件用 CTime 的偏移函数框出「事件前若干秒到事件后 59 秒」的窗口。 若当前时间落在该窗口内,就把事件名、重要度、代码写进变量并 return true;扫完都没命中则返回 false。外汇与贵金属受此类事件冲击可能出现瞬时跳空,属高风险场景,验证前请在策略测试器用历史日历回放。 下面这段代码是循环判断部分,可直接贴进 MT5 的 .mqh 类里跑:

MQL5 / C++
for (class="type">uint i = class="num">0; i < CalendarArray.Size(); i++)
if (CTime.TimeIsInRange(CTime.TimeMinusOffset(class="type">class="kw">datetime(CalendarArray[i].EventDate), SecondsPreEvent),
                      CTime.TimePlusOffset(class="type">class="kw">datetime(CalendarArray[i].EventDate), class="num">59)))
  {
   Name = CalendarArray[i].EventName;
   Importance = CalendarArray[i].EventImportance;
   Code = CalendarArray[i].EventCode;
   class="kw">return true;
  }
class="kw">return class="kw">false;

「用视图把财经日历拆成可读表」

在 MT5 里直接啃 MQL5Calendar 原生表很反人类,字段名全带着 CALENDAR_TYPE_ 这类前缀。上面这段做法是用 SQL 视图做一层清洗:把 EVENTTYPE、EVENTSECTOR 等字段的冗余前缀用 REPLACE 剥掉,输出成 Type、Sector、Importance 这种干净列名,并按国家升序、重要性(HIGH=1 / MODERATE=2 / LOW=3)排序。 CalendarContents 数组的下标对应不同视图。下标 5 挂的是 Event Info 视图(type="view"),下标 7 是 Upcoming Event Dates——它用 CTE 先取 UNIQUE_EVENTS 去重,再套子查询从 TIMESCHEDULE 抓每条事件下一个非空时间,没有就标 Unknown,并顺手用 strftime('%w') 把星期算出来。 下标 8 接着定义 Recent Event Dates,结构同思路但方向相反,抓已发生事件的近期时间点。你在 MQl5 编辑器里建好这几个视图后,EA 或脚本就能直接 SELECT 干净字段,不用每次解析前缀。外汇与贵金属受事件冲击大,这类日历视图只帮你看清时点,不预示波动方向,实盘仍属高风险。

MQL5 / C++
CalendarContents[class="num">5].tbl_name = "Event Info";
CalendarContents[class="num">5].type = "view";
CalendarContents[class="num">7].Content = UpcomingEventInfo_View;
CalendarContents[class="num">7].name = "Upcoming Event Dates";
CalendarContents[class="num">7].sql = "CREATE VIEW IF NOT EXISTS &class="macro">#x27;Upcoming Event Dates&class="macro">#x27; AS WITH UNIQUE_EVENTS AS(SELECT DISTINCT M.EVENTID as &class="macro">#x27;E_ID&class="macro">#x27;,M.COUNTRY as &class="macro">#x27;Country&class="macro">#x27;,M.EVENTNAME as &class="macro">#x27;Name&class="macro">#x27;,M.EVENTCURRENCY as &class="macro">#x27;Currency&class="macro">#x27; FROM &class="macro">#x27;MQL5Calendar&class="macro">#x27; M),INFO_DATE AS(SELECT E_ID,Country,Name,Currency,(SELECT T.DST_NONE as &class="macro">#x27;Time&class="macro">#x27; FROM MQL5Calendar M,Record R INNER JOIN TIMESCHEDULE T ON T.ID=M.ID WHERE DATE(REPLACE(Time,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))>R.Date AND E_ID=M.EVENTID ORDER BY Time ASC LIMIT class="num">1) as &class="macro">#x27;Next Event Date&class="macro">#x27; FROM UNIQUE_EVENTS) SELECT E_ID as &class="macro">#x27;ID&class="macro">#x27;,Country,Name,Currency,(CASE WHEN \"Next Event Date\" IS NULL THEN &class="macro">#x27;Unknown&class="macro">#x27; ELSE \"Next Event Date\" END) as &class="macro">#x27;Upcoming Date&class="macro">#x27;,(CASE WHEN \"Next Event Date\"<>&class="macro">#x27;Unknown&class="macro">#x27; THEN(case cast(strftime(&class="macro">#x27;%w&class="macro">#x27;, DATE(REPLACE(\"Next Event Date\",&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))) as integer) WHEN class="num">0 THEN &class="macro">#x27;Sunday&class="macro">#x27; WHEN class="num">1 THEN &class="macro">#x27;Monday&class="macro">#x27; WHEN class="num">2 THEN &class="macro">#x27;Tuesday&class="macro">#x27; WHEN class="num">3 THEN &class="macro">#x27;Wednesday&class="macro">#x27; WHEN class="num">4 THEN &class="macro">#x27;Thursday&class="macro">#x27; WHEN class="num">5 THEN &class="macro">#x27;Friday&class="macro">#x27; ELSE &class="macro">#x27;Saturday&class="macro">#x27; END) ELSE &class="macro">#x27;Unknown&class="macro">#x27; END) as &class="macro">#x27;Day&class="macro">#x27; FROM INFO_DATE Order BY \"Upcoming Date\" ASC;";
CalendarContents[class="num">7].tbl_name = "Upcoming Event Dates";
CalendarContents[class="num">7].type = "view";
CalendarContents[class="num">8].Content = RecentEventInfo_View;
CalendarContents[class="num">8].name = "Recent Event Dates";
CalendarContents[class="num">8].sql = "CREATE VIEW IF NOT EXISTS &class="macro">#x27;Recent Event Dates&class="macro">#x27; AS WITH UNIQUE_EVENTS AS(SELECT DISTINCT M.EVENTID"

用视图把财经日历对齐到本地时区

在 MT5 里直接吃原生日历表,时区永远是坑。上面这段逻辑建了两个视图:一个 Recent Event Dates 把每个事件的上次发生日期算出来并标出星期;另一个 Calendar_AU 专门按澳洲时区(DST_AU)把事件映射成昨天 / 今天 / 明天,过滤窗口是 Record 日期加减 1 天。 CalendarContents[8] 把视图名钉成 Recent Event Dates、类型 view,说明这套日历不是临时查询,而是常驻内存结构,小布盯盘这类工具靠它做前置渲染。 重点看 CASE 分支:Date(REPLACE(T.DST_AU,'.','-')) 先把点号换成横线再转日期,和 R.Date 比大小,拼出 | Yesterday/Today/Tomorrow。你开 MT5 的 SQL 面板跑一遍 CREATE VIEW IF NOT EXISTS Calendar_AU ...,把 DST_AU 换成 DST_NONE 就能拿到 UTC 版,换 DST_US 就是美盘视角。外汇和贵金属受事件跳空影响大,这类视图只解决时区对齐,不预示波动方向。

MQL5 / C++
CalendarContents[class="num">8].tbl_name = "Recent Event Dates";
CalendarContents[class="num">8].type = "view";
class=class="str">"cmt">// ...
CREATE VIEW IF NOT EXISTS Calendar_AU AS SELECT C.Eventid as &class="macro">#x27;ID&class="macro">#x27;,C.Eventname as &class="macro">#x27;Name&class="macro">#x27;,C.Country as &class="macro">#x27;Country&class="macro">#x27;,
(CASE WHEN Date(REPLACE(T.DST_AU,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))<R.Date THEN CONCAT(T.DST_AU,&class="macro">#x27; | Yesterday&class="macro">#x27;) WHEN Date(REPLACE(T.DST_AU,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))=R.Date
THEN CONCAT(T.DST_AU,&class="macro">#x27; | Today&class="macro">#x27;) WHEN Date(REPLACE(T.DST_AU,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))>R.Date THEN CONCAT(T.DST_AU,&class="macro">#x27; | Tomorrow&class="macro">#x27;) END) as &class="macro">#x27;Date&class="macro">#x27;,
C.EventCurrency as &class="macro">#x27;Currency&class="macro">#x27;,Replace(C.EventImportance,&class="macro">#x27;CALENDAR_IMPORTANCE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Importance&class="macro">#x27; from MQL5Calendar C,
Record R Inner join TimeSchedule T on C.ID=T.ID Where DATE(REPLACE(T.DST_AU,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))>=DATE(R.Date,&class="macro">#x27;-class="num">1 day&class="macro">#x27;) AND
DATE(REPLACE(T.DST_AU,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))<=DATE(R.Date,&class="macro">#x27;+class="num">1 day&class="macro">#x27;) Order by T.DST_AU Asc;
SELECT
   C.Eventid as &class="macro">#x27;ID&class="macro">#x27;,
   C.Eventname as &class="macro">#x27;Name&class="macro">#x27;,
   C.Country as &class="macro">#x27;Country&class="macro">#x27;,
   (CASE
       WHEN Date(REPLACE(T.DST_AU,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)) < R.Date THEN CONCAT(T.DST_AU, &class="macro">#x27; | Yesterday&class="macro">#x27;)
       WHEN Date(REPLACE(T.DST_AU,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)) = R.Date THEN CONCAT(T.DST_AU, &class="macro">#x27; | Today&class="macro">#x27;)
       WHEN Date(REPLACE(T.DST_AU,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)) > R.Date THEN CONCAT(T.DST_AU, &class="macro">#x27; | Tomorrow&class="macro">#x27;)
   END) as &class="macro">#x27;Date&class="macro">#x27;,
   C.EventCurrency as &class="macro">#x27;Currency&class="macro">#x27;,
   Replace(C.EventImportance,&class="macro">#x27;CALENDAR_IMPORTANCE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Importance&class="macro">#x27;
FROM
   MQL5Calendar C,
   Record R
   Inner join TimeSchedule T on C.ID=T.ID
WHERE
   DATE(REPLACE(T.DST_AU,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)) >= DATE(R.Date,&class="macro">#x27;-class="num">1 day&class="macro">#x27;)

◍ 把财经日历清洗成可排序列视图

在 MT5 的日历数据库里,原始字段常带 CALENDAR_TYPE_、CALENDAR_SECTOR_ 这类前缀,直接读很不顺手。用 REPLACE 剥掉前缀再建视图,能把事件类型、板块、重要性都变成干净短值,方便后续按国家与重要性排序。 下面这段 SQL 会创建一个名为 'Event Info' 的视图,把 MQL5Calendar 表里的事件去重后导出。排序逻辑是先按国家升序,再按重要性 HIGH=1、MODERATE=2、LOW=3、其余=4 走 CASE 映射,最后板块降序,这样高影响事件总浮在前面。 实际跑出来,2024.09.23 新西兰进出口(NZD,LOW)与 2024.09.24 澳洲 RBA 利率声明(AUD,MODERATE)会出现在同一窗口,后者因重要性权重更高而排前。外汇与贵金属受此类事件扰动明显,属高风险品种,事件窗口前后报价滑点可能扩大,验证时建议切到模拟盘。 如果只想临时抽取而不落视图,把 CREATE VIEW 那句去掉,直接发后面的 SELECT DISTINCT 也能拿到同样列。改 ORDER BY 里的 CASE 顺序,就能把 LOW 事件提权,适合非农夜之外找流动性盲区。

MQL5 / C++
CREATE VIEW IF NOT EXISTS &class="macro">#x27;Event Info&class="macro">#x27; AS SELECT DISTINCT MC.EVENTID as &class="macro">#x27;ID&class="macro">#x27;,MC.COUNTRY as &class="macro">#x27;Country&class="macro">#x27;,MC.EVENTNAME as &class="macro">#x27;Name&class="macro">#x27;,
REPLACE(MC.EVENTTYPE,&class="macro">#x27;CALENDAR_TYPE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Type&class="macro">#x27;,REPLACE(MC.EVENTSECTOR,&class="macro">#x27;CALENDAR_SECTOR_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Sector&class="macro">#x27;,
REPLACE(MC.EVENTIMPORTANCE,&class="macro">#x27;CALENDAR_IMPORTANCE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Importance&class="macro">#x27;,MC.EVENTCURRENCY as &class="macro">#x27;Currency&class="macro">#x27;,
REPLACE(MC.EVENTFREQUENCY,&class="macro">#x27;CALENDAR_FREQUENCY_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Frequency&class="macro">#x27;,MC.EVENTCODE as &class="macro">#x27;Code&class="macro">#x27; FROM MQL5Calendar MC
ORDER BY "Country" Asc,CASE "Importance" WHEN &class="macro">#x27;HIGH&class="macro">#x27; THEN class="num">1 WHEN &class="macro">#x27;MODERATE&class="macro">#x27; THEN class="num">2 WHEN &class="macro">#x27;LOW&class="macro">#x27; THEN class="num">3 ELSE class="num">4 END,"Sector" Desc;
SELECT DISTINCT
   MC.EVENTID as &class="macro">#x27;ID&class="macro">#x27;,
   MC.COUNTRY as &class="macro">#x27;Country&class="macro">#x27;,
   MC.EVENTNAME as &class="macro">#x27;Name&class="macro">#x27;,
   REPLACE(MC.EVENTTYPE,&class="macro">#x27;CALENDAR_TYPE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Type&class="macro">#x27;,
   REPLACE(MC.EVENTSECTOR,&class="macro">#x27;CALENDAR_SECTOR_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Sector&class="macro">#x27;,
   REPLACE(MC.EVENTIMPORTANCE,&class="macro">#x27;CALENDAR_IMPORTANCE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Importance&class="macro">#x27;,
   MC.EVENTCURRENCY as &class="macro">#x27;Currency&class="macro">#x27;,
   REPLACE(MC.EVENTFREQUENCY,&class="macro">#x27;CALENDAR_FREQUENCY_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Frequency&class="macro">#x27;,
   MC.EVENTCODE as &class="macro">#x27;Code&class="macro">#x27;
FROM MQL5Calendar MC
ORDER BY
   "Country" Asc,
   CASE "Importance"
      WHEN &class="macro">#x27;HIGH&class="macro">#x27; THEN class="num">1
      WHEN &class="macro">#x27;MODERATE&class="macro">#x27; THEN class="num">2
      WHEN &class="macro">#x27;LOW&class="macro">#x27; THEN class="num">3
      ELSE class="num">4
   END,
   "Sector" Desc

「抓澳元事件链:从利率决议到贸易账的日历视图」

经济日历里澳大利亚的事件按冲击力度分了三层。RBA 利率决议(ID 36030008)和行长 Lowe 讲话(36030006)标为 HIGH,影响 AUD 最直接;就业变化(36010003)同样 HIGH 但按月公布。中间档 MODERATE 含经常账(36010036)、贸易账(36010011)、PPI 季率(36010029),低档则有出口/进口月率及净出口贡献。 外汇和贵金属杠杆高,澳元系事件常在公布后 15 分钟内制造 20~40 点跳动,概率上偏脉冲而非趋势,实盘须控仓。 下面这段 SQL 在 MT5 内置的 MQL5 日历库上建了一个视图,把每个事件最近一次发生日期捞出来并按时间倒序排。它先去重事件,再关联时间表取最后一次非夏令时时间,最后用 strftime 算出星期几。 别只盯 HIGH 事件 MODERATE 的贸易账和 PPI 常在利率决议前 1~2 日出,组合看能提前嗅到 RBA 倾向。开 MT5 把视图跑出来,对照 AUDUSD 1 分钟 K 线验证跳动规律。

MQL5 / C++
CREATE VIEW IF NOT EXISTS &class="macro">#x27;Recent Event Dates&class="macro">#x27; AS WITH UNIQUE_EVENTS AS(SELECT DISTINCT M.EVENTID as &class="macro">#x27;E_ID&class="macro">#x27;,M.COUNTRY as &class="macro">#x27;Country&class="macro">#x27;,
M.EVENTNAME as &class="macro">#x27;Name&class="macro">#x27;,M.EVENTCURRENCY as &class="macro">#x27;Currency&class="macro">#x27;FROM &class="macro">#x27;MQL5Calendar&class="macro">#x27; M),INFO_DATE AS(SELECT E_ID,Country,Name,Currency,
(SELECT T.DST_NONE as &class="macro">#x27;Time&class="macro">#x27; FROM MQL5Calendar M,Record R INNER JOIN TIMESCHEDULE T ON T.ID=M.ID WHERE DATE(REPLACE(Time,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))
<=R.Date AND E_ID=M.EVENTID ORDER BY Time DESC LIMIT class="num">1) as &class="macro">#x27;Last Event Date&class="macro">#x27; FROM UNIQUE_EVENTS) SELECT E_ID as &class="macro">#x27;ID&class="macro">#x27;,Country,Name,
Currency,"Last Event Date" as &class="macro">#x27;Recent Date&class="macro">#x27;,(case cast(strftime(&class="macro">#x27;%w&class="macro">#x27;, DATE(REPLACE("Last Event Date",&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))) as integer) WHEN class="num">0
THEN &class="macro">#x27;Sunday&class="macro">#x27; WHEN class="num">1 THEN &class="macro">#x27;Monday&class="macro">#x27; WHEN class="num">2 THEN &class="macro">#x27;Tuesday&class="macro">#x27; WHEN class="num">3 THEN &class="macro">#x27;Wednesday&class="macro">#x27; WHEN class="num">4 THEN &class="macro">#x27;Thursday&class="macro">#x27; WHEN class="num">5 THEN &class="macro">#x27;Friday&class="macro">#x27; ELSE
&class="macro">#x27;Saturday&class="macro">#x27; END) as &class="macro">#x27;Day&class="macro">#x27; FROM INFO_DATE Order BY "Recent Date" DESC;
WITH UNIQUE_EVENTS AS(
    SELECT DISTINCT
        M.EVENTID as &class="macro">#x27;E_ID&class="macro">#x27;,

把财经日历事件锚定到具体星期

上面这段 SQL 视图逻辑,核心是把 MQL5 日历里的每个独立事件,反查它上一次实际发生的时间点,并换算成星期几。思路是先去重出事件清单(UNIQUE_EVENTS),再对每个事件用子查询抓不大于记录日的最近一条 TimeSchedule,得到 Last Event Date。 最后一步用 strftime('%w') 把日期转成周序号,0 到 6 映射成 Sunday 到 Saturday。实跑结果里,CFTC 的 NZD、EUR、JPY 非商业净头寸三条记录,最近一次都是 2024.09.27 21:30,落在 Friday——说明这类持仓报告在样本周内统一周五晚挂出。 视图尾部写了两遍 ORDER BY "Recent Date" DESC,属冗余笔误,MT5 里建视图时留一句即可,否则部分 SQLite 内核会报语法错。外汇与贵金属受此类数据扰动明显,事件窗口前后波动率可能跳升,实操需自担高风险。

MQL5 / C++
M.COUNTRY as &class="macro">#x27;Country&class="macro">#x27;,
M.EVENTNAME as &class="macro">#x27;Name&class="macro">#x27;,
M.EVENTCURRENCY as &class="macro">#x27;Currency&class="macro">#x27;
FROM &class="macro">#x27;MQL5Calendar&class="macro">#x27; M
)
INFO_DATE AS(
	SELECT
		E_ID,
		Country,
		Name,
		Currency,
		(
			SELECT
				T.DST_NONE as &class="macro">#x27;Time&class="macro">#x27;
			FROM
				MQL5Calendar M,
				Record R
				INNER JOIN TimeSchedule T ON T.ID = M.ID
			WHERE
				DATE(REPLACE(Time, &class="macro">#x27;.&class="macro">#x27;, &class="macro">#x27;-&class="macro">#x27;)) <= R.Date
				AND E_ID = M.EVENTID
			ORDER BY
				Time DESC
			LIMIT class="num">1
		) as &class="macro">#x27;Last Event Date&class="macro">#x27;
	FROM UNIQUE_EVENTS
)
SELECT
	E_ID as &class="macro">#x27;ID&class="macro">#x27;,
	Country,
	Name,
	Currency,
	"Last Event Date" as &class="macro">#x27;Recent Date&class="macro">#x27;,
	(
		CASE CAST(strftime(&class="macro">#x27;%w&class="macro">#x27;, DATE(REPLACE("Last Event Date", &class="macro">#x27;.&class="macro">#x27;, &class="macro">#x27;-&class="macro">#x27;))) AS INTEGER)
		WHEN class="num">0 THEN &class="macro">#x27;Sunday&class="macro">#x27;
		WHEN class="num">1 THEN &class="macro">#x27;Monday&class="macro">#x27;
		WHEN class="num">2 THEN &class="macro">#x27;Tuesday&class="macro">#x27;
		WHEN class="num">3 THEN &class="macro">#x27;Wednesday&class="macro">#x27;
		WHEN class="num">4 THEN &class="macro">#x27;Thursday&class="macro">#x27;
		WHEN class="num">5 THEN &class="macro">#x27;Friday&class="macro">#x27;
		ELSE &class="macro">#x27;Saturday&class="macro">#x27;
		END
	) as &class="macro">#x27;Day&class="macro">#x27;
FROM INFO_DATE
ORDER BY "Recent Date" DESC;
ORDER BY "Recent Date" DESC
CREATE VIEW IF NOT EXISTS &class="macro">#x27;Upcoming Event Dates&class="macro">#x27; AS WITH UNIQUE_EVENTS AS(SELECT DISTINCT M.EVENTID as &class="macro">#x27;E_ID&class="macro">#x27;,M.COUNTRY as &class="macro">#x27;Country&class="macro">#x27;,M.EVENTNAME as &class="macro">#x27;Name&class="macro">#x27;,M.EVENTCURRENCY as &class="macro">#x27;Currency&class="macro">#x27; FROM &class="macro">#x27;MQL5Calendar&class="macro">#x27; M),INFO_DATE AS(SELECT E_ID,Country,Name,Currency,(SELECT T.DST_NONE as &class="macro">#x27;Time&class="macro">#x27; FROM MQL5Calendar M,Record R INNER JOIN TIMESCHEDULE T ON T.ID=M.ID WHERE DATE(REPLACE(Time,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))>R.Date AND E_ID=M.EVENTID ORDER BY Time ASC

◍ 用 SQL 拉出每个事件的下一个公布日

在 MT5 内置的 MQL5Calendar 表上跑一段 SQLite 查询,可以把每个宏观事件的下一次发布时间直接算出来,并按时间升序排列,省去人工翻日历。 查询先以 UNIQUE_EVENTS 做去重,取出 EVENTID、国家、事件名、货币四个字段;再用 INFO_DATE 子查询,针对每个事件找比当前 Record 日期更大的最近一条 TimeSchedule,LIMIT 1 拿到‘Next Event Date’。 若子查询无结果则返回 NULL,外层用 CASE 转成‘Unknown’;有值时再用 strftime('%w',...) 把日期换成星期名,0 到 5 对应周日至周五,其余归 Saturday。 实跑样本里,ID 为 410020004 的韩国 Industrial Production y/y(KRW)下一次时间是 2024.09.30 01:00,被标为 Monday。外汇与贵金属受此类数据跳空影响明显,属于高风险场景,实际布局前建议在策略测试器里先验证日历读取逻辑。

MQL5 / C++
WITH UNIQUE_EVENTS AS(
    SELECT DISTINCT
        M.EVENTID as &class="macro">#x27;E_ID&class="macro">#x27;,
        M.COUNTRY as &class="macro">#x27;Country&class="macro">#x27;,
        M.EVENTNAME as &class="macro">#x27;Name&class="macro">#x27;,
        M.EVENTCURRENCY as &class="macro">#x27;Currency&class="macro">#x27;
    FROM &class="macro">#x27;MQL5Calendar&class="macro">#x27; M
),
INFO_DATE AS(
    SELECT
        E_ID,
        Country,
        Name,
        Currency,
        (
            SELECT
                T.DST_NONE as &class="macro">#x27;Time&class="macro">#x27;
            FROM
                MQL5Calendar M,
                Record R
                INNER JOIN TimeSchedule T ON T.ID=M.ID
            WHERE
                DATE(REPLACE(Time, &class="macro">#x27;.&class="macro">#x27;, &class="macro">#x27;-&class="macro">#x27;)) > R.Date
                AND E_ID = M.EVENTID
            ORDER BY Time ASC
            LIMIT class="num">1
        ) as &class="macro">#x27;Next Event Date&class="macro">#x27;
    FROM UNIQUE_EVENTS
)
SELECT
    E_ID as &class="macro">#x27;ID&class="macro">#x27;,
    Country,
    Name,
    Currency,
    (CASE WHEN "Next Event Date" IS NULL THEN &class="macro">#x27;Unknown&class="macro">#x27; ELSE "Next Event Date" END) as &class="macro">#x27;Upcoming Date&class="macro">#x27;,
    (CASE WHEN "Next Event Date" <> &class="macro">#x27;Unknown&class="macro">#x27; THEN(CASE CAST(strftime(&class="macro">#x27;%w&class="macro">#x27;, DATE(REPLACE("Next Event Date", &class="macro">#x27;.&class="macro">#x27;, &class="macro">#x27;-&class="macro">#x27;))) AS INTEGER)
            WHEN class="num">0 THEN &class="macro">#x27;Sunday&class="macro">#x27;
            WHEN class="num">1 THEN &class="macro">#x27;Monday&class="macro">#x27;
            WHEN class="num">2 THEN &class="macro">#x27;Tuesday&class="macro">#x27;
            WHEN class="num">3 THEN &class="macro">#x27;Wednesday&class="macro">#x27;
            WHEN class="num">4 THEN &class="macro">#x27;Thursday&class="macro">#x27;
            WHEN class="num">5 THEN &class="macro">#x27;Friday&class="macro">#x27;
            ELSE &class="macro">#x27;Saturday&class="macro">#x27;
        END)
    ELSE &class="macro">#x27;Unknown&class="macro">#x27; END) as &class="macro">#x27;Day&class="macro">#x27;
FROM INFO_DATE
ORDER BY "Upcoming Date" ASC;

「用日历数据喂给EA做亚盘事件过滤」

上面这段是某段MQL5脚本里直接打印出的财经日历原始行,能看到韩国、澳洲、日本在2024年9月30日到10月1日之间的事件排布:韩国零售销售月率和服务业指数在9月30日01:00(周一)发布,澳洲S&P全球制造业PMI在10月1日01:00(周二),日本失业率与求才倍率在10月1日01:30(周二)。每行前面的数字如410020005、36500001是事件ID,后面紧跟国家、指标名、货币对代号和发布时间。 这类原始数据若直接塞进EA,亚盘时段(东京01:00–03:00)会同时撞上日韩多个事件,黄金和USDJPY的瞬时波动概率会明显抬升。实操上建议在MQL5里按货币字段(KRW/AUD/JPY)做过滤,只保留你盯的品种相关行,避免在非农之外被小数据噪音误触发。 外汇与贵金属属高风险品种,事件窗口内的滑点和点差扩张可能吞掉策略缓冲,任何基于日历的过滤都只是降低干扰,不保证方向。

MQL5 / C++
class="num">410020005      South Korea   Retail Sales m/m         KRW    class="num">2024.09.class="num">30 class="num">01:class="num">00      Monday
class="num">410020006      South Korea   Index of Services m/m   KRW    class="num">2024.09.class="num">30 class="num">01:class="num">00      Monday
class=class="str">"cmt">// ...
class="num">36500001        Australia    S&P Global Manufacturing PMI    AUD    class="num">2024.10.class="num">01 class="num">01:class="num">00      Tuesday
class="num">392030007       Japan        Unemployment Rate               JPY    class="num">2024.10.class="num">01 class="num">01:class="num">30      Tuesday
class="num">392050002       Japan        Jobs to Applicants Ratio       JPY    class="num">2024.10.class="num">01 class="num">01:class="num">30      Tuesday
class=class="str">"cmt">// ...

EA主程序怎么吃进自定义新闻事件

新闻交易EA的主文件靠一组 input 把自定义事件ID喂给策略。最多支持5组(CEvent1~CEvent5),每组一个开关加一条逗号分隔的ID串,单组上限14个ID,比如 "36010006,840030005,840030016" 这种写法。 初始化阶段用 Answer() 把 Choice 型的 Yes/No 转成布尔值挂到 CEvent.useEvents,再用 StringSplit() 按逗号把字符串拆成 EventIds 数组。实盘里 OnInit() 会先判深浅色模式、跑 InitCommon(),再用 MQLInfoInteger(MQL_TESTER) 分流测试器与实盘两套初始化;经济数据库靠 NewsObject.CreateEconomicDatabaseMemory() 建在内存里,图形层由 CGraphics 按 iSecondsPreEvent 节奏刷新。 定时器是节拍器:实盘下每30秒(服务器时间能被30整除)触发一次 OnTimer(),新日线K线出现就重抓当日新闻,新小时线则视情况调 InitNonTester() 重建。交易时段由 CTS.isSessionStart() / isSessionEnd() 卡口,时段外不动作、临近结束主动平仓。 开仓逻辑看 NewsObject.IMPACT() 定方向:负面冲击且事件货币等于账户盈利货币,市价单买、止损单挂买停;不匹配则反手卖。正影响对称处理。止损单模式会双向同挂买停卖停,Magic 编号直接用 EventId,吃两边波动。ExecutionOnTrade() 里若非市价单类型就跑 Trade.SlippageReduction(),试图压住新闻行情里的滑点——外汇与贵金属新闻市滑点可能剧烈扩大,属高风险场景。 下面这段是输入声明与事件对象初始化的原样代码,逐行对应上面说的开关与拆串: input Choice iCustom_Event_1=No; // 第1组自定义事件总开关,默认关 input string iCustom_Event_1_IDs=""; // 第1组事件ID串,空串表示未填 input Choice iCustom_Event_2=No; // 第2组开关 input string iCustom_Event_2_IDs=""; // 第2组ID串 input Choice iCustom_Event_3=No; // 第3组开关 input string iCustom_Event_3_IDs=""; // 第3组ID串 input Choice iCustom_Event_4=No; // 第4组开关 input string iCustom_Event_4_IDs=""; // 第4组ID串 input Choice iCustom_Event_5=No; // 第5组开关 input string iCustom_Event_5_IDs=""; // 第5组ID串 CEvent1.useEvents = Answer(iCustom_Event_1); // 把第1组开关转布尔塞进结构体 StringSplit(iCustom_Event_1_IDs, ',', CEvent1.EventIds); // 第1组ID串按逗号拆数组 CEvent2.useEvents = Answer(iCustom_Event_2); // 第2组同上 StringSplit(iCustom_Event_2_IDs, ',', CEvent2.EventIds); // 第2组拆串 CEvent3.useEvents = Answer(iCustom_Event_3); // 第3组同上 StringSplit(iCustom_Event_3_IDs, ',', CEvent3.EventIds); // 第3组拆串 CEvent4.useEvents = Answer(iCustom_Event_4); // 第4组同上 StringSplit(iCustom_Event_4_IDs, ',', CEvent4.EventIds); // 第4组拆串 CEvent5.useEvents = Answer(iCustom_Event_5); // 第5组开关转布尔

MQL5 / C++
input Choice iCustom_Event_1=No;class=class="str">"cmt">//USE EVENT IDs BELOW?
input class="type">class="kw">string iCustom_Event_1_IDs="";class=class="str">"cmt">//EVENT IDs[Separate with a comma][MAX class="num">14]
input Choice iCustom_Event_2=No;class=class="str">"cmt">//USE EVENT IDs BELOW?
input class="type">class="kw">string iCustom_Event_2_IDs="";class=class="str">"cmt">//EVENT IDs[Separate with a comma][MAX class="num">14]
input Choice iCustom_Event_3=No;class=class="str">"cmt">//USE EVENT IDs BELOW?
input class="type">class="kw">string iCustom_Event_3_IDs="";class=class="str">"cmt">//EVENT IDs[Separate with a comma][MAX class="num">14]
input Choice iCustom_Event_4=No;class=class="str">"cmt">//USE EVENT IDs BELOW?
input class="type">class="kw">string iCustom_Event_4_IDs="";class=class="str">"cmt">//EVENT IDs[Separate with a comma][MAX class="num">14]
input Choice iCustom_Event_5=No;class=class="str">"cmt">//USE EVENT IDs BELOW?
input class="type">class="kw">string iCustom_Event_5_IDs="";class=class="str">"cmt">//EVENT IDs[Separate with a comma][MAX class="num">14]
CEvent1.useEvents = Answer(iCustom_Event_1);
StringSplit(iCustom_Event_1_IDs, &class="macro">#x27;,&class="macro">#x27;, CEvent1.EventIds);
CEvent2.useEvents = Answer(iCustom_Event_2);
StringSplit(iCustom_Event_2_IDs, &class="macro">#x27;,&class="macro">#x27;, CEvent2.EventIds);
CEvent3.useEvents = Answer(iCustom_Event_3);
StringSplit(iCustom_Event_3_IDs, &class="macro">#x27;,&class="macro">#x27;, CEvent3.EventIds);
CEvent4.useEvents = Answer(iCustom_Event_4);
StringSplit(iCustom_Event_4_IDs, &class="macro">#x27;,&class="macro">#x27;, CEvent4.EventIds);
CEvent5.useEvents = Answer(iCustom_Event_5);

◍ 初始化与定时器的分环境写法

EA 启动阶段最忌讳把回测和实盘混为一谈。这段 OnInit 用 MQLInfoInteger(MQL_TESTER) 做分支:不在策略测试器里就跑 InitNonTester(),在测试器里跑 InitTester(),两套初始化逻辑分开维护,避免实盘对象在回测环境里报错。 定时器频率也跟着环境走——EventSetMillisecondTimer 在非测试器下设为 100 毫秒,测试器里直接拉到 30000 毫秒(30 秒)。回测时没必要毫秒级刷图形,能省掉大量无谓的计算开销。 OnTimer 里还有一层节流:非测试器时用 TimeTradeServer()%30==0 做 30 秒对齐,只有整 30 秒才执行主体;测试器则每 tick 都放行。日历数组 CalendarArray 在 OnInit 和每日新蜡烛时由 EconomicDetailsMemory 重抓,保证新闻事件不跨日污染。 开 MT5 把这段贴进 EA,改 iSecondsPreEvent 和定时器毫秒数,能直观看到新闻对象在 D1 切换时的刷新节奏。外汇与贵金属受新闻冲击跳空频繁,这类日历驱动逻辑需先在策略测试器跑通再上实盘,高风险品种尤须谨慎。

MQL5 / C++
StringSplit(iCustom_Event_5_IDs, &class="macro">#x27;,&class="macro">#x27;, CEvent5.EventIds);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Assign if in LightMode or not
   isLightMode=(iDisplayMode==Display_LightMode)?true:class="kw">false;
class=class="str">"cmt">//--- call function for common initialization procedure
   InitCommon();
class=class="str">"cmt">//--- store Init result
   class="type">int InitResult;
   if(!MQLInfoInteger(MQL_TESTER))class=class="str">"cmt">//Checks whether the program is in the strategy tester
     {
     class=class="str">"cmt">//--- initialization procedure outside strategy tester
      InitResult=InitNonTester();
     }
   else
     {
     class=class="str">"cmt">//--- initialization procedure inside strategy tester
      InitResult=InitTester();
     }
class=class="str">"cmt">//--- Create DB in memory
   NewsObject.CreateEconomicDatabaseMemory();
class=class="str">"cmt">//--- Initialize Candle properties pointer object
   CP = new CCandleProperties();
class=class="str">"cmt">//--- Retrieve news events for the current Daily period into array CalendarArray
   NewsObject.EconomicDetailsMemory(CalendarArray,CTM.Time(TimeTradeServer(),class="num">0,class="num">0,class="num">0),
                                     (iOrderType!=StopOrdersType)?true:class="kw">false);
class=class="str">"cmt">//--- Initialize Common graphics class pointer object
   CGraphics = new CCommonGraphics(Answer(iDisplay_Date),Answer(iDisplay_Spread),
                                   Answer(iDisplay_NewsInfo),Answer(iDisplay_EventObj));
   CGraphics.GraphicsRefresh(iSecondsPreEvent);class=class="str">"cmt">//-- Create chart objects
class=class="str">"cmt">//--- Set Time
   CDay.SetmyTime(CalendarArray);
   class=class="str">"cmt">/* create timer, if in the strategy tester set the timer to 30s else 100ms */
   EventSetMillisecondTimer((!MQLInfoInteger(MQL_TESTER))?class="num">100:class="num">30000);
class=class="str">"cmt">//-- Initialize Trade Management class pointer object
   Trade = new CTradeManagement(iDeviation);
class=class="str">"cmt">//--- class="kw">return Init result
   class="kw">return InitResult;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
   if(((!MQLInfoInteger(MQL_TESTER))?class="type">int(TimeTradeServer())%class="num">30==class="num">0:true))
     {
     class=class="str">"cmt">//--- Store start-up time.
      class="kw">static class="type">class="kw">datetime Startup_date = TimeTradeServer();
     if(CTM.DateisToday(Startup_date)&&CP.NewCandle(class="num">0,PERIOD_D1)
        &&MQLInfoInteger(MQL_TESTER))
       {
       class=class="str">"cmt">//--- Retrieve news events for the current Daily period into array CalendarArray
        NewsObject.EconomicDetailsMemory(CalendarArray,CTM.Time(TimeTradeServer(),class="num">0,class="num">0,class="num">0),

「定时器里的事件刷新与交易触发节奏」

这段逻辑跑在 EA 的 OnTimer 类调用链里,核心是把「日历事件」和「交易窗口」解耦成两个独立检查。先判断今天是不是启动日,不是才允许每日新 K 线时重建内存里的经济数据库,避免重复初始化拖慢 MT5 终端。 每日逻辑里若不在策略测试器,新日线(PERIOD_D1)出现就调用 CreateEconomicDatabaseMemory 刷新内存 DB;随后用 EconomicDetailsMemory 抓当天事件,并把时间写进 CDay.SetmyTime。注意检索时带了个三元判断 (iOrderType!=StopOrdersType)?true:false,决定挂单类事件是否纳入。 小时维度上,非测试环境下每根 H1 新烛(NewCandle(2,PERIOD_H1))会再查一次存储 DB 是否需要更新——这是为了防止服务器时区切换或新闻源延迟导致漏事件。会话控制则由 CTS.isSessionStart/End 接管:开盘且未收盘才跑 TradeTime(),而收盘前 45 分钟(isSessionEnd(0,0) 为假)会强制 CloseTrades("NewsTrading") 平掉新闻单。 外汇与贵金属受新闻跳空影响大、滑点可能瞬时扩大,这类自动平仓逻辑仅降低尾部风险,不保证成交价。实盘前建议在 MT5 策略测试器用 2023 年任一季度 Tick 数据跑一遍,确认 45 分钟阈值在你的经纪商点差下不会提前踏空。

MQL5 / C++
if(!CTM.DateisToday(Startup_date))
  {
  if(CP.NewCandle(class="num">1,PERIOD_D1))
    {
    if(!MQLInfoInteger(MQL_TESTER))
      {
      NewsObject.CreateEconomicDatabaseMemory();
      }
    NewsObject.EconomicDetailsMemory(CalendarArray,CTM.Time(TimeTradeServer(),class="num">0,class="num">0,class="num">0),
                (iOrderType!=StopOrdersType)?true:class="kw">false);
    CDay.SetmyTime(CalendarArray);
    CGraphics.GraphicsRefresh(iSecondsPreEvent);
    }
  if(!MQLInfoInteger(MQL_TESTER))
    {
    if(CP.NewCandle(class="num">2,PERIOD_H1))
      {
      class=class="str">"cmt">//--- Check if DB in Storage needs an update

新闻事件驱动的订单触发时机

这段逻辑把日历新闻和 K 线周期绑在一起:日线新蜡烛出现时刷新图表上的事件对象,M1 新蜡烛则每 1 分钟拉取未来一分钟内的新闻并绘制实时区块。也就是说,图形层最低以 1 分钟粒度跟进事件,日线层只做每日一次的事件重建。 TradeTime() 遍历 myTimeData 里的每条事件时间,用 CTM.TimePreEvent 判断当前是否进入「事件前 iSecondsPreEvent 秒」窗口,再叠加交易日过滤;命中后按 iOrderType 分流到 StopOrders 或 SingleOrder。这里没有市价单的硬触发,只是把「允许挂单/下单」的时间门打开了。 外汇与贵金属新闻市前后流动性可能骤降、点差可能跳扩,用这套机制前务必在 MT5 策略测试器外跑 InitNonTester() 核对事件表,并手动调 iSecondsPreEvent 看预警块位置是否符合你的风控。

MQL5 / C++
if(NewsObject.UpdateRecords())
  {
   class=class="str">"cmt">//--- initialization procedure outside strategy tester
   InitNonTester();
  }
   }
  }
 }
 else
  {
   class=class="str">"cmt">//--- Run every New Daily Candle
   if(CP.NewCandle(class="num">3,PERIOD_D1))
     {
      class=class="str">"cmt">//--- Update Event objects on chart
      CGraphics.NewsEvent();
     }
   }
class=class="str">"cmt">//--- Update realtime Graphic every class="num">1 min
   if(CP.NewCandle(class="num">4,PERIOD_M1))
   {
    class=class="str">"cmt">//--- get the news events for the next min ahead of time.
    class="type">class="kw">datetime Time_ahead = TimeTradeServer()+CTM.MinutesS();
    CDay.GetmyTime(CTV.Hourly(CTM.ReturnHour(Time_ahead)),
                   CTV.Minutely(CTV.Minutely(CTM.ReturnMinute(Time_ahead))),
                   myTimeData,myEvents);
    CGraphics.Block_2_Realtime(iSecondsPreEvent);
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|function to check trading time                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void TradeTime()
  {
class=class="str">"cmt">//--- Iterate through the event times
   for(class="type">uint i=class="num">0;i<myTimeData.Size();i++)
    {
     class=class="str">"cmt">//--- Check if it is time to trade each news event
     if(CTM.TimePreEvent(CTM.TimeMinusOffset(class="type">class="kw">datetime(myEvents[i].EventDate),iSecondsPreEvent)
                        ,class="type">class="kw">datetime(myEvents[i].EventDate))
         &&(CTM.isDayOfTheWeek(TradingDay)||iNewSelection==News_Select_Custom_Events))
       {
        class=class="str">"cmt">//--- class="kw">switch for order type selection
        class="kw">switch(iOrderType)
          {
           case StopOrdersType:class=class="str">"cmt">// triggers for STOP ORDERS
             StopOrders(myEvents[i]);
             class="kw">break;
           class="kw">default:class=class="str">"cmt">// triggers for both MARKET POSITION & SINGLE STOP ORDER
             SingleOrder(myEvents[i]);
             class="kw">break;
          }
       }
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|function to open single order types                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SingleOrder(Calendar &NewsEvent)
  {
class=class="str">"cmt">//--- Check each Impact value type
   class="kw">switch(NewsObject.IMPACT(NewsEvent.EventImpact))
     {
      class=class="str">"cmt">//--- When Impact news is negative

◍ 负向事件下的多空分派逻辑

当财经事件被判定为 CALENDAR_IMPACT_NEGATIVE 时,EA 并不会无脑做空,而是先核对事件币种与当前图表品种的盈利币种是否一致。若 NewsEvent.EventCurrency 等于 CSymbol.CurrencyProfit(),说明砸盘冲击直接落在你持仓的结算货币上,此时按 iOrderType 分流:市价单走 Trade.Buy,挂单走 Trade.OpenBuyStop。 反过来,如果事件币种和盈利币种不同,负向冲击对当前品种更倾向构成利多,代码里就切到 Trade.Sell 与 Trade.OpenSellStop。两个分支都用 NewsEvent.EventId 强转 ulong 当作魔术号,注释串里拼了 NewsTrading- 或 NewsTrading-SStop- 加 EventCode,方便在 MT5 终端按魔术号筛单。 这套分派没有用任何方向写死,而是把「币种匹配」当作翻转开关。实盘里你可以把 EventCurrency 和 CurrencyProfit() 的打印加进 OnTick,验证欧美盘遇到美元负向数据却开 buy 的那一瞬间——外汇与贵金属事件交易滑点大、假突破多,属高风险操作,参数未验证前勿直接上实盘。

MQL5 / C++
case CALENDAR_IMPACT_NEGATIVE:
      class=class="str">"cmt">//--- Check if profit currency is news event currency
      if(NewsEvent.EventCurrency==CSymbol.CurrencyProfit())
      {
         class="kw">switch(iOrderType)
         {
          case  MarketPositionType:class=class="str">"cmt">// triggers for MARKET POSITION
            class=class="str">"cmt">//--- Open buy trade with Event id as Magic number
            Trade.Buy(iStoploss,iTakeprofit,class="type">class="kw">ulong(NewsEvent.EventId),
                  "NewsTrading-"+NewsEvent.EventCode);
            class="kw">break;
          case StopOrderType:class=class="str">"cmt">// triggers for SINGLE STOP ORDER
            class=class="str">"cmt">//--- Open buy-stop with Event id as Magic number
            Trade.OpenBuyStop(iStoploss,iTakeprofit,class="type">class="kw">ulong(NewsEvent.EventId),
                        "NewsTrading-SStop-"+NewsEvent.EventCode);
            class="kw">break;
          class="kw">default:
            class="kw">break;
         }
      }
      else
      {
         class="kw">switch(iOrderType)
         {
          case  MarketPositionType:class=class="str">"cmt">// triggers for MARKET POSITION
            class=class="str">"cmt">//--- Open sell trade with Event id as Magic number
            Trade.Sell(iStoploss,iTakeprofit,class="type">class="kw">ulong(NewsEvent.EventId),
                   "NewsTrading-"+NewsEvent.EventCode);
            class="kw">break;
          case StopOrderType:class=class="str">"cmt">// triggers for SINGLE STOP ORDER
            class=class="str">"cmt">//--- Open buy-stop with Event id as Magic number
            Trade.OpenSellStop(iStoploss,iTakeprofit,class="type">class="kw">ulong(NewsEvent.EventId),
                        "NewsTrading-SStop-"+NewsEvent.EventCode);
            class="kw">break;
          class="kw">default:
            class="kw">break;
         }
      }
      class="kw">break;
class=class="str">"cmt">//--- When Impact news is positive

「利好事件下的多空分叉逻辑」

当财经日历事件被判定为 CALENDAR_IMPACT_POSITIVE(利好)时,EA 并不会无脑做多,而是先比对事件货币与当前品种报价货币(Profit Currency)是否一致。 若 NewsEvent.EventCurrency 等于 CSymbol.CurrencyProfit(),说明利好直接作用于本品种结算货币,此时按 iOrderType 分支:市价单走 Trade.Sell 做空,挂单走 Trade.OpenSellStop 下卖停。反之,事件货币与结算货币不同,则市价单 Trade.Buy 做多、挂单 Trade.OpenBuyStop 下买停。 Magic 号统一用 NewsEvent.EventId 的 ulong 强转值,注释里标记了这是把事件 ID 写进订单魔数,方便回测时按事件溯源。订单备注前缀为 NewsTrading- 或 NewsTrading-SStop- 再拼 EventCode,在 MT5 订单池里一眼能筛出新闻单。 外汇与贵金属受新闻跳空影响大,这类逻辑实盘触发滑点可能偏离预期,建议先在策略测试器用 2023 年非农数据跑一遍验证分叉方向。

MQL5 / C++
case CALENDAR_IMPACT_POSITIVE:
      class=class="str">"cmt">//--- Check if profit currency is news event currency
      if(NewsEvent.EventCurrency==CSymbol.CurrencyProfit())
      {
         class="kw">switch(iOrderType)
         {
          case  MarketPositionType:class=class="str">"cmt">// triggers for MARKET POSITION
            class=class="str">"cmt">//--- Open sell trade with Event id as Magic number
            Trade.Sell(iStoploss,iTakeprofit,class="type">class="kw">ulong(NewsEvent.EventId),
                  "NewsTrading-"+NewsEvent.EventCode);
            class="kw">break;
          case StopOrderType:class=class="str">"cmt">// triggers for SINGLE STOP ORDER
            class=class="str">"cmt">//--- Open sell-stop with Event id as Magic number
            Trade.OpenSellStop(iStoploss,iTakeprofit,class="type">class="kw">ulong(NewsEvent.EventId),
                      "NewsTrading-SStop-"+NewsEvent.EventCode);
            class="kw">break;
          class="kw">default:
            class="kw">break;
         }
      }
      else
      {
         class="kw">switch(iOrderType)
         {
          case  MarketPositionType:class=class="str">"cmt">// triggers for MARKET POSITION
            class=class="str">"cmt">//--- Open buy trade with Event id as Magic number
            Trade.Buy(iStoploss,iTakeprofit,class="type">class="kw">ulong(NewsEvent.EventId),
                  "NewsTrading-"+NewsEvent.EventCode);
            class="kw">break;
          case StopOrderType:class=class="str">"cmt">// triggers for SINGLE STOP ORDER
            class=class="str">"cmt">//--- Open sell-stop with Event id as Magic number
            Trade.OpenBuyStop(iStoploss,iTakeprofit,class="type">class="kw">ulong(NewsEvent.EventId),
                      "NewsTrading-SStop-"+NewsEvent.EventCode);
            class="kw">break;
          class="kw">default:
            class="kw">break;
         }
      }
      class="kw">break;
class=class="str">"cmt">//--- Unknown

挂单触发后的执行分支

上面这段是新闻交易 EA 里负责挂单与成交后处理的核心片段。StopOrders 函数不区分事件影响等级,直接用 Trade.OpenStops 双向铺 buy-stop 和 sell-stop,订单备注里拼了 EventCode 方便回测时按事件归类。 OnTrade 只在交易时段内(isSessionStart 成立且未到 session 结束)才跑 ExecutionOnTrade,避免非交易时间被外部成交干扰逻辑。 ExecutionOnTrade 里有两个判断:选了 StopOrdersType 就开 FundamentalMode 把新闻上下文喂给交易类;只要不是市价单(MarketPositionType),就调 SlippageReduction 用止损止盈参数压滑点。外汇和贵金属新闻行情瞬时跳空多,这种滑点收敛对挂单成交成本影响很直接,建议开 MT5 把 iStoploss / iTakeprofit 调小一档看成交率变化。

MQL5 / C++
   class="kw">default:
      class="kw">break;
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|function to open orders                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void StopOrders(Calendar &NewsEvent)
  {
class=class="str">"cmt">//--- Opens both buy-stop & sell-stop regardless of event impact
   Trade.OpenStops(iStoploss,iTakeprofit,class="type">class="kw">ulong(NewsEvent.EventId),
                   "NewsTrading-Stops-"+NewsEvent.EventCode);
  }
class="type">void OnTrade()
{
   class=class="str">"cmt">//--- Check if time is within the trading session
   if(CTS.isSessionStart() && !CTS.isSessionEnd(class="num">0,class="num">0))
   {
      class=class="str">"cmt">//--- Run procedures
      ExecutionOnTrade();
   }
}
class="type">void ExecutionOnTrade()
{
   class=class="str">"cmt">//--- if stop orders, enable fundamental mode
   if(iOrderType == StopOrdersType)
   {
      Trade.FundamentalMode("NewsTrading");
   }

   class=class="str">"cmt">//--- when stop order(s), enable slippage reduction
   if(iOrderType != MarketPositionType)
   {
      Trade.SlippageReduction(iStoploss, iTakeprofit, "NewsTrading");
   }
}

◍ 一点提醒

这套新闻交易 EA 把事件库拆成了「近期事件」和「即将发生事件」两个视图,查询逻辑在前面各节已逐一拆过,实际跑起来你可以直接读存储里的日历数据核对字段。OnTimer 与 OnTrade 两个回调的加入,意味着策略不再只靠 Tick 驱动,而是能按你设的时间窗或成交动作触发,外汇和贵金属行情在新闻前后跳空频繁,用这套机制要先在小点差账户做历史回放。 附件 NewsTrading_Part6.zip 约 639.7 KB,里面是完整工程;拷进 MT5 的 MQL5/Experts 目录编译后,从输入参数填自定义事件名就能起跑。新闻交易杠杆高、滑点不可控,任何事件触发的下单都只是概率优势,不是稳赢。

把重复劳动交给小布
这些新闻筛选与止损单触发的诊断,小布盯盘的AIGC已内置,打开对应品种页即可看到事件影响力与缓冲价位的参考,你只管定配置文件。

常见问题

每个输入项最多可输入14个事件ID,超出部分不会被EA读取,建议按货币对分组管理。
目前小布提供事件影响力与下次日历时间的看板,配置逻辑仍需在EA输入菜单中手动设定,但可照搬看板参数减少试错。
单一止损订单要求事先具备事件影响力以确定方向,而普通止损订单双边挂单无需预知方向,触发其一便删另一单。
本篇EA整合时复用性能提升篇的数据库视图与缓存逻辑,能压低策略测试器运行时间,避免重复计算日历。
会保留反向风险敞口,行情反转时可能双向亏损,EA主动删除是控制新闻滑点冲击的基本动作。