English Русский Español Deutsch 日本語 Português
preview
在MQL5中创建动态多品种、多周期相对强弱指数(RSI)指标仪表盘

在MQL5中创建动态多品种、多周期相对强弱指数(RSI)指标仪表盘

MetaTrader 5交易系统 |
477 0
Allan Munene Mutiiria
Allan Munene Mutiiria

概述

在本文中,我们将指导您在MetaTrader 5(MT5)的MetaQuotes Language 5(MQL5)环境中,创建一个动态多品种、多周期的RSI(相对强弱指数)指标仪表盘。本综合指南将探讨自定义RSI仪表盘的定义、功能以及实际应用,以及使用MetaQuotes Language 5(MQL5)开发它所需的步骤。

一个动态的RSI仪表盘是交易者强大的工具,它可以提供跨多个标的和时间段的RSI值的综合视图。这有助于交易者通过识别市场的超买或超卖状况来做出更加明智的决策。通过在一个界面中可视化RSI数据,交易者可以快速评估市场状况并相应地调整他们的策略。

我们将涵盖以下关键领域:

  • 仪表盘初始化: 设置环境,创建主要按钮,并显示时间段和标的。
  • 实时更新: 实现动态计算和显示RSI值的功能,并根据实时市场数据进行更新。
  • 按钮创建和更新: 详细解释用于创建和更新按钮的函数,确保仪表盘既用户友好又信息丰富。
  • 定制和实际应用: 如何根据个人的交易需求定制仪表盘,并将其融入你的交易策略中。

到本文结束时,你将全面了解如何在MQL5中构建和使用多标的、多周期的RSI仪表盘。这将增强您的交易工具箱,提高你分析市场趋势的能力,并最终帮助您做出更加明智的交易决策。为了实现这一点,我们将利用以下主题:

  1. 概述和要素说明
  2. 在MQL5中的实现
  3. 结论

在这次旅程中,我们将广泛使用MetaQuotes Language 5(MQL5)作为我们的基础集成开发环境(IDE)编码环境,在MetaEditor中进行编码,并在MetaTrader 5(MT5)交易平台执行文件。因此,拥有上述版本至关重要。让我们深入探索开发这一强大交易工具的细节。 


概述和要素说明

我们将创建一个综合仪表盘,整合多个交易品种和不同时间框架下的RSI值,为交易者提供一个强大的市场分析工具。我们将详细概述并涵盖所有内容,以确保深入理解。我们的开发将重点关注以下关键要素:

  • 仪表盘初始化:

主按钮创建: 初始化过程的第一步是创建一个中心按钮,作为仪表盘的基础和基准。此按钮将作为仪表盘的主要控件,并为用户界面提供一个焦点。依照策略将其放置在仪表盘的顶部,以便用户能够轻松地访问和清晰地查看。

时间框架按钮: 接下来,我们将创建代表不同时间框架的按钮,以展示来自不同品种周期的其他指标数据。这些按钮将被水平地排列在主按钮旁边,并设计为显示每个相应周期的RSI值。每个时间框架按钮都将用适当的时间周期缩写进行标记,使交易者能够迅速识别和比较不同时间框架下的数据。

  • 品种按钮

动态交易品种列表:为了提供全面的市场趋势视图,我们将为用户在MetaTrader 5平台中的每个交易品种生成按钮并且添加市场观察动态。动态创建这些按钮并垂直排列在主按钮下方。当前激活的交易品种将以醒目的颜色(例如,亮绿色)高亮显示,以便轻松识别。此功能将确保交易者能够快速识别活跃品种并实时监控其RSI值。

基础按钮:在交易品种列表的底部,我们将添加一个基础按钮,其宽度覆盖所有时间框架按钮的总和。此按钮将被命名为仪表盘的名称或标题,但也可以用于显示当前品种的信号状态,或作为品种列表的摘要或页脚。它将清楚地划分品种按钮和RSI值显示区域,确保仪表盘组织有序且易于导航。

  • 实时更新:

RSI计算:为确保仪表盘提供准确且最新的信息,我们将使用内置的MQL5函数实时计算每个品种和时间框架的RSI值。此函数将根据选定周期的收盘价计算RSI,提供市场动量的重要指标。RSI值将被存储在数组中,并在每个新行情数据时更新,以反映最新的市场数据。

动态显示:计算出的RSI值将动态显示在相应的按钮上。为增强视觉清晰度,我们将基于预定义的RSI阈值实施颜色编码方案。如果RSI值低于30,表明市场超卖,按钮的背景颜色将变为绿色。如果RSI值高于70,表明市场超买,背景颜色将变为红色。对于RSI值在30到70之间的,背景颜色将保持白色,表明市场处于中性状态。这种动态显示将使交易者能够快速评估市场状况并做出明智的交易决策。

为了说明整个过程,以下是我们最终期望实现的效果。

概况图

为了说明整个开发过程,我们将把每个要素分解成详细的步骤,并提供代码段和解释说明。到本文结束时,您将拥有一个功能齐全的RSI仪表盘,您可以对其进行自定义并将其集成到您的交易策略中。


在MQL5中的实现

该指标仪表盘将基于一个EA。要在MetaTrader 5终端中创建一个EA,请点击“工具”选项卡,并查看MetaQuotes语言编辑器,或者按键盘上的F4键。或者,您也可以点击工具栏上的IDE(集成开发环境)图标。这样就会打开MetaQuotes语言编辑器环境,该环境允许用户编写自动交易、技术指标、脚本和函数库。

IDE

打开MetaEditor后,在工具栏上,点击“文件”选项卡,然后勾选“新建文件”,或者直接按CTRL + N键,以创建一个新文档。或者,您也可以点击工具栏选项卡上的“新建”图标。这将弹出一个MQL向导窗口。

新EA

在弹出的向导中,选中EA(模板),然后单击下一步。

MQL 向导

在EA的一般属性中,在名称部分,提供您的文件名称。请注意,如果要指定或创建一个不存在的文件夹,您需要在EA名称前使用反斜杠。例如,这里我们默认有“Experts\”。这意味着我们的EA将被创建在Experts文件夹中,我们可以去那里找。其他部分相对直观,但您可以按照向导底部的链接了解如何精准地执行该过程。

新EA

在输入您希望的EA文件名后,依次点击“下一步”、再“下一步”,然后点击“完成”。完成所有这些之后,我们现在准备对指标仪表盘进行写码和编程。

首先,我们需要为将要新建的按钮创建一个函数。这将非常有用,因为它将允许我们在创建类似功能时重复使用相同的函数,而无需在创建类似对象时重复整个过程。它还将为我们节省大量时间和空间,使过程快速、简单,且代码段简短。

为了新建按钮,我们将创建一个接受11个参数或自变量的函数。

//+------------------------------------------------------------------+
//| Function to create a button                                      |
//+------------------------------------------------------------------+
bool createButton(string objName, string text, int xD, int yD, int xS, int yS,
   color clrTxt, color clrBg, int fontSize = 12, color clrBorder = clrNONE,
   string font = "Arial Rounded MT Bold"
) {

...

}

函数签名说明了一切。这是一个名为“createButton”的布尔函数,意味着它将根据成功或失败的情况分别返回两个布尔标识,即true或false。为了更容易理解其参数,我们下面逐一概述并解释它们。

  • objName(字符串):该参数指定按钮对象的名称。每个按钮都必须有一个唯一的名称,以便在图表上与其他对象区分开来。此名称用于关联按钮以进行更新和修改。
  • text(字符串): 定义将在按钮上显示的文本。它可以是任何字符串,例如“RSI”、“BUY”或任何其他与按钮用途相关的标签。
  • xD(整数): 该参数定义按钮与图表指定角的水平距离。单位是像素。
  • yD(整数): 与xD类似,该参数定义按钮于图表指定角的垂直距离。
  • xS(整数): 该参数定义按钮的像素宽度。其决定了按钮在图表上将显示的宽度。
  • yS(整数): 定义按钮的像素高度。其决定了按钮在图表上将显示的高度。

