
使用MQL5经济日历进行交易(第二部分):创建新闻交易面板
引言
本文将在前文第一部分基于MQL5的经济日历的基础上进行展开,当时我们专注于掌握检索和分析经济新闻事件所必需的函数。现在,我们将通过创建一个新闻面板来迈出下一步,该面板为交易者提供了一个便捷的界面,用于实时访问关键经济数据。这个面板将通过突出显示可能影响市场走势的相关新闻事件,帮助简化决策过程。我们将涵盖的主题包括:
- 设计面板
- 在MQL5中设置面板
- 结论
本文中,我们旨在通过为MQL5中的经济新闻提供一个有效的实时监控工具,来增强交易体验。
设计面板
面板的设计是使用MQL5经济日历来作为监控经济新闻事件之工具的关键步骤。我们的目标是创建一个用户友好且视觉上吸引人的界面,清晰简洁地呈现重要信息。一个结构良好的面板将使我们能够快速评估经济事件同我们交易策略的相关性和影响。
在设计面板时,我们需要首先确定需要显示的关键组件。这些组件通常包括事件名称、预定时间、受影响的货币、重要性级别以及事件的简要描述。为了提高可用性,我们将这些信息以表格形式组织,每一行代表一个不同的经济事件。我们打算使表格易于阅读,使用不同重要性级别的对比颜色,以便快速识别高影响力事件。
为了使面板更具吸引力,我们将选择视觉元素,如边框、背景和字体,以创建一个清爽、专业的外观。布局将便于导航,确保我们能够迅速找到我们需要的信息,而不会被过多的细节所淹没。通过保持设计直观和简单,我们将使交易者能够专注于根据面板中显示的经济事件做出明智的决策。面板将按上述方式排列,并具有以下所示的组件:
在明确了目标之后,让我们深入了解自动化过程。在上一篇文章中,我们专注于掌握MQL5经济日历的函数,以有效地检索和分析经济新闻事件。如果您还没有,请参考该内容,以确保您在我们继续创建新闻面板时做好了充分的准备。让我们开始吧!
在MQL5中设置面板
在本节中,我们将专注于通过使用MQL5创建必要的面板元素来设置面板。首先,我们需要创建三个元素所需的函数:矩形标签、按钮和文本标签。这种方法将带来极大的好处,因为它允许我们在创建类似功能时重用相同的函数,消除了为每个新对象重复整个过程的麻烦。通过这样做,我们节省了时间和空间,使过程变得快速、简单,并保持代码片段简洁。
为了创建矩形标签,我们将创建一个接受十个参数的函数。这个函数将定义矩形的属性,例如它的位置、大小、颜色和样式,使我们能够根据面板设计要求定制标签的视觉外观。
//+------------------------------------------------------------------+ //| Function to create rectangle label | //+------------------------------------------------------------------+ bool createRecLabel(string objName, int xD, int yD, int xS, int yS, color clrBg, int widthBorder, color clrBorder = clrNONE, ENUM_BORDER_TYPE borderType = BORDER_FLAT, ENUM_LINE_STYLE borderStyle = STYLE_SOLID) { ... }
函数的标题说明了一切。这是一个名为“createRecLabel”的布尔函数,意味着它将返回两个布尔标志,在成功或失败的情况下分别返回true或false。为了便于理解其参数,让我们在下面逐一概述并解释它们。
- “objName:” 这个参数代表矩形标签对象的唯一名称。它作为正在创建的图形元素的标识符。
- “xD和yD:"这些参数决定了矩形标签将被定位的角落的X和Y距离。可以将它们视为定义矩形左上角相对于图表的坐标的参数。
- “xS和yS:” 这些参数指定矩形的宽度和高度。“xS”值决定了矩形的水平宽度,而“yS”控制其垂直高度。
- “clrBg:” “clrBg”参数代表矩形标签的背景颜色。选择一种与图表背景对比鲜明或与其他元素相辅相成的颜色。
- “widthBorder:” 此参数定义矩形周围的边框宽度。如果需要边框,请设置正值;否则,使用零表示无边框。
- “clrBorder:” 边框颜色的可选参数。如果需要边框,请指定颜色(例如,“clrNONE”表示无边框颜色)。
- “borderType:” 指定矩形的边框类型。选项包括平面、浮雕或其他样式。对于简单的平面边框,请使用BORDER_FLAT。
- “borderStyle:” 如果选择平面边框,此参数确定线条样式(例如,实线、虚线)。使用STYLE_SOLID 表示连续线。
在函数签名中,您可能已经注意到某些参数已经初始化为某个值。初始化值表示在函数调用期间忽略该参数时将分配给该参数的默认值。例如,我们的默认边框颜色为无,这意味着如果在函数调用期间未指定颜色值,则不会为我们的矩形标签的边框应用颜色。
在函数体内部,由大括号({})包围,我们定义我们的对象创建过程。
// Create a rectangle label object if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); return (false); // Return false if object creation fails }
我们首先使用if语句检查对象是否未创建。使用ObjectCreate函数,这是一个返回类型为布尔值的函数,接受6个参数。此函数在指定的图表子窗口中创建具有指定名称、类型和初始坐标的对象。首先,我们指定图表窗口,0表示对象将在主窗口上创建。然后,我们提供对象名称。这是将唯一分配给特定对象的名称。我们想要创建的对象类型是OBJ_RECTANGLE_LABEL,表示用于创建和设计自定义图形界面的对象。然后,我们继续设定子窗口,0表示当前子窗口。最后,我们设定时间为零(0)和价格值为零(0),因为我们不会将它们附加到图表上,而是附加到图表窗口坐标上。使用像素设置映射。
如果对象创建失败,最终ObjectCreate函数返回false,显然没有继续进行的必要,我们返回错误。在这种情况下,我们报错信息和错误代码一并打印到日志中,并返回false。可能存在之前的报错信息,因此为了获取最新的报错信息,我们需要清除之前的报错。这是通过在我们的对象创建逻辑之前调用“ResetLastError”函数来实现的,这是MQL5的一个内置函数。
ResetLastError(); // Reset any previous error codes
该函数的目的是将预定义变量_LastError的值设置为零,该变量存储遇到错误的最后一个操作的错误代码。通过调用它,我们确保在进行下一步操作之前清除任何之前的错误代码。这一步是必要的,因为它允许我们独立处理新的错误,而不受之前错误状态的干扰。
如果到目前为止还没有返回,这意味着我们成功创建了对象,因此可以继续更新对象的属性。内置函数“ObjectSet...”设置相应对象属性的值。对象属性必须是datetime、整数、颜色、布尔值或字符类型。
// Set properties for the rectangle label ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // X distance from the corner ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Y distance from the corner ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); // Width of the rectangle ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); // Height of the rectangle ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); // Positioning corner ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); // Rectangle background color ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); // Border type ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle); // Border style (only if borderType is flat) ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder); // Border width (only if borderType is flat) ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder); // Border color (only if borderType is flat) ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Not a background object ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Not selectable ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Not selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Not selected
让我们聚焦到第一个属性设置的代码。
ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // X distance from the corner
在这里,我们使用内置的ObjectSetInteger函数,并分别传递参数。 参数如下所述。
- 图表ID:这是图表标识符。“0”表示当前图表(图表ID)。我们正在调整此图表中对象的属性。
- 名称:这是对象的名称。“objName”表示分配给矩形标签对象的唯一名称。
- 属性ID:这是对象属性的ID,其值可以是ENUM_OBJECT_PROPERTY_INTEGER枚举的值之一。OBJPROP_XDISTANCE指定我们正在修改X距离属性。
- 属性值:这是属性的值。分配给“xD”的值决定了我们的矩形标签的左上角将从图表的左边缘水平向右(或如果为负数则向左)定位多远。
同样,我们使用相同的格式设置其他属性。OBJPROP_YDISTANCE配置矩形标签的Y距离属性。“yD”值决定了矩形标签的左上角将从图表的上边缘垂直定位多远。换句话说,它控制标签在图表区域内的垂直位置。这设置了从角落的Y距离。“OBJPROP_XSIZE”和“OBJPROP_YSIZE”分别设置矩形的宽度和高度。
为了定位我们的对象,我们使用OBJPROP_CORNER属性来确定我们希望对象在图表窗口上的角落。
ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); // Positioning corner
属性只能是4种类型之一:
- CORNER_LEFT_UPPER:坐标中心位于图表的左上角。
- CORNER_LEFT_LOWER:坐标中心位于图表的左下角。
- CORNER_RIGHT_LOWER:坐标中心位于图表的右下角。
- CORNER_RIGHT_UPPER:坐标中心位于图表的右上角。
在图形表达中,这就是我们能够得到的。
其余属性都很简单。我们为它们添加了注释以便于理解。然后,我们只需使用ChartRedraw函数重新绘制图表,使更改自动生效,而无需等待价格报价或图表事件的变化。
ChartRedraw(0); // Redraw the chart
最后,我们返回true,表示对象的创建和属性更新成功。
return (true); // Return true if object creation and property settings are successful
负责在图表窗口上创建矩形对象的完整函数代码如下。
bool createRecLabel(string objName, int xD, int yD, int xS, int yS, color clrBg, int widthBorder, color clrBorder = clrNONE, ENUM_BORDER_TYPE borderType = BORDER_FLAT, ENUM_LINE_STYLE borderStyle = STYLE_SOLID) { ResetLastError(); // Reset any previous error codes // Create a rectangle label object if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); return (false); // Return false if object creation fails } // Set properties for the rectangle label ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // X distance from the corner ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Y distance from the corner ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); // Width of the rectangle ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); // Height of the rectangle ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); // Positioning corner ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); // Rectangle background color ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); // Border type ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle); // Border style (only if borderType is flat) ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder); // Border width (only if borderType is flat) ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder); // Border color (only if borderType is flat) ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Not a background object ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Not selectable ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Not selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Not selected ChartRedraw(0); // Redraw the chart return (true); // Return true if object creation and property settings are successful }
创建按钮对象的方式也类似。创建按钮函数的代码如下所示。
//+------------------------------------------------------------------+ //| Function to create button | //+------------------------------------------------------------------+ bool createButton(string objName, int xD, int yD, int xS, int yS, string txt = "", color clrTxt = clrBlack, int fontSize = 12, color clrBg = clrNONE, color clrBorder = clrNONE, string font = "Arial Rounded MT Bold") { // Reset any previous errors ResetLastError(); // Attempt to create the button object if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) { // Print an error message if creation fails Print(__FUNCTION__, ": failed to create the button! Error code = ", _LastError); return (false); } // Set properties for the button ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // X distance from the corner ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Y distance from the corner ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); // Width of the button ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); // Height of the button ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); // Positioning corner ObjectSetString(0, objName, OBJPROP_TEXT, txt); // Text displayed on the button ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Text color ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); // Font size ObjectSetString(0, objName, OBJPROP_FONT, font); // Font name ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); // Background color ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Border color ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Transparent background ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Button state (not pressed) ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Not selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Not selected // Redraw the chart to display the button ChartRedraw(0); return (true); // Button creation successful }
代码的不同之处在于,矩形对象不能包含文本,但如果有需要,按钮对象确实可以包含描述按钮功能的文本。因此,对于输入参数,考虑文本属性,在我们的情况下是文本值、颜色、字体大小和字体名称。按钮的边框类型是静态的,因此我们去掉它的属性,只保留边框颜色。
我们创建的对象类型是OBJ_BUTTON,表示创建一个按钮图形对象。它的锚点以像素设置。我们保留的边框属性是边框颜色,并用文本输入属性替换其余部分。
最后,我们需要最后一个元素的函数,即文本标签。文本标签消除了对背景对象的需求,因此它的实现比其他函数要简单得多。我们只需要文本,因此专注于文本属性。它的代码如下。
//+------------------------------------------------------------------+ //| Function to create text label | //+------------------------------------------------------------------+ bool createLabel(string objName, int xD, int yD, string txt, color clrTxt = clrBlack, int fontSize = 12, string font = "Arial Rounded MT Bold") { // Reset any previous errors ResetLastError(); // Attempt to create the label object if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { // Print an error message if creation fails Print(__FUNCTION__, ": failed to create the label! Error code = ", _LastError); return (false); } // Set properties for the label ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // X distance from the corner ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Y distance from the corner ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); // Positioning corner ObjectSetString(0, objName, OBJPROP_TEXT, txt); // Text displayed on the label ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Text color ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); // Font size ObjectSetString(0, objName, OBJPROP_FONT, font); // Font name ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Transparent background ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Label state (not active) ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Not selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Not selected // Redraw the chart to display the label ChartRedraw(0); return (true); // Label creation successful }
这段代码结构与按钮函数的主要区别在于对象大小和边框属性。在函数签名中,我们去掉了对象大小以及边框属性。我们定义对象类型为OBJ_LABEL,表示我们根据定义的标签坐标在图表窗口上绘制标签。最后,我们去掉了大小和边框参数,仅此而已。就是这么简单。
现在我们已经拥有了创建图形用户界面(GUI)所需的函数,让我们使用它们来创建面板。我们需要对象的名称,为了便于管理对象名称的交互,定义宏会更加容易。
#define MAIN_REC "MAIN_REC"
我们使用#define关键字定义一个名为"MAIN_REC"的宏,其值为"MAIN_REC",以便轻松存储我们的主矩形基础名称,而无需在每次创建级别时重复输入名称,这大大节省了我们的时间,并减少了错误提供名称的可能性。因此,基本上,宏在编译期间用于文本替换。
我们的代码将主要基于EA初始化函数,因为我们希望在初始化实例时创建面板。因此OnInit事件处理程序将包含大部分代码。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... return(INIT_SUCCEEDED); }
OnInit函数是一个事件处理程序,它在EA初始化实例时被调用,用于执行必要的初始化操作。
然后我们通过输入其名称并提供其参数来调用创建矩形标签的函数。
//--- Create main rectangle label for the dashboard panel createRecLabel(MAIN_REC,50,50,740,410,clrSeaGreen,1);
在这里,我们的矩形名称是"MAIN_REC",正如宏定义中所定义的那样。沿着x轴(时间与日期刻度)从图表窗口的左上角的距离是50像素,沿着y轴(价格刻度)的距离是50像素。宽度为740像素,高度为410像素。我们选择背景颜色为海洋绿,边框宽度为1,其余参数默认。为了大致获取像素范围,你可以将图表缩放到0,两个十字准线坐标之间的柱数等于水平刻度上的像素数。举个例子,这就是我们所说的。
其他参数已被省略,这意味着将自动应用默认值。也就是说,边框类型将是平面的,线条样式将是连续的实线。编译后,我们目前有如下结果。
为了创建子框架,我们再次明确地声明相应的宏。
#define SUB_REC1 "SUB_REC1" #define SUB_REC2 "SUB_REC2"
然后我们调用相同的函数来创建子框架。我们希望这些框架位于基础面板框架内,因此需要使用略微不同的颜色。为了实现这一点,我们使用了白色和绿色,并设置了3和5像素的边距。
//--- Create sub-rectangle labels within the main panel for different sections createRecLabel(SUB_REC1,50+3,50+30,740-3-3,410-30-3,clrWhite,1); createRecLabel(SUB_REC2,50+3+5,50+30+50+27,740-3-3-5-5,410-30-3-50-27-10,clrGreen,1);
此处,我们在主面板内设置了两个子面板,“SUB_REC1”和“SUB_REC2”,从视觉上组织和分隔内容。使用“createRecLabel”函数,我们通过在主矩形“MAIN_REC”的左侧和右侧添加3像素的偏移量,以及在顶部添加30像素的偏移量,来定位“SUB_REC1”——有效地在主面板内创建了一个带框的区域。我们将其宽度定义为“740-3-3”,以适应减少的边距,其高度定义为“410-30-3”,以在上方和下方留出空间,使其能够整齐地嵌套在主矩形内。这个子面板设置为白色,与主面板的海洋绿色形成对比,以增强视觉清晰度。
接下来,我们使用“createRecLabel”添加“SUB_REC2”,这是“SUB_REC1”内的一个额外部分,并通过更精细的偏移量来实现有组织的分层布局。为了实现这一点,我们将起始X坐标设置为“50+3+5”,将其进一步定位在“SUB_REC1”内,以在视觉上将其定义为这个子模块内的一个独立区域。我们将Y坐标设置为“50+30+50+27”,以考虑主矩形和第一个子矩形的垂直偏移量。宽度“740-3-3-5-5”使“SUB_REC2”精确地适应剩余的水平空间,而高度“410-30-3-50-27-10”允许一个平衡且分离的区域。将“SUB_REC2”设置为绿色,增加了强烈的对比度,表明它是一个将显示关键数据的区域。这种对矩形的仔细分层对于建立一个结构化且视觉上清晰简洁的面板至关重要。编译后,我们得到以下结果:
到目前为止,我们已经完成了面板的框架、边距和边界的设置。然后我们继续添加其他面板工具、它们的属性和效果。首先,让我们给面板一个标题。
#define HEADER_LABEL "HEADER_LABEL" //--- //--- Create the header label with text "MQL5 Economic Calendar" createLabel(HEADER_LABEL,50+3+5,50+5,"MQL5 Economic Calendar",clrWhite,15);
在这里,我们定义标签标识符“HEADER_LABEL”,其值为“HEADER_LABEL”,以便在代码中一致且方便地引用这个特定的标签。这个标签将作为我们面板的标题,突出显示标题“MQL5经济日历”。
然后,使用“createLabel”函数,我们在指定位置创建标题标签。我们将X坐标设置为“50+3+5”,将其略微定位在主面板边缘的右侧,以确保它对齐在“SUB_REC1”矩形内,并且不与任何边距重叠。Y坐标“50+5”将其放置在主矩形顶部边缘下方几像素处,确保可读性。为了提高可见性,我们将文本颜色设置为白色,字体大小为“15”,创建一个醒目且显眼的标题,标明面板的用途。这个标题将锚定面板的视觉设计,立即向用户传达其用途。这就是我们得到的。
成功了。我们现在可以继续创建面板标题了。为此,我们将使用最简单的方法,即定义标题名称,并将它们放置在一个数组中,然后使用循环动态放置它们,因为它们只在一行中。然而,我们还将不得不分别定义按钮的大小,因为它们将因各个标题的长度而有不同的宽度。以下是我们将使用的逻辑。
string array_calendar[] = {"Date","Time","Cur.","Imp.","Event","Actual","Forecast","Previous"}; int buttons[] = {80,50,50,40,281,60,70,70};
我们定义了两个数组,用于在面板中组织标签和按钮的尺寸。第一个数组“array_calendar”包含了我们将会显示的每个列标题的字符串,指定了信息的类型:“Date”(日期)、“Time”(时间)、“Cur.”(货币)、“Imp.”(影响/重要性)、“Event”(事件)、“Actual”(实际值)、“Forecast”(预测值)和“Previous”(前值)。每个字符串代表一个数据类别的标签,帮助我们理解面板的每个部分将显示什么内容。
第二个数组“buttons”包含了整数,表示与“array_calendar”中相应列相关联的每个按钮的宽度(以像素为单位)。这些宽度是根据每种数据类型量身定制的,以确保布局保持对齐并且视觉上井然有序。例如,“Date”列的“80”像素可以容纳较长的日期格式,而“Time”和“Cur.”列的宽度则设置为“50”像素,因为它们需要的空间较少。这两个数组共同帮助简化了面板列标题和按钮的创建过程,为后续的用户界面(UI)元素奠定了结构化的基础。从这里开始,我们就可以使用循环动态创建标题了。
#define ARRAY_CALENDAR "ARRAY_CALENDAR" //--- //--- Initialize starting x-coordinate for button positioning int startX = 59; //--- Loop through the array_calendar elements to create buttons for (int i=0; i<ArraySize(array_calendar); i++){ //--- Create each button for calendar categories createButton(ARRAY_CALENDAR+IntegerToString(i),startX,132,buttons[i],25,array_calendar[i],clrWhite,13,clrGreen,clrNONE,"Calibri Bold"); startX += buttons[i]+3; //--- Update x-coordinate for the next button }
在这里,我们根据“array_calendar”中的元素初始化并定位按钮,以标记我们面板中的每个类别。首先,我们为日历按钮系列定义了标识符“ARRAY_CALENDAR”。然后,我们将“startX”设置为“59”,作为初始x坐标,这将把第一个按钮水平定位在面板上。
接下来,我们使用for循环遍历 “array_calendar”中的每个项目以创建一个按钮。在每次迭代中,我们调用“createButton”函数,并通过将循环索引附加到“ARRAY_CALENDAR”来为每个按钮传递一个唯一的ID。这确保了每个按钮ID都是唯一的,引用了诸如“Date”(日期)、“Time”(时间)、“Cur.”(货币)等类别。我们指定“startX”位置,并使用“buttons”中的值来定义每个按钮的宽度,确保与相应数据类别对齐。每个按钮还接收样式属性,包括字体颜色(白色)、字体大小(“13”)以及背景颜色(按钮为绿色,边框无色),并设置为“Calibri Bold”字体。在创建每个按钮后,我们通过加上当前按钮的宽度加上“3”像素的边距来调整“startX”,以便在下一次迭代中均匀地间隔开按钮。编译后,我们得到了以下输出。
创建完标题后,我们现在需要创建另一个子模块,用于显示时间、已识别新闻事件的数量以及影响级别。我们将首先从系列的前一部分获取新闻事件。
//--- Declare variables for tracking news events and status int totalNews = 0; bool isNews = false; MqlCalendarValue values[]; //--- Array to store calendar values //--- Define start and end time for calendar event retrieval datetime startTime = TimeTradeServer() - PeriodSeconds(PERIOD_H12); datetime endTime = TimeTradeServer() + PeriodSeconds(PERIOD_H12); //--- Set a specific country code filter (e.g., "US" for USD) string country_code = "US"; string currency_base = SymbolInfoString(_Symbol,SYMBOL_CURRENCY_BASE); //--- Retrieve historical calendar values within the specified time range int allValues = CalendarValueHistory(values,startTime,endTime,NULL,NULL); //--- Print the total number of values retrieved and the array size Print("TOTAL VALUES = ",allValues," || Array size = ",ArraySize(values));
这使我们能够获取从MQL5经济日历检索到的历史事件值,因此我们不仅可以打印它们,还可以将它们显示在面板上。这是我们使用的代码逻辑。
#define TIME_LABEL "TIME_LABEL" //--- //--- Create label displaying server time and total number of news events found createLabel(TIME_LABEL,70,85,"Server Time: "+TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS)+" ||| Total News: "+ IntegerToString(allValues),clrBlack,14,"Times new roman bold");
在这里,我们创建一个标签来显示当前服务器时间以及检索到的新闻事件总数。首先,我们定义标识符“TIME_LABEL”,以便在我们的面板内唯一引用此标签。
接下来,我们调用“createLabel”函数来生成标签本身。我们通过提供坐标“70”和“85”来指定标签的位置,这些坐标决定了标签在面板上的显示位置。标签的文本是通过使用TimeToString函数动态构建的,该函数将TimeCurrent函数检索到的当前服务器时间以日期和秒的格式进行格式化。我们将这个格式化的时间与字符串“||| Total News: ”连接起来,并使用IntegerToString函数将包含新闻事件数量的变量“allValues”转换为字符串。这创建了一个全面的标签,显示了服务器时间和找到的新闻事件总数。我们将标签样式设置为黑色,字体大小为14,并使用“Times New Roman bold”字体以确保清晰可见。通过相同的逻辑,我们还创建了影响标签。
#define IMPACT_LABEL "IMPACT_LABEL" //--- //--- Create label for displaying "Impact" category header createLabel(IMPACT_LABEL,70,105,"Impact: ",clrBlack,14,"Times new roman bold");
编译后,我们得到以下输出。
成功了。我们现在需要进一步显示各自的影响按钮及其对应的标签和颜色,以便用户能够了解每个影响级别所代表的含义。
//--- Define labels for impact levels and size of impact display areas string impact_labels[] = {"None", "Low", "Medium", "High"}; int impact_size = 100; //--- Loop through impact levels to create buttons for each level for (int i=0; i<ArraySize(impact_labels); i++){ color impact_color = clrBlack, label_color = clrBlack; //--- Default colors for label and button //--- Assign color based on impact level if (impact_labels[i] == "None"){label_color = clrWhite;} else if (impact_labels[i] == "Low"){impact_color = clrYellow;} else if (impact_labels[i] == "Medium"){impact_color = clrOrange;} else if (impact_labels[i] == "High"){impact_color = clrRed;} //--- Create button for each impact level createButton(IMPACT_LABEL+string(i),140+impact_size*i,105,impact_size,25,impact_labels[i],label_color,12,impact_color,clrBlack); }
在这里,我们定义了与经济事件相关联的不同影响级别的标签以及这些影响指示器的显示区域大小。首先,我们声明了一个名为“impact_labels”的数组,其中包含代表各种影响级别的字符串:“None”(无)、“Low”(低)、“Medium”(中)和“High”(高)。此外,我们初始化了一个整数变量“impact_size”,其值为100,它决定了将为每个影响级别创建的按钮的宽度。
接下来,我们进入一个循环,该循环使用ArraySize函数来确定影响级别的总数,从而遍历“impact_labels”数组。在这个循环中,我们首先使用黑色设置按钮和标签的默认颜色。然后,使用条件语句根据当前的影响级别分配特定的颜色。 如果影响级别是“None”,我们将“label_color”更改为白色。如果级别是“Low”,我们将“impact_color”设置为黄色。对于“Medium”,我们分配“impact_color”为橙色,而对于“High”,我们指定“impact_color”为红色。最后,我们调用“createButton”函数为每个影响级别生成一个按钮,使用“140 + impact_size * i”计算的动态x坐标进行定位,保持固定的y坐标105,并相应地提供尺寸和颜色。以下是当前的里程碑。
成功了。我们现在可以继续将实际的日历数据添加到面板中。然而,在此之前,我们需要对第二个子面板进行分区,以便为面板塑造更专业化的视觉效果,而不是仅仅在其中放置数据。我们通过以下代码来实现这一点。
//--- Limit the total number of values to display int valuesTotal = (allValues <= 11) ? allValues : 11; //--- Initialize starting y-coordinate for displaying news data int startY = 162; //--- Loop through each calendar value up to the maximum defined total for (int i = 0; i < valuesTotal; i++){ //--- Set alternating colors for each data row holder color holder_color = (i % 2 == 0) ? C'213,227,207' : clrWhite; //--- Create rectangle label for each data row holder createRecLabel(DATA_HOLDERS+string(i),62,startY-1,716,26,holder_color,1,clrBlack); //--- Increment y-coordinate for the next row of data startY += 25; Print(startY); //--- Print current y-coordinate for debugging }
我们通过定义一个名为“valuesTotal”的整数变量来限制在面板中显示的值的总数。我们使用一个条件(ternary)运算符来检查“allValues”是否小于或等于“11”。如果是,我们将“valuesTotal”设置为“allValues”;否则,我们将其设置为“11”。这种方法将确保我们不会尝试显示超过“11”个新闻事件,使我们的面板保持整洁且易于管理。
接下来,我们初始化一个整数变量“startY”,其值为“162”,这将作为在面板上定位新闻数据的起始y坐标。然后,进入一个从“0”到“valuesTotal”的循环,有效地处理我们打算显示的每个日历值。在这个循环中,我们根据当前索引“i”使用交替模式定义每个行容器的颜色。如果“i”是偶数,我们将“holder_color”设置为浅灰色,用“C'213,227,207'”表示;如果“i”是奇数,我们将其设置为白色。在确定颜色后,我们调用“createRecLabel”函数为每个数据行容器生成一个矩形标签,定位在x轴的“62”处,y轴的“startY - 1”处,宽度为“716”,高度为“26”,边框颜色为黑色。最后,我们将“startY”增加“25”,以调整下一行数据的y坐标,确保每个条目依次显示。为了调试目的,打印当前的“startY”值,使我们能够跟踪每个数据行在创建时的垂直位置。以下是当前的里程碑。
你可能已经注意到,在创建数据容器子框架时,我们使用了一个宏变量“DATA_HOLDERS”。以下是我们的定义方式。
#define DATA_HOLDERS "DATA_HOLDERS" #define ARRAY_NEWS "ARRAY_NEWS"
我们还定义了“ARRAY_NEWS”宏,以便于在数据容器中映射与列标题相关的特定数据。为了填充数据,我们需要为每个选定的容器,遍历特定事件值的所有数据并获取其数据,这些数据将被显示。因此,这将在第一个循环中完成,代码逻辑如下。
//--- Initialize starting x-coordinate for each data entry int startX = 65; //--- Loop through calendar data columns for (int k=0; k<ArraySize(array_calendar); k++){ MqlCalendarEvent event; //--- Declare event structure CalendarEventById(values[i].event_id,event); //--- Retrieve event details by ID MqlCalendarCountry country; //--- Declare country structure CalendarCountryById(event.country_id,country); //--- Retrieve country details by event's country ID //--- Print event details for debugging Print("Name = ",event.name,", IMP = ",EnumToString(event.importance),", COUNTRY = ",country.name,", TIME = ",values[i].time); //--- Skip event if currency does not match the selected country code // if (StringFind(_Symbol,country.currency) < 0) continue; //--- Prepare news data array with time, country, and other event details string news_data[ArraySize(array_calendar)]; news_data[0] = TimeToString(values[i].time,TIME_DATE); //--- Event date news_data[1] = TimeToString(values[i].time,TIME_MINUTES); //--- Event time news_data[2] = country.currency; //--- Event country currency //--- Determine importance color based on event impact color importance_color = clrBlack; if (event.importance == CALENDAR_IMPORTANCE_LOW){importance_color=clrYellow;} else if (event.importance == CALENDAR_IMPORTANCE_MODERATE){importance_color=clrOrange;} else if (event.importance == CALENDAR_IMPORTANCE_HIGH){importance_color=clrRed;} //--- Set importance symbol for the event news_data[3] = ShortToString(0x25CF); //--- Set event name in the data array news_data[4] = event.name; MqlCalendarValue value; //--- Declare calendar value structure CalendarValueById(values[i].id,value); //--- Retrieve actual, forecast, and previous values //--- Populate actual, forecast, and previous values in the news data array news_data[5] = DoubleToString(value.GetActualValue(),3); news_data[6] = DoubleToString(value.GetForecastValue(),3); news_data[7] = DoubleToString(value.GetPreviousValue(),3); //--- Create label for each news data item if (k == 3){ createLabel(ARRAY_NEWS+IntegerToString(i)+" "+array_calendar[k],startX,startY-(22-12),news_data[k],importance_color,22,"Calibri"); } else { createLabel(ARRAY_NEWS+IntegerToString(i)+" "+array_calendar[k],startX,startY,news_data[k],clrBlack,12,"Calibri"); } //--- Increment x-coordinate for the next column startX += buttons[k]+3; }
在这里,我们初始化一个名为“startX”的整数变量,其值为65,这将作为定位与日历事件相关的每个数据条目的起始x坐标。然后,我们进入一个循环,使用索引“k”遍历“array_calendar”中的每一列。在这个循环中,我们声明一个类型为MqlCalendarEvent的结构变量“event”,它将用于保存特定日历事件的详细信息。我们通过调用CalendarEventById函数并传递“values”数组中的事件ID来检索事件详细信息,并将结果存储在“event”中。
接下来,我们声明另一个类型为MqlCalendarCountry的结构变量“country”,用于保存与日历事件相关的国家信息。我们使用CalendarCountryById函数根据事件的国家ID填充“country”中的详细信息。为了调试目的,我们打印出关键事件详细信息,如事件名称、其重要性级别(使用EnumToString函数转换为字符串)、国家名称以及存储在“values[i].time”中的事件时间。
然后,准备一个名为“news_data”的字符串数组,其大小与“array_calendar”相同,用于存储与事件相关的信息。“news_data”的第一个元素设置为事件日期,使用带有“TIME_DATE”标志的TimeToString函数将其格式化为字符串。第二个元素捕获事件时间,使用“TIME_MINUTES”标志进行格式化。第三个元素存储事件国家的货币。
接下来,我们根据事件的影响级别确定事件的重要性颜色,初始化一个变量“importance_color”为黑色。我们检查“event.importance”的值,并根据其值(低、中或高),分配适当的颜色:低为黄色,中为橙色,高为红色。
我们还将“news_data”的第四个元素设置为一个表示事件重要性级别的符号,使用“ShortToString(0x25CF)”创建一个实心圆。第五个元素分配从“event.name”检索到的事件名称。
为了获取事件的实际值、预测值和前值,我们声明另一个类型为MqlCalendarValue的结构变量“value”,并使用CalendarValueById函数根据存储在“values[i].id”中的事件ID填充此结构。“news_data”的第六、第七和第八个元素分别用实际值、预测值和前值填充,使用DoubleToString函数将其格式化为三位小数。
最后,我们使用“createLabel”函数为每个新闻数据项创建一个标签。如果“k”等于“3”,我们应用重要性颜色;否则,我们默认为黑色。每个标签的x坐标由“startX”确定,然后我们通过加上“buttons”数组中的按钮宽度来增加“startX”,确保每列数据都正确定位以便清晰显示。编译后,我们得到以下输出。
成功了。我们现在创建了一个MQL5经济日历面板,该面板在图表上显示新闻数据,便于参考。到目前为止,我们实现了获取所有的数据。在本系列的后续部分中,我们将通过集成过滤器、实现实时数据更新以及将新闻数据用于交易目的来改进面板。
结论
总之,至此我们成功地奠定了MQL5经济日历的基础,通过构建一个交互式面板,以用户友好的界面显示关键经济事件。 通过实现诸如日历数据检索、基于其重要性的事件视觉分类以及直观的标签等功能,我们可以了解重要的市场动态。这一初步成果不仅增强了用户体验,而且为将我们的面板提升到更高水平,进一步增强其功能,夯定了坚实的基础。
在本系列文章的后续部分,我们将为程序集成其他功能,例如新闻过滤器,以帮助我们在MQL5中专注于同策略相关性最强的信息。我们还将实现实时更新,以确保面板反映最新的经济数据。此外,我们将专注于面板的互动响应性,使其能够完美适配不同的屏幕尺寸和用户交互。最终,我们旨在利用这些数据来促进交易决策的合理性,将经济日历转变为一个强大的工具,供交易者利用市场波动获利。敬请期待。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16301
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



