监视多币种的交易信号 (第二部分) : 应用程序可视部分的实现·综合运用
🖥️

监视多币种的交易信号 (第二部分) : 应用程序可视部分的实现·综合运用

(3/3)· 把前两篇的架构草稿变成能用的 GUI,品种、周期、信号三步配置一次跑通

偏理论 第 3/3 篇
很多自建监控面板的尝试停在黑框打印阶段,真正卡住的不是逻辑而是界面状态机。把配置拆成顺序步骤、用类实例隔离窗口对象,能少写一半调试时间。

「周期分组按钮的互斥与复选联动」

在自定义面板里用三个 currency_set 按钮做周期分组切换时,核心逻辑是互斥按下加复选框批量同步。以「Junior Timeframes」分支为例:当检测到 m_currency_set[1] 被按下,先把索引 0 和 2 的两个分组按钮置为未按下并 Update,避免多组同时高亮导致状态混乱。 接着代码定义了一个 11 元素的字符串数组 pairs,覆盖 M1 到 M30 共 11 个短周期标签。随后用两层循环先清空全部 21 个复选框的按下态,再遍历比对 LabelText,命中短周期标签的复选框才重新 IsPressed(true) 并刷新。 这种写法意味着你的复选框总数固定为 21,而 junior 组只映射其中 11 个。开 MT5 把这段直接塞进 OnEvent 的 lparam 分支里,改 pairs 数组就能重排周期分组,外汇与贵金属品种波动大、杠杆高,面板只是辅助筛周期,下单前仍要自判风险。

MQL5 / C++
m_currency_set[class="num">1].Update(true);
m_currency_set[class="num">2].Update(true);
class=class="str">"cmt">//---
for(class="type">int i=class="num">0; i<class="num">21; i++)
{
   m_checkbox[i].IsPressed(true);
   m_checkbox[i].Update(true);
}
   }
   class=class="str">"cmt">//--- Junior Timeframes
   else if(lparam==m_currency_set[class="num">1].Id() && m_currency_set[class="num">1].IsPressed())
   {
      m_currency_set[class="num">0].IsPressed(false);
      m_currency_set[class="num">2].IsPressed(false);
      m_currency_set[class="num">0].Update(true);
      m_currency_set[class="num">2].Update(true);
      class=class="str">"cmt">//---
      class="type">class="kw">string pairs[class="num">11]=
      {
         "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30"
      };
      class=class="str">"cmt">//--- Clear the selection
      for(class="type">int i=class="num">0; i<class="num">21; i++)
      {
         m_checkbox[i].IsPressed(false);
         m_checkbox[i].Update(true);
      }
      class=class="str">"cmt">//---
      for(class="type">int i=class="num">0; i<class="num">21; i++)
      {
         for(class="type">int j=class="num">0; j<class="num">11; j++)
         if(m_checkbox[i].LabelText()==pairs[j])
         {
            m_checkbox[i].IsPressed(true);
            m_checkbox[i].Update(true);
         }
      }
   }
   class=class="str">"cmt">//--- Senior Timeframes
   else if(lparam==m_currency_set[class="num">2].Id() && m_currency_set[class="num">2].IsPressed())
   {
      m_currency_set[class="num">0].IsPressed(false);
      m_currency_set[class="num">1].IsPressed(false);

◍ 币种面板与周期复选框的初始化套路

在信号监视器的引导流程里,第一步要先刷新两组币种集合的状态,再把周期复选框清掉重选。下面这段逻辑先把 m_currency_set[0] 和 [1] 强制 Update(true),保证界面上的「All / Majors」按钮显示最新 pressed 状态,随后用一个 10 元素的字符串数组 pairs 容纳从 H1 到 MN 的全部周期标签。 清空动作分两层:先用 for 循环把 m_all_symbols 个复选框全部 IsPressed(false) 并 Update,相当于把用户上一次勾选痕迹抹平;再跑一层双重循环,把 LabelText 命中 pairs 里周期名的复选框重新 IsPressed(true),实现「默认只亮周期、不亮币种」的界面基线。 ToStep_1 函数接管后,头部标签改成 "Signal Monitor Step 1: Choose Symbols",Back 按钮直接 Hide,避免用户在首步回退。它先对 21 个复选框做取消勾选,再按 SymbolName(i,false) 把 m_all_symbols 个真实交易品种名填进复选框并显示出来——这里硬写 21 是历史最大值,若你 MT5 品种数超过 21,数组越界会静默失败,得自己改上限。 三组快捷按钮名被改成了 "All" / "Majors" / "Crosses",循环里顺手把 m_currency_set[i] 的 pressed 复位。外汇与贵金属波动剧烈、杠杆风险高,这类界面预设仅降低手动操作成本,不构成任何方向暗示,跑之前请在策略测试器用少量品种验证循环边界。

MQL5 / C++
m_currency_set[class="num">0].Update(true);
m_currency_set[class="num">1].Update(true);
class=class="str">"cmt">//---
class="type">class="kw">string pairs[class="num">10]=
{
  "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"
};
class=class="str">"cmt">//--- Clear the selection
for(class="type">int i=class="num">0; i<m_all_symbols; i++)
{
  m_checkbox[i].IsPressed(false);
  m_checkbox[i].Update(true);
}
class=class="str">"cmt">//---
for(class="type">int i=class="num">0; i<m_all_symbols; i++)
{
  for(class="type">int j=class="num">0; j<class="num">10; j++)
    if(m_checkbox[i].LabelText()==pairs[j])
    {
      m_checkbox[i].IsPressed(true);
      m_checkbox[i].Update(true);
    }
}
}
class="type">void CProgram::ToStep_1(class="type">void)
{
class=class="str">"cmt">//--- Change header
  m_step_window.LabelText("Signal Monitor Step class="num">1: Choose Symbols");
  m_step_window.Update(true);
class=class="str">"cmt">//--- Hide the Back button
  m_back_button.Hide();
class=class="str">"cmt">//--- Clear selection
  for(class="type">int i=class="num">0; i<class="num">21; i++)
  {
    m_checkbox[i].IsPressed(false);
    m_checkbox[i].Update(true);
  }
class=class="str">"cmt">//--- Show elements of Step class="num">1
  for(class="type">int i=class="num">0; i<m_all_symbols; i++)
  {
    m_checkbox[i].Show();
    m_checkbox[i].LabelText(SymbolName(i,false));
    m_checkbox[i].Update(true);
  }
  class="type">class="kw">string names[class="num">3]= {"All","Majors","Crosses"};
class=class="str">"cmt">//--- Change names of selection buttons
  for(class="type">int i=class="num">0; i<class="num">3; i++)
  {
    m_currency_set[i].IsPressed(false);
    m_currency_set[i].LabelText(names[i]);

更新货币组并切入模板配置

在货币组对象数组上调用 Update(true) 时,true 参数强制立即刷新报价与属性,而不是等下一帧周期。循环里逐个 m_currency_set[i] 都这样处理,能保证面板刚弹出时所有品种状态是最新的,避免看到滞后点差。 随后把文本编辑框、加载按钮、保存按钮全部 Show(),这一步只是把模板管理区块从隐藏态切到可见态,不涉及任何读写。最后把 m_current_step 置为 1,意味着向导流程重新定位到第一步,用户后续操作会按这个步骤变量走分支。 这段逻辑本身不触发交易,但步骤变量若初始化错,后面加载模板会直接跳错。开 MT5 挂上面板后,可在 OnClick 里断点看 m_current_step 是否真的落为 1。

MQL5 / C++
   m_currency_set[i].Update(true);
   }
class=class="str">"cmt">//--- Show block for working with templates
   m_text_edit.Show();
   m_load_button.Show();
   m_save_button.Show();
class=class="str">"cmt">//--- Set the current setup step
   m_current_step=class="num">1;
}

