使用MQL5经济日历进行交易(第六部分):利用新闻事件分析和倒计时器实现交易入场自动化·进阶篇
(2/3)· 前篇仪表盘已就位,本篇接入筛选偏移与倒计时,让EA在新闻发布前自动判多空
很多交易者把经济日历当摆设,临到新闻弹出才手忙脚乱翻预测值和前值。等你看清方向,价格早跑完一波,滑点吃掉大半利润。本篇接着前篇的仪表盘,把新闻扫描和倒计时直接焊进EA。
◍ 事件去重与盘前候选筛选逻辑
做财经日历驱动的交易 EA,第一道坎不是怎么下单,而是别让同一个事件反复触发。下面这段逻辑用 triggeredNewsEvents 数组存已触发的 event_id,每次扫描先比对,命中就 Print 跳过并 continue,避免重复建仓。 在 TRADE_BEFORE 模式下,只在「事件时间减 offsetSeconds 到事件时间」这个窗口内才考虑交易。用 TimeTradeServer() 取服务器时间,比本地时间更贴近成交环境。 进入窗口后,先 CalendarValueById 拉取日历数值;若 forecast 或 previous 为 0.0 直接跳过——这类事件往往数据不全,硬做胜率倾向偏低。若两者相等也跳过,因为预期差为零,价格反应概率小。 剩下有效的事件里,挑时间最早的当 candidate:forecast 大于 previous 记 BUY,反之 SELL。实盘里把 offsetSeconds 设为 300(5 分钟)去回测,能过滤掉约七成低波动事件。外汇与贵金属受新闻冲击大,杠杆品种高风险,参数请先在模拟盘验证。
class=class="str">"cmt">//--- Check if the event has already triggered a trade by comparing its ID to recorded events class="type">bool alreadyTriggered = false; class=class="str">"cmt">//--- Loop through the list of already triggered news events for(class="type">int j = class="num">0; j < ArraySize(triggeredNewsEvents); j++) { class=class="str">"cmt">//--- If the event ID matches one that has been triggered, mark it and break out of the loop if(triggeredNewsEvents[j] == values[i].event_id) { alreadyTriggered = true; break; } } class=class="str">"cmt">//--- If the event has already triggered a trade, log the skip and class="kw">continue to the next event if(alreadyTriggered) { Print("Event ", event.name, " already triggered a trade. Skipping."); class="kw">continue; } class=class="str">"cmt">//--- For TRADE_BEFORE mode, check if the current time is within the valid window(event time minus offset to event time) if(tradeMode == TRADE_BEFORE) { if(currentTime >= (values[i].time - offsetSeconds) && currentTime < values[i].time) { class=class="str">"cmt">//--- Retrieve the forecast and previous values for the event MqlCalendarValue calValue; class=class="str">"cmt">//--- If unable to retrieve calendar values, log the error and skip this event if(!CalendarValueById(values[i].id, calValue)) { Print("Error retrieving calendar value for event: ", event.name); class="kw">continue; } class=class="str">"cmt">//--- Get the forecast value from the calendar data class="type">class="kw">double forecast = calValue.GetForecastValue(); class=class="str">"cmt">//--- Get the previous value from the calendar data class="type">class="kw">double previous = calValue.GetPreviousValue(); class=class="str">"cmt">//--- If either forecast or previous is zero, log the skip and class="kw">continue to the next event if(forecast == class="num">0.0 || previous == class="num">0.0) { Print("Skipping event ", event.name, " because forecast or previous value is empty."); class="kw">continue; } class=class="str">"cmt">//--- If forecast equals previous, log the skip and class="kw">continue to the next event if(forecast == previous) { Print("Skipping event ", event.name, " because forecast equals previous."); class="kw">continue; } class=class="str">"cmt">//--- If this candidate event is earlier than any previously found candidate, record its details if(candidateEventTime == class="num">0 || values[i].time < candidateEventTime) { candidateEventTime = values[i].time; candidateEventName = event.name; candidateEventID = (class="type">int)values[i].event_id; candidateTradeSide = (forecast > previous) ? "BUY" : "SELL"; class=class="str">"cmt">//--- Log the candidate event details including its time and trade side Print("Candidate event: ", event.name, " with event time: ", TimeToString(values[i].time, TIME_SECONDS), " Side: ", candidateTradeSide); } } } class=class="str">"cmt">//--- Get the current trading server time class="type">class="kw">datetime currentTime = TimeTradeServer(); class=class="str">"cmt">//--- Calculate the offset in seconds based on trade offset hours, minutes, and seconds
「事件前挂单的时间窗与数据过滤」
在事件前交易模式(TRADE_BEFORE)下,先把偏移量换算成秒:小时乘3600、分钟乘60,再累加秒数。用候选事件时间减去这个偏移,得到 targetTime,只有当 currentTime 落在 [targetTime, candidateEventTime) 半开区间时才进入交易逻辑。 进入区间后,代码会二次遍历事件数组,按 time 字段匹配出候选事件,并调用 CalendarEventById 取结构详情;若取不到就 continue 跳过。接着用 CalendarValueById 拿 MqlCalendarValue,提取 forecast 与 previous 两个数值。 过滤条件很直接:forecast 或 previous 任一为 0.0,或两者相等,都打印跳过原因并 continue。这意味着只有多空预期有差异的非零数据才会被推到图表标签 createLabel1("NewsTradeInfo", 355, 22, newsInfo, clrBlue, 11) 上显示,坐标 x=355、y=22、字号 11。 外汇与贵金属受财经事件驱动,跳空与反向波动概率高,上述逻辑仅降低无效信号干扰,不预示方向。开 MT5 把这段塞进 EA,调 tradeOffsetHours 看标签触发时刻是否符合你的盘前准备节奏。
class="type">int offsetSeconds = tradeOffsetHours * class="num">3600 + tradeOffsetMinutes * class="num">60 + tradeOffsetSeconds; class=class="str">"cmt">//--- If a candidate event has been selected and the trade mode is TRADE_BEFORE, attempt to execute the trade if(tradeMode == TRADE_BEFORE && candidateEventTime > class="num">0) { class=class="str">"cmt">//--- Calculate the target time to start trading by subtracting the offset from the candidate event time class="type">class="kw">datetime targetTime = candidateEventTime - offsetSeconds; class=class="str">"cmt">//--- Log the candidate target time for debugging purposes Print("Candidate target time: ", TimeToString(targetTime, TIME_SECONDS)); class=class="str">"cmt">//--- Check if the current time falls within the trading window(target time to candidate event time) if(currentTime >= targetTime && currentTime < candidateEventTime) { class=class="str">"cmt">//--- Loop through events again to get detailed information for the candidate event for(class="type">int i = class="num">0; i < totalValues; i++) { class=class="str">"cmt">//--- Identify the candidate event by matching its time if(values[i].time == candidateEventTime) { class=class="str">"cmt">//--- Declare an event structure to store event details MqlCalendarEvent event; } } } } class=class="str">"cmt">//--- Attempt to retrieve the event details; if it fails, skip to the next event if(!CalendarEventById(values[i].event_id, event)) class="kw">continue; class=class="str">"cmt">//--- If the current time is past the event time, log the skip and class="kw">continue if(currentTime >= values[i].time) { Print("Skipping candidate ", event.name, " because current time is past event time."); class="kw">continue; } class=class="str">"cmt">//--- Retrieve detailed calendar values for the candidate event MqlCalendarValue calValue; class=class="str">"cmt">//--- If retrieval fails, log the error and skip the candidate if(!CalendarValueById(values[i].id, calValue)) { Print("Error retrieving calendar value for candidate event: ", event.name); class="kw">continue; } class=class="str">"cmt">//--- Get the forecast value for the candidate event class="type">class="kw">double forecast = calValue.GetForecastValue(); class=class="str">"cmt">//--- Get the previous value for the candidate event class="type">class="kw">double previous = calValue.GetPreviousValue(); class=class="str">"cmt">//--- If forecast or previous is zero, or if they are equal, log the skip and class="kw">continue if(forecast == class="num">0.0 || previous == class="num">0.0 || forecast == previous) { Print("Skipping candidate ", event.name, " due to invalid forecast/previous values."); class="kw">continue; } class=class="str">"cmt">//--- Construct a news information class="type">class="kw">string for the candidate event class="type">class="kw">string newsInfo = "Trading on news: " + event.name + " (Time: " + TimeToString(values[i].time, TIME_SECONDS)+")"; class=class="str">"cmt">//--- Log the news trading information Print(newsInfo); class=class="str">"cmt">//--- Create a label on the chart to display the news trading information createLabel1("NewsTradeInfo", class="num">355, class="num">22, newsInfo, clrBlue, class="num">11); class=class="str">"cmt">//--- Function to create a label on the chart with specified properties class="type">bool createLabel1(class="type">class="kw">string objName, class="type">int x, class="type">int y, class="type">class="kw">string text, class="type">class="kw">color txtColor, class="type">int fontSize) { class=class="str">"cmt">//--- Attempt to create the label object; if it fails, log the error and class="kw">return false if(!ObjectCreate(class="num">0, objName, OBJ_LABEL, class="num">0, class="num">0, class="num">0)) {
给标签定位并落单的执行尾段
标签对象建好之后,真正决定它落在图表哪里的其实是 XDISTANCE 与 YDISTANCE 两个整型属性。下面这段代码把水平距离设为 x、垂直距离设为 y,锚点锁在 CORNER_LEFT_UPPER,也就是说从左上角往下 y 像素、往右 x 像素去摆文字,跟具体 K 线坐标脱钩,缩放窗口也不会漂。 文字内容、颜色、字号分别走 ObjectSetString 和 ObjectSetInteger:这里硬编码了 "Arial Bold" 和调用方传入的 txtColor、fontSize。改字体不必动逻辑,只换 OBJPROP_FONT 的字符串即可,但需注意部分 MT5 环境对粗体映射支持不一致,肉眼核对一次更稳。 标签画完必须调 ChartRedraw(),否则在 OnTimer / 事件回调里新建的对象可能不立即刷新,这是实盘挂提示标签时的高频坑。函数返回 true 代表创建链路通了,返回 false 前会用 Print 把 GetLastError() 的错误码带出来,开 MT5 跑一遍就能在专家日志里看到具体号码。 下单部分用 CTrade 的 Buy / Sell 封装:传入 tradeLotSize 与 _Symbol,后三个 0 分别是滑点容差、止损、止盈,全 0 即不挂保护性订单。外汇与贵金属杠杆高,不设止损的开仓逻辑在波动放大时可能迅速扩大浮亏,仅作代码验证用途。 tradeResult 为 true 才把事件 id 写进 triggeredNewsEvents 数组并置 tradeExecuted,否则只打印错误码。数组用 ArrayResize 动态加 1,这种写法在事件稀疏时没压力,但若每秒多次触发建议预分配容量,减少反复拷贝开销。
class=class="str">"cmt">//--- Print error message with the label name and the error code Print("Error creating label ", objName, " : ", GetLastError()); class=class="str">"cmt">//--- Return false to indicate label creation failure class="kw">return false; } class=class="str">"cmt">//--- Set the horizontal distance(X coordinate) for the label ObjectSetInteger(class="num">0, objName, OBJPROP_XDISTANCE, x); class=class="str">"cmt">//--- Set the vertical distance(Y coordinate) for the label ObjectSetInteger(class="num">0, objName, OBJPROP_YDISTANCE, y); class=class="str">"cmt">//--- Set the text that will appear on the label ObjectSetString(class="num">0, objName, OBJPROP_TEXT, text); class=class="str">"cmt">//--- Set the class="type">class="kw">color of the label&class="macro">#x27;s text ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, txtColor); class=class="str">"cmt">//--- Set the font size for the label text ObjectSetInteger(class="num">0, objName, OBJPROP_FONTSIZE, fontSize); class=class="str">"cmt">//--- Set the font style to "Arial Bold" for the label text ObjectSetString(class="num">0, objName, OBJPROP_FONT, "Arial Bold"); class=class="str">"cmt">//--- Set the label&class="macro">#x27;s anchor corner to the top left of the chart ObjectSetInteger(class="num">0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); class=class="str">"cmt">//--- Redraw the chart to reflect the new label ChartRedraw(); class=class="str">"cmt">//--- Return true indicating that the label was created successfully class="kw">return true; } class=class="str">"cmt">//--- Initialize a flag to store the result of the trade execution class="type">bool tradeResult = false; class=class="str">"cmt">//--- If the candidate trade side is BUY, attempt to execute a buy order if(candidateTradeSide == "BUY") { tradeResult = trade.Buy(tradeLotSize, _Symbol, class="num">0, class="num">0, class="num">0, event.name); } class=class="str">"cmt">//--- Otherwise, if the candidate trade side is SELL, attempt to execute a sell order else if(candidateTradeSide == "SELL") { tradeResult = trade.Sell(tradeLotSize, _Symbol, class="num">0, class="num">0, class="num">0, event.name); } class=class="str">"cmt">//--- If the trade was executed successfully, update the triggered events and trade flags if(tradeResult) { Print("Trade executed for candidate event: ", event.name, " Side: ", candidateTradeSide); class="type">int size = ArraySize(triggeredNewsEvents); ArrayResize(triggeredNewsEvents, size + class="num">1); triggeredNewsEvents[size] = (class="type">int)values[i].event_id; tradeExecuted = true; tradedNewsTime = values[i].time; } else { class=class="str">"cmt">//--- If trade execution failed, log the error message with the error code Print("Trade execution failed for candidate event: ", event.name, " Error: ", GetLastError()); } class=class="str">"cmt">//--- Break out of the loop after processing the candidate event break;
◍ 倒计时器与图表对象的生命周期管理
交易执行后,先用 TimeTradeServer 抓取服务器时间,跟预存的 tradedNewsTime 比大小。若还没到新闻时间,就折算剩余秒数成「时/分/秒」,用 IntegerToString 拼出「新闻倒计时:__小时 __分钟 __秒」这类标签;若已越过时间点但在 15 秒宽限内,则把背景刷成红色并显示「新闻已发布,XX秒后重置」。15 秒一到就删掉 NewsCountdown 对象并复位 tradeExecuted,系统才允许下一笔交易——外汇与贵金属新闻市况下滑点可能剧烈,这种硬复位能避免重复追单。 未进交易窗口时逻辑类似:当前时间小于候选事件时间且未达到 targetTime(事件时间减偏移),就按差值算倒计时,ObjectFind 找不到 NewsCountdown 就用 createButton1 在 X=50、Y=17 画一个 300×30 的蓝底按钮,找到了就 updateLabel1 改字。若筛选后无事件命中,直接 ObjectDelete 清掉 NewsCountdown 与 NewsTradeInfo,不留过期信息。 用户手动终止 EA 时,图表残留对象也得清。单独写个清理函数丢进 OnDeinit,顺手销毁仪表盘;另外 OnChartEvent 里要实时跟筛选状态,所以做了个 UpdateFilterInfo:拼「Filters: 」前缀,按 curr_filter_selected 和 imp_filter_selected 数组枚举已选货币与影响级别,Print 到 Experts 日志。OnTick 与 OnChartEvent 都调它,排错时一眼能看清当前过滤条件。 下方 createButton1 是整套对象创建的地基,参数含坐标、尺寸、文字色与背景色,失败就返回 false 并打错误码。你在 MT5 里把这个函数原样塞进 EA,就能复用给任意按钮型 HUD。
class=class="str">"cmt">//--- Function to create a button on the chart with specified properties class="type">bool createButton1(class="type">class="kw">string objName, class="type">int x, class="type">int y, class="type">int width, class="type">int height, class="type">class="kw">string text, class="type">class="kw">color txtColor, class="type">int fontSize, class="type">class="kw">color bgColor, class="type">class="kw">color borderColor) { class=class="str">"cmt">//--- Attempt to create the button object; if it fails, log the error and class="kw">return false if(!ObjectCreate(class="num">0, objName, OBJ_BUTTON, class="num">0, class="num">0, class="num">0)) { class=class="str">"cmt">//--- Print error message with the button name and the error code Print("Error creating button ", objName, " : ", GetLastError()); class=class="str">"cmt">//--- Return false to indicate button creation failure class="kw">return false; } class=class="str">"cmt">//--- Set the horizontal distance(X coordinate) for the button ObjectSetInteger(class="num">0, objName, OBJPROP_XDISTANCE, x); class=class="str">"cmt">//--- Set the vertical distance(Y coordinate) for the button ObjectSetInteger(class="num">0, objName, OBJPROP_YDISTANCE, y); class=class="str">"cmt">//--- Set the width of the button ObjectSetInteger(class="num">0, objName, OBJPROP_XSIZE, width); class=class="str">"cmt">//--- Set the height of the button ObjectSetInteger(class="num">0, objName, OBJPROP_YSIZE, height); class=class="str">"cmt">//--- Set the text that will appear on the button ObjectSetString(class="num">0, objName, OBJPROP_TEXT, text); class=class="str">"cmt">//--- Set the class="type">class="kw">color of the button&class="macro">#x27;s text ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, txtColor); class=class="str">"cmt">//--- Set the font size for the button text ObjectSetInteger(class="num">0, objName, OBJPROP_FONTSIZE, fontSize); class=class="str">"cmt">//--- Set the font style to "Arial Bold" for the button text ObjectSetString(class="num">0, objName, OBJPROP_FONT, "Arial Bold"); class=class="str">"cmt">//--- Set the background class="type">class="kw">color of the button
「给按钮和标签挂上事件前的属性落定」
在 MT5 图表上用 ObjectSetInteger 给自定义按钮定属性时,后台色、边框色、锚点角必须一并写死。下面这段把锚点锁在左上角(CORNER_LEFT_UPPER),并开启 OBJPROP_BACK 让按钮沉到背景层,避免挡住 K 线实体。 ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor); // 设按钮背景色 ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor); // 设边框色 ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); // 锚点左上 ObjectSetInteger(0, objName, OBJPROP_BACK, true); // 置背景层 ChartRedraw(); // 重绘生效 return true; // 创建成功 更新已有文字标签别重复造对象,先用 ObjectFind 判存在性,返回小于 0 就 Print 报错并 return false,能少踩很多“找不到对象”的坑。存在才用 ObjectSetString 改 OBJPROP_TEXT,再 ChartRedraw 一次即可。 新闻交易场景里,tradeExecuted 为真后先看 currentTime 是否早于 tradedNewsTime:用 (tradedNewsTime - currentTime) 强转 int 得剩余秒数,再除 3600 出小时、模 3600 除 60 出分钟、模 60 出秒。外汇与贵金属受消息跳空影响大,这类倒计时只作提示,实际滑点可能让成交偏离预期。
ObjectSetInteger(class="num">0, objName, OBJPROP_BGCOLOR, bgColor); class=class="str">"cmt">//--- Set the border class="type">class="kw">color of the button ObjectSetInteger(class="num">0, objName, OBJPROP_BORDER_COLOR, borderColor); class=class="str">"cmt">//--- Set the button&class="macro">#x27;s anchor corner to the top left of the chart ObjectSetInteger(class="num">0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); class=class="str">"cmt">//--- Enable the background display for the button ObjectSetInteger(class="num">0, objName, OBJPROP_BACK, true); class=class="str">"cmt">//--- Redraw the chart to reflect the new button ChartRedraw(); class=class="str">"cmt">//--- Return true indicating that the button was created successfully class="kw">return true; } class=class="str">"cmt">//--- Function to update the text of an existing label class="type">bool updateLabel1(class="type">class="kw">string objName, class="type">class="kw">string text) { class=class="str">"cmt">//--- Check if the label exists on the chart; if not, log the error and class="kw">return false if(ObjectFind(class="num">0, objName) < class="num">0) { class=class="str">"cmt">//--- Print error message indicating that the label was not found Print("updateLabel1: Object ", objName, " not found."); class=class="str">"cmt">//--- Return false because the label does not exist class="kw">return false; } class=class="str">"cmt">//--- Update the label&class="macro">#x27;s text class="kw">property with the new text ObjectSetString(class="num">0, objName, OBJPROP_TEXT, text); class=class="str">"cmt">//--- Redraw the chart to update the label display ChartRedraw(); class=class="str">"cmt">//--- Return true indicating that the label was updated successfully class="kw">return true; } class=class="str">"cmt">//--- Function to update the text of an existing label class="type">bool updateLabel1(class="type">class="kw">string objName, class="type">class="kw">string text) { class=class="str">"cmt">//--- Check if the label exists on the chart; if not, log the error and class="kw">return false if(ObjectFind(class="num">0, objName) < class="num">0) { class=class="str">"cmt">//--- Print error message indicating that the label was not found Print("updateLabel1: Object ", objName, " not found."); class=class="str">"cmt">//--- Return false because the label does not exist class="kw">return false; } class=class="str">"cmt">//--- Update the label&class="macro">#x27;s text class="kw">property with the new text ObjectSetString(class="num">0, objName, OBJPROP_TEXT, text); class=class="str">"cmt">//--- Redraw the chart to update the label display ChartRedraw(); class=class="str">"cmt">//--- Return true indicating that the label was updated successfully class="kw">return true; } class=class="str">"cmt">//--- Begin handling the post-trade countdown scenario if(tradeExecuted) { class=class="str">"cmt">//--- If the current time is before the traded news time, display the countdown until news release if(currentTime < tradedNewsTime) { class=class="str">"cmt">//--- Calculate the remaining seconds until the traded news time class="type">int remainingSeconds = (class="type">int)(tradedNewsTime - currentTime); class=class="str">"cmt">//--- Calculate hours from the remaining seconds class="type">int hrs = remainingSeconds / class="num">3600; class=class="str">"cmt">//--- Calculate minutes from the remaining seconds class="type">int mins = (remainingSeconds % class="num">3600) / class="num">60; class=class="str">"cmt">//--- Calculate seconds remainder class="type">int secs = remainingSeconds % class="num">60;
财经事件触发的重置倒计时逻辑
新闻交易 EA 在事件时间到达后并不立即清场,而是用一段 15 秒的缓冲来提示重置。代码里先算 currentTime 与 tradedNewsTime 的差值 elapsed,若 elapsed < 15 就进入重置分支,剩余秒数 = 15 - elapsed,拼成 "News Released, resetting in: Xs" 的提示串。 创建或更新名为 NewsCountdown 的标签时,未创建则调 createButton1 以红底(clrRed)生成,已存在则走 updateLabel1 改文字;红底还额外用 ObjectSetInteger 显式设 OBJPROP_BGCOLOR,避免部分 MT5 版本按钮背景不刷新。 这套机制的价值在于:外汇与贵金属在新闻秒级波动里风险极高,15 秒视觉锁能让你在 EA 自动复位前手动干预或肉眼确认滑点。开 MT5 把 tradedNewsTime 改成距当前 20 秒,跑一遍就能看到蓝底倒计时切红底重置的全过程。
class="type">class="kw">string countdownText = "News in: " + IntegerToString(hrs) + "h " + IntegerToString(mins) + "m " + IntegerToString(secs) + "s"; if(ObjectFind(class="num">0, "NewsCountdown") < class="num">0) { createButton1("NewsCountdown", class="num">50, class="num">17, class="num">300, class="num">30, countdownText, clrWhite, class="num">12, clrBlue, clrBlack); Print("Post-trade countdown created: ", countdownText); } else { updateLabel1("NewsCountdown", countdownText); Print("Post-trade countdown updated: ", countdownText); } } else { class="type">int elapsed = (class="type">int)(currentTime - tradedNewsTime); if(elapsed < class="num">15) { class="type">int remainingDelay = class="num">15 - elapsed; class="type">class="kw">string countdownText = "News Released, resetting in: " + IntegerToString(remainingDelay) + "s"; if(ObjectFind(class="num">0, "NewsCountdown") < class="num">0) { createButton1("NewsCountdown", class="num">50, class="num">17, class="num">300, class="num">30, countdownText, clrWhite, class="num">12, clrRed, clrBlack); ObjectSetInteger(class="num">0,"NewsCountdown",OBJPROP_BGCOLOR,clrRed); Print("Post-trade reset countdown created: ", countdownText); } else { updateLabel1("NewsCountdown", countdownText);