MT5多周期同窗监控指标开发
用MQL5按钮面板在子窗口同步显示多时间表
为什么需要在单一窗口监控多个时间表
在选择建仓方向时,同时观察多个时间框架的价格走势往往非常有用。MetaTrader 5客户端本身提供了21个时间表供分析使用,包括从M1到MN的各种周期。通常交易者会在屏幕上平铺多个图表窗口,但这样会占用大量空间且难以对齐比较。
MT5提供了一种特殊的图形对象——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
按钮与图表对象的创建函数
程序需要两类图形对象: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();
}
}
常见坑与对比
- 手动添加OBJ_CHART无法显示Ask/Bid线,必须代码设置OBJPROP_PRICE_SCALE等
- 子窗口指标必须用iCustom从资源加载,路径为::Indicators\SubWindow.ex5
- 按钮状态用OBJPROP_STATE读取,不要自己维护全局变量而不同步UI
- 缩放事件若遗漏AdjustChartObjectsSizes,图表对象会错位
- OnCalculate不可删,否则编译失败
总结与实战价值
该指标将多周期分析从“多窗口拼接”升级为“单窗口面板”,尤其适合短线交易者快速比对M1/H1/D1共振。代码模式也可复用于其他控件开发(如滚动条、悬停按钮),是学习MQL5图形界面与事件体系的优质范例。原文及代码见 https://www.mql5.com/zh/articles/749