MQL5交易工具(第三部分):构建用于策略交易的多时间周期扫描仪表盘
概述
在先前第二部分中,我们为MetaQuotes Language 5(MQL5)中的交易助手工具增添了动态视觉反馈功能,以提升其交互性。现在,我们将聚焦于构建一个多时间周期扫描仪表盘,为策略决策提供实时交易信号。我们引入一个基于网格的界面,该界面包含由指标驱动的信号以及一个关闭按钮,并通过以下子主题阐述这些改进:
这些章节将带领我们打造一个直观且功能强大的交易仪表盘。
扫描仪表盘方案
我们旨在构建一个多时间周期扫描仪表盘,提供清晰、实时的交易信号,从而提升策略决策能力。该仪表盘将采用网格布局,展示多个时间框架的买卖信号,使我们无需切换图表即可快速评估市场状况。此外,还将设置一个关闭按钮,以便轻松关闭面板,确保用户获得简洁、灵活的体验,满足我们的交易需求。
我们将整合相对强弱指数(RSI)、随机振荡指标(STOCH)、商品通道指数(CCI)、平均趋向指数(ADX)以及动量振荡指标(AO)等关键指标信号,这些指标旨在通过可自定义的强度阈值,帮助识别潜在的交易机会。然而,具体使用哪些指标或价格行为数据,由您自行决定。这种设置将帮助我们跨时间周期发现趋势和反转,同时支持短期和长期交易策略。我们的目标是打造一款精简、直观的工具,既能提供可操作的见解,又易于使用,为未来实现自动提醒或添加更多指标等功能升级铺平道路。以下是我们期望达成的效果图示。

