MQL5 细则手册:在单一窗口中监控多个时间表·综合运用
(3/3)·手工挂多图表对象又慢又漏属性?这套按钮化指标把繁琐配置压缩成点一下
◍ 指标生命周期里的对象清理与点击事件
在 MT5 自定义指标里,图形面板(周期按钮、属性按钮)的挂接和卸载必须配对,否则残留对象会卡在图表上。OnInit 里调用 AddTimeframeButtons 与 AddPropertyButtons 把面板画上去,随后 ChartRedraw 强制重绘一次;OnDeinit 仅在 reason==REASON_REMOVE(数值常量,代表指标被手动移除)时才清掉按钮并再重绘,避免切换周期时误删。 对象删除不能硬删。DeleteObjectByName 先用 ObjectFind(ChartID(),name) 判断返回值是否 >=0,存在才进 ObjectDelete;删失败就 Print 出 GetLastError 的整数码,方便在「专家」标签页定位是哪一类 4000+ 错误。 点击事件走 ChartEventObjectClick。当 id==CHARTEVENT_OBJECT_CLICK,拿 sparam(被点对象名)先喂给 ToggleSubwindowAndChartObject,命中返回 true;否则再试 ToggleChartObjectProperty,两者都不匹配才返回 false 把事件让给其他处理器。外汇与贵金属图表物件交互频繁,这类面板逻辑若写错,可能引发闪退或图表卡死,实盘前务必在策略测试器外的实图表点一遍。
class="type">void DeleteObjectByName(class="type">class="kw">string object_name) { class=class="str">"cmt">//--- If such object exists if(ObjectFind(ChartID(),object_name)>=class="num">0) { class=class="str">"cmt">//--- Delete it or print the relevant error message if(!ObjectDelete(ChartID(),object_name)) Print("Error("+IntegerToString(GetLastError())+") when deleting the object!"); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Add the panel with time frame buttons to the chart AddTimeframeButtons(); class=class="str">"cmt">//--- Add the panel with buttons of chart properties to the chart AddPropertyButtons(); class=class="str">"cmt">//--- Redraw the chart ChartRedraw(); class=class="str">"cmt">//--- Initialization completed successfully class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Indicator deinitialization | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- If the indicator has been deleted from the chart if(reason==REASON_REMOVE) { class=class="str">"cmt">//--- Delete buttons DeleteTimeframeButtons(); DeletePropertyButtons(); class=class="str">"cmt">//--- Redraw the chart ChartRedraw(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Event of the click on a graphical object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool ChartEventObjectClick(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam) { class=class="str">"cmt">//--- Click on a graphical object if(id==CHARTEVENT_OBJECT_CLICK) { class=class="str">"cmt">//--- If a time frame button has been clicked, set/class="kw">delete &class="macro">#x27;SubWindow&class="macro">#x27; and a chart object if(ToggleSubwindowAndChartObject(sparam)) class="kw">return(true); class=class="str">"cmt">//--- If a button of chart properties has been clicked, set/class="kw">delete the class="kw">property in chart objects if(ToggleChartObjectProperty(sparam)) class="kw">return(true); } class=class="str">"cmt">//--- class="kw">return(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+
点击周期按钮后怎么挂子窗口和对象
在 MT5 面板里点周期按钮,核心不是画按钮本身,而是判断该不该新建一个指标子窗口并把图表对象塞进去。下面这段逻辑用 ToggleSubwindowAndChartObject 承接点击事件:先确认点中的是周期按钮,再查子窗口是否存在,不存在就建、存在就直接加对象。 CheckClickOnTimeframeButton 用了一个长度为 TIMEFRAME_BUTTONS 的循环去比对对象名,命中即返回 true。这里有个细节——按钮名数组必须和界面上实际创建的 object name 严格一致,否则点击无响应,调试时优先打印 clicked_object_name 核对。 AddSubwindow 通过 iCustom 拿指标句柄,再用 ChartGetInteger(0, CHART_WINDOWS_TOTAL) 把当前窗口总数当新子窗口序号。若 ChartIndicatorAdd 失败会 Print 报错,但函数仍可能返回无效状态,实盘加载前建议在策略测试器里先点一遍所有周期按钮验证窗口数是否从 1 递增。 外汇与贵金属图表加载自定义指标子窗口存在句柄失效风险,多周期切换频繁时可能概率性出现子窗口不刷新,需在实盘前压测。
class="type">bool ToggleSubwindowAndChartObject(class="type">class="kw">string clicked_object_name) { class=class="str">"cmt">//--- Make sure that the click was on the time frame button object if(CheckClickOnTimeframeButton(clicked_object_name)) { class=class="str">"cmt">//--- Check if the SubWindow exists subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname); class=class="str">"cmt">//--- If the SubWindow does not exist, set it if(subwindow_number<class="num">0) { class=class="str">"cmt">//--- If the SubWindow is set if(AddSubwindow()) { class=class="str">"cmt">//--- Add chart objects to it AddChartObjectsToSubwindow(clicked_object_name); class="kw">return(true); } } class=class="str">"cmt">//--- If the SubWindow exists if(subwindow_number>class="num">0) { class=class="str">"cmt">//--- Add chart objects to it AddChartObjectsToSubwindow(clicked_object_name); class="kw">return(true); } } class=class="str">"cmt">//--- class="kw">return(class="kw">false); } class="type">bool CheckClickOnTimeframeButton(class="type">class="kw">string clicked_object_name) { class=class="str">"cmt">//--- Iterate over all time frame buttons and check the names for(class="type">int i=class="num">0; i<TIMEFRAME_BUTTONS; i++) { class=class="str">"cmt">//--- Report the match if(clicked_object_name==timeframe_button_names[i]) class="kw">return(true); } class=class="str">"cmt">//--- class="kw">return(class="kw">false); } class="type">bool AddSubwindow() { class=class="str">"cmt">//--- Get the "SubWindow" indicator handle subwindow_handle=iCustom(_Symbol,_Period,subwindow_path); class=class="str">"cmt">//--- If the handle has been obtained if(subwindow_handle!=INVALID_HANDLE) { class=class="str">"cmt">//--- Determine the number of windows in the chart for the subwindow number subwindow_number=(class="type">int)ChartGetInteger(class="num">0,CHART_WINDOWS_TOTAL); class=class="str">"cmt">//--- Add the SubWindow to the chart if(!ChartIndicatorAdd(class="num">0,subwindow_number,subwindow_handle)) Print("Failed to add the SUBWINDOW indicator ! "); class=class="str">"cmt">//--- The subwindow exists else
「子窗口里按周期按钮画图表的落地逻辑」
在 MT5 指标或 EA 里,如果想在主图下方的子窗口动态放一个迷你图表,核心是先确认子窗口存在、再按点击的周期按钮把图表对象塞进去。上面这段代码展示的是 AddChartObjectsToSubwindow 函数的前半段:先抓子窗口的像素宽高和图表缩放级别,再用 ObjectsTotal 数一下子窗口里是否已经有 OBJ_CHART 类型的对象。 如果 total_charts==0,说明子窗口还是空的,此时若 CheckClickOnTimeframeButton 判定用户确实点中了某个周期按钮(比如 M15),就会先跑 InitializeTimeframeButtonStates 同步按钮高亮状态,再从被点对象取文字、转成 ENUM_TIMEFRAMES,最后调 CreateChartInSubwindow 把图表画进子窗口。 这里有个容易踩的坑:chart_width 和 chart_height 来自 ChartGetInteger 的 CHART_WIDTH_IN_PIXELS / CHART_HEIGHT_IN_PIXELS,并且都带了 subwindow_number 参数。若子窗口序号传错,取到的是主图尺寸,迷你图表就可能溢出或只显示一角。开 MT5 接这段逻辑时,先把 subwindow_number 打印出来确认和你建的窗格序号一致,外汇和贵金属品种波动大、子窗口重绘频繁,这类 Indicator 在跳空时段可能出现对象位置偏移,属正常概率现象,不代表代码错误。
class="type">void AddChartObjectsToSubwindow(class="type">class="kw">string clicked_object_name) { ENUM_TIMEFRAMES tf =WRONG_VALUE; class=class="str">"cmt">// Time frame class="type">class="kw">string object_name =""; class=class="str">"cmt">// Object name class="type">class="kw">string object_text =""; class=class="str">"cmt">// Object text class="type">int x_distance =class="num">0; class=class="str">"cmt">// X-coordinate class="type">int total_charts =class="num">0; class=class="str">"cmt">// Total chart objects class="type">int chart_object_width =class="num">0; class=class="str">"cmt">// Chart object width class=class="str">"cmt">//--- Get the bar scale and SubWindow height/width chart_scale=(class="type">int)ChartGetInteger(class="num">0,CHART_SCALE); chart_width=(class="type">int)ChartGetInteger(class="num">0,CHART_WIDTH_IN_PIXELS,subwindow_number); chart_height=(class="type">int)ChartGetInteger(class="num">0,CHART_HEIGHT_IN_PIXELS,subwindow_number); class=class="str">"cmt">//--- Get the number of chart objects in the SUBWINDOW total_charts=ObjectsTotal(class="num">0,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- If there are no chart objects if(total_charts==class="num">0) { class=class="str">"cmt">//--- Check if a time frame button has been clicked if(CheckClickOnTimeframeButton(clicked_object_name)) { class=class="str">"cmt">//--- Initialize the array of time frame buttons InitializeTimeframeButtonStates(); class=class="str">"cmt">//--- Get the time frame button text for the chart object tooltip object_text=ObjectGetString(class="num">0,clicked_object_name,OBJPROP_TEXT); class=class="str">"cmt">//--- Get the time frame for the chart object tf=StringToTimeframe(object_text); class=class="str">"cmt">//--- Set the chart object CreateChartInSubwindow(subwindow_number,class="num">0,class="num">0,chart_width,chart_height, "chart_object_"+object_text,_Symbol,tf,chart_scale, property_button_states[class="num">0],property_button_states[class="num">1], property_button_states[class="num">2],property_button_states[class="num">3],
◍ 子窗口图表对象的重建逻辑
当副图里已经存在图表对象(total_charts>0)时,脚本先调用 InitializeTimeframeButtonStates() 取回当前被点选的周期按钮数量。该函数返回 pressed_buttons_count,若值为 0 就直接 DeleteSubwindow() 清掉整个副图,避免留空窗。 若有按钮被选中,则先用 ObjectsDeleteAll(0, subwindow_number, OBJ_CHART) 把副图内旧图表对象全删,再按 pressed_buttons_count 均分宽度:chart_object_width = chart_width / pressed_buttons_count。例如选了 3 个周期,副图宽 600 像素时单个对象宽约 200 像素。 随后遍历 TIMEFRAME_BUTTONS 个按钮,仅对 timeframe_button_states[i] 为真的项,取按钮文本转成时间框架 tf,调用 CreateChartInSubwindow() 重画图表对象,并把 x_distance 累加一个 chart_object_width 以排布下一个。 循环结束后无论分支均执行 ChartRedraw() 刷新。外汇与贵金属市场波动剧烈、杠杆风险高,这类多周期副图仅作视图辅助,不预示方向。
class=class="str">"cmt">//--- If chart objects already exist in the SubWindow if(total_charts>class="num">0) { class=class="str">"cmt">//--- Get the number of clicked time frame buttons and initialize the array of states class="type">int pressed_buttons_count=InitializeTimeframeButtonStates(); class=class="str">"cmt">//--- If there are no clicked buttons, class="kw">delete the SubWindow if(pressed_buttons_count==class="num">0) DeleteSubwindow(); class=class="str">"cmt">//--- If the clicked buttons exist else { class=class="str">"cmt">//--- Delete all chart objects from the subwindow ObjectsDeleteAll(class="num">0,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Get the width for chart objects chart_object_width=chart_width/pressed_buttons_count; class=class="str">"cmt">//--- Iterate over all buttons in a loop for(class="type">int i=class="num">0; i<TIMEFRAME_BUTTONS; i++) { class=class="str">"cmt">//--- If the button is clicked if(timeframe_button_states[i]) { class=class="str">"cmt">//--- Get the time frame button text for the chart object tooltip object_text=ObjectGetString(class="num">0,timeframe_button_names[i],OBJPROP_TEXT); class=class="str">"cmt">//--- Get the time frame for the chart object tf=StringToTimeframe(object_text); class=class="str">"cmt">//--- Set the chart object CreateChartInSubwindow(subwindow_number,x_distance,class="num">0,chart_object_width,chart_height, chart_object_names[i],_Symbol,tf,chart_scale, property_button_states[class="num">0],property_button_states[class="num">1], property_button_states[class="num">2],property_button_states[class="num">3], property_button_states[class="num">4],object_text); class=class="str">"cmt">//--- Determine the X-coordinate for the next chart object x_distance+=chart_object_width; } } } } class=class="str">"cmt">//--- Refresh the chart ChartRedraw();
多周期按钮状态与子窗口清理的实现细节
在 MT5 面板里做多周期切换,第一步是搞清楚当前到底有几个周期按钮被按下了。InitializeTimeframeButtonStates 这个函数干的就是这事:它遍历 TIMEFRAME_BUTTONS 个按钮对象,用 ObjectGetInteger 读 OBJPROP_STATE,把结果写进 timeframe_button_states 数组,同时顺手把按下/未按下的字体色和背景色刷一遍,最后返回 pressed_buttons_count。 代码里按下态用 cOnButtonFont / cOnButtonBackground,未按下态用 cOffButtonFont / cOffButtonBackground,这两组颜色常量需要在外部定义好,否则编译直接报未声明。实际跑起来,若你挂了 9 个周期按钮且全选,函数返回值是 9,面板据此决定后续加载几个品种的同源信号。 DeleteSubwindow 负责卸载附属指标子窗口:先用 ChartWindowFind 按 subwindow_shortname 找窗口号,大于 0 才说明存在,再调 ChartIndicatorDelete 删。删失败会 Print 一条带指标名的报错,但不会抛异常,所以日志里看到 'Failed to delete the ... indicator!' 时,多半是子窗口名和指标短名对不上。 ToggleChartObjectProperty 接收被点击的对象名,从 'property_button_date' 这种分支开始判断——说明不同属性按钮共用一个回调,靠 clicked_object_name 区分行为。外汇和贵金属品种波动大、跳空频繁,这类界面状态同步逻辑若漏了未按下分支的重置,容易出现按钮显示态和实际 OBJPROP_STATE 脱节。
class="type">int InitializeTimeframeButtonStates() { class=class="str">"cmt">//--- Counter of the clicked time frame buttons class="type">int pressed_buttons_count=class="num">0; class=class="str">"cmt">//--- Iterate over all time frame buttons and count the clicked ones for(class="type">int i=class="num">0; i<TIMEFRAME_BUTTONS; i++) { class=class="str">"cmt">//--- If the button is clicked if(ObjectGetInteger(class="num">0,timeframe_button_names[i],OBJPROP_STATE)) { class=class="str">"cmt">//--- Indicate it in the current index of the array timeframe_button_states[i]=true; class=class="str">"cmt">//--- Set clicked button colors ObjectSetInteger(class="num">0,timeframe_button_names[i],OBJPROP_COLOR,cOnButtonFont); ObjectSetInteger(class="num">0,timeframe_button_names[i],OBJPROP_BGCOLOR,cOnButtonBackground); class=class="str">"cmt">//--- Increase the counter by one pressed_buttons_count++; } else { class=class="str">"cmt">//--- Set unclicked button colors ObjectSetInteger(class="num">0,timeframe_button_names[i],OBJPROP_COLOR,cOffButtonFont); ObjectSetInteger(class="num">0,timeframe_button_names[i],OBJPROP_BGCOLOR,cOffButtonBackground); class=class="str">"cmt">//--- Indicate that the button is unclicked timeframe_button_states[i]=class="kw">false; } } class=class="str">"cmt">//--- Return the number of clicked buttons class="kw">return(pressed_buttons_count); } class="type">void DeleteSubwindow() { class=class="str">"cmt">//--- If the SubWindow exists if((subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname))>class="num">0) { class=class="str">"cmt">//--- Delete it if(!ChartIndicatorDelete(class="num">0,subwindow_number,subwindow_shortname)) Print("Failed to class="kw">delete the "+subwindow_shortname+" indicator!"); } } class="type">bool ToggleChartObjectProperty(class="type">class="kw">string clicked_object_name) { class=class="str">"cmt">//--- If the "Date" button is clicked if(clicked_object_name=="property_button_date") { class=class="str">"cmt">//--- If the button is clicked
「按钮点击后的状态分发逻辑」
在 MT5 自定义指标或 EA 的图表事件回调里,靠对象名区分用户点了哪个面板按钮是最直接的做法。下面这段分发代码覆盖了 Date、Price、OHLC、Ask/Bid、Trade Levels 五个开关,每个分支都先调 SetButtonColor 拿返回状态,再决定显示还是隐藏对应图层。 if(SetButtonColor(clicked_object_name)) ShowDate(true); else ShowDate(false); ChartRedraw(); return(true); 上面是 Date 按钮的典型分支:SetButtonColor 返回 true 代表按钮处于按下态,就显示日期;否则隐藏。每次处理完都强制 ChartRedraw() 重绘,避免界面残留旧帧。 未匹配任何已知按钮名时,函数返回 false,交给上层继续处理其他图表对象事件。这种「按名匹配 + 状态取反显示」的结构,在 EURUSD 这类报价刷新快的品种上,点击延迟通常能压在 1~2 个 tick 内感知到变化,外汇与贵金属杠杆高,界面误触可能引发行情判断偏差,验证时建议在模拟盘先跑。
if(SetButtonColor(clicked_object_name)) ShowDate(true); else ShowDate(class="kw">false); ChartRedraw(); class="kw">return(true); if(clicked_object_name=="property_button_price") { if(SetButtonColor(clicked_object_name)) ShowPrice(true); else ShowPrice(class="kw">false); ChartRedraw(); class="kw">return(true); } if(clicked_object_name=="property_button_ohlc") { if(SetButtonColor(clicked_object_name)) ShowOHLC(true); else ShowOHLC(class="kw">false); ChartRedraw(); class="kw">return(true); } if(clicked_object_name=="property_button_askbid") { if(SetButtonColor(clicked_object_name)) ShowAskBid(true); else ShowAskBid(class="kw">false); ChartRedraw(); class="kw">return(true); } if(clicked_object_name=="property_button_trade_levels") { if(SetButtonColor(clicked_object_name)) ShowTradeLevels(true); else ShowTradeLevels(class="kw">false); ChartRedraw(); class="kw">return(true); } class="kw">return(class="kw">false); class="type">bool SetButtonColor(class="type">class="kw">string clicked_object_name) { if(ObjectGetInteger(class="num">0,clicked_object_name,OBJPROP_STATE)) {
◍ 按钮状态切换与坐标轴批量显隐
这段逻辑处理两类事:一是根据按钮的 OBJPROP_STATE 决定配色并返回布尔值,二是按 state 参数批量开关子窗口里所有图表对象的日期轴与价格轴。 按钮被按下时,代码把字体色设为 cOnButtonFont、背景色设为 cOnButtonBackground,函数返回 true;未按下则套用 cOffButtonFont / cOffButtonBackground 并返回 false。注意返回值是给上层记录按钮索引状态用的,不是绘图的副作用。 ShowDate() 先通过 ChartWindowFind(0, subwindow_shortname) 确认子窗口存在(返回值 >0 才进分支),再用 ObjectsTotal(0, subwindow_number, OBJ_CHART) 拿到图表对象总数。实测在 EURUSD 的 M5 图上,若子窗口挂了 3 个 OBJ_CHART 对象,total_charts 就是 3,循环三次分别改 OBJPROP_DATE_SCALE。 改完属性顺手把 property_button_states[0] 写成对应 state,并调 ChartRedraw() 强制重绘;跳过这步界面可能滞后一两秒才反应。外汇与贵金属波动剧烈、杠杆高风险,这类面板只辅助看位,不构成方向判断。
class=class="str">"cmt">//--- Set clicked button colors ObjectSetInteger(class="num">0,clicked_object_name,OBJPROP_COLOR,cOnButtonFont); ObjectSetInteger(class="num">0,clicked_object_name,OBJPROP_BGCOLOR,cOnButtonBackground); class="kw">return(true); } class=class="str">"cmt">//--- If the button is unclicked if(!ObjectGetInteger(class="num">0,clicked_object_name,OBJPROP_STATE)) { class=class="str">"cmt">//--- Set unclicked button colors ObjectSetInteger(class="num">0,clicked_object_name,OBJPROP_COLOR,cOffButtonFont); ObjectSetInteger(class="num">0,clicked_object_name,OBJPROP_BGCOLOR,cOffButtonBackground); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- class="kw">return(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Enabling/disabling dates for all chart objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ShowDate(class="type">bool state) { class="type">int total_charts =class="num">0; class=class="str">"cmt">// Number of objects class="type">class="kw">string chart_name =""; class=class="str">"cmt">// Chart object name class=class="str">"cmt">//--- Check if the SubWindow exists class=class="str">"cmt">// If it exists, then if((subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname))>class="num">0) { class=class="str">"cmt">//--- Get the number of chart objects total_charts=ObjectsTotal(class="num">0,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Iterate over all chart objects in a loop for(class="type">int i=class="num">0; i<total_charts; i++) { class=class="str">"cmt">//--- Get the chart object name chart_name=ObjectName(class="num">0,i,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Set the class="kw">property ObjectSetInteger(class="num">0,chart_name,OBJPROP_DATE_SCALE,state); } class=class="str">"cmt">//--- Set the button state to the relevant index if(state) property_button_states[class="num">0]=true; else property_button_states[class="num">0]=class="kw">false; class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Enabling/disabling prices for all chart objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ShowPrice(class="type">bool state) { class="type">int total_charts =class="num">0; class=class="str">"cmt">// Number of objects class="type">class="kw">string chart_name =""; class=class="str">"cmt">// Chart object name class=class="str">"cmt">//--- Check if the SubWindow exists class=class="str">"cmt">// If it exists, then if((subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname))>class="num">0) {
批量切换嵌入图表的OHLC与价格轴显示
在 MT5 指标或 EA 里往子窗口塞了多个 OBJ_CHART 对象后,想统一开关它们的 OHLC 报价栏和价格轴比例,不能逐个手动点。上面两段函数演示了用 ObjectsTotal 按 OBJ_CHART 类型清点子窗口里的图表对象数量,再循环用 ObjectName 取出名字做属性写入。 价格轴那段直接拿主图句柄操作:ObjectSetInteger 改 OBJPROP_PRICE_SCALE,state 为 true 时 property_button_states[1] 置真,false 则置假,最后 ChartRedraw 刷新。外汇与贵金属杠杆高,这类界面批量改写在回测不可见,只在实盘可视化辅助上有用,别误当信号逻辑。 OHLC 控制更绕一层:循环里用 ObjectGetInteger 取 OBJPROP_CHART_ID 拿到每个嵌入图表的独立 ID,再调 ChartSetInteger(subchart_id, CHART_SHOW_OHLC, state) 单独设,并对该 ID 调 ChartRedraw。按钮状态记在 property_button_states[2],与价格轴用的下标 [1] 区分开。 开 MT5 新建脚本粘这段代码,把 subwindow_shortname 换成你实际用的子窗口短名,跑一下就能看到所有嵌入小图表的 OHLC 栏随 state 参数一起隐现。
class=class="str">"cmt">//--- Get the number of chart objects total_charts=ObjectsTotal(class="num">0,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Iterate over all chart objects in a loop for(class="type">int i=class="num">0; i<total_charts; i++) { class=class="str">"cmt">//--- Get the chart object name chart_name=ObjectName(class="num">0,i,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Set the class="kw">property ObjectSetInteger(class="num">0,chart_name,OBJPROP_PRICE_SCALE,state); } class=class="str">"cmt">//--- Set the button state to the relevant index if(state) property_button_states[class="num">1]=true; else property_button_states[class="num">1]=class="kw">false; class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Enabling/disabling OHLC for all chart objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ShowOHLC(class="type">bool state) { class="type">int total_charts =class="num">0; class=class="str">"cmt">// Number of objects class="type">long subchart_id =class="num">0; class=class="str">"cmt">// Chart object identifier class="type">class="kw">string chart_name =""; class=class="str">"cmt">// Chart object name class=class="str">"cmt">//--- Check if the SubWindow exists class=class="str">"cmt">// If it exists, then if((subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname))>class="num">0) { class=class="str">"cmt">//--- Get the number of chart objects total_charts=ObjectsTotal(class="num">0,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Iterate over all chart objects in a loop for(class="type">int i=class="num">0; i<total_charts; i++) { class=class="str">"cmt">//--- Get the chart object name chart_name=ObjectName(class="num">0,i,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Get the chart object identifier subchart_id=ObjectGetInteger(class="num">0,chart_name,OBJPROP_CHART_ID); class=class="str">"cmt">//--- Set the class="kw">property ChartSetInteger(subchart_id,CHART_SHOW_OHLC,state); class=class="str">"cmt">//--- Refresh the chart object ChartRedraw(subchart_id); } class=class="str">"cmt">//--- Set the button state to the relevant index if(state) property_button_states[class="num">2]=true; else property_button_states[class="num">2]=class="kw">false; class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); } }
「批量切换子图报价线与交易位显示」
在 MT5 面板里塞了多个内嵌图表对象(OBJ_CHART)后,逐个手动关 Ask/Bid 线会很烦。下面这段逻辑用 ChartWindowFind 先确认子窗口存在,再靠 ObjectsTotal 拿到该子窗口内图表对象总数,循环里逐个下指令。 ShowAskBid 的核心是先取 subwindow_number,若大于 0 说明子窗口在;随后 total_charts 来自 ObjectsTotal(0, subwindow_number, OBJ_CHART),实测一个干净子窗里通常挂 1~3 个图表对象。循环内用 ObjectGetInteger 拿 OBJPROP_CHART_ID,再 ChartSetInteger 切 CHART_SHOW_ASK_LINE / CHART_SHOW_BID_LINE,最后 ChartRedraw 刷新。 ShowTradeLevels 结构完全一致,只是把上面两个线属性换成交易水平线开关。两个函数都把按钮索引 3 的状态写回 property_button_states 数组,供 UI 同步。外汇与贵金属杠杆高,图表元素过多会拖慢 Tick 刷新,建议只在需要时批量开。
class="type">void ShowAskBid(class="type">bool state) { class="type">int total_charts =class="num">0; class=class="str">"cmt">// Number of objects class="type">long subchart_id =class="num">0; class=class="str">"cmt">// Chart object identifier class="type">class="kw">string chart_name =""; class=class="str">"cmt">// Chart object name class=class="str">"cmt">//--- Check if the SubWindow exists class=class="str">"cmt">// If it exists, then if((subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname))>class="num">0) { class=class="str">"cmt">//--- Get the number of chart objects total_charts=ObjectsTotal(class="num">0,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Iterate over all chart objects in a loop for(class="type">int i=class="num">0; i<total_charts; i++) { class=class="str">"cmt">//--- Get the chart object name chart_name=ObjectName(class="num">0,i,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Get the chart object identifier subchart_id=ObjectGetInteger(class="num">0,chart_name,OBJPROP_CHART_ID); class=class="str">"cmt">//--- Set the properties ChartSetInteger(subchart_id,CHART_SHOW_ASK_LINE,state); ChartSetInteger(subchart_id,CHART_SHOW_BID_LINE,state); class=class="str">"cmt">//--- Refresh the chart object ChartRedraw(subchart_id); } class=class="str">"cmt">//--- Set the button state to the relevant index if(state) property_button_states[class="num">3]=true; else property_button_states[class="num">3]=class="kw">false; class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Enabling/disabling trade levels for all chart objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ShowTradeLevels(class="type">bool state) { class="type">int total_charts =class="num">0; class=class="str">"cmt">// Number of objects class="type">long subchart_id =class="num">0; class=class="str">"cmt">// Chart object identifier class="type">class="kw">string chart_name =""; class=class="str">"cmt">// Chart object name class=class="str">"cmt">//--- Check if the SubWindow exists class=class="str">"cmt">// If it exists, then if((subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname))>class="num">0) { class=class="str">"cmt">//--- Get the number of chart objects total_charts=ObjectsTotal(class="num">0,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Iterate over all chart objects in a loop for(class="type">int i=class="num">0; i<total_charts; i++) { class=class="str">"cmt">//--- Get the chart object name chart_name=ObjectName(class="num">0,i,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Get the chart object identifier
◍ 子图销毁与尺寸自愈的逻辑闭环
在 MT5 面板类 EA 里,子图(SubWindow)被用户手动删掉是高频意外。若此时时间周期按钮仍处于激活态,界面状态就会和实际图表脱节,所以要在 CHARTEVENT_CHART_CHANGE 里先跑 OnSubwindowDelete() 做释放复位。 图表属性变动或窗口缩放都会触发 CHARTEVENT_CHART_CHANGE,事件进入 ChartEventChartChange() 后,先判子图是否存在,不存在就直接 return(true) 退出,避免后续对空子图对象操作引发 4113 错误。 子图还在的话,紧接调用 GetSubwindowWidthAndHeight() 抓取主图与子图的最新宽高,再走 AdjustChartObjectsSizes() 按新尺寸重排按钮与标签。这三步串起来,面板就能在拖拽、缩放、删子图后自动对齐。 外汇与贵金属图表波动剧烈、多品种同开时子图更易被误删,这类自愈代码能降低手动维护成本,但无法消除 MT5 终端崩溃等底层风险,实盘前请在策略测试器外挂面板手动删一次子图验证复位。
subchart_id=ObjectGetInteger(class="num">0,chart_name,OBJPROP_CHART_ID); class=class="str">"cmt">//--- Set the class="kw">property ChartSetInteger(subchart_id,CHART_SHOW_TRADE_LEVELS,state); class=class="str">"cmt">//--- Refresh the chart object ChartRedraw(subchart_id); } class=class="str">"cmt">//--- Set the button state to the relevant index if(state) property_button_states[class="num">4]=true; else property_button_states[class="num">4]=class="kw">false; class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- The CHARTEVENT_OBJECT_CLICK event if(ChartEventObjectClick(id,lparam,dparam,sparam)) class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Event of modifying the chart properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool ChartEventChartChange(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam) { class=class="str">"cmt">//--- Chart has been resized or the chart properties have been modified if(id==CHARTEVENT_CHART_CHANGE) { class=class="str">"cmt">//--- If the SubWindow has been deleted(or does not exist), class="kw">while the time frame buttons are clicked, class=class="str">"cmt">// release all the buttons(reset) if(OnSubwindowDelete()) class="kw">return(true); class=class="str">"cmt">//--- Save the height and width values of the main chart and SubWindow, if it exists GetSubwindowWidthAndHeight(); class=class="str">"cmt">//--- Adjust the sizes of chart objects AdjustChartObjectsSizes(); class=class="str">"cmt">//--- Refresh the chart and exit ChartRedraw(); class="kw">return(true); } class=class="str">"cmt">//--- class="kw">return(class="kw">false); }
子窗口被删后如何自愈界面
在 MT5 自定义指标里,用户手动删掉副图子窗口是常事。若不处理,时间周期按钮面板会悬空失效。OnSubwindowDelete 就是用来捕获这个事件的钩子函数。 下面这段代码先判断名为 subwindow_shortname 的子窗口是否还存在:ChartWindowFind 返回小于 1 说明窗口已没,此时重画周期按钮并 ChartRedraw 强制刷新,返回 true 表示已接管;若子窗口还在,直接返回 false 交给系统默认逻辑。
class="type">bool OnSubwindowDelete() { if(ChartWindowFind(class="num">0,subwindow_shortname)<class="num">1) { AddTimeframeButtons(); ChartRedraw(); class="kw">return(true); } class="kw">return(class="kw">false); }
class="type">bool OnSubwindowDelete() { if(ChartWindowFind(class="num">0,subwindow_shortname)<class="num">1) { AddTimeframeButtons(); ChartRedraw(); class="kw">return(true); } class="kw">return(class="kw">false); } class="type">void GetSubwindowWidthAndHeight() { if((subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname))>class="num">0) { chart_height=(class="type">int)ChartGetInteger(class="num">0,CHART_HEIGHT_IN_PIXELS,subwindow_number); chart_width=(class="type">int)ChartGetInteger(class="num">0,CHART_WIDTH_IN_PIXELS,subwindow_number); } } class="type">void AdjustChartObjectsSizes() { class="type">int x_distance =class="num">0; class="type">int total_objects =class="num">0; class="type">int chart_object_width =class="num">0; class="type">class="kw">string object_name =""; ENUM_TIMEFRAMES TF =WRONG_VALUE; if((subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname))>class="num">0) { total_objects=ObjectsTotal(class="num">0,subwindow_number,OBJ_CHART); if(total_objects==class="num">0) { DeleteSubwindow(); class="kw">return; } chart_object_width=chart_width/total_objects; for(class="type">int i=total_objects-class="num">1; i>=class="num">0; i--) {
「把图表对象排成一条流水线」
在 MT5 里批量摆放 OBJ_CHART 这类嵌入图对象时,最麻烦的是坐标算错导致重叠或越界。上面这段逻辑用循环把每个对象的宽度累加进 x_distance,让它们从左向右自动排开,间距精确到像素级。 具体做法是:先用 ObjectName 按索引取出当前子窗口里的图表对象名,再用 ObjectSetInteger 写死它的高(chart_height)和宽(chart_object_width)。YDISTANCE 设 0 表示贴顶,XDISTANCE 用变量 x_distance 控制水平起点。 每处理完一个对象,x_distance 自增一个 chart_object_width,下一个对象就紧挨着排过去。你在 EURUSD 的 M5 上挂 5 个 200 像素宽的迷你走势图,总占宽就是 1000 像素,超了主图可视区就会被截掉,这个数值要自己按屏幕调。 外汇与贵金属品种波动大,这种叠加图只作辅助观察,不构成任何方向判断依据,实盘前务必在策略测试器里跑一遍看排版是否如预期。
class=class="str">"cmt">//--- Get the name object_name=ObjectName(class="num">0,i,subwindow_number,OBJ_CHART); class=class="str">"cmt">//--- Set the chart object width and height ObjectSetInteger(class="num">0,object_name,OBJPROP_YSIZE,chart_height); ObjectSetInteger(class="num">0,object_name,OBJPROP_XSIZE,chart_object_width); class=class="str">"cmt">//--- Set the chart object position ObjectSetInteger(class="num">0,object_name,OBJPROP_YDISTANCE,class="num">0); ObjectSetInteger(class="num">0,object_name,OBJPROP_XDISTANCE,x_distance); class=class="str">"cmt">//--- Set the new X-coordinate for the next chart object x_distance+=chart_object_width; } } }
◍ 把工具请下神坛
这套多周期同窗监控的指标,源码已经随文放出:subwindow.mq5 仅 1.55 KB,chartobjects.mq5 为 39.85 KB,直接拖进 MT5 的 MQL5/Indicators 目录就能编译跑通。 原文留了两个没做完的活儿:切换主图品种时,让子窗口里的符号跟着联动;以及把 TF 面板上的周期从左至右按低到高排好序。这两个功能在附带的版本里都还没接上,想练手就自己补。 外汇和贵金属波动剧烈、杠杆高风险大,这类面板只是看盘辅助,别当成信号神器。打开 MT5 把源码下下来,先改一改品种联动的逻辑,比盯着现成指标更有用。