「把添加信号的按钮和标题挂进步骤窗口」

第三步界面只做一件事:在分步窗口里放一个“Add Signal”按钮和一行信号列表标题。先在 Program.mqh 的 CProgram 里声明 m_add_signal(CButton)与 m_signal_header(CTextLabel),再在 StepWindow.mqh 末尾补上两个创建函数,并在 CreateStepWindow() 里调用,让程序启动时就把控件建出来。 启动后默认处在第一步,所以在 CreateGUI() 末尾要把这两个元素先 Hide(),避免过早露面。等用户走完前两步、触发 ToStep_3() 时,再清掉上一步可视化对象并显示它们。注意:前两步若没选完必填项,应用会拦住你进第三步。 下面这段是按钮与标签的落地代码。CreateIconButton 里按钮尺寸锁死为宽 110、高 30,图标用 plus.bmp(16×16 图标资源),底色 C'70,180,70'、按下 C'70,170,70',文字白色居中;CreateLabel 的标签宽 120,背景沿用 m_background。最后在窗口坐标 (10,30) 生成按钮,返回 false 即代表创建失败。 编译后双击 Next 进第三步,能在 MT5 里直接看到带加号图标的绿色按钮。外汇与贵金属 GUI 自动化涉及实盘逻辑,参数误用可能引发错单,属高风险操作,验证时建议先用模拟账户。

MQL5 / C++
  CButton            m_add_signal;
  class=class="str">"cmt">//---
  CTextLabel         m_signal_header;
  class="type">bool               CreateIconButton(CButton &button,class="type">class="kw">string text,const class="type">int x_gap,const class="type">int y_gap);
  class=class="str">"cmt">//--- Text label
  class="type">bool               CreateLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Creates a button with an image                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\plus.bmp"
