财经日历-利用新闻事件辅助交易
工具主要功能点
- 获取新闻事件:学习过滤新闻事件或基于新闻创建策略。
- 展示新闻事件面板:涉及图形化界面创建、分页功能实现,使用数组和结构体,适合编程基础薄弱的同学。
- MT5交互性事件应用:讲解鼠标移动、点击等事件,适合开发面向用户的友好界面,最后介绍DLL动态连接库的应用场景。
代码实现
cpp
input group "货币过滤";
sinput string InpCurrency = "A";// 相关货币(A:全部货币 C:当前品种货币 自定义的方式:USD|EUR|JPY...)
input group "级别过滤";
sinput bool InpUseImportanceNone = false; // 重要性未设置 的新闻事件
sinput bool InpUseImportanceLow = false; // 低重要性 的新闻事件
sinput bool InpUseImportanceModerate = true; // 中重要性 的新闻事件
sinput bool InpUseImportanceHigh = true; // 高重要性 的新闻事件
input group "面板设置";
sinput int HowManyDays = 60; // 获取多少天的新闻事件
sinput int eventsPerPage = 25;// 每页显示新闻事件数量
sinput int timerSeconds = 300; // 面板刷新频率(单位:秒)
// 用于记录当前所处的页面 用于分页功能
int currentPage = 0;
// 用于存储新闻事件的相关信息, 包括事件,名称,重要性,货币,实际值等
struct EVENT_STRUCT {
datetime time;
string name;
ENUM_CALENDAR_EVENT_IMPORTANCE importance;
string currency;
double actualValue;
double forecastValue;
double previousValue;
ENUM_CALENDAR_EVENT_IMPACT impactType;
string url;
string sourceUrl;
};
// 用于存储所有新闻事件的结构体数组, 用于显示面板
EVENT_STRUCT events[];
// 用于存储面板的列名
string columns[] = {"新闻事件","重要级别","影响货币","实际值","预测值", "前值","影响"};
// 一定一些常量 位置尺寸相关数据
#define XS1 150
#define XS2 140
#define YS1 25
#define firstPosition 1200
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
int OnInit() {
// 允许鼠标移动事件 当鼠标在图表上移动时,会触发相应的事件
ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
// 获取新闻事件
selectEventNews();
// 显示新闻事件的面板
showEvent();
// 定时器,用于刷新面板
EventSetTimer(timerSeconds);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void OnTimer(void) {
selectEventNews();
showEvent();
}
// 特殊函数,用于处理图表事件, 当用于与图表交互时(例如单击,双击,拖动等操作),该函数会被调用以响应响应的事件
// 例如单击事件、拖拽事件,键盘输入事件,文本编辑时间,鼠标移动时间等等,MQL5没有双击事件
// id 参数表示触发事件的类型,具体取决于用户的操作
// lparam、dparam 和 sparam 分别是长整型、双精度浮点型和字符串参数,用于传递额外的事件信息,可以根据需要进行使用。
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string& sparam) {
static ulong clickTimeMemory ;
if(id ==CHARTEVENT_OBJECT_CLICK) {
// 检查用户是否单击了"下页"按钮
if(StringFind(sparam,"下页",0)>=0) {
// 计算最大页数,通过计算事件数组大小除以每页显示的事件数量来得到最大页数
int maxPage = (ArraySize(events)-1) / eventsPerPage; // 最大页数
// 检查当前页 是否小于最大页数,以确保当前页不是最后一页
if(currentPage < maxPage) {
ObjectsDeleteAll(0,"面板",0,-1);
currentPage++;
showEvent();
} else {
Alert("已经是最后一页");
}
} else if( StringFind(sparam,"首页",0)>=0) {
currentPage =0 ;
showEvent(); // 重新显示事件以更新为首页
}
//如果按钮显示的文本是"隐藏", 则将文本更改为"显示", 并调整按钮的位置和大小,接着删除所有名为"面板"的对象,并重绘图表
else if(StringFind(sparam,"隐藏显示",0)>=0) {
string status = ObjectGetString(0,"隐藏显示",OBJPROP_TEXT);
if(status == "隐藏") {
ObjectSetString(0,"隐藏显示",OBJPROP_TEXT,"显示");
ObjectSetInteger(0,"隐藏显示",OBJPROP_XDISTANCE,55);
ObjectSetInteger(0,"隐藏显示",OBJPROP_XSIZE,50);
ObjectsDeleteAll(0,"面板",0,-1);
ChartRedraw();
} else if (status =="显示") {
showEvent();
}
} else if(StringFind(sparam,"面板-名称",0)>=0) { // 双击事件
ulong clickTime = GetTickCount();
if(clickTime < clickTimeMemory + 300) { // 双击事件
clickTimeMemory = 0;
// 打开url 地址
string tips = ObjectGetString(0,sparam,OBJPROP_TOOLTIP);
// 获得一个纯净的HTTP链接
StringReplace(tips,"双击打开:","");
OpenUrl(tips);
} else {
clickTimeMemory = clickTime;
}
} else if(StringFind(sparam,"面板-时间",0)>=0) { // 双击事件
ulong clickTime = GetTickCount();
if(clickTime < clickTimeMemory + 300) { // 双击事件
clickTimeMemory = 0;
// 打开url 地址
string tips = ObjectGetString(0,sparam,OBJPROP_TOOLTIP);
StringReplace(tips,"双击打开:","");
OpenUrl(tips);
} else {
clickTimeMemory = clickTime;
}
}
}
}
// 获取经济日历事件并根据一些过滤条件进行处理,并最终按时间进行排序
void selectEventNews() {
// 清理界面
ObjectsDeleteAll(0,"新闻事件",0,-1);
ObjectsDeleteAll(0,"面板",0,-1);
ObjectsDeleteAll(0,"隐藏显示",0,-1);
ChartRedraw(0);
// 数组重置为空数组,以便存储最新的经济日历事件
ArrayResize(events,0);
//存储经济日历事件的值
MqlCalendarValue values[];
// 30天前做为起始时间
// datetime startTime = iTime(_Symbol,PERIOD_D1,0) - PeriodSeconds(PERIOD_D1)*30;
// 通过iTime函数获取当天时间点, 用于确定事件获取的起始时间
datetime startTime = iTime(_Symbol,PERIOD_D1,0);
// 经济日历事件获取的结束时间
datetime endTime = startTime+PeriodSeconds(PERIOD_D1)*HowManyDays;
// 获取在指定时间范围内的经济日历事件,存储在values数组中
CalendarValueHistory(values,startTime,endTime,NULL,NULL);
for(int i=0;i<ArraySize(values);i++) {
MqlCalendarEvent event;
CalendarEventById(values[i].event_id,event);
MqlCalendarCountry country;
CalendarCountryById(event.country_id,country);
// 事件重要性过滤
if(!InpUseImportanceNone && event.importance == CALENDAR_IMPORTANCE_NONE) continue;
if(!InpUseImportanceLow && event.importance == CALENDAR_IMPORTANCE_LOW) continue;
if(!InpUseImportanceModerate && event.importance == CALENDAR_IMPORTANCE_MODERATE) continue;
if(!InpUseImportanceHigh && event.importance == CALENDAR_IMPORTANCE_HIGH) continue;
// 封装数据对象 时间,名称,重要性,货币,实际值,预测值,前值,影响 以及URL链接和来源URL
EVENT_STRUCT eventStruct;
eventStruct.time = values[i].time;
eventStruct.name = event.name;
eventStruct.importance = event.importance;
eventStruct.currency = country.currency;
eventStruct.actualValue = values[i].GetActualValue();
eventStruct.forecastValue = values[i].GetForecastValue();
eventStruct.previousValue = values[i].GetPreviousValue();
eventStruct.impactType = values[i].impact_type;
string url = "https://www.mql5.com/zh/economic-calendar/"+country.url_name+"/"+event.event_code+"?utm_campaign=calendar.event&utm_medium=special&utm_source=mt5terminal";
eventStruct.url = url;
eventStruct.sourceUrl = event.source_url;
//以上代码 可以继续优化 写在 过滤之后 可以提高性能
// 货币过滤
string currencyArray[];
if(InpCurrency == "A") { // 全部货币
// 显示垂直时间线
showFlag(eventStruct,clrRed);
// 获取当前已存储事件的数量, 将数组events的尺寸扩展一个单位,并将新的事件结构添加到数组的末尾
int currentArraySize = ArraySize(events);
ArrayResize(events,currentArraySize+1);
events[currentArraySize] = eventStruct;
/*
可以把这个功能融合到你们的EA之中, 放在OnTick函数中,根据当前时间判断是否处于新闻事件附近, 进行相应的操作,比如暂停交易
*/
// if(TimeCurrent() >= values[i].time -30*PeriodSeconds(PERIOD_M1) &&
// TimeCurrent() <= values[i].time +30*PeriodSeconds(PERIOD_M1)){
// Print(event.name," > 当前处于新闻事件附近,暂停交易...") ;
// }
} else if(InpCurrency == "C") { // 当前图表品种货币
// 查找当前新闻所影响的货币在当前品种是否存在
if(StringFind(_Symbol,country.currency)<0) continue;
showFlag(eventStruct,clrRed);
int currentArraySize = ArraySize(events);
ArrayResize(events,currentArraySize+1);
events[currentArraySize] = eventStruct;
} else {
// 自定义的货币过滤条件
StringSplit(InpCurrency,'|',currencyArray);
for(int i=0;i<ArraySize(currencyArray);i++) {
string currentCurrency = currencyArray[i];
if(StringFind(currentCurrency,country.currency)<0) continue;
showFlag(eventStruct,clrRed);
int currentArraySize = ArraySize(events);
ArrayResize(events,currentArraySize+1);
events[currentArraySize] = eventStruct;
}
}
}
// 数据按照时间排序
sortByTime(events,ArraySize(events));
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void showFlag(EVENT_STRUCT& ev, color clr) {
string objName = "新闻事件: ";
StringConcatenate(objName,objName,"-",ev.name,"-",ev.currency,"-",ev.importance,"-",ev.time);
objName+=" \n"+string(MathRand());
ObjectCreate(NULL,objName,OBJ_VLINE,0,ev.time,0);
ObjectSetString(NULL,objName,OBJPROP_TOOLTIP,objName);// 鼠标悬停显示信息
ObjectSetInteger(NULL,objName,OBJPROP_COLOR,clr);
ObjectSetInteger(NULL,objName,OBJPROP_WIDTH,1);
ObjectSetInteger(NULL,objName,OBJPROP_BACK,true);
ObjectSetInteger(NULL,objName, OBJPROP_STYLE, STYLE_DOT); // 关键修改:虚线样式
ChartRedraw(0);
}
// 按照时间排序 采用典型的冒泡排序
void sortByTime(EVENT_STRUCT &ev[], int size) {
for(int i=0;i<size-1;i++) {
for(int j=0;j<size-i-1;j++) { // 内部循环 比较两个相邻的元素
if(ev[j].time > ev[j+1].time) { // 如果前一个元素的时间大于后一个元素的时间,则交换着两个元素的位置
EVENT_STRUCT temp = ev[j];
ev[j] = ev[j+1];
ev[j+1] = temp;
}
}
}
}
void showEvent() {
// 分页相关
createPaginationButtons();
// 表头
createButton("面板","财 经 日 历",firstPosition-145*3, 20,695, 30,clrRed,clrBlack,18,clrGray,"楷体");
createButton("面板","发布时间",firstPosition,50,XS1,YS1,clrWhite,clrBlack,10,clrBlack);
// 创建表头显示列名
for(int i=0;i<ArraySize(columns);i++) {
createButton("面板"+(string)i,columns[i],(firstPosition-XS1)+i*-XS2,50,XS2,YS1,clrWhite,clrBlack,10,clrBlack);
}
// 根据当前页面和每页显示的时间数量计算要显示的时间索引范围
// startIndex 表示当前页面要显示的时间列表的起始索引,第一页就是自从零开始查
// endIndex 表示要查询多少条数据,如果超过events数组的大小,则取events数组的大小,就是events的事件数量小于每页的时间数,否则会引发数组下标越界
int startIndex = currentPage*eventsPerPage;
int endIndex = MathMin(startIndex+eventsPerPage,ArraySize(events));
// 在循环中 ,只显示在索引范围内的事件
int j=0;
for(int i=startIndex;i<endIndex;i++) {
// 每一行的颜色控制, 隔行显示, 偶数行一个颜色, 奇数行另一个颜色
color clr = clrWhite;
if(j%2==0) clr = C'185,181,181';
// 显示事件信息
// 增加URL参数, 用于打开事件的详情页面
createButton("面板-时间"+(string)i,(string)events[i].time,firstPosition,(50+YS1)+j*YS1,XS1,YS1,clrBlue,clr,9,clr,"Consolas","双击打开:"+events[i].sourceUrl);
createButton("面板-名称"+(string)i,events[i].name,firstPosition-XS2,(50+YS1)+j*YS1,XS1,YS1,clrRed,clr,9,clr,"Consolas","双击打开:"+events[i].url);
// 根据事件的重要性显示不同的颜色
string importance ;
color colorText = clrNONE;
if(events[i].importance == CALENDAR_IMPORTANCE_NONE) {
importance = "无";
colorText = clrBlack;
} else if(events[i].importance == CALENDAR_IMPORTANCE_LOW) {
importance = "低";
colorText = clrSteelBlue;
} else if(events[i].importance == CALENDAR_IMPORTANCE_MODERATE) {
importance = "中";
colorText = clrBlue;
} else if( events[i].importance == CALENDAR_IMPORTANCE_HIGH) {
importance = "高";
colorText = clrRed;
}
createButton("面板-级别"+(string)i,importance,firstPosition-XS2*2,(50+YS1)+j*YS1,XS1,YS1,colorText,clr,9,clr);
createButton("面板-货币"+(string)i,events[i].currency,firstPosition-XS2*3,(50+YS1)+j*YS1,XS1,YS1,clrBlack,clr,9,clr);
createButton("面板-实际"+(string)i,(string)events[i].actualValue=="nan"?"0":(string)events[i].actualValue,firstPosition-XS2*4,(50+YS1)+j*YS1,XS1,YS1,clrBlack,clr,9,clr);
createButton("面板-预测"+(string)i,(string)events[i].forecastValue=="nan"?"0":(string)events[i].forecastValue,firstPosition-XS2*5,(50+YS1)+j*YS1,XS1,YS1,clrBlack,clr,9,clr);
createButton("面板-前值"+(string)i,(string)events[i].previousValue=="nan"?"0":(string)events[i].previousValue,firstPosition-XS2*6,(50+YS1)+j*YS1,XS1,YS1,clrBlack,clr,9,clr);
// 根据事件的具体影响显示不同的颜色
string impactType ;
color colorText2 = clrNONE;
if(events[i].impactType == CALENDAR_IMPACT_NA) {
impactType ="无影响";
colorText2 = clrBlack;
} else if(events[i].impactType == CALENDAR_IMPACT_POSITIVE) {
impactType ="积极影响";
colorText2 = clrGreen;
} else if(events[i].impactType == CALENDAR_IMPACT_NEGATIVE) {
impactType ="消极影响";
colorText2 = clrRed;
}
createButton("面板-影响"+(string)i,impactType,firstPosition-XS2*7,(50+YS1)+j*YS1,XS1,YS1,colorText2,clr,9,clr);
j++;
}
// 表尾
createButton("面板-版权"+(string)j,"周老师工作室",firstPosition,50+YS1+j*YS1,XS1+XS2*ArraySize(columns)+1,YS1,clrWhite,clrBlack,10,clrBlack);
ChartRedraw(0);
}
//用于创建页面分页功能的按钮
/*
"面板-首页"按钮:位于firstPosion位置,大小为buttonwidth*buttonHeight,文本为“首页”,颜色为自色背景和黑色文本字体大小为15,灰色边框,使用"楷体"
"面板-下页"按钮:位于firstPosion-buttonwidth位置,大小和属性与“面板-首页”按钮相似,但文本是动态生成的,根据clrrentPage的值来确定显示的内容。
"隐藏显示"按钮:位于firstPosion -2*buttonwidth位置大小和属性与“面板-首页”按钮相似,文本为“隐藏”
最后一个操作chartRedraw(0)用于重绘图表。
*/
void createPaginationButtons() {
int buttonWidth = 145, buttonHeight = 30,buttonMargin = 10;
createButton("面板-首页","首页",firstPosition,20,buttonWidth,buttonHeight,clrWhite,clrBlack,15,clrGray,"楷体");
createButton("面板-下页","第"+(string(currentPage+1))+"页| 下一页",firstPosition-buttonWidth,20,buttonWidth,buttonHeight,clrWhite,clrBlack,15,clrGray);
createButton("隐藏显示","隐藏",firstPosition-buttonWidth*2,20,buttonWidth,buttonHeight,clrWhite,clrBlack,15,clrGray,"楷体");
ChartRedraw(0);
}
// 创建按钮对象
bool createButton(string objName,string text,int xD,int yD,int xS,int yS,color clrTxt,color clrBg,int fontSize=12,color clrBorder=clrNONE,string fontName="楷体",string tips="") {
// 格式化 事件名称
// string truncatedEventName = text;
// if(StringLen(text)>5) truncatedEventName= StringSubstr(text,0,5)+"...";
if(StringFind(objName,"隐藏显示",0)<0) objName+=(string)MathRand();
ResetLastError();
if(!ObjectCreate(0,objName,OBJ_BUTTON,0,0,0)) {
Print(__FUNCTION__,": Btn创建失败: ",GetLastError());
return false;
}
// xD 和yD 按钮位置坐标
ObjectSetInteger(0,objName,OBJPROP_XDISTANCE,xD);
ObjectSetInteger(0,objName,OBJPROP_YDISTANCE,yD);
// xS 和yS 按钮大小
ObjectSetInteger(0,objName,OBJPROP_XSIZE,xS);
ObjectSetInteger(0,objName,OBJPROP_YSIZE,yS);
// 以屏幕右上角为原点
ObjectSetInteger(0,objName,OBJPROP_CORNER,CORNER_RIGHT_UPPER);
// 按钮文本
ObjectSetString(0,objName,OBJPROP_TEXT,text);
ObjectSetString(0,objName,OBJPROP_FONT,fontName);
ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,fontSize);
// 按钮文本颜色
ObjectSetInteger(0,objName,OBJPROP_COLOR,clrTxt);
// 按钮背景颜色
ObjectSetInteger(0,objName,OBJPROP_BGCOLOR,clrBg);
// 按钮边框颜色
ObjectSetInteger(0,objName,OBJPROP_BORDER_COLOR,clrBorder);
ObjectSetInteger(0,objName,OBJPROP_BACK,false);
ObjectSetInteger(0,objName,OBJPROP_STATE,false);
ObjectSetInteger(0,objName,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,objName,OBJPROP_SELECTED,false);
ObjectSetString(0,objName,OBJPROP_TOOLTIP,tips);
// 图形对象有限接受点击图表事件
ObjectSetInteger(0,objName,OBJPROP_ZORDER,2);
// 重绘, 确保对图表的任何修改能够立即生效并反映在图表上
ChartRedraw(0);
return true;
}
// 版权跑马灯效果
void OnTick(void) {
string partialName = "面板-版权";
int totalObjects = ObjectsTotal(0);
for(int i=0;i<totalObjects;i++) {
string objName = ObjectName(0,i);
// 遍历所有图表对象查找名称中包含"面板-版权"的对象
if(StringFind(objName,partialName,0)==0) {
color randomColor = GenerateRandomColor();
ObjectSetInteger(0,objName,OBJPROP_COLOR,randomColor);
ChartRedraw(0);
}
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
ObjectsDeleteAll(0,"新闻事件",0,-1);
ObjectsDeleteAll(0,"面板",0,-1);
ObjectsDeleteAll(0,"隐藏显示",0,-1);
ChartRedraw(0);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
color GenerateRandomColor() {
int randomIndex = MathRand()%6; // 生成0到5之间的随机数
switch(randomIndex) {
case 0 :
return clrRed;
case 1:
return clrGreen;
case 2:
return clrWhiteSmoke;
case 3:
return clrWhite;
case 4:
return clrMagenta;
case 5:
return clrYellow;
default:
return clrWhite;
}
}
// ************************ 浏览器打开URL 需要开启DLL*************
#import "shell32.dll"
int ShellExecuteW(int hwnd, string lpOperation,string lpFile, string lpParameters, string lpDirectory, int nShowCmd);
#import
void OpenUrl(string url) {
int hwnd =0 ;
string operation = "open";
string file =url;
string parameters =NULL;
string directory =NULL;
int showCmd = 5;
Print("click",url);
ShellExecuteW(hwnd,operation,file,parameters,directory,showCmd);
}
// ********** 浏览器打开URL 需要开启DLL*************
//+------------------------------------------------------------------+