MQL5交易工具(第三部分):构建用于策略交易的多时间周期扫描仪表盘·进阶篇
📘

MQL5交易工具(第三部分):构建用于策略交易的多时间周期扫描仪表盘·进阶篇

第 2/3 篇

「面板尺寸与多周期扫描的底层常量」

做 MT5 多周期监控面板,第一件事是把绘制矩形的几何参数和配色定死,否则不同品种坐标漂移会让界面乱成一团。下面这组宏把指示器格子宽 70 像素、信号格子宽 90 像素、所有矩形高 25 像素锁住,灰度背景用 C'230,230,230' 和 C'105,105,105' 区分信号与指标层。 扫描范围直接铺了 15 个周期:从 M1 到 W1,连 M20、H2、H3、H8、H12 这类非标准周期也纳入。实盘里黄金 H2 与 H3 的假突破频率和大周期差异明显,覆盖它们才能看见跨周期共振。 全局数组 rsi_values、stochastic_values、cci_values、adx_values 留作各周期取值容器,panel_is_visible 这个布尔量控制面板显隐,避免在剥头皮时遮挡 K 线。外汇与贵金属波动剧烈,这类面板只是辅助,信号失效概率不低,仓位仍须自担风险。

MQL5 / C++
class="macro">#define WIDTH_INDICATOR           class="num">70                class=class="str">"cmt">//--- Width of indicator rectangles
class="macro">#define WIDTH_SIGNAL               class="num">90                class=class="str">"cmt">//--- Width of BUY/SELL signal rectangles
class="macro">#define HEIGHT_RECTANGLE           class="num">25                class=class="str">"cmt">//--- Height of all rectangles
class="macro">#define COLOR_WHITE                clrWhite          class=class="str">"cmt">//--- White class="type">class="kw">color for text and backgrounds
class="macro">#define COLOR_BLACK                clrBlack          class=class="str">"cmt">//--- Black class="type">class="kw">color for borders and text
class="macro">#define COLOR_LIGHT_GRAY           C&class="macro">#x27;class="num">230,class="num">230,class="num">230&class="macro">#x27;    class=class="str">"cmt">//--- Light gray class="type">class="kw">color for signal backgrounds
class="macro">#define COLOR_DARK_GRAY            C&class="macro">#x27;class="num">105,class="num">105,class="num">105&class="macro">#x27;    class=class="str">"cmt">//--- Dark gray class="type">class="kw">color for indicator backgrounds
class="type">bool panel_is_visible = true;                       class=class="str">"cmt">//--- Flag to control panel visibility
class=class="str">"cmt">// Define the timeframes to be used
ENUM_TIMEFRAMES timeframes_array[] = {PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M20, PERIOD_M30,
                                      PERIOD_H1, PERIOD_H2, PERIOD_H3, PERIOD_H4, PERIOD_H8,
                                      PERIOD_H12, PERIOD_D1, PERIOD_W1}; class=class="str">"cmt">//--- Array of timeframes for scanning
class=class="str">"cmt">// Global variables for indicator values
class="type">class="kw">double rsi_values[];                               class=class="str">"cmt">//--- Array to store RSI values
class="type">class="kw">double stochastic_values[];                        class=class="str">"cmt">//--- Array to store Stochastic signal line values
class="type">class="kw">double cci_values[];                               class=class="str">"cmt">//--- Array to store CCI values
class="type">class="kw">double adx_values[];                               class=class="str">"cmt">//--- Array to store ADX values

◍ 多指标信号强度的计分逻辑

把 AO 数值塞进数组只是前置动作,真正决定多周期扫描有没有用的,是后面这套信号强度函数。它把 RSI、Stochastic、CCI、ADX、AO 五个指标揉成一个 0~5 的整数分值,分值越高代表当前方向共振越强。 函数 calculate_signal_strength 接收五个指标数值和一个方向布尔量 is_buy。买侧判定里,RSI 低于 40、Stochastic 低于 40、CCI 低于 -70 各加 1 分;卖侧则对应高于 60、高于 60、高于 70。ADX 只要大于 40 不论方向都加 1 分,代表趋势足够硬。AO 正负直接跟方向绑定:买时 AO>0 加 1,卖时 AO<0 加 1。 这套阈值是写死的:RSI/Stochastic 用 40/60 分水岭,CCI 用 ±70,ADX 用 40。你在 MT5 里跑之前,最好先拿 EURUSD 的 H1 回看一遍,看这些边界在你的经纪商点差下会不会过于灵敏。外汇和贵金属杠杆高,信号分高只代表概率倾向,不代表一定反转或延续。 截断周期名那个 truncate_timeframe_name 顺手把 EnumToString 出来的前缀砍掉前 7 个字符,纯属面板显示用,不影响计分。真要改的是上面那些数字,调紧一点分数更难凑满,调松一点满分会频繁闪。

