让新闻交易轻松上手(第六部分):执行交易(3)
概述
在本文中,我们将对存储数据库进行改进,新增一些用于展示数据的视图,例如在MQL5经济日历中显示每个独特事件的最新新闻事件日期或下一个新闻事件日期。这样将提升用户使用程序时的体验,因为能够了解未来或过去的事件。此外,我们将扩展智能教育系统(EA)输入菜单,以适应新闻筛选和止损单入场方法。
此外,我们将更新EA代码,利用之前所写代码来减少策略测试器中EA的运行时间,这些代码来自本系列《让新闻交易轻松上手(第四部分):性能提升》以及《新闻交易轻松上手(第五部分):执行交易(2)》,在后者中,我们管理滑点并开立止损单。
新闻设置输入项
- 选择新闻选项:此选项的目的是允许设置不同的新闻配置文件。不同的配置文件包括:
- 新闻设置:在此新闻配置文件中,用户/交易者可以根据以下条件按自己的喜好筛选新闻事件:
- 日历重要性(CALENDAR IMPORTANCE)
- 事件频率(EVENT FREQUENCY)
- 事件行业(EVENT SECTOR)
- 事件类型(EVENT TYPE)
- 事件货币(EVENT CURRENCY)
- 自定义新闻事件:在此新闻配置文件中,用户/交易者可根据输入的事件ID按自己的喜好筛选新闻事件,每个输入项最多可输入14个事件ID。

交易设置输入项
- 选择交易入场方式:此选项旨在允许采用不同的交易入场方法。这些方法具体包括:
- 市价成交:采用此交易入场方法时,交易仅通过市价执行(买入或卖出交易)。主要要求是,任何被选中进行交易的新闻事件,必须事先具备事件影响力,以便提前知晓交易方向(做多或做空)。
- 止损订单:采用此交易入场方法时,在任何选定的新闻事件发生前,交易将仅通过买入止损和卖出止损订单执行。主要要求是,用户/交易者应设置价格偏差,以便EA有价格缓冲空间来放置买入止损和卖出止损订单。一旦买入止损或卖出止损订单被触发,相反的订单将被删除。例如,在非农就业数据(新闻事件)公布前放置买入止损和卖出止损订单,如果买入止损订单被触发,即达到某一特定价格时执行买入头寸,EA将主动删除剩余的卖出止损订单。此交易入场方法无需事先知晓事件影响力即可进行交易。
- 单一止损订单:采用此交易入场方法时,交易将仅通过买入止损或卖出止损订单执行。两个主要要求是:
- 在此交易入场选择中,任何被选中进行交易的新闻事件,必须事先具备事件影响力,以便提前知晓订单方向(买入止损或卖出止损)。
- 用户/交易者应设置价格偏差,以便EA有价格缓冲空间来放置买入止损或卖出止损订单。

