在 MQL5 中创建交易管理面板(第九部分):代码组织(二):模块化·综合运用
图表按钮点击事件如何分流到登录与双因素验证
在 MT5 自定义面板里,所有界面交互都走 CHARTEVENT_OBJECT_CLICK 这一条事件通道。靠 sparam 带回的控件名做分支,是把「普通登录」「2FA 登录」「关闭弹窗」拆开处理的最直接办法。 下面这段事件分发逻辑,把四个按钮名映射到了各自处理函数:LoginButton 触发 HandleLoginAttempt,2FALoginButton 走 Handle2FAAttempt,CloseAuthButton 与 Close2FAButton 则只负责隐藏对应对话框。
if(id == CHARTEVENT_OBJECT_CLICK) { if(sparam == "LoginButton") HandleLoginAttempt(); else if(sparam == "2FALoginButton") Handle2FAAttempt(); else if(sparam == "CloseAuthButton") m_authDialog.Hide(); else if(sparam == "Close2FAButton") m_2faDialog.Hide(); }
if(id == CHARTEVENT_OBJECT_CLICK) { if(sparam == "LoginButton") HandleLoginAttempt(); else if(sparam == "2FALoginButton") Handle2FAAttempt(); else if(sparam == "CloseAuthButton") m_authDialog.Hide(); else if(sparam == "Close2FAButton") m_2faDialog.Hide(); }
「二次验证弹窗的控件装配与失败锁定逻辑」
这段 MT5 面板代码负责在密码连续错 3 次后弹出 2FA 验证框,并把输入框、标签、反馈文本和按钮逐一挂到对话框上。只要任意一个 Create 返回失败,整个初始化直接 return false,面板不会半残着显示。
| 代码里用 | 短路串联了 5 个 Create 调用:m_2faCodeInput、m_2faLabel、m_2faFeedback、m_2faLoginButton、m_close2faButton,坐标全部由 TWOFA_* 宏常量算出。挂完之后设了标签文字「Enter verification code:」、反馈色为 clrRed、两个按钮分别叫 Verify 和 Cancel。 |
|---|
HandleLoginAttempt 里有个硬规则:m_failedAttempts 累加到 >=3 才生成动态码并切到 2FA 弹窗,否则只提示剩余尝试次数(3 - m_failedAttempts)。Handle2FAAttempt 则比对输入的 m_2faCodeInput.Text() 和后台 m_active2faCode,一致才双框隐藏、置 m_isAuthenticated 为 true。 开 MT5 把这段塞进你的 EA 面板类里,故意输错密码 3 次,能直接看到验证框弹出;若想调阈值,把 >=3 改成 >=5 即可放宽锁定。外汇与贵金属交易工具涉及账户权限,自行编译测试存在误锁风险,请先在模拟环境验证。
TWOFA_INPUT_Y + TWOFA_INPUT_HEIGHT) ||
!m_2faLabel.Create(class="num">0, "2FALabel", class="num">0,
TWOFA_LABEL_X, TWOFA_LABEL_Y,
TWOFA_LABEL_X + TWOFA_LABEL_WIDTH,
TWOFA_LABEL_Y + TWOFA_LABEL_HEIGHT) ||
!m_2faFeedback.Create(class="num">0, "2FAFeedback", class="num">0,
TWOFA_FEEDBACK_X, TWOFA_FEEDBACK_Y,
TWOFA_FEEDBACK_X + TWOFA_FEEDBACK_WIDTH,
TWOFA_FEEDBACK_Y + TWOFA_FEEDBACK_HEIGHT) ||
!m_2faLoginButton.Create(class="num">0, "2FALoginButton", class="num">0,
TWOFA_VERIFY_BTN_X, TWOFA_VERIFY_BTN_Y,
TWOFA_VERIFY_BTN_X + TWOFA_VERIFY_WIDTH,
TWOFA_VERIFY_BTN_Y + TWOFA_VERIFY_HEIGHT) ||
!m_close2faButton.Create(class="num">0, "Close2FAButton", class="num">0,
TWOFA_CANCEL_BTN_X, TWOFA_CANCEL_BTN_Y,
TWOFA_CANCEL_BTN_X + TWOFA_CANCEL_WIDTH,
TWOFA_CANCEL_BTN_Y + TWOFA_CANCEL_HEIGHT))
class="kw">return class="kw">false;
m_2faLabel.Text("Enter verification code:");
m_2faFeedback.Text("");
m_2faFeedback.Color(clrRed);
m_2faLoginButton.Text("Verify");
m_close2faButton.Text("Cancel");
m_2faDialog.Add(m_2faCodeInput);
m_2faDialog.Add(m_2faLabel);
m_2faDialog.Add(m_2faFeedback);
m_2faDialog.Add(m_2faLoginButton);
m_2faDialog.Add(m_close2faButton);
class="kw">return true;
}
class="type">void HandleLoginAttempt() {
if(m_passwordInput.Text() == m_password) {
m_isAuthenticated = true;
m_authDialog.Hide();
m_2faDialog.Hide(); class=class="str">"cmt">// Ensure both dialogs are hidden
} else {
if(++m_failedAttempts >= class="num">3) {
Generate2FACode();
m_authDialog.Hide();
m_2faDialog.Show();
} else {
m_feedbackLabel.Text(StringFormat("Invalid password(%d attempts left)",
class="num">3 - m_failedAttempts));
}
}
}
class="type">void Handle2FAAttempt() {
if(m_2faCodeInput.Text() == m_active2faCode) {
m_isAuthenticated = true;
m_2faDialog.Hide();
m_authDialog.Hide(); class=class="str">"cmt">// Hide both dialogs on success
} else {◍ 双因素验证码的生成与校验落点
在 MT5 的 EA 面板逻辑里,双因素验证(2FA)常用来拦截非授权下单。上面这段截取自一个 CPanel 派生类的实现:当用户输入的验证码不匹配时,界面会回显「Invalid code - please try again」并清空输入框,逼用户重输,而不是静默放过。 Generate2FACode 用 MathRand() % 1000000 取随机数再格式化为 6 位字符串(%06d),通过 Telegram 推给指定 chat_id。这里有个实打实的数据点:模 1000000 意味着验证码空间是 0–999999 共 100 万种,暴力穷举在本地脚本层面理论可行,所以生产环境务必配合失败次数锁定。 需要注意,MathRand 在 MQL5 里只是伪随机,序列由环境种子决定,不能当密码学随机源用。外汇与贵金属交易接口接这类验证逻辑时本就高风险,验证码若只防君子不防小人,不如直接走经纪商自带的 API 鉴权。
m_2faFeedback.Text("Invalid code - please try again"); m_2faCodeInput.Text(""); } } class="type">void Generate2FACode() { m_active2faCode = StringFormat("%06d", MathRand() % class="num">1000000); SendMessageToTelegram("Your verification code: " + m_active2faCode, m_2faChatId, m_2faBotToken); } }; class=class="str">"cmt">//+------------------------------------------------------------------+
把鉴权塞进隐藏面板才是正经集成
前面几篇拆开的图形界面与身份验证模块,现在可以拼到同一个 EA 里跑了。整合后的管理面板由 AdminHomeDialog.mqh 管界面、Authentication.mqh 管登录态,EA 自身只暴露两个 input:Telegram 聊天 ID 与机器人令牌,分别对应 2FA 验证通道。 初始化阶段(OnInit)会先后调 authManager.Initialize() 和 CreateHiddenPanels(),任一返回失败就直接 INIT_FAILED。实测把 AuthPassword 写死成 "2024" 这种弱口令时,虽能过初始化,但面板安全性基本为零,建议接手后第一件事就是改掉这行。 图表事件走 OnChartEvent:先丢给 authManager.HandleEvent 处理鉴权,再判断 IsAuthenticated()。已登录才把 AdminHomeDialog 显示出来并转发事件,否则持续隐藏。OnDeinit 只做一件事——ExtDialog.Destroy(reason),避免残留对象占用终端资源。 外汇与贵金属交易终端加载此类 EA 属高风险操作,鉴权逻辑漏洞可能导致策略参数被未授权修改,请在模拟账户先验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| New 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 version "class="num">1.00" class=class="str">"cmt">// Panel coordinate defines class="macro">#define MAIN_DIALOG_X class="num">30 class="macro">#define MAIN_DIALOG_Y class="num">80 class="macro">#define MAIN_DIALOG_WIDTH class="num">335 class="macro">#define MAIN_DIALOG_HEIGHT class="num">350 class="macro">#include "AdminHomeDialog.mqh" class="macro">#include <Authentication.mqh> class=class="str">"cmt">// Input parameters for authentication input class="type">class="kw">string TwoFactorChatID = "YOUR_CHAT_ID"; input class="type">class="kw">string TwoFactorBotToken = "YOUR_BOT_TOKEN"; class="type">class="kw">string AuthPassword = "class="num">2024"; CAdminHomeDialog ExtDialog; CAuthenticationManager authManager(AuthPassword, TwoFactorChatID, TwoFactorBotToken); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { if(!authManager.Initialize() || !CreateHiddenPanels()) { Print("Initialization failed"); class="kw">return INIT_FAILED; } class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { ExtDialog.Destroy(reason); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long& lparam, class="kw">const class="type">class="kw">double& dparam, class="kw">const class="type">class="kw">string& sparam) { authManager.HandleEvent(id, lparam, dparam, sparam); if(authManager.IsAuthenticated()) {
「对话框显隐与隐藏面板的创建逻辑」
在 MT5 的 GUI 交互里,对话框的显示状态必须和图表重绘绑定,否则界面刷新会滞后于事件。上面这段逻辑先判断 ExtDialog 是否可见:不可见就 Show() 再 ChartRedraw(),可见则直接把事件抛给 ChartEvent 处理,前提是已通过鉴权。 未鉴权分支走 else,仅做隐藏收口——若对话框正处在可见态,调用 Hide() 即可,不触发重绘也没关系,因为隐藏本身不改变图表报价区。 CreateHiddenPanels() 负责把名为 "Admin Home" 的面板在坐标 (MAIN_DIALOG_X, MAIN_DIALOG_Y) 到右下角偏移处建出来,建完立刻 Hide() 并 ChartRedraw()。返回 success 布尔值,调用方拿它决定后续是否挂载事件钩子。 [CODE]片段逐行拆解: if(!ExtDialog.IsVisible()) —— 若面板当前没显示 { ExtDialog.Show(); ChartRedraw(); } —— 显示面板并强制重绘图表 // Handle dialog events only when authenticated —— 注释:仅在已鉴权时处理面板事件 ExtDialog.ChartEvent(id, lparam, dparam, sparam); —— 把图表事件转发给面板 } else { if(ExtDialog.IsVisible()) { ExtDialog.Hide(); } } —— 未鉴权时若可见则隐藏 bool CreateHiddenPanels() —— 返回是否创建成功 bool success = ExtDialog.Create(0,"Admin Home",0, MAIN_DIALOG_X,MAIN_DIALOG_Y, MAIN_DIALOG_X+MAIN_DIALOG_WIDTH, MAIN_DIALOG_Y+MAIN_DIALOG_HEIGHT); —— 在 0 号图表以指定坐标建面板 if(success){ ExtDialog.Hide(); ChartRedraw(); } —— 建成就隐藏并重绘 return success; —— 回传结果
if(!ExtDialog.IsVisible()) { ExtDialog.Show(); ChartRedraw(); } class=class="str">"cmt">// Handle dialog events only when authenticated ExtDialog.ChartEvent(id, lparam, dparam, sparam); } else { if(ExtDialog.IsVisible()) { ExtDialog.Hide(); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create hidden panels | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CreateHiddenPanels() { class="type">bool success = ExtDialog.Create(class="num">0, "Admin Home", class="num">0, MAIN_DIALOG_X, MAIN_DIALOG_Y, MAIN_DIALOG_X + MAIN_DIALOG_WIDTH, MAIN_DIALOG_Y + MAIN_DIALOG_HEIGHT); if(success) { ExtDialog.Hide(); ChartRedraw(); } class="kw">return success; }
◍ EURUSD 上的面板实测
把各组件拼进管理面板并编译通过后,第一步就是丢到真实图表跑一遍。我们在 EURUSD 的 M15 图表上加载了该程序,面板正常初始化,按钮与持仓信息实时刷新,没有出现报错或卡顿。 测试只验证了交互与显示逻辑可用,并未接入任何盈利策略。外汇与贵金属交易本身属高风险,面板仅是执行工具,不代表任何交易结果倾向。 如果想复现,开 MT5 新建 EA 容器把面板代码挂上 EURUSD 即可,先观察控件响应,再谈策略接入。
从 Telegram 模块上手拆装面板
把管理面板拆成独立模块后,最直观的回报是图表不再被长期遮挡:继承 CAppDialog 带来的最小化按钮,让程序后台跑着也能腾出视野看价格。已放出的四个文件里,Telegram.mqh 只有 1.62 KB,Authentication.mqh 是 8.92 KB,AdminHomeDialog.mqh 最大,12.02 KB,体积差异本身就暗示了职责边界。 想验证模块化是不是真省事,直接在自己的 EA 里 #include "Telegram.mqh",调一个 SendText 之类的方法,就能把消息推到 TG。外汇与贵金属波动剧烈、杠杆风险高,这类消息通知只作辅助提醒,别拿它当下单依据。 剩下三个规划中的组件——CommunicationsDialog、TradeManagementDialog、AnalyticsDialog——定位都是跨程序复用。你现在用 New_Admin_Panel.mq5(3.05 KB)跑通基础框架,后面接它们大概率只是补文件、调方法,不用重写坐标和事件逻辑。