在MQL5中的实现
要在MQL5中创建该程序,我们需要先定义程序元数据,然后定义一些对象名称常量,这些常量将有助于我们引用仪表盘对象并轻松管理它们。
//+------------------------------------------------------------------+ //| TimeframeScanner Dashboard EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" // Define identifiers and properties for UI elements #define MAIN_PANEL "PANEL_MAIN" //--- Main panel rectangle identifier #define HEADER_PANEL "PANEL_HEADER" //--- Header panel rectangle identifier #define HEADER_PANEL_ICON "PANEL_HEADER_ICON" //--- Header icon label identifier #define HEADER_PANEL_TEXT "PANEL_HEADER_TEXT" //--- Header title label identifier #define CLOSE_BUTTON "BUTTON_CLOSE" //--- Close button identifier #define SYMBOL_RECTANGLE "SYMBOL_HEADER" //--- Symbol rectangle identifier #define SYMBOL_TEXT "SYMBOL_TEXT" //--- Symbol text label identifier #define TIMEFRAME_RECTANGLE "TIMEFRAME_" //--- Timeframe rectangle prefix #define TIMEFRAME_TEXT "TIMEFRAME_TEXT_" //--- Timeframe text label prefix #define HEADER_RECTANGLE "HEADER_" //--- Header rectangle prefix #define HEADER_TEXT "HEADER_TEXT_" //--- Header text label prefix #define RSI_RECTANGLE "RSI_" //--- RSI rectangle prefix #define RSI_TEXT "RSI_TEXT_" //--- RSI text label prefix #define STOCH_RECTANGLE "STOCH_" //--- Stochastic rectangle prefix #define STOCH_TEXT "STOCH_TEXT_" //--- Stochastic text label prefix #define CCI_RECTANGLE "CCI_" //--- CCI rectangle prefix #define CCI_TEXT "CCI_TEXT_" //--- CCI text label prefix #define ADX_RECTANGLE "ADX_" //--- ADX rectangle prefix #define ADX_TEXT "ADX_TEXT_" //--- ADX text label prefix #define AO_RECTANGLE "AO_" //--- AO rectangle prefix #define AO_TEXT "AO_TEXT_" //--- AO text label prefix #define BUY_RECTANGLE "BUY_" //--- Buy rectangle prefix #define BUY_TEXT "BUY_TEXT_" //--- Buy text label prefix #define SELL_RECTANGLE "SELL_" //--- Sell rectangle prefix #define SELL_TEXT "SELL_TEXT_" //--- Sell text label prefix #define WIDTH_TIMEFRAME 90 //--- Width of timeframe and symbol rectangles #define WIDTH_INDICATOR 70 //--- Width of indicator rectangles #define WIDTH_SIGNAL 90 //--- Width of BUY/SELL signal rectangles #define HEIGHT_RECTANGLE 25 //--- Height of all rectangles #define COLOR_WHITE clrWhite //--- White color for text and backgrounds #define COLOR_BLACK clrBlack //--- Black color for borders and text #define COLOR_LIGHT_GRAY C'230,230,230' //--- Light gray color for signal backgrounds #define COLOR_DARK_GRAY C'105,105,105' //--- Dark gray color for indicator backgrounds
我们首先使用#define指令为多时间周期扫描仪表盘搭建用户界面框架,创建诸如“MAIN_PANEL”和“HEADER_PANEL”等常量,用于表示主面板和标题面板的矩形区域,同时用“HEADER_PANEL_ICON”、“HEADER_PANEL_TEXT”和“CLOSE_BUTTON”等常量来标识标题面板的图标、标题和关闭按钮元素。
我们为仪表盘的网格结构定义标识符。对于交易品种,我们设置“SYMBOL_RECTANGLE”和“SYMBOL_TEXT”;"TIMEFRAME_RECTANGLE"和"TIMEFRAME_TEXT"前缀则用于标识时间周期行。我们使用“HEADER_RECTANGLE”和“HEADER_TEXT”前缀来标识列标题,并使用“RSI_RECTANGLE”、“STOCH_RECTANGLE”、“BUY_RECTANGLE”等前缀,以及对应的“RSI_TEXT”、“STOCH_TEXT”和“BUY_TEXT”来标识指标和信号单元格。
我们配置尺寸参数,包括“WIDTH_TIMEFRAME”(90像素)、“WIDTH_INDICATOR”(70像素)、“WIDTH_SIGNAL”(90像素)以及“HEIGHT_RECTANGLE”(25像素)。我们通过"COLOR_WHITE"和"COLOR_BLACK"定义文本和边框颜色,将信号背景设置为"COLOR_LIGHT_GRAY"(“C'230,230,230'”),指标背景设为"COLOR_DARK_GRAY"(“C'105,105,105'”),以确保布局统一清晰。接下来,我们还需要在程序中定义一些全局变量。
bool panel_is_visible = true; //--- Flag to control panel visibility // 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}; //--- Array of timeframes for scanning // Global variables for indicator values double rsi_values[]; //--- Array to store RSI values double stochastic_values[]; //--- Array to store Stochastic signal line values double cci_values[]; //--- Array to store CCI values double adx_values[]; //--- Array to store ADX values double ao_values[]; //--- Array to store AO values
在此阶段,我们声明一个布尔型变量“panel_is_visible”,并将其初始值设置为“true”,用于确定仪表盘是否显示在图表上。这个标识位使我们能够根据需要切换仪表盘的可见性,特别是在我们不需要数据更新时。接下来,我们使用ENUM_TIMEFRAMES 类型定义数组“timeframes_array”,该数组列出了从“PERIOD_M1”(1分钟)到“PERIOD_W1”(周线)的时间周期。该数组指定了仪表盘将要分析的时间周期,使我们能够以结构化的方式跨多个时间范围扫描市场信号。如果您不需要某些时间周期,或者需要调整它们,只需修改枚举值即可。
为了存储指标数据,我们创建了双精度数组“rsi_values”、“stochastic_values”、“cci_values”、“adx_values”和“ao_values”。这些数组分别用于存储RSI、STOCH、CCI、ADI和AO的计算值,使我们能够高效地处理并显示每个时间周期的交易信号。现在,我们可以定义一些辅助函数,用于确定所获取图表交易品种的方向和截断方式,具体如下:
//+------------------------------------------------------------------+ //| Truncate timeframe enum to display string | //+------------------------------------------------------------------+ string truncate_timeframe_name(int timeframe_index) //--- Function to format timeframe name { string timeframe_string = StringSubstr(EnumToString(timeframes_array[timeframe_index]), 7); //--- Extract timeframe name return timeframe_string; //--- Return formatted name } //+------------------------------------------------------------------+ //| Calculate signal strength for buy/sell | //+------------------------------------------------------------------+ string calculate_signal_strength(double rsi, double stochastic, double cci, double adx, double ao, bool is_buy) //--- Function to compute signal strength { int signal_strength = 0; //--- Initialize signal strength counter if(is_buy && rsi < 40) signal_strength++; //--- Increment for buy if RSI is oversold else if(!is_buy && rsi > 60) signal_strength++; //--- Increment for sell if RSI is overbought if(is_buy && stochastic < 40) signal_strength++; //--- Increment for buy if Stochastic is oversold else if(!is_buy && stochastic > 60) signal_strength++; //--- Increment for sell if Stochastic is overbought if(is_buy && cci < -70) signal_strength++; //--- Increment for buy if CCI is oversold else if(!is_buy && cci > 70) signal_strength++; //--- Increment for sell if CCI is overbought if(adx > 40) signal_strength++; //--- Increment if ADX indicates strong trend if(is_buy && ao > 0) signal_strength++; //--- Increment for buy if AO is positive else if(!is_buy && ao < 0) signal_strength++; //--- Increment for sell if AO is negative if(signal_strength >= 3) return is_buy ? "Strong Buy" : "Strong Sell"; //--- Return strong signal if 3+ conditions met if(signal_strength >= 2) return is_buy ? "Buy" : "Sell"; //--- Return regular signal if 2 conditions met return "Neutral"; //--- Return neutral if insufficient conditions }
在此阶段,我们定义了“truncate_timeframe_name”函数,该函数接受一个整型参数“timeframe_index”,用于格式化时间周期名称以供显示。在内部,我们使用StringSubstr函数从“EnumToString”函数对“timeframes_array[timeframe_index]”转换后的结果中提取子字符串,从第7个位置开始提取,并将其存储在“timeframe_string”中。接下来,我们返回“timeframe_string”,提供一个简洁、用户可读的时间周期名称。
我们创建了“calculate_signal_strength”函数,用于根据指标值判断买入或卖出信号。将整型变量“signal_strength”初始化为0,用于统计符合条件的信号数量。对于相对强弱指数,如果“is_buy”为true且“rsi”值低于40(超卖状态),或“is_buy”为false且“rsi”值超过60(超买状态),则“signal_strength”加1。同理,我们检查“stochastic”指标(低于40或高于60)、“CCI”指标(低于-70或高于70)以及“ao”指标(正值对应买入,负值对应卖出),每当满足一个条件时,“signal_strength”就增加1。
我们还对ADX进行评估,如果“adx”的值超过40,表明趋势强劲,无论买入还是卖出场景,“signal_strength”均加1。如果“signal_strength”达到3或以上,当“is_buy”为true时返回“强烈买入”,否则返回“强烈卖出”。如果“signal_strength”为2,则返回“买入”或“卖出”;反之,如果小于2,则返回“中性”,从而为仪表盘提供清晰的信号分类。最后,我们现在可以定义创建对象所需的函数了。
//+------------------------------------------------------------------+ //| Create a rectangle for the UI | //+------------------------------------------------------------------+ bool create_rectangle(string object_name, int x_distance, int y_distance, int x_size, int y_size, color background_color, color border_color = COLOR_BLACK) //--- Function to create a rectangle { ResetLastError(); //--- Reset error code if(!ObjectCreate(0, object_name, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle object Print(__FUNCTION__, ": failed to create Rectangle: ERR Code: ", GetLastError()); //--- Log creation failure return(false); //--- Return failure } ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_distance); //--- Set x position ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_distance); //--- Set y position ObjectSetInteger(0, object_name, OBJPROP_XSIZE, x_size); //--- Set width ObjectSetInteger(0, object_name, OBJPROP_YSIZE, y_size); //--- Set height ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_RIGHT_UPPER); //--- Set corner to top-right ObjectSetInteger(0, object_name, OBJPROP_BGCOLOR, background_color); //--- Set background color ObjectSetInteger(0, object_name, OBJPROP_BORDER_COLOR, border_color); //--- Set border color ObjectSetInteger(0, object_name, OBJPROP_BORDER_TYPE, BORDER_FLAT); //--- Set flat border style ObjectSetInteger(0, object_name, OBJPROP_BACK, false); //--- Set to foreground ChartRedraw(0); //--- Redraw chart return(true); //--- Return success } //+------------------------------------------------------------------+ //| Create a text label for the UI | //+------------------------------------------------------------------+ bool create_label(string object_name, string text, int x_distance, int y_distance, int font_size = 12, color text_color = COLOR_BLACK, string font = "Arial Rounded MT Bold") //--- Function to create a label { ResetLastError(); //--- Reset error code if(!ObjectCreate(0, object_name, OBJ_LABEL, 0, 0, 0)) { //--- Create label object Print(__FUNCTION__, ": failed to create Label: ERR Code: ", GetLastError()); //--- Log creation failure return(false); //--- Return failure } ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_distance); //--- Set x position ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_distance); //--- Set y position ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_RIGHT_UPPER); //--- Set corner to top-right ObjectSetString(0, object_name, OBJPROP_TEXT, text); //--- Set label text ObjectSetString(0, object_name, OBJPROP_FONT, font); //--- Set font ObjectSetInteger(0, object_name, OBJPROP_FONTSIZE, font_size); //--- Set font size ObjectSetInteger(0, object_name, OBJPROP_COLOR, text_color); //--- Set text color ObjectSetInteger(0, object_name, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Center text ChartRedraw(0); //--- Redraw chart return(true); //--- Return success }
为支持对象的创建,我们定义了“create_rectangle”函数,该函数接受以下参数:“object_name”、“x_distance”、“y_distance”、“x_size”、“y_size”、“background_color”和“border_color”。我们使用ResetLastError函数重置错误状态,通过ObjectCreate函数创建一个OBJ_RECTANGLE_LABEL(矩形标签对象),如果创建失败则使用“Print”函数记录错误信息,并返回false。
我们使用ObjectSetInteger函数设置矩形对象的属性,包括位置、尺寸、"CORNER_RIGHT_UPPER"(右上角锚点)、"background_color"(背景颜色)、"border_color"(边框颜色)和"BORDER_FLAT"(平直边框样式),确保其在前景层显示。随后调用ChartRedraw函数刷新图表,并返回true表示操作成功。对于文本显示,我们定义了"create_label"函数,其参数包括:"object_name"、"text"、"x_distance"、"y_distance"、"font_size"、"text_color"和"font"。
我们调用"ResetLastError"函数重置错误状态,使用"ObjectCreate"函数创建一个"OBJ_LABEL",如果创建失败则记录错误日志。接下来,通过"ObjectSetInteger"函数设置标签的位置、尺寸、颜色以及"ANCHOR_CENTER"(居中锚点),并使用ObjectSetString函数配置文本内容和字体。随后调用"ChartRedraw"函数刷新图表,并返回true表示操作成功。借助这些函数,我们便可以在"OnInit"事件处理器中创建初始面板对象,为仪表盘搭建基础框架。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() //--- Initialize EA { create_rectangle(MAIN_PANEL, 632, 40, 617, 374, C'30,30,30', BORDER_FLAT); //--- Create main panel background create_rectangle(HEADER_PANEL, 632, 40, 617, 27, C'60,60,60', BORDER_FLAT); //--- Create header panel background create_label(HEADER_PANEL_ICON, CharToString(91), 620, 54, 18, clrAqua, "Wingdings"); //--- Create header icon create_label(HEADER_PANEL_TEXT, "TimeframeScanner", 527, 52, 13, COLOR_WHITE); //--- Create header title create_label(CLOSE_BUTTON, CharToString('r'), 32, 54, 18, clrYellow, "Webdings"); //--- Create close button // Create header rectangle and label create_rectangle(SYMBOL_RECTANGLE, 630, 75, WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, clrGray); //--- Create symbol rectangle create_label(SYMBOL_TEXT, _Symbol, 585, 85, 11, COLOR_WHITE); //--- Create symbol label // Create summary and indicator headers (rectangles and labels) string header_names[] = {"BUY", "SELL", "RSI", "STOCH", "CCI", "ADX", "AO"}; //--- Define header titles for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers int x_offset = (630 - WIDTH_TIMEFRAME) - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set width based on header type create_rectangle(HEADER_RECTANGLE + IntegerToString(header_index), x_offset, 75, width, HEIGHT_RECTANGLE, clrGray); //--- Create header rectangle create_label(HEADER_TEXT + IntegerToString(header_index), header_names[header_index], x_offset - width/2, 85, 11, COLOR_WHITE); //--- Create header label } // Create timeframe rectangles and labels, and summary/indicator cells for(int timeframe_index = 0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) { //--- Loop through timeframes // Highlight current timeframe color timeframe_background = (timeframes_array[timeframe_index] == _Period) ? clrLimeGreen : clrGray; //--- Set background color for current timeframe color timeframe_text_color = (timeframes_array[timeframe_index] == _Period) ? COLOR_BLACK : COLOR_WHITE; //--- Set text color for current timeframe create_rectangle(TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), 630, (75 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, timeframe_background); //--- Create timeframe rectangle create_label(TIMEFRAME_TEXT + IntegerToString(timeframe_index), truncate_timeframe_name(timeframe_index), 585, (85 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), 11, timeframe_text_color); //--- Create timeframe label // Create summary and indicator cells for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers for cells string cell_rectangle_name, cell_text_name; //--- Declare cell name and label variables color cell_background = (header_index < 2) ? COLOR_LIGHT_GRAY : COLOR_BLACK; //--- Set cell background color switch(header_index) { //--- Select cell type case 0: cell_rectangle_name = BUY_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = BUY_TEXT + IntegerToString(timeframe_index); break; //--- Buy cell case 1: cell_rectangle_name = SELL_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = SELL_TEXT + IntegerToString(timeframe_index); break; //--- Sell cell case 2: cell_rectangle_name = RSI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = RSI_TEXT + IntegerToString(timeframe_index); break; //--- RSI cell case 3: cell_rectangle_name = STOCH_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = STOCH_TEXT + IntegerToString(timeframe_index); break; //--- Stochastic cell case 4: cell_rectangle_name = CCI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = CCI_TEXT + IntegerToString(timeframe_index); break; //--- CCI cell case 5: cell_rectangle_name = ADX_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = ADX_TEXT + IntegerToString(timeframe_index); break; //--- ADX cell case 6: cell_rectangle_name = AO_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = AO_TEXT + IntegerToString(timeframe_index); break; //--- AO cell } int x_offset = (630 - WIDTH_TIMEFRAME) - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set width based on cell type create_rectangle(cell_rectangle_name, x_offset, (75 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), width, HEIGHT_RECTANGLE, cell_background); //--- Create cell rectangle create_label(cell_text_name, "-/-", x_offset - width/2, (85 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), 10, COLOR_WHITE); //--- Create cell label } } // Initialize indicator arrays ArraySetAsSeries(rsi_values, true); //--- Set RSI array as timeseries ArraySetAsSeries(stochastic_values, true); //--- Set Stochastic array as timeseries ArraySetAsSeries(cci_values, true); //--- Set CCI array as timeseries ArraySetAsSeries(adx_values, true); //--- Set ADX array as timeseries ArraySetAsSeries(ao_values, true); //--- Set AO array as timeseries return(INIT_SUCCEEDED); //--- Return initialization success }
在OnInit事件处理器中,我们初始化多时间周期扫描仪表盘的用户界面。我们使用 create_rectangle 函数绘制"MAIN_PANEL"(主面板),位置为 (632, 40),尺寸为 617×374 像素,背景色设置为“C’30,30,30’”(深灰色);同时绘制"HEADER_PANEL"(标题栏),位置与主面板相同,但高度为27像素,背景色为“C’60,60,60’”(稍浅的灰色)。接着,我们使用"create_label"函数在标题栏上添加"HEADER_PANEL_ICON",位置为 (620, 54),并使用Wingdings字体中的字符。MQL5中可直接使用默认字符,我们通过CharToString函数将字符代码转换为字符串。此处使用的字符代码为91,您可根据需要替换为其他代码。