DISTANCE AND SIZE

  • clrTxt(颜色):该参数设置按钮上显示文本的颜色。颜色可以使用MQL5中预定义的颜色常量来指定,例如clrBlack(黑色)、clrWhite(白色)、clrRed(红色)等。
  • clrBg(颜色):该参数定义按钮的背景颜色。其同样使用预定义的颜色常量,并决定按钮的填充颜色。
  • fontSize(整数):该可选参数指定按钮文本所使用的字体大小。如果未提供,则默认为12。字体大小决定按钮上显示的文本尺寸。
  • clrBorder(颜色):该另一个可选参数设置按钮边框的颜色。如果未提供,则默认为"clrNONE",意味着不使用边框颜色。如果指定了边框颜色,可以提升按钮的可见性和美观性。
  • font(字符串):该另一个可选参数定义按钮文本所使用的字体类型。如果未提供,则默认为"Arial Rounded MT Bold"。字体决定按钮上显示的文本样式。

在函数签名上,您可能注意到一些参数已经被初始化为某个值。这个初始化值代表了如果在函数调用时忽略该参数,则会赋给该参数的默认值。例如,我们的默认边框颜色是“none”,这意味着如果在函数调用时没有指定颜色值,则矩形标签的边框将不会用任何颜色。 

在函数体内,由大括号({})包围的部分,我们定义了对象的创建过程。 

   // Attempt to create the button
   if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) {
      Print(__FUNCTION__, ": failed to create Btn: ERR Code: ", GetLastError()); // Print error message if button creation fails
      return (false); // Return false if creation fails
   }

我们从使用if语句开始,检查对象是否未被创建。ObjectCreate是一个布尔函数,接受6个参数。该函数用于在指定的图表子窗口中创建一个具有指定名称、类型和初始坐标的对象。首先,我们指定图表窗口,其中0表示将在主窗口中创建对象。然后,我们提供对象的名称。这是将唯一分配给特定对象的名称。我们想要创建的对象类型是“OBJ_BUTTON”,其代表一个用于创建和设计自定义指标仪表盘的对象。接下来,我们提供子窗口,其中0表示当前子窗口。最后,我们将时间和价格值都设置为0,因为我们不会将它们附加到图表上,而是附加到图表窗口的坐标上。使用像素来设置映射。

如果对象创建失败,最终ObjectCreate函数会返回false,显然继续执行下去没有意义,我们会报错并返回。在这种情况下,我们通过将错误信息及其代码打印到日志中来通知错误,并返回false。可能之前已经存在错误,因此为了获取最新的错误,我们需要在对象创建逻辑之前清除之前的错误。可以通过调用内置的MQL5的ResetLastError函数来实现。

   ResetLastError(); // Reset the last error code

该函数的目的是将函数GetLastError的值设置为零,GetLastError函数用于获取最近一次出错操作的错误代码。通过调用这个函数,我们可以确保在进行下一步操作之前清除任何之前的错误代码。这一步至关重要,因为它使我们能够独立地处理新的错误,而不会受到之前错误状态的干扰。

如果我们没有返回到这里,这意味着我们已经创建了对象,因此我们可以继续更新对象的属性。一个内置函数“ObjectSet...”用于设置相应对象属性的值。对象属性必须是日期时间、整数、颜色、布尔值或字符类型。

   // Set button properties
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // Set X distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Set Y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); // Set X size
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); // Set Y size
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); // Set corner position
   ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text
   ObjectSetString(0, objName, OBJPROP_FONT, font); // Set font type
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); // Set font size
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); // Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Set background property
   ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Set button state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Set if the button is selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Set if the button is selected

让我们专注于第一属性逻辑。

   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // Set X distance

在此,我们使用内置的ObjectSetInteger函数,并分别传递参数。参数描述如下:

  • Chart id: 这是图表的标识符。“0”指的是当前图表(chart ID)。我们要调整此图表内对象的属性。
  • Name: 这是对象的名称。“objName”表示分配给矩形标签对象的唯一名称。
  • Property id: 这是对象属性ID,其值可以是“ENUM_OBJECT_PROPERTY_INTEGER”枚举中的一个值。“OBJPROP_XDISTANCE”指定我们正在修改的X距离属性。
  • Property value: 这是属性的值。分配给“xD”的值决定了矩形标签的左上角将从图表的左边缘水平向右(或向左,如果为负值)移动多远。

同样,我们使用相同的格式来设置其他属性。“OBJPROP_YDISTANCE”配置矩形标签的Y距离属性。“yD”值决定了矩形标签的左上角将从图表的顶部边缘垂直向下移动多远。换句话说,它控制着标签在图表区域内的垂直位置。这样就设置了从角落开始的Y距离。“OBJPROP_XSIZE”和“OBJPROP_YSIZE”分别设置矩形的宽度和高度。 

为了定位我们的对象,我们使用“OBJPROP_CORNER”属性来确定我们希望对象位于图表窗口的哪个角落。

   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); // Set corner position

该属性只能包含4种类型:

  • CORNER_LEFT_UPPER: 坐标中心位于图表的左上角。
  • CORNER_LEFT_LOWER: 坐标中心位于图表的左下角。
  • CORNER_RIGHT_LOWER: 坐标中心位于图表的右下角。
  • CORNER_RIGHT_UPPER: 坐标中心位于图表的右上角。

图像显示的情况就是这样。

OBJECT CORNERS

其余的属性都很直接明了。我们为它们添加了注释,以便更容易理解。然后,我们只需重新绘制图表,就能使更改自动生效,而无需等待价格报价或图表事件的变化。 

   ChartRedraw(0); // Redraw the chart to reflect the new button

最后,我们返回true,表示对象属性的创建和更新都是成功的。

   return (true); // Return true if creation is successful

负责在图表窗口上创建按钮对象的完整函数代码如下:

//+------------------------------------------------------------------+
//| Function to create a button                                      |
//+------------------------------------------------------------------+
bool createButton(string objName, string text, int xD, int yD, int xS, int yS,
   color clrTxt, color clrBg, int fontSize = 12, color clrBorder = clrNONE,
   string font = "Arial Rounded MT Bold"
) {
   ResetLastError(); // Reset the last error code
   // Attempt to create the button
   if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) {
      Print(__FUNCTION__, ": failed to create Btn: ERR Code: ", GetLastError()); // Print error message if button creation fails
      return (false); // Return false if creation fails
   }
   // Set button properties
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // Set X distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Set Y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); // Set X size
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); // Set Y size
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); // Set corner position
   ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text
   ObjectSetString(0, objName, OBJPROP_FONT, font); // Set font type
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); // Set font size
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); // Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Set background property
   ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Set button state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Set if the button is selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Set if the button is selected
   
   ChartRedraw(0); // Redraw the chart to reflect the new button
   return (true); // Return true if creation is successful
}

现在我们有了新建按钮所需的功能,让我们用它来创建指标仪表盘。我们需要为对象命名,并且为了更轻松地管理对象名称的交互,定义宏会更加方便。

// Define button identifiers and properties
#define BTN1 "BTN1"

我们使用#define关键字定义了一个名为“BTN1”的宏,其值也为“BTN1”,这样做可以方便地存储我们的主按钮名称。在每次创建按钮部分时,我们无需重复键入该名称,从而大大节省了时间,并降低了提供错误名称的可能性。因此,基本上宏在编译过程中被用于替换文本。

同样,我们定义了将要使用的其他宏。 

#define Desc "Desc "
#define Symb "Symb "
#define Base "Base "
#define RSI "RSI "
#define XS1 90
#define XS2 100
#define YS1 25
#define clrW clrWhite
#define clrB clrBlack
#define clrW_Gray C'230,230,230'

