
在MQL5中创建交易管理员面板(第二部分):增强响应性和快速消息传递
内容:
概述:
想象一下,由于消息延迟而错过一个关键的市场信号——在快节奏的交易环境中,这是一个常见的障碍,会让交易者失去宝贵的机会和利润。在这种情况下,管理员的洞察力和市场信号本身一样重要。虽然算法系统反应迅速且具有情感智能,但它们无法取代熟练交易者的敏锐监督,交易者们需要持续监控系统表现并做出关键决策。
比较“点击发送消息”和“手动输入消息”。
在上图中,存在一个问题:尝试拖动面板时,图表反而被移动了,而且最小化按钮没有反应。
随着算法交易在金融市场中占据主导地位,交易系统用户(交易者)与人工管理员(交易系统背后的人)之间的高效沟通变得至关重要。以前,我们创建的管理面板消息界面对于实时任务(如快速发送消息和拖动面板本身)的响应性有限,这给需要迅速做出反应的管理员带来了重大挑战。
管理面板:针对旧版管理面板的改进
本文旨在通过使用MQL5为管理员消息界面引入响应性,打破这些沟通障碍。此外,它还强调了快速消息传递在支持敏捷交易决策和运营效率方面的重要性。
在这次讨论中,我们将探讨如何利用MQL5提升交易平台中的消息响应性。我们将引导您了解关键的实施步骤,帮助您更深入地理解MQL5编程所提供的可能性。我们将共同创建一个更有效的消息界面,以满足现代交易的需求。为了从头到尾跟踪本次讨论,我将涉及以下问题:
- GUI中的响应性是什么?
- 什么是快速消息?
响应性:
在MQL5中,GUI(图形用户界面)的响应性指的是界面对于用户交互(如点击按钮、移动滑块或调整面板大小)的反应速度和流畅程度。响应式的GUI能够立即向用户提供反馈,确保界面使用起来直观且便捷。这在交易应用中尤为重要,因为及时的操作至关重要。
实现响应性通常涉及优化代码以减少GUI相关函数的执行时间,尽量减少在图表上绘制的对象数量,并尽可能采用异步处理。这样可以确保主线程对用户的输入保持响应,从而在关键交易活动中提供无缝衔接的用户体验。
让我详细地概述MQL5 GUI中响应性的关键方面:
- 即时反馈:界面应立即响应用户操作,例如点击按钮或输入文本。用户操作与系统反馈之间不应存在明显的延迟。
- 流畅性能:即使有多个GUI元素和复杂的逻辑,界面也应运行流畅,不出现卡顿或死机的情况。这需要熟练高效的编码实践,以减少对CPU的负载并确保用户命令的快速执行。
- 动态更新: GUI应能够动态更新元素,而无需重新绘制整个界面。例如,如果达到一个新的价格水平,相应的元素(如标签、线条)应平滑更新,不会闪烁。
- 可扩展性: 界面应能够很好地处理尺寸或分辨率的变化。例如,如果用户调整面板大小,内容应自动调整并保持可用性。
- 错误处理:GUI应妥善地处理错误,一旦出现问题,需要向用户提供清晰且即时的反馈,而不会瘫痪或变得无响应。
快速消息:
指的是预先定义的、常用的消息,可以通过单次点击或最少的交互发送。这些消息通常提前配置,以满足频繁的沟通需求,使用户能够快速响应或发送标准消息,而无需手动输入。
快速消息的用户实例:
- 标准回复:快速消息可用于标准回复或指令,例如确认交易信号、确认操作或通知团队特定事件。
- 错误通知:如果交易中出现错误,可以立即发送简短的消息,如“无效信号”或“检测到错误”。
- 常规指令:快速消息可以包括交易操作中经常使用的常规指令,如“关闭所有头寸”或“激活EA”。
例如:
假设您有一个用于自动化交易系统的MQL5管理员面板,上面有几条快速消息。这些可能包括:
- 开始监控:一条简短的消息,用于开始监控市场条件。
- 停止监控:一条简短的消息,用于停止监控。
- 无效信号:一条消息,用于通知用户检测到了无效的交易信号。
每条消息都可以与面板上的一个按钮关联。当点击按钮时,预定义的消息会立即发送出去,节省时间并确保沟通的一致性。
在MQL5中的实现
在MQL5中,可以通过创建与特定预写文本字符串链接的按钮或下拉菜单,在消息面板中实现快速消息。当用户点击这些按钮中的一个时,相应的消息会通过所选择的通信渠道(如Telegram、电子邮件或其他消息API)自动发送。在介绍中展示的动画图像存在一个明显弊端:我们的面板遮挡了图表的部分区域,而当我们想全面地分析图表时,这一问题就很令人苦恼。为了攻克这一难题,我将其分为两个方面:
- 合理放置面板控制按钮。
- 使用重复函数编写我们的快速消息按钮。
假设您已经阅读了第一部分,并对我们的起点和目标有了初步的了解。
1. 合理放置面板控制按钮
最小化、最大化和关闭按钮
- 按钮声明:
在此,我们先展示如何声明按钮,然后再继续深入讲解:
///Global variables
CButton minimizeButton;
CButton maximizeButton;
CButton closeButton;
- 最小化按钮:
对于最小化按钮,我们使用CButton类创建,将其定位在图表上的(375, -22)位置,大小设置为(30, 22)像素。按钮上显示一个下划线_,是用于表示最小化窗口的常用符号。我们使用"adminPanel.Add(minimizeButton)"将其添加到管理面板中。该按钮的目的是允许用户暂时隐藏管理面板,而不完全关闭它。在"OnMinimizeButtonClick()"函数中,我们编写代码,使得点击该按钮时会隐藏管理面板,并且仅显示最小化、最大化和关闭按钮。这样模拟了最小化窗口的效果,同时保留了必要的控件。
// Create the minimize button if (!minimizeButton.Create(chart_id, "MinimizeButton", 0, 375, -22, 405, 0)) { Print("Failed to create minimize button"); return INIT_FAILED; } minimizeButton.Text("_"); adminPanel.Add(minimizeButton); // Function to handle minimize button click void OnMinimizeButtonClick() { minimized = true; // Hide the full admin panel adminPanel.Hide(); minimizeButton.Show(); maximizeButton.Show(); closeButton.Show(); }
- 最大化按钮:
对于最大化按钮,我们使用了相同的“CButton”类,并将其定位在最小化按钮旁边的(405, -22)位置。按钮显示[ ],是用于表示最大化或恢复窗口的常用符号,我们使用“adminPanel.Add(maximizeButton)”将其添加到管理面板中。该按钮允许用户在管理面板被最小化后将其恢复到全尺寸。在"OnMaximizeButtonClick()"函数中,点击此按钮会将管理面板恢复到原始大小,并隐藏最小化、最大化和关闭按钮。这样模拟了将最小化窗口最大化的行为。
// Create the maximize button if (!maximizeButton.Create(chart_id, "MaximizeButton", 0, 405, -22, 435, 0)) { Print("Failed to create maximize button"); return INIT_FAILED; } maximizeButton.Text("[ ]"); adminPanel.Add(maximizeButton); // Function to handle maximize button click void OnMaximizeButtonClick() { if (minimized) { minimizeButton.Hide(); maximizeButton.Hide(); closeButton.Hide(); adminPanel.Show(); } }
- 关闭按钮:
对于关闭按钮,我们按照创建其他两个按钮的相同方式,将其定位在(435, -22)位置,并显示字母“X”,是用于表示关闭窗口的通用符号。我们使用"adminPanel.Add(closeButton)"将其添加到管理面板中。该按钮允许用户通过在"OnCloseButtonClick()"函数中调用"ExpertRemove()",将EA从图表中完全移除。当用户使用完管理面板后,该按钮提供了一种直接的方式来关闭管理面板并停止EA。
// Create the close button if (!closeButton.Create(chart_id, "CloseButton", 0, 435, -22, 465, 0)) { Print("Failed to create close button"); return INIT_FAILED; } closeButton.Text("X"); adminPanel.Add(closeButton); // Function to handle close button click void OnCloseButtonClick() { ExpertRemove(); // Completely remove the EA Print("Admin Panel closed."); }2. 使用循环(重复)函数编写我们的快速消息按钮
多个快速消息按钮界面
快速消息的输入参数:
- 我们使用输入变量(QuickMessage1到QuickMessage8),以便让用户能够自定义消息内容。这些输入参数允许用户直接从EA的设置中修改每条快速消息的文本,而无需更改核心代码。这种灵活性使得用户可以轻松地根据不同的交易场景或个人偏好调整消息内容。此外,按钮的放置是动态的,这意味着您可以通过修改循环参数或“quickMessages”数组来调整按钮的数量、大小或位置。这种结构确保了管理员面板可以轻松适应不同的需求,提供一个强大且用户友好的界面。
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input string QuickMessage1 = "Updates"; input string QuickMessage2 = "Close all"; input string QuickMessage3 = "In deep profits"; input string QuickMessage4 = "Hold position"; input string QuickMessage5 = "Swing Entry"; input string QuickMessage6 = "Scalp Entry"; input string QuickMessage7 = "Book profit"; input string QuickMessage8 = "Invalid Signal"; input string InputChatId = "Enter Chat ID from Telegram bot API"; // User's Telegram chat ID input string InputBotToken = "Enter BOT TOKEN from your Telegram bot"; // User's Telegram bot token
循环实现:
- 我们通过创建一个由 (CButton)) 对象组成的数组(即quickMessageButtons[8]),并在循环中初始化这些按钮进行,从而设置多个快捷消息按钮。循环遍历包含预定义消息的(quickMessages)数组。每次迭代都会创建一个按钮,从(quickMessages)中分配一个标签,并根据它们的索引动态定位这些按钮。循环结构的精髓在于,它为每个按钮复制了创建和设置的过程,确保了一致性和效率。在MQL5中,此方法通过使用循环来处理创建多个具有相似特征的按钮等重复性任务,从而最小化冗余,简化代码并减少潜在的错误。
// Array of predefined quick messages string quickMessages[8] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 }; // Coordinates and dimensions for the buttons int startX = 5, startY = 160, width = 222, height = 65, spacing = 5; // Loop to create and configure quick message buttons for (int i = 0; i < 8; i++) { if (!quickMessageButtons[i].Create(chart_id, "QuickMessageButton" + IntegerToString(i + 1), 0, startX + (i % 2) * (width + spacing), startY + (i / 2) * (height + spacing), startX + (i % 2) * (width + spacing) + width, startY + (i / 2) * (height + spacing) + height)) { Print("Failed to create quick message button ", i + 1); return INIT_FAILED; } quickMessageButtons[i].Text(quickMessages[i]); adminPanel.Add(quickMessageButtons[i]); }
管理消息长度:
- 实现字符长度功能涉及一个字符计数器,它会跟踪输入框中键入的字符数量,并更新一个标签以显示当前长度以及允许的最大消息长度。每当输入文本发生变化时,会触发(OnInputChange)函数,该函数用于获取文本,使用(StringLen)计算其长度,然后以“current_length/MAX_MESSAGE_LENGTH”的格式更新(charCounter)标签。这确保用户在撰写消息时知道他们还剩下多少字符,从而防止超出允许的限制。可选地,我将最大字符长度设置为600。
// Maximum number of characters allowed in a message int MAX_MESSAGE_LENGTH = 600; // Function to update the character counter void OnInputChange() { string text = inputBox.Text(); int currentLength = StringLen(text); charCounter.Text(IntegerToString(currentLength) + "/" + IntegerToString(MAX_MESSAGE_LENGTH)); }
以下是我们完整集成的程序:
//+------------------------------------------------------------------+ //| Admin Panel.mq5 | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com/en/users/billionaire2024/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/en/users/billionaire2024/seller" #property description "A responsive Admin Panel. Send messages to your telegram clients without leaving MT5" #property version "1.09" #include <Trade\Trade.mqh> #include <Controls\Dialog.mqh> #include <Controls\Button.mqh> #include <Controls\Edit.mqh> #include <Controls\Label.mqh> // Use CLabel for displaying text //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input string QuickMessage1 = "Updates"; input string QuickMessage2 = "Close all"; input string QuickMessage3 = "In deep profits"; input string QuickMessage4 = "Hold position"; input string QuickMessage5 = "Swing Entry"; input string QuickMessage6 = "Scalp Entry"; input string QuickMessage7 = "Book profit"; input string QuickMessage8 = "Invalid Signal"; input string InputChatId = "Enter Chat ID from Telegram bot API"; // User's Telegram chat ID input string InputBotToken = "Enter BOT TOKEN from your Telegram bot"; // User's Telegram bot token //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ CDialog adminPanel; CButton sendButton; CButton clearButton; CButton minimizeButton; CButton maximizeButton; CButton closeButton; CButton quickMessageButtons[8]; CEdit inputBox; CLabel charCounter; // Use CLabel for the character counter bool minimized = false; int MAX_MESSAGE_LENGTH = 600; // Maximum number of characters //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { long chart_id = ChartID(); // Create the dialog if (!adminPanel.Create(chart_id, "Admin Panel", 0, 30, 30, 500, 500)) { Print("Failed to create dialog"); return INIT_FAILED; } // Create the input box if (!inputBox.Create(chart_id, "InputBox", 0, 5, 5, 460, 75)) { Print("Failed to create input box"); return INIT_FAILED; } adminPanel.Add(inputBox); // Create the clear button for the input box if (!clearButton.Create(chart_id, "ClearButton", 0, 180, 75, 270, 105)) { Print("Failed to create clear button"); return INIT_FAILED; } clearButton.Text("Clear"); adminPanel.Add(clearButton); // Create the send button for custom messages if (!sendButton.Create(chart_id, "SendButton", 0, 270, 75, 460, 105)) { Print("Failed to create send button"); return INIT_FAILED; } sendButton.Text("Send Message"); adminPanel.Add(sendButton); // Create the character counter label if (!charCounter.Create(chart_id, "CharCounter", 0, 380, 110, 460, 130)) { Print("Failed to create character counter label"); return INIT_FAILED; } charCounter.Text("0/" + IntegerToString(MAX_MESSAGE_LENGTH)); adminPanel.Add(charCounter); // Create the quick message buttons string quickMessages[8] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 }; int startX = 5, startY = 160, width = 222, height = 65, spacing = 5; for (int i = 0; i < 8; i++) { if (!quickMessageButtons[i].Create(chart_id, "QuickMessageButton" + IntegerToString(i + 1), 0, startX + (i % 2) * (width + spacing), startY + (i / 2) * (height + spacing), startX + (i % 2) * (width + spacing) + width, startY + (i / 2) * (height + spacing) + height)) { Print("Failed to create quick message button ", i + 1); return INIT_FAILED; } quickMessageButtons[i].Text(quickMessages[i]); adminPanel.Add(quickMessageButtons[i]); } adminPanel.Show(); // Create the minimize button if (!minimizeButton.Create(chart_id, "MinimizeButton", 0, 375, -22, 405, 0)) { Print("Failed to create minimize button"); return INIT_FAILED; } minimizeButton.Text("_"); adminPanel.Add(minimizeButton); // Create the maximize button if (!maximizeButton.Create(chart_id, "MaximizeButton", 0, 405, -22, 435, 0)) { Print("Failed to create maximize button"); return INIT_FAILED; } maximizeButton.Text("[ ]"); adminPanel.Add(maximizeButton); // Create the close button if (!closeButton.Create(chart_id, "CloseButton", 0, 435, -22, 465, 0)) { Print("Failed to create close button"); return INIT_FAILED; } closeButton.Text("X"); adminPanel.Add(closeButton); adminPanel.Show(); // Enable chart events ChartSetInteger(ChartID(), CHART_EVENT_OBJECT_CREATE, true); ChartSetInteger(ChartID(), CHART_EVENT_OBJECT_DELETE, true); ChartSetInteger(ChartID(), CHART_EVENT_MOUSE_WHEEL, true); ChartRedraw(); Print("Initialization complete"); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { adminPanel.Destroy(); Print("Deinitialization complete"); } //+------------------------------------------------------------------+ //| Expert event handling function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // Handle different types of events switch (id) { case CHARTEVENT_OBJECT_CLICK: if (sparam == "SendButton") { OnSendButtonClick(); } else if (sparam == "ClearButton") { OnClearButtonClick(); } else if (sparam == "MinimizeButton") { OnMinimizeButtonClick(); } else if (sparam == "MaximizeButton") { OnMaximizeButtonClick(); } else if (sparam == "CloseButton") { OnCloseButtonClick(); } else if (StringFind(sparam, "QuickMessageButton") >= 0) { int index = StringToInteger(StringSubstr(sparam, StringLen("QuickMessageButton"))); OnQuickMessageButtonClick(index - 1); } break; case CHARTEVENT_OBJECT_CHANGE: if (sparam == "InputBox") { OnInputChange(); } break; default: break; } } //+------------------------------------------------------------------+ //| Function to handle custom message send button click | //+------------------------------------------------------------------+ void OnSendButtonClick() { string message = inputBox.Text(); if (message != "") { if (SendMessageToTelegram(message)) Print("Custom message sent: ", message); else Print("Failed to send custom message."); } else { Print("No message entered."); } } //+------------------------------------------------------------------+ //| Function to handle clear button click | //+------------------------------------------------------------------+ void OnClearButtonClick() { inputBox.Text(""); // Clear the text in the input box OnInputChange(); // Update the character counter Print("Input box cleared."); } //+------------------------------------------------------------------+ //| Function to handle quick message button click | //+------------------------------------------------------------------+ void OnQuickMessageButtonClick(int index) { string quickMessages[8] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 }; string message = quickMessages[index]; if (SendMessageToTelegram(message)) Print("Quick Message Button Clicked - Quick message sent: ", message); else Print("Failed to send quick message."); } //+------------------------------------------------------------------+ //| Function to update the character counter | //+------------------------------------------------------------------+ void OnInputChange() { string text = inputBox.Text(); int currentLength = StringLen(text); charCounter.Text(IntegerToString(currentLength) + "/" + IntegerToString(MAX_MESSAGE_LENGTH)); } //+------------------------------------------------------------------+ //| Function to handle minimize button click | //+------------------------------------------------------------------+ void OnMinimizeButtonClick() { minimized = true; // Hide the full admin panel adminPanel.Hide(); minimizeButton.Show(); maximizeButton.Show(); closeButton.Show(); } //+------------------------------------------------------------------+ //| Function to handle maximize button click | //+------------------------------------------------------------------+ void OnMaximizeButtonClick() { if (minimized) { minimizeButton.Hide(); maximizeButton.Hide(); closeButton.Hide(); adminPanel.Show(); } } //+------------------------------------------------------------------+ //| Function to handle close button click | //+------------------------------------------------------------------+ void OnCloseButtonClick() { ExpertRemove(); // Completely remove the EA Print("Admin Panel closed."); } //+------------------------------------------------------------------+ //| Function to send the message to Telegram | //+------------------------------------------------------------------+ bool SendMessageToTelegram(string message) { // Use the input values for bot token and chat ID string botToken = InputBotToken; string chatId = InputChatId; string url = "https://api.telegram.org/bot" + botToken + "/sendMessage"; char post_data[]; // Prepare the message data string jsonMessage = "{\"chat_id\":\"" + chatId + "\", \"text\":\"" + message + "\"}"; // Resize the character array to fit the JSON payload ArrayResize(post_data, StringToCharArray(jsonMessage, post_data)); int timeout = 5000; char result[]; string responseHeaders; // Make the WebRequest int res = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, post_data, result, responseHeaders); if (res == 200) // HTTP 200 OK { Print("Message sent successfully: ", message); return true; } else { Print("Failed to send message. HTTP code: ", res, " Error code: ", GetLastError()); Print("Response: ", CharArrayToString(result)); return false; } }
测试高级且响应迅速的管理面板:
在此,我启动了管理面板,它运行得很好,几乎没什么缺点。请看下面的图片。
波动率150秒指数:管理面板测试
Telegram集成运行正常,我们的消息一点即达!
Telegram快捷消息接收中
结论
总结而言,将响应性和快速消息功能集成到管理面板EA中,能够显著提升实用性和用户体验。新增的最小化、最大化和关闭按钮提供了一个无缝且直观的界面,让用户能够轻松管理面板的可见性和操作。这些功能不仅确保了面板的功能性,还使其能够适应用户的需求,无论用户是需要完整的视图还是紧凑、不明显的显示方式。快速消息功能的实现进一步简化了沟通流程,允许用户在不离开MetaTrader 5环境的情况下,即时将预定义的消息发送到他们的Telegram客户端。这一功能在快节奏的交易场景中尤为宝贵,时间就是关键。面板发送自定义消息的能力,加上快速消息按钮的便捷性,使用户能够在最小干扰的情况下保持有效的沟通。
总体而言,这些改进使管理面板成为交易者和管理员更强大的工具,提高了效率和灵活性。这一演变反映了我们致力于提供解决算法交易中现实挑战的方案,确保用户能够以更强大的控制力和便利性管理他们的交易操作。
当然,未来还有更多可以改进的地方,但今天我们已经取得了这些进展。以下是附加的源文件。祝大家开发和交易顺利!
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15418