class="type">bool CProgram::CreateIconButton(CButton &button,class="type">class="kw">string text,const class="type">int x_gap,const class="type">int y_gap)
{
class=class="str">"cmt">//---
  class="type">color baseclr=C&class="macro">#x27;class="num">70,class="num">180,class="num">70&class="macro">#x27;;
  class="type">color pressed=C&class="macro">#x27;class="num">70,class="num">170,class="num">70&class="macro">#x27;;
class=class="str">"cmt">//--- Save the window pointer
  button.MainPointer(m_step_window);
class=class="str">"cmt">//--- Set properties before creation
  button.XSize(class="num">110);
  button.YSize(class="num">30);
  button.Font(m_base_font);
  button.FontSize(m_base_font_size);
  button.IconXGap(class="num">3);
  button.IconYGap(class="num">7);
  button.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\plus.bmp");
  button.BackColor(baseclr);
  button.BackColorHover(baseclr);
  button.BackColorPressed(pressed);
  button.BorderColor(baseclr);
  button.BorderColorHover(baseclr);
  button.BorderColorPressed(pressed);
  button.LabelColor(clrWhite);
  button.LabelColorPressed(clrWhite);
  button.LabelColorHover(clrWhite);
  button.IsCenterText(true);
class=class="str">"cmt">//--- Create a control
  if(!button.CreateButton(text,x_gap,y_gap))
     class="kw">return(false);
class=class="str">"cmt">//--- Add a pointer to element to the base
  CWndContainer::AddToElementsArray(class="num">0,button);
  class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Creates the text label                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text)
{
class=class="str">"cmt">//--- Save the window pointer
  text_label.MainPointer(m_step_window);
class=class="str">"cmt">//---
  text_label.Font(m_base_font);
  text_label.FontSize(m_base_font_size);
  text_label.XSize(class="num">120);
  text_label.BackColor(m_background);
class=class="str">"cmt">//--- Create the button
  if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
     class="kw">return(false);
class=class="str">"cmt">//--- Add a pointer to element to the base
  CWndContainer::AddToElementsArray(class="num">0,text_label);
  class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//---
  if(!CreateIconButton(m_add_signal,"Add Signal",class="num">10,class="num">30))
     class="kw">return(false);

◍ 三步向导里的界面显隐逻辑

这段 MQL5 片段来自一个信号监控程序的 GUI 构建流程,核心在 CreateGUI 与 ToStep_3 两个方法里控制窗口元素的显示与隐藏。CreateGUI 里把返回按钮、添加信号键和信号列表头直接 Hide,说明初始向导阶段这些控件不该露面。 [CODE] if(!CreateLabel(m_signal_header,10,30+30+10,"Signal List")) return(false); //+------------------------------------------------------------------+

//Creates the graphical interface of the program

//+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Step 1-3 if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols")) return(false); //--- Finish the creation of GUI CWndEvents::CompletedGUI(); m_back_button.Hide(); m_add_signal.Hide(); m_signal_header.Hide(); return(true); } //+------------------------------------------------------------------+

//

//+------------------------------------------------------------------+ void CProgram::ToStep_3(void) { //--- Check whether at least one timeframe is selected int cnt=0; for(int i=0; i<21; i++) { if(m_checkbox[i].IsPressed()) cnt++; } if(cnt<1) { MessageBox("No timeframes selected!","Warning"); return; } //--- m_step_window.LabelText("Signal Monitor Step 3: Create Signals"); m_step_window.Update(true); m_next_button.LabelText("Create"); m_next_button.Update(true); //--- Hide elements of Step 2 for(int i=0; i<21; i++) { if(i<3) m_currency_set[i].Hide(); m_checkbox[i].Hide(); } //--- m_add_signal.Show(); m_signal_header.Show(); //--- m_current_step=3; } [/CODE] 逐行看 CreateGUI:先尝试在坐标 (10, 70) 处给信号头创建标签,失败直接返回 false;随后建第一步选符号的窗口,成功才调 CompletedGUI 标记界面就绪,并把三个后续控件藏起。 ToStep_3 先遍历 21 个复选框统计被按下的数量,少于 1 就弹“No timeframes selected!”警告并退出,确保至少选了一个周期。接着把步骤窗标题改成第三步、下一步按钮改名“Create”,并隐藏第二步的 3 个货币集与全部 21 个复选框,再 Show 出添加信号与信号头,最后把当前步置为 3。 开 MT5 把这段塞进自己的 CProgram 类,断点跟一下 m_current_step 从 1 到 3 的跳转,能确认隐藏逻辑是否如预期——外汇与贵金属品种多、周期杂,界面状态错乱可能让信号漏触发,这类 GUI bug 在高波动时段尤其难排查。

MQL5 / C++
if(!CreateLabel(m_signal_header,class="num">10,class="num">30+class="num">30+class="num">10,"Signal List"))
   class="kw">return(false);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Creates the graphical interface of the program                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateGUI(class="type">void)
{
class=class="str">"cmt">//--- Step class="num">1-class="num">3
   if(!CreateStepWindow("Signal Monitor Step class="num">1: Choose Symbols"))
      class="kw">return(false);
class=class="str">"cmt">//--- Finish the creation of GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   m_add_signal.Hide();
   m_signal_header.Hide();
   class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::ToStep_3(class="type">void)
{
class=class="str">"cmt">//--- Check whether at least one timeframe is selected
   class="type">int cnt=class="num">0;
   for(class="type">int i=class="num">0; i<class="num">21; i++)
   {
      if(m_checkbox[i].IsPressed())
         cnt++;
   }
   if(cnt<class="num">1)
   {
      MessageBox("No timeframes selected!","Warning");
      class="kw">return;
   }
class=class="str">"cmt">//---
   m_step_window.LabelText("Signal Monitor Step class="num">3: Create Signals");
   m_step_window.Update(true);
   m_next_button.LabelText("Create");
   m_next_button.Update(true);
class=class="str">"cmt">//--- Hide elements of Step class="num">2
   for(class="type">int i=class="num">0; i<class="num">21; i++)
   {
      if(i<class="num">3)
         m_currency_set[i].Hide();
      m_checkbox[i].Hide();
   }
class=class="str">"cmt">//---
   m_add_signal.Show();
   m_signal_header.Show();
class=class="str">"cmt">//---
   m_current_step=class="num">3;
}

搭一个信号编辑的独立窗口

信号编辑的可视组件都挂在 SetWindow.mqh 里,但它现在只通过 #include 连了 Program.mqh。先在 Program.mqh 的 CProgram 类里声明一个 CWindow 实例 m_set_window,再补一个 CreateSetWindow() 负责把窗口本体造出来,返回 bool 表示成败。 窗口尺寸定死为 568×555 像素,标题栏高 22,横向坐标取图表宽度减半再减窗宽一半,纵向贴着 y=30。类型用 W_DIALOG,可拖动,配色走统一背景与标题色,这些写死在方法里方便后面照抄。 CreateGUI() 中插入对 CreateSetWindow("Signal Monitor Edit Signal") 的调用;点“Add Signal”按钮时在 OnEvent() 里触发 m_set_window.OpenWindow(),编译后第 3 步点按钮就会弹出这个编辑窗。 窗口里第一块是选指标类型,从 7 种标准振荡器里挑。做法一样:建类实例、写创建方法,在 CreateSetWindow() 末尾调 CreateIndicatorType()。随后是指标设置板块(周期、适用价格)和信号设置板块(规则、标签色、提示、图形标记、时间帧等八项),均在同文件里追加变量与方法,并在窗口方法末尾统一调用。 交互上要处理几件事:时间帧按钮只显示前面步骤选中的数字;点“Value”会弹起“Text”并隐藏文本输入;颜色按钮拉出调色板;取消勾选时相关字段失活。这些在 OnEvent() 里用循环扫四个按钮数组实现。外汇与贵金属 GUI 自动化涉及实盘逻辑,误触可能造成下单风险,验证时先用模拟账户。

MQL5 / C++
  CWindow            m_set_window;
  class="type">bool                CreateSetWindow(const class="type">class="kw">string caption_text);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Creates a window for creating and editing trading signals        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateSetWindow(const class="type">class="kw">string text)
{
class=class="str">"cmt">//--- Add the pointer to the window array
  CWndContainer::AddWindow(m_set_window);
class=class="str">"cmt">//--- Properties
  m_set_window.XSize(class="num">568);
  m_set_window.YSize(class="num">555);
class=class="str">"cmt">//--- Coordinates
  class="type">int x=class="type">int(ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)-m_set_window.XSize())/class="num">2;
  class="type">int y=class="num">30;
class=class="str">"cmt">//---
  m_set_window.CaptionHeight(class="num">22);
  m_set_window.IsMovable(true);
  m_set_window.CaptionColor(m_caption);
  m_set_window.CaptionColorLocked(m_caption);
  m_set_window.CaptionColorHover(m_caption);
  m_set_window.BackColor(m_background);
  m_set_window.FontSize(m_base_font_size);
  m_set_window.Font(m_base_font);
  m_set_window.WindowType(W_DIALOG);
class=class="str">"cmt">//--- Creating a form
  if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
    class="kw">return(false);
  class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Creates the graphical interface of the program                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateGUI(class="type">void)
{
class=class="str">"cmt">//--- Step class="num">1-class="num">3
  if(!CreateStepWindow("Signal Monitor Step class="num">1: Choose Symbols"))
    class="kw">return(false);
class=class="str">"cmt">//--- Creation and editing window
  if(!CreateSetWindow("Signal Monitor Edit Signal"))
    class="kw">return(false);
class=class="str">"cmt">//--- Finish the creation of GUI
  CWndEvents::CompletedGUI();
  m_back_button.Hide();
  m_add_signal.Hide();
  m_signal_header.Hide();
  class="kw">return(true);
}
        class=class="str">"cmt">//--- Click on the "Add Signal" button
        if(lparam==m_add_signal.Id())
        {
          m_set_window.OpenWindow();
        }
  class=class="str">"cmt">//--- Drop-down menu

「给面板塞一个指标类型下拉框」

在 MT5 自定义面板里做指标切换,最省事的办法是用 CComboBox 封装一个下拉控件。下面这段逻辑把 7 种常见震荡类指标名写死进数组,运行时直接渲染成可选项,交易者点一下就能换品种分析口径。 代码里先给组合框定尺寸:宽 200、高 26,标签上间距 4,列表项总数 7,字体走基类 m_base_font。按钮区单独设了白底、宽 100,列表视区每项高 26 且开启悬停高亮(LightsHover(true)),这些数值直接决定面板在 125% 缩放下是否挤版。 注意 m_indicator_type.SelectItem(5) 这行——它默认选中数组下标 5,也就是 RSI。如果你习惯先看 WPR 或 ATR,把数字改成对应下标即可,下标从 0 计(ATR=0,Momentum=6)。 创建完后用 CWndContainer::AddToElementsArray(1,m_indicator_type) 把控件挂进元素组,外部调用 CreateIndicatorType(10,32) 就能以左上偏移 (10,32) 落位。外汇与贵金属波动大、杠杆高,这类面板仅作辅助筛选,信号本身不预示单边行情。

MQL5 / C++
  CComboBox          m_indicator_type;
  class=class="str">"cmt">//--- Creates a drop-down method
  class="type">bool                CreateIndicatorType(const class="type">int x_gap,const class="type">int y_gap);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Creates a drop-down menu with indicator types                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateIndicatorType(const class="type">int x_gap,const class="type">int y_gap)
{
class=class="str">"cmt">//--- Pass the object to the panel
  m_indicator_type.MainPointer(m_set_window);
class=class="str">"cmt">//--- Array of the item values in the list view
  class="type">class="kw">string pattern_names[class="num">7]=
  {
    "ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum"
  };
class=class="str">"cmt">//--- Set properties before creation
  m_indicator_type.XSize(class="num">200);
  m_indicator_type.YSize(class="num">26);
  m_indicator_type.LabelYGap(class="num">4);
  m_indicator_type.ItemsTotal(class="num">7);
  m_indicator_type.Font(m_base_font);
  m_indicator_type.FontSize(m_base_font_size);
  m_indicator_type.BackColor(m_background);
  m_indicator_type.GetButtonPointer().Font(m_base_font);
  m_indicator_type.GetButtonPointer().FontSize(m_base_font_size);
  m_indicator_type.GetButtonPointer().BackColor(clrWhite);
  m_indicator_type.GetButtonPointer().XGap(class="num">100);
  m_indicator_type.GetButtonPointer().XSize(class="num">100);
  m_indicator_type.GetListViewPointer().Font(m_base_font);
  m_indicator_type.GetListViewPointer().FontSize(m_base_font_size);
  m_indicator_type.GetListViewPointer().ItemYSize(class="num">26);
class=class="str">"cmt">//--- Store the item values in the combo box list view
  for(class="type">int i=class="num">0; i<class="num">7; i++)
    m_indicator_type.SetValue(i,pattern_names[i]);
class=class="str">"cmt">//--- Get the list pointer
  CListView *lv=m_indicator_type.GetListViewPointer();
class=class="str">"cmt">//--- Set the list view properties
  lv.LightsHover(true);
  m_indicator_type.SelectItem(class="num">5);
class=class="str">"cmt">//--- Create a control
  if(!m_indicator_type.CreateComboBox("Indicator Type",x_gap,y_gap))
    class="kw">return(false);
class=class="str">"cmt">//--- Add an object to the common array of object groups
  CWndContainer::AddToElementsArray(class="num">1,m_indicator_type);
  class="kw">return(true);
}

◍ 下拉选指标时界面跟着变

在自定义面板里用组合框切换指标类型时,MT5 的 GUI 逻辑要靠 CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM 事件来捕捉点击。拿到选中项索引后,用 switch 分流:ATR、DeMarker、WPR、Momentum 这类不依赖应用价格的指标,会把周期输入框标签改成对应名字,同时隐藏 applied_price 控件;而 CCI、Force Index、RSI 则需要显示 applied_price 让用户选收盘价或典型价。 代码里 default 分支兜底成 RSI Period 并隐藏价格选择,说明作者把 RSI 当缺省信号。注意 m_period_edit.Update(true) 必须放在 switch 外、统一刷新,否则标签文本可能不重绘。 信号设置区另起一组控件:CreateSetLabel 在 y=22+10+4*(25+10)=162 像素处写“2.Signal Settings”标题;条件行由 CreateRuleValue(130, 197) 与 CreateRule(10, 197) 生成,197 来自 22+10+5*(25+10)。这种坐标硬编码方式在 4K 屏或缩放改动时可能错位,调试时建议先 Print 实际像素再改常量。 外汇与贵金属波动剧烈、杠杆高风险大,此类面板仅用于策略原型验证,实盘前请在模拟盘跑通事件响应。

MQL5 / C++
  class=class="str">"cmt">//--- Item selection in the combobox list
  if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
  {
    class="type">int index=m_indicator_type.GetListViewPointer().SelectedItemIndex();
    class="kw">switch(index)
    {
    case  class="num">0:
       m_period_edit.LabelText("ATR Period");
       m_applied_price.Hide();
       break;
    case  class="num">1:
       m_period_edit.LabelText("CCI Period");
       m_applied_price.Show();
       break;
    case  class="num">2:
       m_period_edit.LabelText("DeMarker Period");
       m_applied_price.Hide();
       break;
    case  class="num">3:
       m_period_edit.LabelText("Force Index Period");
       m_applied_price.Show();
       break;
    case  class="num">4:
       m_period_edit.LabelText("WPR Period");
       m_applied_price.Hide();
       break;
    case  class="num">5:
       m_period_edit.LabelText("RSI Period");
       m_applied_price.Show();
       break;
    case  class="num">6:
       m_period_edit.LabelText("Momentum Period");
       m_applied_price.Hide();
       break;
    class="kw">default:
       m_period_edit.LabelText("RSI Period");
       m_applied_price.Hide();
       break;
    }
     m_period_edit.Update(true);
  }
  class=class="str">"cmt">//--- Signal settings
  if(!CreateSetLabel(m_set_header[class="num">1],class="num">10,class="num">22+class="num">10+class="num">4*(class="num">25+class="num">10),"class="num">2.Signal Settings"))
    class="kw">return(false);
CTextEdit        m_rule_value;
CComboBox        m_rule_type;
...
  class="type">bool            CreateRuleValue(const class="type">int x_gap,const class="type">int y_gap);
  class="type">bool            CreateRule(const class="type">int x_gap,const class="type">int y_gap);
  class=class="str">"cmt">//--- Condition settings
  if(!CreateRuleValue(class="num">130,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10)))
    class="kw">return(false);
  if(!CreateRule(class="num">10,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10)))
    class="kw">return(false);

