监视多币种的交易信号(第三部分):引入搜索算法·进阶篇
🔍

监视多币种的交易信号(第三部分):引入搜索算法·进阶篇

(2/3)· 当品种和信号堆到几十个,手工翻看就是漏单之源,这篇把保存集与搜索逻辑一次讲透

案例拆解新手友好 第 2/3 篇
很多人把品种集每次重新勾选,却从没用过模板保存,结果换电脑或重开终端就全乱了。信号条件越加越多时,没有搜索算法就意味着你永远在手动翻页找触发。先把集管理和搜索跑顺,后面才有资格谈多品种纪律。

「信号集存盘与读取的结构细节」

保存信号集时,边框颜色仅在第二个参数按钮被按下时才写入当前色板第三号颜色,否则直接置为 clrNONE;背景色则始终清空为 clrNONE。这种写法意味着界面状态与配置文件严格一一对应,手动改面板不点按就不会落盘。 时间段写入用了一个 0 到 20 的 21 次循环,遍历全部周期按钮。若按钮未锁定且处于按下态,就把对应 timeframes[i] 标 true 并拷贝按钮标签文本,同时 tf 计数加一;否则写 false。 若循环结束 tf 仍小于 1,弹窗“No timeframes selected”并关闭文件返回 false,说明至少选一个周期才能存成功。该检查直接拦在 FileWriteStruct 之前。 读取侧用 FileOpen 以 FILE_READ|FILE_BIN 打开 Signal Monitor\signal_*.bin,句柄无效就报“Configuration not found”。随后 ZeroMemory 清空结构体再 FileReadStruct 载入,接着把 ind_type、ind_period、app_price、rule_type 依次推回界面控件并 Update(true),实现面板复原。

MQL5 / C++
m_signal_set[index].back_color=clrNONE;
class=class="str">"cmt">//--- Border class="type">color
if(m_set_param[class="num">1].IsPressed())
   m_signal_set[index].border_color=m_color_button[class="num">2].CurrentColor();
else
   m_signal_set[index].border_color=clrNONE;
class=class="str">"cmt">//--- Tooltip value
m_signal_set[index].tooltip=m_set_param[class="num">2].IsPressed();
if(m_signal_set[index].tooltip)
   StringToCharArray(m_tooltip_text.GetValue(),m_signal_set[index].tooltip_text);
class=class="str">"cmt">//--- Selected image
m_signal_set[index].image=m_set_param[class="num">3].IsPressed();
if(m_signal_set[index].image)
   m_signal_set[index].img_index=m_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex();
