在MQL5中创建交易管理员面板(第六部分):多功能界面(一)·综合运用
📘

在MQL5中创建交易管理员面板(第六部分):多功能界面(一)·综合运用

第 3/3 篇

「面板按钮的创建与销毁逻辑」

这段 MT5 面板代码负责在图表上生成关闭按钮和一组快捷消息按钮,并在 EA 卸载时清理资源。关闭按钮若创建失败会直接返回 false 中断初始化,位置参数里 Y 坐标用了 -22,说明它是贴着图表右上角外沿放的。 快捷消息按钮用数组存了 8 条文本(QuickMessage1~8),以两列网格排布:起始 X=5、Y=160,单按钮宽 222、高 65,间距 5 像素。循环里用 i%2 算列偏移、i/2 算行偏移,任何一枚按钮 Create 失败就打印编号并整体返回 false。 OnDeinit 里只调了 adminPanel.Destroy() 一句,把所有子控件一次性销毁,比逐个删按钮更省事。OnSendButtonClick 则读 inputBox 的文本,长度大于 0 才尝试发 Telegram,空内容直接跳过不发请求。 在 MT5 里跑这套,先把 adminPanel 和 quickMessageButtons[] 的全局对象声明补齐,否则编译会报未定义;外汇与贵金属交易本身高风险,这类面板只是辅助发消息的工具,不替代任何风控。