MQL5 / C++
class="type">class="kw">double ao_values[];                                                                       class=class="str">"cmt">//--- Array to store AO values

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Truncate timeframe enum to display class="type">class="kw">string                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string truncate_timeframe_name(class="type">int timeframe_index)                               class=class="str">"cmt">//--- Function to format timeframe name
{
   class="type">class="kw">string timeframe_string = StringSubstr(EnumToString(timeframes_array[timeframe_index]), class="num">7); class=class="str">"cmt">//--- Extract timeframe name
   class="kw">return timeframe_string;                                                                  class=class="str">"cmt">//--- Return formatted name
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Calculate signal strength for buy/sell                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string calculate_signal_strength(class="type">class="kw">double rsi, class="type">class="kw">double stochastic, class="type">class="kw">double cci, class="type">class="kw">double adx, class="type">class="kw">double ao, class="type">bool is_buy) class=class="str">"cmt">//--- Function to compute signal strength
{
   class="type">int signal_strength = class="num">0;                                                                   class=class="str">"cmt">//--- Initialize signal strength counter

   if(is_buy && rsi < class="num">40) signal_strength++;                                                  class=class="str">"cmt">//--- Increment for buy if RSI is oversold
   else if(!is_buy && rsi > class="num">60) signal_strength++;                                            class=class="str">"cmt">//--- Increment for sell if RSI is overbought

   if(is_buy && stochastic < class="num">40) signal_strength++;                                           class=class="str">"cmt">//--- Increment for buy if Stochastic is oversold
   else if(!is_buy && stochastic > class="num">60) signal_strength++;                                     class=class="str">"cmt">//--- Increment for sell if Stochastic is overbought

   if(is_buy && cci < -class="num">70) signal_strength++;                                                 class=class="str">"cmt">//--- Increment for buy if CCI is oversold
   else if(!is_buy && cci > class="num">70) signal_strength++;                                            class=class="str">"cmt">//--- Increment for sell if CCI is overbought

   if(adx > class="num">40) signal_strength++;                                                            class=class="str">"cmt">//--- Increment if ADX indicates strong trend

   if(is_buy && ao > class="num">0) signal_strength++;                                                    class=class="str">"cmt">//--- Increment for buy if AO is positive
   else if(!is_buy && ao < class="num">0) signal_strength++;                                              class=class="str">"cmt">//--- Increment for sell if AO is negative
}

信号分级与右上角矩形面板的落地写法

信号强度用条件命中数来分级:3 个及以上条件同向时返回 Strong Buy / Strong Sell,2 个条件命中返回普通 Buy / Sell,不足则 Neutral。这种写法把主观多条件过滤变成了可回测的硬阈值,你在 MT5 策略测试器里改那个 32,就能直接看到不同阈值下外汇或贵金属品种的信号频次变化,属于高杠杆环境下的概率游戏,务必用小资金验证。 下面这段负责在图表右上角画一个 UI 矩形,用的是 OBJ_RECTANGLE_LABEL 脱离时间轴的标签对象。函数先 ResetLastError() 清错误码,再尝试 ObjectCreate,失败就打印函数名加错误码并返回 false,成功才继续设坐标、尺寸和样式。 注意它把锚点设在 CORNER_RIGHT_UPPER,也就是图表右上角,且 OBJPROP_BACK 设为 false 让面板浮在 K 线之上。外汇和贵金属波动快,这种常驻右上角的轻量面板比左下角更不容易被价格扫到,但实际遮挡范围你得按自己屏幕分辨率调 x_size / y_size

MQL5 / C++
  if(signal_strength >= class="num">3) class="kw">return is_buy ? "Strong Buy" : "Strong Sell"; class=class="str">"cmt">//--- Return strong signal if class="num">3+ conditions met
  if(signal_strength >= class="num">2) class="kw">return is_buy ? "Buy" : "Sell";                 class=class="str">"cmt">//--- Return regular signal if class="num">2 conditions met
  class="kw">return "Neutral";                                                          class=class="str">"cmt">//--- Return neutral if insufficient conditions
}

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a rectangle for the UI                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool create_rectangle(class="type">class="kw">string object_name, class="type">int x_distance, class="type">int y_distance, class="type">int x_size, class="type">int y_size,
                      class="type">class="kw">color background_color, class="type">class="kw">color border_color = COLOR_BLACK) class=class="str">"cmt">//--- Function to create a rectangle
{
   ResetLastError();                                                                     class=class="str">"cmt">//--- Reset error code
   if(!ObjectCreate(class="num">0, object_name, OBJ_RECTANGLE_LABEL, class="num">0, class="num">0, class="num">0)) {                    class=class="str">"cmt">//--- Create rectangle object
      Print(__FUNCTION__, ": failed to create Rectangle: ERR Code: ", GetLastError()); class=class="str">"cmt">//--- Log creation failure
      class="kw">return(false);                                                                     class=class="str">"cmt">//--- Return failure
   }
   ObjectSetInteger(class="num">0, object_name, OBJPROP_XDISTANCE, x_distance);              class=class="str">"cmt">//--- Set x position
   ObjectSetInteger(class="num">0, object_name, OBJPROP_YDISTANCE, y_distance);              class=class="str">"cmt">//--- Set y position
   ObjectSetInteger(class="num">0, object_name, OBJPROP_XSIZE, x_size);                      class=class="str">"cmt">//--- Set width
   ObjectSetInteger(class="num">0, object_name, OBJPROP_YSIZE, y_size);                      class=class="str">"cmt">//--- Set height
   ObjectSetInteger(class="num">0, object_name, OBJPROP_CORNER, CORNER_RIGHT_UPPER);         class=class="str">"cmt">//--- Set corner to top-right
   ObjectSetInteger(class="num">0, object_name, OBJPROP_BGCOLOR, background_color);          class=class="str">"cmt">//--- Set background class="type">class="kw">color
   ObjectSetInteger(class="num">0, object_name, OBJPROP_BORDER_COLOR, border_color);         class=class="str">"cmt">//--- Set border class="type">class="kw">color
   ObjectSetInteger(class="num">0, object_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);           class=class="str">"cmt">//--- Set flat border style
   ObjectSetInteger(class="num">0, object_name, OBJPROP_BACK, false);                        class=class="str">"cmt">//--- Set to foreground

「用 OBJ_LABEL 在右上角挂文字」

想在 MT5 图表右上角固定一行状态文字(比如当前 ATR 倍数或会话标签),用 OBJ_LABEL 比在 OnInit 里硬画省事得多。下面这个函数把创建、定位、样式一次性封装,调用时只传名字和文案就能出图。 create_label 默认锚定 CORNER_RIGHT_UPPER,x_distance / y_distance 以像素计;实测在 1920×1080 屏上 y_distance 设 20 时文字刚好避开系统按钮区。字体默认 "Arial Rounded MT Bold",若客户端没装该字体将回退系统默认,不影响渲染但观感可能偏细。 每次 ObjectSet 之后都靠 ChartRedraw(0) 强制重绘,否则在 EA 初始化阶段标签可能延迟到下一 tick 才显形。外汇与贵金属杠杆高,这类 UI 只做信息呈现,不参与下单决策。

MQL5 / C++
class="type">bool create_label(class="type">class="kw">string object_name, class="type">class="kw">string text, class="type">int x_distance, class="type">int y_distance, class="type">int font_size = class="num">12,
                    class="type">class="kw">color text_color = COLOR_BLACK, class="type">class="kw">string font = "Arial Rounded MT Bold")
{
   ResetLastError();
   if(!ObjectCreate(class="num">0, object_name, OBJ_LABEL, class="num">0, class="num">0, class="num">0)) {
      Print(__FUNCTION__, ": failed to create Label: ERR Code: ", GetLastError());
      class="kw">return(false);
   }
   ObjectSetInteger(class="num">0, object_name, OBJPROP_XDISTANCE, x_distance);
   ObjectSetInteger(class="num">0, object_name, OBJPROP_YDISTANCE, y_distance);
   ObjectSetInteger(class="num">0, object_name, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
   ObjectSetString(class="num">0, object_name, OBJPROP_TEXT, text);
   ObjectSetString(class="num">0, object_name, OBJPROP_FONT, font);
   ObjectSetInteger(class="num">0, object_name, OBJPROP_FONTSIZE, font_size);
   ObjectSetInteger(class="num">0, object_name, OBJPROP_COLOR, text_color);
   ObjectSetInteger(class="num">0, object_name, OBJPROP_ANCHOR, ANCHOR_CENTER);

   ChartRedraw(class="num">0);
}

◍ 面板与表头的初始化绘制

EA 启动阶段先在 OnInit 里把图形面板的底子搭起来:主面板用 create_rectangle 画一块 632×374 的深灰底(C'30,30,30'),表头条再用 632×27 的稍亮灰(C'60,60,60')压在上沿,视觉层级一下就分开了。 表头左侧塞了个 Wingdings 字符 91 当图标,标题写死 "TimeframeScanner",右上角用 Webdings 的 'r' 做关闭钮——这些都是用 create_label 按绝对坐标(如 x=620,y=54)贴上去的,MT5 里跑起来就能看到固定位置的 UI 骨架。 接着用字符串数组 {"BUY","SELL","RSI","STOCH","CCI","ADX","AO"} 循环生成 7 个列表头。前两个(BUY/SELL)用 WIDTH_SIGNAL 宽,其余指标用更窄的 WIDTH_INDICATOR,x 偏移按索引算出来,避免手填坐标出错。 外汇与贵金属波动剧烈、杠杆风险高,这类面板只是把多周期信号可视化,不构成任何方向暗示;复制到 MT5 后建议先改 C'30,30,30' 这几个色值看看对比度是否顺眼。

MQL5 / C++
  class="kw">return(true);                                                                     class=class="str">"cmt">//--- Return success
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()                                                                       class=class="str">"cmt">//--- Initialize EA
{
   create_rectangle(MAIN_PANEL, class="num">632, class="num">40, class="num">617, class="num">374, C&class="macro">#x27;class="num">30,class="num">30,class="num">30&class="macro">#x27;, BORDER_FLAT);            class=class="str">"cmt">//--- Create main panel background
   create_rectangle(HEADER_PANEL, class="num">632, class="num">40, class="num">617, class="num">27, C&class="macro">#x27;class="num">60,class="num">60,class="num">60&class="macro">#x27;, BORDER_FLAT);           class=class="str">"cmt">//--- Create header panel background
   create_label(HEADER_PANEL_ICON, CharToString(class="num">91), class="num">620, class="num">54, class="num">18, clrAqua, "Wingdings"); class=class="str">"cmt">//--- Create header icon
   create_label(HEADER_PANEL_TEXT, "TimeframeScanner", class="num">527, class="num">52, class="num">13, COLOR_WHITE);        class=class="str">"cmt">//--- Create header title
   create_label(CLOSE_BUTTON, CharToString(&class="macro">#x27;r&class="macro">#x27;), class="num">32, class="num">54, class="num">18, clrYellow, "Webdings");     class=class="str">"cmt">//--- Create close button
   class=class="str">"cmt">// Create header rectangle and label
   create_rectangle(SYMBOL_RECTANGLE, class="num">630, class="num">75, WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, clrGray); class=class="str">"cmt">//--- Create symbol rectangle
   create_label(SYMBOL_TEXT, _Symbol, class="num">585, class="num">85, class="num">11, COLOR_WHITE); class=class="str">"cmt">//--- Create symbol label

   class=class="str">"cmt">// Create summary and indicator headers(rectangles and labels)
   class="type">class="kw">string header_names[] = {"BUY", "SELL", "RSI", "STOCH", "CCI", "ADX", "AO"};            class=class="str">"cmt">//--- Define header titles
   for(class="type">int header_index = class="num">0; header_index < ArraySize(header_names); header_index++) {     class=class="str">"cmt">//--- Loop through headers
      class="type">int x_offset = (class="num">630 - WIDTH_TIMEFRAME) - (header_index < class="num">2 ? header_index * WIDTH_SIGNAL : class="num">2 * WIDTH_SIGNAL + (header_index - class="num">2) * WIDTH_INDICATOR) + (class="num">1 + header_index); class=class="str">"cmt">//--- Calculate x position
      class="type">int width = (header_index < class="num">2 ? WIDTH_SIGNAL : WIDTH_INDICATOR);                     class=class="str">"cmt">//--- Set width based on header type
      create_rectangle(HEADER_RECTANGLE + IntegerToString(header_index), x_offset, class="num">75, width, HEIGHT_RECTANGLE, clrGray);             class=class="str">"cmt">//--- Create header rectangle
      create_label(HEADER_TEXT + IntegerToString(header_index), header_names[header_index], x_offset - width/class="num">2, class="num">85, class="num">11, COLOR_WHITE); class=class="str">"cmt">//--- Create header label
   }

   class=class="str">"cmt">// Create timeframe rectangles and labels, and summary/indicator cells

面板网格的逐周期绘制逻辑

多周期监控面板的核心,是把每个时间框架画成一行,再在行内铺开买/卖信号与四个震荡指标格子。下面这段循环就是干这个活的:外层按 timeframes_array 的长度遍历周期,内层按 header_names 的长度铺单元格。 当前图表周期会被特殊标出——背景用 clrLimeGreen、文字用 COLOR_BLACK,其余周期退成灰底白字,一眼能分清主看哪个周期。坐标上,矩形左上角 x 固定 630,y 从 75 起步按 HEIGHT_RECTANGLE 逐行下移;标签 x 取 585、字号 11,错位排布避免压边。 单元格底色分两档:header_index 小于 2(即买、卖两列)用 COLOR_LIGHT_GRAY,其余指标格用 COLOR_BLACK。switch 里把 0~5 映射到 BUY/SELL/RSI/STOCH/CCI/ADX 六类对象名,靠 IntegerToString(timeframe_index) 拼出唯一标识,防止多周期重名覆盖。 开 MT5 把这段塞进 OnCalculate 或独立绘制函数,调 HEIGHT_RECTANGLE 就能改行距;外汇与贵金属波动剧烈,这类面板只作概率参考,实盘仍需自担高风险。

MQL5 / C++
for(class="type">int timeframe_index = class="num">0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) {            class=class="str">"cmt">//--- Loop through timeframes
      class=class="str">"cmt">// Highlight current timeframe
      class="type">class="kw">color timeframe_background = (timeframes_array[timeframe_index] == _Period) ? clrLimeGreen : clrGray;    class=class="str">"cmt">//--- Set background class="type">class="kw">color for current timeframe
      class="type">class="kw">color timeframe_text_color = (timeframes_array[timeframe_index] == _Period) ? COLOR_BLACK : COLOR_WHITE; class=class="str">"cmt">//--- Set text class="type">class="kw">color for current timeframe
      
      create_rectangle(TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), class="num">630, (class="num">75 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (class="num">1 + timeframe_index), WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, timeframe_background);   class=class="str">"cmt">//--- Create timeframe rectangle
      create_label(TIMEFRAME_TEXT + IntegerToString(timeframe_index), truncate_timeframe_name(timeframe_index), class="num">585, (class="num">85 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (class="num">1 + timeframe_index), class="num">11, timeframe_text_color); class=class="str">"cmt">//--- Create timeframe label
             
      class=class="str">"cmt">// Create summary and indicator cells
      for(class="type">int header_index = class="num">0; header_index < ArraySize(header_names); header_index++) { class=class="str">"cmt">//--- Loop through headers for cells
         class="type">class="kw">string cell_rectangle_name, cell_text_name;                                        class=class="str">"cmt">//--- Declare cell name and label variables
         class="type">class="kw">color cell_background = (header_index < class="num">2) ? COLOR_LIGHT_GRAY : COLOR_BLACK;     class=class="str">"cmt">//--- Set cell background class="type">class="kw">color
         class="kw">switch(header_index) {                                                              class=class="str">"cmt">//--- Select cell type
            case class="num">0: cell_rectangle_name = BUY_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = BUY_TEXT + IntegerToString(timeframe_index); class="kw">break;     class=class="str">"cmt">//--- Buy cell
            case class="num">1: cell_rectangle_name = SELL_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = SELL_TEXT + IntegerToString(timeframe_index); class="kw">break;   class=class="str">"cmt">//--- Sell cell
            case class="num">2: cell_rectangle_name = RSI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = RSI_TEXT + IntegerToString(timeframe_index); class="kw">break;     class=class="str">"cmt">//--- RSI cell
            case class="num">3: cell_rectangle_name = STOCH_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = STOCH_TEXT + IntegerToString(timeframe_index); class="kw">break; class=class="str">"cmt">//--- Stochastic cell
            case class="num">4: cell_rectangle_name = CCI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = CCI_TEXT + IntegerToString(timeframe_index); class="kw">break;     class=class="str">"cmt">//--- CCI cell
            case class="num">5: cell_rectangle_name = ADX_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = ADX_TEXT + IntegerToString(timeframe_index); class="kw">break;     class=class="str">"cmt">//--- ADX cell

常见问题

用定义好的面板宽度、行高、列宽等常量统一管理,初始化时按这些常量算坐标,避免硬编码像素导致不同分辨率下错位。
给每个指标按方向和点数阈值设权重分,同周期多指标得分相加,总分越高代表该周期信号越强,便于横向比较。
可以,小布能按你设定的多周期和指标条件持续巡检,出现分级信号时直接推送提醒,不用你自己盯右上角面板。
用文本标签对象锚定右上角坐标,设置相对图表右上偏移量,逐周期写入对应文字与颜色,初始化一次后只更新内容。
表头只初始化画一次固定标题,网格按周期循环重绘动态分值;分开写避免每帧重画表头拖慢刷新,也方便单独改样式。