新闻类
在名为News.mqh的头文件中,我们将在CNews类外部声明一个名为NewsSelection的枚举类型,该枚举的目的是允许用户/交易者在EA(智能交易系统)的输入参数中选择不同的新闻配置文件,同时我们还将声明一个名为myNewsSelection的变量,用于存储用户/交易者偏好的选择。此外,我们还将声明一个名为CustomEvent的结构体。该结构体将存储一个布尔值,用于决定是否根据结构体内部EventIds字符串数组中的事件ID进行筛选。此外,该结构体还声明了诸如CEvent1等变量,这些变量将作为用户/交易者筛选自定义事件ID的5个选项之一。
枚举类型:
- NewsSelection:定义两种配置文件
- News_Select_Custom_Events:用于自定义新闻事件。
- News_Select_Settings:用于新闻设置。
- myNewsSelection是此枚举类型的一个变量,用于存储当前选择的新闻配置文件。
结构:
- CustomEvent:一个用于存储自定义事件ID以及标识(useEvents)的结构体,该标识指示是否应将这些事件包含在查询中。
- 包含五个变量:CEvent1、CEvent2、CEvent3、CEvent4和CEvent5,每个变量代表一组独立的事件。
//--- Enumeration for News Profiles enum NewsSelection { News_Select_Custom_Events,//CUSTOM NEWS EVENTS News_Select_Settings//NEWS SETTINGS } myNewsSelection; //--- Structure to store event ids and whether to use these ids struct CustomEvent { bool useEvents; string EventIds[]; } CEvent1,CEvent2,CEvent3,CEvent4,CEvent5;
枚举类型CalendarComponents:
- CalendarComponents:枚举经济日历中的各种组成部分,例如用于构建与夏令时(DST)安排、事件信息以及货币数据相关的数据的表格和视图。
在CalendarComponents枚举中,我们新增了两个值:
- RecentEventInfo_View
- UpcomingEventInfo_View
//-- To keep track of what is in our database enum CalendarComponents { // ... RecentEventInfo_View,//View for Recent Dates For Events UpcomingEventInfo_View,//View for Upcoming Dates For Events // ... };
函数 GetCalendar(CalendarData &Data[]):
- 此函数从存储的日历数据库中检索所有相关日历数据,并将其存储在Data数组中。
- 它会打开一个数据库(NEWS_DATABASE_FILE),并根据当前选择的新闻类型(myNewsSelection)执行SQL查询。
- 根据选择的是“News_Select_Custom_Events”(自定义新闻事件)还是“News_Select_Settings”(新闻设置),将生成不同的SQL查询以获取事件信息。
- 自定义新闻事件:通过连接MQL5Calendar表和TimeSchedule表,使用基于自定义事件ID的筛选条件来检索自定义新闻事件。
- 新闻设置:根据用户指定的设置(如重要性、频率、行业、类型和货币)筛选并检索事件数据。
- 该函数处理SQL查询结果,并将检索到的数据存储在Data数组中。
- 如果数据库查询失败,将打印错误信息以及失败的SQL查询。
//--- Will Retrieve all relevant Calendar data for DB in Memory from DB in Storage void GetCalendar(CalendarData &Data[]) { // ... string SqlRequest; //--- switch statement for different News Profiles switch(myNewsSelection) { case News_Select_Custom_Events://CUSTOM NEWS EVENTS //--- Get filtered calendar DB data SqlRequest = StringFormat("Select MQ.EventId,MQ.Country,MQ.EventName,MQ.EventType,MQ.EventImportance,MQ.EventCurrency," "MQ.EventCode,MQ.EventSector,MQ.EventForecast,MQ.EventPreValue,MQ.EventImpact,MQ.EventFrequency," "TS.DST_UK,TS.DST_US,TS.DST_AU,TS.DST_NONE from %s MQ " "Inner Join %s TS on TS.ID=MQ.ID Where %s OR %s OR %s OR %s OR %s;", CalendarStruct(MQL5Calendar_Table).name,CalendarStruct(TimeSchedule_Table).name, Request_Events(CEvent1),Request_Events(CEvent2),Request_Events(CEvent3), Request_Events(CEvent4),Request_Events(CEvent5)); break; case News_Select_Settings://NEWS SETTINGS //--- Get filtered calendar DB data SqlRequest = StringFormat("Select MQ.EventId,MQ.Country,MQ.EventName,MQ.EventType,MQ.EventImportance,MQ.EventCurrency," "MQ.EventCode,MQ.EventSector,MQ.EventForecast,MQ.EventPreValue,MQ.EventImpact,MQ.EventFrequency," "TS.DST_UK,TS.DST_US,TS.DST_AU,TS.DST_NONE from %s MQ " "Inner Join %s TS on TS.ID=MQ.ID " "Where %s and %s and %s and %s and %s;", CalendarStruct(MQL5Calendar_Table).name,CalendarStruct(TimeSchedule_Table).name, Request_Importance(myImportance),Request_Frequency(myFrequency), Request_Sector(mySector),Request_Type(myType),Request_Currency(myCurrency)); break; default://Unknown break; } // ...
函数Request_Events(CustomEvent &CEvent):
- 该函数根据CustomEvent结构体中存储的自定义事件ID生成用于查询数据库的SQL WHERE子句。
- 它会检查useEvents是否为true。如果是,会将CEvent.EventIds[]数组中的每个事件ID添加到SQL查询中。
//--- Retrieve Sql request string for custom event ids string Request_Events(CustomEvent &CEvent) { //--- Default request string string EventReq="MQ.EventId='0'"; //--- Check if this Custom event should be included in the SQL request if(CEvent.useEvents) { //--- Get request for first event id EventReq=StringFormat("(MQ.EventId='%s'", (CEvent.EventIds.Size()>0)? CEvent.EventIds[0]:"0"); //--- Iterate through remaining event ids and add to the SQL request for(uint i=1;i<CEvent.EventIds.Size();i++) { EventReq+=StringFormat(" OR MQ.EventId='%s'",CEvent.EventIds[i]); } EventReq+=")"; } //--- Return SQL request for custom event ids return EventReq; }
公有类成员:
对以下函数进行更新:EconomicDetailsMemory、EconomicNextEvent和isEvent。
- EconomicDetailsMemory:从内存中的日历数据库中获取数据值。
- EconomicNextEvent:使用下一个事件的数据更新结构体变量。
- isEvent:检查是否有新闻事件即将发生,并相应地修改传入的参数。
//Public declarations accessable via a class's Object public: // ... void EconomicDetailsMemory(Calendar &NewsTime[],datetime date,bool ImpactRequired);//Gets values from the MQL5 DB Calendar in Memory void EconomicNextEvent();//Will update UpcomingNews structure variable with the next event data // ... //--- Checks if a news event is occurring and modifies the parameters passed by reference bool isEvent(uint SecondsPreEvent,string &Name,string &Importance,string &Code);
在函数EconomicDetailsMemory中,其目的是从内存数据库(DBMemory)中检索特定日期的经济日历事件数据,可选择性地考虑事件的影响力,并将数据存储在数组NewsTime[]中。
- 该函数从数据库中获取指定日期的经济事件详情,并将其存储在NewsTime[]数组中。
- 如果ImpactRequired为true,则会检索事件的先前值和预测值,并根据历史数据分配事件影响力。
- 如果ImpactRequired为false,则仅检索当天和下一个事件的数据。
- 结果通过预编译的SQL查询获取,并存储在动态调整大小的NewsTime[]数组中,数组大小根据检索到的事件数量动态调整。
//+------------------------------------------------------------------+ //|Gets values from the MQL5 DB Calendar in Memory | //+------------------------------------------------------------------+ void CNews::EconomicDetailsMemory(Calendar &NewsTime[],datetime date,bool ImpactRequired) { //--- SQL query to retrieve news data for a certain date string request_text; //--- Check if Event impact is required for retrieving news events if(ImpactRequired) { request_text=StringFormat("WITH DAILY_EVENTS AS(SELECT M.EVENTID as 'E_ID',M.COUNTRY,M.EVENTNAME as 'Name',M.EVENTTYPE as" " 'Type',M.EVENTIMPORTANCE as 'Importance',M.%s as 'Time',M.EVENTCURRENCY as 'Currency',M.EVENTCODE" " as 'Code',M.EVENTSECTOR as 'Sector',M.EVENTFORECAST as 'Forecast',M.EVENTPREVALUE as 'PREVALUE'," "M.EVENTFREQUENCY as 'Freq' FROM %s M WHERE DATE(REPLACE(Time,'.','-'))=DATE(REPLACE('%s','.','-'))" " AND (Forecast<>'None' AND Prevalue<>'None')),DAILY_IMPACT AS(SELECT DE.E_ID,DE.COUNTRY,DE.Name," "DE.Type,DE.Importance,DE.Time,DE.Currency,DE.Code,DE.Sector,DE.Forecast,DE.Prevalue,DE.Freq," "MC.EVENTIMPACT as 'IMPACT', RANK() OVER(PARTITION BY DE.E_ID,DE.Time ORDER BY MC.%s DESC)DateOrder" " FROM %s MC INNER JOIN DAILY_EVENTS DE on DE.E_ID=MC.EVENTID WHERE DATE(REPLACE(MC.%s,'.','-'))<" "DATE(REPLACE(DE.Time,'.','-')) AND DATE(REPLACE(MC.%s,'.','-'))>=DATE(REPLACE(DE.Time,'.','-')," "'-24 months') AND (MC.EVENTFORECAST<>'None' AND MC.EVENTPREVALUE<>'None' AND (CASE WHEN Forecast>" "Prevalue THEN 'more' WHEN Forecast<Prevalue THEN 'less' ELSE 'equal' END)=(CASE WHEN MC.EVENTFORECAST" ">MC.EVENTPREVALUE THEN 'more' WHEN MC.EVENTFORECAST<MC.EVENTPREVALUE THEN 'less' ELSE 'equal' END)) " "ORDER BY MC.%s),DAILY_EVENTS_RECORDS AS(SELECT * FROM DAILY_IMPACT WHERE DateOrder=1 ORDER BY Time" " ASC),NEXT_EVENT AS(SELECT M.EVENTID as 'E_ID',M.COUNTRY,M.EVENTNAME as 'Name',M.EVENTTYPE as 'Type'," "M.EVENTIMPORTANCE as 'Importance',M.%s as 'Time',M.EVENTCURRENCY as 'Currency',M.EVENTCODE as 'Code'," "M.EVENTSECTOR as 'Sector',M.EVENTFORECAST as 'Forecast',M.EVENTPREVALUE as 'PREVALUE',M.EVENTFREQUENCY" " as 'Freq' FROM %s M WHERE DATE(REPLACE(Time,'.','-'))>DATE(REPLACE('%s','.','-')) AND (Forecast<>" "'None' AND Prevalue<>'None' AND DATE(REPLACE(Time,'.','-'))<=DATE(REPLACE('%s','.','-'),'+60 days')))," "NEXT_IMPACT AS(SELECT NE.E_ID,NE.COUNTRY,NE.Name,NE.Type,NE.Importance,NE.Time,NE.Currency,NE.Code" ",NE.Sector,NE.Forecast,NE.Prevalue,NE.Freq,MC.EVENTIMPACT as 'IMPACT',RANK() OVER(PARTITION BY " "NE.E_ID,NE.Time ORDER BY MC.%s DESC)DateOrder FROM %s MC INNER JOIN NEXT_EVENT NE on NE.E_ID=MC.EVENTID " "WHERE DATE(REPLACE(MC.%s,'.','-'))<DATE(REPLACE(NE.Time,'.','-')) AND DATE(REPLACE(MC.%s,'.','-'))>=" "DATE(REPLACE(NE.Time,'.','-'),'-24 months') AND (MC.EVENTFORECAST<>'None' AND MC.EVENTPREVALUE<>'None'" " AND (CASE WHEN Forecast>Prevalue THEN 'more' WHEN Forecast<Prevalue THEN 'less' ELSE 'equal' END)=" "(CASE WHEN MC.EVENTFORECAST>MC.EVENTPREVALUE THEN 'more' WHEN MC.EVENTFORECAST<MC.EVENTPREVALUE THEN " "'less' ELSE 'equal' END)) ORDER BY MC.%s),NEXT_EVENT_RECORD AS(SELECT * FROM NEXT_IMPACT WHERE " "DateOrder=1 ORDER BY Time ASC LIMIT 1),ALL_EVENTS AS(SELECT * FROM NEXT_EVENT_RECORD UNION ALL " "SELECT * FROM DAILY_EVENTS_RECORDS)SELECT E_ID,Country,Name,Type,Importance,Time,Currency,Code," "Sector,Forecast,Prevalue,Impact,Freq FROM ALL_EVENTS GROUP BY Time ORDER BY Time Asc;", EnumToString(MySchedule),DBMemory.name,TimeToString(date),EnumToString(MySchedule),DBMemory.name, EnumToString(MySchedule),EnumToString(MySchedule),EnumToString(MySchedule),EnumToString(MySchedule) ,DBMemory.name,TimeToString(date),TimeToString(date),EnumToString(MySchedule),DBMemory.name, EnumToString(MySchedule),EnumToString(MySchedule),EnumToString(MySchedule)); } else { /* Within this request we select all the news events that will occur or have occurred in the current day and the next news event after the current day */ request_text=StringFormat("WITH DAILY_EVENTS AS(SELECT M.EVENTID as 'E_ID',M.COUNTRY,M.EVENTNAME as 'Name',M.EVENTTYPE as" " 'Type',M.EVENTIMPORTANCE as 'Importance',M.%s as 'Time',M.EVENTCURRENCY as 'Currency',M.EVENTCODE" " as 'Code',M.EVENTSECTOR as 'Sector',M.EVENTFORECAST as 'Forecast',M.EVENTPREVALUE as 'PREVALUE'" ",M.EVENTFREQUENCY as 'Freq',M.EVENTIMPACT as 'Impact' FROM %s M WHERE DATE(REPLACE(Time,'.','-'))" "=DATE(REPLACE('%s','.','-'))),DAILY_EVENTS_RECORDS AS(SELECT * FROM DAILY_EVENTS ORDER BY Time ASC)" ",NEXT_EVENT AS(SELECT M.EVENTID as 'E_ID',M.COUNTRY,M.EVENTNAME as 'Name',M.EVENTTYPE as 'Type'," "M.EVENTIMPORTANCE as 'Importance',M.%s as 'Time',M.EVENTCURRENCY as 'Currency',M.EVENTCODE as " "'Code',M.EVENTSECTOR as 'Sector',M.EVENTFORECAST as 'Forecast',M.EVENTPREVALUE as 'PREVALUE'," "M.EVENTFREQUENCY as 'Freq',M.EVENTIMPACT as 'Impact' FROM %s M WHERE DATE(REPLACE(Time,'.','-'))" ">DATE(REPLACE('%s','.','-')) AND (DATE(REPLACE(Time,'.','-'))<=DATE(REPLACE('%s','.','-')," "'+60 days'))),NEXT_EVENT_RECORD AS(SELECT * FROM NEXT_EVENT ORDER BY Time ASC LIMIT 1)," "ALL_EVENTS AS(SELECT * FROM NEXT_EVENT_RECORD UNION ALL SELECT * FROM " "DAILY_EVENTS_RECORDS)SELECT * FROM ALL_EVENTS GROUP BY Time ORDER BY Time Asc;", EnumToString(MySchedule),DBMemory.name,TimeToString(date),EnumToString(MySchedule),DBMemory.name, TimeToString(date),TimeToString(date)); } // ...
函数签名
- 参数:
- Calendar &NewsTime[]:一个Calendar结构体数组(每个结构体存储事件详情),用于填充检索到的数据。
- datetime date:检索经济事件的具体日期。
- bool ImpactRequired:一个标识,指示查询是否应考虑事件影响力。
构建基于影响力需求的SQL查询
根据ImpactRequired被设置为tue或false,SQL查询有所不同。这是因为当ImpactRequired为true时,新闻事件需要事件影响力来确定交易方向;当ImpactRequired为false时,事件影响力则不是必需的,因为交易方向不需要基于事件影响力。
A. 如果ImpactRequired为true
此查询更为复杂,涉及多个部分:
- 查询检索给定日期的经济事件,并考虑当前日期之后的下一个即将发生的事件。
- 会查找过去 24 个月内既有预测值又有先前值的事件,并将其与当前事件进行比较。
- 如果预测值大于先前值(或反之,甚至相等),则将其与具有相同趋势(预测值 > 先前值、预测值 < 先前值或预测值 = 先前值)的历史事件进行匹配。
- 将过去事件的影响力分配给当前事件。
以下是查询构建概述:
"WITH DAILY_EVENTS AS(...) , DAILY_IMPACT AS(...) , NEXT_EVENT AS(...), NEXT_IMPACT AS(...),
ALL_EVENTS AS(...) SELECT * FROM ALL_EVENTS GROUP BY Time ORDER BY Time Asc;"
- DAILY_EVENTS(每日事件):从日历中选取指定日期的所有经济事件。仅选择具有有效预测值和先前值的事件。
- DAILY_IMPACT(每日影响力分析):查找过去24个月内的历史事件,这些事件的预测值与先前值的变化趋势(大于/小于/等于)需与当前事件相似。按日期对事件进行排序,以确定最近的匹配事件,并将其影响力值分配给当前事件。
- NEXT_EVENT(下一事件):选取当前日期之后的下一个即将发生的经济事件。
- NEXT_IMPACT(下一事件影响力分析):与DAILY_IMPACT类似,检索与下一事件的预测值/先前值趋势相匹配的最近历史事件,并将其影响力值分配给下一事件。
- ALL_EVENTS(全部事件整合):将当日事件记录(DAILY_EVENTS_RECORDS)和下一事件记录(NEXT_EVENT_RECORD)进行合并,并按时间顺序进行排序。
B. 如果ImpactRequired为false
如果无需分析事件影响力,则查询更为简单:
- 检索当前日期的所有事件,以及当前日期之后60天内的下一事件。
SQL查询概述:
"WITH DAILY_EVENTS AS(...) , NEXT_EVENT AS(...), ALL_EVENTS AS(...) SELECT * FROM ALL_EVENTS GROUP BY Time ORDER BY Time Asc;"
- DAILY_EVENTS:检索当前日期的经济事件。
- NEXT_EVENT:检索当前日期之后60天内的下一事件。
- ALL_EVENTS:合并当日事件和下一事件,并按时间顺序排序。
SQL查询执行:
int request = DatabasePrepare(DBMemoryConnection, request_text);
- DatabasePrepare:准备SQL查询以供执行。将查询结果存储在request中,request为查询的句柄,其后续将用于获取数据。
- 错误处理:如果请求失败(request == INVALID_HANDLE),则打印错误信息及SQL查询语句,以便进行调试。
读取结果:
Calendar ReadDB_Data; ArrayRemove(NewsTime, 0, WHOLE_ARRAY); for (int i = 0; DatabaseReadBind(request, ReadDB_Data); i++) { ArrayResize(NewsTime, i + 1, i + 2); NewsTime[i] = ReadDB_Data; }
- ArrayRemove:清空NewsTime[]数组,为存储新数据做准备。
- DatabaseReadBind:从已准备好的SQL查询中获取结果,并将每一行数据绑定到ReadDB_Data变量(该变量是一个Calendar结构体)。
- ArrayResize:调整NewsTime[]数组的大小,以容纳新数据。每次DatabaseReadBind返回一行数据时,数组会扩展1个元素。
- NewsTime[i] = ReadDB_Data:将检索到的数据复制到NewsTime[]数组中。
查询收尾
DatabaseFinalize(request);
- DatabaseFinalize:清理DatabasePrepare分配的资源,释放查询句柄。
EconomicNextEvent函数负责查找下一个即将发生的经济新闻事件,并更新UpcomingNews变量以存储该事件的详细信息。
- 遍历CalendarArray中的所有事件,并根据服务器时间(TimeTradeServer())确定下一个即将发生的事件。
- 更新UpcomingNews结构体,存储该事件的详细信息。
- 该逻辑确保仅考虑相对于服务器时间的未来事件,并选择即将发生的最早事件。
//+------------------------------------------------------------------+ //|Will update UpcomingNews structure variable with the next news | //|event data | //+------------------------------------------------------------------+ void CNews::EconomicNextEvent() { //--- Declare unassigned Calendar structure variable Next Calendar Next; //--- assign empty values to Calendar structure variable UpcomingNews UpcomingNews = Next; //--- assign default date datetime NextEvent=0; //--- Iterate through CalendarArray to retrieve news events for(uint i=0;i<CalendarArray.Size();i++) { //--- Check for next earliest news event from CalendarArray if((NextEvent==0)||(TimeTradeServer()<datetime(CalendarArray[i].EventDate) &&NextEvent>datetime(CalendarArray[i].EventDate))||(NextEvent<TimeTradeServer())) { //--- assign values from the CalendarArray NextEvent = datetime(CalendarArray[i].EventDate); Next = CalendarArray[i]; } } //--- assign the next news event data into UpcomingNews variable UpcomingNews = Next; }
声明未赋值的Calendar结构
Calendar结构的Next变量:
- 变量Next被声明为Calendar结构(自定义结构,存放事件日期、名称、国家等详情)。
- 初始状态为空,稍后用于存储下一条经济事件的数据。
为UpcomingNews赋空值
UpcomingNews = Next;
- UpcomingNews是全局/类级变量(同为Calendar结构),用于保存即将发生的事件详情。
- 启动时先重置Next变量为默认(空)值。
设定默认日期
datetime NextEvent = 0;
- 初始化NextEvent变量为 0,表示尚未分配任何事件。
- 循环期间,NextEvent将存放下条经济事件的时间戳。
遍历CalendarArray
for (uint i = 0; i < CalendarArray.Size(); i++)
- CalendarArray数组用于存储经济事件的详细信息。
- for循环会遍历数组中的每个元素(每个元素代表一个经济事件),并检查该事件是否符合“下一个即将发生的事件”的条件。
判断下条事件的条件
if ((NextEvent == 0) || (TimeTradeServer() < datetime(CalendarArray[i].EventDate) && NextEvent > datetime(CalendarArray[i].EventDate)) || (NextEvent < TimeTradeServer()))
该 if 语句通过检查多个条件,判断数组CalendarArray[i]中的当前事件是否为下一个新闻事件:
- 第一个条件:(NextEvent == 0)
- 如果NextEvent仍为 0(尚未分配任何事件),则将当前事件直接选为下一个事件。
- 第二个条件:(TimeTradeServer() < datetime(CalendarArray[i].EventDate) && NextEvent > datetime(CalendarArray[i].EventDate))
- 检查当前事件是否满足:发生在服务器时间之后(TimeTradeServer() < EventDate),并且早于当前已存储的NextEvent时间。如果为true,那么当前事件成为下一个事件。
- 第三个条件:(NextEvent < TimeTradeServer())
- 如果存储过的NextEvent已过期(早于服务器时间),则函数将继续搜索下一个有效未来事件。
从CalendarArray中赋值
NextEvent = datetime(CalendarArray[i].EventDate);
Next = CalendarArray[i];
- 当任一条件满足时,认定CalendarArray[i]中的当前事件为下一个事件:
- 更新NextEvent为当前事件日期。
- Next保存当前事件的全部详情(日期、名称、类型等)。
将下一个事件赋给UpcomingNews
UpcomingNews = Next;
- 循环遍历完CalendarArray后,变量UpcomingNews将被更新为下一个即将发生事件的详细信息(存储在Next中)。
- 该函数确保将相对于当前服务器时间的第一个未来事件存储在UpcomingNews中。
函数isEvent用于检查是否有新闻事件即将发生或正在发生(在特定时间范围内)。其目的是基于时间偏移量,判断CalendarArray中是否存在符合条件的事件。如果找到此类事件,函数将返回该事件的详细信息(如名称、重要性和代码)。
- 该函数会遍历CalendarArray(存储经济事件数据的数组),并检查是否有事件发生在SecondsPreEvent定义的时间范围内(包括即将发生或正在发生)。
- 如果找到符合条件的事件,函数将更新Name、Importance和Code为该事件的详细信息,并返回true。
- 如果在定义的时间范围内未找到任何事件,函数将返回false,且Name、Importance和Code保持不变(或设置为默认值/NULL)。
//+------------------------------------------------------------------+ //|Checks if News is event is about to occur or is occurring | //+------------------------------------------------------------------+ bool CNews::isEvent(uint SecondsPreEvent,string &Name,string &Importance,string &Code) { //--- assign default value Name=NULL; //--- Iterate through CalendarArray for(uint i=0;i<CalendarArray.Size();i++) { //--- Check if news event is within a timespan if(CTime.TimeIsInRange(CTime.TimeMinusOffset(datetime(CalendarArray[i].EventDate),SecondsPreEvent), CTime.TimePlusOffset(datetime(CalendarArray[i].EventDate),59))) { //--- assign appropriate CalendarArray values Name=CalendarArray[i].EventName; Importance=CalendarArray[i].EventImportance; Code=CalendarArray[i].EventCode; //--- news event is currently within the timespan return true; } } //--- no news event is within the current timespan. return false; }
参数:
- uint SecondsPreEvent:定义事件被视为“即将发生”的提前时间阈值(单位:秒)。
- string &Name:字符串引用,用于存储符合时间范围内的事件名称。(如果能找到)
- string &Importance:字符串引用,用于存储符合条件事件的重要性等级(如果能找到)。
- string &Code:字符串引用,用于存储事件代码(如果能找到)。
为Name分配默认值
Name = NULL;
- 将变量Name初始化为NULL。如果在指定的时间范围内未找到任何事件,Name将保持为NULL。
- 这样确保仅当在时间范围内找到事件时,才会更新Name。
遍历CalendarArray
for (uint i = 0; i < CalendarArray.Size(); i++)
- 循环遍历CalendarArray中的每个元素,其中每个元素代表一个新闻事件。
- 循环将检查每个事件是否落在当前时间附近的指定时间范围内。
检查事件是否在时间范围内
if (CTime.TimeIsInRange(CTime.TimeMinusOffset(datetime(CalendarArray[i].EventDate), SecondsPreEvent), CTime.TimePlusOffset(datetime(CalendarArray[i].EventDate), 59)))
- CTime.TimeMinusOffset(datetime(CalendarArray[i].EventDate), SecondsPreEvent):
- 该函数调用检查事件日期(CalendarArray[i].EventDate)减去SecondsPreEvent后的时间是否已过去。
- 本质上讲,其定义;额时间窗口的下界(事件前多少秒开始生效)。
- CTime.TimePlusOffset(datetime(CalendarArray[i].EventDate), 59):
- 该函数检查事件日期加59秒后的时间是否在未来,从而定义时间窗口的上界(事件被视为有效的持续时间)。
- 函数CTime.TimeIsInRange随后判断当前时间是否落在此范围内。如果在范围内,则表示事件即将发生或正在进行中。
从CalendarArray中赋值
Name = CalendarArray[i].EventName; Importance = CalendarArray[i].EventImportance; Code = CalendarArray[i].EventCode;
- 如果事件在指定时间范围内,则从CalendarArray提取相关信息(事件名称、重要性、代码)并赋给引用参数:
- Name:新闻事件名称。
- Importance:事件重要性等级。
- Code:事件代码。
如果找到事件返回true
return true;
- 如果在时间范围内找到符合条件的事件,函数返回true,表明相关事件正在发生或即将发生。
如果未找到事件返回false
return false;
- 如果循环遍历完CalendarArray中的所有事件后,仍未在指定时间范围内找到符合条件的事件,则函数返回false,表示当前没有正在发生或即将发生的相关事件。
Constructor CNews::CNews(void):
- 初始化类时设置经济日历各组件对应的SQL DROP、CREATE 与INSERT语句。
- 表:定义如AutoDST、Record、TimeSchedule、MQL5Calendar等。
- 视图:定义按不同DST时区(Calendar_AU、Calendar_UK等)、事件信息、货币、近期/即将发生事件日期等。
- 触发器:定义如OnlyOne_AutoDST、OnlyOne_Record等,确保特定表始终仅保留一条记录。
每个组件(表、视图或触发器)均用相应SQL命令初始化,涵盖创建、插入与删除操作。
创建SQL视图:
- 针对每个夏令时(DST)时序安排及日历组件,均定义了特定的SQL视图,以便按照不同标准(如事件重要性、货币类型或事件日期等)对数据进行结构化。例如:
- 即将发生的事件视图:展示即将发生事件的日期、星期几及事件详情。
- 近期的事件视图:与即将发生事件视图类似,但检索的是近期已发生事件的日期。
SQL触发器:
- 触发器用于确保AutoDST表和Record表中任何时刻仅存在一条记录,具体做法是在执行插入操作前删除现有记录。
//+------------------------------------------------------------------+ //|Constructor | //+------------------------------------------------------------------+ CNews::CNews(void):DropRequest("PRAGMA foreign_keys = OFF; " "PRAGMA secure_delete = ON; " "Drop %s IF EXISTS '%s'; " "Vacuum; " "PRAGMA foreign_keys = ON;")//Sql drop statement { // ... string views[] = {"AU","NONE","UK","US"}; //-- Sql statement for creating the table views for each DST schedule string view_sql = "CREATE VIEW IF NOT EXISTS Calendar_%s " "AS " "SELECT C.Eventid as 'ID',C.Eventname as 'Name',C.Country as 'Country', " "(CASE WHEN Date(REPLACE(T.DST_%s,'.','-'))<R.Date THEN CONCAT(T.DST_%s,' | Yesterday') " "WHEN Date(REPLACE(T.DST_%s,'.','-'))=R.Date THEN CONCAT(T.DST_%s,' | Today') " "WHEN Date(REPLACE(T.DST_%s,'.','-'))>R.Date THEN CONCAT(T.DST_%s,' | Tomorrow') END) as " "'Date',C.EventCurrency as 'Currency',Replace(C.EventImportance,'CALENDAR_IMPORTANCE_','')" " as 'Importance' from MQL5Calendar C,Record R Inner join TimeSchedule T on C.ID=T.ID Where" " DATE(REPLACE(T.DST_%s,'.','-'))>=DATE(R.Date,'-1 day') AND DATE(REPLACE(T.DST_%s,'.','-'))" "<=DATE(R.Date,'+1 day') Order by T.DST_%s Asc;"; // ... //--- initializing properties for the EventInfo view CalendarContents[5].Content = EventInfo_View; CalendarContents[5].name = "Event Info"; CalendarContents[5].sql = "CREATE VIEW IF NOT EXISTS 'Event Info' " "AS SELECT DISTINCT MC.EVENTID as 'ID',MC.COUNTRY as 'Country',MC.EVENTNAME as 'Name'," "REPLACE(MC.EVENTTYPE,'CALENDAR_TYPE_','') as 'Type',REPLACE(MC.EVENTSECTOR,'CALENDAR_SECTOR_','') as 'Sector'," "REPLACE(MC.EVENTIMPORTANCE,'CALENDAR_IMPORTANCE_','') as 'Importance',MC.EVENTCURRENCY as 'Currency'," "REPLACE(MC.EVENTFREQUENCY,'CALENDAR_FREQUENCY_','') as 'Frequency',MC.EVENTCODE as 'Code' " "FROM MQL5Calendar MC ORDER BY \"Country\" Asc," "CASE \"Importance\" WHEN 'HIGH' THEN 1 WHEN 'MODERATE' THEN 2 WHEN 'LOW' THEN 3 ELSE 4 END,\"Sector\" Desc;"; CalendarContents[5].tbl_name = "Event Info"; CalendarContents[5].type = "view"; // ... //--- initializing properties for the UpcomingEventInfo view CalendarContents[7].Content = UpcomingEventInfo_View; CalendarContents[7].name = "Upcoming Event Dates"; CalendarContents[7].sql = "CREATE VIEW IF NOT EXISTS 'Upcoming Event Dates' AS WITH UNIQUE_EVENTS AS(SELECT DISTINCT M.EVENTID as 'E_ID'," "M.COUNTRY as 'Country',M.EVENTNAME as 'Name',M.EVENTCURRENCY as 'Currency' FROM 'MQL5Calendar' M)," "INFO_DATE AS(SELECT E_ID,Country,Name,Currency,(SELECT T.DST_NONE as 'Time' FROM MQL5Calendar M," "Record R INNER JOIN TIMESCHEDULE T ON T.ID=M.ID WHERE DATE(REPLACE(Time,'.','-'))>R.Date AND " "E_ID=M.EVENTID ORDER BY Time ASC LIMIT 1) as 'Next Event Date' FROM UNIQUE_EVENTS) SELECT E_ID " "as 'ID',Country,Name,Currency,(CASE WHEN \"Next Event Date\" IS NULL THEN 'Unknown' ELSE " "\"Next Event Date\" END) as 'Upcoming Date',(CASE WHEN \"Next Event Date\"<>'Unknown' THEN " "(case cast (strftime('%w', DATE(REPLACE(\"Next Event Date\",'.','-'))) as integer) WHEN 0 THEN" " 'Sunday' WHEN 1 THEN 'Monday' WHEN 2 THEN 'Tuesday' WHEN 3 THEN 'Wednesday' WHEN 4 THEN 'Thursday'" " WHEN 5 THEN 'Friday' ELSE 'Saturday' END) ELSE 'Unknown' END) as 'Day' FROM INFO_DATE Order BY " "\"Upcoming Date\" ASC;"; CalendarContents[7].tbl_name = "Upcoming Event Dates"; CalendarContents[7].type = "view"; //--- initializing properties for the RecentEventInfo view CalendarContents[8].Content = RecentEventInfo_View; CalendarContents[8].name = "Recent Event Dates"; CalendarContents[8].sql = "CREATE VIEW IF NOT EXISTS 'Recent Event Dates' AS WITH UNIQUE_EVENTS AS(SELECT DISTINCT M.EVENTID" " as 'E_ID',M.COUNTRY as 'Country',M.EVENTNAME as 'Name',M.EVENTCURRENCY as 'Currency'" "FROM 'MQL5Calendar' M),INFO_DATE AS(SELECT E_ID,Country,Name,Currency," "(SELECT T.DST_NONE as 'Time' FROM MQL5Calendar M,Record R INNER JOIN TIMESCHEDULE T ON" " T.ID=M.ID WHERE DATE(REPLACE(Time,'.','-'))<=R.Date AND E_ID=M.EVENTID ORDER BY Time DESC" " LIMIT 1) as 'Last Event Date' FROM UNIQUE_EVENTS) SELECT E_ID as 'ID',Country,Name,Currency" ",\"Last Event Date\" as 'Recent Date',(case cast (strftime('%w', DATE(REPLACE(\"Last Event Date\"" ",'.','-'))) as integer) WHEN 0 THEN 'Sunday' WHEN 1 THEN 'Monday' WHEN 2 THEN 'Tuesday' WHEN 3 THEN" " 'Wednesday' WHEN 4 THEN 'Thursday' WHEN 5 THEN 'Friday' ELSE 'Saturday' END) as 'Day' FROM INFO_DATE" " Order BY \"Recent Date\" DESC;"; CalendarContents[8].tbl_name = "Recent Event Dates"; CalendarContents[8].type = "view"; // ...
以下查询负责创建四个相似但有所区别的视图:Calendar_AU、Calendar_NONE、Calendar_UK和Calendar_US。每个视图均从三个表(MQL5Calendar、Record和TimeSchedule)中提取数据。这些查询创建的视图用于显示不同时区(澳大利亚、无/默认、英国和美国)下的事件详情(ID、名称、国家/地区、日期、货币和重要性)。每个事件的日期根据其相对于当前日期的发生时间(昨天、今天或明天)进行标注,并筛选结果以仅显示当前日期前后一天内发生的事件。视图按对应时区的事件日期进行排序。
我们将以Calendar_AU视图为例进行说明。
完整的可视化查询:
CREATE VIEW IF NOT EXISTS Calendar_AU AS SELECT C.Eventid as 'ID',C.Eventname as 'Name',C.Country as 'Country', (CASE WHEN Date(REPLACE(T.DST_AU,'.','-'))<R.Date THEN CONCAT(T.DST_AU,' | Yesterday') WHEN Date(REPLACE(T.DST_AU,'.','-'))=R.Date THEN CONCAT(T.DST_AU,' | Today') WHEN Date(REPLACE(T.DST_AU,'.','-'))>R.Date THEN CONCAT(T.DST_AU,' | Tomorrow') END) as 'Date', C.EventCurrency as 'Currency',Replace(C.EventImportance,'CALENDAR_IMPORTANCE_','') as 'Importance' from MQL5Calendar C, Record R Inner join TimeSchedule T on C.ID=T.ID Where DATE(REPLACE(T.DST_AU,'.','-'))>=DATE(R.Date,'-1 day') AND DATE(REPLACE(T.DST_AU,'.','-'))<=DATE(R.Date,'+1 day') Order by T.DST_AU Asc;
CREATE VIEW IF NOT EXISTS Calendar_AU:如果视图Calendar_AU不存在,则创建该视图。视图本质上是一个基于查询创建的虚拟表,允许您在不重复存储数据的情况下检索数据。
SELECT子句:
SELECT C.Eventid as 'ID', C.Eventname as 'Name', C.Country as 'Country', (CASE WHEN Date(REPLACE(T.DST_AU,'.','-')) < R.Date THEN CONCAT(T.DST_AU, ' | Yesterday') WHEN Date(REPLACE(T.DST_AU,'.','-')) = R.Date THEN CONCAT(T.DST_AU, ' | Today') WHEN Date(REPLACE(T.DST_AU,'.','-')) > R.Date THEN CONCAT(T.DST_AU, ' | Tomorrow') END) as 'Date', C.EventCurrency as 'Currency', Replace(C.EventImportance,'CALENDAR_IMPORTANCE_','') as 'Importance'
该查询部分从MQL5Calendar、Record 和 TimeSchedule表中选取特定字段,并相应地格式化数据:
- C.Eventid是事件ID。
- C.Eventname是事件名称。
- C.Country是事件关联的国家/地区。
- 使用CASE语句将TimeSchedule表中存储的澳大利亚时区日期DST_AU与Record表中的当前日期R.Date进行比较,将事件标记为“昨天”、“今天”或“明天”。
- C.EventCurrency是与事件相关的货币。
- Replace(C.EventImportance,'CALENDAR_IMPORTANCE_','') removes the prefix 'CALENDAR_IMPORTANCE_' from the EventImportance field, extracting only the relevant importance level (e.g., "HIGH" or "LOW").
FROM子句:
FROM MQL5Calendar C, Record R Inner join TimeSchedule T on C.ID=T.ID
- 该查询从三个表中提取数据:MQL5Calendar( C)、Record( R)和TimeSchedule(T)。
- MQL5Calendar和Record表直接包含在FROM子句中,而TimeSchedule表通过INNER JOIN基于条件C.ID = T.ID进行关联,这意味着MQL5Calendar表中的ID必须与TimeSchedule表中的ID匹配。
WHERE子句:
WHERE DATE(REPLACE(T.DST_AU,'.','-')) >= DATE(R.Date,'-1 day') AND DATE(REPLACE(T.DST_AU,'.','-')) <= DATE(R.Date,'+1 day')
- 该条件将结果筛选为仅包含澳大利亚时区事件日期(DST_AU)在当前日期(R.Date)前后一天范围内的事件。
ORDER BY子句:
ORDER BY T.DST_AU Asc;
- 该语句按澳大利亚时区日期(DST_AU)升序对事件进行排序。
关键概念:
- 日期和时间格式化:REPLACE() 函数用于标准化日期。将 DST_ 列中以字符串形式存储的日期(如 2024.09.23)中的点号(.)替换为连字符(-),转换为标准格式(如 2024-09-23)。
- 条件逻辑:CASE语句检查事件日期(DST_AU、DST_NONE、DST_UK或DST_US)是否早于、等于或晚于当前日期(R.Date),并相应添加标签(“昨天”、“今天”或“明天”)。
- 筛选:WHERE子句确保视图中仅包含当前日期前后一天范围内的事件。
- 重要性提取:REPLACE() 函数移除EventImportance列的前缀,仅显示相关重要性级别(如“高”、“中”或“低”)。
Calendar_AU视图输出数据:
ID Name Country Date Currency Importance 392080012 Autumnal Equinox Day Japan 2024.09.22 02:00 | Yesterday JPY NONE 554010007 Exports New Zealand 2024.09.23 00:45 | Today NZD LOW 554010008 Imports New Zealand 2024.09.23 00:45 | Today NZD LOW // ... 710010010 Heritage Day South Africa 2024.09.24 02:00 | Tomorrow ZAR NONE 36030005 RBA Rate Statement Australia 2024.09.24 07:30 | Tomorrow AUD MODERATE // ...
以下查询创建了一个名为“Event Info”的视图,该视图从MQL5Calendar表中选取并整理事件信息。此视图从MQL5Calendar表中提取唯一的事件信息,并对数据进行标准化处理,以便更容易阅读和分析。它通过移除不必要的字段名前缀(如CALENDAR_TYPE_、CALENDAR_SECTOR_ 等)来清理字段名称。
CREATE VIEW IF NOT EXISTS 'Event Info' AS SELECT DISTINCT MC.EVENTID as 'ID',MC.COUNTRY as 'Country',MC.EVENTNAME as 'Name', REPLACE(MC.EVENTTYPE,'CALENDAR_TYPE_','') as 'Type',REPLACE(MC.EVENTSECTOR,'CALENDAR_SECTOR_','') as 'Sector', REPLACE(MC.EVENTIMPORTANCE,'CALENDAR_IMPORTANCE_','') as 'Importance',MC.EVENTCURRENCY as 'Currency', REPLACE(MC.EVENTFREQUENCY,'CALENDAR_FREQUENCY_','') as 'Frequency',MC.EVENTCODE as 'Code' FROM MQL5Calendar MC ORDER BY "Country" Asc,CASE "Importance" WHEN 'HIGH' THEN 1 WHEN 'MODERATE' THEN 2 WHEN 'LOW' THEN 3 ELSE 4 END,"Sector" Desc;
CREATE VIEW IF NOT EXISTS 'Event Info'
此部分在视图“Event Info”不存在时创建该视图。SQL中的视图是基于SELECT查询结果生成的虚拟表,允许您将复杂查询封装起来,并像操作普通表一样引用它。
SELECT DISTINCT子句:
SELECT DISTINCT MC.EVENTID as 'ID', MC.COUNTRY as 'Country', MC.EVENTNAME as 'Name', REPLACE(MC.EVENTTYPE,'CALENDAR_TYPE_','') as 'Type', REPLACE(MC.EVENTSECTOR,'CALENDAR_SECTOR_','') as 'Sector', REPLACE(MC.EVENTIMPORTANCE,'CALENDAR_IMPORTANCE_','') as 'Importance', MC.EVENTCURRENCY as 'Currency', REPLACE(MC.EVENTFREQUENCY,'CALENDAR_FREQUENCY_','') as 'Frequency', MC.EVENTCODE as 'Code'
此部分从MQL5Calendar表(别名MC)中检索唯一行(去除重复项),并选择特定列以构成视图。
所选字段:
- MC.EVENTID as 'ID':检索每个事件的唯一标识符,并将其重命名为“ID”。
- MC.COUNTRY as 'Country':检索与事件关联的国家/地区。
- MC.EVENTNAME as 'Name':检索事件名称。
使用REPLACE()函数进行数据转换:
多个字段使用 REPLACE() 函数移除 CALENDAR_TYPE_、CALENDAR_SECTOR_、CALENDAR_IMPORTANCE_ 和 CALENDAR_FREQUENCY_ 等前缀,仅保留字段中有意义的部分:
- REPLACE(MC.EVENTTYPE,'CALENDAR_TYPE_','') as 'Type':从EVENTTYPE字段中移除CALENDAR_TYPE_前缀,得到更简洁的值(例如,将 CALENDAR_TYPE_CONSUMER转换为CONSUMER)。
- REPLACE(MC.EVENTSECTOR,'CALENDAR_SECTOR_','') as 'Sector':从EVENTSECTOR字段中移除CALENDAR_SECTOR_前缀,得到行业部门名称。
- REPLACE(MC.EVENTIMPORTANCE,'CALENDAR_IMPORTANCE_','') as 'Importance':从EVENTIMPORTANCE字段中移除CALENDAR_IMPORTANCE_前缀,显示事件重要性级别(例如,HIGH、MODERATE、LOW)。
- MC.EVENTCURRENCY as 'Currency':检索事件涉及的货币,无需转换。
- REPLACE(MC.EVENTFREQUENCY,'CALENDAR_FREQUENCY_','') as 'Frequency':从EVENTFREQUENCY字段中移除CALENDAR_FREQUENCY_前缀,提供事件频率(如MONTHLY或QUARTERLY)。
- MC.EVENTCODE as 'Code':检索事件代码,无需任何转换。
FROM子句:
FROM MQL5Calendar MC
该查询从MQL5Calendar表中提取数据,并使用别名MC。
ORDER BY子句:
ORDER BY "Country" Asc, CASE "Importance" WHEN 'HIGH' THEN 1 WHEN 'MODERATE' THEN 2 WHEN 'LOW' THEN 3 ELSE 4 END, "Sector" Desc
此部分通过多字段对视图输出数据进行排序。
按国家/地区升序排序:
- 首个排序条件是按国家/地区(Country)字段升序(Asc)排列,即事件按国家/地区名称的字母顺序分组并排序。
按重要性自定义排序:
- 通过CASE语句根据自定义优先级按重要性(Importance)级别进行排序:
- 'HIGH' 重要性赋值为1(最高优先级)。
- 'MODERATE' 重要性赋值为2。
- 'LOW' 重要性赋值为3。
- 其余值(如 'NONE')赋值为 4(最低优先级)。
这意味着重要性为 HIGH 的事件将优先显示,其次是MODERATE、LOW,最后是NONE级别的事件。
按行业部门降序排序:
- 最后,事件按行业部门(Sector)字段降序(Desc)排列。例如,行业部门如MONEY、CONSUMER等将按逆字母顺序显示。
“Event Info”视图输出数据示例:
ID Country Name Type Sector Importance Currency Frequency Code 36030008 Australia RBA Interest Rate Decision INDICATOR MONEY HIGH AUD NONE AU 36030006 Australia RBA Governor Lowe Speech EVENT MONEY HIGH AUD NONE AU 36010003 Australia Employment Change INDICATOR JOBS HIGH AUD MONTH AU // ... 36010036 Australia Current Account INDICATOR TRADE MODERATE AUD QUARTER AU 36010011 Australia Trade Balance INDICATOR TRADE MODERATE AUD MONTH AU 36010029 Australia PPI q/q INDICATOR PRICES MODERATE AUD QUARTER AU // ... 36010009 Australia Exports m/m INDICATOR TRADE LOW AUD MONTH AU 36010010 Australia Imports m/m INDICATOR TRADE LOW AUD MONTH AU 36010037 Australia Net Exports Contribution INDICATOR TRADE LOW AUD QUARTER AU // ... 76020002 Brazil BCB Interest Rate Decision INDICATOR MONEY HIGH BRL NONE BR // ...
以下查询创建了一个名为“Recent Event Dates”(近期事件日期)的视图,该视图从MQL5Calendar表中提取近期事件的摘要信息,并显示事件发生的星期几。此视图提供了MQL5Calendar表中近期事件的列表,同时标注了每个事件发生的星期信息。该视图聚焦于日历中每个不同事件的最近一次发生记录。
CREATE VIEW IF NOT EXISTS 'Recent Event Dates' AS WITH UNIQUE_EVENTS AS(SELECT DISTINCT M.EVENTID as 'E_ID',M.COUNTRY as 'Country', M.EVENTNAME as 'Name',M.EVENTCURRENCY as 'Currency'FROM 'MQL5Calendar' M),INFO_DATE AS(SELECT E_ID,Country,Name,Currency, (SELECT T.DST_NONE as 'Time' FROM MQL5Calendar M,Record R INNER JOIN TIMESCHEDULE T ON T.ID=M.ID WHERE DATE(REPLACE(Time,'.','-')) <=R.Date AND E_ID=M.EVENTID ORDER BY Time DESC LIMIT 1) as 'Last Event Date' FROM UNIQUE_EVENTS) SELECT E_ID as 'ID',Country,Name, Currency,"Last Event Date" as 'Recent Date',(case cast (strftime('%w', DATE(REPLACE("Last Event Date",'.','-'))) as integer) WHEN 0 THEN 'Sunday' WHEN 1 THEN 'Monday' WHEN 2 THEN 'Tuesday' WHEN 3 THEN 'Wednesday' WHEN 4 THEN 'Thursday' WHEN 5 THEN 'Friday' ELSE 'Saturday' END) as 'Day' FROM INFO_DATE Order BY "Recent Date" DESC;
CREATE VIEW IF NOT EXISTS 'Recent Event Dates'
此部分在视图“Recent Event Dates”不存在时创建该视图。
WITH 子句:公用表表达式(CTEs)
此部分定义了两个公用表表达式(CTEs),通过将查询逻辑拆分为中间步骤来简化整体查询。
CTE 1: UNIQUE_EVENTS
WITH UNIQUE_EVENTS AS ( SELECT DISTINCT M.EVENTID as 'E_ID', M.COUNTRY as 'Country', M.EVENTNAME as 'Name', M.EVENTCURRENCY as 'Currency' FROM 'MQL5Calendar' M )
- 此公用表表达式(UNIQUE_EVENTS)从MQL5Calendar表中提取唯一的事件记录。
- 它选取事件的 ID(EVENTID)、国家/地区、名称和货币,确保每个事件仅列出一次(DISTINCT可去除重复条目)。
UNIQUE_EVENTS中选取的列:
- M.EVENTID as 'E_ID':事件的唯一标识符。
- M.COUNTRY as 'Country':与事件关联的国家/地区。
- M.EVENTNAME as 'Name':事件的名称。
- M.EVENTCURRENCY as 'Currency':与事件关联的货币。
CTE 2: INFO_DATE
INFO_DATE AS ( SELECT E_ID, Country, Name, Currency, ( SELECT T.DST_NONE as 'Time' FROM MQL5Calendar M, Record R INNER JOIN TimeSchedule T ON T.ID = M.ID WHERE DATE(REPLACE(Time, '.', '-')) <= R.Date AND E_ID = M.EVENTID ORDER BY Time DESC LIMIT 1 ) as 'Last Event Date' FROM UNIQUE_EVENTS )
此公用表表达式(INFO_DATE)为前一个CTE(UNIQUE_EVENTS)中的唯一事件添加了一个日期字段(最近事件日期)。具体实现方式如下:
- 针对每个唯一事件(E_ID、国家/地区、名称、货币),从TimeSchedule和MQL5Calendar表中选取该事件的最新日期。
INFO_DATE中的字段:子查询说明:
- 子查询从TimeSchedule表(与MQL5Calendar和Record表关联)中提取DST_NONE字段,该字段代表时间戳或事件时间。
- 条件DATE(REPLACE(Time, '.', '-')) <= R.Date确保将Time字段中的点号(.)替换为连字符(-)以形成有效的日期格式。该日期小于或等于Record表中的当前日期(R.Date)。
- 事件按时间降序排序(ORDER BY Time DESC),并通过LIMIT 1确保仅获取最近的事件时间。
- E_ID:来自UNIQUE_EVENTS CTE的事件ID。
- Country:来自UNIQUE_EVENTS CTE的国家/地区。
- Name:来自UNIQUE_EVENTS CTE的事件名称。
- Currency:来自UNIQUE_EVENTS CTE的货币。
- Last Event Date:从MQL5Calendar和TimeSchedule表中获取的每个事件的最近日期。
主查询:最终选择与数据转换
SELECT E_ID as 'ID', Country, Name, Currency, "Last Event Date" as 'Recent Date', ( CASE CAST (strftime('%w', DATE(REPLACE("Last Event Date", '.', '-'))) AS INTEGER) WHEN 0 THEN 'Sunday' WHEN 1 THEN 'Monday' WHEN 2 THEN 'Tuesday' WHEN 3 THEN 'Wednesday' WHEN 4 THEN 'Thursday' WHEN 5 THEN 'Friday' ELSE 'Saturday' END ) as 'Day' FROM INFO_DATE ORDER BY "Recent Date" DESC;
查询的主部分从INFO_DATE中选取最终结果,包含以下字段:
- E_ID as 'ID':事件ID,重命名为"ID"。
- Country:与事件关联的国家/地区。
- Name:事件名称。
- Currency:与事件相关的货币。
- "Last Event Date" as 'Recent Date':最近的事件日期,重命名为"Recent Date"。
星期几计算:
- 查询使用strftime('%w', DATE(REPLACE("Last Event Date", '.', '-'))),将"Last Event Date"转换为有效日期格式并提取星期几。
- %w返回表示星期几的整数(0=星期日,1=星期一,……,6=星期六)。
- CASE语句将该整数映射为对应的星期名称(例如,0→星期日,1→星期一,依此类推)。
结果排序:
ORDER BY "Recent Date" DESC
结果按"Recent Date"降序排列,即最新事件显示在列表顶部。
"Recent Event Dates"视图输出数据示例:
ID Country Name Currency Recent Date Day 554520001 New Zealand CFTC NZD Non-Commercial Net Positions NZD 2024.09.27 21:30 Friday 999520001 European Union CFTC EUR Non-Commercial Net Positions EUR 2024.09.27 21:30 Friday 392520001 Japan CFTC JPY Non-Commercial Net Positions JPY 2024.09.27 21:30 Friday // ...
以下查询创建一个名为“Upcoming Event Dates”(即将发生事件日期)的视图,用于从MQL5Calendar表中列出即将发生的事件。其包含事件的下一个日期及其对应的星期几。该查询分为两个关键部分:首先识别唯一事件,然后为每个事件确定下一个计划日期。
CREATE VIEW IF NOT EXISTS 'Upcoming Event Dates' AS WITH UNIQUE_EVENTS AS(SELECT DISTINCT M.EVENTID as 'E_ID',M.COUNTRY as 'Country',M.EVENTNAME as 'Name',M.EVENTCURRENCY as 'Currency' FROM 'MQL5Calendar' M),INFO_DATE AS(SELECT E_ID,Country,Name,Currency,(SELECT T.DST_NONE as 'Time' FROM MQL5Calendar M,Record R INNER JOIN TIMESCHEDULE T ON T.ID=M.ID WHERE DATE(REPLACE(Time,'.','-'))>R.Date AND E_ID=M.EVENTID ORDER BY Time ASC LIMIT 1) as 'Next Event Date' FROM UNIQUE_EVENTS) SELECT E_ID as 'ID',Country,Name,Currency,(CASE WHEN "Next Event Date" IS NULL THEN 'Unknown' ELSE "Next Event Date" END) as 'Upcoming Date',(CASE WHEN "Next Event Date"<>'Unknown' THEN (case cast (strftime('%w', DATE(REPLACE("Next Event Date",'.','-'))) as integer) WHEN 0 THEN 'Sunday' WHEN 1 THEN 'Monday' WHEN 2 THEN 'Tuesday' WHEN 3 THEN 'Wednesday' WHEN 4 THEN 'Thursday' WHEN 5 THEN 'Friday' ELSE 'Saturday' END) ELSE 'Unknown' END) as 'Day' FROM INFO_DATE Order BY "Upcoming Date" ASC;
CREATE VIEW IF NOT EXISTS 'Upcoming Event Dates'
创建名为“Upcoming Event Dates”的视图(如果该视图尚不存在)。
WITH 子句:公用表表达式(CTEs)
该查询使用公用表表达式(CTEs),将复杂查询拆分为更简单、可复用的部分。此处包含两个CTE:UNIQUE_EVENTS和INFO_DATE。
CTE 1: UNIQUE_EVENTS
WITH UNIQUE_EVENTS AS ( SELECT DISTINCT M.EVENTID as 'E_ID', M.COUNTRY as 'Country', M.EVENTNAME as 'Name', M.EVENTCURRENCY as 'Currency' FROM 'MQL5Calendar' M )
- 此部分从MQL5Calendar表中筛选唯一事件记录。
- 其通过DISTINCT关键字确保每个事件仅被列出一次,同时提取事件的以下字段:ID(EVENTID)、国家/地区、名称和货币。
该CTE的输出结果是一组包含事件关联详情的唯一事件集合。
UNIQUE_EVENTS中的字段:
- E_ID:事件的唯一标识符。
- Country:事件关联的国家/地区。
- Name:事件的名称。
- Currency:事件关联的货币类型。
CTE 2: INFO_DATE
INFO_DATE AS ( SELECT E_ID, Country, Name, Currency, ( SELECT T.DST_NONE as 'Time' FROM MQL5Calendar M, Record R INNER JOIN TimeSchedule T ON T.ID=M.ID WHERE DATE(REPLACE(Time, '.', '-')) > R.Date AND E_ID = M.EVENTID ORDER BY Time ASC LIMIT 1 ) as 'Next Event Date' FROM UNIQUE_EVENTS )
此CTE(INFO_DATE)用于为每个唯一事件获取下一次发生的事件日期。
- 针对每个事件(基于 E_ID、Country、Name和Currency),该查询会从TimeSchedule表的DST_NONE字段中查找即将发生的事件日期。
子查询说明:
- 子查询从TimeSchedule表中提取DST_NONE字段,该字段表示事件的时间或日期。
- 条件 DATE(REPLACE(Time, '.', '-')) > R.Date 确保仅选择未来事件(即日期大于当前日期R.Date的记录)。
- 事件按时间升序排序(ORDER BY Time ASC),因此最早发生的未来事件日期会被选中(LIMIT 1确保仅返回一条日期记录)。
INFO_DATE中的字段:
- E_ID:来自UNIQUE_EVENTS的事件 ID。
- Country:事件所属的国家/地区。
- Name:事件名称。
- Currency:与事件关联的货币。
- Next Event Date:通过子查询确定的下一次即将发生的事件日期。
主查询:最终选择与数据转换
SELECT E_ID as 'ID', Country, Name, Currency, (CASE WHEN "Next Event Date" IS NULL THEN 'Unknown' ELSE "Next Event Date" END) as 'Upcoming Date', (CASE WHEN "Next Event Date" <> 'Unknown' THEN (CASE CAST (strftime('%w', DATE(REPLACE("Next Event Date", '.', '-'))) AS INTEGER) WHEN 0 THEN 'Sunday' WHEN 1 THEN 'Monday' WHEN 2 THEN 'Tuesday' WHEN 3 THEN 'Wednesday' WHEN 4 THEN 'Thursday' WHEN 5 THEN 'Friday' ELSE 'Saturday' END) ELSE 'Unknown' END) as 'Day' FROM INFO_DATE ORDER BY "Upcoming Date" ASC;
主查询从CTE的INFO_DATE中提取最终结果,对数据进行转换并添加额外逻辑以处理可能缺失的事件日期(NULL值)的情况。
选择的字段:
- E_ID as 'ID':事件ID,重命名为"ID"。
- Country:与事件关联的国家/地区。
- Name:事件名称。
- Currency:与事件相关的货币。
- 即将发生日期:该字段基于Next Event Date生成。如果Next Event Date为 NULL,则显示Unknown;否则显示实际日期。
CASE WHEN "Next Event Date" IS NULL THEN 'Unknown' ELSE "Next Event Date" END
此CASE语句检查是否存在有效的即将发生日期。若日期为NULL,则输出Unknown;否则显示Next Event Date的值。
星期几计算:
- 如果Next Event Date不是Unknown,查询会通过以下CASE语句将日期转换为对应的星期几:
CASE CAST (strftime('%w', DATE(REPLACE("Next Event Date", '.', '-'))) AS INTEGER)
WHEN 0 THEN 'Sunday'
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
WHEN 3 THEN 'Wednesday'
WHEN 4 THEN 'Thursday'
WHEN 5 THEN 'Friday'
ELSE 'Saturday'
END
strftime('%w', ...) 函数从Next Event Date中提取星期几的数字:
- %w返回代表星期几的整数,其中 0 = 星期日,1 = 星期一,依此类推)。
- CASE语句将此数字映射为对应的星期名称(例如,0映射为Sunday)。
- 如果Next Event Date为Unknown,则Day列也会显示Unknown。
结果排序:
ORDER BY "Upcoming Date" ASC;
结果按Upcoming Date的升序排列,即最早的即将发生事件会显示在最前面。
“即将发生日期”视图输出数据示例:
ID Country Name Currency Upcoming Date Day 410020004 South Korea Industrial Production y/y KRW 2024.09.30 01:00 Monday 410020005 South Korea Retail Sales m/m KRW 2024.09.30 01:00 Monday 410020006 South Korea Index of Services m/m KRW 2024.09.30 01:00 Monday // ... 36500001 Australia S&P Global Manufacturing PMI AUD 2024.10.01 01:00 Tuesday 392030007 Japan Unemployment Rate JPY 2024.10.01 01:30 Tuesday 392050002 Japan Jobs to Applicants Ratio JPY 2024.10.01 01:30 Tuesday // ...
EA代码
这是实现新闻交易策略的主程序文件。以下代码支持针对自定义的新闻事件设置交易输入参数。
input Choice iCustom_Event_1=No;//USE EVENT IDs BELOW? input string iCustom_Event_1_IDs="";//EVENT IDs[Separate with a comma][MAX 14] input Choice iCustom_Event_2=No;//USE EVENT IDs BELOW? input string iCustom_Event_2_IDs="";//EVENT IDs[Separate with a comma][MAX 14] input Choice iCustom_Event_3=No;//USE EVENT IDs BELOW? input string iCustom_Event_3_IDs="";//EVENT IDs[Separate with a comma][MAX 14] input Choice iCustom_Event_4=No;//USE EVENT IDs BELOW? input string iCustom_Event_4_IDs="";//EVENT IDs[Separate with a comma][MAX 14] input Choice iCustom_Event_5=No;//USE EVENT IDs BELOW? input string iCustom_Event_5_IDs="";//EVENT IDs[Separate with a comma][MAX 14]
各输入参数说明:
Choice iCustom_Event_1=No;
- 类型:选择项
- 变量名:iCustom_Event_1
- 默认值:No
- 描述:该输入允许用户启用或禁用自定义事件ID(针对事件1)。此处的Choice为枚举类型,包含两个值:Yes或No。若设置为Yes,程序将使用对应字符串输入(iCustom_Event_1_IDs)中提供的事件ID。
string iCustom_Event_1_IDs="";
- 类型:字符串
- 变量名:iCustom_Event_1_IDs
- 默认值:空字符串""
- 描述:该输入允许用户输入自定义事件1的事件ID列表。ID需以逗号分隔(例如:"36010006,840030005,840030016"),最多允许输入14个ID。
初始化自定义新闻事件
CEvent1.useEvents = Answer(iCustom_Event_1); StringSplit(iCustom_Event_1_IDs, ',', CEvent1.EventIds); CEvent2.useEvents = Answer(iCustom_Event_2); StringSplit(iCustom_Event_2_IDs, ',', CEvent2.EventIds); CEvent3.useEvents = Answer(iCustom_Event_3); StringSplit(iCustom_Event_3_IDs, ',', CEvent3.EventIds); CEvent4.useEvents = Answer(iCustom_Event_4); StringSplit(iCustom_Event_4_IDs, ',', CEvent4.EventIds); CEvent5.useEvents = Answer(iCustom_Event_5); StringSplit(iCustom_Event_5_IDs, ',', CEvent5.EventIds);
该代码块用于初始化多个自定义新闻事件对象(如 CEvent1、CEvent2 等),并处理其关联的事件ID。
- CEvent1.useEvents = Answer(iCustom_Event_1):
- CEvent1是一个结构体,用于存储特定自定义新闻事件集的信息。useEvents标识通过Answer()函数设置,该函数根据输入变量iCustom_Event_1返回布尔值(true或false)。
- 如果iCustom_Event_1为 true,那么EA将在交易逻辑中使用该自定义事件;否则忽略。
- StringSplit(iCustom_Event_1_IDs, ',', CEvent1.EventIds):
- StringSplit()函数用于按逗号(,)拆分事件ID字符串(iCustom_Event_1_IDs),并将拆分后的ID列表存储在CEvent1.EventIds中。
- iCustom_Event_1_IDs是包含一个或多个事件ID的字符串,拆分函数将其转换为数组,供后续交易逻辑使用。
类似代码适用于CEvent2至CEvent5。
OnInit()函数
这是EA启动或添加到图表时调用的初始化函数。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Assign if in LightMode or not isLightMode=(iDisplayMode==Display_LightMode)?true:false; //--- call function for common initialization procedure InitCommon(); //--- store Init result int InitResult; if(!MQLInfoInteger(MQL_TESTER))//Checks whether the program is in the strategy tester { //--- initialization procedure outside strategy tester InitResult=InitNonTester(); } else { //--- initialization procedure inside strategy tester InitResult=InitTester(); } //--- Create DB in memory NewsObject.CreateEconomicDatabaseMemory(); //--- Initialize Candle properties pointer object CP = new CCandleProperties(); //--- Retrieve news events for the current Daily period into array CalendarArray NewsObject.EconomicDetailsMemory(CalendarArray,CTM.Time(TimeTradeServer(),0,0,0), (iOrderType!=StopOrdersType)?true:false); //--- Initialize Common graphics class pointer object CGraphics = new CCommonGraphics(Answer(iDisplay_Date),Answer(iDisplay_Spread), Answer(iDisplay_NewsInfo),Answer(iDisplay_EventObj)); CGraphics.GraphicsRefresh(iSecondsPreEvent);//-- Create chart objects //--- Set Time CDay.SetmyTime(CalendarArray); /* create timer, if in the strategy tester set the timer to 30s else 100ms */ EventSetMillisecondTimer((!MQLInfoInteger(MQL_TESTER))?100:30000); //-- Initialize Trade Management class pointer object Trade = new CTradeManagement(iDeviation); //--- return Init result return InitResult; }
关键步骤:
显示模式:
- EA检查当前运行模式为浅色模式还是深色模式(isLightMode)。
通用初始化:
- 调用InitCommon()函数执行常规初始化任务。
策略测试器检查:
- 通过MQLInfoInteger(MQL_TESTER)检测EA是否在策略测试器模式下运行,并根据结果调用InitNonTester()或InitTester()。
新闻事件数据库:
- 调用NewsObject.CreateEconomicDatabaseMemory()初始化内存中的经济事件数据库。这是EA存储新闻相关数据之处。
初始化K线属性:
- 创建CCandleProperties类指针 CP。该类负责管理K线属性(如开盘价、收盘价、最高价、最低价等)。
获取新闻事件:
- 调用NewsObject.EconomicDetailsMemory()根据筛选条件获取相关新闻事件。过滤出当前交易日的新闻。
初始化图形对象:
- 初始化CGraphics类,负责在图表上创建图形元素(如可视化新闻事件数据)。通过GraphicsRefresh()方法确保图形对象按配置时间(iSecondsPreEvent)刷新。
设置时间与定时器:
- 调用CDay.SetmyTime()处理新闻事件数组并管理交易时间。
- 根据测试模式或实盘模式设置不同间隔的定时器(EventSetMillisecondTimer()),为的是在策略测试器中测试EA策略的性能表现。
初始化交易管理:
- 初始化Trade类指针,并设置iDeviation参数以在需要时开立止损订单。
返回初始化结果:
- 函数返回初始化过程的执行结果。
定时器函数:
OnTimer()函数由定时器事件周期性触发,每次定时器触发时执行相应逻辑。
//+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { if(((!MQLInfoInteger(MQL_TESTER))?int(TimeTradeServer())%30==0:true)) { //--- Store start-up time. static datetime Startup_date = TimeTradeServer(); if(CTM.DateisToday(Startup_date)&&CP.NewCandle(0,PERIOD_D1) &&MQLInfoInteger(MQL_TESTER)) { //--- Retrieve news events for the current Daily period into array CalendarArray NewsObject.EconomicDetailsMemory(CalendarArray,CTM.Time(TimeTradeServer(),0,0,0), (iOrderType!=StopOrdersType)?true:false); //--- Initialize Common graphics class pointer object CGraphics = new CCommonGraphics(Answer(iDisplay_Date),Answer(iDisplay_Spread), Answer(iDisplay_NewsInfo),Answer(iDisplay_EventObj)); CGraphics.GraphicsRefresh(iSecondsPreEvent);//-- Create chart objects //--- Set Time CDay.SetmyTime(CalendarArray); } //--- Run procedures ExecutionOnTimer(Startup_date); if(CTS.isSessionStart()&&!CTS.isSessionEnd()) { //--- function to open trades TradeTime(); } //--- close trades within 45 min before end of session if(CTS.isSessionStart()&&CTS.isSessionEnd()&&!CTS.isSessionEnd(0,0)) { Trade.CloseTrades("NewsTrading"); } } }
关键特征:
- 基于时间的条件判断:通过if语句检查代码是在策略测试器中运行还是实时运行。如果为实时运行(非策略测试器环境),则检查服务器时间是否能被30整除(即每30秒触发一次逻辑)。
- K线与新闻更新:
- 如果当前日期与启动日期(Startup_date)匹配,且当日为新交易日(已形成新的日线K线,通过CP.NewCandle(0, PERIOD_D1) 判断),EA会调用 NewsObject.EconomicDetailsMemory 获取当日经济新闻数据。
- 同时更新图表上的最新信息。
- 交易时段控制:如果交易时段已开始(CTS.isSessionStart()),允许在时段内执行交易。如果交易时段即将结束,EA会主动平仓现有订单。
定时器执行函数
//+------------------------------------------------------------------+ //|Execute program procedures in time intervals | //+------------------------------------------------------------------+ void ExecutionOnTimer(datetime Startup_date) { //--- Check if not start-up date if(!CTM.DateisToday(Startup_date)) { //--- Run every New Daily Candle if(CP.NewCandle(1,PERIOD_D1)) { //--- Check if not in strategy tester if(!MQLInfoInteger(MQL_TESTER)) { //--- Update/Create DB in Memory NewsObject.CreateEconomicDatabaseMemory(); } //--- retrieve news events for the current day NewsObject.EconomicDetailsMemory(CalendarArray,CTM.Time(TimeTradeServer(),0,0,0), (iOrderType!=StopOrdersType)?true:false); //--- Set time from news events CDay.SetmyTime(CalendarArray); CGraphics.GraphicsRefresh(iSecondsPreEvent);//-- Create/Re-create chart objects } //--- Check if not in strategy tester if(!MQLInfoInteger(MQL_TESTER)) { //--- Run every New Hourly Candle if(CP.NewCandle(2,PERIOD_H1)) { //--- Check if DB in Storage needs an update if(NewsObject.UpdateRecords()) { //--- initialization procedure outside strategy tester InitNonTester(); } } } } else { //--- Run every New Daily Candle if(CP.NewCandle(3,PERIOD_D1)) { //--- Update Event objects on chart CGraphics.NewsEvent(); } } //--- Update realtime Graphic every 1 min if(CP.NewCandle(4,PERIOD_M1)) { //--- get the news events for the next min ahead of time. 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); } }
关键特征:
- 每日K线更新:系统会检测是否形成新的日线K线。如果检测到新日线K线,那么将更新新闻数据库(调用 NewsObject.CreateEconomicDatabaseMemory()),并获取当日的新闻事件数据。
- 小时K线更新:如果形成新的小时K线,EA会检查经济新闻数据库是否需要更新。如果需要更新,且当前不在策略测试器模式下,则重新初始化EA(调用 InitNonTester())。
- 实时更新:每分钟,EA会更新图表上的实时图形元素,并获取下一分钟的新闻事件数据,为后续交易做好准备。
交易时间管理函数
该函数负责管理新闻事件前后的交易执行逻辑。
//+------------------------------------------------------------------+ //|function to check trading time | //+------------------------------------------------------------------+ void TradeTime() { //--- Iterate through the event times for(uint i=0;i<myTimeData.Size();i++) { //--- Check if it is time to trade each news event if(CTM.TimePreEvent(CTM.TimeMinusOffset(datetime(myEvents[i].EventDate),iSecondsPreEvent) ,datetime(myEvents[i].EventDate)) &&(CTM.isDayOfTheWeek(TradingDay)||iNewSelection==News_Select_Custom_Events)) { //--- switch for order type selection switch(iOrderType) { case StopOrdersType:// triggers for STOP ORDERS StopOrders(myEvents[i]); break; default:// triggers for both MARKET POSITION & SINGLE STOP ORDER SingleOrder(myEvents[i]); break; } } } }
关键特征:
- 事件时间检查:针对myTimeData数组中的每个事件,EA会检查当前时间是否处于该事件实际发生前的预设“事件前”时间窗口(iSecondsPreEvent)内。
- 星期几过滤:仅当前星期几与配置的交易日(TradingDay)匹配,或已选择自定义事件(iNewSelection == News_Select_Custom_Events)时,才允许开仓。
- 订单类型选择:根据输入参数iOrderType(市价单或止损单),函数会在新闻事件前后执行市价单开仓或止损单挂单操作。
单笔订单函数
该函数根据新闻事件的影响程度,开立单笔市价单或止损单。
//+------------------------------------------------------------------+ //|function to open single order types | //+------------------------------------------------------------------+ void SingleOrder(Calendar &NewsEvent) { //--- Check each Impact value type switch(NewsObject.IMPACT(NewsEvent.EventImpact)) { //--- When Impact news is negative case CALENDAR_IMPACT_NEGATIVE: //--- Check if profit currency is news event currency if(NewsEvent.EventCurrency==CSymbol.CurrencyProfit()) { switch(iOrderType) { case MarketPositionType:// triggers for MARKET POSITION //--- Open buy trade with Event id as Magic number Trade.Buy(iStoploss,iTakeprofit,ulong(NewsEvent.EventId), "NewsTrading-"+NewsEvent.EventCode); break; case StopOrderType:// triggers for SINGLE STOP ORDER //--- Open buy-stop with Event id as Magic number Trade.OpenBuyStop(iStoploss,iTakeprofit,ulong(NewsEvent.EventId), "NewsTrading-SStop-"+NewsEvent.EventCode); break; default: break; } } else { switch(iOrderType) { case MarketPositionType:// triggers for MARKET POSITION //--- Open sell trade with Event id as Magic number Trade.Sell(iStoploss,iTakeprofit,ulong(NewsEvent.EventId), "NewsTrading-"+NewsEvent.EventCode); break; case StopOrderType:// triggers for SINGLE STOP ORDER //--- Open buy-stop with Event id as Magic number Trade.OpenSellStop(iStoploss,iTakeprofit,ulong(NewsEvent.EventId), "NewsTrading-SStop-"+NewsEvent.EventCode); break; default: break; } } break; //--- When Impact news is positive case CALENDAR_IMPACT_POSITIVE: //--- Check if profit currency is news event currency if(NewsEvent.EventCurrency==CSymbol.CurrencyProfit()) { switch(iOrderType) { case MarketPositionType:// triggers for MARKET POSITION //--- Open sell trade with Event id as Magic number Trade.Sell(iStoploss,iTakeprofit,ulong(NewsEvent.EventId), "NewsTrading-"+NewsEvent.EventCode); break; case StopOrderType:// triggers for SINGLE STOP ORDER //--- Open sell-stop with Event id as Magic number Trade.OpenSellStop(iStoploss,iTakeprofit,ulong(NewsEvent.EventId), "NewsTrading-SStop-"+NewsEvent.EventCode); break; default: break; } } else { switch(iOrderType) { case MarketPositionType:// triggers for MARKET POSITION //--- Open buy trade with Event id as Magic number Trade.Buy(iStoploss,iTakeprofit,ulong(NewsEvent.EventId), "NewsTrading-"+NewsEvent.EventCode); break; case StopOrderType:// triggers for SINGLE STOP ORDER //--- Open sell-stop with Event id as Magic number Trade.OpenBuyStop(iStoploss,iTakeprofit,ulong(NewsEvent.EventId), "NewsTrading-SStop-"+NewsEvent.EventCode); break; default: break; } } break; //--- Unknown default: break; } }
关键特征:
- 影响评估:EA通过NewsObject.IMPACT(NewsEvent.EventImpact) 评估新闻事件的影响方向(正向或负向)。
- 负面影响处理:如果新闻影响为负面,且事件货币与账户盈利货币匹配,当订单类型(iOrderType)为市价单时,则开立买入交易;当订单类型为止损单,则挂买入止损订单。如果事件货币与盈利货币不匹配,则在市价单模式下开立卖出交易;止损单模式下挂卖出止损订单。
- 正面影响处理:如果新闻影响为正面,且事件货币与账户盈利货币匹配,则在市价单模式下开立卖出交易;止损单模式下挂卖出止损订单。如果事件货币与盈利货币不匹配,则在市价单模式下开立买入交易;止损单模式下挂买入止损订单。
止损单处理函数
该函数在新闻事件前后同时挂设买入止损和卖出止损订单。无论事件影响方向如何,以捕捉价格双向波动的机会。
//+------------------------------------------------------------------+ //|function to open orders | //+------------------------------------------------------------------+ void StopOrders(Calendar &NewsEvent) { //--- Opens both buy-stop & sell-stop regardless of event impact Trade.OpenStops(iStoploss,iTakeprofit,ulong(NewsEvent.EventId), "NewsTrading-Stops-"+NewsEvent.EventCode); }
关键特征:
- 买入止损与卖出止损订单:同时挂设买入止损和卖出止损两种订单,每笔订单均与特定新闻事件关联,使用该事件的EventId作为交易的Magic编号。
交易处理函数
每当有新的交易事件发生时触发。该函数用于实时管理订单。
void OnTrade() { //--- Check if time is within the trading session if(CTS.isSessionStart() && !CTS.isSessionEnd(0,0)) { //--- Run procedures ExecutionOnTrade(); } }
- CTS.isSessionStart() and !CTS.isSessionEnd(0,0):
- CTS(交易时段类对象) 用于检查当前时间是否处于活跃的交易时段内。
- isSessionStart()检查交易时段是否已开始。
- !CTS.isSessionEnd(0,0)检查交易时段是否尚未结束。参数0,0代表在时段结束前的缓冲时间(偏移量),即不提前结束判断。
- 确保仅在当前时间处于活跃交易时段时,才对订单进行调整或操作。
- ExecutionOnTrade():
- 如果交易时段处于活跃状态,则调用ExecutionOnTrade()函数,处理与新订单执行相关的必要逻辑。
ExecutionOnTrade函数
该函数包含每次执行新订单时运行的逻辑。
void ExecutionOnTrade() { //--- if stop orders, enable fundamental mode if(iOrderType == StopOrdersType) { Trade.FundamentalMode("NewsTrading"); } //--- when stop order(s), enable slippage reduction if(iOrderType != MarketPositionType) { Trade.SlippageReduction(iStoploss, iTakeprofit, "NewsTrading"); } }
- if (iOrderType == StopOrdersType):
- 检查当前交易是否为止损订单类型(StopOrdersType)。如果满足,则调用Trade.FundamentalMode("NewsTrading") 函数。
- Trade.FundamentalMode("NewsTrading"):
- 该函数启用“基本面交易模式”,负责删除相反方向的挂单。
- "NewsTrading":EA的交易标签/注释,用于标识订单来源。
- if(iOrderType != MarketPositionType):
- 检查输入变量 是否非市价单类型。如果满足,则通过代码实现减少滑点。
- Trade.SlippageReduction(iStoploss, iTakeprofit, "NewsTrading"):
- 该函数可减少非市价单的滑点。
- 滑点是指实际成交价与预期价之间的偏差,尤其在剧烈波动或新闻事件时更为常见。
- 通过调用SlippageReduction(),EA尝试将此类滑点降至最低。
- 参数:
- iStoploss:止损值,即当价格触及该水平时,交易将自动平仓以限制潜在损失。
- iTakeprofit:止盈值,即交易应自动平仓以锁定利润的价格。
- "NewsTrading":EA的交易标签/注释,用于标识订单来源。
结论
在本文中,EA允许用户自定义新闻事件,并依据这些事件进行交易。通过用户输入初始化这些事件,随后由EA解析并处理。存储中的日历数据库已升级,新增“近期事件”与“即将发生事件”视图,为使用者提供更丰富的事件信息;各视图的查询均在文中详细说明。此外,新增了OnTimer与OnTrade函数,使代码可按特定时间条件或交易事件触发执行。感谢您的阅读,我期待在下一篇文章中为您提供更多有价值的内容。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16170
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
构建自优化型MQL5智能交易系统(EA)(第3部分):动态趋势跟踪与均值回归策略
交易中的神经网络:搭配区段注意力的参数效率变换器(PSformer)
开发回放系统(第 73 部分):不寻常的通信(二)