Skip to content

全时段市场监控指标

一、适用场景

针对手动交易或半自动系统交易者。许多交易策略需人工参与信号评判,即使是完全量化策略,也可通过类似工具辅助检查盘面。

二、多周期分析需求

  • 顺大逆小理念:大周期(日线/周线)定趋势→中周期(4小时/6小时)找机会→小周期(15分钟/20分钟)确认进场点(如KD、RSI超卖信号)。
  • 痛点:多周期切换繁琐,指标分散,主观判断叠加时盯盘效率低。

三、全时段市场监控指标解决方案

1. 核心功能

  • 整合多周期指标:将趋势判断(双均线、Vegas隧道)、震荡指标(KD、RSI)集中在一个窗口,显示大/中/小周期的多空状态。
  • 可视化优势:一眼看清各周期趋势(如大级别做多、中级别震荡偏多、小级别震荡偏空),无需频繁切换图表。

2. 指标界面示例

指标M5M15M30H1H4D1
KD
RSI
双均线
Vegas隧道

四、代码实现

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*************
//+------------------------------------------------------------------+

最终效果

image.png