对话框尺寸与控件坐标的硬编码布局

在 MT5 自定义指标面板里,设置窗口的几何参数往往是写死的。下面这段 CreateSetWindow 把对话框宽高锁在 568×575 像素,标题栏高 22,距图表顶部 30 像素,水平居中靠 ChartGetInteger 取图表宽再减自身宽除 2 算 x 坐标。 控件纵向排布用了一个隐蔽节奏:以 22(标题)+10(上边距)为基线,每个区块占 25+10 的行高步进。比如『Indicator Settings』标签落在 22+10+26+10,周期输入框在 22+10+2*(25+10)=100,应用价格在下一组 125,信号设置标题在 22+10+4*35=172。改布局时直接调这些常数比拖控件快。 外汇与贵金属图表加载这类 GUI 高风险:像素写死在 4K 屏或缩放异于 100% 的终端上可能错位,实盘前务必在目标分辨率开 MT5 验证一遍。 别把 568×575 当通用尺寸 不同指标字段数量差很多,硬抄这个窗口大小会让下部控件溢出。按你自己 CreateRule / CreateButton2 的实际个数重算 YSize,公式就是基线 + N*(25+10) + 余量。

MQL5 / C++
class="type">bool CProgram::CreateSetWindow(const class="type">class="kw">string text)
{
class=class="str">"cmt">//--- Add the pointer to the window array
  CWndContainer::AddWindow(m_set_window);
class=class="str">"cmt">//--- Properties
  m_set_window.XSize(class="num">568);
  m_set_window.YSize(class="num">575);
class=class="str">"cmt">//--- Coordinates
  class="type">int x=class="type">int(ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)-m_set_window.XSize())/class="num">2;
  class="type">int y=class="num">30;
