MQL5 细则手册:指标子窗口控件 - 滚动条·进阶篇
(2/3)· 接上篇按钮控件,本文用 CCanvas 与 OBJ_RECTANGLE_LABEL 在指标子窗口实现垂直滚动条,避开大量标签对象拖累性能
用位图标签承载品种属性面板
在 MT5 指标子窗口里画一块自定义画布,比用几十个 Text 对象省事得多。下面这段把画布的增、改、删三件事拆成独立函数,初始化时再挂上鼠标跟踪和字体。 AddCanvas 先查画布在不在:ObjectFind 返回小于 0 表示没找到,就用 CreateBitmapLabel 在指定子窗口 (0,0) 位置按 chart_width × subwindow_height 建一块位图标签。ResizeCanvas 则判断画布是否已归属于该子窗口(返回值等于 subwindow_number),是就直接 Resize,否则补建。DeleteCanvas 仅在 ObjectFind 大于 0 时调用 Destroy 清掉对象。 OnInit 里顺手开了 CHART_EVENT_MOUSE_MOVE,后面悬停读属性才抓得到坐标;ArrayResize 给 symbol_property_colors 和 symbol_property_values 都开了 LIST_SIZE 长度,TEXT 高度用 TextHeight("A")-1 存下来做排版基线。外汇与贵金属杠杆高,面板只是辅助,信号错了止损照样被打。
class="type">void AddCanvas() { class=class="str">"cmt">//--- If there is no canvas, add it if(ObjectFind(class="num">0,canvas_name)<class="num">0) canvas.CreateBitmapLabel(class="num">0,subwindow_number,canvas_name,class="num">0,class="num">0,chart_width,subwindow_height,color_format); } class="type">void ResizeCanvas() { class=class="str">"cmt">//--- If the canvas has already been added to the indicator subwindow, set the new size if(ObjectFind(class="num">0,canvas_name)==subwindow_number) canvas.Resize(chart_width,subwindow_height); class=class="str">"cmt">//--- If there is no canvas, add it else canvas.CreateBitmapLabel(class="num">0,subwindow_number,canvas_name,class="num">0,class="num">0,chart_width,subwindow_height,color_format); } class="type">void DeleteCanvas() { if(ObjectFind(class="num">0,canvas_name)>class="num">0) canvas.Destroy(); } class="type">int OnInit() { class=class="str">"cmt">//--- Enable tracking of mouse events ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,true); class=class="str">"cmt">//--- Set the class="type">short name IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname); class=class="str">"cmt">//--- Set sizes of arrays of symbol properties and their colors ArrayResize(symbol_property_colors,LIST_SIZE); ArrayResize(symbol_property_values,LIST_SIZE); class=class="str">"cmt">//--- Set subwindow properties SetSubwindowProperties(); class=class="str">"cmt">//--- Set the font for displaying on the canvas canvas.FontSet(font_name,font_size,FW_NORMAL); class=class="str">"cmt">//--- Save the text size(height) for calculations text_height=canvas.TextHeight("A")-class="num">1;
「画布上滚动显示品种属性的实现细节」
在指标初始化尾部,先按 text_height*LIST_SIZE 算出整张列表高度并写入 list_height,随后 AddCanvas 把画布挂到图表、ShowSymbolInfo 输出内容、ChartRedraw 强制重绘,最后 return(INIT_SUCCEEDED) 结束 OnInit。这一段决定了你打开 MT5 后能否立刻看到面板。 ShowSymbolInfo 的核心是根据滚动条位置 current_thumb_position 跳过已滚出视野的行。for 循环里用 thumb_position 累加 line_size,一旦超过当前滚动位置就 break,此时 line_number 就是可视区起始行号——这意味着 LIST_SIZE 若设 50、line_size 为 0.02,滚动到 0.4 时会从第 20 行起绘。 真正落笔在 canvas.TextOut:属性名固定在 x=655 右对齐,值从 x=665 左对齐,颜色由 symbol_property_colors[i] 控制。y_distance 每行加 text_height,当 list_lines*text_height 超出 subwindow_height 就 break,避免画到子窗口外。 InitializePropertyArrays 从 line_number 开始填 symbol_property_values 和颜色,GetStringSymbolInfoByIndex(i) 按索引抓品种字段。想验证就改 LIST_SIZE 或 line_size,看滚动时跳行是否如算式预期。外汇与贵金属波动剧烈,面板数值仅作参考,实操请自担高风险。
class=class="str">"cmt">//--- Calculate the height of the entire list list_height=text_height*LIST_SIZE; class=class="str">"cmt">//--- Add the canvas to the chart AddCanvas(); class=class="str">"cmt">//--- Display the list of symbol properties ShowSymbolInfo(); class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); class=class="str">"cmt">//--- Everything completed successfully class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Displaying current symbol information | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ShowSymbolInfo(class="type">class="kw">double current_thumb_position=class="num">0.0) { class="type">int list_lines =class="num">0; class=class="str">"cmt">// Counter of strings displayed in the canvas class="type">class="kw">double thumb_position =class="num">0.0; class=class="str">"cmt">// Position of the scroll box expressed as percentage for determining the first displayed class="type">class="kw">string class="type">int y_distance =class="num">0; class=class="str">"cmt">// For determining the coordinate of the next class="type">class="kw">string in the list class="type">int line_number =class="num">0; class=class="str">"cmt">// Number of the class="type">class="kw">string starting from which the list will be displayed class=class="str">"cmt">//--- Determine the class="type">class="kw">string starting from which the list will be displayed for(class="type">int i=class="num">0; i<LIST_SIZE; i++) { class=class="str">"cmt">//--- Count strings until you reach the one starting from which the list will be displayed if(thumb_position>=current_thumb_position) break; class=class="str">"cmt">//--- thumb_position+=line_size; line_number++; } class=class="str">"cmt">//--- Initialize list arrays from the specified class="type">class="kw">string InitializePropertyArrays(line_number); class=class="str">"cmt">//--- Clear the canvas canvas.Erase(canvas_background_color); class=class="str">"cmt">//--- Show the list on the canvas for(class="type">int i=line_number; i<LIST_SIZE; i++) { class=class="str">"cmt">//--- Property name canvas.TextOut(class="num">655,y_distance,symbol_propety_names[i]+" :",ColorToARGB(clrWhite),TA_RIGHT|TA_TOP); class=class="str">"cmt">//--- Property value canvas.TextOut(class="num">665,y_distance,symbol_property_values[i],ColorToARGB(symbol_property_colors[i]),TA_LEFT|TA_TOP); class=class="str">"cmt">//--- Calculate the coordinate for the next class="type">class="kw">string y_distance+=text_height; class=class="str">"cmt">//--- Count the number of displayed strings list_lines++; class=class="str">"cmt">//--- If you go beyond the subwindow boundaries, terminate the loop if(list_lines*text_height>subwindow_height) break; } class=class="str">"cmt">//--- Refresh the canvas canvas.Update(); } class=class="str">"cmt">//+-----------------------------------------------------------------------+ class=class="str">"cmt">//| Initializing arrays of values and their colors for the current symbol | class=class="str">"cmt">//+-----------------------------------------------------------------------+ class="type">void InitializePropertyArrays(class="type">int line_number) { class="type">int lines_count=class="num">0; class=class="str">"cmt">//--- for(class="type">int i=line_number; i<LIST_SIZE; i++) { class=class="str">"cmt">//--- Determine the value and class="type">class="kw">color of the symbol class="kw">property symbol_property_values[i]=GetStringSymbolInfoByIndex(i);
◍ 按索引取品种属性字符串的写法
在 MT5 指标里做自定义信息面板时,常需要把品种属性按索引取出来再拼成字符串显示。下面这段 GetStringSymbolInfoByIndex 用 switch 按 index 分流,分别读 session 成交、买卖挂单、成交量等整数属性。 当对应整数返回 0 时,函数给字符串填 "-",否则用 IntegerToString 转成文本。这样面板里不会出现一堆 0,视觉上更干净,也方便一眼扫到非零活跃字段。 调用方在循环里累加 lines_count,并用 lines_count*text_height 跟 subwindow_height 比较,一旦超出子窗口高度就 break,避免在窄副图里把文字画出边界。 外汇与贵金属品种波动剧烈、杠杆风险高,这类面板只做行情属性透视,不构成任何方向建议;复制代码后建议先在 EURUSD 的 M1 上验证字符高度与换行是否符合你的屏幕。
class="type">class="kw">string GetStringSymbolInfoByIndex(class="type">int index) { class="type">class="kw">string str ="-"; class="type">long l_check_value =class="num">0; class="type">class="kw">double d_check_value =class="num">0.0; class="type">class="kw">string s_check_value =""; class=class="str">"cmt">//--- class="kw">switch(index) { case class="num">0 : l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_DEALS); str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value); break; case class="num">1 : l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_BUY_ORDERS); str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value); break; case class="num">2 : l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_SELL_ORDERS); str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value); break; case class="num">3 : l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUME); str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value); break; case class="num">4 : l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUMEHIGH); str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value); break; case class="num">5 : l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUMELOW);
用 switch 把品种属性一次性读出来
在 MT5 里逐个调 SymbolInfoInteger 太碎,用 switch-case 把常用字段集中处理,代码可读性立刻上来。下面这段覆盖时间、小数位、点差、挂单簿深度、交易计算模式等字段,case 6 到 case 14 都是直接读 _Symbol 的整型属性再转字符串。
注意 case 6、13、14 都做了 l_check_value==0 判断:时间为 0 代表该品种无此项数据(例如过期时间对现券可能为 0),这时输出 '-' 而不是 1970 年的脏时间。case 10 的 BOOKDEPTH 若为 0 也同理处理。
case 11、12 调用了 TradeCalcModeToString / TradeModeToString 两个自定义(或标准库)转换函数,把枚举值翻成可读文本,省得你对着数字猜含义。直接把下面片段塞进你的信息面板 EA,编译后能在图表角落看到实时品种画像。
str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value); break; case class="num">6 : l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TIME); str=(l_check_value==class="num">0) ? "-" : TimeToString(l_check_value); break; case class="num">7 : str=IntegerToString(SymbolInfoInteger(_Symbol,SYMBOL_DIGITS)); break; case class="num">8 : str=IntegerToString(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD)); break; case class="num">9 : str=(!SymbolInfoInteger(_Symbol,SYMBOL_SPREAD_FLOAT)) ? "false" : "true"; break; case class="num">10 : l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TICKS_BOOKDEPTH); str=(l_check_value==class="num">0) ? "-" : DoubleToString(l_check_value,_Digits); break; case class="num">11 : str=TradeCalcModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_CALC_MODE)); break; case class="num">12 : str=TradeModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_MODE)); break; case class="num">13 : l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_START_TIME); str=(l_check_value==class="num">0) ? "-" : TimeToString(l_check_value); break; case class="num">14 : l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_EXPIRATION_TIME); str=(l_check_value==class="num">0) ? "-" : TimeToString(l_check_value); break; case class="num">15 :
「止损冻结与报价字段的读取分支」
这段 switch 分支专门处理交易约束类与实时报价类的合约属性。case 15 和 16 用 SymbolInfoInteger 取 SYMBOL_TRADE_STOPS_LEVEL 与 SYMBOL_TRADE_FREEZE_LEVEL,若返回 0 表示经纪商未设最小止损距或冻结距,代码将其转成字符串 "false",否则用 IntegerToString 保留具体点数。 case 17~21 则把执行模式、掉期计算方式、周三展期日、到期模式、成交填充模式等枚举值,通过自定义转换函数(如 TradeExeModeToString、SwapModeToString)映射成可读文本,方便面板直接显示。 case 22~24 转向 double 型报价:用 SymbolInfoDouble 抓 SYMBOL_BID、SYMBOL_BIDHIGH、SYMBOL_BIDLOW,等于 0 时打 "-",否则按 _Digits 精度格式化。开 MT5 把 _Symbol 换成 XAUUSD 跑一遍,能直接看到黄金当前买价与当日买价高低,外汇和贵金属波动大、杠杆高,这些字段读数偏差可能让挂单被拒。
l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); str=(l_check_value==class="num">0) ? "false" : IntegerToString(l_check_value); break; case class="num">16 : l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL); str=(l_check_value==class="num">0) ? "false" : IntegerToString(l_check_value); break; case class="num">17 : str=TradeExeModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE)); break; case class="num">18 : str=SwapModeToString(SymbolInfoInteger(_Symbol,SYMBOL_SWAP_MODE)); break; case class="num">19 : str=WeekdayToString(SymbolInfoInteger(_Symbol,SYMBOL_SWAP_ROLLOVER3DAYS)); break; case class="num">20 : str=ExpirationModeToString(); break; case class="num">21 : str=FillingModeToString(); break; class=class="str">"cmt">//--- case class="num">22 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BID); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits); break; case class="num">23 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BIDHIGH); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits); break; case class="num">24 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BIDLOW);
◍ 报价字段的逐档取值写法
这段 switch 分支把 MT5 里 25~32 号报价属性一次性铺开,核心都是用 SymbolInfoDouble 抓当前品种 _Symbol 的双精度值,再按 _Digits 精度转成字符串。 case 25 取 SYMBOL_ASK 实时卖价,case 26/27 取 SYMBOL_ASKHIGH / SYMBOL_ASKLOW 当日卖价极值;case 28~30 对应 SYMBOL_LAST 及其高低,适用于有最后成交价的品种,外汇现货常返回 0 故用 '-' 占位。 case 31 直接取 SYMBOL_POINT 点值,case 32 取 SYMBOL_TRADE_TICK_VALUE_PROFIT 并按 2 位小数显示每跳盈亏金额——这是算仓位风险时最实在的一栏。 开 MT5 按 F4 把这段塞进你的信息面板 EA,切到 XAUUSD 这类点值波动大的品种,盯 case 32 输出,能直观看到每跳盈亏随合约规模变化的差异。外汇与贵金属杠杆高,tick 价值随时变,数值仅作当下参考。
str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits); break; case class="num">25 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASK); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits); break; case class="num">26 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASKHIGH); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits); break; case class="num">27 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASKLOW); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits); break; case class="num">28 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LAST); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits); break; case class="num">29 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LASTHIGH); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits); break; case class="num">30 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LASTLOW); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits); break; case class="num">31 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_POINT),_Digits); break; case class="num">32 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_PROFIT),class="num">2); break;