在这里,我们使用宏“Desc”作为描述不同时间框架的按钮名称的前缀,通过附加索引和额外字符串(如“Desc0”,“Desc1”等)来生成这些描述按钮的唯一名称。同样地,宏“Symb”被用作代表不同交易品种的按钮名称的前缀,帮助我们创建如“Symb0”,“Symb1”等唯一标识符。宏“Base”则作为基础按钮名称的前缀,为我们的仪表盘组件提供了一个清晰且一致的命名规范。对于处理与RSI相关的按钮,我们使用宏“RSI”,以确保跨不同品种和时间框架显示RSI值的按钮具有唯一标识符。

关于尺寸方面,“XS1”将设置某些按钮的宽度,而“XS2”和“YS1”则分别指定其他按钮的宽度和高度,从而实现我们图形用户界面(GUI)元素尺寸的标准化。我们定义了颜色宏“clrW”和“clrB”,以便在代码中方便地引用白色和黑色。MQL5有预定义的颜色格式变量,当我们使用“clrWhite”来表示白色时,实际上就是在分配和引用这些预定义的颜色变量,即网格颜色

颜色格式第1节:

颜色第1节

颜色格式第2节:

颜色第2节

另外,我们定义了一个自定义的灰色"clrW_Gray" C'230,230,230',用作背景色或边框色,以确保仪表盘具有一致的视觉风格。为了更精确地控制颜色,我们以文字格式表示最后一个宏。这种格式采用“C'000,000,000'”的形式,其中的三组0可以是0到255之间的任何数字。所采用的格式是RGB(红、绿、蓝)格式。这三个值分别代表红色、绿色和蓝色元素,它们的取值范围都是从0到255。因此,230,230,230代表的是接近白色的色调。

我们需要在仪表盘中定义将要使用的特定交易品种的时间框架或周期。因此,我们需要存储这些信息,而最简单的方式就是将它们存储在数组中,这样便于后续轻松地访问。 

// Define the timeframes to be used
ENUM_TIMEFRAMES periods[] = {PERIOD_M1, PERIOD_M5, PERIOD_H1, PERIOD_H4, PERIOD_D1};

我们定义了一个静态数组,其类型为ENUM_TIMEFRAMES,用于定义将在仪表盘中使用的时间框架。我们将该数组命名为“periods”,并包含以下特定的时间框架:PERIOD_M1(1分钟)、PERIOD_M5(5分钟)、PERIOD_H1(1小时)、PERIOD_H4(4小时)和PERIOD_D1(1天)。通过将这些时间框架列在“periods”数组中,我们确保仪表盘将显示每个不同时间框架下的RSI值,从而提供跨多个时间框架的市场趋势全方位视图。该设置将允许我们稍后在数组中迭代,并为每个指定的时间框架统一应用计算和创建按钮。用于定义数组的数据类型变量是一个枚举,它包含MetaTrader 5(MT5)中所有可用的时间框架。只要有明确提供,您就可以使用其中的任何一个。以下是您可以使用的所有时间框架的列表:

TIMEFRAMES ENUMERATION

最后,在全局变量中,我们仍然需要创建一个指示器句柄,以保存我们的指示器和一个数组,该数组用于存储在不同的时间框架和交易品种中使用的指示器数据。这可以通过以下代码段来实现:

// Global variables
int handleName_Id; // Variable to store the handle ID for the RSI indicator
double rsi_Data_Val[]; // Array to store the RSI values

我们声明了一个名为“handleName_Id”的整型变量,用于存储RSI指示器的句柄ID。当我们为特定货币对和时间框架创建一个RSI指示器时,它会返回一个唯一的句柄ID。然后,该ID将被存储在指示器句柄中,这样我们就可以在后续操作中引用这个特定的RSI指示器,例如检索其值以供进一步分析。此时,第二个变量数组就派上了用场。我们定义了一个名为“rsi_Data_Val”的双精度浮点数动态数组,用于存储从指示器获取的RSI值。当我们检索RSI数据时,这些值将被复制到该数组中。通过将这个数组设置为全局变量,确保了RSI数据在整个程序中都是可访问的,这样我们就可以使用这些值进行实时更新,并在仪表盘按钮上显示它们。

我们的仪表盘将在EA初始化部分创建,因此让我们来看看初始化事件处理器的作用。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {

...
   
   return(INIT_SUCCEEDED); // Return initialization success
}

OnInit函数是一个事件处理程序,它在初始化实例时被调用,以执行必要的初始化操作(如果有需要时)。其设计目的是执行所有必要的初始设置任务,以确保EA能够正常运行。这包括创建用户界面元素、初始化变量以及为程序正常运行设置必要的条件。在我们的案例中,将使用它来初始化仪表盘元素。

然后,我们通过输入按钮的名称并提供其参数来调用创建按钮的函数。

   // Create the main button for the pair with specific properties
   createButton(BTN1, "PAIR", 600, 50, XS1, YS1, clrW, clrGray, 15, clrGray);

在这里,我们的按钮名称是“BTN1”,这是从宏定义中得出的。第二个参数用于指定要在按钮附近显示的文字或文本。从图表窗口的右上角开始,沿x轴(即时间和日期刻度)的距离是600像素,沿y轴(即价格刻度)的距离是50像素。宽度是从已经预定义的宏中获取的,以便于后续按钮的引用。宽度为“XS1”,即90像素,高度为“YSI”,即25像素。我们选择文本颜色为“clrW”,即白色,背景为灰色,字体大小为15,按钮边框颜色也为灰色。要大致获取像素范围,您可以将图表缩小到0,此时两个十字光标坐标之间的柱形图数量等于水平刻度上的像素数。下面举例说明。

CROSSHAIR PIXELS

另一个参数被省略了,这意味着将自动应用默认值,即字体名称为“Arial Rounded MT Bold”。编译后,我们目前得到如下结果:

PAIR BUTTON

即使我们有如下默认值的所有参数,结果也总是相同。

   // Create the main button for the pair with specific properties
   createButton(BTN1, "PAIR", 600, 50, XS1, YS1, clrW, clrGray, 15, clrGray, "Arial Rounded MT Bold");

接下来,我们会为每个预定义的时间框架创建按钮,以显示相应的RSI信息。我们可以通过静态地调用函数来为每个要新建的元素创建按钮,但这会使我们的代码变得非常冗长。因此,我们将采用动态格式,这将帮助我们在受控的迭代中创建按钮。

   // Loop to create buttons for each timeframe with the corresponding RSI label
   for(int i = 0; i < ArraySize(periods); i++) {
      createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI 14", (600 - XS1) + i * -XS2, 50, XS2, YS1, clrW, clrGray, 13, clrGray);
   }

我们启动一个for循环,遍历“periods”数组,该数组包含我们之前定义并填充的特定时间段。我们使用ArraySize函数来确保循环覆盖到数组中的所有元素。这个函数很简单,只需要一个参数,并返回选定数组(在我们的例子中是“periods”)的元素数量。在循环内部,我们调用“createButton”函数为每个时间段创建一个按钮。按钮的名称是通过将宏“Desc”(定义为“Desc ”)与转换为字符串的索引“i”(使用IntegerToString函数)拼接而成的,确保每个按钮都有一个唯一的名称,如“Desc 0”、“Desc 1”等。该函数将一个整数类型的值转换为指定长度的字符串,并返回得到的字符串。它接受三个输入参数,但后面两个参数是可选的。第一个参数是要转换的数字,在我们的例子中是索引“i”。我们使用一个自定义函数来生成按钮的标签,即“truncPrds”函数,该函数将时间段的字符串表示截断为更易读的格式(例如,“M1”、“M5”),并附加“RSI 14”以指示此按钮将显示周期为14的RSI值。该函数的代码段如下:

// Function to truncate the ENUM_TIMEFRAMES string for display purposes
string truncPrds(ENUM_TIMEFRAMES period) {
   // Extract the timeframe abbreviation from the full ENUM string
   string prd = StringSubstr(EnumToString(period), 7);
   return prd; // Return the truncated string
}