class=class="str">"cmt">//---
  m_set_window.CaptionHeight(class="num">22);
  m_set_window.IsMovable(true);
  m_set_window.CaptionColor(m_caption);
  m_set_window.CaptionColorLocked(m_caption);
  m_set_window.CaptionColorHover(m_caption);
  m_set_window.BackColor(m_background);
  m_set_window.FontSize(m_base_font_size);
  m_set_window.Font(m_base_font);
  m_set_window.WindowType(W_DIALOG);
class=class="str">"cmt">//--- Creating a form
  if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      class="kw">return(false);
class=class="str">"cmt">//--- Indicator type
  if(!CreateIndicatorType(class="num">10,class="num">22+class="num">10))
      class="kw">return(false);
class=class="str">"cmt">//--- Settings of the selected indicator
  if(!CreateSetLabel(m_set_header[class="num">0],class="num">10,class="num">22+class="num">10+class="num">26+class="num">10,"class="num">1.Indicator Settings"))
      class="kw">return(false);
  if(!CreatePeriodEdit(class="num">10,class="num">22+class="num">10+class="num">2*(class="num">25+class="num">10)))
      class="kw">return(false);
  if(!CreateAppliedPrice(class="num">10,class="num">22+class="num">10+class="num">3*(class="num">25+class="num">10)))
      class="kw">return(false);
class=class="str">"cmt">//--- Signal settings
  if(!CreateSetLabel(m_set_header[class="num">1],class="num">10,class="num">22+class="num">10+class="num">4*(class="num">25+class="num">10),"class="num">2.Signal Settings"))
      class="kw">return(false);
class=class="str">"cmt">//--- Condition settings
  if(!CreateRuleValue(class="num">130,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10)))
      class="kw">return(false);
  if(!CreateRule(class="num">10,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10)))
      class="kw">return(false);
class=class="str">"cmt">//--- Label display settings
  if(!CreateSetLabel(m_set_header[class="num">2],class="num">10,class="num">22+class="num">10+class="num">6*(class="num">25+class="num">10),"Label"))
      class="kw">return(false);
  if(!CreateButton2(m_label_button[class="num">0],"Value",class="num">100,class="num">22+class="num">7+class="num">6*(class="num">25+class="num">10)))
      class="kw">return(false);
  if(!CreateButton2(m_label_button[class="num">1],"Text",class="num">100+class="num">80,class="num">22+class="num">7+class="num">6*(class="num">25+class="num">10)))
      class="kw">return(false);
