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

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

(2/3)· 继通用结构之后,本篇把配置初始化与界面元素一步步变成可操作的窗口与按钮

新手友好 第 2/3 篇
很多人以为多币种监控器写好逻辑就完事,结果界面卡在第一步连品种都选不出来。其实可视部分的初始化顺序错了,后面时间帧和信号添加全都会编译报错。先把窗口和输入参数理顺,才谈得上真正盯盘。

◍ 给面板按钮写一套可复用的创建函数

在 MT5 自定义面板里,按钮若每次手写属性会很啰嗦。下面这个函数把尺寸、字体、配色一次性封进 CButton 派生逻辑,调用方只需传文本和坐标间隙。 [CODE] bool CProgram::CreateButton1(CButton &button,string text,const int x_gap,const int y_gap) { color baseclr=C'70,180,70'; color pressed=C'70,170,70'; button.MainPointer(m_step_window); button.XSize(80); button.YSize(30); button.Font(m_base_font); button.FontSize(m_base_font_size); 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); if(!button.CreateButton(text,x_gap,y_gap)) return(false); CWndContainer::AddToElementsArray(0,button); return(true); } [/CODE] 逐行看:baseclr 用 C'70,180,70' 定义绿色底,pressed 只把 G 通道从 180 降到 170,按下去视觉微暗;XSize(80) 与 YSize(30) 锁死按钮像素盒,避免不同 DPI 下错位。 字体走 m_base_font(构造里设为 Trebuchet MS),文字色恒白且 hover/pressed 不变,配合 IsCenterText(true) 让“Load(L)”“Save(S)”这类短标居中。 实际挂按钮时,x_gap 用窗口宽减偏移算:例如 m_step_window.XSize()-2*(80+10) 把 Load 按钮推到距右边缘两个按钮位,Save 占最右一格,y 统一取标题栏高+10。外汇与贵金属图表加载此类面板属高风险环境,参数错乱可能误触下单逻辑,请在策略测试器先验证坐标。

MQL5 / C++
class="type">bool CProgram::CreateButton1(CButton &button,class="type">class="kw">string text,const class="type">int x_gap,const class="type">int y_gap)
{
  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;;
  button.MainPointer(m_step_window);
  button.XSize(class="num">80);
  button.YSize(class="num">30);
  button.Font(m_base_font);
  button.FontSize(m_base_font_size);
  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);
  if(!button.CreateButton(text,x_gap,y_gap))
    class="kw">return(false);
  CWndContainer::AddToElementsArray(class="num">0,button);
  class="kw">return(true);
}

「多选符号面板的网格排布与点击联动」

在 MT5 自定义面板里批量生成交易品种勾选框,核心不是逐个写坐标,而是用双层循环按 7 列折行。下面这段逻辑里,外层 j 控制行、内层 i 控制列,每格横向步进 80 像素、纵向 25 像素,X 起点 10、Y 起点从标题栏高度加 70 开始——改这几个数就能调面板密度。 CreateCheckBox 函数本身只负责单个控件的样式与挂载:绿色勾选框、未按下状态、基础字体,以及悬停标签色 C'55,160,250'(一种浅蓝)。注意它把指针塞进 m_step_window 主窗体,再经 CWndContainer::AddToElementsArray(0,checkbox) 归入元素数组,缺一步都可能导致界面不刷新。 循环里用 MathCeil(m_all_symbols/7) 算总行数,比如 23 个符号会占 4 行(23/7≈3.29 向上取整为 4)。末尾按最后一个勾选框的 YGap()+60 重设窗体高度,若你加了新的底部按钮(Back/Next 各 80 宽、贴右下),窗体高度不够就会遮住它们。 事件端用 CHARTEVENT_CUSTOM+ON_CLICK_BUTTON 拦截点击:当“全部”按钮被按下,代码强制把另外两组货币集的 IsPressed 置 false 并 Update,等于做了单选互斥。外汇与贵金属品种多、波动快,这类面板若用于实盘切换,请先在策略测试器里验证点击事件响应延迟,高风险操作务必小仓位试。

