价格行为分析工具包开发(第 17 部分):TrendLoom EA 工具·进阶篇
📊

价格行为分析工具包开发(第 17 部分):TrendLoom EA 工具·进阶篇

(2/3)·在 M1 到 W1 之间反复切换图表验证趋势,是很多交易者漏掉反向信号的直接原因

进阶 第 2/3 篇
在 M5 上看到卖出信号就入场,切到 H4 才发现整体是买入趋势——这种错位进场常发生在手动跨周期核对时。TrendLoom EA 把七个风格对应的三周期 SMA 信号合成一键面板,正是为了堵住这个漏洞。外汇与贵金属杠杆高,信号冲突时仓位暴露风险会被放大。

「面板生命周期的初始化与销毁钩子」

在 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。外汇与贵金属波动剧烈,这类面板只做辅助观察,不构成任何方向建议。

MQL5 / C++
   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 文件头部,就能在编译后看到一套暗色系悬浮面板雏形,外汇与贵金属品种波动剧烈,挂面板前先在模拟盘验证坐标是否被价格标签遮挡。

MQL5 / C++
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 里的周期组合就能快速验证不同风格按钮的排布逻辑,外汇与贵金属波动剧烈,多周期共振信号也仅代表概率倾向,实操需自担高风险。

MQL5 / C++
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 终止整个面板构建,所以数组越界或重名会让你面板直接空白。

MQL5 / C++
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 图形对象坐标系最容易踩的坑。

MQL5 / C++
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 弹出的周期组合是否符合预期;外汇和贵金属波动受消息影响大,这类多周期共振信号只提高概率,不代表方向必然成立,仓位控制仍是高风险下的第一道防线。

MQL5 / C++
      {
         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);
把多周期核对交给小布
小布盯盘的 AIGC 已内置类似多周期趋势叠合视图,打开对应品种页就能直接看各周期偏多偏空的概率分布,你只管做决策。

常见问题

短期关注用 M1/M5/M15,剥头皮日内用 M5/M15/H1,波段用 M15/H1/H4,趋势交易用 H1/H4/D1,多周期确认用 H1/H4/W1,短剥头皮中期用 M5/H1/D1,长期趋势用 H1/D1/W1。
原文以 50 周期 SMA 作为价格基准线,收盘价站上赋 +1、跌破赋 -1、相等为 0,三周期加总≥2 出买入、≤-2 出卖出,参数选择偏向平滑中期噪声,具体可调但原文未展开。
总和在 -1 到 1 之间属于混合或中性,EA 返回“中性”,不给出方向性确认,避免弱信号误导进场。
小布盯盘的品种页内置了多周期偏多偏空的概率视图,逻辑类似但呈现更轻量,适合快速扫盘;需要 EA 级自动刷新和按钮交互仍可用 MQL5 原工具。
信号基于已收盘 K 线计算,切换周期只读取收盘价与 SMA,计算量小,正常 VPS 上刷新接近实时,但极端行情跳空可能造成一根 K 线偏差。