class=class="str">"cmt">//--- Label class="type">color display settings

「面板控件的坐标排布与周期选择」

这段初始化逻辑把自定义面板的控件按纵向网格逐个塞进界面,行距用 (25+10)=35 像素的固定步长算出来,从 y=22+10 开始往下排。 先看标签和文本框:第 7 行位置放了 Label Color 的颜色按钮(x=10),文本框落在 x=270、y=22+7+6*35=282 附近;随后从索引 1 起,颜色按钮和勾选框成对出现,分别管背景、边框、提示框、图片,直到第 11 行 Use Image 配图像滑块。 周期选择段单独拉了一个 header,位置在 y=22+10+12*35=462;之后 y 重算为 22+10+13*35=497,用 k 计数、i 从 0 跑到 20 共 21 个周期按钮,i==11 时 y 跳到 22+20+14*35=532 做换行。 任何一个 Create* 返回 false 就直接 return(false),面板建不全则整个 OnInit 失败。开 MT5 把这段粘进 EA 的面板初始化函数,改 (25+10) 这个步长就能压扁或拉高控件间距。

MQL5 / C++
if(!CreateColorButton(m_color_button[class="num">0],class="num">10,class="num">22+class="num">10+class="num">7*(class="num">25+class="num">10),"Label Color"))
      class="kw">return(false);
   if(!CreateTextBox(class="num">180+class="num">80+class="num">10,class="num">22+class="num">7+class="num">6*(class="num">25+class="num">10)))
      class="kw">return(false);
class=class="str">"cmt">//---
   if(!CreateColorButton(m_color_button[class="num">1],class="num">25,class="num">22+class="num">10+class="num">8*(class="num">25+class="num">10),""))
      class="kw">return(false);
   if(!CreateSetCheckBox(m_set_param[class="num">0],class="num">10,class="num">22+class="num">10+class="num">8*(class="num">25+class="num">10),"Use Background"))
      class="kw">return(false);
   if(!CreateColorButton(m_color_button[class="num">2],class="num">25,class="num">22+class="num">10+class="num">9*(class="num">25+class="num">10),""))
      class="kw">return(false);
   if(!CreateSetCheckBox(m_set_param[class="num">1],class="num">10,class="num">22+class="num">10+class="num">9*(class="num">25+class="num">10),"Use Border"))
      class="kw">return(false);
   if(!CreateColorButton(m_color_button[class="num">3],class="num">25,class="num">22+class="num">10+class="num">10*(class="num">25+class="num">10),""))
      class="kw">return(false);
   if(!CreateSetCheckBox(m_set_param[class="num">2],class="num">10,class="num">22+class="num">10+class="num">10*(class="num">25+class="num">10),"Use Tooltip"))
      class="kw">return(false);
   if(!CreateTooltipText(class="num">240,class="num">22+class="num">10+class="num">10*(class="num">25+class="num">10)))
      class="kw">return(false);
   if(!CreateSetCheckBox(m_set_param[class="num">3],class="num">10,class="num">22+class="num">10+class="num">11*(class="num">25+class="num">10),"Use Image"))
      class="kw">return(false);
   if(!CreateImageSlider(class="num">125,class="num">22+class="num">10+class="num">11*(class="num">25+class="num">10)))
      class="kw">return(false);
class=class="str">"cmt">//--- Timeframe selection
   if(!CreateSetLabel(m_set_header[class="num">4],class="num">10,class="num">22+class="num">10+class="num">12*(class="num">25+class="num">10),"Timeframes"))
      class="kw">return(false);
class=class="str">"cmt">//---
   y=class="num">22+class="num">10+class="num">13*(class="num">25+class="num">10);
   class="type">int k=class="num">0;
   for(class="type">int i=class="num">0; i<class="num">21; i++)
   {
      if(i==class="num">11)
      {
         y=class="num">22+class="num">20+class="num">14*(class="num">25+class="num">10);

◍ 多周期勾选后如何重建按钮阵列

面板里最多挂 21 个周期复选框,点「Add Signal」后会先弹窗,可用时调用 RebuildTimeframes() 按勾选状态重排周期按钮。 函数先跑两遍 0~20 的循环:第一遍数出被按下的复选框数量 cnt,用 ArrayResize(m_timeframes,cnt) 把周期数组缩到实际尺寸;第二遍把被按下的复选框 LabelText() 写进 m_timeframes,完成选中周期收集。 接下来把前 cnt 个周期按钮解锁并刷新文字,超出 cnt 的按钮直接 IsLocked(true) 锁死。这样界面上永远只露出当前选中的周期,多余按钮不会再被误触。 点击逻辑里还有一对互斥标签按钮:m_label_button[0] 按下时强制弹起 [1] 并隐藏文本框,[1] 按下时反过来弹起 [0] 并显示文本框。这种二选一写法在 MT5 自定义面板里很实用,复制时留意 Id() 比对必须精确。

MQL5 / C++
k=class="num">0;
}
if(!CreateTfButton(m_tf_button[i],class="num">40*k+class="num">10,y))
   class="kw">return(false);
k++;
}
class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::RebuildTimeframes(class="type">void)
{
class=class="str">"cmt">//--- Count the number of selected timeframes
   class="type">int cnt=class="num">0;
   for(class="type">int i=class="num">0; i<class="num">21; i++)
   {
      if(m_checkbox[i].IsPressed())
         cnt++;
   }
   ArrayResize(m_timeframes,cnt);
   cnt=class="num">0;
class=class="str">"cmt">//--- Remember the selected timeframe to the array
   for(class="type">int i=class="num">0; i<class="num">21; i++)
   {
      if(m_checkbox[i].IsPressed())
      {
         m_timeframes[cnt]=m_checkbox[i].LabelText();
         cnt++;
      }
   }
class=class="str">"cmt">//---
   for(class="type">int i=class="num">0; i<cnt; i++)
      m_tf_button[i].IsLocked(false);
class=class="str">"cmt">//---
   for(class="type">int i=class="num">0; i<cnt; i++)
   {
      m_tf_button[i].LabelText(m_timeframes[i]);
      m_tf_button[i].Update(true);
   }
   class=class="str">"cmt">//---
   for(class="type">int i=cnt; i<class="num">21; i++)
      m_tf_button[i].IsLocked(true);
}
         class=class="str">"cmt">//--- Click on the "Add Signal" button
         if(lparam==m_add_signal.Id())
         {
            m_set_window.OpenWindow();
            if(m_set_window.IsAvailable())
               RebuildTimeframes();
         }