class=class="str">"cmt">//--- Selected timegrames
class="type">int tf=class="num">0;
for(class="type">int i=class="num">0; i<class="num">21; i++)
{
   if(!m_tf_button[i].IsLocked() && m_tf_button[i].IsPressed())
   {
       m_signal_set[index].timeframes[i]=true;
       StringToCharArray(m_tf_button[i].LabelText(),m_signal_set[index].tf_name[i].tf);
       tf++;
   }
   else
       m_signal_set[index].timeframes[i]=false;
}
class=class="str">"cmt">//---
if(tf<class="num">1)
{
   MessageBox("No timeframes selected","Signal Monitor");
   FileClose(h);
   class="kw">return(false);
}
class=class="str">"cmt">//---
FileWriteStruct(h,m_signal_set[index]);
FileClose(h);
Print("Configuration signal_"+class="type">class="kw">string(index)+" has been successfully saved");
class=class="str">"cmt">//---
class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::LoadSignalSet(class="type">int index)
{
   class="type">int h=FileOpen("Signal Monitor\\signal_"+class="type">class="kw">string(index)+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuration not found","Signal Monitor");
      class="kw">return(false);
   }
   ZeroMemory(m_signal_set[index]);
   FileReadStruct(h,m_signal_set[index]);
class=class="str">"cmt">//--- Loading indicator type
   m_indicator_type.SelectItem(m_signal_set[index].ind_type);
   RebuildParameters(m_signal_set[index].ind_type);
   m_indicator_type.GetButtonPointer().Update(true);
class=class="str">"cmt">//--- Loading indicator period
   m_period_edit.SetValue((class="type">class="kw">string)m_signal_set[index].ind_period);
   m_period_edit.GetTextBoxPointer().Update(true);
class=class="str">"cmt">//--- Loading applied price
   if(!m_applied_price.IsLocked())
   {
      m_applied_price.SelectItem(m_signal_set[index].app_price);
      m_applied_price.GetButtonPointer().Update(true);
   }
class=class="str">"cmt">//--- Loading signal rule
   m_rule_type.SelectItem(m_signal_set[index].rule_type);

信号集回填界面控件的判定分支

这段逻辑干的是把已保存的 signal_set 某一条记录,原样灌回面板控件。rule_type 和 rule_value 先刷新,文本用 (string) 强转,保证枚举或数值都能落进文本框。 label_type==0 走预设标签分支:button[0] 按下、button[1] 弹起,文本框锁死不可编辑;非 0 则反过来,文本框解锁并清空后用 CharArrayToString 把 label_value 字符数组转成可见文字,注意这里用的是索引 0 的 AddText,多行缓冲若没清干净可能叠字。 颜色与开关是三套平行判断:文字色直接赋值;背景色和边框色若等于 clrNONE,对应 set_param 不按、颜色按钮锁住,否则解锁并 CurrentColor 写入。tooltip 为假时锁定提示框,为真才放开——这套 0/非0 与 clrNONE 双基准,决定了面板“只读”还是“可改”的状态机。 开 MT5 把这段塞进你的 CAppDialog 派生类,断点看 m_signal_set[index] 各字段,能直接验证 label_type 切到 1 时文本框是否如约解锁。外汇与贵金属 GUI 回测无收益含义,界面状态错乱只会影响人工判单效率,实盘前务必手点一遍三种颜色开关。

MQL5 / C++
  m_rule_type.GetButtonPointer().Update(true);
  m_rule_value.SetValue((class="type">class="kw">string)m_signal_set[index].rule_value);
  m_rule_value.GetTextBoxPointer().Update(true);
class=class="str">"cmt">//--- Loading a text label
  if(m_signal_set[index].label_type==class="num">0)
  {
    m_label_button[class="num">0].IsPressed(true);
    m_label_button[class="num">0].Update(true);
    m_label_button[class="num">1].IsPressed(false);
    m_label_button[class="num">1].Update(true);
    m_text_box.IsLocked(true);
  }
  else
  {
    m_label_button[class="num">0].IsPressed(false);
    m_label_button[class="num">0].Update(true);
    m_label_button[class="num">1].IsPressed(true);
    m_label_button[class="num">1].Update(true);
    m_text_box.IsLocked(false);
    m_text_box.ClearTextBox();
    m_text_box.AddText(class="num">0,CharArrayToString(m_signal_set[index].label_value));
    m_text_box.Update(true);
  }
class=class="str">"cmt">//--- Loading the class="type">color of the text label
  m_color_button[class="num">0].CurrentColor(m_signal_set[index].label_color);
  m_color_button[class="num">0].Update(true);
class=class="str">"cmt">//--- Loading the background class="type">color
  if(m_signal_set[index].back_color==clrNONE)
  {
    m_set_param[class="num">0].IsPressed(false);
    m_set_param[class="num">0].Update(true);
    m_color_button[class="num">1].IsLocked(true);
    m_color_button[class="num">1].GetButtonPointer().Update(true);
  }
  else
  {
    m_set_param[class="num">0].IsPressed(true);
    m_set_param[class="num">0].Update(true);
    m_color_button[class="num">1].IsLocked(false);
    m_color_button[class="num">1].CurrentColor(m_signal_set[index].back_color);
    m_color_button[class="num">1].GetButtonPointer().Update(true);
  }
class=class="str">"cmt">//--- Loading the border class="type">color
  if(m_signal_set[index].border_color==clrNONE)
  {
    m_set_param[class="num">1].IsPressed(false);
    m_set_param[class="num">1].Update(true);
    m_color_button[class="num">2].IsLocked(true);
    m_color_button[class="num">2].GetButtonPointer().Update(true);
  }
  else
  {
    m_set_param[class="num">1].IsPressed(true);
    m_set_param[class="num">1].Update(true);
    m_color_button[class="num">2].IsLocked(false);
    m_color_button[class="num">2].CurrentColor(m_signal_set[index].border_color);
    m_color_button[class="num">2].GetButtonPointer().Update(true);
  }
class=class="str">"cmt">//--- Loading the tooltip value
  if(!m_signal_set[index].tooltip)
  {
    m_set_param[class="num">2].IsPressed(false);
    m_set_param[class="num">2].Update(true);
    m_tooltip_text.IsLocked(true);
    m_tooltip_text.Update(true);
  }
  else
  {
    m_set_param[class="num">2].IsPressed(true);

◍ 信号编辑器面板的回灌与按钮构造

从文件载入信号配置时,代码先把第 2 号参数控件的选中态刷新为 true,并解锁提示文本框、清空后写入对应信号的 tooltip 字符串。若当前信号不带图像(m_signal_set[index].image 为假),第 3 号参数按钮置为非按下、图片滑块锁定;反之则按下该按钮、解锁滑块并按 img_index 选中对应单选钮。 时间框架的回灌用了一个 0 到 20 的循环,共 21 个周期按钮——只要某按钮未被锁定,就按其 timeframes[i] 布尔值设按下态并刷新。注意循环上限写死 21,若你后期扩到 28 个周期(含 Year),不改为 i<28 就会漏载后 7 个。 文件句柄在尾部统一 FileClose(h) 并返回 true,说明载入流程无论界面元素多少都走一次性批刷新,而非增量重绘。 下方 CreateSignalEditor 负责生成带图标的编辑器按钮:尺寸固定 110×30,图标左距 3、上距 7,底色 C'70,180,70'、按下态 C'70,170,70',文字白色居中。它依赖 #resource 预编译指令把 settings_light.bmp 打进 ex5,路径为 Images\EasyAndFastGUI\Icons\bmp16\。改按钮宽度时记得同步 XSize 与图标间隙,否则 MT5 上可能文字被图标挤掉。 外汇与贵金属信号面板涉及高频参数切换,界面逻辑错误可能引发行情误读,实盘前务必在策略测试器里跑一遍载入回灌。

MQL5 / C++
m_set_param[class="num">2].Update(true);
m_tooltip_text.IsLocked(false);
m_tooltip_text.ClearTextBox();
m_tooltip_text.AddText(class="num">0,CharArrayToString(m_signal_set[index].tooltip_text));
m_tooltip_text.Update(true);
 }
class=class="str">"cmt">//--- Loading the image
 if(!m_signal_set[index].image)
 {
   m_set_param[class="num">3].IsPressed(false);
   m_set_param[class="num">3].Update(true);
   m_pictures_slider.IsLocked(true);
   m_pictures_slider.GetRadioButtonsPointer().Update(true);
 }
 else
 {
   m_set_param[class="num">3].IsPressed(true);
   m_set_param[class="num">3].Update(true);
   m_pictures_slider.IsLocked(false);
   m_pictures_slider.GetRadioButtonsPointer().SelectButton(m_signal_set[index].img_index);
   m_pictures_slider.GetRadioButtonsPointer().Update(true);
 }
class=class="str">"cmt">//--- Loading selected timeframes
 for(class="type">int i=class="num">0; i<class="num">21; i++)
 {
   if(!m_tf_button[i].IsLocked())
   {
     m_tf_button[i].IsPressed(m_signal_set[index].timeframes[i]);
     m_tf_button[i].Update(true);
   }
 }
class=class="str">"cmt">//---
 FileClose(h);
 class="kw">return(true);
}
CButton          m_signal_editor[class="num">5];
class="type">bool             CreateSignalEditor(CButton &button,class="type">class="kw">string text,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap);
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\\settings_light.bmp"
class="type">bool CProgram::CreateSignalEditor(CButton &button,class="type">class="kw">string text,class="kw">const class="type">int x_gap,class="kw">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 up 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\\settings_light.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 the control
  if(!button.CreateButton(text,x_gap,y_gap))
    class="kw">return(false);
class=class="str">"cmt">//--- Add the element pointer to the base
  CWndContainer::AddToElementsArray(class="num">0,button);
  class="kw">return(true);
}
class=class="str">"cmt">//---

