在MQL5中创建交易管理员面板(第六部分):多功能界面(一)·综合运用
- 面板按钮的创建与销毁逻辑
- 面板交互回调与主题切换的实现细节
- 面板控件的主题与字体热切换
- 面板状态切换与 Telegram 推送的实现细节
- 面板初始化与硬编码鉴权入口
- EA 初始化与密码验证面板的硬骨头
- 在MT5图表上拼出双因子验证弹窗
- 管理面板按钮的创建与坐标排布
- 面板按钮的事件分发逻辑
- 面板登录与双因素校验的事件分支
- 在图表上拼出通讯面板的控件
- 面板按钮与快捷消息的布局逻辑
- 面板交互逻辑的底层回调
- 面板控件主题与字体切换的实现细节
- 把面板事件接到 Telegram 推送
- 首版面板卡窗与导航缺口
- 给子面板加回家的入口
- 在图表上拼出交易管理面板
- 在 MT5 里直接给电报客户发信号的面板骨架
- 面板初始化与鉴权弹窗的构建顺序
- 在图表上拼出登录与双因子验证界面
- 在图表上拼出双因素验证与管理面板
- 面板按钮的锚点坐标与容错返回
- 面板按钮的事件路由怎么接
- 面板事件分发与两步验证登录逻辑
- EA面板里的双因子校验与控件装配
- 面板控件的逐一定位与挂载
- 在MT5图表上铺一排快捷喊话按钮
- 面板交互与主题切换的事件处理
- 面板控件的交互与随机码生成
- 把信号推到 Telegram 的那段 POST
- 面板 V1.21 跑起来长什么样
- 面板迭代到哪一步了
「面板按钮的创建与销毁逻辑」
这段 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[] 的全局对象声明补齐,否则编译会报未定义;外汇与贵金属交易本身高风险,这类面板只是辅助发消息的工具,不替代任何风控。
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 初始值改一下编译即可看到差异。
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 最大化键,逻辑轻量。
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。外汇与贵金属交易本身杠杆高、风险大,这类自动发信辅助工具只降低沟通成本,不改善信号胜率。
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,叠在同一个图表上,靠后续逻辑切换显隐。 别把明文密码当临时方案留着。外汇与贵金属杠杆高、滑点突变频繁,这类面板若直连真实账号,硬编码凭证泄露可能触发非授权平仓。
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 鉴权模块出错概率不低。
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 里的关闭键同尺寸,保持界面控件节奏一致。外汇与贵金属账户接这类远程验证涉及密钥流转,属于高风险操作,建议先在模拟盘把整条登录链路跑通再上真实账户。
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 图表看是否弹出带五个按钮的面板——外汇与贵金属交易界面定制属高风险环境,代码异常可能引发图表卡死。
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 属高风险操作,按钮误触可能瞬间撤掉专家或发错指令;实盘前务必在策略测试器里把每个分支跑一遍。
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 的做法在真实账户跑有泄露风险,外汇和贵金属本身杠杆高、波动猛,密钥落代码里可能被人反编译扒走,实盘前建议改外部加密配置。
}
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 值贴到图表边框。
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 明文写在输入参数里属于高风险暴露,建议用本地加密或隔离账户。
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 封装,就能在图表上跑出可切换主题的喊单面板。
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 最大化与关闭键;最大化逻辑对称反向操作即可。
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 分发泄露,建议从输入框或加密文件读取。外汇与贵金属杠杆高,任何自动通知失效都可能让你错过平仓信号,上线前先在模拟盘跑通这套回调。
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 拖一下锚点验证最实在。
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 图表,就能看到六个方块浮在左上角。
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 退出。
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,调用方应据此阻断后续逻辑而非静默继续。
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。
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 或对应函数,改下按钮文本和坐标就能复现这套界面;外汇与贵金属交易本身高风险,这类面板只解决操作入口,不暗示任何收益。
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 像素步长微调;外汇与贵金属杠杆高,面板只是下单辅助,真触发市价单前仍要人工核对点差与滑点。
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 大小写和创建时传的字符串必须完全一致,差一个字符事件就不会触发。外汇与贵金属行情波动剧烈,这类面板仅作辅助工具,实际操作仍有较高风险。
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 之后,能直接验证按钮路由是否按预期走。
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 并打印授权成功,不一致则把提示标签改成错误文案并清空输入框。
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">// 清空输入 } }
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."); }
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)) {
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() 把快捷消息按钮的创建交给了另一个函数。
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 里改 width 和 startY 实地拖一下再看排布。
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 格式是不是纯字符串而非带引号的字面量。
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); // 格式化为六位带前导零字符串 }
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。外汇和贵金属波动剧烈,这类推送只做辅助提醒,任何信号都不构成入场保证,请自担高风险。
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 扒过来贴上的,后续版本才会细抠视觉规范。 外汇与贵金属品种波动剧烈、杠杆风险高,这类后台工具只解决通知与操作入口问题,不等于交易策略本身安全。下一步若打开交易管理面板,建议先在小布盯盘里对照自己的风控参数再动手。