MQL5交易工具(第三部分):构建用于策略交易的多时间周期扫描仪表盘·进阶篇
「面板尺寸与多周期扫描的底层常量」
做 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 线。外汇与贵金属波动剧烈,这类面板只是辅助,信号失效概率不低,仓位仍须自担风险。
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 个字符,纯属面板显示用,不影响计分。真要改的是上面那些数字,调紧一点分数更难凑满,调松一点满分会频繁闪。
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 策略测试器里改那个 3 或 2,就能直接看到不同阈值下外汇或贵金属品种的信号频次变化,属于高杠杆环境下的概率游戏,务必用小资金验证。
下面这段负责在图表右上角画一个 UI 矩形,用的是 OBJ_RECTANGLE_LABEL 脱离时间轴的标签对象。函数先 ResetLastError() 清错误码,再尝试 ObjectCreate,失败就打印函数名加错误码并返回 false,成功才继续设坐标、尺寸和样式。
注意它把锚点设在 CORNER_RIGHT_UPPER,也就是图表右上角,且 OBJPROP_BACK 设为 false 让面板浮在 K 线之上。外汇和贵金属波动快,这种常驻右上角的轻量面板比左下角更不容易被价格扫到,但实际遮挡范围你得按自己屏幕分辨率调 x_size / y_size。
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 只做信息呈现,不参与下单决策。
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' 这几个色值看看对比度是否顺眼。
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 就能改行距;外汇与贵金属波动剧烈,这类面板只作概率参考,实盘仍需自担高风险。
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