MT5多周期同窗监控指标开发
📊

MT5多周期同窗监控指标开发

用MQL5按钮面板在子窗口同步显示多时间表

MQL5 编程 难度 · 进阶 2026-02-01 6 分钟阅读
#MQL5#多周期#子窗口#图表对象#指标开发

为什么需要在单一窗口监控多个时间表

在选择建仓方向时,同时观察多个时间框架的价格走势往往非常有用。MetaTrader 5客户端本身提供了21个时间表供分析使用,包括从M1到MN的各种周期。通常交易者会在屏幕上平铺多个图表窗口,但这样会占用大量空间且难以对齐比较。

MT5提供了一种特殊的图形对象——OBJ_CHART(图表对象),它可以嵌入到现有图表之上,并独立设置交易品种、时间表以及显示属性。你可以手动通过“插入->对象->图形对象->图表”添加任意数量的此类对象,但手工操作不便且耗时,更关键的是,很多属性如买价卖价线、右侧缩进、交易价位等只能通过编程设置。

✦ 核心思路
我们开发一个自定义指标,利用按钮控件在子窗口中批量创建和管理OBJ_CHART对象,实现一键切换多周期同屏,且随主图缩放自动校正。

手动添加图表对象的局限

通过菜单手动添加图表对象后,在属性对话框中只能控制有限参数,例如周期、品种、样式等。但以下重要属性无法在UI中配置:显示买价/卖价线、图表右侧缩进、交易价位线、OHLC显示等。这些只能在代码中通过ObjectSetInteger等函数设定。

此外,手动添加的对象不会随主图大小变化而自动调整尺寸,也不会在指标卸载时被统一清理。因此,用MQL5编程封装整套逻辑,既解决功能限制,也提升易用性。

指标架构与资源包含设计

本方案使用两个指标文件:主指标ChartObjects.mq5(运行于主窗口,提供按钮面板)和虚拟指标SubWindow.mq5(作为子窗口容器)。通过#resource指令将SubWindow.ex5编译进主指标,最终用户只需获得一个文件。

//--- Include indicator resource
#resource "\Indicators\SubWindow.ex5"

主指标声明图表窗口属性,不绘制任何序列:

#property indicator_chart_window // Indicator is in the main window
#property indicator_plots 0      // Zero plotting series

SubWindow.mq5同样不绘图,仅作为子窗口占位:

#property indicator_chart_window // Indicator is in the subwindow
#property indicator_plots 0      // Zero plotting series
⚠ 注意
OnCalculate函数虽无实际逻辑,但必须存在否则无法编译。真正的核心在OnInit、OnDeinit和OnChartEvent中。

按钮与图表对象的创建函数

程序需要两类图形对象:OBJ_BUTTON(按钮)和OBJ_CHART(子窗口内图表)。CreateButton函数封装了按钮的字体、颜色、锚点等属性设置;CreateChartInSubwindow函数在指定子窗口中创建图表对象,并设置时间表、缩放、各类显示开关。

void CreateChartInSubwindow(int window_number, int x_distance, int y_distance, int x_size, int y_size, string name, string symbol, ENUM_TIMEFRAMES timeframe, int subchart_scale, bool show_dates, bool show_prices, bool show_ohlc, bool show_ask_bid, bool show_levels, string tooltip)
  {
   if(ObjectCreate(0,name,OBJ_CHART,window_number,0,0))
     {
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x_distance);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y_distance);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,x_size);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,y_size);
      ObjectSetInteger(0,name,OBJPROP_CHART_SCALE,subchart_scale);
      ObjectSetInteger(0,name,OBJPROP_DATE_SCALE,show_dates);
      ObjectSetInteger(0,name,OBJPROP_PRICE_SCALE,show_prices);
     }
  }

按钮布局由AddTimeframeButtons和AddPropertyButtons完成,分别生成21个周期按钮和5个属性开关按钮(日期、价格、OHLC、Ask/Bid、交易价位)。

void AddTimeframeButtons()
  {
   int x_dist =1; int y_dist =125; int x_size =28; int y_size =20;
   for(int i=0; i<TIMEFRAME_BUTTONS; i++)
     {
      if(i%7==0) { x_dist=1; y_dist-=21; }
      CreateButton(0,0,timeframe_button_names[i],timeframe_button_texts[i], ANCHOR_LEFT_LOWER,CORNER_LEFT_LOWER,"Arial",8, cOffButtonFont,cOffButtonBackground,cOffButtonBorder, x_size,y_size,x_dist,y_dist,3);
      x_dist+=x_size+1;
     }
  }

子窗口的动态管理

当用户点击周期按钮时,程序先检查子窗口是否存在(ChartWindowFind)。若不存在,则用iCustom加载资源中的SubWindow.ex5,并通过ChartIndicatorAdd将其加入图表。随后调用AddChartObjectsToSubwindow按已选周期创建OBJ_CHART。