MQL5 / C++
if (!closeButton.Create(chart_id, "CloseButton", class="num">0, class="num">435, -class="num">22, class="num">465, class="num">0))
{
   Print("Failed to create close button");
   class="kw">return false;
}
closeButton.Text("X");
adminPanel.Add(closeButton);
class="kw">return CreateQuickMessageButtons();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create quick message buttons                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CreateQuickMessageButtons()
{
   class="type">class="kw">string quickMessages[] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 };
   class="type">int startX = class="num">5, startY = class="num">160, width = class="num">222, height = class="num">65, spacing = class="num">5;
   for (class="type">int i = class="num">0; i < ArraySize(quickMessages); i++)
   {
      class="type">bool created = quickMessageButtons[i].Create(ChartID(), "QuickMessageButton" + IntegerToString(i + class="num">1), class="num">0,
         startX + (i % class="num">2) * (width + spacing), startY + (i / class="num">2) * (height + spacing),
         startX + (i % class="num">2) * (width + spacing) + width, startY + (i / class="num">2) * (height + spacing) + height);
      if (!created)
      {
         Print("Failed to create quick message button ", i + class="num">1);
         class="kw">return false;
      }
      quickMessageButtons[i].Text(quickMessages[i]);
      adminPanel.Add(quickMessageButtons[i]);
   }
   class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
{
   adminPanel.Destroy();
   Print("Deinitialization complete");
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle custom message send button click                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnSendButtonClick()
{
   class="type">class="kw">string message = inputBox.Text();
   if (StringLen(message) > class="num">0)
   {
      if (SendMessageToTelegram(message, InputChatId, InputBotToken))
         Print("Custom message sent: ", message);
      else
         Print("Failed to send custom message.");
   }
   else
   {

◍ 面板交互回调与主题切换的实现细节

这段逻辑承接了 MT5 自定义面板的事件响应层,把清屏、快捷发信、字数统计和主题切换都拆成了独立函数,方便在 EA 或脚本里直接挂到 GUI 控件事件上。 清空按钮的回调先把输入框文本置空,再主动调用 OnInputChange() 刷新计数与重绘,最后向日志打印 "Input box cleared.",这样界面状态和后台状态不会脱节。 快捷消息按钮按传入的 index 从长度为 8 的数组里取词(QuickMessage1~8),调 SendMessageToTelegram 发到指定 chat,成功或失败都只走 Print,不弹窗——回测或实盘时看专家日志就能确认是否发出。外汇与贵金属消息推送涉及 API 密钥,实盘误发可能泄露 token,属高风险操作。 OnInputChange 用 StringLen 拿当前长度,拼成 "当前/上限" 写到 charCounter,并 ChartRedraw 强制重画;上限由 MAX_MESSAGE_LENGTH 宏控制,改这个常量就能调输入框容限。 主题切换靠 darkTheme 布尔取反,再统一调 UpdateThemeColors 给 7 个按钮(clear/send/toggleTheme/changeFont/minimize/maximize/close)刷文字色、背景色和边框色,暗色用 clrDarkBlue 底配 clrWhite 字,亮色反之。想验证配色,开 MT5 把 darkTheme 初始值改一下编译即可看到差异。

MQL5 / C++
class="type">void OnClearButtonClick()
{
   inputBox.Text("");
   OnInputChange();
   Print("Input box cleared.");
}
class="type">void OnQuickMessageButtonClick(class="type">long index)
{
   class="type">class="kw">string quickMessages[] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 };
   class="type">class="kw">string message = quickMessages[(class="type">int)index];
   if (SendMessageToTelegram(message, InputChatId, InputBotToken))
      Print("Quick message sent: ", message);
   else
      Print("Failed to send quick message.");
}
class="type">void OnInputChange()
{
   class="type">int currentLength = StringLen(inputBox.Text());
   charCounter.Text(IntegerToString(currentLength) + "/" + IntegerToString(MAX_MESSAGE_LENGTH));
   ChartRedraw();
}
class="type">void OnToggleThemeButtonClick()
{
   darkTheme = !darkTheme;
   UpdateThemeColors();
   Print("Theme toggled: ", darkTheme ? "Dark" : "Light");
}
class="type">void UpdateThemeColors()
{
   class="type">color textColor = darkTheme ? clrWhite : clrBlack;
   class="type">color buttonBgColor = darkTheme ? clrDarkSlateGray : clrGainsboro;
   class="type">color borderColor = darkTheme ? clrSlateGray : clrGray;
   class="type">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);

面板控件的主题与字体热切换

这段 MT5 面板代码把按钮批量换肤和字体轮换拆成了独立函数,方便在运行时动态调整界面而不重绘整个 EA。 循环里用 ArraySize(quickMessageButtons) 遍历快捷消息按钮数组,逐个调用 UpdateButtonTheme 改文字、背景和边框色,最后 ChartRedraw() 一次刷新,避免每改一个控件就重绘一次图表。 字体切换靠 currentFontIndex 对 availableFonts 数组长度取模实现循环轮换,SetFontForAll 一口气给输入框、发送、清屏、主题、字体、最小化等 9 个固定控件加快捷消息按钮统一换字体,Print 出当前字体名便于日志排查。 双因素认证的 6 位随机码用 MathRand() % 1000000 生成,再用 StringFormat("%06d", code) 补前导零,保证像 000123 这种短码也能正确显示。最小化只 Hide 面板和最小化键、Show 最大化键,逻辑轻量。

MQL5 / C++
for(class="type">int i = class="num">0; i < ArraySize(quickMessageButtons); i++)
{
   UpdateButtonTheme(quickMessageButtons[i], textColor, buttonBgColor, borderColor);
}
ChartRedraw();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Apply theme settings to a button                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void UpdateButtonTheme(CButton &button, class="type">color textColor, class="type">color bgColor, class="type">color borderColor)
{
   button.SetTextColor(textColor);
   button.SetBackgroundColor(bgColor);
   button.SetBorderColor(borderColor);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle change font button click                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChangeFontButtonClick()
{
   currentFontIndex = (currentFontIndex + class="num">1) % ArraySize(availableFonts);
   SetFontForAll(availableFonts[currentFontIndex]);
   Print("Font changed to: ", availableFonts[currentFontIndex]);
   ChartRedraw();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Set font for all input boxes and buttons                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SetFontForAll(class="type">class="kw">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(class="type">int i = class="num">0; i < ArraySize(quickMessageButtons); i++)
   {
      quickMessageButtons[i].Font(fontName);
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Generate a random class="num">6-digit code for 2FA                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string GenerateRandom6DigitCode()
{
   class="type">int code = MathRand() % class="num">1000000; class=class="str">"cmt">// Produces a class="num">6-digit number
   class="kw">return StringFormat("%06d", code); class=class="str">"cmt">// Ensures leading zeros
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle minimize button click                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnMinimizeButtonClick()
{
   minimized = true;
   adminPanel.Hide();
   minimizeButton.Hide();
   maximizeButton.Show();
   closeButton.Show();
   Print("Panel minimized.");
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle maximize button click                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnMaximizeButtonClick()
{
   if(minimized)
   {

「面板状态切换与 Telegram 推送的实现细节」

管理面板的最大化逻辑里,先把 adminPanel 显示出来,同时把最小化按钮露出来、最大化和关闭按钮藏掉,再把 minimized 标记置为 false,最后往日志打一句 Panel maximized.,方便在 MT5 Experts 标签页里确认状态翻转。 关闭按钮的回调很硬:直接调 ExpertRemove() 把 EA 从图表卸掉,并打印 Admin panel closed.。这意味着该按钮不是收起界面,而是终止整个脚本运行,挂实盘前得想清楚要不要留这刀。 向 Telegram 发消息的函数用 WebRequest 走 HTTPS POST。URL 拼的是 https://api.telegram.org/bot + botToken + /sendMessage,JSON 体只塞 chat_id 和 text 两个字段;超时写死 5000 毫秒,返回码等于 200 才视为发送成功。 实测在 MT5 里若没在 EA 设置勾选「允许 WebRequest 指向 https://api.telegram.org」,该函数必返回 false 且 GetLastError() 通常是 4014。外汇与贵金属交易本身杠杆高、风险大,这类自动发信辅助工具只降低沟通成本,不改善信号胜率。

MQL5 / C++
adminPanel.Show();
minimizeButton.Show();
maximizeButton.Hide();
closeButton.Hide();
minimized = false;
Print("Panel maximized.");
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle close button click for admin panel                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnCloseButtonClick()
{
   ExpertRemove();
   Print("Admin panel closed.");
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Send the message to Telegram                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SendMessageToTelegram(class="type">class="kw">string message, class="type">class="kw">string chatId, class="type">class="kw">string botToken)
{
   class="type">class="kw">string url = "https:class=class="str">"cmt">//api.telegram.org/bot" + botToken + "/sendMessage";
   class="type">class="kw">string jsonMessage = "{\"chat_id\":\"" + chatId + "\", \"text\":\"" + message + "\"}";
   class="type">char postData[];
   ArrayResize(postData, StringToCharArray(jsonMessage, postData) - class="num">1);
   class="type">int timeout = class="num">5000;
   class="type">char result[];
   class="type">class="kw">string responseHeaders;
   class="type">int responseCode = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, postData, result, responseHeaders);
   if (responseCode == class="num">200)
   {
      Print("Message sent successfully: ", message);
      class="kw">return true;
   }
   else
   {
      Print("Failed to send message. HTTP code: ", responseCode, " Error code: ", GetLastError());
      Print("Response: ", CharArrayToString(result));
      class="kw">return false;
   }
}

◍ 面板初始化与硬编码鉴权入口

这套 MT5 管理面板在 OnInit 阶段先弹鉴权窗,过了才允许创建主界面。代码里把密码写死成 "2024",Telegram 的 ChatID 和 BotToken 也以 input 常量暴露,属于本地 demo 级实现,实盘前必须替换。 快速消息预设了 8 个字符串输入(Updates / Close all / Swing Entry 等),对应盘中对助手或喊单群的常用指令,MAX_MESSAGE_LENGTH 锁在 4096——正好是 Telegram API 单消息上限,超长会被截断。 OnInit 里先调 ShowAuthenticationPrompt(),返回 false 直接 INIT_FAILED 退出;通过后依次建 adminHomePanel 与 communicationsPanel 两个 CDialog,坐标都是 (30,30) 宽高 500,叠在同一个图表上,靠后续逻辑切换显隐。 别把明文密码当临时方案留着。外汇与贵金属杠杆高、滑点突变频繁,这类面板若直连真实账号,硬编码凭证泄露可能触发非授权平仓。

MQL5 / C++
class="macro">#include <Controls\Dialog.mqh>
class="macro">#include <Controls\Button.mqh>
class="macro">#include <Controls\Edit.mqh>
class="macro">#include <Controls\Label.mqh>
class=class="str">"cmt">// Input parameters for quick messages
input class="type">class="kw">string QuickMessage1 = "Updates";
input class="type">class="kw">string QuickMessage2 = "Close all";
input class="type">class="kw">string QuickMessage3 = "In deep profits";
input class="type">class="kw">string QuickMessage4 = "Hold position";
input class="type">class="kw">string QuickMessage5 = "Swing Entry";
input class="type">class="kw">string QuickMessage6 = "Scalp Entry";
input class="type">class="kw">string QuickMessage7 = "Book profit";
input class="type">class="kw">string QuickMessage8 = "Invalid Signal";
input class="type">class="kw">string InputChatId = "YOUR_CHAT_ID";
input class="type">class="kw">string InputBotToken = "YOUR_BOT_TOKEN";
class=class="str">"cmt">// Constants for 2FA
const class="type">class="kw">string Hardcoded2FAChatId = "Replace with your chat ID from telegram";
const class="type">class="kw">string Hardcoded2FABotToken = "Replace with your bot token from telegram";
class=class="str">"cmt">// Global variables
CDialog adminHomePanel, tradeManagementPanel, communicationsPanel;
CDialog authentication, twoFactorAuth;
CButton sendButton, clearButton, changeFontButton, toggleThemeButton;
CButton loginButton, closeAuthButton, twoFALoginButton, close2FAButton;
CButton quickMessageButtons[class="num">8], minimizeButton, maximizeButton, closeButton;
CButton tradeMgmtAccessButton, communicationsPanelAccessButton;
CEdit inputBox, passwordInputBox, twoFACodeInput;
CLabel charCounter, passwordPromptLabel, feedbackLabel, twoFAPromptLabel, twoFAFeedbackLabel;
class="type">bool minimized = false;
class="type">bool darkTheme = false;
class="type">int MAX_MESSAGE_LENGTH = class="num">4096;
class="type">class="kw">string availableFonts[] = { "Arial", "Courier New", "Verdana", "Times New Roman" };
class="type">int currentFontIndex = class="num">0;
class="type">class="kw">string Password = "class="num">2024"; class=class="str">"cmt">// Hardcoded password
class="type">class="kw">string twoFACode = "";
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
   if (!ShowAuthenticationPrompt())
   {
      Print("Authorization failed. Exiting...");
      class="kw">return INIT_FAILED;
   }
   if (!adminHomePanel.Create(ChartID(), "Admin Home Panel", class="num">0, class="num">30, class="num">30, class="num">500, class="num">500))
   {
      Print("Failed to create Admin Home Panel");
      class="kw">return INIT_FAILED;
   }
   if (!CreateAdminHomeControls())
   {
      Print("Home panel control creation failed");
      class="kw">return INIT_FAILED;
   }
   if (!communicationsPanel.Create(ChartID(), "Communications Panel", class="num">0, class="num">30, class="num">30, class="num">500, class="num">500))
   {
      Print("Failed to create Communications panel dialog");

EA 初始化与密码验证面板的硬骨头

在 MT5 自定义 EA 的 OnInit 阶段,任何子模块创建失败都必须立刻 return INIT_FAILED,否则图表上会挂一个半残界面。上面这段把 adminHomePanel、communicationsPanel、tradeManagementPanel 三个面板默认全 Hide,坐标分别给了 (0,30) 起步、宽高 500×500,初始化成功才放行。 ShowAuthenticationPrompt 函数单独拉出一个鉴权弹窗:密码框 passwordInputBox 定位在 (0,20) 偏移 70 像素,宽 260 高 95;提示标签和反馈标签 feedbackLabel 用了 clrRed,专门标错误的密码尝试。 跑这段前先在 MT5 编辑器里确认三个面板类和 authentication 对象都已声明,否则 Create 返回 false 会直接卡在初始化。外汇与贵金属 EA 涉及下单逻辑,实盘前务必在策略测试器用模拟盘验证,这类 GUI 鉴权模块出错概率不低。

MQL5 / C++
    class="kw">return INIT_FAILED;
  }
  if (!CreateControls())
  {
    Print("Control creation failed");
    class="kw">return INIT_FAILED;
  }
  if (!tradeManagementPanel.Create(ChartID(), "Trade Management Panel", class="num">0, class="num">30, class="num">30, class="num">500, class="num">500))
  {
    Print("Failed to create Communictions panel dialog");
    class="kw">return INIT_FAILED;
  }
  
  adminHomePanel.Hide(); class=class="str">"cmt">// Hide home panel by class="kw">default on initialization
  communicationsPanel.Hide(); class=class="str">"cmt">// Hide the Communications Panel
  tradeManagementPanel.Hide();class=class="str">"cmt">// Hide the Trade Management Panel
  class="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Show authentication input dialog                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool ShowAuthenticationPrompt()
{
  if (!authentication.Create(ChartID(), "Authentication", class="num">0, class="num">100, class="num">100, class="num">500, class="num">300))
  {
    Print("Failed to create authentication dialog");
    class="kw">return false;
  }
  if (!passwordInputBox.Create(ChartID(), "PasswordInputBox", class="num">0, class="num">20, class="num">70, class="num">260, class="num">95))
  {
    Print("Failed to create password input box");
    class="kw">return false;
  }
  authentication.Add(passwordInputBox);
  if (!passwordPromptLabel.Create(ChartID(), "PasswordPromptLabel", class="num">0, class="num">20, class="num">20, class="num">260, class="num">40))
  {
    Print("Failed to create password prompt label");
    class="kw">return false;
  }
  passwordPromptLabel.Text("Enter password: Access Admin Panel");
  authentication.Add(passwordPromptLabel);
  if (!feedbackLabel.Create(ChartID(), "FeedbackLabel", class="num">0, class="num">20, class="num">140, class="num">380, class="num">160))
  {
    Print("Failed to create feedback label");
    class="kw">return false;
  }
  feedbackLabel.Text("");
  feedbackLabel.Color(clrRed); class=class="str">"cmt">// Red class="type">color for incorrect attempts
  authentication.Add(feedbackLabel);
  if (!loginButton.Create(ChartID(), "LoginButton", class="num">0, class="num">20, class="num">120, class="num">100, class="num">140))
  {
    Print("Failed to create login button");
    class="kw">return false;
  }

「在MT5图表上拼出双因子验证弹窗」

做信号社区类的EA,登录环节塞一个图表内2FA弹窗比系统对话框更顺手。下面这段把验证框、输入框、提示标签和反馈标签按坐标堆到同一父容器里,坐标单位都是像素,原点在图表左上角。 注意 twoFAFeedbackLabel 默认刷成 clrRed,专门用来在验证码输错时显示红字提示;初始 Text("") 留空,只有后端比对失败才写内容。这种视觉反馈能让用户立刻知道哪一步卡住了,不用去翻专家日志。 close2FAButton 的 Create 参数里,X=120、Y=120、W=200、H=140,和前面 authentication 里的关闭键同尺寸,保持界面控件节奏一致。外汇与贵金属账户接这类远程验证涉及密钥流转,属于高风险操作,建议先在模拟盘把整条登录链路跑通再上真实账户。

MQL5 / C++
loginButton.Text("Login");
authentication.Add(loginButton);
if (!closeAuthButton.Create(ChartID(), "CloseAuthButton", class="num">0, class="num">120, class="num">120, class="num">200, class="num">140))
{
   Print("Failed to create close button for authentication");
   class="kw">return false;
}
closeAuthButton.Text("Close");
authentication.Add(closeAuthButton);
authentication.Show();
ChartRedraw();
class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Show two-factor authentication input dialog                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ShowTwoFactorAuthPrompt()
{
   if (!twoFactorAuth.Create(ChartID(), "Two-Factor Authentication", class="num">0, class="num">100, class="num">100, class="num">500, class="num">300))
   {
      Print("Failed to create 2FA dialog");
      class="kw">return;
   }
   if (!twoFACodeInput.Create(ChartID(), "TwoFACodeInput", class="num">0, class="num">20, class="num">70, class="num">260, class="num">95))
   {
      Print("Failed to create 2FA code input box");
      class="kw">return;
   }
   twoFactorAuth.Add(twoFACodeInput);
   if (!twoFAPromptLabel.Create(ChartID(), "TwoFAPromptLabel", class="num">0, class="num">20, class="num">20, class="num">380, class="num">40))
   {
      Print("Failed to create 2FA prompt label");
      class="kw">return;
   }
   twoFAPromptLabel.Text("Enter the 2FA code sent to your Telegram:");
   twoFactorAuth.Add(twoFAPromptLabel);
   if (!twoFAFeedbackLabel.Create(ChartID(), "TwoFAFeedbackLabel", class="num">0, class="num">20, class="num">140, class="num">380, class="num">160))
   {
      Print("Failed to create 2FA feedback label");
      class="kw">return;
   }
   twoFAFeedbackLabel.Text("");
   twoFAFeedbackLabel.Color(clrRed); class=class="str">"cmt">// Red class="type">color for incorrect 2FA attempts
   twoFactorAuth.Add(twoFAFeedbackLabel);
   if (!twoFALoginButton.Create(ChartID(), "TwoFALoginButton", class="num">0, class="num">20, class="num">120, class="num">100, class="num">140))
   {
      Print("Failed to create 2FA login button");
      class="kw">return;
   }
   twoFALoginButton.Text("Verify");
   twoFactorAuth.Add(twoFALoginButton);
   if (!close2FAButton.Create(ChartID(), "Close2FAButton", class="num">0, class="num">120, class="num">120, class="num">200, class="num">140))
   {
      Print("Failed to create close button for 2FA");
      class="kw">return;

◍ 管理面板按钮的创建与坐标排布

在 MT5 自定义面板开发中,管理员主页的控件靠 CreateAdminHomeControls() 统一生成。函数先取 ChartID(),随后逐个用 Create() 把按钮挂到图表上,任何一次创建失败就 Print 报错并返回 false,保证面板完整性。 坐标参数是排布关键:交易管理按钮放在 (50,50) 到 (250,90),通讯面板按钮在 (50,100) 到 (250,140),两者纵向错开 50 像素避免重叠。最小化、最大化、关闭三个系统按钮则贴在右上角负坐标区,X 从 375 到 465,Y 统一为 -22,形成类窗口标题栏。 每个按钮创建后都要 Text() 写标签并 Add() 进 adminHomePanel 容器,最后返回 true 表示装载成功。想验证可直接把这段代码塞进 EA 的 OnInit,挂 EURUSD 图表看是否弹出带五个按钮的面板——外汇与贵金属交易界面定制属高风险环境,代码异常可能引发图表卡死。

MQL5 / C++
class="type">bool CreateAdminHomeControls()
{
   class="type">long chart_id = ChartID();
   if (!tradeMgmtAccessButton.Create(chart_id, "TradeMgmtAccessButton", class="num">0, class="num">50, class="num">50, class="num">250, class="num">90))
   {
      Print("Failed to create Trade Management Access button");
      class="kw">return false;
   }
   tradeMgmtAccessButton.Text("Trade Management Panel");
   adminHomePanel.Add(tradeMgmtAccessButton);
   if (!communicationsPanelAccessButton.Create(chart_id, "CommunicationsPanelAccessButton", class="num">0, class="num">50, class="num">100, class="num">250, class="num">140))
   {
      Print("Failed to create Communications Panel Access button");
      class="kw">return false;
   }
   communicationsPanelAccessButton.Text("Communications Panel");
   adminHomePanel.Add(communicationsPanelAccessButton);
   if (!minimizeButton.Create(chart_id, "MinimizeButton", class="num">0, class="num">375, -class="num">22, class="num">405, class="num">0))
   {
      Print("Failed to create minimize button");
      class="kw">return false;
   }
   minimizeButton.Text("_");
   adminHomePanel.Add(minimizeButton);
   if (!maximizeButton.Create(chart_id, "MaximizeButton", class="num">0, class="num">405, -class="num">22, class="num">435, class="num">0))
   {
      Print("Failed to create maximize button");
      class="kw">return false;
   }
   maximizeButton.Text("[ ]");
   adminHomePanel.Add(maximizeButton);
   if (!closeButton.Create(chart_id, "CloseButton", class="num">0, class="num">435, -class="num">22, class="num">465, class="num">0))
   {
      Print("Failed to create close button");
      class="kw">return false;
   }
   closeButton.Text("X");
   adminHomePanel.Add(closeButton);
   class="kw">return true;
}

面板按钮的事件分发逻辑

在 MT5 自定义面板里,所有图形对象的点击最终都会汇到 OnChartEvent,靠 sparam 字符串区分是哪个按钮被触发。下面这段是典型的 admin 面板与登录鉴权面板的按钮路由,用 if-else 链把字符串映射到对应处理函数。 注意 CloseButton 在 admin 分支直接调 ExpertRemove(),意味着点关闭就卸载整个 EA;而通信面板里的 CloseButton 走的是 OnCloseButtonClick(),只关界面不退出程序。两套关闭语义不一致,复制代码时容易踩坑。 switch(id) 里只处理 CHARTEVENT_OBJECT_CLICK,并在其中再次判断 sparam。QuickMessageButton 用 StringFind 做前缀匹配,再用 StringSubstr(sparam,18) 截出序号——因为 "QuickMessageButton" 自身长度正好是 18,截出来的子串转 long 就是按钮索引,调用时再减 1 对齐数组下标。 在外盘黄金或外汇品种上挂这类面板 EA 属高风险操作,按钮误触可能瞬间撤掉专家或发错指令;实盘前务必在策略测试器里把每个分支跑一遍。

MQL5 / C++
if (sparam == "TradeMgmtAccessButton")
{
   tradeManagementPanel.Show();
   adminHomePanel.Hide();
}
else if (sparam == "CommunicationsPanelAccessButton")
{
   communicationsPanel.Show();
   adminHomePanel.Hide();
}
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();
   }
}
class="kw">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") != -class="num">1)
      {
         class="type">long index = StringToInteger(StringSubstr(sparam, class="num">18));
         OnQuickMessageButtonClick(index - class="num">1);

「面板登录与双因素校验的事件分支」

这段逻辑承接图表事件分发,专门处理用户改输入框和点击登录按钮两类动作。CHARTEVENT_OBJECT_ENDEDIT 里只认 sparam 为 "InputBox" 的回车事件,触发 OnInputChange() 做本地回显或校验,其余编辑动作直接 break 不浪费 CPU。 OnLoginButtonClick() 先读 passwordInputBox.Text(),和硬编码的 Password 比对。一致就调 GenerateRandom6DigitCode() 生成 6 位随机码,用 SendMessageToTelegram() 推到写死 chat id 和 bot token 的会话,随后销毁登录框、弹双因素提示。不一致则反馈 "Wrong password. Try again." 并清空密码框。 双因素那一层,OnTwoFALoginButtonClick() 比对 twoFACodeInput 与内存里的 twoFACode,对上才 Destroy 掉 2FA 框并 Show() 出 adminHomePanel 交易管理面板;错就清输入框。两个关闭按钮 OnCloseAuthButtonClick / OnClose2FAButtonClick 都直接 Destroy 当前 UI 并 ExpertRemove() 退出 EA,防止无授权挂机。 这类写死 token 的做法在真实账户跑有泄露风险,外汇和贵金属本身杠杆高、波动猛,密钥落代码里可能被人反编译扒走,实盘前建议改外部加密配置。

MQL5 / C++
      }
      break;
      case CHARTEVENT_OBJECT_ENDEDIT:
         if (sparam == "InputBox") OnInputChange();
         break;
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle login button click                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnLoginButtonClick()
{
   class="type">class="kw">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("");
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle 2FA login button click                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTwoFALoginButtonClick()
{
   class=class="str">"cmt">// If 2FA is successful, show the trade management panel
   class="type">class="kw">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("");
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle close button for authentication                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnCloseAuthButtonClick()
{
   authentication.Destroy();
   ExpertRemove(); class=class="str">"cmt">// Exit the expert
   Print("Authentication dialog closed.");
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle close button for 2FA                                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnClose2FAButtonClick()
{
   twoFactorAuth.Destroy();
   ExpertRemove();
   Print("2FA dialog closed.");
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create necessary UI controls                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 在图表上拼出通讯面板的控件

想在 MT5 图表里嵌一个能发消息的悬浮面板,第一步是把各个子控件用代码真正画出来。下面这段 CreateControls 函数把输入框、字数计数器、清空/发送/换字体/换主题按钮以及最小化最大化按钮逐个挂到 communicationsPanel 容器上。 坐标参数都是相对于图表左上角的像素值,比如输入框占 (5,25) 到 (460,95),而最小化按钮被放到 y=-22 的位置,也就是图表标题栏那一圈,这种负坐标用法在原生 GUI 里很容易踩坑。 每个 Create 调用失败都会 Print 具体错误并返回 false,意味着只要有一个控件建不出来,整个面板就不会显示。你可以直接把这段代码塞进 EA 的 OnInit,编译后拖到 XAUUSD 的 M1 图上验证——外汇和贵金属杠杆高,面板只是辅助,信号本身请自行甄别风险。 逐行看:ChartID() 取当前图表句柄;inputBox.Create 建输入框并加入面板;charCounter 建在右上角并显示 "0/" 加最大长度;clearButton、sendButton 等依次按坐标布局,最后 minimizeButton 和 maximizeButton 用负 y 值贴到图表边框。

MQL5 / C++
class="type">bool CreateControls()
{
   class="type">long chart_id = ChartID();
   if (!inputBox.Create(chart_id, "InputBox", class="num">0, class="num">5, class="num">25, class="num">460, class="num">95))
   {
      Print("Failed to create input box");
      class="kw">return false;
   }
   communicationsPanel.Add(inputBox);
   if (!charCounter.Create(chart_id, "CharCounter", class="num">0, class="num">380, class="num">5, class="num">460, class="num">25))
   {
      Print("Failed to create character counter");
      class="kw">return false;
   }
   charCounter.Text("class="num">0/" + IntegerToString(MAX_MESSAGE_LENGTH));
   communicationsPanel.Add(charCounter);
   if (!clearButton.Create(chart_id, "ClearButton", class="num">0, class="num">235, class="num">95, class="num">345, class="num">125))
   {
      Print("Failed to create clear button");
      class="kw">return false;
   }
   clearButton.Text("Clear");
   communicationsPanel.Add(clearButton);
   if (!sendButton.Create(chart_id, "SendButton", class="num">0, class="num">350, class="num">95, class="num">460, class="num">125))
   {
      Print("Failed to create send button");
      class="kw">return false;
   }
   sendButton.Text("Send");
   communicationsPanel.Add(sendButton);
   if (!changeFontButton.Create(chart_id, "ChangeFontButton", class="num">0, class="num">95, class="num">95, class="num">230, class="num">115))
   {
      Print("Failed to create change font button");
      class="kw">return false;
   }
   changeFontButton.Text("Font<>");
   communicationsPanel.Add(changeFontButton);
   if (!toggleThemeButton.Create(chart_id, "ToggleThemeButton", class="num">0, class="num">5, class="num">95, class="num">90, class="num">115))
   {
      Print("Failed to create toggle theme button");
      class="kw">return false;
   }
   toggleThemeButton.Text("Theme<>");
   communicationsPanel.Add(toggleThemeButton);
   if (!minimizeButton.Create(chart_id, "MinimizeButton", class="num">0, class="num">375, -class="num">22, class="num">405, class="num">0))
   {
      Print("Failed to create minimize button");
      class="kw">return false;
   }
   minimizeButton.Text("_");
   communicationsPanel.Add(minimizeButton);
   if (!maximizeButton.Create(chart_id, "MaximizeButton", class="num">0, class="num">405, -class="num">22, class="num">435, class="num">0))
   {
      Print("Failed to create maximize button");

面板按钮与快捷消息的布局逻辑

这段代码片段承接了通信面板的收尾构建:先挂上最大化按钮(文本为 [ ])和关闭按钮(文本 X,坐标 435,-22 到 465,0),任一创建失败就打印错误并退出,最后转交快捷消息按钮的批量生成。 CreateQuickMessageButtons 里定义了 8 条快捷消息,以两列网格排布:起始点 (5,160),单按钮宽 222、高 65,间隙 5 像素。循环用 i%2 算横移、i/2 算纵移,能在 4 行内把 8 个按钮铺满,坐标计算直接写在 Create 参数里。 OnDeinit 只做一件事:销毁面板对象并打印 Deinitialization complete,避免 MT5 图表残留 GUI 资源。 OnSendButtonClick 读取输入框文本,长度大于 0 才尝试发 Telegram;这里截取的片段显示若发送成功打印回显,失败则走 else 分支(原文未给出)。外汇与贵金属交易使用这类自动推送面板时,Bot Token 与 Chat ID 明文写在输入参数里属于高风险暴露,建议用本地加密或隔离账户。

MQL5 / C++
  class="kw">return false;
  }
  maximizeButton.Text("[ ]");
  communicationsPanel.Add(maximizeButton);
  if (!closeButton.Create(chart_id, "CloseButton", class="num">0, class="num">435, -class="num">22, class="num">465, class="num">0))
  {
    Print("Failed to create close button");
    class="kw">return false;
  }
  closeButton.Text("X");
  communicationsPanel.Add(closeButton);
  class="kw">return CreateQuickMessageButtons();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create quick message buttons                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CreateQuickMessageButtons()
{
  class="type">class="kw">string quickMessages[] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 };
  class="type">int startX = class="num">5, startY = class="num">160, width = class="num">222, height = class="num">65, spacing = class="num">5;
  for (class="type">int i = class="num">0; i < ArraySize(quickMessages); i++)
  {
    class="type">bool created = quickMessageButtons[i].Create(ChartID(), "QuickMessageButton" + IntegerToString(i + class="num">1), class="num">0,
      startX + (i % class="num">2) * (width + spacing), startY + (i / class="num">2) * (height + spacing),
      startX + (i % class="num">2) * (width + spacing) + width, startY + (i / class="num">2) * (height + spacing) + height);
    if (!created)
    {
      Print("Failed to create quick message button ", i + class="num">1);
      class="kw">return false;
    }
    quickMessageButtons[i].Text(quickMessages[i]);
    communicationsPanel.Add(quickMessageButtons[i]);
  }
  class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
{
  communicationsPanel.Destroy();
  Print("Deinitialization complete");
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle custom message send button click                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnSendButtonClick()
{
  class="type">class="kw">string message = inputBox.Text();
  if (StringLen(message) > class="num">0)
  {
    if (SendMessageToTelegram(message, InputChatId, InputBotToken))
      Print("Custom message sent: ", message);
    else

「面板交互逻辑的底层回调」

这段 MT5 面板代码把几个关键按钮的点击事件拆成了独立函数,方便在 EA 或指标里直接挂接 GUI 逻辑。清空按钮只做两件事:把输入框文本置空,再触发一次输入变更回调刷新计数。 快捷消息函数用数组存了 8 条预设文本(QuickMessage1~8),按传入的 index 取对应内容发给 Telegram;发送失败仅在日志打印提示,不弹窗阻断。外汇与贵金属行情波动剧烈,这类自动推送仅作辅助提醒,实操仍有滑点漏发风险。 字符计数器在 OnInputChange 里实时算 StringLen,拼成「当前/上限」格式,上限由 MAX_MESSAGE_LENGTH 宏控制,改这个值就能放宽单条长度。主题切换靠 darkTheme 布尔取反,再统一调 UpdateThemeColors 重刷按钮与背景色——深色用 clrDarkBlue 底配白字,浅色反之。 直接把这几段抄进你的 .mq5,把 SendMessageToTelegram 换成自己的 WebRequest 封装,就能在图表上跑出可切换主题的喊单面板。

MQL5 / C++
class="type">void OnClearButtonClick()
{
   inputBox.Text("");
   OnInputChange();
   Print("Input box cleared.");
}

class="type">void OnQuickMessageButtonClick(class="type">long index)
{
   class="type">class="kw">string quickMessages[] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 };
   class="type">class="kw">string message = quickMessages[(class="type">int)index];
   if (SendMessageToTelegram(message, InputChatId, InputBotToken))
      Print("Quick message sent: ", message);
   else
      Print("Failed to send quick message.");
}

class="type">void OnInputChange()
{
   class="type">int currentLength = StringLen(inputBox.Text());
   charCounter.Text(IntegerToString(currentLength) + "/" + IntegerToString(MAX_MESSAGE_LENGTH));
   ChartRedraw();
}

class="type">void OnToggleThemeButtonClick()
{
   darkTheme = !darkTheme;
   UpdateThemeColors();
   Print("Theme toggled: ", darkTheme ? "Dark" : "Light");
}

class="type">void UpdateThemeColors()
{
   class="type">color textColor = darkTheme ? clrWhite : clrBlack;
   class="type">color buttonBgColor = darkTheme ? clrDarkSlateGray : clrGainsboro;
   class="type">color borderColor = darkTheme ? clrSlateGray : clrGray;
   class="type">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);

◍ 面板控件主题与字体切换的实现细节

这段逻辑集中处理 MT5 自定义通讯面板的视觉状态:按钮配色、字体轮换、最小化收起,以及一段 2FA 随机码生成。直接看代码能少走弯路。 UpdateButtonTheme 把文字色、背景色、边框色一次性套到指定 CButton 引用上;调用方先处理最大化、关闭两个固定按钮,再用 for 循环遍历 quickMessageButtons 数组统一刷新,最后 ChartRedraw 让图表重绘生效。 字体切换由 OnChangeFontButtonClick 驱动:currentFontIndex 对 availableFonts 数组长度取模实现循环切换,SetFontForAll 则把 inputBox、八个功能按钮及快捷消息按钮全部重设字体,Print 出当前字体名便于终端排查。 GenerateRandom6DigitCode 用 MathRand() % 1000000 取 0~999999 的随机整数,StringFormat("%06d", code) 补前导零保证固定 6 位;外汇/贵金属面板若接 2FA,这种本地随机码仅适合演示,实盘存在被预测可能,属高风险环节。 最小化由 OnMinimizeButtonClick 置 minimized=true 并 Hide 面板与最小化键,同时 Show 最大化与关闭键;最大化逻辑对称反向操作即可。

MQL5 / C++
UpdateButtonTheme(maximizeButton, textColor, buttonBgColor, borderColor);
UpdateButtonTheme(closeButton, textColor, buttonBgColor, borderColor);
for (class="type">int i = class="num">0; i < ArraySize(quickMessageButtons); i++)
{
   UpdateButtonTheme(quickMessageButtons[i], textColor, buttonBgColor, borderColor);
}
ChartRedraw();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Apply theme settings to a button                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void UpdateButtonTheme(CButton &button, class="type">color textColor, class="type">color bgColor, class="type">color borderColor)
{
   button.SetTextColor(textColor);
   button.SetBackgroundColor(bgColor);
   button.SetBorderColor(borderColor);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle change font button click                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChangeFontButtonClick()
{
   currentFontIndex = (currentFontIndex + class="num">1) % ArraySize(availableFonts);
   SetFontForAll(availableFonts[currentFontIndex]);
   Print("Font changed to: ", availableFonts[currentFontIndex]);
   ChartRedraw();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Set font for all input boxes and buttons                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SetFontForAll(class="type">class="kw">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 (class="type">int i = class="num">0; i < ArraySize(quickMessageButtons); i++)
   {
      quickMessageButtons[i].Font(fontName);
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Generate a random class="num">6-digit code for 2FA                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string GenerateRandom6DigitCode()
{
   class="type">int code = MathRand() % class="num">1000000; class=class="str">"cmt">// Produces a class="num">6-digit number
   class="kw">return StringFormat("%06d", code); class=class="str">"cmt">// Ensures leading zeros
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle minimize button click                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnMinimizeButtonClick()
{
   minimized = true;
   communicationsPanel.Hide();
   minimizeButton.Hide();
   maximizeButton.Show();
   closeButton.Show();
   Print("Panel minimized.");
}

把面板事件接到 Telegram 推送

在自定义管理面板里,最大化按钮和恢复关闭态是两套独立回调。下面这段把窗口还原逻辑和彻底退出 EA 的逻辑分开写,避免误点关闭直接干掉实例。 OnMaximizeButtonClick 只在 minimized 为真时生效:重新显示通讯面板、把最小化按钮露出来、隐藏最大化与关闭键,最后把状态位翻回 false 并打日志。OnCloseButtonClick 则直接调 ExpertRemove() 卸载 EA,适合管理员面板那种「确认离开」的场景。 真正有用的是 SendMessageToTelegram:用 WebRequest 向 Telegram Bot API 发 POST。注意 timeout 写死 5000 毫秒,返回码等于 200 才视为送达,否则把 HTTP 码和 GetLastError() 一起打印出来排错。 别在实盘 EA 里硬编码 botToken 把密钥写进源码会随 ex5 分发泄露,建议从输入框或加密文件读取。外汇与贵金属杠杆高,任何自动通知失效都可能让你错过平仓信号,上线前先在模拟盘跑通这套回调。

MQL5 / C++
class="type">void OnMaximizeButtonClick()
{
   if (minimized)
   {
      communicationsPanel.Show();
      minimizeButton.Show();
      maximizeButton.Hide();
      closeButton.Hide();
      minimized = false;
      Print("Panel maximized.");
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle close button click for admin panel                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnCloseButtonClick()
{
   ExpertRemove();
   Print("Admin panel closed.");
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Send the message to Telegram                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SendMessageToTelegram(class="type">class="kw">string message, class="type">class="kw">string chatId, class="type">class="kw">string botToken)
{
   class="type">class="kw">string url = "https:class=class="str">"cmt">//api.telegram.org/bot" + botToken + "/sendMessage";
   class="type">class="kw">string jsonMessage = "{\"chat_id\":\"" + chatId + "\", \"text\":\"" + message + "\"}";
   class="type">char postData[];
   ArrayResize(postData, StringToCharArray(jsonMessage, postData) - class="num">1);
   class="type">int timeout = class="num">5000;
   class="type">char result[];
   class="type">class="kw">string responseHeaders;
   class="type">int responseCode = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, postData, result, responseHeaders);
   if (responseCode == class="num">200)
   {
      Print("Message sent successfully: ", message);
      class="kw">return true;
   }
   else
   {
      Print("Failed to send message. HTTP code: ", responseCode, " Error code: ", GetLastError());
      Print("Response: ", CharArrayToString(result));
      class="kw">return false;
   }
}

「首版面板卡窗与导航缺口」

在 MT5 管理员面板里跑第一版集成功能,整体能加载、能响应,但实测发现一个硬伤:一旦进入子窗口就锁死在当前上下文,没法切回主面板调别的功能。 这个卡窗不是偶发,是单次窗口栈没有后退入口导致的。当场能确认的结论只有一条——必须补导航按钮,才能在窗口与功能之间无缝跳。 顺带看一眼交易管理面板,当前是空的,没有任何挂单、平仓或风控逻辑接进来,属于未实现状态,后续要补按钮并跑终测。

◍ 给子面板加回家的入口

做多层级图表面板时,最容易卡住使用者的不是功能本身,而是从子面板回不到主界面。通信面板和交易管理面板各自挂一个“Home”按钮,点击即回到管理员主页面板,同时把当前子面板隐藏,这条路径必须提前铺好。 代码里用 CButton 声明了两个实例:homeButtonComm 与 homeButtonTrade,分别归属通信面板和交易管理面板。CreateControls 中它们的坐标都设为 (20,400) 到 (120,420),即距图表左缘 20 像素、上缘 400 像素、宽高 100 像素,两个按钮在屏幕上重叠位,实际显示时因所属面板互斥不会同时露面。 OnChartEvent 只认 CHARTEVENT_OBJECT_CLICK。sparam 等于 "HomeButtonComm" 时调 adminHomePanel.Show() 并 communicationsPanel.Hide();等于 "HomeButtonTrade" 时换 tradeManagementPanel 隐藏。这样点击来源和面板切换是一一映射,不会串台。 别把按钮坐标当装饰。若你之后把主面板缩到 (10,10) 起算,子面板 Home 按钮仍留在 400 像素下方,小屏可能点不到,开 MT5 拖一下锚点验证最实在。

MQL5 / C++
CButton homeButtonComm, homeButtonTrade; class=class="str">"cmt">// 每个子面板的“主页”按钮
class="type">bool CreateControls()
{
   class=class="str">"cmt">// Create Home Button for Communications Panel
   if (!homeButtonComm.Create(ChartID(), "HomeButtonComm", class="num">0, class="num">20, class="num">400, class="num">120, class="num">420))
   {
      Print("Failed to create Home button for Communications Panel");
      class="kw">return false;
   }
   homeButtonComm.Text("Home");
   communicationsPanel.Add(homeButtonComm);
   class=class="str">"cmt">// Create Home Button for Trade Management Panel
   if (!homeButtonTrade.Create(ChartID(), "HomeButtonTrade", class="num">0, class="num">20, class="num">400, class="num">120, class="num">420))
   {
      Print("Failed to create Home button for Trade Management Panel");
      class="kw">return false;
   }
   homeButtonTrade.Text("Home");
   tradeManagementPanel.Add(homeButtonTrade);
   class="kw">return true;
}
class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam)
{
   if (id == CHARTEVENT_OBJECT_CLICK)
   {
      if (sparam == "HomeButtonComm")
      {
         adminHomePanel.Show();
         communicationsPanel.Hide();
      }
      else if (sparam == "HomeButtonTrade")
      {
         adminHomePanel.Show();
         tradeManagementPanel.Hide();
      }
      else if (sparam == "TradeMgmtAccessButton")
      {
          tradeManagementPanel.Show();
          adminHomePanel.Hide();
      }
      else if (sparam == "CommunicationsPanelAccessButton")
      {
         communicationsPanel.Show();
         adminHomePanel.Hide();
      }
      else if (sparam == "MinimizeButton")
      {
         OnMinimizeButtonClick();
      }
      else if (sparam == "MaximizeButton")
      {
         OnMaximizeButtonClick();
      }
      else if (sparam == "CloseButton")
      {

在图表上拼出交易管理面板

做半自动盯盘,最怕手点 MT5 右键菜单找平仓入口,行情跳动时容易点错。把买、卖、平仓、改仓、设损、设盈做成图表按钮,点一下就发指令,节奏能快半拍。外汇和贵金属杠杆高,误触代价大,面板按钮位置必须自己算准。 下面这段在全局声明了六个 CButton 对象,然后在 CreateTradeManagementControls() 里逐个绑定到当前图表 ID。坐标用的是像素:Buy 按钮左上角在 (50,50),宽 150 高 90;Sell 在 (160,50),和 Buy 同高并排;Close Position 在 (50,100) 延伸到 (190,140),比上面两个矮一截。 Modify Position 放在 (200,100) 到 (340,140),Set SL 在 (50,150) 到 (150,190),Set TP 在 (160,150) 到 (260,190)。每个 Create 失败就 Print 错误并返回 false,全过才 return true。你直接把这段代码贴进 EA,编译后拖上 EURUSD 图表,就能看到六个方块浮在左上角。

MQL5 / C++
ExpertRemove();
    }
  }
}
CButton buyButton, sellButton, closePosButton, modifyPosButton, setSLButton, setTPButton; class=class="str">"cmt">/// 全局层面声明持仓管理按钮
class=class="str">"cmt">// Create the Trade Management Panel controls
class="type">bool CreateTradeManagementControls()
{
    class="type">long chart_id = ChartID();
    
    class=class="str">"cmt">// Buy Button
    if (!buyButton.Create(chart_id, "BuyButton", class="num">0, class="num">50, class="num">50, class="num">150, class="num">90))
    {
        Print("Failed to create Buy button");
        class="kw">return false;
    }
    buyButton.Text("Buy");
    tradeManagementPanel.Add(buyButton);
    
    class=class="str">"cmt">// Sell Button
    if (!sellButton.Create(chart_id, "SellButton", class="num">0, class="num">160, class="num">50, class="num">260, class="num">90))
    {
        Print("Failed to create Sell button");
        class="kw">return false;
    }
    sellButton.Text("Sell");
    tradeManagementPanel.Add(sellButton);
    class=class="str">"cmt">// Close Position Button
    if (!closePosButton.Create(chart_id, "ClosePosButton", class="num">0, class="num">50, class="num">100, class="num">190, class="num">140))
    {
        Print("Failed to create Close Position button");
        class="kw">return false;
    }
    closePosButton.Text("Close Position");
    tradeManagementPanel.Add(closePosButton);
    class=class="str">"cmt">// Modify Position Button
    if (!modifyPosButton.Create(chart_id, "ModifyPosButton", class="num">0, class="num">200, class="num">100, class="num">340, class="num">140))
    {
        Print("Failed to create Modify Position button");
        class="kw">return false;
    }
    modifyPosButton.Text("Modify Position");
    tradeManagementPanel.Add(modifyPosButton);
    class=class="str">"cmt">// Set Stop-Loss Button
    if (!setSLButton.Create(chart_id, "SetSLButton", class="num">0, class="num">50, class="num">150, class="num">150, class="num">190))
    {
        Print("Failed to create Set Stop-Loss button");
        class="kw">return false;
    }
    setSLButton.Text("Set SL");
    tradeManagementPanel.Add(setSLButton);
    class=class="str">"cmt">// Set Take-Profit Button
    if (!setTPButton.Create(chart_id, "SetTPButton", class="num">0, class="num">160, class="num">150, class="num">260, class="num">190))
    {
        Print("Failed to create Set Take-Profit button");
        class="kw">return false;
    }
    setTPButton.Text("Set TP");
    tradeManagementPanel.Add(setTPButton);
    class="kw">return true;
}

「在 MT5 里直接给电报客户发信号的面板骨架」

这个管理面板把交易操作与 Telegram 推送揉进同一个 EA 界面,版本号 1.21,核心目的是不离开 MT5 就能给客户端发消息。它用 CDialog 搭了主页、交易管理、通讯三个子面板,外加认证与二次验证两层弹窗,按钮和编辑框都在全局声明,方便跨函数调用。 面板预设了 8 条快捷话术,从「Updates」到「Invalid Signal」,覆盖喊单、平仓、加减仓等场景;单条消息上限锁在 4096 字符,对应 Telegram API 的文本长度边界。输入参数里留了 InputChatId 和 InputBotToken 两个坑,实盘前必须换成你自己的,否则推送会落到别人会话里。 二次验证写死了硬编码的 ChatId(7049213628)和 BotToken,外加一个明文字符串密码「2024」。这种写法在本地测试能跑,但丢到公开环境等于把门钥匙插在锁上——任何拿到 ex5 的人都能逆向出凭证。 字体切换给了 Arial、Courier New、Verdana、Times New Roman 四个选项,currentFontIndex 从 0 起算;minimized 和 darkTheme 两个布尔控制收起与暗色,初始化时都是 false。想验证的话,把下面代码贴进 MT5 新建 EA,编译后会在 OnInit 先弹认证框,关掉或验证失败就直接 Print 退出。

MQL5 / C++
class=class="str">"cmt">//|                                                                  Admin Panel.mq5  |
class=class="str">"cmt">//|                     Copyright class="num">2024, Clemence Benjamin              |
class=class="str">"cmt">//|      [MQL5官方文档]         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, Clemence Benjamin"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property description "A secure and responsive Admin Panel. Send messages to your telegram clients without leaving MT5"
class="macro">#class="kw">property version   "class="num">1.21"
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <Controls\Dialog.mqh>
class="macro">#include <Controls\Button.mqh>
class="macro">#include <Controls\Edit.mqh>
class="macro">#include <Controls\Label.mqh>
class=class="str">"cmt">// Input parameters for quick messages
input class="type">class="kw">string QuickMessage1 = "Updates";
input class="type">class="kw">string QuickMessage2 = "Close all";
input class="type">class="kw">string QuickMessage3 = "In deep profits";
input class="type">class="kw">string QuickMessage4 = "Hold position";
input class="type">class="kw">string QuickMessage5 = "Swing Entry";
input class="type">class="kw">string QuickMessage6 = "Scalp Entry";
input class="type">class="kw">string QuickMessage7 = "Book profit";
input class="type">class="kw">string QuickMessage8 = "Invalid Signal";
input class="type">class="kw">string InputChatId = "YOUR_CHAT_ID";
input class="type">class="kw">string InputBotToken = "YOUR_BOT_TOKEN";
class=class="str">"cmt">// Constants for 2FA
const class="type">class="kw">string Hardcoded2FAChatId = "class="num">7049213628";
const class="type">class="kw">string Hardcoded2FABotToken = "class="num">7491148147:AAHjzHVL1S74RG0Ib-pN2bgG7wEKD2Rd2MU";
class=class="str">"cmt">// Global variables
CDialog adminHomePanel, tradeManagementPanel, communicationsPanel;
CDialog authentication, twoFactorAuth;
CButton homeButtonComm, homeButtonTrade;
CButton buyButton, sellButton, closePosButton, modifyPosButton, setSLButton, setTPButton;class=class="str">"cmt">//| Position management buttons at global level declaration.
CButton sendButton, clearButton, changeFontButton, toggleThemeButton;
CButton loginButton, closeAuthButton, twoFALoginButton, close2FAButton;
CButton quickMessageButtons[class="num">8], minimizeButton, maximizeButton, closeButton;
CButton tradeMgmtAccessButton, communicationsPanelAccessButton;
CEdit inputBox, passwordInputBox, twoFACodeInput;
CLabel charCounter, passwordPromptLabel, feedbackLabel, twoFAPromptLabel, twoFAFeedbackLabel;
class="type">bool minimized = false;
class="type">bool darkTheme = false;
class="type">int MAX_MESSAGE_LENGTH = class="num">4096;
class="type">class="kw">string availableFonts[] = { "Arial", "Courier New", "Verdana", "Times New Roman" };
class="type">int currentFontIndex = class="num">0;
class="type">class="kw">string Password = "class="num">2024"; class=class="str">"cmt">// Hardcoded password
class="type">class="kw">string twoFACode = "";
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
   if (!ShowAuthenticationPrompt())
   {
      Print("Authorization failed. Exiting...");

◍ 面板初始化与鉴权弹窗的构建顺序

在 MT5 自定义控件的 EA 初始化里,所有面板对象必须逐个 Create 成功,否则直接返回 INIT_FAILED 中断装载。上面这段把 Admin Home、Communications、Trade Management 三个 500×500 的面板都挂在 ChartID() 上,坐标统一设在 (0,30),任何一步失败都会在终端打印具体错误并停止。 注意三个业务面板在初始化末尾都被 Hide() 隐藏,用户进图时只看到鉴权入口,这种「先藏后显」的顺序能避免界面闪跳。外汇与贵金属图表加载此类 EA 属高风险操作,面板逻辑出错可能阻塞下单响应。 鉴权函数 ShowAuthenticationPrompt 单独建了一个 500×300 的对话框,密码框定位在 (20,70) 尺寸 260×95,用 authentication.Add() 挂进容器。若 Create 失败返回 false,调用方应据此阻断后续逻辑而非静默继续。

MQL5 / C++
   class="kw">return INIT_FAILED;
   }
   if (!adminHomePanel.Create(ChartID(), "Admin Home Panel", class="num">0, class="num">30, class="num">30, class="num">500, class="num">500))
   {
      Print("Failed to create Admin Home Panel");
      class="kw">return INIT_FAILED;
   }
   if (!CreateAdminHomeControls())
   {
      Print("Home panel control creation failed");
      class="kw">return INIT_FAILED;
   }
   if (!communicationsPanel.Create(ChartID(), "Communications Panel", class="num">0, class="num">30, class="num">30, class="num">500, class="num">500))
   {
      Print("Failed to create Communications panel dialog");
      class="kw">return INIT_FAILED;
   }
   if (!tradeManagementPanel.Create(ChartID(), "Trade Management Panel", class="num">0, class="num">30, class="num">30, class="num">500, class="num">500))
   {
      Print("Failed to create Communictions panel dialog");
      class="kw">return INIT_FAILED;
   }
   if (!CreateControls())
   {
      Print("Control creation failed");
      class="kw">return INIT_FAILED;
   }
   if (!CreateTradeManagementControls())
   {
      Print("Control creation failed");
      class="kw">return INIT_FAILED;
   }
   
   adminHomePanel.Hide(); class=class="str">"cmt">// Hide home panel by class="kw">default on initialization
   communicationsPanel.Hide(); class=class="str">"cmt">// Hide the Communications Panel
   tradeManagementPanel.Hide();class=class="str">"cmt">// Hide the Trade Management Panel
   class="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Show authentication input dialog                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool ShowAuthenticationPrompt()
{
   if (!authentication.Create(ChartID(), "Authentication", class="num">0, class="num">100, class="num">100, class="num">500, class="num">300))
   {
      Print("Failed to create authentication dialog");
      class="kw">return false;
   }
   if (!passwordInputBox.Create(ChartID(), "PasswordInputBox", class="num">0, class="num">20, class="num">70, class="num">260, class="num">95))
   {
      Print("Failed to create password input box");
      class="kw">return false;
   }
   authentication.Add(passwordInputBox);

在图表上拼出登录与双因子验证界面

这段逻辑干的事很直白:在 MT5 当前图表上用 CChartObject 子类逐个创建控件,再塞进一个容器对象里统一显示。密码提示标签左上角坐标 (20,20),宽 260 高 40;反馈标签放在 (20,140),尺寸 380×160,且把颜色写死成 clrRed,专门用来在密码错时给交易员视觉警告。 登录按钮与关闭按钮的 y 坐标都是 120,但 x 分别是 20 和 120,宽度 100 与 200——也就是说两个按钮在同一水平线,关闭按钮向右错开 100 像素,避免重叠。任何一个 Create 返回 false 就 Print 错误并 return false,整体初始化失败时能快速在专家日志里定位是哪一行控件没建出来。 双因子弹窗另起一个 void 函数 ShowTwoFactorAuthPrompt,主对话框尺寸 500×300 位于 (100,100)。里面的验证码输入框 twoFACodeInput 坐标 (20,70)、宽 260 高 95,提示标签 twoFAPromptLabel 在 (20,20) 宽 380。注意这里失败只 return 不返回布尔值,调用方不会知道 2FA 对话框到底建没建成功,真要集成进面板建议改成返回 bool。 开 MT5 新建 EA 把下面代码贴进 OnStart 之前的同名函数,编译后拖上图表,就能直接看到这两层界面叠在左上区域;若反馈标签没变红,先查 authentication 容器是否真的 Add 了 feedbackLabel。

MQL5 / C++
if (!passwordPromptLabel.Create(ChartID(), "PasswordPromptLabel", class="num">0, class="num">20, class="num">20, class="num">260, class="num">40))
{
   Print("Failed to create password prompt label");
   class="kw">return false;
}
passwordPromptLabel.Text("Enter password: Access Admin Panel");
authentication.Add(passwordPromptLabel);
if (!feedbackLabel.Create(ChartID(), "FeedbackLabel", class="num">0, class="num">20, class="num">140, class="num">380, class="num">160))
{
   Print("Failed to create feedback label");
   class="kw">return false;
}
feedbackLabel.Text("");
feedbackLabel.Color(clrRed); class=class="str">"cmt">// Red class="type">color for incorrect attempts
authentication.Add(feedbackLabel);
if (!loginButton.Create(ChartID(), "LoginButton", class="num">0, class="num">20, class="num">120, class="num">100, class="num">140))
{
   Print("Failed to create login button");
   class="kw">return false;
}
loginButton.Text("Login");
authentication.Add(loginButton);
if (!closeAuthButton.Create(ChartID(), "CloseAuthButton", class="num">0, class="num">120, class="num">120, class="num">200, class="num">140))
{
   Print("Failed to create close button for authentication");
   class="kw">return false;
}
closeAuthButton.Text("Close");
authentication.Add(closeAuthButton);
authentication.Show();
ChartRedraw();
class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Show two-factor authentication input dialog                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ShowTwoFactorAuthPrompt()
{
   if (!twoFactorAuth.Create(ChartID(), "Two-Factor Authentication", class="num">0, class="num">100, class="num">100, class="num">500, class="num">300))
   {
      Print("Failed to create 2FA dialog");
      class="kw">return;
   }
   if (!twoFACodeInput.Create(ChartID(), "TwoFACodeInput", class="num">0, class="num">20, class="num">70, class="num">260, class="num">95))
   {
      Print("Failed to create 2FA code input box");
      class="kw">return;
   }
   twoFactorAuth.Add(twoFACodeInput);
   if (!twoFAPromptLabel.Create(ChartID(), "TwoFAPromptLabel", class="num">0, class="num">20, class="num">20, class="num">380, class="num">40))
   {
      Print("Failed to create 2FA prompt label");
      class="kw">return;
   }

「在图表上拼出双因素验证与管理面板」

给 MT5 图表挂自定义控件时,双因素验证(2FA)弹窗和管理员主页是两个典型落地场景。下面这段逻辑把提示标签、反馈标签、验证按钮、关闭按钮依次塞进 2FA 容器,再切到管理员主页放交易管理、通讯面板和最小化按钮。 2FA 反馈标签建在坐标 (140,380) 到 (160,?) 区域,初始文本为空、配色用 clrRed,专门用来在验证码填错时给红色提示;验证按钮叫 Verify,关闭按钮叫 Close,位置错开 100 像素避免重叠。任何 Create() 返回 false 就 Print 错误并直接 return,保证控件缺失时不继续画界面。 管理员主页用 ChartID() 拿图表句柄,交易管理按钮放在 (50,50) 尺寸 250x90,通讯面板按钮放在 (50,100) 尺寸 250x140,最小化按钮坐标出现负值 -22,说明它是贴着图表右上角外沿放的。所有按钮 Add 进 adminHomePanel 后由面板统一调度。 开 MT5 新建 EA 把下面代码粘进 OnStart 或对应函数,改下按钮文本和坐标就能复现这套界面;外汇与贵金属交易本身高风险,这类面板只解决操作入口,不暗示任何收益。

MQL5 / C++
twoFAPromptLabel.Text("Enter the 2FA code sent to your Telegram:");
twoFactorAuth.Add(twoFAPromptLabel);
if (!twoFAFeedbackLabel.Create(ChartID(), "TwoFAFeedbackLabel", class="num">0, class="num">20, class="num">140, class="num">380, class="num">160))
{
   Print("Failed to create 2FA feedback label");
   class="kw">return;
}
twoFAFeedbackLabel.Text("");
twoFAFeedbackLabel.Color(clrRed); class=class="str">"cmt">// Red class="type">color for incorrect 2FA attempts
twoFactorAuth.Add(twoFAFeedbackLabel);
if (!twoFALoginButton.Create(ChartID(), "TwoFALoginButton", class="num">0, class="num">20, class="num">120, class="num">100, class="num">140))
{
   Print("Failed to create 2FA login button");
   class="kw">return;
}
twoFALoginButton.Text("Verify");
twoFactorAuth.Add(twoFALoginButton);
if (!close2FAButton.Create(ChartID(), "Close2FAButton", class="num">0, class="num">120, class="num">120, class="num">200, class="num">140))
{
   Print("Failed to create close button for 2FA");
   class="kw">return;
}
close2FAButton.Text("Close");
twoFactorAuth.Add(close2FAButton);
twoFactorAuth.Show();
ChartRedraw();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Admin Home Panel controls creation                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CreateAdminHomeControls()
{
   class="type">long chart_id = ChartID();
   if (!tradeMgmtAccessButton.Create(chart_id, "TradeMgmtAccessButton", class="num">0, class="num">50, class="num">50, class="num">250, class="num">90))
   {
      Print("Failed to create Trade Management Access button");
      class="kw">return false;
   }
   tradeMgmtAccessButton.Text("Trade Management Panel");
   adminHomePanel.Add(tradeMgmtAccessButton);
   if (!communicationsPanelAccessButton.Create(chart_id, "CommunicationsPanelAccessButton", class="num">0, class="num">50, class="num">100, class="num">250, class="num">140))
   {
      Print("Failed to create Communications Panel Access button");
      class="kw">return false;
   }
   communicationsPanelAccessButton.Text("Communications Panel");
   adminHomePanel.Add(communicationsPanelAccessButton);
   if (!minimizeButton.Create(chart_id, "MinimizeButton", class="num">0, class="num">375, -class="num">22, class="num">405, class="num">0))
   {
      Print("Failed to create minimize button");
      class="kw">return false;
   }
   minimizeButton.Text("_");
   adminHomePanel.Add(minimizeButton);

◍ 面板按钮的锚点坐标与容错返回

在 MT5 自定义面板里,按钮不是凭空画的,每一个 Create() 都要吃一组像素坐标。以最大化按钮为例,它挂在 chart_id 下、名字 "MaximizeButton",左上角落在 X=405、Y=-22,右下角到 X=435、Y=0,也就是宽 30 高 22 像素的窄条,负 Y 值说明它是贴着图表右上角往外探的。 创建失败必须立刻 Print 并 return false,否则后面 Add 到面板对象时会拿到空指针,整个 EA 初始化可能直接崩。关闭按钮同理:X=435 起、Y=-22,宽 30 高 22,文字标成 "X",这两个按钮都归 adminHomePanel 管。 交易管理面板那组坐标更密:Buy 按钮 X 50~150、Y 50~90(100×40),Sell 按钮 X 160~260、Y 50~90,Close Position 占 X 50~190、Y 100~140,Modify Position 占 X 200~340、Y 100~140,Set SL 占 X 50~150、Y 150~190。肉眼能看出横向间距留了 10 像素、纵向区块隔了 10 像素,避免控件互压。 开 MT5 把这套坐标抄进自己的 CPanel 派生类,编译后拖到图表上,若按钮重叠就按上面那 10 像素步长微调;外汇与贵金属杠杆高,面板只是下单辅助,真触发市价单前仍要人工核对点差与滑点。

MQL5 / C++
if (!maximizeButton.Create(chart_id, "MaximizeButton", class="num">0, class="num">405, -class="num">22, class="num">435, class="num">0))
{
   Print("Failed to create maximize button");
   class="kw">return false;
}
maximizeButton.Text("[ ]");
adminHomePanel.Add(maximizeButton);
if (!closeButton.Create(chart_id, "CloseButton", class="num">0, class="num">435, -class="num">22, class="num">465, class="num">0))
{
   Print("Failed to create close button");
   class="kw">return false;
}
closeButton.Text("X");
adminHomePanel.Add(closeButton);
class="kw">return true;
}
class=class="str">"cmt">// Create the Trade Management Panel controls
class="type">bool CreateTradeManagementControls()
{
   class="type">long chart_id = ChartID();
   
   class=class="str">"cmt">// Buy Button
   if (!buyButton.Create(chart_id, "BuyButton", class="num">0, class="num">50, class="num">50, class="num">150, class="num">90))
   {
      Print("Failed to create Buy button");
      class="kw">return false;
   }
   buyButton.Text("Buy");
   tradeManagementPanel.Add(buyButton);
   
   class=class="str">"cmt">// Sell Button
   if (!sellButton.Create(chart_id, "SellButton", class="num">0, class="num">160, class="num">50, class="num">260, class="num">90))
   {
      Print("Failed to create Sell button");
      class="kw">return false;
   }
   sellButton.Text("Sell");
   tradeManagementPanel.Add(sellButton);
   class=class="str">"cmt">// Close Position Button
   if (!closePosButton.Create(chart_id, "ClosePosButton", class="num">0, class="num">50, class="num">100, class="num">190, class="num">140))
   {
      Print("Failed to create Close Position button");
      class="kw">return false;
   }
   closePosButton.Text("Close Position");
   tradeManagementPanel.Add(closePosButton);
   class=class="str">"cmt">// Modify Position Button
   if (!modifyPosButton.Create(chart_id, "ModifyPosButton", class="num">0, class="num">200, class="num">100, class="num">340, class="num">140))
   {
      Print("Failed to create Modify Position button");
      class="kw">return false;
   }
   modifyPosButton.Text("Modify Position");
   tradeManagementPanel.Add(modifyPosButton);
   class=class="str">"cmt">// Set Stop-Loss Button
   if (!setSLButton.Create(chart_id, "SetSLButton", class="num">0, class="num">50, class="num">150, class="num">150, class="num">190))
   {
      Print("Failed to create Set Stop-Loss button");
      class="kw">return false;
   }
   setSLButton.Text("Set SL");

面板按钮的事件路由怎么接

在 MT5 自定义面板里,按钮不是画上去就完事,得在 OnChartEvent 里把对象点击 ID 映射到对应面板的显隐逻辑。上面这段把 HomeButtonComm、TradeMgmtAccessButton 等字符串 sparam 跟 adminHomePanel、tradeManagementPanel 的 Show/Hide 绑死,点哪个就切哪个层。 注意 CloseButton 直接调 ExpertRemove() 卸载 EA,MinimizeButton / MaximizeButton 则交给各自点击处理函数,这种分流写法能避免把所有 UI 状态塞进一个函数。 实跑时建议把按钮的 object name(如 "SetTPButton"、"LoginButton")先 Print 出来核对,因为 sparam 大小写和创建时传的字符串必须完全一致,差一个字符事件就不会触发。外汇与贵金属行情波动剧烈,这类面板仅作辅助工具,实际操作仍有较高风险。

MQL5 / C++
tradeManagementPanel.Add(setSLButton);
class=class="str">"cmt">// Set Take-Profit Button
if (!setTPButton.Create(chart_id, "SetTPButton", class="num">0, class="num">160, class="num">150, class="num">260, class="num">190))
{
   Print("Failed to create Set Take-Profit button");
   class="kw">return false;
}
setTPButton.Text("Set TP");
tradeManagementPanel.Add(setTPButton);
class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle chart events                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam)
{
   if (id == CHARTEVENT_OBJECT_CLICK)
   {
   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();
      }
      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")
      {

「面板事件分发与两步验证登录逻辑」

在 MT5 自定义面板里,所有图形对象的交互都汇聚到 OnChartEvent。上面这段把 sparam 字符串和具体按钮名做比较:Close2FAButton 触发关掉两步验证弹窗,SendButton、ClearButton 等分别路由到各自的点击事件处理函数,QuickMessageButton 还用 StringSubstr 从名称第 18 位截出序号再减 1 得到索引。 CHARTEVENT_OBJECT_CLICK 与 CHARTEVENT_OBJECT_ENDEDIT 是两个最常用事件分支:前者管按钮点击,后者管输入框编辑结束。InputBox 结束编辑就调 OnInputChange,这种写法能避免每次按键都刷新界面。 登录函数 OnLoginButtonClick 先比对 passwordInputBox.Text() 和预设 Password。一致就生成 6 位随机码 twoFACode,通过 Telegram 接口推送到指定 chat_id,随后销毁登录框并弹出二步验证提示;不一致则清空密码框并写『Wrong password. Try again.』。 实盘接这套面板要清楚:外汇和贵金属杠杆高、波动猛,把硬编码 bot token 和 chat_id 写死在代码里有泄露风险,建议改从加密配置文件读。开 MT5 把这段粘进 EA 的 OnChartEvent 之后,能直接验证按钮路由是否按预期走。

MQL5 / C++
        OnTwoFALoginButtonClick();
      }
      else if (sparam == "Close2FAButton")
      {
        OnClose2FAButtonClick();
      }
    }
    class="kw">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") != -class="num">1)
        {
          class="type">long index = StringToInteger(StringSubstr(sparam, class="num">18));
          OnQuickMessageButtonClick(index - class="num">1);
        }
        break;
      case CHARTEVENT_OBJECT_ENDEDIT:
        if (sparam == "InputBox") OnInputChange();
        break;
    }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle login button click                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnLoginButtonClick()
{
    class="type">class="kw">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("");
    }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle 2FA login button click                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTwoFALoginButtonClick()
{

◍ EA面板里的双因子校验与控件装配

在自定义 EA 的图形界面里,双因子校验通过后才放交易管理面板是常见的权限隔离做法。下面这段逻辑先读输入框文本,和预设的 twoFACode 比对;一致就销毁验证窗、显示 adminHomePanel 并打印授权成功,不一致则把提示标签改成错误文案并清空输入框。

MQL5 / C++
class=class="str">"cmt">// 如果 2FA 校验通过,显示交易管理面板
class="type">class="kw">string enteredCode = twoFACodeInput.Text();          class=class="str">"cmt">// 读取用户输入的验证码文本
if (enteredCode == twoFACode)                        class=class="str">"cmt">// 比对输入与预设码
{
   twoFactorAuth.Destroy();                          class=class="str">"cmt">// 销毁双因子验证弹窗
   adminHomePanel.Show();                            class=class="str">"cmt">// 显示交易管理主面板
   Print("2FA authentication successful. Access granted to Trade Management Panel."); class=class="str">"cmt">// 日志输出授权成功
}
else
{
   twoFAFeedbackLabel.Text("Wrong code. Try again."); class=class="str">"cmt">// 提示错误
   twoFACodeInput.Text("");                           class=class="str">"cmt">// 清空输入
}
}
关闭按钮的处理更直接:无论是认证窗还是 2FA 窗,点关闭就 Destroy 掉对应对象并调用 ExpertRemove() 终止 EA,同时 Print 一条关闭日志。注意 ExpertRemove 会立刻结束 Expert,实盘里误触可能直接退单环境,外汇与贵金属波动下属于高风险操作。
MQL5 / C++
class="type">void OnCloseAuthButtonClick()
{
   authentication.Destroy();      class=class="str">"cmt">// 销毁认证对话框
   ExpertRemove();                class=class="str">"cmt">// 退出 EA
   Print("Authentication dialog closed.");
}
class="type">void OnClose2FAButtonClick()
{
   twoFactorAuth.Destroy();       class=class="str">"cmt">// 销毁 2FA 对话框
   ExpertRemove();                class=class="str">"cmt">// 退出 EA
   Print("2FA dialog closed.");
}
CreateControls 里用 ChartID 拿图表句柄,再逐个 Create 界面元素。inputBox 放在 (5,25) 到 (460,95) 的坐标区,homeButtonComm 尺寸 120x150 挂到 communicationsPanel,homeButtonTrade 尺寸 120x30 挂到 tradeManagementPanel,都写了 Home 🏠 文案;charCounter 放在 (380,5)。任何 Create 失败就 Print 原因并 return false,调用方据此判断 UI 初始化是否完整。
MQL5 / C++
class="type">bool CreateControls()
{
   class="type">long chart_id = ChartID();     class=class="str">"cmt">// 获取当前图表 ID
   if (!inputBox.Create(chart_id, "InputBox", class="num">0, class="num">5, class="num">25, class="num">460, class="num">95)) class=class="str">"cmt">// 创建输入框
   {
      Print("Failed to create input box");
      class="kw">return false;
   }
   communicationsPanel.Add(inputBox); class=class="str">"cmt">// 加入通信面板
   if (!homeButtonComm.Create(chart_id, "HomeButtonComm", class="num">0, class="num">20, class="num">120, class="num">120, class="num">150))
   {
      Print("Failed to create Home button for Communications Panel");
      class="kw">return false;
   }
   homeButtonComm.Text("Home 🏠");
   communicationsPanel.Add(homeButtonComm);
   if (!homeButtonTrade.Create(chart_id, "HomeButtonTrade", class="num">0, class="num">20, class="num">10, class="num">120, class="num">30))
   {
      Print("Failed to create Home button for Trade Management Panel");
      class="kw">return false;
   }
   homeButtonTrade.Text("Home 🏠");
   tradeManagementPanel.Add(homeButtonTrade);
   if (!charCounter.Create(chart_id, "CharCounter", class="num">0, class="num">380, class="num">5, class="num">460, class="num">25))
   {
开 MT5 新建 EA 把这段塞进 OnStart 前的控件模块,改两个 Home 按钮坐标就能看到面板布局差异;2FA 预设码写死在代码里仅适合本地调试,对外挂 EA 建议接外部加密配置。

MQL5 / C++
class=class="str">"cmt">// If 2FA is successful, show the trade management panel
class="type">class="kw">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("");
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle close button for authentication                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnCloseAuthButtonClick()
{
   authentication.Destroy();
   ExpertRemove(); class=class="str">"cmt">// Exit the expert
   Print("Authentication dialog closed.");
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle close button for 2FA                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnClose2FAButtonClick()
{
   twoFactorAuth.Destroy();
   ExpertRemove();
   Print("2FA dialog closed.");
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create necessary UI controls                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CreateControls()
{
   class="type">long chart_id = ChartID();
   
   if (!inputBox.Create(chart_id, "InputBox", class="num">0, class="num">5, class="num">25, class="num">460, class="num">95))
   {
      Print("Failed to create input box");
      class="kw">return false;
   }
   communicationsPanel.Add(inputBox);
   
   class=class="str">"cmt">// Create Home Button for Communications Panel
   if (!homeButtonComm.Create(chart_id,  "HomeButtonComm", class="num">0, class="num">20, class="num">120, class="num">120,class="num">150))
   {
      Print("Failed to create Home button for Communications Panel");
      class="kw">return false;
   }
   homeButtonComm.Text("Home 🏠");
   communicationsPanel.Add(homeButtonComm);
   class=class="str">"cmt">// Create Home Button for Trade Management Panel
   if (!homeButtonTrade.Create(chart_id, "HomeButtonTrade", class="num">0, class="num">20, class="num">10, class="num">120, class="num">30))
   {
      Print("Failed to create Home button for Trade Management Panel");
      class="kw">return false;
   }
   homeButtonTrade.Text("Home 🏠");
   tradeManagementPanel.Add(homeButtonTrade);
   if (!charCounter.Create(chart_id, "CharCounter", class="num">0, class="num">380, class="num">5, class="num">460, class="num">25))
   {

面板控件的逐一定位与挂载

在通信面板里把各个子控件挂上去时,坐标参数是硬编码的,左侧 X 从 5 到 465 像素铺开,右侧标题栏按钮则用了负 Y(如 -22)顶到面板外缘。 每一个 Create 调用失败都会 Print 具体错误并返回 false,这意味着只要有一个按钮建不出来,整个面板初始化就中断,不会带残缺控件运行。 字符计数器初始文本被设成 "0/" 拼接 MAX_MESSAGE_LENGTH,说明上限长度由宏决定,运行时再动态改写;清屏、发送、字体、主题四个功能键的 Y 高度集中在 95~125 区间,排布紧凑。 最小化、最大化、关闭三个键的 Y 坐标为 -22 到 0,视觉上脱离主面板 body,属于标题条悬浮件,最后一行 return CreateQuickMessageButtons() 把快捷消息按钮的创建交给了另一个函数。

MQL5 / C++
Print("Failed to create character counter");
class="kw">return false;
}
charCounter.Text("class="num">0/" + IntegerToString(MAX_MESSAGE_LENGTH));
communicationsPanel.Add(charCounter);
if (!clearButton.Create(chart_id, "ClearButton", class="num">0, class="num">235, class="num">95, class="num">345, class="num">125))
{
   Print("Failed to create clear button");
   class="kw">return false;
}
clearButton.Text("Clear");
communicationsPanel.Add(clearButton);
if (!sendButton.Create(chart_id, "SendButton", class="num">0, class="num">350, class="num">95, class="num">460, class="num">125))
{
   Print("Failed to create send button");
   class="kw">return false;
}
sendButton.Text("Send");
communicationsPanel.Add(sendButton);
if (!changeFontButton.Create(chart_id, "ChangeFontButton", class="num">0, class="num">95, class="num">95, class="num">230, class="num">115))
{
   Print("Failed to create change font button");
   class="kw">return false;
}
changeFontButton.Text("Font<>");
communicationsPanel.Add(changeFontButton);
if (!toggleThemeButton.Create(chart_id, "ToggleThemeButton", class="num">0, class="num">5, class="num">95, class="num">90, class="num">115))
{
   Print("Failed to create toggle theme button");
   class="kw">return false;
}
toggleThemeButton.Text("Theme<>");
communicationsPanel.Add(toggleThemeButton);
if (!minimizeButton.Create(chart_id, "MinimizeButton", class="num">0, class="num">375, -class="num">22, class="num">405, class="num">0))
{
   Print("Failed to create minimize button");
   class="kw">return false;
}
minimizeButton.Text("_");
communicationsPanel.Add(minimizeButton);
if (!maximizeButton.Create(chart_id, "MaximizeButton", class="num">0, class="num">405, -class="num">22, class="num">435, class="num">0))
{
   Print("Failed to create maximize button");
   class="kw">return false;
}
maximizeButton.Text("[ ]");
communicationsPanel.Add(maximizeButton);
if (!closeButton.Create(chart_id, "CloseButton", class="num">0, class="num">435, -class="num">22, class="num">465, class="num">0))
{
   Print("Failed to create close button");
   class="kw">return false;
}
closeButton.Text("X");
communicationsPanel.Add(closeButton);
class="kw">return CreateQuickMessageButtons();

「在MT5图表上铺一排快捷喊话按钮」

想把常用的盘面口令直接钉在图表上,靠循环批量生成按钮比手写八个对象省力得多。下面这段逻辑把八个预设短语排成 4×2 的矩阵,起点坐标 (5,160),单个按钮宽 222、高 65,间距 5 像素,算下来整块面板约占 449×270 的客户区。 代码里用 i % 2 控制左右列、i / 2 控制上下行,因此第 1 和第 2 个按钮在同一行,第 3、4 个在下一行,依此类推。若某个按钮 Create 返回 false,直接打印失败序号并整体退出,避免半成品面板挂在那里。 退出时要收干净。 OnDeinit 里调 communicationsPanel.Destroy() 把整组图形对象注销,否则切换周期可能留下僵尸按钮。发送逻辑分两块:点发送按钮时取输入框文本,长度大于 0 才调 Telegram 接口;点清除按钮则把 inputBox 置空并触发一次输入变更回调。外汇与贵金属波动剧烈,这类一键喊话只适合作辅助提醒,信号误发可能造成真实账户风险。 别把坐标当死数。 不同屏幕 DPI 下 222 宽可能挤占别的面板,建议你在 MT5 里改 widthstartY 实地拖一下再看排布。

MQL5 / C++
class="type">bool CreateQuickMessageButtons()
{
   class="type">class="kw">string quickMessages[] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 };
   class="type">int startX = class="num">5, startY = class="num">160, width = class="num">222, height = class="num">65, spacing = class="num">5;
   for (class="type">int i = class="num">0; i < ArraySize(quickMessages); i++)
   {
      class="type">bool created = quickMessageButtons[i].Create(ChartID(), "QuickMessageButton" + IntegerToString(i + class="num">1), class="num">0,
         startX + (i % class="num">2) * (width + spacing), startY + (i / class="num">2) * (height + spacing),
         startX + (i % class="num">2) * (width + spacing) + width, startY + (i / class="num">2) * (height + spacing) + height);
      if (!created)
      {
         Print("Failed to create quick message button ", i + class="num">1);
         class="kw">return false;
      }
      quickMessageButtons[i].Text(quickMessages[i]);
      communicationsPanel.Add(quickMessageButtons[i]);
   }
   
   class="kw">return true;
}
class="type">void OnDeinit(const class="type">int reason)
{
   communicationsPanel.Destroy();
   Print("Deinitialization complete");
}
class="type">void OnSendButtonClick()
{
   class="type">class="kw">string message = inputBox.Text();
   if (StringLen(message) > class="num">0)
   {
      if (SendMessageToTelegram(message, InputChatId, InputBotToken))
         Print("Custom message sent: ", message);
      else
         Print("Failed to send custom message.");
   }
   else
   {
      Print("No message entered.");
   }
}
class="type">void OnClearButtonClick()
{
   inputBox.Text("");
   OnInputChange();
   Print("Input box cleared.");
}

◍ 面板交互与主题切换的事件处理

在 MT5 自定义面板里,快捷消息按钮的点击逻辑依赖一个索引映射:把 8 个预设短语存进数组,用传入的 long 型 index 强转 int 直接取串,再调 SendMessageToTelegram 推到指定 chat。发送成功或失败仅用 Print 留日志,不弹窗——回测或实盘时盯 Experts 标签就能确认投递状态。 输入框字数统计由 OnInputChange 驱动,每次文本变动取 StringLen 算当前长度,拼上 MAX_MESSAGE_LENGTH 常量显示成「当前/上限」。这个数字若你设了 4096,就正好卡在 Telegram 单消息上限,超长会被服务端拒,本地却不报错,得自己截断。 暗色主题靠一个 bool 翻转实现。OnToggleThemeButtonClick 里 darkTheme = !darkTheme 后批量改 7 个主按钮加 N 个快捷按钮的字体、背景、边框三色,最后 ChartRedraw 刷新。颜色枚举写死 clrDarkSlateGray / clrGainsboro 这类,想要更顺眼只能进代码改枚举值。 让小布替你跑这套:把 MAX_MESSAGE_LENGTH 和 QuickMessage1~8 先填进输入参数,挂 EA 后点快捷按钮,去 Telegram 看是否落地,没收到就查 InputBotToken 格式是不是纯字符串而非带引号的字面量。

MQL5 / C++
class="type">void OnQuickMessageButtonClick(class="type">long index)
{
   class="type">class="kw">string quickMessages[] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 };
   class="type">class="kw">string message = quickMessages[(class="type">int)index];
   if (SendMessageToTelegram(message, InputChatId, InputBotToken))
      Print("Quick message sent: ", message);
   else
      Print("Failed to send quick message.");
}
class="type">void OnInputChange()
{
   class="type">int currentLength = StringLen(inputBox.Text());
   charCounter.Text(IntegerToString(currentLength) + "/" + IntegerToString(MAX_MESSAGE_LENGTH));
   ChartRedraw();
}
class="type">void OnToggleThemeButtonClick()
{
   darkTheme = !darkTheme;
   UpdateThemeColors();
   Print("Theme toggled: ", darkTheme ? "Dark" : "Light");
}
class="type">void UpdateThemeColors()
{
   class="type">color textColor = darkTheme ? clrWhite : clrBlack;
   class="type">color buttonBgColor = darkTheme ? clrDarkSlateGray : clrGainsboro;
   class="type">color borderColor = darkTheme ? clrSlateGray : clrGray;
   class="type">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 (class="type">int i = class="num">0; i < ArraySize(quickMessageButtons); i++)
   {
      UpdateButtonTheme(quickMessageButtons[i], textColor, buttonBgColor, borderColor);
   }
   ChartRedraw();
}
class="type">void UpdateButtonTheme(CButton &button, class="type">color textColor, class="type">color bgColor, class="type">color borderColor)

面板控件的交互与随机码生成

这段逻辑集中处理通信面板的字体切换、最小化/还原以及双因素认证用的随机码。点击换字体按钮时,索引在 availableFonts 数组里循环自增,再统一刷给所有输入框和按钮,ChartRedraw 强制重绘。 GenerateRandom6DigitCode 用 MathRand() % 1000000 取余数得到 0~999999 的整数,StringFormat("%06d", code) 保证不足六位时补前导零,输出如 004217 这种格式。 最小化只隐藏主面板和最小化键、露出最大化与关闭键;最大化则在 minimized 为 true 时才复原布局。外汇与贵金属插件涉及账户交互,这类 UI 改动若接真实下单须警惕误触导致的高风险敞口。 下面拆一下关键片段: button.SetTextColor(textColor); // 设置按钮文字颜色 button.SetBackgroundColor(bgColor); // 设置按钮背景色 button.SetBorderColor(borderColor); // 设置按钮边框色 void OnChangeFontButtonClick() { currentFontIndex = (currentFontIndex + 1) % ArraySize(availableFonts); // 字体索引循环+1 SetFontForAll(availableFonts[currentFontIndex]); // 全部控件套用新字体 Print("Font changed to: ", availableFonts[currentFontIndex]); // 终端打印当前字体名 ChartRedraw(); // 重绘图表使样式生效 } string GenerateRandom6DigitCode() { int code = MathRand() % 1000000; // 取百万以内随机整数 return StringFormat("%06d", code); // 格式化为六位带前导零字符串 }

MQL5 / C++
button.SetTextColor(textColor);
button.SetBackgroundColor(bgColor);
button.SetBorderColor(borderColor);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle change font button click                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChangeFontButtonClick()
{
  currentFontIndex = (currentFontIndex + class="num">1) % ArraySize(availableFonts);
  SetFontForAll(availableFonts[currentFontIndex]);
  Print("Font changed to: ", availableFonts[currentFontIndex]);
  ChartRedraw();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Set font for all input boxes and buttons                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SetFontForAll(class="type">class="kw">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 (class="type">int i = class="num">0; i < ArraySize(quickMessageButtons); i++)
  {
    quickMessageButtons[i].Font(fontName);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Generate a random class="num">6-digit code for 2FA                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string GenerateRandom6DigitCode()
{
  class="type">int code = MathRand() % class="num">1000000; class=class="str">"cmt">// Produces a class="num">6-digit number
  class="kw">return StringFormat("%06d", code); class=class="str">"cmt">// Ensures leading zeros
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle minimize button click                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnMinimizeButtonClick()
{
  minimized = true;
  communicationsPanel.Hide();
  minimizeButton.Hide();
  maximizeButton.Show();
  closeButton.Show();
  Print("Panel minimized.");
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle maximize button click                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnMaximizeButtonClick()
{
  if (minimized)
  {
    communicationsPanel.Show();
    minimizeButton.Show();
    maximizeButton.Hide();
    closeButton.Hide();
    minimized = false;
    Print("Panel maximized.");
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle close button click for admin panel                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnCloseButtonClick()
{
  ExpertRemove();

「把信号推到 Telegram 的那段 POST」

在 MT5 里把交易信号往外推,最轻量的办法是直接调 Telegram 的 Bot API,不用架中间件。下面这个函数把消息体和 chat_id 拼成 JSON,用 WebRequest 发 POST。 超时设的是 5000 毫秒,也就是 5 秒没返回就走失败分支;HTTP 返回 200 才认作发送成功,其余码全部打印错误码和响应体,方便你当场排查 botToken 或 chat_id 填错。 实盘前先在策略测试器里用模拟消息跑一遍,确认 MT5 的「允许 WebRequest」白名单里加了 api.telegram.org,否则函数会直接返回 false。外汇和贵金属波动剧烈,这类推送只做辅助提醒,任何信号都不构成入场保证,请自担高风险。

MQL5 / C++
class="type">bool SendMessageToTelegram(class="type">class="kw">string message, class="type">class="kw">string chatId, class="type">class="kw">string botToken)
{
   class="type">class="kw">string url = "https:class=class="str">"cmt">//api.telegram.org/bot" + botToken + "/sendMessage";
   class="type">class="kw">string jsonMessage = "{\"chat_id\":\"" + chatId + "\", \"text\":\"" + message + "\"}";
   class="type">char postData[];
   ArrayResize(postData, StringToCharArray(jsonMessage, postData) - class="num">1);
   class="type">int timeout = class="num">5000;
   class="type">char result[];
   class="type">class="kw">string responseHeaders;
   class="type">int responseCode = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, postData, result, responseHeaders);
   if (responseCode == class="num">200)
   {
      Print("Message sent successfully: ", message);
      class="kw">return true;
   }
   else
   {
      Print("Failed to send message. HTTP code: ", responseCode, " Error code: ", GetLastError());
      Print("Response: ", CharArrayToString(result));
      class="kw">return false;
   }
}

◍ 面板 V1.21 跑起来长什么样

把多种管理功能塞进同一个 EA 并不复杂,关键在于编译后界面是否按预期渲染。我们在更新后的代码里加了批量平仓与风险阈值监控,重新编译出管理员面板 V1.21,在 MetaTrader 5 终端加载后界面正常初始化,无报错日志。 这次验证只确认一件事:融合新功能后的面板能在 MT5 实盘环境启动并响应手动指令。外汇与贵金属品种波动剧烈、杠杆风险高,任何面板都只是执行工具,不替代仓位判断。 建议你直接把编译好的 ex5 拖进 EURUSD 或 XAUUSD 的 1 分钟图,观察按钮回调延迟是否在 200ms 内,这是检验面板是否卡 UI 线程的最快办法。

面板迭代到哪一步了

这套管理界面从只管 Telegram 通知的最小后台,叠到了 1.21 版:主页面板当仪表盘,通信面板和新建的交易管理面板都能直接进,双因素认证卡在前面。所有模块在同一个集成工具里跑,导航路径比早期版本短了一截。 当前版本的源码包 Admin_Panel_V1.21.mq5 体积 56.4 KB,想看实际交互逻辑的,直接下回来在 MT5 里编译跑一遍最直观。主页那个按钮图标是直接从 Telegram 扒过来贴上的,后续版本才会细抠视觉规范。 外汇与贵金属品种波动剧烈、杠杆风险高,这类后台工具只解决通知与操作入口问题,不等于交易策略本身安全。下一步若打开交易管理面板,建议先在小布盯盘里对照自己的风控参数再动手。

常见问题

在回调里先判断控件存在性再调用 Destroy,释放后把指针置空,防止重复销毁报错。
可以,把主题和字体参数抽成全局变量,切换时遍历控件重设 Color 与 FontSize 并 Redraw。
能,小布可接管状态监听,在面板切换关键状态时直接发提醒,不用自己接推送接口。
硬编码易泄露且难改,建议外部配置文件或输入框临时校验,别把密码写死在代码里。
在每类控件创建后打日志输出句柄值,句柄为空即该步失败,逐个注释排查最快。