在MQL5中创建交易管理员面板(第六部分):多功能界面(一)·进阶篇
🧩

在MQL5中创建交易管理员面板(第六部分):多功能界面(一)·进阶篇

(2/3)·单图表只能挂一个EA的硬约束,正卡住多数人的自动化扩展,本篇给出界面解法的工程路径

含代码示例 第 2/3 篇

不少交易者以为在MT5里多挂几个EA就能分摊订单管理、风控和播报的任务,结果新EA一加载就把原EA顶掉,图表上永远只剩一个在跑。这种单图表单EA的约束不解决,再多的功能设想都只能停留在纸面。

「面板切换与全局变量的初始化落点」

这段逻辑是典型的 MQL5 对话框事件分支:通过 sparam 字符串判断用户点了哪个控件,再决定显示通信面板、隐藏管理员主页,还是触发最小化、最大化、关闭 EA 等行为。比如 sparam 等于 "CommunicationsPanelAccessButton" 时,communicationsPanel.Show() 与 adminHomePanel.Hide() 成对出现,保证界面不重叠。 关闭按钮直接调 ExpertRemove() 卸载智能交易系统,属于硬退出,没有二次确认,实盘加载前建议自己加一道拦截。授权分支里 LoginButton、TwoFALoginButton 等也走独立回调,和主面板事件树分开维护。 全局区定义了 adminPanel(注释里写明准备改名 communicationsPanel)、authentication、twoFactorAuth 三个 CDialog,以及 8 个快捷消息按钮的数组 quickMessageButtons[8]。MAX_MESSAGE_LENGTH 写死 4096,availableFonts 给了 4 种字体,currentFontIndex 从 0 起算。 Password 被硬编码成 "2024",twoFACode 初始为空串,这种明文口令在 MT5 实盘环境里风险极高,外汇与贵金属交易本身杠杆波动大,EA 鉴权逻辑千万别直接抄去用。OnInit 里先 ShowAuthenticationPrompt(),返回 false 就 Print 报错并 return INIT_FAILED,EA 根本不会跑主循环。

MQL5 / C++
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=class="str">"cmt">// Global variables
CDialog adminPanel;   class=class="str">"cmt">//To rename it to communicationsPanel
CDialog authentication, twoFactorAuth;
CButton sendButton, clearButton, changeFontButton, toggleThemeButton;
CButton loginButton, closeAuthButton, twoFALoginButton, close2FAButton;
CButton quickMessageButtons[class="num">8], minimizeButton, maximizeButton, closeButton;
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;
   }

EA 面板的初始化与鉴权弹窗搭建

在 MT5 自定义 EA 里,图形面板通常在 OnInit 阶段落地。下面这段逻辑先尝试用 ChartID() 在坐标 (30,30) 建一个 500×500 的隐藏管理面板,若 Create 返回 false 就打印错误并以 INIT_FAILED 退出,避免后续空指针。 管理面板建好后立刻调用 Hide(),终端加载时不会闪一下界面;控制台输出 "Initialization complete" 并返回 INIT_SUCCEEDED,说明资源已就绪。 鉴权弹窗另起函数 ShowAuthenticationPrompt 处理。它在 (100,100) 生成 500×300 的对话框,再依次塞入密码输入框(260×95)、提示标签(坐标20,20)、红色反馈标签(380×160)和登录按钮(100×140)。任何控件 Create 失败都直接 return false,调用方据此决定是否阻断 EA 运行。 feedbackLabel 默认空文本且染成 clrRed,专用于密码错误时的提示。你在 MT5 里改这句 Color(clrRed) 为 clrBlack,就能把报错语气调温和,便于调试阶段观察输入流程。

MQL5 / C++
if (!adminPanel.Create(ChartID(), "Admin Panel", class="num">0, class="num">30, class="num">30, class="num">500, class="num">500))
{
   Print("Failed to create admin panel dialog");
   class="kw">return INIT_FAILED;
}
if (!CreateControls())
{
   Print("Control creation failed");
   class="kw">return INIT_FAILED;
}
adminPanel.Hide();
Print("Initialization complete");
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;
   }
   loginButton.Text("Login");
   authentication.Add(loginButton);

◍ 在MT5图表上拼出双因素验证弹窗

想让EA在图表端做身份校验,不必跳出MT5去碰系统对话框,直接用CChartObject派生类在图表坐标上画图元就行。下面这段把关闭按钮挂到认证面板里,坐标用的是相对图表的像素:左上锚点(120,120),宽200高140。 closeAuthButton.Create失败就Print并返回false,这种写法能保证面板没拼出来时及早退出,不至于后面Add空对象导致图表重绘异常。Text("Close")只是给按钮塞了文案,真正入栈靠authentication.Add(),最后Show()+ChartRedraw()才让玩家看见。 双因素弹窗ShowTwoFactorAuthPrompt()更繁琐:主框(100,100,500,300),输入框(20,70,260,95),提示标签(20,20,380,40),反馈标签(20,140,380,160)初始空文本且染红,验证按钮(20,120,100,140)写死"Verify"。任何一个Create返回false都直接return,避免半截界面。 外汇与贵金属账户接Telegram推码本身是高风险的,图表端2FA只挡误操作,不替代经纪商后台安全设置;参数里的坐标数字你可按自己屏幕DPI改,改完开MT5拖一个测试EA挂图表就能看到布局。

MQL5 / C++
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;
   }
   close2FAButton.Text("Close");

「图表事件如何分流到面板按钮」