该函数接受一个类型为ENUM_TIMEFRAMES(时间段)的参数,并使用EnumToString函数将该ENUM(枚举)值转换为字符串表示。这通常会生成一个包含前缀的字符串,而我们显示时并不需要这个前缀。为了去除这个不必要的部分,我们使用StringSubstr函数从第7个字符开始提取子字符串。这实际上会将字符串截断为更短、更易读的格式,适合在用户界面中显示。最后,我们返回截断后的字符串,为用户界面中的按钮提供一个清晰简洁的时间段表示,可用于按钮的标签。为了理解我们为什么需要这个函数,下面是一个示例说明。

采用的逻辑:

      Print("BEFORE: ",EnumToString(periods[i]));
      Print("AFTER: ",truncPrds(periods[i]));

打印声明。

TRUNCATED PERIODS

现在您可以清楚地看出,未截断的时间段比截断后的时间段要长,并且它们确实包含了不必要的7个字符“PERIOD_”(包括下划线),这些字符最终会被我们删除。

每个按钮的x坐标都是动态计算的。首先,从一个初始值600减去“XS1”开始,然后通过减去“XS2”(按钮宽度)乘以索引“i”的值来进行调整。这种定位方式可以确保每个按钮都被放置在前一个按钮的左侧,从而实现水平对齐。y坐标则固定在图表顶部以下50像素的位置,以保持所有时间段按钮的垂直位置一致。接下来,我们使用由宏“XS2”定义的宽度(100像素)和“YS1”定义的高度(25像素)来设置按钮的尺寸。此外,我们还将每个按钮的文本颜色设置为“clrWhite”(白色),背景颜色设置为灰色,字体大小设置为13,最后边框颜色也设置为灰色。编译后,我们得到如下结果:

PERIODS GRAY

只是为了举例说明,但背景颜色可能并不适合您。您可以随意使用您认为合适的颜色。您只需要将颜色更改为符合您审美偏好的颜色即可。例如,如果您想要一个蓝色背景和一个黑色边框,您可以通过以下代码更改:

      createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI 14", (600 - XS1) + i * -XS2, 50, XS2, YS1, clrW, clrDodgerBlue, 13, clrBlack);

这里,我们将背景颜色更改为道奇蓝(dodger blue),并将边框颜色更改为黑色。编译后,我们得到如下结果:

PERIODS ALTERED COLORS

注意,这些按钮变得很时尚且吸引人。然而,为了保持文章的一致性,我们还是使用那些不张扬的默认颜色。在文章后面的部分,当我们创建和识别有效信号时,我们会使用醒目的颜色。

接下来,我们需要再次创建一个垂直的品种按钮系列,并同样采用动态表示格式。我们不需要在数组中定义特定的交易品种并将其用于可视化。我们可以自动访问交易商提供的交易品种。为了实现这一点,我们使用一个for循环来遍历交易商提供的所有交易品种,并在必要时选择所需的品种。

   // Loop to create buttons for each symbol
   for(int i = 0; i < SymbolsTotal(true); i++) {

   ...

   }

为了获取交易商提供的交易品种,我们使用MQL5的内置函数SymbolsTotal。该函数返回可用的交易品种数量(在市场观察中选择的或全部品种)。它只需要一个布尔类型的输入参数,如果将该参数设置为true,则函数将返回在市场观察中选择的品种数量。如果值为false,将返回所有交易品种的总数。为了更清楚地理解这一点,让我们在函数的输入参数值设置为false时打印其返回值。

   // Loop to create buttons for each symbol
   for(int i = 0; i < SymbolsTotal(false); i++) {
      Print("Index ",i,": Symbol = ",SymbolName(i,false));

      ...

   }

在for循环中,我们将选定值的标识设置为false,以便我们可以访问整个交易品种列表。在打印语句中,我们使用了另一个SymbolName函数,通过位置获取列表中的交易品种名称。该函数的第二个参数基于市场观察的交易品种选择标准指定请求模式。如果值为true,则从中选定的品种列表中获取交易品种。如果值为false,则从通用列表中获取交易品种。编译后,我们得到如下结果: 

ALL SYMBOLS 1

继续。

ALL SYMBOLS 2

您可以看到,所有交易品种都已被选中。在这种情况下,有396个品种被选中并打印出来。现在想象一下,如果您在图表上显示所有品种的情况。那太多了,对吧?无法将它们全部显示在一张图表上,如果您尝试这样做,字体会非常小,以至于您无法轻松看到品种,或者您的图表会变得杂乱无章。此外,并不是所有品种都对您有用。在这一点上,您可能会考虑只选择几个优先级最高的品种,而将其他品种排除在外。假设您已经在市场观察部分选择了您喜欢的最佳货币对。这是您放置您最喜欢的交易品种的位置,以便一眼就能看到价格报价并监控它们的走势。因此,我们将从市场观察中选择要显示的品种。为了实现这一点,我们将这两个函数的值设置为true。

   // Loop to create buttons for each symbol
   for(int i = 0; i < SymbolsTotal(true); i++) {
      Print("Index ",i,": Symbol = ",SymbolName(i,true));

      ...

   }

编译后,我们得到如下结果:

MARKET WATCH SYMBOLS PRINT OUT

请注意,市场观察中的12个品种(已按时间顺序排列)被打印于工具箱日志中。为了更容易区分和查阅,我们已分别用红色和黑色高亮显示它们。为了创建动态垂直按钮,我们采用以下逻辑:

         createButton(Symb + IntegerToString(i), SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrW, clrGray, 11, clrGray);

这里,我们首先通过连接宏“Symb”(定义为“Symb ”)和转换为字符串的索引“i”(使用IntegerToString函数)来构造按钮的名称,确保每个按钮都有一个唯一的标识符,如“Symb 0”、“Symb 1”等。我们将按钮的标签设置为索引“i”处的交易品种名称,这是通过使用SymbolName函数获取的,该函数检索交易品种名称,其中true参数确保名称在Market Watch列表中。我们将按钮的x坐标设置为600像素,使所有品种按钮垂直对齐。y坐标是动态计算的,为(50 + "YS1") + "i"乘以"YS1",通过将按钮的高度(“YS1”,即25像素)乘以索引“i”并加到初始偏移量50像素加上“YS1”上,从而将每个按钮依次向下定位。我们使用“XS1”的值(宽度为90像素)和“YS1”的值(高度为25像素)来指定按钮的尺寸。我们将文本颜色设置为“clrW”(白色),背景颜色设置为灰色,字体大小设置为11,最后边框颜色也设置为灰色。编译后,结果如下:

SYMBOLS COLUMN

这是市场观察中的所有交易品种列表。如果你添加或删除了某些品种,它们将会被自动创建或移除,这也证实了我们使用动态逻辑来创建它们的必要性。让我们删除其中的一些对,特别是最后三对,然后看看是否确实如此。

LESSER SYMBOLS

您可以看到,这是自动完成的。现在,我们将把删除的交易品种重新添加回来,并继续制定一个逻辑,以帮助识别并优先处理当前选中的品种,并将其与其他品种按钮区分开来。

      if (SymbolName(i, true) == _Symbol) {
         createButton(Symb + IntegerToString(i), "*" + SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrB, clrLimeGreen, 11, clrW);
      } else {
         createButton(Symb + IntegerToString(i), SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrW, clrGray, 11, clrGray);
      }

这里,我们不是创建具有相似外观的按钮,而是创建按钮并区分当前活跃交易品种按钮的外观。我们首先从检查索引“i”处的品种名称是否与通过预定义变量_Symbol检索到的当前活跃品种相匹配开始。如果匹配,我们将创建一个具有独特外观的按钮,以突出显示活跃品种。对于活跃品种,我们将文本颜色设置为“clrB”(黑色),背景颜色设置为亮绿色,字体大小设置为11,边框颜色设置为“clrW”(白色)。这种独特的配色方案将突出显示活跃品种,便于识别。如果品种与活跃品种不匹配,则默认函数将接管并创建具有标准外观方案的按钮,确保非活跃品种以一致且视觉上区别于活跃品种的方式显示。编译后,我们达成了以下效果:

SELECTED SYMBOL

在创建完所有所需的交易品种按钮后,让我们添加一个页脚来表示品种可视化矩阵的结束,并在其中添加一些摘要信息。以下代码段已做过相应调整。

      // Create the base button for the RSI Dashboard at the end
      if (i == SymbolsTotal(true) - 1) {
         createButton(Base + IntegerToString(i), "RSI DashBoard", 600, (50 + YS1) + (i * YS1) + YS1, XS1 + XS2 * ArraySize(periods), YS1, clrW, clrGray, 11, clrGray);
      }

为了创建RSI仪表盘的基准按钮,并确保它位于交易品种列表的末尾,我们首先检查当前索引“i”是否等于品种总数减1,这意味着它是列表中的最后一个品种。如果条件为true,我们将继续创建基准按钮。我们使用“createButton”函数来定义这个基准按钮的外观和位置。为了构建按钮的名称,我们将“Base”字符串与索引“i”拼接起来,提供一个唯一的标识符,并将其标签设置为“RSI仪表盘”。这个标识符可以是一个任意值,您可以根据需要更改为其他内容。然后,我们将按钮的位置设置为x坐标为600像素,y坐标计算为(50 + “YS1”)+ (i * “YS1”) + “YS1”,确保它出现在最后一个品种按钮的正下方。我们定义按钮的宽度为“XS1”+“XS2”乘以ArraySize函数返回的值(该函数返回“periods”数组中的总元素数),以覆盖所有时间框架按钮的宽度总和,而高度则定义为“YS1”(25像素)。按钮的颜色方案采用标准外观。通常,这个基准按钮将作为整个RSI仪表盘的清晰标签,为品种列表的底部提供一个视觉锚点。以下是关键成果概述。

BUTTON BASE

最后,我们需要创建按钮,以显示每个品种和时间框架组合的RSI值。以下代码段用于实现这一目标:

      // Loop to create buttons for RSI values for each symbol and timeframe
      for (int j = 0; j < ArraySize(periods); j++) {
         createButton(RSI + IntegerToString(j) + " " + SymbolName(i, true), "-/-", (600 - XS1) + (j * -XS2), (50 + YS1) + (i * YS1), XS2 - 1, YS1 - 1, clrB, clrW, 12, clrW);
      }
   }

我们使用整数计数器“j”来启动循环,它表示时间框架的索引。对于每个时间框架,我们调用“createButton”函数来设置一个按钮,该按钮将显示当前品种和时间框架的RSI值。我们通过将RSI字符串与索引“j”和品种名称拼接在一起来生成按钮的名称,从而确保每个按钮都有唯一的标识符。我们将按钮的标签设置为“-/-”,稍后将用实际的RSI值进行更新。目前,让我们先确保能够生成品种-时间框架网格布局。我们将按钮的x坐标设置为(600 - “XS1”)+ (j * - “XS2”),这样按钮就会按水平方向排列,通过调整以考虑按钮宽度,每个按钮之间的间隔为“XS2”(100像素)。同样地,我们将y坐标设置为(50 + “YS1”)+ (i * “YS1”),以确保按钮根据品种索引垂直放置。按钮的宽度为“XS2” - 1,高度为“YS1” - 1。减去1是为了确保我们留出1像素的边界,从而产生按钮网格的错觉。然后,我们为按钮设置颜色方案,文本颜色为“clrB”(黑色),背景颜色为“clrW”(白色),字体大小为12,边框颜色为“clrW”(白色)。这种设置将以网格布局排列RSI按钮,每个按钮都与特定的品种和时间框架相关联,从而提供不同时间段RSI值的清晰且结构化的视图。编译后,我们得到如下结果:

BUTTON GRID INITIALIZATION

既然我们已经创建了仪表盘的一般网格布局,接下来只需要获取指标值并更新按钮即可。但在此之前,我们可以通过在图表上任意位置右键点击,并在弹出的窗口中选择“对象列表”选项来预览已创建的对象。或者,您也可以按“Ctrl + B”快捷键。在弹出的窗口中,点击“列出所有”,然后您将看到我们创建的所有元素列表。

LIST OF OBJECTS

现在我们确定在图表上按照时间顺序创建了具有各自唯一名称的对象。确保我们不会遗漏任何细节。例如,您可以看到在交易品种方面,第一个品种被命名为“Symb 0”,这使其与其他品种区分开来。如果我们没有将品种的名称与相应的对象名称拼接在一起,那么所有按钮都将具有相同的品种名称,这将在创建其他按钮时导致错误,因为一旦创建了某个按钮,它的名称就固定了,其他按钮不能使用相同的名称。所以,从技术上讲,只会创建一个按钮,而其余的将被忽略。这是多么巧妙的设计啊!我们现在继续创建初始化值。

   // Loop to initialize RSI values and update buttons
   for(int i = 0; i < SymbolsTotal(true); i++) {
      for (int j = 0; j < ArraySize(periods); j++) {
         // Get the handle ID for the RSI indicator for the specific symbol and timeframe
         handleName_Id = iRSI(SymbolName(i, true), periods[j], 14, PRICE_CLOSE);
         ArraySetAsSeries(rsi_Data_Val, true); // Set the array to be used as a time series
         CopyBuffer(handleName_Id, 0, 0, 1, rsi_Data_Val); // Copy the RSI values into the array

         ...

   }

在这里,我们使用两个循环来遍历每个交易品种和时间框架,以初始化RSI值并更新相应的按钮。我们首先使用一个外部循环,其索引为“i”,该循环在市场观察中选择一个交易品种。在这个外部循环里,我们有一个内部for循环,它遍历为每个选定品种定义的所有时间框架。这意味着,例如,当我们选择“AUDUSD”时,我们会遍历所有定义的时间框架,即“M1”、“M5”、“H1”、“H4”和“D1”。为了更容易理解这个逻辑,让我们打印出来看一下。

   // Loop to initialize RSI values and update buttons
   for(int i = 0; i < SymbolsTotal(true); i++) {
      Print("SELECTED SYMBOL = ",SymbolName(i, true));
      for (int j = 0; j < ArraySize(periods); j++) {
         Print("PERIOD = ",EnumToString(periods[j]));

         ...

   }

这是我们现有的。

SYMBOLS PRINT-OUT

接下来,对于每个品种和时间框架的组合,我们使用iRSI函数来获取RSI指标的句柄。该函数的参数包括:品种、时间框架、RSI周期(设为14)以及价格类型(设为收盘价)。句柄“handleName_Id”只是一个整数,它被定义和存储在我们计算机内存的某个位置,并允许我们与RSI指标数据进行交互。在默认情况下,这个整数从索引10开始,如果创建了其他指标,它会递增1,即10、11、12、13……以此类推。为了说明这一点,让我们打印出来看一下。

         Print("PERIOD = ",EnumToString(periods[j]),": HANDLE ID = ",handleName_Id);

编译后,我们得到如下结果:

HANDLES 1

您可以看到,句柄的ID从10开始,由于我们有12个交易品种,每个品种有5个时间段,因此我们预计总共有(12 * 5 = 60)60个指标句柄。但是,由于我们的索引从10开始,因此我们需要将结果中的前9个值也包括在内,以得到最终的句柄ID。用数学公式表示,这将是60 + 9,结果为69(60 + 9 = 69)。让我们通过可视化表示来确认是否正确。

HANDLES 2

那是正确的。接下来,我们通过调用ArraySetAsSeries函数并将标识设置为true来确认操作,从而将“rsi_Data_Val”数组作为时间序列。该配置将确保最新的RSI值位于数组的开头。然后,我们使用CopyBuffer函数将RSI值复制到数组中。其中,0表示缓冲区索引,0是起始位置,1指定要检索的数据点数量,“rsi_Data_Val”是目标数组,我们存储检索到的数据以供进一步分析。让我们将数据打印到日志中,看一下我们得到了什么。我们使用ArrayPrint函数来打印简单的动态数组

         ArrayPrint(rsi_Data_Val);

以下是我们得到的:

DATA ARRAY PRINT

太棒了!我们得到了正确的数据。您可以在指标窗口和数据窗口中,通过与我们检索到的数据进行对比来确认这些数据。这清楚地说明了我们已经获取到数据,并且可以继续进行下一步。由此可以看出,确认我们所做的每一件事情的重要性。每当您在仪表盘上添加新的逻辑时,建议您编译并运行测试,以确保得到的结果符合预期。现在,我们只需要使用指标值来更新仪表盘,然后就完成了。

         // Update the button with the RSI value and colors
         update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), clrBlack, clrW_Gray, clrWhite);

