价格行为分析工具包开发(第 17 部分):TrendLoom EA 工具·进阶篇
(2/3)·在 M1 到 W1 之间反复切换图表验证趋势,是很多交易者漏掉反向信号的直接原因
「面板生命周期的初始化与销毁钩子」
在 MT5 里用自定义控件做趋势面板,初始化和销毁必须走 OnInit / OnDeinit 两个系统钩子,否则图表 detached 时资源不会释放。上面这段代码把 TrendPanel 的创建放在 OnInit,坐标从 PanelX、PanelY 起算,宽度加 20 像素留边距,高度向下 400 像素。 若 CreateTrendPanel 返回 false,直接 Print 报错并返回 INIT_FAILED,EA 就不会挂载——这一点能帮你快速定位是坐标越界还是控件库缺失。 OnChartEvent 里把 id、lparam、dparam、sparam 原样转给 TrendPanel.ChartEvent,意味着按钮点击、鼠标移动等事件都由面板内部处理逻辑接管,主程序保持干净。 #include 路径里 ChartObjectsTxtControls.mqh 用了单数文件夹写法,老版本标准库可能是 ChartObjects,复制代码前先确认你 MT5 的 Include 目录结构,否则编译会报 cannot open file。外汇与贵金属波动剧烈,这类面板只做辅助观察,不构成任何方向建议。
class="kw">return "NEUTRAL"; } class="type">int OnInit() { if(!TrendPanel.CreateTrendPanel(class="num">0, "TrendLoom Panel", PanelX, PanelY, PanelX + PanelWidth + class="num">20, PanelY + class="num">400)) { Print("Failed to create TrendLoom Panel."); class="kw">return INIT_FAILED; } class="kw">return INIT_SUCCEEDED; } class="type">void OnDeinit(const class="type">int reason) { TrendPanel.Destroy(reason); } 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) { TrendPanel.ChartEvent(id, lparam, dparam, sparam); } class="macro">#include <Controls/Dialog.mqh> class="macro">#include <Controls/Button.mqh> class="macro">#include <Controls/Label.mqh> class="macro">#include <Controls/Panel.mqh> class="macro">#include <ChartObjects/ChartObjectsTxtControls.mqh> class=class="str">"cmt">// Adjusted include path(singular folder)
◍ EA控制面板的代码骨架
下面这段 MQL5 源码定义了一个名为 TrendLoom 的 EA 头部与界面参数,核心是在图表左上角(10,10)坐标处绘制一个宽250像素的操控面板。 代码开头用 #property strict 强制开启严格编译模式,并引入 Dialog / Button / Label / Panel 等标准控件库;其中 ChartObjectsTxtControls.mqh 的包含路径已被调整为单数文件夹写法,老版本 MT5 若报找不到文件需手动核对目录。 为避免控件库未定义对齐常量导致编译失败,源码用 #ifndef 补了 ALIGN_LEFT=0、ALIGN_CENTER=1、ALIGN_RIGHT=2 三个宏,并自定义 clrSilver 为 0xC0C0C0。 输入参数全部用 input 暴露给 EA 属性框:按钮宽220、高30、间距5,面板背景 clrDimGray、头部文字 clrBlueViolet、按钮底黑字紫。把这些数值直接拷进 MT5 的 MQ5 文件头部,就能在编译后看到一套暗色系悬浮面板雏形,外汇与贵金属品种波动剧烈,挂面板前先在模拟盘验证坐标是否被价格标签遮挡。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TrendLoom EA.mq5 | class=class="str">"cmt">//| Copyright class="num">2025, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2025, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property strict class="macro">#include <Controls/Dialog.mqh> class="macro">#include <Controls/Button.mqh> class="macro">#include <Controls/Label.mqh> class="macro">#include <Controls/Panel.mqh> class="macro">#include <ChartObjects/ChartObjectsTxtControls.mqh> class=class="str">"cmt">// Adjusted include path(singular folder) class=class="str">"cmt">// Define alignment constants if not already defined class="macro">#ifndef ALIGN_LEFT class="macro">#define ALIGN_LEFT class="num">0 class="macro">#endif class="macro">#ifndef ALIGN_CENTER class="macro">#define ALIGN_CENTER class="num">1 class="macro">#endif class="macro">#ifndef ALIGN_RIGHT class="macro">#define ALIGN_RIGHT class="num">2 class="macro">#endif class="macro">#define clrSilver 0xC0C0C0 class=class="str">"cmt">//---- Input parameters ----------------------------------------------- input class="type">int PanelX = class="num">10; class=class="str">"cmt">// Top-left X coordinate of panel input class="type">int PanelY = class="num">10; class=class="str">"cmt">// Top-left Y coordinate of panel input class="type">int PanelWidth = class="num">250; class=class="str">"cmt">// Panel width(for longer text) input class="type">int btnWidth = class="num">220; class=class="str">"cmt">// Button width input class="type">int btnHeight = class="num">30; class=class="str">"cmt">// Button height input class="type">int btnSpacing = class="num">5; class=class="str">"cmt">// Spacing between buttons input class="type">class="kw">color PanelBackgroundColor = clrDimGray; class=class="str">"cmt">// Panel background class="type">class="kw">color input class="type">class="kw">color PanelHeaderColor = clrBlueViolet; class=class="str">"cmt">// Panel header text class="type">class="kw">color input class="type">class="kw">color ButtonBgColor = clrBlack; class=class="str">"cmt">// Button background class="type">class="kw">color input class="type">class="kw">color ButtonTextColor = clrBlueViolet; class=class="str">"cmt">// Button text class="type">class="kw">color
面板按钮与多周期分析类的骨架
这套 TrendLoom 面板的输入参数里,先定义了分析文字颜色为 clrLime,并在 buttonNames 与 buttonTexts 两个长度为 7 的字符串数组里写死了 7 种交易风格按钮:从 Short Term Focus(M1/M5/M15)到 Long Term Trend(H1/D1/W1),覆盖短线剥头皮到周线趋势。 宏定义 PANEL_BG、PANEL_HEADER、ANALYSIS_LABEL 分别接管背景、标题栏与分析结果标签的全局对象名,避免图表上对象名冲突。 CTrendLoomPanel 类继承自 CAppDialog,私有成员列了 7 个 CButton 加一个 CLabel 结果框,对外只暴露 CreateTrendPanel 方法。该方法先调父类 Create 建对话框,失败就 Print 并返回 false;随后建头部标签,位置锁定在 (10,10)、宽 x2-x1-20、高 30,文字写死 "TrendLoom EA",颜色用 PanelHeaderColor。 开 MT5 把这段拷进 EA 的 include 面板文件,改 buttonTexts 里的周期组合就能快速验证不同风格按钮的排布逻辑,外汇与贵金属波动剧烈,多周期共振信号也仅代表概率倾向,实操需自担高风险。
input class="type">class="kw">color AnalysisTextColor = clrLime; class=class="str">"cmt">// Analysis result text class="type">class="kw">color class=class="str">"cmt">//---- Button Names and Texts(class="num">7 analysis options) -------------------- class="type">class="kw">string buttonNames[class="num">7] = { "btnShortTerm", "btnScalping", "btnSwing", "btnTrend", "btnMTFTrend", "btnShortScalper", "btnLongTerm" }; class="type">class="kw">string buttonTexts[class="num">7] = { "Short Term Focus\n(M1, M5, M15)", "Scalping/Intraday\n(M5, M15, H1)", "Swing Trading\n(M15, H1, H4)", "Trend Trading\n(H1, H4, D1)", "MTF Trend Confirm\n(H1, H4, W1)", "Short Scalper/Mid Trend\n(M5, H1, D1)", "Long Term Trend\n(H1, D1, W1)" }; class=class="str">"cmt">// Global object names for panel header and analysis label class="macro">#define PANEL_BG "PanelBG" class="macro">#define PANEL_HEADER "PanelHeader" class="macro">#define ANALYSIS_LABEL "AnalysisResult" class=class="str">"cmt">//--- Helper function declarations class="type">class="kw">double GetSMA(class="type">class="kw">string symbol, ENUM_TIMEFRAMES timeframe, class="type">int period, class="type">int shift); class="type">class="kw">string AnalyzeTimeframes(ENUM_TIMEFRAMES tf1, ENUM_TIMEFRAMES tf2, ENUM_TIMEFRAMES tf3); class=class="str">"cmt">//------------------------------------------------------------------------------ class=class="str">"cmt">// CTrendLoomPanel class - A modern, modular panel for TrendLoom EA class=class="str">"cmt">//------------------------------------------------------------------------------ class CTrendLoomPanel : class="kw">public CAppDialog { class="kw">private: CLabel m_lblHeader; CPanel m_panelMain; CButton m_btnShortTerm; CButton m_btnScalping; CButton m_btnSwing; CButton m_btnTrend; CButton m_btnMTFTrend; CButton m_btnShortScalper; CButton m_btnLongTerm; CLabel m_lblResults; class="kw">public: class=class="str">"cmt">// Create the TrendLoom Panel dialog class="type">bool CreateTrendPanel(const class="type">long chart, const class="type">class="kw">string name, const class="type">int x1, const class="type">int y1, const class="type">int x2, const class="type">int y2) { if(!CAppDialog::Create(chart, name, class="num">0, x1, y1, x2, y2)) { Print("Failed to create TrendLoom dialog."); class="kw">return false; } class=class="str">"cmt">// Create header label if(!m_lblHeader.Create(class="num">0, "TrendLoomHeader", class="num">0, class="num">10, class="num">10, x2 - x1 - class="num">20, class="num">30)) { Print("Failed to create header label."); class="kw">return false; } m_lblHeader.Text("TrendLoom EA"); m_lblHeader.Color(PanelHeaderColor);
「面板与按钮的布局坐标怎么算」
在 MT5 自定义指标或 EA 的 GUI 里,主面板高度不是拍脑袋给的。代码用 50 + (btnHeight + btnSpacing) * 7 + btnSpacing 推导底部坐标,意味着若按钮高 30、间距 10,面板底部就是 50 + 40*7 + 10 = 340 像素,多一个少一个按钮都得重算,否则底部留白或裁切。
标题标签先用 m_lblHeader.FontSize(14) 和 Font("Segoe UI") 定调,再用 ObjectSetInteger(0, Name(), OBJPROP_ALIGN, ALIGN_CENTER) 把文字居中;失败仅 Print 不中断,实际界面可能左对齐歪掉,调试时得自己盯图表。
按钮从 (20, 60) 相对对话框起步,每创建一个就 btnY += buttonHeight + btnSpacing 往下推。ShortTerm、Scalping、Swing、Trend 四个按钮共用 ButtonBgColor 和 12 号 Segoe UI,任何一个 Create 返回 false 就直接 return false 终止整个面板构建,所以数组越界或重名会让你面板直接空白。
m_lblHeader.FontSize(class="num">14); m_lblHeader.Font("Segoe UI"); Add(m_lblHeader); class=class="str">"cmt">// Set header text alignment to center using ObjectSetInteger if(!ObjectSetInteger(0L, m_lblHeader.Name(), OBJPROP_ALIGN, (class="type">long)ALIGN_CENTER)) Print("Failed to set header alignment"); class=class="str">"cmt">// Create main panel background class="type">int panelBottom = class="num">50 + (btnHeight + btnSpacing) * class="num">7 + btnSpacing; if(!m_panelMain.Create(class="num">0, "TrendLoomPanel", class="num">0, class="num">10, class="num">50, x2 - x1 - class="num">10, panelBottom)) { Print("Failed to create main panel."); class="kw">return false; } m_panelMain.Color(PanelBackgroundColor); m_panelMain.BorderType(BORDER_RAISED); m_panelMain.ColorBorder(clrSilver); Add(m_panelMain); class=class="str">"cmt">// Starting coordinates for buttons class="type">int btnX = class="num">20; class=class="str">"cmt">// relative to dialog class="type">int btnY = class="num">60; class="type">int buttonWidth = btnWidth; class="type">int buttonHeight = btnHeight; class=class="str">"cmt">// Create each button with a modern look if(!m_btnShortTerm.Create(class="num">0, buttonNames[class="num">0], class="num">0, btnX, btnY, btnX + buttonWidth, btnY + buttonHeight)) class="kw">return false; m_btnShortTerm.Text(buttonTexts[class="num">0]); m_btnShortTerm.Font("Segoe UI"); m_btnShortTerm.FontSize(class="num">12); m_btnShortTerm.Color(ButtonBgColor); Add(m_btnShortTerm); btnY += buttonHeight + btnSpacing; if(!m_btnScalping.Create(class="num">0, buttonNames[class="num">1], class="num">0, btnX, btnY, btnX + buttonWidth, btnY + buttonHeight)) class="kw">return false; m_btnScalping.Text(buttonTexts[class="num">1]); m_btnScalping.Font("Segoe UI"); m_btnScalping.FontSize(class="num">12); m_btnScalping.Color(ButtonBgColor); Add(m_btnScalping); btnY += buttonHeight + btnSpacing; if(!m_btnSwing.Create(class="num">0, buttonNames[class="num">2], class="num">0, btnX, btnY, btnX + buttonWidth, btnY + buttonHeight)) class="kw">return false; m_btnSwing.Text(buttonTexts[class="num">2]); m_btnSwing.Font("Segoe UI"); m_btnSwing.FontSize(class="num">12); m_btnSwing.Color(ButtonBgColor); Add(m_btnSwing); btnY += buttonHeight + btnSpacing; if(!m_btnTrend.Create(class="num">0, buttonNames[class="num">3], class="num">0, btnX, btnY, btnX + buttonWidth, btnY + buttonHeight)) class="kw">return false; m_btnTrend.Text(buttonTexts[class="num">3]); m_btnTrend.Font("Segoe UI"); m_btnTrend.FontSize(class="num">12); m_btnTrend.Color(ButtonBgColor);
◍ 按钮堆叠与事件绑定的落点
这段逻辑在面板初始化尾部连续塞入三个策略按钮:MTF 趋势、短线剥头皮、长线布局,索引分别是 buttonNames[4] 到 [6]。每个按钮都走同一套 Create → Text → Font("Segoe UI") → FontSize(12) → Color(ButtonBgColor) → Add 的流程,btnY 每次累加 buttonHeight + btnSpacing 实现纵向等距排布。 按钮之下还创建了一个高 30 像素的标签 m_lblResults,初始文案写死 "Analysis Result: [Waiting for Input]",并用 ObjectSetInteger 把 OBJPROP_ALIGN 设成 ALIGN_LEFT,否则默认居中会和左侧按钮边缘对不齐。 事件分发从 OnEvent 的 sparam 入手,例如 sparam=="btnShortTerm" 时调用 AnalyzeTimeframes(PERIOD_M1, PERIOD_M5, PERIOD_M15),把三周期共振结果拼进 "Short Term Focus: " 前缀,同时 UpdateResults 改标签、Alert 弹窗。外汇与贵金属波动剧烈,这类一键多周期扫描仅作概率参考,实盘前请在 MT5 策略测试器跑一遍确认无句柄泄漏。 直接把下面片段贴进你的面板类,改 buttonNames 数组就能增删按钮;若 btnY 起始值算错,标签会压在最后一个按钮上,这是 MT5 图形对象坐标系最容易踩的坑。
Add(m_btnTrend); btnY += buttonHeight + btnSpacing; if(!m_btnMTFTrend.Create(class="num">0, buttonNames[class="num">4], class="num">0, btnX, btnY, btnX + buttonWidth, btnY + buttonHeight)) class="kw">return false; m_btnMTFTrend.Text(buttonTexts[class="num">4]); m_btnMTFTrend.Font("Segoe UI"); m_btnMTFTrend.FontSize(class="num">12); m_btnMTFTrend.Color(ButtonBgColor); Add(m_btnMTFTrend); btnY += buttonHeight + btnSpacing; if(!m_btnShortScalper.Create(class="num">0, buttonNames[class="num">5], class="num">0, btnX, btnY, btnX + buttonWidth, btnY + buttonHeight)) class="kw">return false; m_btnShortScalper.Text(buttonTexts[class="num">5]); m_btnShortScalper.Font("Segoe UI"); m_btnShortScalper.FontSize(class="num">12); m_btnShortScalper.Color(ButtonBgColor); Add(m_btnShortScalper); btnY += buttonHeight + btnSpacing; if(!m_btnLongTerm.Create(class="num">0, buttonNames[class="num">6], class="num">0, btnX, btnY, btnX + buttonWidth, btnY + buttonHeight)) class="kw">return false; m_btnLongTerm.Text(buttonTexts[class="num">6]); m_btnLongTerm.Font("Segoe UI"); m_btnLongTerm.FontSize(class="num">12); m_btnLongTerm.Color(ButtonBgColor); Add(m_btnLongTerm); btnY += buttonHeight + btnSpacing; class=class="str">"cmt">// Create results label below the buttons if(!m_lblResults.Create(class="num">0, "TrendResults", class="num">0, btnX, btnY, btnX + buttonWidth, btnY + class="num">30)) class="kw">return false; m_lblResults.Text("Analysis Result: [Waiting for Input]"); m_lblResults.Font("Segoe UI"); m_lblResults.FontSize(class="num">12); m_lblResults.Color(AnalysisTextColor); Add(m_lblResults); class=class="str">"cmt">// Set results text alignment to left using ObjectSetInteger if(!ObjectSetInteger(0L, m_lblResults.Name(), OBJPROP_ALIGN, (class="type">long)ALIGN_LEFT)) Print("Failed to set results alignment"); Show(); class="kw">return true; class=class="str">"cmt">// Process events(button clicks) class="type">bool OnEvent(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(sparam == "btnShortTerm") { class="type">class="kw">string res = AnalyzeTimeframes(PERIOD_M1, PERIOD_M5, PERIOD_M15); class="type">class="kw">string out = "Short Term Focus: " + res; UpdateResults(out); Alert(out); class="kw">return true; } else if(sparam == "btnScalping")
按钮触发的多周期分析分支
这段逻辑挂在 EA 的 OnChartEvent 里,靠 sparam 识别面板按钮,不同按钮对应不同的三周期组合。 scalping 按钮走 M5+M15+H1,swing 按钮走 M15+H1+H4,趋势按钮走 H1+H4+D1,多周期趋势确认走 H1+H4+W1,短 scalp 变体则跳到 M5+H1+D1。 每个分支都调用同一个 AnalyzeTimeframes 拿到字符串结论,前面拼好交易类型标签,再写进 UpdateResults 并弹 Alert。注意这里没有返回值差异,全部 return true,说明事件处理函数只关心『处理过』这件事。 真要在 MT5 里验证,把这段塞进你自己的图表面板 EA,点不同按钮看 Alert 弹出的周期组合是否符合预期;外汇和贵金属波动受消息影响大,这类多周期共振信号只提高概率,不代表方向必然成立,仓位控制仍是高风险下的第一道防线。
{
class="type">class="kw">string res = AnalyzeTimeframes(PERIOD_M5, PERIOD_M15, PERIOD_H1);
class="type">class="kw">string out = "Scalping/Intraday: " + res;
UpdateResults(out);
Alert(out);
class="kw">return true;
}
else
if(sparam == "btnSwing")
{
class="type">class="kw">string res = AnalyzeTimeframes(PERIOD_M15, PERIOD_H1, PERIOD_H4);
class="type">class="kw">string out = "Swing Trading: " + res;
UpdateResults(out);
Alert(out);
class="kw">return true;
}
else
if(sparam == "btnTrend")
{
class="type">class="kw">string res = AnalyzeTimeframes(PERIOD_H1, PERIOD_H4, PERIOD_D1);
class="type">class="kw">string out = "Trend Trading: " + res;
UpdateResults(out);
Alert(out);
class="kw">return true;
}
else
if(sparam == "btnMTFTrend")
{
class="type">class="kw">string res = AnalyzeTimeframes(PERIOD_H1, PERIOD_H4, PERIOD_W1);
class="type">class="kw">string out = "MTF Trend Confirm: " + res;
UpdateResults(out);
Alert(out);
class="kw">return true;
}
else
if(sparam == "btnShortScalper")
{
class="type">class="kw">string res = AnalyzeTimeframes(PERIOD_M5, PERIOD_H1, PERIOD_D1);