在 MT5 自定义面板里,所有鼠标点击、编辑框结束输入都走 OnChartEvent 这一条通道,靠 id 和 sparam 区分来源。上面这段代码把登录、双因素认证、聊天发送等十几个按钮全部挂在同一事件函数里,用字符串对象名做路由。 前半段先用 if(id==CHARTEVENT_OBJECT_CLICK) 单独处理登录与 2FA 弹窗的四种按钮,调用各自的处理函数;后半段又用 switch(id) 把聊天面板的发送、清屏、换字体、主题切换、最小化最大化关闭,以及以 QuickMessageButton 开头的快捷消息按钮统一分发。 注意 QuickMessageButton 的解析:用 StringFind 判前缀后,从 sparam 第 18 个字符截子串转整型,再减 1 得到数组下标。若你在 ObjectCreate 时命名的按钮是 QuickMessageButton3,这里取到的 index 就是 3,减 1 后指向数组第 2 项。 这种集中式事件分发在对象数超过 10 个时仍能保持清晰,但 sparam 字符串必须和创建对象时的名字严格一致,否则点击无响应。外汇与贵金属图表加载此类 EA 属高风险操作,建议先在策略测试器离线验证事件逻辑。

MQL5 / C++
twoFactorAuth.Add(close2FAButton);
twoFactorAuth.Show();
ChartRedraw();
}
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 == "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);
         }
         break;
      case CHARTEVENT_OBJECT_ENDEDIT:
         if(sparam == "InputBox") OnInputChange();
         break;
   }
}

管理员面板的二次验证与退出逻辑

在 MT5 自建管理面板时,登录不是一次性密码比对就完事。下面这段实现把密码校验和二次验证(2FA)拆成两步:先比对接管框里的密码,一致才生成 6 位随机码并推到 Telegram,随后销毁密码窗、弹出 2FA 输入框。 密码错的处理很直接——反馈标签写「Wrong password」,清空输入框,不生成任何码,也不会往下走。这一步如果用户乱试,后台只会留下失败记录,不会触发消息推送。 2FA 那一侧,用户填的码和内存里的 twoFACode 一致才销毁验证窗、调 adminPanel.Show() 亮出主面板;错就清输入框重试。两个关闭按钮(OnCloseAuthButtonClick / OnClose2FAButtonClick)都调 ExpertRemove() 直接退出 EA,避免面板卡在图上。 CreateControls() 里 inputBox.Create() 用 ChartID() 拿当前图表句柄,坐标写死为左 5、顶 25、宽 460、高 95;返回 false 时 Print 报错并中断创建。要复现的话,把 Hardcoded2FAChatId 和 BotToken 换成你自己的,开 MT5 挂 EA 就能看到两步弹窗流程。外汇与贵金属品种波动剧烈,这类面板只管本地鉴权,不涉及下单风控,实盘前务必自测退出路径是否干净。

MQL5 / C++
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="type">void OnTwoFALoginButtonClick()
{
   class="type">class="kw">string enteredCode = twoFACodeInput.Text();
   if (enteredCode == twoFACode)
   {
      twoFactorAuth.Destroy();
      adminPanel.Show();
      Print("2FA authentication successful.");
   }
   else
   {
      twoFAFeedbackLabel.Text("Wrong code. Try again.");
      twoFACodeInput.Text("");
   }
}
class="type">void OnCloseAuthButtonClick()
{
   authentication.Destroy();
   ExpertRemove(); class=class="str">"cmt">// Exit the expert
   Print("Authentication dialog closed.");
}
class="type">void OnClose2FAButtonClick()
{
   twoFactorAuth.Destroy();
   ExpertRemove();
   Print("2FA dialog closed.");
}
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;
   }

◍ 把控件挂进面板并兜底创建失败

面板对象 adminPanel 在前面完成基底后,这里负责把输入框、计数器和一堆功能按钮逐个 Add 进去;任何一步 Create 返回 false 都会直接 Print 报错并 return false,避免半残界面挂在图表上。 字符计数器 charCounter 用 Create(chart_id, "CharCounter", 0, 380, 5, 460, 25) 定位,宽 80 高 20,初始文本写成 "0/" 拼接 MAX_MESSAGE_LENGTH,实时显示已输入字数上限。 clearButton 到 toggleThemeButton 这六个按钮的锚点坐标差异很大:clear 在 (235,95)-(345,125),send 在 (350,95)-(460,125),换字体和换主题按钮挤在左侧 5~230 区间,而 minimize 与 maximize 被放到 y=-22 的位置,也就是标题栏高度,文本分别是 "_" 和 "[ ]"。 这种把最小化、最大化按钮绘到负坐标区的写法,在 MT5 里实际是把控件贴到面板标题条上;若你改了面板头高度,这两个按钮可能错位甚至看不见,需要同步调那两个 -22 的 y 值。

MQL5 / C++
  adminPanel.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));
  adminPanel.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");
  adminPanel.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");
  adminPanel.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<>");
  adminPanel.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<>");
  adminPanel.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("_");
  adminPanel.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("[ ]");
  adminPanel.Add(maximizeButton);
把多面板状态映射交给小布
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到当前EA占用与面板状态,你不必自己写日志去追哪个功能被覆盖。

常见问题

图表容器在设计中仅绑定一个EA实例,后加载的会接管前者。要扩展功能需用多对话框类面板或独立脚本分流,而非堆叠EA。
可以,小布盯盘的品种页会呈现EA占用与面板活跃状态,覆盖发生时会有对应提示,省去你翻日志。
通信面板承接原Telegram播报与群组交互,交易管理面板负责开仓、平仓及SL/TP修改,二者通过主页按钮跳转互不干扰。
MQL5不能直接读Telegram私信,需借助中间件或HTTP回调把验证码回传面板,本篇代码层会给出对话框接收方案。