这里,我们使用一个自定义函数“update_Button”来根据相应的指标数据更新仪表盘上的按钮。该函数的逻辑如下:

//+------------------------------------------------------------------+
//| Function to update a button                                      |
//+------------------------------------------------------------------+
bool update_Button(string objName, string text, color clrTxt = clrBlack,
                  color clrBG = clrWhite, color clrBorder = clrWhite
) {
   int found = ObjectFind(0, objName); // Find the button by name
   // Check if the button exists
   if (found < 0) {
      ResetLastError(); // Reset the last error code
      Print("UNABLE TO FIND THE BTN: ERR Code: ", GetLastError()); // Print error message if button is not found
      return (false); // Return false if button is not found
   } else {
      // Update button properties
      ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color
      ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBG); // Set background color
      ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color
      
      ChartRedraw(0); // Redraw the chart to reflect the updated button
      return (true); // Return true if update is successful
   }
}

这个函数与我们之前用来创建按钮的函数非常相似。不同之处在于,我们不是创建按钮,而是使用ObjectFind函数来查找按钮。如果查找成功,该函数将返回找到对象的子窗口编号(0表示图表的主窗口)。如果未找到对象,该函数将返回一个负数。因此,如果返回值小于0,则表示它是一个负数,表明对象不存在,我们会通过打印错误代码来报告错误,在此之前,我们会重置之前可能存在的错误,并返回false以终止函数。如果查找成功,我们找到对象并继续更新对象的属性,即文本、文本的配色方案、背景和边框。编译后,我们得到如下结果:

FILLED DATA

很好!现在指标数据已经显示在网格中了。请看,只用了一行代码,数据就被正确地映射过来了。同时也请确认,我们之前的数据,即当前交易品种1小时时间框架下的55.27,已经被正确地填入了。为了让它看起来更加酷炫,我们来根据超买和超卖水平定义市场入场条件,并改变颜色以便于用户参考。因此,现在我们不使用固定的颜色,而是使用动态的颜色。

         // Declare variables for button colors
         color text_clr, bg_clr, border_clr;
         
         // Determine button colors based on RSI value
         if (rsi_Data_Val[0] < 30) {
            text_clr = clrWhite; bg_clr = clrGreen; border_clr = clrGreen;
         } else if (rsi_Data_Val[0] > 70) {
            text_clr = clrWhite; bg_clr = clrRed; border_clr = clrRed;
         } else {
            text_clr = clrBlack; bg_clr = clrW_Gray; border_clr = clrWhite;
         }

         // Update the button with the RSI value and colors
         update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), text_clr, bg_clr, border_clr);

首先,我们定义三个用于按钮颜色的变量:“text_clr”代表文本颜色,“bg_clr”代表背景颜色,“border_clr”代表边框颜色。接下来,我们根据存储在RSI数据数组中的RSI值来确定适当的颜色。如果RSI值低于30,这通常表明资产被超卖,我们将文本颜色设置为白色,背景颜色和边框颜色设置为绿色,这种绿色高亮按钮的组合表示潜在的买入机会。

同样地,如果RSI值高于70,这表明资产被超买,我们将文本颜色设置为白色,背景颜色和边框颜色设置为红色,这种红色按钮表示潜在的卖出机会。对于RSI值在30到70之间的情况,我们保持默认的颜色方案,反映标准或中性状态。最后,我们通过调用“update_Button”函数并传递相应的按钮参数来更新按钮的外观。这确保了仪表盘上的每个按钮都能准确反映当前的RSI状态,并且直观地传达市场状况。为了查看这一里程碑式的进展,一个可视化表示如下:

FINAL ONINIT MILESTONE

完美!我们现在创建了一个动态且响应灵敏的指标仪表盘,其能够在图表上显示当前的市场状况,并启用方便作为参考的高亮显示功能。 

负责初始化指标仪表盘的完整源代码如下:

//+------------------------------------------------------------------+
//|                                       ADVANCED IND DASHBOARD.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

// Define button identifiers and properties
#define BTN1 "BTN1"
#define Desc "Desc "
#define Symb "Symb "
#define Base "Base "
#define RSI "RSI "
#define XS1 90
#define XS2 100
#define YS1 25
#define clrW clrWhite
#define clrB clrBlack
#define clrW_Gray C'230,230,230'

// Define the timeframes to be used
ENUM_TIMEFRAMES periods[] = {PERIOD_M1, PERIOD_M5, PERIOD_H1, PERIOD_H4, PERIOD_D1};

// Function to truncate the ENUM_TIMEFRAMES string for display purposes
string truncPrds(ENUM_TIMEFRAMES period) {
   // Extract the timeframe abbreviation from the full ENUM string
   string prd = StringSubstr(EnumToString(period), 7);
   return prd; // Return the truncated string
}

// Global variables
int handleName_Id; // Variable to store the handle ID for the RSI indicator
double rsi_Data_Val[]; // Array to store the RSI values

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   // Create the main button for the pair with specific properties
   createButton(BTN1, "PAIR", 600, 50, XS1, YS1, clrW, clrGray, 15, clrGray);

   // Loop to create buttons for each timeframe with the corresponding RSI label
   for(int i = 0; i < ArraySize(periods); i++) {
      //Print("BEFORE: ",EnumToString(periods[i]));
      //Print("AFTER: ",truncPrds(periods[i]));
      createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI 14", (600 - XS1) + i * -XS2, 50, XS2, YS1, clrW, clrGray, 13, clrGray);
   }
   
   // Loop to create buttons for each symbol
   for(int i = 0; i < SymbolsTotal(true); i++) {
      //Print("Index ",i,": Symbol = ",SymbolName(i,true));
      // Check if the symbol is the current symbol being traded
      if (SymbolName(i, true) == _Symbol) {
         createButton(Symb + IntegerToString(i), "*" + SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrB, clrLimeGreen, 11, clrW);
      } else {
         createButton(Symb + IntegerToString(i), SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrW, clrGray, 11, clrGray);
      }

      // Create the base button for the RSI Dashboard at the end
      if (i == SymbolsTotal(true) - 1) {
         createButton(Base + IntegerToString(i), "RSI DashBoard", 600, (50 + YS1) + (i * YS1) + YS1, XS1 + XS2 * ArraySize(periods), YS1, clrW, clrGray, 11, clrGray);
      }
      
      // Loop to create buttons for RSI values for each symbol and timeframe
      for (int j = 0; j < ArraySize(periods); j++) {
         createButton(RSI + IntegerToString(j) + " " + SymbolName(i, true), "-/-", (600 - XS1) + (j * -XS2), (50 + YS1) + (i * YS1), XS2 - 1, YS1 - 1, clrB, clrW, 12, clrW);
      }
   }
   
   // Loop to initialize RSI values and update buttons
   for(int i = 0; i < SymbolsTotal(true); i++) {
      //Print("SELECTED SYMBOL = ",SymbolName(i, true));
      for (int j = 0; j < ArraySize(periods); j++) {
         //Print("PERIOD = ",EnumToString(periods[j]));
         // Get the handle ID for the RSI indicator for the specific symbol and timeframe
         handleName_Id = iRSI(SymbolName(i, true), periods[j], 14, PRICE_CLOSE);
         //Print("PERIOD = ",EnumToString(periods[j]),": HANDLE ID = ",handleName_Id);
         ArraySetAsSeries(rsi_Data_Val, true); // Set the array to be used as a time series
         CopyBuffer(handleName_Id, 0, 0, 1, rsi_Data_Val); // Copy the RSI values into the array
         //ArrayPrint(rsi_Data_Val);
         // Declare variables for button colors
         color text_clr, bg_clr, border_clr;
         
         // Determine button colors based on RSI value
         if (rsi_Data_Val[0] < 30) {
            text_clr = clrWhite; bg_clr = clrGreen; border_clr = clrGreen;
         } else if (rsi_Data_Val[0] > 70) {
            text_clr = clrWhite; bg_clr = clrRed; border_clr = clrRed;
         } else {
            text_clr = clrBlack; bg_clr = clrW_Gray; border_clr = clrWhite;
         }

         // Update the button with the RSI value and colors
         update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), text_clr, bg_clr, border_clr);
      }
   }
   
   return(INIT_SUCCEEDED); // Return initialization success
}