「信号编辑器与 GUI 初始化的循环写法」

这段逻辑里,信号编辑器数组按固定上限 5 个做批量处理。循环从 i=0 到 i<5,每次尝试创建名为 Signal_0 到 Signal_4 的编辑器,纵向坐标用 40*i+90 算,也就是 90、130、170、210、250 像素——改这个步进值就能调编辑器间距。 CreateGUI 里先建三步窗口(选品种、编辑信号、拾色器),最后把 m_back_button、m_add_signal、m_signal_header 全隐藏,并默认按下 m_label_button[1]。末尾那段高亮循环把 5 个 m_signal_editor 一次性 Hide,说明初始状态不暴露编辑器界面。 ClearSaves 走的是文件级清理:对 i=0..4 调 FileDelete 删掉 Signal Monitor\\signal_i.bin,再把 m_total_signals 归零。想验证就开 MT5 把循环上限从 5 改成 3,看编辑器实例和 bin 文件是否同步减少。 事件分支里,点 m_add_signal 会开设置窗并标“Add”;点某个 m_signal_editor[i] 则 LoadSignalSet(i) 后标“Save”并把 m_number_signal 记成 i——这就是后面存读配置的索引来源。

MQL5 / C++
for(class="type">int i=class="num">0; i<class="num">5; i++)
  {
    if(!CreateSignalEditor(m_signal_editor[i],"Signal_"+class="type">class="kw">string(i),class="num">10,class="num">40*i+class="num">90))
      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. Symbol selection window.
  if(!CreateStepWindow("Signal Monitor Step class="num">1: Choose Symbols"))
    class="kw">return(false);
class=class="str">"cmt">//---
  if(!CreateSetWindow("Signal Monitor Edit Signal"))
    class="kw">return(false);
class=class="str">"cmt">//--- Creating form class="num">2 for the class="type">color picker
  if(!CreateColorWindow("Color Picker"))
    class="kw">return(false);
class=class="str">"cmt">//--- Finishing the creation of GUI
  CWndEvents::CompletedGUI();
  m_back_button.Hide();
  m_add_signal.Hide();
  m_signal_header.Hide();
  m_label_button[class="num">1].IsPressed(true);
  m_label_button[class="num">1].Update(true);
  for(class="type">int i=class="num">0; i<class="num">5; i++)
    m_signal_editor[i].Hide();
  class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::ClearSaves(class="type">void)
{
  for(class="type">int i=class="num">0; i<class="num">5; i++)
    FileDelete("Signal Monitor\\signal_"+class="type">class="kw">string(i)+".bin");
  m_total_signals=class="num">0;
  class="kw">return(true);
}
class=class="str">"cmt">//--- Add Signal button click event
  if(lparam==m_add_signal.Id())
  {
    m_set_window.OpenWindow();
    m_number_signal=-class="num">1;
    RebuildTimeframes();
    m_new_signal.LabelText("Add");
    m_new_signal.Update(true);
  }
class=class="str">"cmt">//---
  for(class="type">int i=class="num">0; i<class="num">5; i++)
  {
    if(lparam==m_signal_editor[i].Id())
    {
      LoadSignalSet(i);
      m_new_signal.LabelText("Save");
      m_new_signal.Update(true);
      m_set_window.OpenWindow();
      m_number_signal=i;
    }
  }

把信号搜索接进监视器

表格视图已经铺好:行是第一步挑的品种,列是第二步挑的时间帧。点 Create 之后 ToMonitor() 被调用,这一步要把编辑信号的按钮全隐藏掉,和第三步那些控件一样处理,否则跳到监视窗口时界面会叠层。 隐藏动作直接复用按钮对象的状态切换。下面这段是 StepWindow 里收尾隐藏的典型写法,循环里把 5 个编辑槽位逐个 Hide: 标示模块(indication block)用 CButton 数组承载,CreateSignalButton() 负责按坐标生成单个模块。尺寸固定为 40×20,双线边框,底色 C'220,225,235',标签左偏 19 像素——这些硬参数决定了监视器里信号格的视觉密度,想加密行列就得回来改 XSize / YSize。 SearchSignal() 是核心:先读连接信号设置的文件,拿到已配置信号总数,再按表格索引判断每个标示模块对应哪个品种+时间帧。CheckTimeframe() 做过滤器,不在选定时间帧内的直接跳过。命中后按信号配置给模块染色。 要让它自己跑,得在 SignalMonitor.mq5 开头加一个刷新间隔枚举,初始化里算好下次更新时间,OnTick() 里做时间差判断。编译后建一组品种挂个信号就能看到监视器活起来。外汇和贵金属波动剧烈,这类自动标信号只是辅助提示,信号出现不代表方向确定性,实盘仍需自担高风险。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//--- Hide Step class="num">3</span>
&nbsp;&nbsp; m_add_signal.Hide();
&nbsp;&nbsp; m_signal_header.Hide();
&nbsp;&nbsp; m_back_button.Hide();
&nbsp;&nbsp; m_next_button.Hide();
&nbsp;&nbsp; <span style="background-class="type">color:rgb(class="num">255, class="num">235, class="num">85);"><span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>; i&lt;<span class="number">class="num">5</span>; i++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_signal_editor[i].Hide();</span>
<span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CreateSignalButton(CButton &amp;button,<span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> x_gap,<span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> y_gap);
CButton&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_signal_button[];
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Creates an indication block&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">bool</span> CProgram::CreateSignalButton(CButton &amp;button,<span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> x_gap,<span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> y_gap)
{
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">class="type">color</span> baseclr=<span class="class="type">class="kw">string">C&class="macro">#x27;class="num">220,class="num">225,class="num">235&class="macro">#x27;</span>;
<span class="comment">class=class="str">"cmt">//--- Save the window pointer</span>
&nbsp;&nbsp; button.MainPointer(m_step_window);
<span class="comment">class=class="str">"cmt">//--- Set up properties before creation</span>
&nbsp;&nbsp; button.TwoState(<span class="macro">false</span>);
&nbsp;&nbsp; button.XSize(<span class="number">class="num">40</span>);
&nbsp;&nbsp; button.YSize(<span class="number">class="num">20</span>);
&nbsp;&nbsp; button.IconXGap(<span class="number">class="num">2</span>);
&nbsp;&nbsp; button.IconYGap(button.YSize()/<span class="number">class="num">2</span>-<span class="number">class="num">8</span>);
&nbsp;&nbsp; button.LabelXGap(<span class="number">class="num">19</span>);
&nbsp;&nbsp; button.LabelYGap(<span class="number">class="num">2</span>);
&nbsp;&nbsp; button.FontSize(m_base_font_size);
&nbsp;&nbsp; button.BackColor(baseclr);
&nbsp;&nbsp; button.BackColorHover(baseclr);
&nbsp;&nbsp; button.BackColorPressed(baseclr);
&nbsp;&nbsp; button.BorderColor(baseclr);
&nbsp;&nbsp; button.BorderColorHover(baseclr);
&nbsp;&nbsp; button.BorderColorPressed(baseclr);
&nbsp;&nbsp; button.LabelColor(<span class="macro">clrBlack</span>);
&nbsp;&nbsp; button.LabelColorPressed(<span class="macro">clrSlateGray</span>);
&nbsp;&nbsp; button.IconFile(<span class="class="type">class="kw">string">""</span>);
&nbsp;&nbsp; button.IconFileLocked(<span class="class="type">class="kw">string">""</span>);
&nbsp;&nbsp; button.IsDoubleBorder(<span class="macro">true</span>);
<span class="comment">class=class="str">"cmt">//--- Create the control</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!button.CreateButton(<span class="class="type">class="kw">string">""</span>,x_gap-button.XSize()/<span class="number">class="num">2</span>,y_gap))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(<span class="macro">false</span>);
<span class="comment">class=class="str">"cmt">//--- Add the element pointer to the base</span>
&nbsp;&nbsp; CWndContainer::AddToElementsArray(<span class="number">class="num">0</span>,button);
&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(<span class="macro">true</span>);
}
<span class="comment">class=class="str">"cmt">//--- Timeframes</span>
&nbsp;&nbsp; <span class="keyword">class="type">int</span> tf=<span class="functions">ArraySize</span>(m_timeframes);
&nbsp;&nbsp; <span class="functions">ArrayResize</span>(m_timeframe_label,tf);
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>; i&lt;tf; i++)
&nbsp;&nbsp; {

◍ 信号面板的按钮布局与配色接口

信号区块的绘制用两层循环铺开:外层按交易品种行数 sy 走,内层按周期数 tf 走,每个格子生成一个信号按钮。按钮横坐标取对应周期标签的中心点(XGap 加 XSize 的一半),纵坐标从窗口标题栏下 25 像素起,每行再叠 25 像素,因此总行高固定为 25*j,布局密度直接由 sy 和 tf 的乘积决定。 数组 m_signal_button 先用 ArrayResize(m_signal_button, sy*tf) 一次性扩到 sy*tf 个,k 从 0 累加穿透两层循环,保证不越界。窗口尺寸最后靠 AutoResize 收口,右边界取最后一个周期标签的右沿加 5,下边界取最后一个品种标签的下沿加 5,这套算法在 4 周期 × 8 品种下会生成 32 个按钮对象。 SetBorderColor、SetLabel、SetBackground 三个方法都针对 m_signal_button[index] 同时改常态、悬停、按下三态属性然后 Update(true)。比如 SetBackground 只动 BackColor 和 BackColorHover,没有 Pressed 态——意味着按钮被按住时背景不会变,如果你要做按压反馈得自己补一行 BorderColorPressed 之外的 BackColorPressed。 外汇与贵金属市场杠杆高、滑点随机,这类面板只是信号可视化辅助,任何颜色标记都不构成方向建议,实盘前请在 MT5 策略测试器里把 sy、tf 改小先验证对象数是否爆栈。

MQL5 / C++
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">//-- Signal blocks
   class="type">int k=class="num">0;
   ArrayResize(m_signal_button,sy*tf);
   for(class="type">int j=class="num">0; j<sy; j++)
   {
      for(class="type">int i=class="num">0; i<tf; i++)
      {
         if(!CreateSignalButton(m_signal_button[k],m_timeframe_label[i].XGap()+m_timeframe_label[i].XSize()/class="num">2,m_step_window.CaptionHeight()+class="num">25+j*class="num">25))
            class="kw">return;
         m_signal_button[k].Update(true);
         k++;
      }
   }
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      SetBorderColor(class="type">int index, class="type">color clr);
   class="type">void      SetLabel(class="type">int index, class="type">class="kw">string text,class="type">color clr=clrBlack);
   class="type">void      SetIcon(class="type">int index,class="type">int number);
   class="type">void      SetBackground(class="type">int index,class="type">color clr);
   class="type">void      SetTooltip(class="type">int index,class="type">class="kw">string text="\n");
class="type">void CProgram::SetBorderColor(class="type">int index, class="type">color clr)
{
   m_signal_button[index].BorderColor(clr);
   m_signal_button[index].BorderColorHover(clr);
   m_signal_button[index].BorderColorPressed(clr);
   m_signal_button[index].Update(true);
}
class="type">void CProgram::SetLabel(class="type">int index, class="type">class="kw">string text,class="type">color clr=clrBlack)
{
   m_signal_button[index].LabelColor(clr);
   m_signal_button[index].LabelColorHover(clr);
   m_signal_button[index].LabelColorPressed(clr);
   m_signal_button[index].LabelText(text);
   m_signal_button[index].Update(true);
}
class="type">void CProgram::SetBackground(class="type">int index,class="type">color clr)
{
   m_signal_button[index].BackColor(clr);
   m_signal_button[index].BackColorHover(clr);
   m_signal_button[index].Update(true);
}
把重复劳动交给小布
这些诊断和小布盯盘的 AIGC 已内置,打开对应品种页即可看到信号触发状态,你只需在关键集上做决策,不必自己轮询十几个窗口。

常见问题

模板以文件形式写在终端公共或私人数据目录,按文中 m_save 结构序列化;迁移前复制对应文件即可,重新加载用 L 热键。
直盘和交叉盘分组在基础项目里写死,本篇聚焦加载逻辑;要改集合成员需改初始化列表并重新编译。
时间帧与价格偏移类参数直接决定扫描粒度,过于密集的条件会拖慢轮询,建议先宽后窄。
目前小布盯盘内置多品种监控视图,可手动映射集名称;后续若开放模板文件接口,就能一键同步文中导出的集。
本篇落在 OnEvent 与按钮交互层,搜索部分在下节展开,倾向事件触发以避免界面卡死。