MQL5 / C++
class="type">bool CProgram::CreateCheckBox(CCheckBox &checkbox,const class="type">int x_gap,const class="type">int y_gap,const class="type">class="kw">string text)
{
class=class="str">"cmt">//--- Store the pointer to the main control
  checkbox.MainPointer(m_step_window);
class=class="str">"cmt">//--- Properties
  checkbox.GreenCheckBox(true);
  checkbox.IsPressed(false);
  checkbox.Font(m_base_font);
  checkbox.FontSize(m_base_font_size);
  checkbox.BackColor(m_background);
  checkbox.LabelColorHover(C&class="macro">#x27;class="num">55,class="num">160,class="num">250&class="macro">#x27;);
class=class="str">"cmt">//--- Create a control
  if(!checkbox.CreateCheckBox(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,checkbox);
  class="kw">return(true);
}
  class=class="str">"cmt">//--- Checkboxes
  class="type">int k=class="num">0;
  for(class="type">int j=class="num">0; j<=MathCeil(m_all_symbols/class="num">7); j++)
  {
    for(class="type">int i=class="num">0; i<class="num">7; i++)
    {
      if(k<m_all_symbols)
        if(!CreateCheckBox(m_checkbox[k],class="num">10+class="num">80*i,m_step_window.CaptionHeight()+class="num">70+j*class="num">25,SymbolName(k,false)))
          class="kw">return(false);
      k++;
    }
  }
  m_step_window.ChangeWindowHeight(m_checkbox[m_all_symbols-class="num">1].YGap()+class="num">30+class="num">30);
class=class="str">"cmt">//---
  if(!CreateButton1(m_back_button,"Back",m_step_window.XSize()-class="num">2*(class="num">80+class="num">10),m_step_window.YSize()-(class="num">30+class="num">10)))
    class="kw">return(false);
  if(!CreateButton1(m_next_button,"Next",m_step_window.XSize()-(class="num">80+class="num">10),m_step_window.YSize()-(class="num">30+class="num">10)))
    class="kw">return(false);
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::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)
{
class=class="str">"cmt">//--- Pressing the button event
  if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
  {
    class=class="str">"cmt">//--- All
    if(lparam==m_currency_set[class="num">0].Id() && m_currency_set[class="num">0].IsPressed())
    {
      m_currency_set[class="num">1].IsPressed(false);
      m_currency_set[class="num">2].IsPressed(false);
      m_currency_set[class="num">1].Update(true);

按货币集切换品种勾选的逻辑

面板里做货币集(All / Majors / Crosses)单选时,核心是在按钮事件中互斥置位并刷新对应 checkbox 状态。以 Majors 被按下为例,先取消 All 和 Crosses 两个集的按下态并 Update,保证界面三选一不串味。 随后用字符串数组锁定目标品种:pairs[4] = {"EURUSD","GBPUSD","USDCHF","USDJPY"},先把 m_all_symbols 个 checkbox 全清成未选,再二层循环比对 LabelText,命中就 IsPressed(true) 并 Update。这样 Majors 模式下只亮 4 个主流直盘。 Crosses 分支同理,只是 pairs 扩到 20 个(含 EURGBP、GBPJPY、CADCHF 等交叉),先关掉集 0 和集 1 再跑匹配。你在 MT5 里改 pairs 数组长度或成员,就能自定义任一组的默认品种池,外汇贵金属波动大,参数误配可能漏看信号。

MQL5 / C++
m_currency_set[class="num">2].Update(true);
class=class="str">"cmt">//---
for(class="type">int i=class="num">0;i<m_all_symbols; i++)
{
  m_checkbox[i].IsPressed(true);
  m_checkbox[i].Update(true);
}
}
class=class="str">"cmt">//--- Majors
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">4]= {"EURUSD","GBPUSD","USDCHF","USDJPY"};
  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">4;j++)
      if(m_checkbox[i].LabelText()==pairs[j])
      {
        m_checkbox[i].IsPressed(true);
        m_checkbox[i].Update(true);
      }
  }
}
class=class="str">"cmt">//--- Crosses
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[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">20]=
  {
    "EURUSD","GBPUSD","USDCHF","USDJPY","USDCAD","AUDUSD","AUDNZD","AUDCAD","AUDCHF","AUDJPY",
    "CHFJPY","EURGBP","EURAUD","EURCHF","EURJPY","EURCAD","EURNZD","GBPCHF","GBPJPY","CADCHF"
  };

◍ 多货币勾选与步骤回退的控件逻辑

在信号监控面板里,币种复选框的状态切换依赖两组循环:先全量取消已选,再按预设的 20 个货币对字符串比对标签并重新点亮。这种先清后选的写法能避免用户跨预设组切换时残留旧勾选。 [CODE] //--- Clear the selection for(int i=0; i<m_all_symbols; i++) { m_checkbox[i].IsPressed(false); m_checkbox[i].Update(true); } //--- for(int i=0; i<m_all_symbols; i++) { for(int j=0; j<20; j++) if(m_checkbox[i].LabelText()==pairs[j]) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } [/CODE] 上面第一段循环遍历 m_all_symbols 个复选框,调用 IsPressed(false) 取消按下并 Update(true) 立即重绘;第二段对每一个控件再嵌套 0 到 19 的 j 循环,若标签文本等于 pairs[j] 就置为按下。pairs 数组长度固定为 20,超出的预设对不会被自动选中。 当三个货币集按钮(m_currency_set[0~2])中任意一个被弹起时,同样触发全量清空,保证单选集与具体币种勾选互斥。导航上,若当前在步骤 2 点返回则跳 ToStep_1,步骤 3 则返回 ToStep_2,回退链路由 m_current_step 驱动。 CreateGUI 里在创建完第一步窗口后调用 m_back_button.Hide() 隐藏返回键,意味着首步不允许往回走;外汇与贵金属品种波动剧烈,这类界面逻辑仅降低误操作概率,不预示任何收益。

MQL5 / C++
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">20; j++)
    if(m_checkbox[i].LabelText()==pairs[j])
    {
      m_checkbox[i].IsPressed(true);
      m_checkbox[i].Update(true);
    }
}

if((lparam==m_currency_set[class="num">0].Id() && !m_currency_set[class="num">0].IsPressed()) ||
   (lparam==m_currency_set[class="num">1].Id() && !m_currency_set[class="num">1].IsPressed()) ||
   (lparam==m_currency_set[class="num">2].Id() && !m_currency_set[class="num">2].IsPressed())
   )
{
  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="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();
  class="kw">return(true);
}
class="kw">private:
  class="type">int m_symbol_total;
  class="type">int m_all_symbols;
  class="type">int m_current_step;
  class="type">void ToStep_1(class="type">void);
  class="type">void ToStep_2(class="type">void);
  class="type">void ToStep_3(class="type">void);
  if(lparam==m_back_button.Id())
  {
    if(m_current_step==class="num">2)
      ToStep_1();
    else if(m_current_step==class="num">3)
      ToStep_2();
  }

「向导按钮背后的步骤跳转逻辑」

在 MT5 自定义面板里,用「下一步」按钮驱动多步流程时,核心是一个 lparam 与按钮 Id 的比对。下面这段事件分发代码说明:当点击 m_next_button 且当前处于第 1 步,就调 ToStep_2();处于第 2 步则调 ToStep_3()。 [CODE] //--- Go to Step 2 if(lparam==m_next_button.Id()) { //--- Go to Step 2 if(m_current_step==1) ToStep_2(); //--- Go to Step 3 else if(m_current_step==2) ToStep_3(); } [/CODE] ToStep_2() 里藏着一个容易漏掉的防护:遍历 m_all_symbols 个复选框,统计被按下的数量 cnt,若 cnt<1 就弹 MessageBox 警告并 return,不往下走。 [CODE] void CProgram::ToStep_2(void) { //--- Check whether at least one symbol is selected int cnt=0; for(int i=0; i<m_all_symbols; i++) { if(m_checkbox[i].IsPressed()) cnt++; } if(cnt<1) { MessageBox("No symbols selected!","Warning"); return; } } [/CODE] ToStep_1() 与 ToStep_3() 在原文中函数体为空,仅留注释框架。实盘写向导时,若不在 ToStep_2 做符号校验,后续步骤拿到空品种列表可能直接抛错;开 MT5 把这段粘进 EA 面板类,改 m_all_symbols 长度即可验证弹窗触发条件。

MQL5 / C++
class=class="str">"cmt">//--- Go to Step class="num">2
if(lparam==m_next_button.Id())
{
  class=class="str">"cmt">//--- Go to Step class="num">2
  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">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Go to Step class="num">1                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::ToStep_1(class="type">void)
{
class=class="str">"cmt">//---
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Go to Step class="num">2                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::ToStep_2(class="type">void)
{
class=class="str">"cmt">//--- Check whether at least one symbol is selected
  class="type">int cnt=class="num">0;
  for(class="type">int i=class="num">0; i<m_all_symbols; i++)
  {
    if(m_checkbox[i].IsPressed())
      cnt++;
  }
  if(cnt<class="num">1)
  {
    MessageBox("No symbols selected!","Warning");
    class="kw">return;
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Move to Step class="num">3 class="num">3                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::ToStep_3(class="type">void)
{
class=class="str">"cmt">//---
}

第二步界面里把时间帧按钮亮出来

做信号监控面板的第二步,核心是把步骤一的品种勾选界面改写成时间帧选择界面。步骤一里已经建好的快速按钮、复选框和返回键直接复用,只是在 ToStep_2() 里改标签和显隐逻辑,不用重画对象。 代码里先把步骤一勾中的品种写进市场观察:循环 m_all_symbols 个复选框,按 IsPressed() 状态调 SymbolSelect(),这样 MT5 左侧只会留下你真想盯的品种,外汇和贵金属多品种同屏时这点很省内存。 时间帧集合用三组快捷按钮收口:All、Junior、Senior,分别对应 names[3] 数组。真正铺开的是 21 个具体周期复选框——从 M1 到 MN 共 21 档,代码里 timeframe_names[21] 写死,循环 Show() 出来。改完把 m_current_step 置 2,界面就切到第二步了。 在 OnEvent() 的按钮单击分支里,必须先用 m_current_step 判断当前处于哪步,否则步骤一和步骤二的快捷按钮 Id 会打架。Back 按钮绑到空的 ToStep_1() 上,编译后界面应如原文档例图 7 所示,若按钮重叠多半是 Update(true) 漏写。 外汇与贵金属波动受杠杆和消息面影响大,这类自选监控面板只解决「看哪些周期」的问题,信号真假还得你自己用价格行为过滤。

MQL5 / C++
class=class="str">"cmt">//--- Set selected symbols in Market Watch
  for(class="type">int i=class="num">0;i<m_all_symbols; i++)
  {
    if(m_checkbox[i].IsPressed())
      SymbolSelect(m_checkbox[i].LabelText(),true);
    else
      SymbolSelect(m_checkbox[i].LabelText(),false);
  }
class=class="str">"cmt">//--- Change header
  m_step_window.LabelText("Signal Monitor Step class="num">2: Choose Timeframes");
  m_step_window.Update(true);
class=class="str">"cmt">//--- Hide elements of Step class="num">1
  for(class="type">int i=class="num">0;i<m_all_symbols; i++)
  {
    m_checkbox[i].IsLocked(false);
    m_checkbox[i].IsPressed(false);
    m_checkbox[i].Hide();
  }
  class="type">class="kw">string names[class="num">3]= {"All","Junior","Senior"};
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].LabelText(names[i]);
    m_currency_set[i].IsPressed(false);
    if(m_current_step==class="num">3)
      m_currency_set[i].Show();
    m_currency_set[i].Update(true);
  }
class=class="str">"cmt">//--- Hide block for working with templates
  m_text_edit.Hide();
  m_load_button.Hide();
  m_save_button.Hide();
class=class="str">"cmt">//--- Show all timeframes
  class="type">class="kw">string timeframe_names[class="num">21]=
  {
    "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30",
    "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"
  };
  for(class="type">int i=class="num">0;i<class="num">21;i++)
  {
    m_checkbox[i].LabelText(timeframe_names[i]);
    m_checkbox[i].Show();
    m_checkbox[i].Update(true);
  }
class=class="str">"cmt">//--- Show Back button
  m_back_button.Show();
class=class="str">"cmt">//---
  m_current_step=class="num">2;
class=class="str">"cmt">//--- Step class="num">1
    if(m_current_step==class="num">1)
    {
     ...
    }
    class=class="str">"cmt">//--- Step class="num">2
    else if(m_current_step==class="num">2)
    {
      class=class="str">"cmt">//--- All
      if(lparam==m_currency_set[class="num">0].Id() && m_currency_set[class="num">0].IsPressed())
      {
        m_currency_set[class="num">1].IsPressed(false);
        m_currency_set[class="num">2].IsPressed(false);
让小布替你跑这套
这些多品种信号面板的诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到,不必自己从头搭界面。

常见问题

因为 CProgram 类里还没声明对应变量和方法,需要在 Program.mqh 中补上 m_base_font_size、m_background、m_caption 和 CreateGUI() 等定义才能通过。
可以,小布盯盘已内置多品种信号诊断面板,省去自己用 MQL5 写可视层的重复劳动,你只需专注品种和信号逻辑。
按结构设计的初始化顺序,第一步创建品种界面后才据此搜索交易信号,跳过会导致后续时间帧和信号步骤无基准。
它在 CProgram 类的构造函数中初始化,负责指定应用程序全局字体名称,配合字号参数控制显示效果。