//+------------------------------------------------------------------+
//| Function to create a button                                      |
//+------------------------------------------------------------------+
bool createButton(string objName, string text, int xD, int yD, int xS, int yS,
   color clrTxt, color clrBg, int fontSize = 12, color clrBorder = clrNONE,
   string font = "Arial Rounded MT Bold"
) {
   ResetLastError(); // Reset the last error code
   // Attempt to create the button
   if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) {
      Print(__FUNCTION__, ": failed to create Btn: ERR Code: ", GetLastError()); // Print error message if button creation fails
      return (false); // Return false if creation fails
   }
   // Set button properties
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // Set X distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Set Y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); // Set X size
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); // Set Y size
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); // Set corner position
   ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text
   ObjectSetString(0, objName, OBJPROP_FONT, font); // Set font type
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); // Set font size
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); // Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Set background property
   ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Set button state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Set if the button is selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Set if the button is selected
   
   ChartRedraw(0); // Redraw the chart to reflect the new button
   return (true); // Return true if creation is successful
}

//+------------------------------------------------------------------+
//| Function to update a button                                      |
//+------------------------------------------------------------------+
bool update_Button(string objName, string text, color clrTxt = clrBlack,
                  color clrBG = clrWhite, color clrBorder = clrWhite
) {
   int found = ObjectFind(0, objName); // Find the button by name
   // Check if the button exists
   if (found < 0) {
      ResetLastError(); // Reset the last error code
      Print("UNABLE TO FIND THE BTN: ERR Code: ", GetLastError()); // Print error message if button is not found
      return (false); // Return false if button is not found
   } else {
      // Update button properties
      ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color
      ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBG); // Set background color
      ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color
      
      ChartRedraw(0); // Redraw the chart to reflect the updated button
      return (true); // Return true if update is successful
   }
}

即使我们已经创建了包含所有元素的仪表盘,我们仍然需要在每个tick时对其进行更新,以确保仪表盘上的数据反映最新的数据。这是通过OnTick事件处理器来实现的。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {

...

}

这是一个void类型的事件处理器函数,每当价格报价发生变化时,就会调用它。为了更新指标值,我们需要在这里运行部分初始化代码,以便获取最新的值。 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   // Loop to update RSI values and buttons on each tick
   for(int i = 0; i < SymbolsTotal(true); i++) {
      for (int j = 0; j < ArraySize(periods); j++) {
         // Get the handle ID for the RSI indicator for the specific symbol and timeframe
         handleName_Id = iRSI(SymbolName(i, true), periods[j], 14, PRICE_CLOSE);
         ArraySetAsSeries(rsi_Data_Val, true); // Set the array to be used as a time series
         CopyBuffer(handleName_Id, 0, 0, 1, rsi_Data_Val); // Copy the RSI values into the array
         
         // Declare variables for button colors
         color text_clr, bg_clr, border_clr;
         
         // Determine button colors based on RSI value
         if (rsi_Data_Val[0] < 30) {
            text_clr = clrWhite; bg_clr = clrGreen; border_clr = clrGreen;
         } else if (rsi_Data_Val[0] > 70) {
            text_clr = clrWhite; bg_clr = clrRed; border_clr = clrRed;
         } else {
            text_clr = clrBlack; bg_clr = clrW_Gray; border_clr = clrWhite;
         }

         // Update the button with the RSI value and colors
         update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), text_clr, bg_clr, border_clr);
      }
   }
}

这里,我们只需复制并粘贴初始化部分中包含的两个循环(内循环和外循环)的代码段,并更新指标值。这确保了每个tick时,我们都会将值更新为最新检索到的数据。这样,仪表盘就变得非常动态、实时且方便使用。在图形交换格式(GIF)上,这是我们达成的里程碑:

LIVE UPDATES GIF

最后,当EA从图表上移除时,我们需要清除创建的对象,即指标仪表盘。需确保指标仪表盘被清理,从而使图表保持整洁。因此,该函数对于维护一个干净且高效的交易环境至关重要。这是在OnDeinit事件处理器函数中实现的,每当将EA从图表上移除时,该函数就会被调用。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   // remove all dashboard objects
   ObjectsDeleteAll(0,BTN1);
   ObjectsDeleteAll(0,Desc);
   ObjectsDeleteAll(0,Symb);
   ObjectsDeleteAll(0,Base);
   ObjectsDeleteAll(0,RSI);
   
   ChartRedraw(0);
}

这里,我们调用ObjectDeleteAll函数来删除所有具有指定前缀名称的对象。该函数使用对象名称中的前缀来删除指定类型的所有对象。此处的逻辑直接了当。我们为每个定义的前缀调用该函数:“BTN1”、“Desc”、“Symb”、“Base”和“RSI”,这将确保删除与这些前缀关联的所有对象,从而有效地从图表中删除所有按钮和图形元素。最后,我们调用ChartRedraw函数来刷新图表,并反映这些对象的删除情况,确保图表已更新且没有程序创建的任何残留元素。以下是我们得到的:

EXPERT REMOVAL GIF

现在,我们在MQL5中创建了一个功能完备的指标仪表盘。负责创建指标仪表盘的完整源代码如下:

//+------------------------------------------------------------------+
//|                                       ADVANCED IND DASHBOARD.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

// Define button identifiers and properties
#define BTN1 "BTN1"
#define Desc "Desc "
#define Symb "Symb "
#define Base "Base "
#define RSI "RSI "
#define XS1 90
#define XS2 100
#define YS1 25
#define clrW clrWhite
#define clrB clrBlack
#define clrW_Gray C'230,230,230'

// Define the timeframes to be used
ENUM_TIMEFRAMES periods[] = {PERIOD_M1, PERIOD_M5, PERIOD_H1, PERIOD_H4, PERIOD_D1};

// Function to truncate the ENUM_TIMEFRAMES string for display purposes
string truncPrds(ENUM_TIMEFRAMES period) {
   // Extract the timeframe abbreviation from the full ENUM string
   string prd = StringSubstr(EnumToString(period), 7);
   return prd; // Return the truncated string
}