随后,我们创建"HEADER_PANEL_TEXT",内容为"TimeframeScanner",位置为 (527, 52);同时创建"CLOSE_BUTTON",位置为 (32, 54)。这次我们改用另一种字体,并通过字符映射将字母 "r" 转换为特定符号字符串。以下是可选用的符号字体及其对应示例:

我们使用"create_rectangle"函数在坐标 (630, 75) 处创建名为"SYMBOL_RECTANGLE"的矩形对象,其宽度为"WIDTH_TIMEFRAME" ,高度为"HEIGHT_RECTANGLE",背景色设置为灰色。接着,通过"create_label"函数在坐标 (585, 85) 处创建文本标签"SYMBOL_TEXT",内容为当前交易品种名称。对于表头部分,我们定义了一个字符串数组"header_names",包含如"BUY"、"RSI"等标题。通过循环遍历该数组,创建位于y=75处的"HEADER_RECTANGLE"(x偏移量基于"WIDTH_SIGNAL"和"WIDTH_INDICATOR"),并使用 "create_label"函数在y=85处创建"HEADER_TEXT"标签。
我们通过遍历"timeframes_array"数组来搭建多时间周期网格布局。我们使用"create_rectangle"函数创建名为"TIMEFRAME_RECTANGLE" 的矩形对象,固定x坐标为 630,y坐标通过 (75 + HEIGHT_RECTANGLE) - (1 + timeframe_index) 动态计算,使用"timeframe_background"定义的动态颜色。通过"create_label"函数创建"TIMEFRAME_TEXT"标签,显示经过"truncate_timeframe_name"函数处理后的时间周期名称。对于每个单元格,使用"create_rectangle"函数创建单元格背景(如"BUY_RECTANGLE"、"RSI_RECTANGLE"等),背景色为"cell_background",并且使用"create_label"函数在单元格内添加初始标签“-/-”(表示无数据)。通过ArraySetAsSeries函数将指标数组设置为时间序列模式,便于后续数据处理。我们返回INIT_SUCCEEDED常量,确认仪表盘布局和数据结构初始化成功。编译后,呈现如下效果:

由图可见,仪表盘的基础布局已经搭建完成。当前,我们只需完成最后一步——接入指标数据并用于分析。虽然已有分析函数,但是我们需要先获取数据为其提供实时输入。为此,我们创建一个函数来处理所有的动态更新逻辑。
//+------------------------------------------------------------------+ //| Update indicator values | //+------------------------------------------------------------------+ void updateIndicators() //--- Update dashboard indicators { for(int timeframe_index = 0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) { //--- Loop through timeframes // Initialize indicator handles int rsi_indicator_handle = iRSI(_Symbol, timeframes_array[timeframe_index], 14, PRICE_CLOSE); //--- Create RSI handle int stochastic_indicator_handle = iStochastic(_Symbol, timeframes_array[timeframe_index], 14, 3, 3, MODE_SMA, STO_LOWHIGH); //--- Create Stochastic handle int cci_indicator_handle = iCCI(_Symbol, timeframes_array[timeframe_index], 20, PRICE_TYPICAL); //--- Create CCI handle int adx_indicator_handle = iADX(_Symbol, timeframes_array[timeframe_index], 14); //--- Create ADX handle int ao_indicator_handle = iAO(_Symbol, timeframes_array[timeframe_index]); //--- Create AO handle // Check for valid handles if(rsi_indicator_handle == INVALID_HANDLE || stochastic_indicator_handle == INVALID_HANDLE || cci_indicator_handle == INVALID_HANDLE || adx_indicator_handle == INVALID_HANDLE || ao_indicator_handle == INVALID_HANDLE) { //--- Check if any handle is invalid Print("Failed to create indicator handle for timeframe ", truncate_timeframe_name(timeframe_index)); //--- Log failure continue; //--- Skip to next timeframe } // Copy indicator values if(CopyBuffer(rsi_indicator_handle, 0, 0, 1, rsi_values) <= 0 || //--- Copy RSI value CopyBuffer(stochastic_indicator_handle, 1, 0, 1, stochastic_values) <= 0 || //--- Copy Stochastic signal line value CopyBuffer(cci_indicator_handle, 0, 0, 1, cci_values) <= 0 || //--- Copy CCI value CopyBuffer(adx_indicator_handle, 0, 0, 1, adx_values) <= 0 || //--- Copy ADX value CopyBuffer(ao_indicator_handle, 0, 0, 1, ao_values) <= 0) { //--- Copy AO value Print("Failed to copy buffer for timeframe ", truncate_timeframe_name(timeframe_index)); //--- Log copy failure continue; //--- Skip to next timeframe } // Update RSI color rsi_text_color = (rsi_values[0] < 30) ? clrBlue : (rsi_values[0] > 70) ? clrRed : COLOR_WHITE; //--- Set RSI text color update_label(RSI_TEXT + IntegerToString(timeframe_index), DoubleToString(rsi_values[0], 2), rsi_text_color); //--- Update RSI label // Update Stochastic (Signal Line only) color stochastic_text_color = (stochastic_values[0] < 20) ? clrBlue : (stochastic_values[0] > 80) ? clrRed : COLOR_WHITE; //--- Set Stochastic text color update_label(STOCH_TEXT + IntegerToString(timeframe_index), DoubleToString(stochastic_values[0], 2), stochastic_text_color); //--- Update Stochastic label // Update CCI color cci_text_color = (cci_values[0] < -100) ? clrBlue : (cci_values[0] > 100) ? clrRed : COLOR_WHITE; //--- Set CCI text color update_label(CCI_TEXT + IntegerToString(timeframe_index), DoubleToString(cci_values[0], 2), cci_text_color); //--- Update CCI label // Update ADX color adx_text_color = (adx_values[0] > 25) ? clrBlue : COLOR_WHITE; //--- Set ADX text color update_label(ADX_TEXT + IntegerToString(timeframe_index), DoubleToString(adx_values[0], 2), adx_text_color); //--- Update ADX label // Update AO color ao_text_color = (ao_values[0] > 0) ? clrGreen : (ao_values[0] < 0) ? clrRed : COLOR_WHITE; //--- Set AO text color update_label(AO_TEXT + IntegerToString(timeframe_index), DoubleToString(ao_values[0], 2), ao_text_color); //--- Update AO label // Update Buy/Sell signals string buy_signal = calculate_signal_strength(rsi_values[0], stochastic_values[0], cci_values[0], adx_values[0], ao_values[0], true); //--- Calculate buy signal string sell_signal = calculate_signal_strength(rsi_values[0], stochastic_values[0], cci_values[0], adx_values[0], ao_values[0], false); //--- Calculate sell signal color buy_text_color = (buy_signal == "Strong Buy") ? COLOR_WHITE : COLOR_WHITE; //--- Set buy text color color buy_background = (buy_signal == "Strong Buy") ? clrGreen : (buy_signal == "Buy") ? clrSeaGreen : COLOR_DARK_GRAY; //--- Set buy background color update_rectangle(BUY_RECTANGLE + IntegerToString(timeframe_index), buy_background); //--- Update buy rectangle update_label(BUY_TEXT + IntegerToString(timeframe_index), buy_signal, buy_text_color); //--- Update buy label color sell_text_color = (sell_signal == "Strong Sell") ? COLOR_WHITE : COLOR_WHITE; //--- Set sell text color color sell_background = (sell_signal == "Strong Sell") ? clrRed : (sell_signal == "Sell") ? clrSalmon : COLOR_DARK_GRAY; //--- Set sell background color update_rectangle(SELL_RECTANGLE + IntegerToString(timeframe_index), sell_background); //--- Update sell rectangle update_label(SELL_TEXT + IntegerToString(timeframe_index), sell_signal, sell_text_color); //--- Update sell label // Release indicator handles IndicatorRelease(rsi_indicator_handle); //--- Release RSI handle IndicatorRelease(stochastic_indicator_handle); //--- Release Stochastic handle IndicatorRelease(cci_indicator_handle); //--- Release CCI handle IndicatorRelease(adx_indicator_handle); //--- Release ADX handle IndicatorRelease(ao_indicator_handle); //--- Release AO handle } }
为高效管理仪表盘数据更新,我们实现"updateIndicators"函数,用于动态刷新指标值及交易信号。我们通过循环遍历"timeframes_array",使用"timeframe_index"定位当前时间周期,确保每个时间周期的数据同步更新。我们通过调用iRSI、iStochastic、iCCI、iADX和"iAO"等内置函数,为当前交易品种和时间周期创建指标句柄(如"rsi_indicator_handle"),并配置参数,例如14周期的RSI指标、20周期的CCI指标。所有指标参数均可根据需求自定义调整,而无需局限于默认值。
接下来,我们检查每个指标句柄(如"rsi_indicator_handle")是否等于INVALID_HANDLE,如果为该值则表明句柄创建失败。如果存在无效句柄,我们使用"Print"函数记录错误信息(结合"truncate_timeframe_name"函数输出的时间周期名称),并跳过当前时间周期,继续处理下一个。我们再通过CopyBuffer函数将最新指标数据读取到数组(如"rsi_values")中,如果数据读取失败,同样记录错误并继续执行后续逻辑。随后,我们调用"update_label"函数更新指标显示内容。例如,我们根据"rsi_values[0]"的值设置"rsi_text_color":当低于30时显示为蓝色(超卖),当高于70时显示未红色(超买),其他情况显示为白色,使用"DoubleToString"函数格式化数值,并更新"RSI_TEXT"。对于其他指标(如"stochastic_values"、"cci_values"、"adx_values"和"ao_values")重复上述流程,并应用对应的颜色逻辑(例如 "ao_values"为正值时显示绿色)。
我们调用"calculate_signal_strength"函数计算交易信号,传入"rsi_values[0]" 等指标当前值,获取"buy_signal"和"sell_signal"。对于买入信号,根据信号强度设置背景颜色"buy_background"(例如“强烈买入”显示绿色背景),并通过"update_rectangle"函数更新矩形控件样式"BUY_RECTANGLE",同时使用"update_label"函数更新"BUY_TEXT"对卖出信号执行相同的操作:设置背景颜色"sell_background"(例如“强烈卖出”显示红色背景),并更新"SELL_RECTANGLE"和"SELL_TEXT"。最后,调用IndicatorRelease函数释放指标句柄,如"rsi_indicator_handle",确保资源高效回收。我们使用的辅助函数定义如下:
//+------------------------------------------------------------------+ //| Update rectangle background color | //+------------------------------------------------------------------+ bool update_rectangle(string object_name, color background_color)//--- Function to update rectangle color { int found = ObjectFind(0, object_name); //--- Find rectangle object if(found < 0) { //--- Check if object not found ResetLastError(); //--- Reset error code Print("UNABLE TO FIND THE RECTANGLE: ", object_name, ". ERR Code: ", GetLastError()); //--- Log error return(false); //--- Return failure } ObjectSetInteger(0, object_name, OBJPROP_BGCOLOR, background_color); //--- Set background color ChartRedraw(0); //--- Redraw chart return(true); //--- Return success } //+------------------------------------------------------------------+ //| Update label text and color | //+------------------------------------------------------------------+ bool update_label(string object_name, string text, color text_color) //--- Function to update label { int found = ObjectFind(0, object_name); //--- Find label object if(found < 0) { //--- Check if object not found ResetLastError(); //--- Reset error code Print("UNABLE TO FIND THE LABEL: ", object_name, ". ERR Code: ", GetLastError()); //--- Log error return(false); //--- Return failure } ObjectSetString(0, object_name, OBJPROP_TEXT, text); //--- Set label text ObjectSetInteger(0, object_name, OBJPROP_COLOR, text_color); //--- Set text color ChartRedraw(0); //--- Redraw chart return(true); //--- Return success }
我们定义"update_rectangle"函数,接收"object_name"和"background_color"作为参数,用于修改矩形控件的外观样式。首先,通过ObjectFind函数查找指定名称的矩形对象,并将返回值存储在"found"变量中。如果"found"小于0(表示对象未找到),则调用ResetLastError重置错误状态,使用"Print"函数结合"GetLastError"记录错误信息,并返回false表示操作失败。如果对象存在,则通过"ObjectSetInteger"函数将矩形的"OBJPROP_BGCOLOR"设置为传入的"background_color"值。最后,我们调用ChartRedraw函数刷新图表界面,并返回true表示操作成功。对于文本标签的更新,我们使用参数"object_name"、"text"和"text_color"定义"update_label"函数。
我们使用"ObjectFind"函数检查标签对象是否存在,如果返回值"found"为负数(表示对象未找到),则调用"ResetLastError"函数,并通过"Print"函数记录错误信息,最后返回false表示操作失败。如果对象存在,我们通过ObjectSetString函数将标签的"OBJPROP_TEXT"属性设置为传入的"text"参数,同时使用ObjectSetInteger函数将"OBJPROP_COLOR"属性设置为"text_color"参数。更新属性后,我们调用"ChartRedraw"函数刷新图表界面,并返回true表示操作成功,实现动态更新标签内容。现在,我们可以在每个tick到来时调用此更新函数,从而实现仪表盘的实时动态更新。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() //--- Handle tick events { if (panel_is_visible) { //--- Check if panel is visible updateIndicators(); //--- Update indicators } }
在OnTick事件处理器中,我们仅在仪表盘面板处于可见状态时调用"updateIndicators"函数,以执行更新操作。最后,我们需要删除所有已创建的图表对象,确保后续不再需要它们时从图表中彻底移除。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) //--- Deinitialize EA { ObjectDelete(0, MAIN_PANEL); //--- Delete main panel ObjectDelete(0, HEADER_PANEL); //--- Delete header panel ObjectDelete(0, HEADER_PANEL_ICON); //--- Delete header icon ObjectDelete(0, HEADER_PANEL_TEXT); //--- Delete header title ObjectDelete(0, CLOSE_BUTTON); //--- Delete close button ObjectsDeleteAll(0, SYMBOL_RECTANGLE); //--- Delete all symbol rectangles ObjectsDeleteAll(0, SYMBOL_TEXT); //--- Delete all symbol labels ObjectsDeleteAll(0, TIMEFRAME_RECTANGLE); //--- Delete all timeframe rectangles ObjectsDeleteAll(0, TIMEFRAME_TEXT); //--- Delete all timeframe labels ObjectsDeleteAll(0, HEADER_RECTANGLE); //--- Delete all header rectangles ObjectsDeleteAll(0, HEADER_TEXT); //--- Delete all header labels ObjectsDeleteAll(0, RSI_RECTANGLE); //--- Delete all RSI rectangles ObjectsDeleteAll(0, RSI_TEXT); //--- Delete all RSI labels ObjectsDeleteAll(0, STOCH_RECTANGLE); //--- Delete all Stochastic rectangles ObjectsDeleteAll(0, STOCH_TEXT); //--- Delete all Stochastic labels ObjectsDeleteAll(0, CCI_RECTANGLE); //--- Delete all CCI rectangles ObjectsDeleteAll(0, CCI_TEXT); //--- Delete all CCI labels ObjectsDeleteAll(0, ADX_RECTANGLE); //--- Delete all ADX rectangles ObjectsDeleteAll(0, ADX_TEXT); //--- Delete all ADX labels ObjectsDeleteAll(0, AO_RECTANGLE); //--- Delete all AO rectangles ObjectsDeleteAll(0, AO_TEXT); //--- Delete all AO labels ObjectsDeleteAll(0, BUY_RECTANGLE); //--- Delete all buy rectangles ObjectsDeleteAll(0, BUY_TEXT); //--- Delete all buy labels ObjectsDeleteAll(0, SELL_RECTANGLE); //--- Delete all sell rectangles ObjectsDeleteAll(0, SELL_TEXT); //--- Delete all sell labels ChartRedraw(0); //--- Redraw chart }
最终,我们通过OnDeinit 函数实现清理逻辑,该函数会在智能交易系统(EA)被移除或程序终止时自动触发执行。我们使用ObjectDelete函数逐个删除用户界面(UI)元素,首先移除主面板矩形对象"MAIN_PANEL",随后依次删除标题栏组件"HEADER_PANEL"、"HEADER_PANEL_ICON"和"HEADER_PANEL_TEXT",以及关闭按钮 "CLOSE_BUTTON",确保从图表中彻底清除所有主面板和标题栏的相关元素。
我们通过ObjectsDeleteAll函数按元素类型系统化地移除所有仪表盘对象。移除所有类型为矩形的"SYMBOL_RECTANGLE"和对应文本标签"SYMBOL_TEXT",清除品种显示区域,以及"TIMEFRAME_RECTANGLE"和对应文本标签"TIMEFRAME_TEXT",清除时间框架显示区域,外加"HEADER_RECTANGLE"及其文本标签"HEADER_TEXT",清理标题区域。同时移除所有技术指标对应的矩形对象,包括 "RSI_RECTANGLE"、"STOCH_RECTANGLE"、"CCI_RECTANGLE"、"ADX_RECTANGLE" 和 "AO_RECTANGLE",并且删除这些指标的配套文本标签,如 "RSI_TEXT"。
我们通过"ObjectsDeleteAll"函数完成清理工作,删除所有"BUY_RECTANGLE"和"SELL_RECTANGLE"矩形对象,以及对应的"BUY_TEXT"和"SELL_TEXT"文本标签,彻底移除所有与交易信号相关的界面元素。最后,调用ChartRedraw函数刷新图表,确保在程序卸载后呈现干净的视觉效果。此外,我们需要设定取消按钮的交互逻辑:当用户点击该按钮时,关闭仪表盘并停止后续数据更新。
//+------------------------------------------------------------------+ //| Expert chart event handler | //+------------------------------------------------------------------+ void OnChartEvent(const int event_id, //--- Event ID const long& long_param, //--- Long parameter const double& double_param, //--- Double parameter const string& string_param) //--- String parameter { if (event_id == CHARTEVENT_OBJECT_CLICK) { //--- Check for object click event if (string_param == CLOSE_BUTTON) { //--- Check if close button clicked Print("Closing the panel now"); //--- Log panel closure PlaySound("alert.wav"); //--- Play alert sound panel_is_visible = false; //--- Hide panel ObjectDelete(0, MAIN_PANEL); //--- Delete main panel ObjectDelete(0, HEADER_PANEL); //--- Delete header panel ObjectDelete(0, HEADER_PANEL_ICON); //--- Delete header icon ObjectDelete(0, HEADER_PANEL_TEXT); //--- Delete header title ObjectDelete(0, CLOSE_BUTTON); //--- Delete close button ObjectsDeleteAll(0, SYMBOL_RECTANGLE); //--- Delete all symbol rectangles ObjectsDeleteAll(0, SYMBOL_TEXT); //--- Delete all symbol labels ObjectsDeleteAll(0, TIMEFRAME_RECTANGLE); //--- Delete all timeframe rectangles ObjectsDeleteAll(0, TIMEFRAME_TEXT); //--- Delete all timeframe labels ObjectsDeleteAll(0, HEADER_RECTANGLE); //--- Delete all header rectangles ObjectsDeleteAll(0, HEADER_TEXT); //--- Delete all header labels ObjectsDeleteAll(0, RSI_RECTANGLE); //--- Delete all RSI rectangles ObjectsDeleteAll(0, RSI_TEXT); //--- Delete all RSI labels ObjectsDeleteAll(0, STOCH_RECTANGLE); //--- Delete all Stochastic rectangles ObjectsDeleteAll(0, STOCH_TEXT); //--- Delete all Stochastic labels ObjectsDeleteAll(0, CCI_RECTANGLE); //--- Delete all CCI rectangles ObjectsDeleteAll(0, CCI_TEXT); //--- Delete all CCI labels ObjectsDeleteAll(0, ADX_RECTANGLE); //--- Delete all ADX rectangles ObjectsDeleteAll(0, ADX_TEXT); //--- Delete all ADX labels ObjectsDeleteAll(0, AO_RECTANGLE); //--- Delete all AO rectangles ObjectsDeleteAll(0, AO_TEXT); //--- Delete all AO labels ObjectsDeleteAll(0, BUY_RECTANGLE); //--- Delete all buy rectangles ObjectsDeleteAll(0, BUY_TEXT); //--- Delete all buy labels ObjectsDeleteAll(0, SELL_RECTANGLE); //--- Delete all sell rectangles ObjectsDeleteAll(0, SELL_TEXT); //--- Delete all sell labels ChartRedraw(0); //--- Redraw chart } } }
在OnChartEvent事件处理器中,我们监听对象点击事件:当事件类型为CHARTEVENT_OBJECT_CLICK且被点击的对象是“取消按钮”时,通过PlaySound函数播放提示音,告知用户仪表盘即将关闭;随后隐藏仪表盘界面,并复用OnDeinit中的清理逻辑彻底清除图表上的所有仪表盘对象。编译后,呈现如下效果:

由图可见,仪表盘已成功更新并显示指标数据及交易方向指示。接下来,我们将在后续章节中验证项目的实际运行效果。
回测
我们已完成测试,以下是整合后的可视化结果,以单一的图形交换格式(GIF)位图图像形式呈现。

结论
总而言之,我们基于MQL5开发了一款多时间框架扫描仪表盘,集成了结构化网格布局、实时指标信号以及交互式关闭按钮,旨在为策略交易决策提供高效支持。我们详细阐述了这些功能的设计与实现过程,通过稳健的初始化逻辑和针对交易需求的动态更新机制,确保其有效性。该仪表盘支持高度的自定义,用户可根据个人偏好调整布局与功能,从而显著提升对多时间框架市场信号的监控能力与响应速度。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18319
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
数据科学和机器学习(第 36 部分):与偏颇的金融市场打交道
MQL5自动化交易策略(第十八部分):基于包络线趋势反弹的剥头皮交易——核心架构与信号生成(1)
MQL5 中的策略可视化:在标准图表中展示优化结果