bool AddSubwindow()
  {
   subwindow_handle=iCustom(_Symbol,_Period,subwindow_path);
   if(subwindow_handle!=INVALID_HANDLE)
     {
      subwindow_number=(int)ChartGetInteger(0,CHART_WINDOWS_TOTAL);
      if(!ChartIndicatorAdd(0,subwindow_number,subwindow_handle))
         Print("Failed to add the SUBWINDOW indicator ! ");
      else return(true);
     }
   return(false);
  }

如果所有周期按钮都取消,DeleteSubwindow会移除子窗口;若已有图表对象,则按当前选中按钮数重新均分宽度并重建。InitializeTimeframeButtonStates负责同步按钮颜色与状态数组。

事件处理:点击与缩放

OnChartEvent中主要处理CHARTEVENT_OBJECT_CLICK(点击按钮)和CHARTEVENT_CHART_CHANGE(主图修改)。点击时由ChartEventObjectClick分发:若命中周期按钮则ToggleSubwindowAndChartObject,若命中属性按钮则ToggleChartObjectProperty。

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if(ChartEventObjectClick(id,lparam,dparam,sparam)) return;
   if(ChartEventChartChange(id,lparam,dparam,sparam)) return;
  }

属性按钮通过ShowDate、ShowPrice、ShowOHLC等函数遍历子窗口内所有OBJ_CHART并统一设置。例如ShowOHLC利用ObjectGetInteger取图表ID,再用ChartSetInteger修改。

当主图大小变化,ChartEventChartChange调用AdjustChartObjectsSizes,依据子窗口新尺寸重新计算并赋给每个图表对象,实现自适应。

void AdjustChartObjectsSizes()
  {
   if((subwindow_number=ChartWindowFind(0,subwindow_shortname))>0)
     {
      total_objects=ObjectsTotal(0,subwindow_number,OBJ_CHART);
      chart_object_width=chart_width/total_objects;
      for(int i=total_objects-1; i>=0; i--)
        {
         object_name=ObjectName(0,i,subwindow_number,OBJ_CHART);
         ObjectSetInteger(0,object_name,OBJPROP_YSIZE,chart_height);
         ObjectSetInteger(0,object_name,OBJPROP_XSIZE,chart_object_width);
         ObjectSetInteger(0,object_name,OBJPROP_XDISTANCE,x_distance);
         x_distance+=chart_object_width;
        }
     }
  }

清理与卸载逻辑

指标从图表移除时(reason==REASON_REMOVE),OnDeinit必须删除所有按钮及子窗口,避免残留对象。DeleteTimeframeButtons和DeletePropertyButtons遍历名称数组调用ObjectDelete。

void OnDeinit(const int reason)
  {
   if(reason==REASON_REMOVE)
     {
      DeleteTimeframeButtons();
      DeletePropertyButtons();
      ChartRedraw();
     }
  }

常见坑与对比

✦ 扩展练习
原文建议:可扩展为主图品种变更时同步子窗口品种,或将周期按钮按从低到高排序。这些在现成TF PANEL产品中已实现。

总结与实战价值

该指标将多周期分析从“多窗口拼接”升级为“单窗口面板”,尤其适合短线交易者快速比对M1/H1/D1共振。代码模式也可复用于其他控件开发(如滚动条、悬停按钮),是学习MQL5图形界面与事件体系的优质范例。原文及代码见 https://www.mql5.com/zh/articles/749

常见问题

编写主指标用OBJ_BUTTON做周期按钮,通过iCustom加载含#property indicator_chart_window的虚拟子窗口指标,再用ObjectCreate(0,name,OBJ_CHART,subwindow_number,...)创建图表对象并设置ENUM_TIMEFRAMES,详见 https://www.mql5.com/zh/articles/749 示例代码。
买价卖价、交易价位等属编程专属属性,需代码调用ObjectSetInteger设置OBJPROP_PRICE_SCALE或ChartSetInteger(CHART_SHOW_TRADE_LEVELS),手动属性框不可见。
在OnDeinit中判断reason==REASON_REMOVE,遍历按钮名称数组用ObjectDelete删除,并调用ChartIndicatorDelete移除子窗口指标,防止残留。
在OnChartEvent处理CHARTEVENT_CHART_CHANGE,获取子窗口新宽高后,用AdjustChartObjectsSizes遍历OBJ_CHART重设OBJPROP_XSIZE/YSIZE和XDISTANCE。
用#resource "\Indicators\SubWindow.ex5"包含子指标,主指标用iCustom(_Symbol,_Period,"::Indicators\SubWindow.ex5")调用,编译后仅分发主ex5。