
MQL5 交易管理面板开发指南(第六部分):交易管理面板(续篇)
内容:
引言
在顺利通过安全验证并进入管理员主面板后,我们已拥有对核心功能的初始访问权限。今天,我们将继续推进 MQL5 实践,在开发项目中进一步提升多功能界面的性能。上一篇文章作为多功能面板的介绍,仅展示了最新功能,并未逐一详解“交易管理面板”内每个按钮的具体功能。接下来,我们要确保交易管理面板中的每一个按钮在点击后都能立即产生响应。
此外,我想回应一个疑问:我们是否受限于当前面板的布局?为此,我们将探讨替代布局方案,以便更好地发挥 MQL5 这一知名交易算法开发语言中对话框类的潜力。下方附有一张示意图(仅示意用途,无需解析链接),展示了通过调整图表纵向缩放来腾出更多面板空间的技巧。我认为允许两块面板同时显示、且仍能清晰观察图表,会带来更好的体验。为实现这一功能,我们将在管理员主面板中新增一个按钮,供用户一键展开所有面板。
MetaTrader 5 图表纵向缩放会腾出空白区域,让 K 线区间显示得更清晰。
在继续之前,我希望给大家一次重新熟悉整套多功能界面各个组件的机会。这有助于解决本系列文章中一些“未明说”的难点。- 如果编译报错,多半是因为使用了尚未扩展的默认库文件。请将之前文章提供的Extended Header文件夹的内容,复制到 MQL5/Include/Controls 目录下。
- 另一个常见问题是:Chat ID 与 Bot Token 必须换成您自己的,否则验证码会发到别人账号。
针对第二个难题,解决方法是在 Telegram 应用中创建两个机器人: 一个专用于程序启动时的身份验证;另一个用于在 EA 程序的“通讯面板”中,与个人聊天、群组和频道进行消息通信。
请在下方输入您真实的聊天 ID 和机器人令牌。务必确保该聊天 ID 来自个人对话,而非群组或频道,否则会向多人泄露验证码。验证方法:先给机器人发送一条消息,再通过 Telegram API 查询返回的聊天 ID。注意:这些值需在编译前硬编码到源码中,与程序启动时输入的聊天 ID 和令牌并非同一组。
我们在先前的文章中讨论过这一点,步骤如下:
步骤 1:
请先确认自己是已注册的 Telegram 用户。通过下载 Telegram 手机或桌面版应用,按照应用内的指引完成注册即可。
步骤 2:
与 BotFather 对话,创建两个机器人:一个用于双因素认证(2FA);一个用于通讯。每个机器人都会获得唯一的 Bot Token,可用于调用 Telegram API 并获取对应的 Chat ID。两个机器人可以起任意名称,方便区分即可。
STEP 3:
点击链接:https://api.telegram.org/botReplaceMeWithTheTokenFromTheBotFather/getUpdates,访问相关API。
步骤 4:
与专门用于发送 2FA 验证码的机器人开始对话。您还可以把通讯机器人添加到任何准备分享交易观点的群组或频道,并将其设为管理员以确保正常运行。完成配置后,先在群组内发一条消息,即可获取该群组的 Chat ID。随后刷新浏览器中的 API 标签页,即可看到更新后的信息。
Telegram 聊天:先与机器人开始对话(此处我将机器人命名为 Admin Panel)。
Telegram API:在此处可获取我们的 2FA 专用 Chat ID。
上图中,我发起了一条私聊,主要用于获取 2FA 验证码的 Chat ID。您也可以将同一机器人添加到群组或频道;当您在这些会话中发消息后,对应的 Chat ID 会自动出现在 API 返回结果里,通常排在第一条对话下方。例如,我把机器人加入了一个教学频道,收到的 API 消息见下图。
总结:同一机器人可同时用于多个频道,每个会话都有唯一 Chat ID。若愿意,也可为不同频道分别使用不同机器人。
在 Telegram 频道中将机器人设为管理员
最后,下方展示了包含频道 Chat ID 的 API JSON 响应。
会话生成的 JSON
以下代码段用于输入机器人验证所需的2FA 验证码:// Constants for 2FA. Here put the chat ID dedicated for verification code delivery. It must be kept secure always. const string Hardcoded2FAChatId = "REPLACE WITH YOUR CHAT ID"; const string Hardcoded2FABotToken = "REPLACE WITH YOU BOT TOKEN"; //Obtain your bot token from a telegram bot father. //If you don't already have a bot you can start the chat with the bot father to create one.
下方图片演示了在应用初始化时,应在何处输入用于交易通讯的 Telegram 凭证。您也可以按需自定义快捷消息。
初始化输入设置说明
请注意,代码段与图片中出现了两个不同的 Chat ID。一个用于验证码传递;另一个用于通过个人聊天或 Telegram 群组广播管理消息。每个用户、群组和频道与机器人连接后都会获得唯一的 Chat ID。
前文我们一直使用 PIN 2024 作为示例,您可在源码中改为任意自定义 PIN。2FA 的六位动态码由算法自动生成,只需确保拥有正确的 Chat ID 即可安全接收。
简要回顾完毕,希望大家已准备就绪。今天我们将聚焦于实现“交易管理按钮”的事件处理;优化它们在图表上的位置与尺寸布局。
增强交易管理面板
首先,我希望您明确区分“通讯面板”按钮与“交易管理面板”按钮之间的差异。我知道,我们大多数人都已熟悉 MetaTrader 5 桌面端和移动端提供的批量操作功能。
正如面板名称所示,它们的功能一目了然;然而,由于名称或描述相似,您可能会感到困惑。举例而言,“通讯面板”中的按钮仅用于通讯,而“交易管理面板”中的按钮则专门用于处理交易。
完全可以在“通讯面板”的按钮上同时实现交易处理逻辑,使得点击一次即可同时完成两项任务。不过,将通讯按钮与交易管理按钮分开设计,仍有若干理由。其中一个关键原因是:管理员可能希望对某些交易进行管理,而这些交易并不打算对外广播。
请见下方图片,它们突出展示了两者最显著的区别。
通信面板:消息按钮
这里除了导航按钮外,几乎所有按钮都用于通讯。此外,这些快捷消息的内容可在初始化阶段进行自定义。“全部平仓”快捷消息按钮仅用于向指定的 Telegram 客户端发送消息,不会执行任何其他操作。
交易管理面板:(这些按钮在点击时必须执行交易操作)
通过以上图片,我们已阐明各功能模块的用途。现在,我们将注意力转向交易管理面板,仍有大量工作待完成。当前面板的按钮数量有限,我们希望在最大化图表可视区域的同时对其进行增强。一些关键的交易操作需要新增按钮,例如“全部平仓”按钮。
调整交易管理布局
新的界面布局是一项创新设计,旨在为图表留出更多可视空间,同时仍允许在此开发系列中执行常规管理操作。该方案利用面板相对于 MetaTrader 5 图表的位置坐标。
目前,管理员主面板相对其内容而言过大,我们将对其进行调整。为使过程更清晰,我们将把它分为三个部分。
调整交易管理面板
在初始化阶段,面板通过以下代码段创建:
if (!tradeManagementPanel.Create(ChartID(), "Trade Management Panel", 0, 30, 30, 500, 500))
{
Print("Failed to create Communictions panel dialog");
return INIT_FAILED;
}
上述代码中我们有:
x1 =30
x2 =500
因此 width = X2 -X1 = 500-30 = 470。
如果我们把 X2 值调大,新宽度就会按照设计理念相应扩展。举个例子,我会把宽度增加原尺寸的 50%。请看下面的代码。
// Let's increase our panel width by 50% of the former panel and is likely to be 250px but we will add an extra 30px to cover for the initial px on x1 co-ordinate.
// For the y co-ordinate we will reduce so much to 150px
if (!tradeManagementPanel.Create(ChartID(), "Trade Management Panel", 0, 30, 30, 780, 150))
{
Print("Failed to create Communictions panel dialog");
return INIT_FAILED;
}
编译测试后的结果下:
新的面板布局
最近的改动导致一些按钮没有按我们期望的顺序排列,它们漂浮在图表之上,而非保持在面板边框内。为解决该问题,我们采用CreateTradeManagementControls() 函数,并按下方的代码片段调整按钮坐标。
// Create the Trade Management Panel controls
// Here we adjusted our button coordinates to fit well in the new Trade Management Panel
bool CreateTradeManagementControls()
{
long chart_id = ChartID();
// Buy Button
if (!buyButton.Create(chart_id, "BuyButton", 0, 130, 5, 210, 40))
{
Print("Failed to create Buy button");
return false;
}
buyButton.Text("Buy");
tradeManagementPanel.Add(buyButton);
// Sell Button
if (!sellButton.Create(chart_id, "SellButton", 0, 220, 5, 320, 40))
{
Print("Failed to create Sell button");
return false;
}
sellButton.Text("Sell");
tradeManagementPanel.Add(sellButton);
// Close Position Button
if (!closePosButton.Create(chart_id, "ClosePosButton", 0, 130, 50, 260, 70))
{
Print("Failed to create Close Position button");
return false;
}
closePosButton.Text("Close Position");
tradeManagementPanel.Add(closePosButton);
// Modify Position Button
if (!modifyPosButton.Create(chart_id, "ModifyPosButton", 0, 270, 50, 410, 70))
{
Print("Failed to create Modify Position button");
return false;
}
modifyPosButton.Text("Modify Position");
tradeManagementPanel.Add(modifyPosButton);
// Set Stop-Loss Button
if (!setSLButton.Create(chart_id, "SetSLButton", 0, 330, 5, 430, 40))
{
Print("Failed to create Set Stop-Loss button");
return false;
}
setSLButton.Text("Set SL");
tradeManagementPanel.Add(setSLButton);
// Set Take-Profit Button
if (!setTPButton.Create(chart_id, "SetTPButton", 0, 440, 5, 540, 40))
{
Print("Failed to create Set Take-Profit button");
return false;
}
setTPButton.Text("Set TP");
tradeManagementPanel.Add(setTPButton);
return true;
}
编译并运行代码后,生成了下图所示结果,所有按钮均已正确就位。
交易管理面板:按钮已排列整齐
既然我们已完成布局调整并排列好按钮,接下来就开始编辑现有按钮并添加新按钮。
另一个需要注意的重点是避免与内部按钮发生冲突。例如,下图展示了平台原生按钮如何与我们的管理员面板重叠。
快捷按钮与管理员面板重叠
为了解决此问题,我们将对 x 坐标进行平移,把面板整体向右移动。请见下方代码片段中的更新值:
//1
//2
//2
if (!tradeManagementPanel.Create(ChartID(), "Trade Management Panel", 0, 260, 30, 1040, 150))
{
Print("Failed to create Communictions panel dialog");
return INIT_FAILED;
}
编译非常顺利,问题得到了解决。布局如下:
快捷按钮已排列整齐且无重叠
辅助函数与新建按钮
我在 C++ 课上,老师反复强调 repeat 函数的重要性,认为它是任何自称程序员的人都必须掌握的基础概念。后来研究 MQL5 的辅助函数时,我一度以为找到了与 C++ repeat 类似的东西。但很快发现,它们虽然共享某些优点,目的却完全不同。
总的来说,MQL5 的辅助函数更像“函数对象”,用于参数化行为;而 C++ 的重复工具则侧重一致地应用变换或模式。
二者共同点如下:
- 两者都是为了减少重复代码
- 并都提升了代码的简洁性与可维护性。
为此,我们引入辅助函数(Helper Functions)概念,以便一次性实现大量按钮,保持代码简洁易读。敬请期待后续实现!
在管理多个交易订单时,我最喜欢的一点是能一键批量处理,大幅提升效率。
批量操作:在MT5中管理多个交易订单
我们将优先实现“一键执行”的交易操作。稍后再处理止损/止盈等修改功能,并可直接在管理员面板配置。上述图片也可为按钮命名提供灵感。
按钮列表如下:
- 全部平仓
- 平掉盈利单
- 平掉亏损单
- 平掉所有买单
- 平掉所有卖单
- 删除全部挂单
- 删除限价单
- 删除止损单
- 删除止损限价单
至此,我们对按钮不再陌生。创建单个按钮的步骤基本相同,区别仅在于坐标。为避免重复劳动,我们使用自定义的辅助函数来简化流程,既缩减代码量,又能高效纳入全部新按钮。
首先,将按钮声明为全局变量:
// Button Declarations CButton buyButton; // Button for Buy operations CButton sellButton; // Button for Sell operations CButton closeAllButton; // Button for closing all positions CButton closeProfitButton; // Button for closing profitable positions CButton closeLossButton; // Button for closing losing positions CButton closeBuyButton; // Button for closing Buy positions CButton closeSellButton; // Button for closing Sell positions CButton deleteAllOrdersButton; // Button for deleting all orders CButton deleteLimitOrdersButton; // Button for deleting limit orders CButton deleteStopOrdersButton; // Button for deleting stop orders CButton deleteStopLimitOrdersButton; // Button for deleting stop limit orders
声明完毕后,就该正式推出我编写的按钮创建辅助函数了。这个名为 CreateButton 的辅助函数是一个精简的工具,用于简化在交易管理面板中重复创建和配置按钮的任务。
该函数接收按钮引用、名称、标签文本及坐标等参数,同时内部完成底层创建与设置逻辑,并附带错误处理。通过把这一过程集中化,我们消除了冗余代码,并确保所有按钮都能以最小的工作量被一致地创建。
这种模块化至关重要,它提高了代码的可读性与可维护性;日后若要扩展面板、增加按钮或调整功能,只需在一个中心位置修改,而无需在多处逐一更新。本质上,这个辅助函数充当了面板设计与按钮创建流程之间的桥梁,确保两者无缝整合。
以下就是我们的辅助函数代码:
//Helper Function For seamless Button creation bool CreateButton(CButton &button, const string name, const string text, int x1, int y1, int x2, int y2) { long chart_id = ChartID(); if (!button.Create(chart_id, name, 0, x1, y1, x2, y2)) { Print("Failed to create button: ", name); return false; } button.Text(text); tradeManagementPanel.Add(button); return true; }
上述代码片段将按钮创建的整个流程封装起来。CreateTradeManagementControls 函数充当“主调度器”,在交易管理面板内反复调用 CreateButton,以逻辑顺序定义并摆放每一个按钮。该函数无需为每个控件重复按钮创建逻辑,而只需专注于指定诸如坐标、标签和按钮类型等唯一细节。
CreateButton 的模块化设计使这一高层函数保持简洁,并聚焦于其首要任务:构造面板的整体布局。这两个函数协同工作——CreateTradeManagementControls 负责结构,CreateButton 负责重复性细节——从而打造出一个干净、高效且易扩展的交易管理面板实现。所有按钮的代码如下:
//+------------------------------------------------------------------+ //| Create Trade Management Controls | //+------------------------------------------------------------------+ bool CreateTradeManagementControls() { // Coordinates for buttons (adjust as needed) const int Y1_TOP = 5, Y2_TOP = 40; const int Y1_MID = 50, Y2_MID = 70; const int Y1_BOTTOM = 80, Y2_BOTTOM = 100; // Buy Button if (!CreateButton(buyButton, "BuyButton", "Buy", 130, Y1_TOP, 210, Y2_TOP)) return false; // Sell Button if (!CreateButton(sellButton, "SellButton", "Sell", 220, Y1_TOP, 320, Y2_TOP)) return false; // Close All Positions Button if (!CreateButton(closeAllButton, "CloseAllButton", "Close All", 130, Y1_MID, 230, Y2_MID)) return false; // Close Profitable Positions Button if (!CreateButton(closeProfitButton, "CloseProfitButton", "Close Profitable", 240, Y1_MID, 380, Y2_MID)) return false; // Close Losing Positions Button if (!CreateButton(closeLossButton, "CloseLossButton", "Close Losing", 390, Y1_MID, 510, Y2_MID)) return false; // Close Buy Positions Button if (!CreateButton(closeBuyButton, "CloseBuyButton", "Close Buys", 520, Y1_MID, 620, Y2_MID)) return false; // Close Sell Positions Button if (!CreateButton(closeSellButton, "CloseSellButton", "Close Sells", 630, Y1_MID, 730, Y2_MID)) return false; // Delete All Orders Button if (!CreateButton(deleteAllOrdersButton, "DeleteAllOrdersButton", "Delete All Orders", 130, Y1_BOTTOM , 270, Y2_BOTTOM )) return false; // Delete Limit Orders Button if (!CreateButton(deleteLimitOrdersButton, "DeleteLimitOrdersButton", "Delete Limits", 275, Y1_BOTTOM , 385, Y2_BOTTOM )) return false; // Delete Stop Orders Button if (!CreateButton(deleteStopOrdersButton, "DeleteStopOrdersButton", "Delete Stops", 390, Y1_BOTTOM , 515, Y2_BOTTOM )) return false; // Delete Stop Limit Orders Button if (!CreateButton(deleteStopLimitOrdersButton, "DeleteStopLimitOrdersButton", "Delete Stop Limits", 520, Y1_BOTTOM , 660, Y2_BOTTOM )) return false; return true; // All buttons created successfully }
这就是新界的面布局
新按钮整合后的布局
在添加新按钮的过程中,为了保持统一性,部分旧按钮已被移除。目前,我们只关注需要即时执行的操作,无需额外输入数据——例如订单修改等任务暂不考虑。
编写按钮处理程序
为了增强交易管理面板的功能,我们为每个按钮实现了专门的处理函数,以执行对应的交易操作。以下是各代码片段的说明:
1. 买入按钮处理程序 (OnBuyButtonClick)
OnBuyButtonClick 函数用于创建指定资产的市价买单。通过 CTrade 类,它处理关键的交易参数,如交易量、滑点、止损与止盈,确保精确执行。这对需要在程序化环境中快速开立多单的交易者至关重要。
//+------------------------------------------------------------------+ //| Handle Buy button click | //+------------------------------------------------------------------+ void OnBuyButtonClick() { CTrade trade; double lotSize = 0.1; // Example lot size double slippage = 3; // Example slippage double stopLoss = 0; // Example stop loss (in points) double takeProfit = 0; // Example take profit (in points) // Open Buy order double askPrice; if (SymbolInfoDouble(Symbol(), SYMBOL_ASK, askPrice) && askPrice > 0) { if (trade.Buy(lotSize, Symbol(), askPrice, slippage, stopLoss, takeProfit)) { Print("Buy order executed successfully."); } else { Print("Failed to execute Buy order. Error: ", GetLastError()); } } else { Print("Failed to retrieve Ask price. Error: ", GetLastError()); } // Execute Buy order logic here Print("Executing Buy operation"); }
2. 卖出按钮处理程序 (OnSellButtonClick)
OnSellButtonClick函数与买入处理程序相对应,允许用户通过市价单卖出资产。该函数以结构化方式实现卖出逻辑,确保交易量、滑点等参数的一致处理,使交易面板能够按需高效地发起卖单。
//+------------------------------------------------------------------+ //| Handle Sell button click | //+------------------------------------------------------------------+ void OnSellButtonClick() { CTrade trade; double lotSize = 0.1; // Example lot size double slippage = 3; // Example slippage double stopLoss = 0; // Example stop loss (in points) double takeProfit = 0; // Example take profit (in points) double bidPrice; if (SymbolInfoDouble(Symbol(), SYMBOL_BID, bidPrice) && bidPrice > 0) { // Open Sell order if (trade.Sell(lotSize, Symbol(), bidPrice, slippage, stopLoss, takeProfit)) { Print("Sell order opened successfully."); } else { Print("Error opening sell order: ", trade.ResultRetcode()); } } else { Print("Failed to retrieve Bid price. Error: ", GetLastError()); } }
3. 全部平仓处理程序(OnCloseAllButtonClick)
该函数自动关闭所有持仓:遍历全部持仓,并调用CTrade。PositionClose逐一执行平仓。它特别适用于需要快速退出全部交易的场景,既可防范突发行情波动,也能满足策略的“一键清仓”需求。
//+------------------------------------------------------------------+ //| Handle Close All button click | //+------------------------------------------------------------------+ void OnCloseAllButtonClick() { CPositionInfo position; for (int i = 0; i < PositionsTotal(); i++) { if (position.SelectByIndex(i)) { CTrade trade; if (position.Type() == POSITION_TYPE_BUY) trade.PositionClose(position.Ticket()); else if (position.Type() == POSITION_TYPE_SELL) trade.PositionClose(position.Ticket()); } } Print("All positions closed."); }
4. 盈利单平仓处理程序 (OnCloseProfitButtonClick)
借助OnCloseProfitButtonClick ,交易者可仅对盈利仓位进行平仓以锁定利润。函数通过盈亏值过滤仓位,实现有选择性地平仓,契合“落袋为安、亏损单继续观察”的交易策略。
//+------------------------------------------------------------------+ //| Handle Close Profitable button click | //+------------------------------------------------------------------+ void OnCloseProfitButtonClick() { CPositionInfo position; for (int i = 0; i < PositionsTotal(); i++) { if (position.SelectByIndex(i) && position.Profit() > 0) { CTrade trade; if (position.Type() == POSITION_TYPE_BUY) trade.PositionClose(position.Ticket()); else if (position.Type() == POSITION_TYPE_SELL) trade.PositionClose(position.Ticket()); } } Print("Profitable positions closed."); }
5. 亏损单平仓处理程序(OnCloseLossButtonClick)
该处理程序提供风险管理功能,自动关闭所有当前亏损的持仓。通过仅针对负盈利仓位执行平仓,它能有效遏制进一步回撤,对维持账户净值并遵循预设亏损上限至关重要。
//+------------------------------------------------------------------+ //| Handle Close Losing button click | //+------------------------------------------------------------------+ void OnCloseLossButtonClick() { CPositionInfo position; for (int i = 0; i < PositionsTotal(); i++) { if (position.SelectByIndex(i) && position.Profit() < 0) { CTrade trade; if (position.Type() == POSITION_TYPE_BUY) trade.PositionClose(position.Ticket()); else if (position.Type() == POSITION_TYPE_SELL) trade.PositionClose(position.Ticket()); } } Print("Losing positions closed."); } void OnCloseBuyButtonClick() { // Close Buy positions logic Print("Closing Buy positions"); } void OnCloseSellButtonClick() { // Close Sell positions logic Print("Closing Sell positions"); }
6. 删除全部挂单处理程序(OnDeleteAllOrdersButtonClick)
该函数用于删除所有挂单,确保账户内不会残留任何限价或止损单,从而避免意外成交。它通过 COrderInfo 类获取并撤销订单,确保不存在任何订单,防止非预期的交易执行。
//+------------------------------------------------------------------+ //| Handle Delete All Orders button click | //+------------------------------------------------------------------+ void OnDeleteAllOrdersButtonClick() { COrderInfo order; for (int i = 0; i < OrdersTotal(); i++) { if (order.SelectByIndex(i)) { CTrade trade; trade.OrderDelete(order.Ticket()); } } Print("All orders deleted."); }
7. 删除限价单处理程序 (OnDeleteLimitOrdersButtonClick)
OnDeleteLimitOrdersButtonClick函数仅用于撤销限价单。对于需要调整策略、但仍希望保留止损或其他类型订单的交易者而言,这一功能至关重要,可实现订单管理的精准控制。
//+------------------------------------------------------------------+ //| Handle Delete Limit Orders button click | //+------------------------------------------------------------------+ void OnDeleteLimitOrdersButtonClick() { COrderInfo order; for (int i = 0; i < OrdersTotal(); i++) { if (order.SelectByIndex(i) && order.Type() == ORDER_TYPE_BUY_STOP_LIMIT) { CTrade trade; trade.OrderDelete(order.Ticket()); } } Print("All limit orders deleted."); }
8. 删除止损单处理程序 (OnDeleteStopOrdersButtonClick)
该处理程序专门用于移除所有止损单,防止在行情剧烈波动时触发非预期成交。通过单独处理止损单,交易者可对挂单管理实现更精细的控制。
//+------------------------------------------------------------------+ //| Handle Delete Stop Orders button click | //+------------------------------------------------------------------+ void OnDeleteStopOrdersButtonClick() { COrderInfo order; for (int i = 0; i < OrdersTotal(); i++) { if (order.SelectByIndex(i) && order.Type() == ORDER_TYPE_BUY_STOP && ORDER_TYPE_SELL_STOP) { CTrade trade; trade.OrderDelete(order.Ticket()); } } Print("All stop orders deleted."); }
9. 删除止损限价单处理程序(OnDeleteStopLimitOrdersButtonClick)
此函数负责删除所有止损限价单,适用于涉及混合订单类型的策略。它增强了灵活性,使订单处理与交易者最新的市场判断或策略调整保持一致。
//+------------------------------------------------------------------+ //| Handle Delete Stop Limit Orders button click | //+------------------------------------------------------------------+ void OnDeleteStopLimitOrdersButtonClick() { COrderInfo order; for (int i = 0; i < OrdersTotal(); i++) { if (order.SelectByIndex(i) && order.Type() == ORDER_TYPE_BUY_LIMIT && ORDER_TYPE_SELL_STOP_LIMIT) { CTrade trade; trade.OrderDelete(order.Ticket()); } } Print("All stop limit orders deleted."); }
整合到OnChartEvent函数中:
为了把按钮点击与这些处理函数连接起来,我们在OnChartEvent函数内统一调度。 通过将按钮的sparam值与其对应的处理程序绑定,程序实现了图形界面与后台交易逻辑的无缝交互,使面板响应迅速且易于使用。
//+------------------------------------------------------------------+ //| Handle chart events | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK) { // Panel navigation buttons if (sparam == "HomeButtonComm") { adminHomePanel.Show(); communicationsPanel.Hide(); } else if (sparam == "HomeButtonTrade") { adminHomePanel.Show(); tradeManagementPanel.Hide(); } if (sparam == "TradeMgmtAccessButton") { tradeManagementPanel.Show(); adminHomePanel.Hide(); } else if (sparam == "CommunicationsPanelAccessButton") { communicationsPanel.Show(); adminHomePanel.Hide(); } // Control buttons for panel resizing and closing else if (sparam == "MinimizeButton") { OnMinimizeButtonClick(); } else if (sparam == "MaximizeButton") { OnMaximizeButtonClick(); } else if (sparam == "CloseButton") { ExpertRemove(); } { if (sparam == "LoginButton") { OnLoginButtonClick(); } else if (sparam == "CloseAuthButton") { OnCloseAuthButtonClick(); } else if (sparam == "TwoFALoginButton") { OnTwoFALoginButtonClick(); } else if (sparam == "Close2FAButton") { OnClose2FAButtonClick(); } } switch (id) { case CHARTEVENT_OBJECT_CLICK: if (sparam == "SendButton") OnSendButtonClick(); else if (sparam == "ClearButton") OnClearButtonClick(); else if (sparam == "ChangeFontButton") OnChangeFontButtonClick(); else if (sparam == "ToggleThemeButton") OnToggleThemeButtonClick(); else if (sparam == "MinimizeButton") OnMinimizeButtonClick(); else if (sparam == "MaximizeButton") OnMaximizeButtonClick(); else if (sparam == "CloseButton") OnCloseButtonClick(); else if (StringFind(sparam, "QuickMessageButton") != -1) { long index = StringToInteger(StringSubstr(sparam, 18)); OnQuickMessageButtonClick(index - 1); } break; case CHARTEVENT_OBJECT_ENDEDIT: if (sparam == "InputBox") OnInputChange(); break; } } // Trade management buttons if (sparam == "BuyButton") OnBuyButtonClick(); else if (sparam == "SellButton") OnSellButtonClick(); else if (sparam == "CloseAllButton") OnCloseAllButtonClick(); else if (sparam == "CloseProfitButton") OnCloseProfitButtonClick(); else if (sparam == "CloseLossButton") OnCloseLossButtonClick(); else if (sparam == "CloseBuyButton") OnCloseBuyButtonClick(); else if (sparam == "CloseSellButton") OnCloseSellButtonClick(); else if (sparam == "DeleteAllOrdersButton") OnDeleteAllOrdersButtonClick(); else if (sparam == "DeleteLimitOrdersButton") OnDeleteLimitOrdersButtonClick(); else if (sparam == "DeleteStopOrdersButton") OnDeleteStopOrdersButtonClick(); else if (sparam == "DeleteStopLimitOrdersButton") OnDeleteStopLimitOrdersButtonClick(); }
最终完整代码如下,行数已显著增加:
//+------------------------------------------------------------------+ //| 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 secure and responsive Admin Panel. Send messages to your telegram clients without leaving MT5" #property version "1.22" #include <Trade\Trade.mqh> #include <Controls\Dialog.mqh> #include <Controls\Button.mqh> #include <Controls\Edit.mqh> #include <Controls\Label.mqh> // Input parameters for quick messages 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 = "YOUR_CHAT_ID"; input string InputBotToken = "YOUR_BOT_TOKEN"; // Constants for 2FA const string Hardcoded2FAChatId = "Replace chat ID with yours"; const string Hardcoded2FABotToken = "Replace with your bot token"; // Global variables CDialog adminHomePanel, tradeManagementPanel, communicationsPanel; CDialog authentication, twoFactorAuth; CButton homeButtonComm, homeButtonTrade; CButton sendButton, clearButton, changeFontButton, toggleThemeButton; CButton loginButton, closeAuthButton, twoFALoginButton, close2FAButton; CButton quickMessageButtons[8], minimizeButton, maximizeButton, closeButton; CButton tradeMgmtAccessButton, communicationsPanelAccessButton; CEdit inputBox, passwordInputBox, twoFACodeInput; CLabel charCounter, passwordPromptLabel, feedbackLabel, twoFAPromptLabel, twoFAFeedbackLabel; bool minimized = false; bool darkTheme = false; int MAX_MESSAGE_LENGTH = 4096; string availableFonts[] = { "Arial", "Courier New", "Verdana", "Times New Roman" }; int currentFontIndex = 0; string Password = "2024"; // Hardcoded password string twoFACode = ""; // Button Declarations for Trade Management CButton buyButton; // Button for Buy operations CButton sellButton; // Button for Sell operations CButton closeAllButton; // Button for closing all positions CButton closeProfitButton; // Button for closing profitable positions CButton closeLossButton; // Button for closing losing positions CButton closeBuyButton; // Button for closing Buy positions CButton closeSellButton; // Button for closing Sell positions CButton deleteAllOrdersButton; // Button for deleting all orders CButton deleteLimitOrdersButton; // Button for deleting limit orders CButton deleteStopOrdersButton; // Button for deleting stop orders CButton deleteStopLimitOrdersButton; // Button for deleting stop limit orders //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if (!ShowAuthenticationPrompt()) { Print("Authorization failed. Exiting..."); return INIT_FAILED; } if (!adminHomePanel.Create(ChartID(), "Admin Home Panel", 0, 30, 30, 500, 500)) { Print("Failed to create Admin Home Panel"); return INIT_FAILED; } if (!CreateAdminHomeControls()) { Print("Home panel control creation failed"); return INIT_FAILED; } if (!communicationsPanel.Create(ChartID(), "Communications Panel", 0, 30, 30, 500, 500)) { Print("Failed to create Communications panel dialog"); return INIT_FAILED; } if (!tradeManagementPanel.Create(ChartID(), "Trade Management Panel", 0,260, 30, 1040, 170)) { Print("Failed to create Trade Management panel dialog"); return INIT_FAILED; } if (!CreateControls()) { Print("Control creation failed"); return INIT_FAILED; } if (!CreateTradeManagementControls()) { Print("Trade management control creation failed"); return INIT_FAILED; } adminHomePanel.Hide(); // Hide home panel by default on initialization communicationsPanel.Hide(); // Hide the Communications Panel tradeManagementPanel.Hide(); // Hide the Trade Management Panel return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Handle chart events | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK) { // Panel navigation buttons if (sparam == "HomeButtonComm") { adminHomePanel.Show(); communicationsPanel.Hide(); } else if (sparam == "HomeButtonTrade") { adminHomePanel.Show(); tradeManagementPanel.Hide(); } if (sparam == "TradeMgmtAccessButton") { tradeManagementPanel.Show(); adminHomePanel.Hide(); } else if (sparam == "CommunicationsPanelAccessButton") { communicationsPanel.Show(); adminHomePanel.Hide(); } // Control buttons for panel resizing and closing else if (sparam == "MinimizeButton") { OnMinimizeButtonClick(); } else if (sparam == "MaximizeButton") { OnMaximizeButtonClick(); } else if (sparam == "CloseButton") { ExpertRemove(); } { if (sparam == "LoginButton") { OnLoginButtonClick(); } else if (sparam == "CloseAuthButton") { OnCloseAuthButtonClick(); } else if (sparam == "TwoFALoginButton") { OnTwoFALoginButtonClick(); } else if (sparam == "Close2FAButton") { OnClose2FAButtonClick(); } } switch (id) { case CHARTEVENT_OBJECT_CLICK: if (sparam == "SendButton") OnSendButtonClick(); else if (sparam == "ClearButton") OnClearButtonClick(); else if (sparam == "ChangeFontButton") OnChangeFontButtonClick(); else if (sparam == "ToggleThemeButton") OnToggleThemeButtonClick(); else if (sparam == "MinimizeButton") OnMinimizeButtonClick(); else if (sparam == "MaximizeButton") OnMaximizeButtonClick(); else if (sparam == "CloseButton") OnCloseButtonClick(); else if (StringFind(sparam, "QuickMessageButton") != -1) { long index = StringToInteger(StringSubstr(sparam, 18)); OnQuickMessageButtonClick(index - 1); } break; case CHARTEVENT_OBJECT_ENDEDIT: if (sparam == "InputBox") OnInputChange(); break; } } // Trade management buttons if (sparam == "BuyButton") OnBuyButtonClick(); else if (sparam == "SellButton") OnSellButtonClick(); else if (sparam == "CloseAllButton") OnCloseAllButtonClick(); else if (sparam == "CloseProfitButton") OnCloseProfitButtonClick(); else if (sparam == "CloseLossButton") OnCloseLossButtonClick(); else if (sparam == "CloseBuyButton") OnCloseBuyButtonClick(); else if (sparam == "CloseSellButton") OnCloseSellButtonClick(); else if (sparam == "DeleteAllOrdersButton") OnDeleteAllOrdersButtonClick(); else if (sparam == "DeleteLimitOrdersButton") OnDeleteLimitOrdersButtonClick(); else if (sparam == "DeleteStopOrdersButton") OnDeleteStopOrdersButtonClick(); else if (sparam == "DeleteStopLimitOrdersButton") OnDeleteStopLimitOrdersButtonClick(); } //+------------------------------------------------------------------+ //| Trade management button handlers | //+------------------------------------------------------------------+ void OnBuyButtonClick() { CTrade trade; double lotSize = 0.1; // lot size double slippage = 3; // slippage double stopLoss = 0; // stop loss (in points) double takeProfit = 0; // take profit (in points) // Open Buy order double askPrice; if (SymbolInfoDouble(Symbol(), SYMBOL_ASK, askPrice) && askPrice > 0) { if (trade.Buy(lotSize, Symbol(), askPrice, slippage, stopLoss, takeProfit)) { Print("Buy order executed successfully."); } else { Print("Failed to execute Buy order. Error: ", GetLastError()); } } else { Print("Failed to retrieve Ask price. Error: ", GetLastError()); } // Execute Buy order logic here Print("Executing Buy operation"); } //+------------------------------------------------------------------+ //| Handle Sell button click | //+------------------------------------------------------------------+ void OnSellButtonClick() { CTrade trade; double lotSize = 0.1; // lot size double slippage = 3; // slippage double stopLoss = 0; // stop loss (in points) double takeProfit = 0; // take profit (in points) double bidPrice; if (SymbolInfoDouble(Symbol(), SYMBOL_BID, bidPrice) && bidPrice > 0) { // Open Sell order if (trade.Sell(lotSize, Symbol(), bidPrice, slippage, stopLoss, takeProfit)) { Print("Sell order opened successfully."); } else { Print("Error opening sell order: ", trade.ResultRetcode()); } } else { Print("Failed to retrieve Bid price. Error: ", GetLastError()); } } //+------------------------------------------------------------------+ //| Handle Close All button click | //+------------------------------------------------------------------+ void OnCloseAllButtonClick() { CPositionInfo position; for (int i = 0; i < PositionsTotal(); i++) { if (position.SelectByIndex(i)) { CTrade trade; if (position.Type() == POSITION_TYPE_BUY) trade.PositionClose(position.Ticket()); else if (position.Type() == POSITION_TYPE_SELL) trade.PositionClose(position.Ticket()); } } Print("All positions closed."); } //+------------------------------------------------------------------+ //| Handle Close Profitable button click | //+------------------------------------------------------------------+ void OnCloseProfitButtonClick() { CPositionInfo position; for (int i = 0; i < PositionsTotal(); i++) { if (position.SelectByIndex(i) && position.Profit() > 0) { CTrade trade; if (position.Type() == POSITION_TYPE_BUY) trade.PositionClose(position.Ticket()); else if (position.Type() == POSITION_TYPE_SELL) trade.PositionClose(position.Ticket()); } } Print("Profitable positions closed."); } //+------------------------------------------------------------------+ //| Handle Close Losing button click | //+------------------------------------------------------------------+ void OnCloseLossButtonClick() { CPositionInfo position; for (int i = 0; i < PositionsTotal(); i++) { if (position.SelectByIndex(i) && position.Profit() < 0) { CTrade trade; if (position.Type() == POSITION_TYPE_BUY) trade.PositionClose(position.Ticket()); else if (position.Type() == POSITION_TYPE_SELL) trade.PositionClose(position.Ticket()); } } Print("Losing positions closed."); } void OnCloseBuyButtonClick() { // Close Buy positions logic Print("Closing Buy positions"); } void OnCloseSellButtonClick() { // Close Sell positions logic Print("Closing Sell positions"); } //+------------------------------------------------------------------+ //| Handle Delete All Orders button click | //+------------------------------------------------------------------+ void OnDeleteAllOrdersButtonClick() { COrderInfo order; for (int i = 0; i < OrdersTotal(); i++) { if (order.SelectByIndex(i)) { CTrade trade; trade.OrderDelete(order.Ticket()); } } Print("All orders deleted."); } //+------------------------------------------------------------------+ //| Handle Delete Limit Orders button click | //+------------------------------------------------------------------+ void OnDeleteLimitOrdersButtonClick() { COrderInfo order; for (int i = 0; i < OrdersTotal(); i++) { if (order.SelectByIndex(i) && order.Type() == ORDER_TYPE_BUY_STOP_LIMIT) { CTrade trade; trade.OrderDelete(order.Ticket()); } } Print("All limit orders deleted."); } //+------------------------------------------------------------------+ //| Handle Delete Stop Orders button click | //+------------------------------------------------------------------+ void OnDeleteStopOrdersButtonClick() { COrderInfo order; for (int i = 0; i < OrdersTotal(); i++) { if (order.SelectByIndex(i) && order.Type() == ORDER_TYPE_BUY_STOP && ORDER_TYPE_SELL_STOP) { CTrade trade; trade.OrderDelete(order.Ticket()); } } Print("All stop orders deleted."); } //+------------------------------------------------------------------+ //| Handle Delete Stop Limit Orders button click | //+------------------------------------------------------------------+ void OnDeleteStopLimitOrdersButtonClick() { COrderInfo order; for (int i = 0; i < OrdersTotal(); i++) { if (order.SelectByIndex(i) && order.Type() == ORDER_TYPE_BUY_LIMIT && ORDER_TYPE_SELL_STOP_LIMIT) { CTrade trade; trade.OrderDelete(order.Ticket()); } } Print("All stop limit orders deleted."); } //+------------------------------------------------------------------+ //| Show authentication input dialog | //+------------------------------------------------------------------+ bool ShowAuthenticationPrompt() { if (!authentication.Create(ChartID(), "Authentication", 0, 100, 100, 500, 300)) { Print("Failed to create authentication dialog"); return false; } if (!passwordInputBox.Create(ChartID(), "PasswordInputBox", 0, 20, 70, 260, 95)) { Print("Failed to create password input box"); return false; } authentication.Add(passwordInputBox); if (!passwordPromptLabel.Create(ChartID(), "PasswordPromptLabel", 0, 20, 20, 260, 40)) { Print("Failed to create password prompt label"); return false; } passwordPromptLabel.Text("Enter password: Access Admin Panel"); authentication.Add(passwordPromptLabel); if (!feedbackLabel.Create(ChartID(), "FeedbackLabel", 0, 20, 140, 380, 160)) { Print("Failed to create feedback label"); return false; } feedbackLabel.Text(""); feedbackLabel.Color(clrRed); // Red color for incorrect attempts authentication.Add(feedbackLabel); if (!loginButton.Create(ChartID(), "LoginButton", 0, 20, 120, 100, 140)) { Print("Failed to create login button"); return false; } loginButton.Text("Login"); authentication.Add(loginButton); if (!closeAuthButton.Create(ChartID(), "CloseAuthButton", 0, 120, 120, 200, 140)) { Print("Failed to create close button for authentication"); return false; } closeAuthButton.Text("Close"); authentication.Add(closeAuthButton); authentication.Show(); ChartRedraw(); return true; } //+------------------------------------------------------------------+ //| Show two-factor authentication input dialog | //+------------------------------------------------------------------+ void ShowTwoFactorAuthPrompt() { if (!twoFactorAuth.Create(ChartID(), "Two-Factor Authentication", 0, 100, 100, 500, 300)) { Print("Failed to create 2FA dialog"); return; } if (!twoFACodeInput.Create(ChartID(), "TwoFACodeInput", 0, 20, 70, 260, 95)) { Print("Failed to create 2FA code input box"); return; } twoFactorAuth.Add(twoFACodeInput); if (!twoFAPromptLabel.Create(ChartID(), "TwoFAPromptLabel", 0, 20, 20, 380, 40)) { Print("Failed to create 2FA prompt label"); return; } twoFAPromptLabel.Text("Enter the 2FA code sent to your Telegram:"); twoFactorAuth.Add(twoFAPromptLabel); if (!twoFAFeedbackLabel.Create(ChartID(), "TwoFAFeedbackLabel", 0, 20, 140, 380, 160)) { Print("Failed to create 2FA feedback label"); return; } twoFAFeedbackLabel.Text(""); twoFAFeedbackLabel.Color(clrRed); // Red color for incorrect 2FA attempts twoFactorAuth.Add(twoFAFeedbackLabel); if (!twoFALoginButton.Create(ChartID(), "TwoFALoginButton", 0, 20, 120, 100, 140)) { Print("Failed to create 2FA login button"); return; } twoFALoginButton.Text("Verify"); twoFactorAuth.Add(twoFALoginButton); if (!close2FAButton.Create(ChartID(), "Close2FAButton", 0, 120, 120, 200, 140)) { Print("Failed to create close button for 2FA"); return; } close2FAButton.Text("Close"); twoFactorAuth.Add(close2FAButton); twoFactorAuth.Show(); ChartRedraw(); } //+------------------------------------------------------------------+ //| Admin Home Panel controls creation | //+------------------------------------------------------------------+ bool CreateAdminHomeControls() { long chart_id = ChartID(); if (!tradeMgmtAccessButton.Create(chart_id, "TradeMgmtAccessButton", 0, 50, 50, 250, 90)) { Print("Failed to create Trade Management Access button"); return false; } tradeMgmtAccessButton.Text("Trade Management Panel"); adminHomePanel.Add(tradeMgmtAccessButton); if (!communicationsPanelAccessButton.Create(chart_id, "CommunicationsPanelAccessButton", 0, 50, 100, 250, 140)) { Print("Failed to create Communications Panel Access button"); return false; } communicationsPanelAccessButton.Text("Communications Panel"); adminHomePanel.Add(communicationsPanelAccessButton); if (!minimizeButton.Create(chart_id, "MinimizeButton", 0, 375, -22, 405, 0)) { Print("Failed to create minimize button"); return false; } minimizeButton.Text("_"); adminHomePanel.Add(minimizeButton); if (!maximizeButton.Create(chart_id, "MaximizeButton", 0, 405, -22, 435, 0)) { Print("Failed to create maximize button"); return false; } maximizeButton.Text("[ ]"); adminHomePanel.Add(maximizeButton); if (!closeButton.Create(chart_id, "CloseButton", 0, 435, -22, 465, 0)) { Print("Failed to create close button"); return false; } closeButton.Text("X"); adminHomePanel.Add(closeButton); return true; } //Helper Function seamless Button creation bool CreateButton(CButton &button, const string name, const string text, int x1, int y1, int x2, int y2) { long chart_id = ChartID(); if (!button.Create(chart_id, name, 0, x1, y1, x2, y2)) { Print("Failed to create button: ", name); return false; } button.Text(text); tradeManagementPanel.Add(button); return true; } //+------------------------------------------------------------------+ //| Create Trade Management Controls (Buttons) | //+------------------------------------------------------------------+ bool CreateTradeManagementControls() { // Coordinates for buttons (adjust as needed) const int Y1_TOP = 5, Y2_TOP = 40; const int Y1_MID = 50, Y2_MID = 70; const int Y1_BOTTOM = 80, Y2_BOTTOM = 100; // Create Buttons if (!CreateButton(buyButton, "BuyButton", "Buy", 130, Y1_TOP, 210, Y2_TOP)) return false; if (!CreateButton(sellButton, "SellButton", "Sell", 220, Y1_TOP, 320, Y2_TOP)) return false; if (!CreateButton(closeAllButton, "CloseAllButton", "Close All", 130, Y1_MID, 230, Y2_MID)) return false; if (!CreateButton(closeProfitButton, "CloseProfitButton", "Close Profitable", 240, Y1_MID, 380, Y2_MID)) return false; if (!CreateButton(closeLossButton, "CloseLossButton", "Close Losing", 390, Y1_MID, 510, Y2_MID)) return false; if (!CreateButton(closeBuyButton, "CloseBuyButton", "Close Buys", 520, Y1_MID, 620, Y2_MID)) return false; if (!CreateButton(closeSellButton, "CloseSellButton", "Close Sells", 630, Y1_MID, 730, Y2_MID)) return false; if (!CreateButton(deleteAllOrdersButton, "DeleteAllOrdersButton", "Delete All Orders", 130, Y1_BOTTOM , 270, Y2_BOTTOM )) return false; if (!CreateButton(deleteLimitOrdersButton, "DeleteLimitOrdersButton", "Delete Limits", 275, Y1_BOTTOM , 385, Y2_BOTTOM )) return false; if (!CreateButton(deleteStopOrdersButton, "DeleteStopOrdersButton", "Delete Stops", 390, Y1_BOTTOM , 515, Y2_BOTTOM )) return false; if (!CreateButton(deleteStopLimitOrdersButton, "DeleteStopLimitOrdersButton", "Delete Stop Limits", 520, Y1_BOTTOM , 660, Y2_BOTTOM )) return false; return true; // All buttons created successfully } //+------------------------------------------------------------------+ //| Handle login button click | //+------------------------------------------------------------------+ void OnLoginButtonClick() { string enteredPassword = passwordInputBox.Text(); if (enteredPassword == Password) { twoFACode = GenerateRandom6DigitCode(); SendMessageToTelegram("A login attempt was made on the Admin Panel. Please use this code to verify your identity: " + twoFACode, Hardcoded2FAChatId, Hardcoded2FABotToken); authentication.Destroy(); ShowTwoFactorAuthPrompt(); Print("Password authentication successful. A 2FA code has been sent to your Telegram."); } else { feedbackLabel.Text("Wrong password. Try again."); passwordInputBox.Text(""); } ///Handlers for the trade management } //+------------------------------------------------------------------+ //| Handle 2FA login button click | //+------------------------------------------------------------------+ void OnTwoFALoginButtonClick() { // If 2FA is successful, show the trade management panel string enteredCode = twoFACodeInput.Text(); if (enteredCode == twoFACode) { twoFactorAuth.Destroy(); adminHomePanel.Show(); Print("2FA authentication successful. Access granted to Trade Management Panel."); } else { twoFAFeedbackLabel.Text("Wrong code. Try again."); twoFACodeInput.Text(""); } } //+------------------------------------------------------------------+ //| Handle close button for authentication | //+------------------------------------------------------------------+ void OnCloseAuthButtonClick() { authentication.Destroy(); ExpertRemove(); // Exit the expert Print("Authentication dialog closed."); } //+------------------------------------------------------------------+ //| Handle close button for 2FA | //+------------------------------------------------------------------+ void OnClose2FAButtonClick() { twoFactorAuth.Destroy(); ExpertRemove(); Print("2FA dialog closed."); } //+------------------------------------------------------------------+ //| Create necessary UI controls | //+------------------------------------------------------------------+ bool CreateControls() { long chart_id = ChartID(); if (!inputBox.Create(chart_id, "InputBox", 0, 5, 25, 460, 95)) { Print("Failed to create input box"); return false; } communicationsPanel.Add(inputBox); // Create Home Button for Communications Panel if (!homeButtonComm.Create(chart_id, "HomeButtonComm", 0, 20, 120, 120,150)) { Print("Failed to create Home button for Communications Panel"); return false; } homeButtonComm.Text("Home 🏠"); communicationsPanel.Add(homeButtonComm); // Create Home Button for Trade Management Panel if (!homeButtonTrade.Create(chart_id, "HomeButtonTrade", 0, 20, 10, 120, 30)) { Print("Failed to create Home button for Trade Management Panel"); return false; } homeButtonTrade.Text("Home 🏠"); tradeManagementPanel.Add(homeButtonTrade); if (!charCounter.Create(chart_id, "CharCounter", 0, 380, 5, 460, 25)) { Print("Failed to create character counter"); return false; } charCounter.Text("0/" + IntegerToString(MAX_MESSAGE_LENGTH)); communicationsPanel.Add(charCounter); if (!clearButton.Create(chart_id, "ClearButton", 0, 235, 95, 345, 125)) { Print("Failed to create clear button"); return false; } clearButton.Text("Clear"); communicationsPanel.Add(clearButton); if (!sendButton.Create(chart_id, "SendButton", 0, 350, 95, 460, 125)) { Print("Failed to create send button"); return false; } sendButton.Text("Send"); communicationsPanel.Add(sendButton); if (!changeFontButton.Create(chart_id, "ChangeFontButton", 0, 95, 95, 230, 115)) { Print("Failed to create change font button"); return false; } changeFontButton.Text("Font<>"); communicationsPanel.Add(changeFontButton); if (!toggleThemeButton.Create(chart_id, "ToggleThemeButton", 0, 5, 95, 90, 115)) { Print("Failed to create toggle theme button"); return false; } toggleThemeButton.Text("Theme<>"); communicationsPanel.Add(toggleThemeButton); if (!minimizeButton.Create(chart_id, "MinimizeButton", 0, 375, -22, 405, 0)) { Print("Failed to create minimize button"); return false; } minimizeButton.Text("_"); communicationsPanel.Add(minimizeButton); if (!maximizeButton.Create(chart_id, "MaximizeButton", 0, 405, -22, 435, 0)) { Print("Failed to create maximize button"); return false; } maximizeButton.Text("[ ]"); communicationsPanel.Add(maximizeButton); if (!closeButton.Create(chart_id, "CloseButton", 0, 435, -22, 465, 0)) { Print("Failed to create close button"); return false; } closeButton.Text("X"); communicationsPanel.Add(closeButton); return CreateQuickMessageButtons(); } //+------------------------------------------------------------------+ //| Create quick message buttons | //+------------------------------------------------------------------+ bool CreateQuickMessageButtons() { string quickMessages[] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 }; int startX = 5, startY = 160, width = 222, height = 65, spacing = 5; for (int i = 0; i < ArraySize(quickMessages); i++) { bool created = quickMessageButtons[i].Create(ChartID(), "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); if (!created) { Print("Failed to create quick message button ", i + 1); return false; } quickMessageButtons[i].Text(quickMessages[i]); communicationsPanel.Add(quickMessageButtons[i]); } return true; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { communicationsPanel.Destroy(); Print("Deinitialization complete"); } //+------------------------------------------------------------------+ //| Handle custom message send button click | //+------------------------------------------------------------------+ void OnSendButtonClick() { string message = inputBox.Text(); if (StringLen(message) > 0) { if (SendMessageToTelegram(message, InputChatId, InputBotToken)) Print("Custom message sent: ", message); else Print("Failed to send custom message."); } else { Print("No message entered."); } } //+------------------------------------------------------------------+ //| Handle clear button click | //+------------------------------------------------------------------+ void OnClearButtonClick() { inputBox.Text(""); OnInputChange(); Print("Input box cleared."); } //+------------------------------------------------------------------+ //| Handle quick message button click | //+------------------------------------------------------------------+ void OnQuickMessageButtonClick(long index) { string quickMessages[] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 }; string message = quickMessages[(int)index]; if (SendMessageToTelegram(message, InputChatId, InputBotToken)) Print("Quick message sent: ", message); else Print("Failed to send quick message."); } //+------------------------------------------------------------------+ //| Update character counter | //+------------------------------------------------------------------+ void OnInputChange() { int currentLength = StringLen(inputBox.Text()); charCounter.Text(IntegerToString(currentLength) + "/" + IntegerToString(MAX_MESSAGE_LENGTH)); ChartRedraw(); } //+------------------------------------------------------------------+ //| Handle toggle theme button click | //+------------------------------------------------------------------+ void OnToggleThemeButtonClick() { darkTheme = !darkTheme; UpdateThemeColors(); Print("Theme toggled: ", darkTheme ? "Dark" : "Light"); } //+------------------------------------------------------------------+ //| Update theme colors for the panel | //+------------------------------------------------------------------+ void UpdateThemeColors() { color textColor = darkTheme ? clrWhite : clrBlack; color buttonBgColor = darkTheme ? clrDarkSlateGray : clrGainsboro; color borderColor = darkTheme ? clrSlateGray : clrGray; color bgColor = darkTheme ? clrDarkBlue : clrWhite; UpdateButtonTheme(clearButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(sendButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(toggleThemeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(changeFontButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(minimizeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(maximizeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(closeButton, textColor, buttonBgColor, borderColor); for (int i = 0; i < ArraySize(quickMessageButtons); i++) { UpdateButtonTheme(quickMessageButtons[i], textColor, buttonBgColor, borderColor); } ChartRedraw(); } //+------------------------------------------------------------------+ //| Apply theme settings to a button | //+------------------------------------------------------------------+ void UpdateButtonTheme(CButton &button, color textColor, color bgColor, color borderColor) { button.SetTextColor(textColor); button.SetBackgroundColor(bgColor); button.SetBorderColor(borderColor); } //+------------------------------------------------------------------+ //| Handle change font button click | //+------------------------------------------------------------------+ void OnChangeFontButtonClick() { currentFontIndex = (currentFontIndex + 1) % ArraySize(availableFonts); SetFontForAll(availableFonts[currentFontIndex]); Print("Font changed to: ", availableFonts[currentFontIndex]); ChartRedraw(); } //+------------------------------------------------------------------+ //| Set font for all input boxes and buttons | //+------------------------------------------------------------------+ void SetFontForAll(string fontName) { inputBox.Font(fontName); clearButton.Font(fontName); sendButton.Font(fontName); toggleThemeButton.Font(fontName); changeFontButton.Font(fontName); minimizeButton.Font(fontName); maximizeButton.Font(fontName); closeButton.Font(fontName); for (int i = 0; i < ArraySize(quickMessageButtons); i++) { quickMessageButtons[i].Font(fontName); } } //+------------------------------------------------------------------+ //| Generate a random 6-digit code for 2FA | //+------------------------------------------------------------------+ string GenerateRandom6DigitCode() { int code = MathRand() % 1000000; // Produces a 6-digit number return StringFormat("%06d", code); // Ensures leading zeros } //+------------------------------------------------------------------+ //| Handle minimize button click | //+------------------------------------------------------------------+ void OnMinimizeButtonClick() { minimized = true; communicationsPanel.Hide(); minimizeButton.Hide(); maximizeButton.Show(); closeButton.Show(); Print("Panel minimized."); } //+------------------------------------------------------------------+ //| Handle maximize button click | //+------------------------------------------------------------------+ void OnMaximizeButtonClick() { if (minimized) { communicationsPanel.Show(); minimizeButton.Show(); maximizeButton.Hide(); closeButton.Hide(); minimized = false; Print("Panel maximized."); } } //+------------------------------------------------------------------+ //| Handle close button click for admin panel | //+------------------------------------------------------------------+ void OnCloseButtonClick() { ExpertRemove(); Print("Admin panel closed."); } //+------------------------------------------------------------------+ //| Send the message to Telegram | //+------------------------------------------------------------------+ bool SendMessageToTelegram(string message, string chatId, string botToken) { string url = "https://api.telegram.org/bot" + botToken + "/sendMessage"; string jsonMessage = "{\"chat_id\":\"" + chatId + "\", \"text\":\"" + message + "\"}"; char postData[]; ArrayResize(postData, StringToCharArray(jsonMessage, postData) - 1); int timeout = 5000; char result[]; string responseHeaders; int responseCode = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, postData, result, responseHeaders); if (responseCode == 200) { Print("Message sent successfully: ", message); return true; } else { Print("Failed to send message. HTTP code: ", responseCode, " Error code: ", GetLastError()); Print("Response: ", CharArrayToString(result)); return false; } } //+------------------------------------------------------------------+
测试
经测试,所有按钮均能按预期正确响应,我们的交易管理面板已成为交易管理员工具箱中的得力组件。新按钮的集成及其事件处理程序的实现均已顺利完成。
交易管理米娜版按钮处理函数正常运行
这些是EA的输出的说明,展示了订单的成功下单与平仓
结论
在本次讨论中,我们回顾了 Telegram 配置,并通过新增交易管理按钮对交易管理面板进行了功能增强。同时,我们通过缩减纵向尺寸、扩大水平尺寸,并在 x 方向进行位移,进一步优化了面板布局。这一调整解决了面板与内置快捷交易按钮的重叠问题,在提升图表可视区域的同时,依旧保持交易按钮的便捷访问。
布局最终定稿后,我们整合了按钮事件处理程序,确保点击时能够正确响应。
希望本次探讨能让大家看到 MQL5 GUI 编程的广阔可能性,并激发在设计各类界面组件时的创意潜能。凭借熟练而富有想象力的头脑,在这一基础之上仍有无限空间等待探索。
开发愉快,交易者们!
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16328
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。