// Global variables
int handleName_Id; // Variable to store the handle ID for the RSI indicator
double rsi_Data_Val[]; // Array to store the RSI values

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   // Create the main button for the pair with specific properties
   createButton(BTN1, "PAIR", 600, 50, XS1, YS1, clrW, clrGray, 15, clrGray);

   // Loop to create buttons for each timeframe with the corresponding RSI label
   for(int i = 0; i < ArraySize(periods); i++) {
      //Print("BEFORE: ",EnumToString(periods[i]));
      //Print("AFTER: ",truncPrds(periods[i]));
      createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI 14", (600 - XS1) + i * -XS2, 50, XS2, YS1, clrW, clrGray, 13, clrGray);
   }
   
   // Loop to create buttons for each symbol
   for(int i = 0; i < SymbolsTotal(true); i++) {
      //Print("Index ",i,": Symbol = ",SymbolName(i,true));
      // Check if the symbol is the current symbol being traded
      if (SymbolName(i, true) == _Symbol) {
         createButton(Symb + IntegerToString(i), "*" + SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrB, clrLimeGreen, 11, clrW);
      } else {
         createButton(Symb + IntegerToString(i), SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrW, clrGray, 11, clrGray);
      }

      // Create the base button for the RSI Dashboard at the end
      if (i == SymbolsTotal(true) - 1) {
         createButton(Base + IntegerToString(i), "RSI DashBoard", 600, (50 + YS1) + (i * YS1) + YS1, XS1 + XS2 * ArraySize(periods), YS1, clrW, clrGray, 11, clrGray);
      }
      
      // Loop to create buttons for RSI values for each symbol and timeframe
      for (int j = 0; j < ArraySize(periods); j++) {
         createButton(RSI + IntegerToString(j) + " " + SymbolName(i, true), "-/-", (600 - XS1) + (j * -XS2), (50 + YS1) + (i * YS1), XS2 - 1, YS1 - 1, clrB, clrW, 12, clrW);
      }
   }
   
   // Loop to initialize RSI values and update buttons
   for(int i = 0; i < SymbolsTotal(true); i++) {
      //Print("SELECTED SYMBOL = ",SymbolName(i, true));
      for (int j = 0; j < ArraySize(periods); j++) {
         //Print("PERIOD = ",EnumToString(periods[j]));
         // Get the handle ID for the RSI indicator for the specific symbol and timeframe
         handleName_Id = iRSI(SymbolName(i, true), periods[j], 14, PRICE_CLOSE);
         //Print("PERIOD = ",EnumToString(periods[j]),": HANDLE ID = ",handleName_Id);
         ArraySetAsSeries(rsi_Data_Val, true); // Set the array to be used as a time series
         CopyBuffer(handleName_Id, 0, 0, 1, rsi_Data_Val); // Copy the RSI values into the array
         //ArrayPrint(rsi_Data_Val);
         // Declare variables for button colors
         color text_clr, bg_clr, border_clr;
         
         // Determine button colors based on RSI value
         if (rsi_Data_Val[0] < 30) {
            text_clr = clrWhite; bg_clr = clrGreen; border_clr = clrGreen;
         } else if (rsi_Data_Val[0] > 70) {
            text_clr = clrWhite; bg_clr = clrRed; border_clr = clrRed;
         } else {
            text_clr = clrBlack; bg_clr = clrW_Gray; border_clr = clrWhite;
         }

         // Update the button with the RSI value and colors
         update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), text_clr, bg_clr, border_clr);
      }
   }
   
   return(INIT_SUCCEEDED); // Return initialization success
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   // remove all dashboard objects
   ObjectsDeleteAll(0,BTN1);
   ObjectsDeleteAll(0,Desc);
   ObjectsDeleteAll(0,Symb);
   ObjectsDeleteAll(0,Base);
   ObjectsDeleteAll(0,RSI);
   
   ChartRedraw(0);
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   // Loop to update RSI values and buttons on each tick
   for(int i = 0; i < SymbolsTotal(true); i++) {
      for (int j = 0; j < ArraySize(periods); j++) {
         // Get the handle ID for the RSI indicator for the specific symbol and timeframe
         handleName_Id = iRSI(SymbolName(i, true), periods[j], 14, PRICE_CLOSE);
         ArraySetAsSeries(rsi_Data_Val, true); // Set the array to be used as a time series
         CopyBuffer(handleName_Id, 0, 0, 1, rsi_Data_Val); // Copy the RSI values into the array
         
         // Declare variables for button colors
         color text_clr, bg_clr, border_clr;
         
         // Determine button colors based on RSI value
         if (rsi_Data_Val[0] < 30) {
            text_clr = clrWhite; bg_clr = clrGreen; border_clr = clrGreen;
         } else if (rsi_Data_Val[0] > 70) {
            text_clr = clrWhite; bg_clr = clrRed; border_clr = clrRed;
         } else {
            text_clr = clrBlack; bg_clr = clrW_Gray; border_clr = clrWhite;
         }

         // Update the button with the RSI value and colors
         update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), text_clr, bg_clr, border_clr);
      }
   }
}
//+------------------------------------------------------------------+
//| Function to create a button                                      |
//+------------------------------------------------------------------+
bool createButton(string objName, string text, int xD, int yD, int xS, int yS,
   color clrTxt, color clrBg, int fontSize = 12, color clrBorder = clrNONE,
   string font = "Arial Rounded MT Bold"
) {
   ResetLastError(); // Reset the last error code
   // Attempt to create the button
   if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) {
      Print(__FUNCTION__, ": failed to create Btn: ERR Code: ", GetLastError()); // Print error message if button creation fails
      return (false); // Return false if creation fails
   }
   // Set button properties
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // Set X distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Set Y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); // Set X size
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); // Set Y size
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); // Set corner position
   ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text
   ObjectSetString(0, objName, OBJPROP_FONT, font); // Set font type
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); // Set font size
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); // Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Set background property
   ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Set button state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Set if the button is selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Set if the button is selected
   
   ChartRedraw(0); // Redraw the chart to reflect the new button
   return (true); // Return true if creation is successful
}

//+------------------------------------------------------------------+
//| Function to update a button                                      |
//+------------------------------------------------------------------+
bool update_Button(string objName, string text, color clrTxt = clrBlack,
                  color clrBG = clrWhite, color clrBorder = clrWhite
) {
   int found = ObjectFind(0, objName); // Find the button by name
   // Check if the button exists
   if (found < 0) {
      ResetLastError(); // Reset the last error code
      Print("UNABLE TO FIND THE BTN: ERR Code: ", GetLastError()); // Print error message if button is not found
      return (false); // Return false if button is not found
   } else {
      // Update button properties
      ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color
      ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBG); // Set background color
      ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color
      
      ChartRedraw(0); // Redraw the chart to reflect the updated button
      return (true); // Return true if update is successful
   }
}


结论

总结来说,在MetaQuotes Language 5(MQL5)中创建一个多品种、多周期的RSI指标仪表盘,为交易者提供了一个有用的市场分析工具。该仪表盘能够实时显示不同品种和时间框架下的RSI值,帮助交易者更快地做出明智的决策。

构建这个仪表盘的过程包括设置组件、创建按钮,并使用MQL5函数根据实时数据更新它们。最终产品功能完备且用户友好。

本文展示了如何使用MQL5来创建实用的交易工具。交易者可以复制或修改这个仪表盘以满足其特定的需求,从而在不断变化的市场环境中支持半自动和手动交易策略。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15356

重塑经典策略(第三部分):预测新高与新低 重塑经典策略(第三部分):预测新高与新低
在系列文章的第三部分中,我们将通过实证分析经典交易策略,探讨如何利用人工智能进行优化。本次研究聚焦于运用线性判别分析模型(LDA)预测价格走势中的更高高点与更低低点。
构建蜡烛图趋势约束模型(第7部分):为EA开发优化我们的模型 构建蜡烛图趋势约束模型(第7部分):为EA开发优化我们的模型
在本文中,我们将详细探讨为开发专家顾问(EA)所准备的指标的相关内容。我们不仅会讨论如何对当前版本的指标进行进一步改进,以提升其准确性和功能,还会引入全新的功能来标记退出点,以弥补之前版本仅具备识别入场点功能的不足。
开发多币种 EA 交易系统(第 14 部分):风险管理器的适应性交易量变化 开发多币种 EA 交易系统(第 14 部分):风险管理器的适应性交易量变化
之前开发的风险管理器仅包含基本功能,让我们试着探讨其可能的开发方式,使我们能够在不干扰交易策略逻辑的情况下改善交易结果。
如何将聪明资金概念(SMC)与 RSI 指标结合到 EA 中 如何将聪明资金概念(SMC)与 RSI 指标结合到 EA 中
聪明资金概念(结构突破)与 RSI 指标相结合,可根据市场结构做出明智的自动交易决策。