class=class="str">"cmt">//---
         if(lparam==m_label_button[class="num">0].Id())
         {
            if(m_label_button[class="num">0].IsPressed())
            {
               m_label_button[class="num">1].IsPressed(false);
               m_label_button[class="num">1].Update(true);
            }
            m_text_box.Hide();
         }
         if(lparam==m_label_button[class="num">1].Id())
         {
            if(m_label_button[class="num">1].IsPressed())
            {
               m_label_button[class="num">0].IsPressed(false);
               m_label_button[class="num">0].Update(true);
            }
            m_text_box.Show();
         }
         class=class="str">"cmt">//---
         for(class="type">int i=class="num">0; i<class="num">4; i++)
         {
            if(lparam==m_color_button[i].Id())

复选框联动控件锁定的写法

在自定义面板里,复选框状态常常要反向控制其他控件的可用性。上面这段逻辑监听 CHARTEVENT_CUSTOM+ON_CLICK_CHECKBOX 事件,用 lparam 比对控件 Id 来判断点的是哪一个。 循环里对前 3 个参数控件做处理:若某个复选框被按下,则把对应的颜色按钮(索引 i+1)设为非锁定状态,也就是解锁;若第 3 个复选框(索引 2)处于按下,就显示提示文本,否则隐藏。注意数组越界——m_color_button 至少要有 4 个元素,因为 i 最大为 2 时访问的是 i+1=3。 循环外的第 4 个参数控件(索引 3)单独处理:它的按下状态决定图片滑动条 m_pictures_slider 是否锁定。这种把「勾选=解锁关联控件」写成 IsLocked(!IsPressed()) 的反向绑定,在 MT5 面板里能直接抄去用,开 MT5 新建 EA 面板验证即可。

MQL5 / C++
  {
      m_color_picker.ColorButtonPointer(m_color_button[i]);
      class="kw">return;
   }
class=class="str">"cmt">//--- Click on the checkbox
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_CHECKBOX)
   {
   class=class="str">"cmt">//---
   for(class="type">int i=class="num">0; i<class="num">3; i++)
   {
      if(lparam==m_set_param[i].Id())
      {
         m_color_button[i+class="num">1].IsLocked(!m_set_param[i].IsPressed());
         if(m_set_param[class="num">2].IsPressed())
            m_tooltip_text.Show();
         else
            m_tooltip_text.Hide();
      }
   }
   class=class="str">"cmt">//---
   if(lparam==m_set_param[class="num">3].Id())
      m_pictures_slider.IsLocked(!m_set_param[class="num">3].IsPressed());
   }

「把信号监控窗口搭起来」

开发收尾时要先给交易信号监控器立一个窗口骨架。思路很直接:用第一步挑的信号做行、第二步选的时间帧做列,再按实际元素数量自动撑开窗口尺寸,避免写死宽高导致标签挤成一团。 具体落地是在 CProgram 类里加两个 CTextLabel 数组,分别管时间帧和品种的文本标签,再补 ToMonitor() 与 AutoResize() 两个方法。m_total_signals 用来确认至少建了一个信号,m_symbols[] 存第一步的品种列表,这两个私有变量得在构造函数里把计数清零、数组备好。 代码里两个建标签的方法几乎对称:时间帧标签 XSize 写死 40,品种标签 XSize 给到 100,字体和背景色都从基类 m_base_font、m_background 继承,最后塞进 CWndContainer 的元素数组索引 0。差异只在宽度,品种名普遍比 EURUSD 这种长不了多少,但交叉盘名容易超 40 像素。 别漏了 OnEvent() 里“Create”按钮的单击事件,要点它才调 ToMonitor()。实测流程:第一步选 Crosses,第二步选 Senior,第三步点 Add Signal 关掉子窗再点 Create,监控器就能出来。外汇和贵金属波动剧烈,这种自建面板只是观察辅助,信号触发不代表入场稳赢,仓位仍需自担风险。

MQL5 / C++
  CTextLabel              m_timeframe_label[];
  CTextLabel              m_symbol_label[];
  class="type">bool                    CreateTimeframeLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text);
  class="type">bool                    CreateSymbolLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Creates the text label                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateTimeframeLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text)
{
class=class="str">"cmt">//--- Save the window pointer
  text_label.MainPointer(m_step_window);
class=class="str">"cmt">//---
  text_label.Font(m_base_font);
  text_label.FontSize(m_base_font_size);
  text_label.XSize(class="num">40);
  text_label.BackColor(m_background);
class=class="str">"cmt">//--- Create the button
  if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
    class="kw">return(false);
class=class="str">"cmt">//--- Add a pointer to element to the base
  CWndContainer::AddToElementsArray(class="num">0,text_label);
  class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Creates the text label                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateSymbolLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text)
{
class=class="str">"cmt">//--- Save the window pointer
  text_label.MainPointer(m_step_window);
class=class="str">"cmt">//---
  text_label.Font(m_base_font);
  text_label.FontSize(m_base_font_size);
  text_label.XSize(class="num">100);
  text_label.BackColor(m_background);
class=class="str">"cmt">//--- Create the button
  if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
    class="kw">return(false);
class=class="str">"cmt">//--- Add a pointer to element to the base
  CWndContainer::AddToElementsArray(class="num">0,text_label);
  class="kw">return(true);
}
  class="type">int                  m_total_signals;
  class="type">class="kw">string               m_symbols[];
  class="type">void                 ToMonitor(class="type">void);
  class="type">void                 AutoResize(const class="type">int x_size,const class="type">int y_size);
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 信号监控面板的动态拼装逻辑

把多品种多周期的信号做成可视监控,核心不在画图,而在窗口尺寸和控件坐标跟着数据量走。下面这段切换逻辑先判断信号总数,少于 1 个就弹窗撤回,避免空面板占用 MT5 主图资源。 进入监控态时,先隐藏「添加信号」相关的三步控件(m_add_signal、m_signal_header、前后翻页按钮),再把窗口标题改成 Signal Monitor 并强制重绘。品种标签从左上角 (5, 标题高+25) 起逐行排,行距锁死 25 像素;周期标签从 x=110 起,每个占 50 像素宽。 窗口大小由最后一列周期标签和最后一行品种标签的右下角决定:AutoResize 用 tf-1sy-1 取下标,算出的宽高直接改 m_step_window 的宽高。若你自建面板,把 110+50*i 和 25*i 这两个步长抽成宏,后期加周期或品种就不用改布局代码。 导航回退也在这套状态机里:当前步为 2 按返回就回 ToStep_1,为 3 则返回 ToStep_2;外汇与贵金属波动剧烈,这类自选监控仅作概率参考,实盘仍需自担高风险。

MQL5 / C++
class="type">void CProgram::ToMonitor(class="type">void)
{
class=class="str">"cmt">//--- Check if there is at least one signal
   if(m_total_signals<class="num">1)
   {
      MessageBox("No signals created!","Warning");
      class="kw">return;
   }
class=class="str">"cmt">//--- Hide Step class="num">3
   m_add_signal.Hide();
   m_signal_header.Hide();
   m_back_button.Hide();
   m_next_button.Hide();
class=class="str">"cmt">//--- Change window header
   m_step_window.LabelText("Signal Monitor");
   m_step_window.Update(true);
class=class="str">"cmt">//--- Symbols
   class="type">int sy=ArraySize(m_symbols);
   ArrayResize(m_symbol_label,sy);
   for(class="type">int i=class="num">0; i<sy; i++)
   {
      if(!CreateSymbolLabel(m_symbol_label[i],class="num">5,m_step_window.CaptionHeight()+class="num">25+i*class="num">25,m_symbols[i]))
         class="kw">return;
      m_symbol_label[i].Update(true);
   }
class=class="str">"cmt">//--- Timeframes
   class="type">int tf=ArraySize(m_timeframes);
   ArrayResize(m_timeframe_label,tf);
class=class="str">"cmt">//---
   for(class="type">int i=class="num">0; i<tf; i++)
   {
      if(!CreateTimeframeLabel(m_timeframe_label[i],class="num">110+class="num">50*i,m_step_window.CaptionHeight()+class="num">3,m_timeframes[i]))
         class="kw">return;
      m_timeframe_label[i].Update(true);
   }
class=class="str">"cmt">//--- Resize window
   AutoResize(m_timeframe_label[tf-class="num">1].XGap()+m_timeframe_label[tf-class="num">1].XSize()+class="num">5,m_symbol_label[sy-class="num">1].YGap()+m_symbol_label[sy-class="num">1].YSize()+class="num">5);
}

class="type">void CProgram::AutoResize(const class="type">int x_size,const class="type">int y_size)
{
   m_step_window.ChangeWindowWidth(x_size);
   m_step_window.ChangeWindowHeight(y_size);
}

状态机里的第三步跳监视

这段片段展示了一个多步流程里 step 1 到 3 的衔接逻辑:当 m_current_step 等于 1 时调用 ToStep_2(),等于 2 时调用 ToStep_3(),等于 3 时则进入 ToMonitor()。 注意第 3 个分支被标黄,说明这是从建仓流程切到持仓监控的关口。在 MT5 EA 里,把监控独立成函数能避免 OnTick 里堆满条件判断,回测时 step 3 的触发次数可直接从日志计数验证。 外汇与贵金属杠杆高,状态跳变若漏掉退出条件,可能让监控函数反复重入。复制下面代码到你的类方法里,改 m_current_step 初始值跑一遍就清楚跳转路径。

MQL5 / C++
   if(m_current_step==class="num">1)
      ToStep_2();
   class=class="str">"cmt">//--- Go to Step class="num">3
   else if(m_current_step==class="num">2)
      ToStep_3();
   class=class="str">"cmt">//--- Go to Monitor
   else if(m_current_step==class="num">3)
      ToMonitor();
   }

「把工具请下神坛」

整套监控器的附件已打包在 MQL5.zip 里,体积 1626.37 KB,解压后把 MQL5 文件夹直接丢进终端根目录就能跑。想定位根目录,在 MT5 里按 Ctrl+Shift+D,或右键调出菜单,比翻硬盘省事。 有读者在评论里问过:经纪商符号上千个,怎么只盯自己输入的几个,以及怎么加均线交叉这类自定义信号。作者当时回了句「未来部分」,意味着原版没封死扩展口,你得自己改代码里的符号遍历逻辑。 外汇和贵金属行情跳空频繁,这种多品种监控只是把信息噪声压薄,不替你过滤爆仓风险。拿去用之前,先在策略测试器里用历史数据跑一遍,确认报警触发和你肉眼看图表对得上再上实盘。

交给小布盯盘看多品种面板
这些多品种信号面板的诊断逻辑小布盯盘已内置,打开对应品种页即可看到聚合信号,无需自己从零搭 MQL5 界面。

常见问题

通常是头文件里只写了声明没写空实现,或 OnInit 调用的方法未在该类公开。补齐类内方法骨架再逐步填充即可消除错误。
顺序隔离能避免信号搜索时品种集未定导致空跑,也方便后续加载已保存分组,降低状态耦合。
字体名全局复用且极少变更,构造时定死可避免每建一个元素重复传参,也利于统一改皮肤。
若只需看多品种信号聚合与提醒,小布盯盘内置面板可覆盖大部分场景;自建 MQL5 应用更适深度定制策略与私有信号源。
至少含品种、周期、条件表达式、有效期与告警方式,存文件或全局缓存便于监控器轮询时反序列化。