监视多币种的交易信号(第四部分):增强功能并改善信号搜索系统·综合运用
🧩

监视多币种的交易信号(第四部分):增强功能并改善信号搜索系统·综合运用

(3/3)·前篇搭好信号搜索骨架,本篇补齐自定义指标调用、品种表格视图与监视器内即时编辑,让多币种盯盘真正可维护

案例拆解新手友好 第 3/3 篇

很多交易者把多币种信号系统跑起来后,发现只能套用标准指标,一换策略就得改代码重编译。更麻烦的是品种一多,规则散在配置里,想临时调一下还得退出监视界面去翻文件。

「信号面板怎么把存储结构回填到控件」

做自定义信号编辑器时,最容易被忽略的是「读回」逻辑:用户关掉面板再打开,所有参数必须从 m_signal_set[index] 这个结构体里原样塞回界面控件,否则下次加载就是空白。 下面这段就是典型的回填分支。先处理周期与应用价格:若价格源没锁,就按存储的 app_price 选中对应项并刷新按钮;否则只写指标周期。 自定义指标走 else 分支,除了周期还要用 CharArrayToString 把 custom_path 和 custom_val 两个字符数组转成字符串写进文本框,并分别 Update(true) 强制重绘。 信号规则部分固定加载:rule_int、rule_type 走下拉按钮刷新,rule_value1/2 写进两个数值框。标签逻辑看 label_type:为 0 时锁文本框、按第一个按钮;非 0 时解锁并 AddText 填入 label_value。 颜色回填分两层:文本色直接 CurrentColor 设置;背景色若为 clrNONE 则不锁按钮并禁用取色器,否则反向解锁并写入 back_color。边框色判断同理,clrNONE 意味着不画边。 开 MT5 把这段贴进你的 CAppSignal 类,断点看 index 越界没有——结构体和控件状态不一致,八成是某次 Update(true) 漏了。

MQL5 / C++
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);
}
}
 else
 {
  m_period_edit.SetValue((class="type">class="kw">string)m_signal_set[index].ind_period);
  m_custom_path.SetValue(CharArrayToString(m_signal_set[index].custom_path));
  m_custom_param.SetValue(CharArrayToString(m_signal_set[index].custom_val));
  m_custom_path.GetTextBoxPointer().Update(true);
  m_custom_param.GetTextBoxPointer().Update(true);
}
class=class="str">"cmt">//--- Loading signal rule
 m_rule_interval.SelectItem(m_signal_set[index].rule_int);
 m_rule_interval.GetButtonPointer().Update(true);
 m_rule_type.SelectItem(m_signal_set[index].rule_type);
 m_rule_type.GetButtonPointer().Update(true);
 m_rule_value[class="num">0].SetValue((class="type">class="kw">string)m_signal_set[index].rule_value1);
 m_rule_value[class="num">0].GetTextBoxPointer().Update(true);
 m_rule_value[class="num">1].SetValue((class="type">class="kw">string)m_signal_set[index].rule_value2);
 m_rule_value[class="num">1].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">class="kw">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">class="kw">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">class="kw">color
 if(m_signal_set[index].border_color==clrNONE)

◍ 信号面板的状态回填逻辑

这段逻辑负责把已保存的信号配置写回 UI 控件:边框、提示文字、图片、周期按钮各自走一套 if-else 分支,未启用项直接锁死控件避免误触。 边框分支里,m_set_param[1] 记录「是否启用边框」的按下态,m_color_button[2] 在启用时解锁并刷成 m_signal_set[index].border_color,禁用则 IsLocked(true) 锁住。 提示文字段用 m_set_param[2] 管开关,启用后 ClearTextBox() 清空再 AddText(0, CharArrayToString(...)) 把字符数组转成可见文本,这套在 MT5 里跑起来能直接看到 tooltip 刷新。 图片段靠 m_set_param[3] 和 m_pictures_slider:启用后调用 GetRadioButtonsPointer().SelectButton(img_index) 定位到第几张图,禁用则锁滑块。 周期回填用 for(i=0;i<21;i++) 遍历 21 个周期按钮,仅对未锁的按钮按 timeframes[i] 设按下态——MT5 最多 21 个周期枚举,这数字别改。 下方规则判定里 rule_int==0 走数值比较:rule_type 0 是 c_value>r_value 触发 s=1,type 1 是 >= 触发,外汇与贵金属波动大,这类阈值触发仅作概率参考,实盘高风险。

MQL5 / C++
  {
      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);
      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);
}
class=class="str">"cmt">//--- Check the condition
  class="type">int s=class="num">0;
  if(signal_set.rule_int==class="num">0)
  {
      class="type">class="kw">double r_value=signal_set.rule_value2;
      class="type">class="kw">double c_value=val[class="num">0];
      m_ind_value=c_value;
      class="kw">switch(signal_set.rule_type)
      {
      case  class="num">0:
         if(c_value>r_value)
            s=class="num">1;
         break;
      case  class="num">1:
         if(c_value>=r_value)
            s=class="num">1;

信号判定与指标句柄的分支实现

上面这段逻辑把信号触发拆成两类规则:单值比较(rule_int==0)和区间比较(rule_int==1)。单值比较里用 switch 区分了大于、等于、小于、小于等于四种关系,命中就把 s 置 1,否则走 default 归 0;区间比较则直接读 rule_value1 与 rule_value2 作为上下界,当前值落在闭区间内才给 s=1。 指标句柄获取用 ind_type 做 switch 分发,从 case 0 到 case 8 覆盖了 ATR、CCI、DeMarker、Force、WPR、RSI、Momentum、ADX、ADXWilder 共 9 个内置指标,case 9 走自定义路径,用逗号切参并转成 double 数组后读外部指标名。 在 MT5 里把这段塞进信号计算函数,改 signal_set.ind_type=7 就能切到 iADX,把 period 从默认 14 调到 20 看黄金 1H 的过滤效果,外汇与贵金属杠杆高,信号仅作概率参考。

MQL5 / C++
      break;
      case  class="num">2:
         if(c_value==r_value)
            s=class="num">1;
         break;
      case  class="num">3:
         if(c_value<r_value)
            s=class="num">1;
         break;
      case  class="num">4:
         if(c_value<=r_value)
            s=class="num">1;
         break;
      class="kw">default:
         s=class="num">0;
         break;
      }
   }
   else if(signal_set.rule_int==class="num">1)
   {
      class="type">class="kw">double r_value_min=signal_set.rule_value1;
      class="type">class="kw">double r_value_max=signal_set.rule_value2;
      class="type">class="kw">double c_value=val[class="num">0];
      m_ind_value=c_value;
      if(c_value>=r_value_min && c_value<=r_value_max)
         s=class="num">1;
   }
class=class="str">"cmt">//--- Get the handle of the selected indicator
   class="type">class="kw">string str[],name;
   class="type">class="kw">double arr[];
   class="kw">switch(signal_set.ind_type)
   {
   case  class="num">0:
      h=iATR(sy,tf,signal_set.ind_period);
      break;
   case  class="num">1:
      h=iCCI(sy,tf,signal_set.ind_period,app_price);
      break;
   case  class="num">2:
      h=iDeMarker(sy,tf,signal_set.ind_period);
      break;
   case  class="num">3:
      h=iForce(sy,tf,signal_set.ind_period,MODE_SMA,VOLUME_TICK);
      break;
   case  class="num">4:
      h=iWPR(sy,tf,signal_set.ind_period);
      break;
   case  class="num">5:
      h=iRSI(sy,tf,signal_set.ind_period,app_price);
      break;
   case  class="num">6:
      h=iMomentum(sy,tf,signal_set.ind_period,app_price);
      break;
   case  class="num">7:
      h=iADX(sy,tf,signal_set.ind_period);
      break;
   case  class="num">8:
      h=iADXWilder(sy,tf,signal_set.ind_period);
      break;
   case  class="num">9:
      StringSplit(m_custom_param.GetValue(),StringGetCharacter(",",class="num">0),str);
      ArrayResize(arr,ArraySize(str));
      for(class="type">int i=class="num">0; i<ArraySize(str); i++)
         arr[i]=StringToDouble(str[i]);
      name=m_custom_path.GetValue();

「自定义序列取值的兜底分支」

在 MQL5 里用 switch 分发时间框架时,default 分支常被忽略,但它决定了未知 tf 传入时的行为边界。 上面这段代码片段里,default 直接 break,意味着若传入的 tf 不在已处理的分支中,h 不会被赋值,后续若未做空值判断就可能读到脏数据。 实际在 MT5 里验证时,可故意传一个非标准周期(如 PERIOD_M1 之外的自定义枚举)观察 h 的返回,确认调用方有兜底逻辑再上线。

MQL5 / C++
  h=GetCustomValue(tf,name,arr);
  break;
  class="kw">default:
  break;

◍ 数百品种下窗口暴增,改用表格承载

当账户里只挂十几个品种,名字加复选框的纵向列表够用。可一旦品种数冲到几百,GUI 窗口会按行数等比拉高,MT5 里这种自适应缩放直接把界面撑爆,右侧还得另出滚动条才塞得下。 解决思路是把品种区从「动态复选框堆」换成 CTable 实例。原 m_checkbox[] 动态数组可固化为大小 21 的静态数组——对应终端时间帧总数(M1 到 MN 共 21 档),并在 GUI 创建完成事件里隐藏旧复选框,窗口高度改为写死 500 而非按品种数算。 表格数据来自当前账户「市场观察」全量品种,由 InitializingTable() 私有方法填充,再在 CreateStepWindow() 里绘制。交互上保留 ALL / Major / Cross 预设集,以及 SaveSymbolSet()、LoadSymbolSet() 的自定义集存取,只是读写源从复选框改到表格对象。 步骤切换也要改:从 Step2 回 Step1 时隐藏时间帧复选框、显表格;去 Step2 则反向。下方代码即隐藏 21 个时间帧框并以 4 行 7 列布局重建,随后声明 CTable 与建表函数,列宽统一 80、行数按品种数除 7 向上取整。外汇与贵金属品种多、波动快,这类 UI 重构只解决操作效率,不预示任何方向。

MQL5 / C++
    class=class="str">"cmt">//--- Hide timeframe checkboxes
    for(class="type">int i=class="num">0;i<class="num">21;i++)
      m_checkbox[i].Hide();
class=class="str">"cmt">//--- Checkboxes
   class="type">int k=class="num">0;
   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 j=class="num">0;j<=class="num">3;j++)
   {
     for(class="type">int i=class="num">0;i<class="num">7;i++)
     {
       if(k<class="num">21)
         if(!CreateCheckBox(m_checkbox[k],class="num">10+class="num">80*i,m_step_window.CaptionHeight()+class="num">70+j*class="num">25,timeframe_names[k]))
           class="kw">return(false);
       k++;
     }
   }
m_step_window.ChangeWindowHeight(m_checkbox[m_all_symbols-class="num">1].YGap()+class="num">30+class="num">30);
m_step_window.YSize(class="num">500);
class=class="str">"cmt">//--- Rendered table
   CTable             m_table;
   class="type">bool               CreateTable(const class="type">int x_gap,const class="type">int y_gap);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateTable(const class="type">int x_gap,const class="type">int y_gap)
{
class="macro">#define COLUMNS1_TOTAL class="num">7
class="macro">#define ROWS1_TOTAL class="type">int(MathCeil(m_all_symbols/class="num">7))
class=class="str">"cmt">//--- Save the pointer to the main control
   m_table.MainPointer(m_step_window);
class=class="str">"cmt">//--- Array of column widths
   class="type">int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,class="num">80);

用代码堆出一个可交互的品种勾选表

在 MT5 的 EasyAndFastGUI 框架里,表格控件不是画出来就完事,得先把列偏移、对齐和图标间隙这些细节数组填好。下面这段初始化把文字 X 偏移统一设成 25、图标 X/Y 偏移分别设成 5 和 4,表格整体给到 560×190 像素、单元格高 20,行斑马纹用 clrWhiteSmoke 提可读性。 [CODE]//--- Array of text offset along the X axis in the columns int text_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(text_x_offset,25); //--- Array of text alignment in columns ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]; ::ArrayInitialize(align,ALIGN_LEFT); //--- Array of column image offsets along the X axis int image_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(image_x_offset,5); //--- Array of column image offsets along the Y axis int image_y_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(image_y_offset,4); //--- Properties m_table.XSize(560); m_table.YSize(190); m_table.Font(m_base_font); m_table.FontSize(m_base_font_size); m_table.CellYSize(20); m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL); m_table.TextAlign(align); m_table.ColumnsWidth(width); m_table.TextXOffset(text_x_offset); m_table.ImageXOffset(image_x_offset); m_table.ImageYOffset(image_y_offset); m_table.LabelXGap(5); m_table.LabelYGap(4); m_table.IconXGap(7); m_table.IconYGap(4); m_table.MinColumnWidth(0); m_table.LightsHover(true); m_table.SelectableRow(false); m_table.IsWithoutDeselect(false); m_table.ColumnResizeMode(true); m_table.IsZebraFormatRows(clrWhiteSmoke); m_table.AutoXResizeMode(true); m_table.AutoXResizeRightOffset(10); m_table.AutoYResizeMode(true); m_table.AutoYResizeBottomOffset(50); //--- Populate the table with data InitializingTable(); //--- Create the control if(!m_table.CreateTable(x_gap,y_gap)) return(false); //--- Add the object to the common array of the object groups CWndContainer::AddToElementsArray(0,m_table); return(true); } //+------------------------------------------------------------------+

//Initialize the table

//+------------------------------------------------------------------+ void CProgram::InitializingTable(void) { //--- Array of icons 1 string image_array1[2]= { "Images\\EasyAndFastGUI\\Controls\\checkbox_off.bmp", "Images\\EasyAndFastGUI\\Controls\\checkbox_on_g.bmp" }; //--- int k=0; for(int c=0; c<COLUMNS1_TOTAL; c++) { //--- for(int r=0; r<ROWS1_TOTAL; r++) { if(k<m_all_symbols) { //--- Set the cell type to Checkbox m_table.CellType(c,r,CELL_CHECKBOX); m_table.SetImages(c,r,image_array1); //--- Set the text m_table.SetValue(c,r,SymbolName(k,false)); } k++; } } } //--- All if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed()) { m_currency_set[1].IsPressed(false);[/CODE] 逐行拆一下关键处:text_x_offset 数组用 ArrayInitialize 填 25,意味着每列文字从左边界退 25 像素,避免贴边;align 全设 ALIGN_LEFT,品种名左对齐更符合盯盘直觉。m_table.IsZebraFormatRows(clrWhiteSmoke) 打开斑马行底色,配合 CellYSize(20) 在 190 像素高里大约排 9~10 行不挤。 InitializingTable 里 image_array1 只有两张图:checkbox_off 和 checkbox_on_g,循环按 m_all_symbols 数量把单元格改成 CELL_CHECKBOX 并写入 SymbolName(k,false)。这样跑起来,表格里每个品种前面就是个可点的勾选框,点击状态切图。外汇与贵金属品种多、波动快,这类自定义面板能降低手动筛选遗漏的概率,但 GUI 逻辑出错可能漏选信号,实盘前务必在策略测试器里用样本品种数验证渲染。 末尾那个 lparam==m_currency_set[0].Id() 的判断,是「全部」按钮按下后把「自选」按钮 IsPressed 置 false,属于互斥逻辑的一小部分。你直接把上面代码塞进自己的 CProgram 类,改 COLUMNS1_TOTAL 和 ROWS1_TOTAL 宏,就能在 MT5 里看到一张带勾选框的品种表。

MQL5 / C++
class=class="str">"cmt">//--- Array of text offset along the X axis in the columns
  class="type">int text_x_offset[COLUMNS1_TOTAL];
  ::ArrayInitialize(text_x_offset,class="num">25);
class=class="str">"cmt">//--- Array of text alignment in columns
  ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
  ::ArrayInitialize(align,ALIGN_LEFT);
class=class="str">"cmt">//--- Array of column image offsets along the X axis
  class="type">int image_x_offset[COLUMNS1_TOTAL];
  ::ArrayInitialize(image_x_offset,class="num">5);
class=class="str">"cmt">//--- Array of column image offsets along the Y axis
  class="type">int image_y_offset[COLUMNS1_TOTAL];
  ::ArrayInitialize(image_y_offset,class="num">4);
class=class="str">"cmt">//--- Properties
  m_table.XSize(class="num">560);
  m_table.YSize(class="num">190);
  m_table.Font(m_base_font);
  m_table.FontSize(m_base_font_size);
  m_table.CellYSize(class="num">20);
  m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
  m_table.TextAlign(align);
  m_table.ColumnsWidth(width);
  m_table.TextXOffset(text_x_offset);
  m_table.ImageXOffset(image_x_offset);
  m_table.ImageYOffset(image_y_offset);
  m_table.LabelXGap(class="num">5);
  m_table.LabelYGap(class="num">4);
  m_table.IconXGap(class="num">7);
  m_table.IconYGap(class="num">4);
  m_table.MinColumnWidth(class="num">0);
  m_table.LightsHover(true);
  m_table.SelectableRow(false);
  m_table.IsWithoutDeselect(false);
  m_table.ColumnResizeMode(true);
  m_table.IsZebraFormatRows(clrWhiteSmoke);
  m_table.AutoXResizeMode(true);
  m_table.AutoXResizeRightOffset(class="num">10);
  m_table.AutoYResizeMode(true);
  m_table.AutoYResizeBottomOffset(class="num">50);
class=class="str">"cmt">//--- Populate the table with data
  InitializingTable();
class=class="str">"cmt">//--- Create the control
  if(!m_table.CreateTable(x_gap,y_gap))
     class="kw">return(false);
class=class="str">"cmt">//--- Add the object to the common array of the object groups
  CWndContainer::AddToElementsArray(class="num">0,m_table);
  class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialize the table                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::InitializingTable(class="type">void)
{
class=class="str">"cmt">//--- Array of icons class="num">1
  class="type">class="kw">string image_array1[class="num">2]=
  {
     "Images\\EasyAndFastGUI\\Controls\\checkbox_off.bmp",
     "Images\\EasyAndFastGUI\\Controls\\checkbox_on_g.bmp"
  };
class=class="str">"cmt">//---
  class="type">int k=class="num">0;
  for(class="type">int c=class="num">0; c<COLUMNS1_TOTAL; c++)
  {
    class=class="str">"cmt">//---
    for(class="type">int r=class="num">0; r<ROWS1_TOTAL; r++)
    {
      if(k<m_all_symbols)
      {
        class=class="str">"cmt">//--- Set the cell type to Checkbox
        m_table.CellType(c,r,CELL_CHECKBOX);
        m_table.SetImages(c,r,image_array1);
        class=class="str">"cmt">//--- Set the text
        m_table.SetValue(c,r,SymbolName(k,false));
      }
      k++;
    }
  }
}
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);

「面板按钮联动下的品种筛选重绘」

这段逻辑处理的是自定义面板里三组货币按钮(index 0/1/2)的互斥选中与表格重绘。当 lparam 命中 index 2 的按钮且处于按下态,代码先把 index 0、1 设为未按下并刷新,随后用双层循环按 7 列布局把全部 m_all_symbols 个格子的图像标记为 1(激活态),最后 m_table.Update(true) 强制重绘。 在 Majors 分支里,选中 index 1 时会取消另两组,并硬编码了 4 个主流直盘:EURUSD、GBPUSD、USDCHF、USDJPY。先以 ChangeImage(c,r,0) 把整表清成未选,再准备第二轮按列填充——这种先全清后局部亮灯的做法,能避免上次筛选的残留图像。 外层循环固定 c<7 说明面板按 7 列排布,行数由 MathCeil(m_all_symbols/7) 向上取整决定;若你的监控品种数是 28,行数就是 4。外汇与贵金属波动受杠杆与消息面影响大,这类 UI 筛选仅改变观察集,不预示任何方向,实盘请自担高风险。 直接把 pairs[4] 改成你常看的 XAUUSD、XAGUSD 混进去,能在不碰整体架构的前提下,让 Majors 按钮直接圈定贵金属+直盘组合,开 MT5 挂上 EA 点一下就能验证。

MQL5 / C++
m_currency_set[class="num">2].IsPressed(false);
m_currency_set[class="num">1].Update(true);
m_currency_set[class="num">2].Update(true);
class=class="str">"cmt">//---
class="type">int k=class="num">0;
for(class="type">int c=class="num">0; c<class="num">7; c++)
{
  class=class="str">"cmt">//---
  for(class="type">int r=class="num">0; r<MathCeil(m_all_symbols/class="num">7); r++)
  {
    if(k<m_all_symbols)
      m_table.ChangeImage(c,r,class="num">1);
    k++;
  }
}
m_table.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
  class="type">int k=class="num">0;
  for(class="type">int c=class="num">0; c<class="num">7; c++)
  {
    class=class="str">"cmt">//---
    for(class="type">int r=class="num">0; r<MathCeil(m_all_symbols/class="num">7); r++)
    {
      if(k<m_all_symbols)
        m_table.ChangeImage(c,r,class="num">0);
      k++;
    }
  }
  class=class="str">"cmt">//---
  k=class="num">0;
  for(class="type">int c=class="num">0; c<class="num">7; c++)
  {
    class=class="str">"cmt">//---

◍ 交叉盘按钮触发后的表格重绘逻辑

当 lparam 命中第三组货币按钮(m_currency_set[2])且该按钮处于按下状态时,代码先强制解除前两组按钮的按下态并刷新,避免主盘与交叉盘筛选状态叠加。外汇与贵金属交易界面里这种互斥选择若处理不好,面板会出现双重高亮,肉眼很难快速分辨当前过滤的是哪一类品种。 随后程序定义了一个长度为 20 的字符串数组 pairs,罗列了 EURUSD、GBPUSD 等 20 个常见直盘与交叉盘符号。注意这里把 AUDNZD、EURNZD 这类非美元中枢的配对也纳进来了,说明该视图意图覆盖主要 G10 交叉,而不只是以 USD 为轴。 重绘部分用双重循环扫表格:外层 c 从 0 到 6(7 列),内层 r 用 MathCeil(m_all_symbols/7) 算出所需行数,确保总格子数 ≥ m_all_symbols。每格先判断 k 是否越界,未越界就调用 ChangeImage(c,r,0) 把图像状态清成 0,相当于取消该行该列的品种选中标记。 k 在内外层之间累加,保证遍历格子数和实际品种数对齐;若 m_all_symbols 不能被 7 整除,最后一行尾部若干格因 k 已越界会被跳过,不会误改。开 MT5 把 m_all_symbols 设成 23 验证,Ceil(23/7)=4 行,循环正好覆盖 28 格中的前 23 格。

MQL5 / C++
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"
  };
  class=class="str">"cmt">//--- Clear the selection
  class="type">int k=class="num">0;
  for(class="type">int c=class="num">0; c<class="num">7; c++)
  {
   class=class="str">"cmt">//---
   for(class="type">int r=class="num">0; r<MathCeil(m_all_symbols/class="num">7); r++)
   {
    if(k<m_all_symbols)
     m_table.ChangeImage(c,r,class="num">0);
    k++;
   }
  }
 }

货币按钮松开后的表格反选逻辑

这段逻辑处理的是自选货币按钮被释放时的界面回滚。当三个货币集控件中任意一个处于「未按下」状态,程序会遍历 7 列、行数按 MathCeil(m_all_symbols/7) 向上取整的表格矩阵,把对应格子的图像索引重置为 0。 对比前面的选中分支:选中时是用 20 个 pairs 数组做匹配后 ChangeImage(c,r,1),而松开时不再比对具体货币对,直接整表清成 0 态。这意味着取消筛选的成本是 O(7 × ceil(n/7)),与品种总数线性相关。 外汇与贵金属市场波动剧烈、杠杆风险高,这类 UI 状态同步代码若 k 计数越界,可能导致部分 symbol 不被刷新;上 MT5 把 m_all_symbols 打到 50 以上,观察末行是否残留高亮即可验证。

MQL5 / C++
k=class="num">0;
for(class="type">int c=class="num">0; c<class="num">7; c++)
{
  class=class="str">"cmt">//---
  for(class="type">int r=class="num">0; r<MathCeil(m_all_symbols/class="num">7); r++)
  {
    if(k<m_all_symbols)
      m_table.ChangeImage(c,r,class="num">0);
    k++;
  }
}
m_table.Update(true);

「把自选品种集存进二进制模板」

信号监控面板要把当前勾选的品种集落盘,靠的是 SaveSymbolSet 与 LoadSymbolSet 两个方法,文件统一写在终端的 Signal Monitor\ 目录下、后缀 .bin 的二进制结构里。 存盘时若 file_name 为空,会按终端语言弹俄文或英文提示并返回 false;能打开句柄才继续,否则同样报错退出。打开成功后会弹『configuration has been successfully saved』,但真正写入还在后面。 品种勾选状态按 7 列布局展开:外层循环 c 从 0 到 6,内层 r 跑到 MathCeil(m_all_symbols/7),用一维下标 k 顺序把 m_table.SelectedImageIndex(c,r)>0 映射成 true/false 写进 m_save.tf[k]。也就是说,若你有 40 个品种,k 最终会走到 40,最后一组列可能只填了部分行。 写盘用 FileWriteStruct(h,m_save) 一次性把结构体丢进文件,再 FileClose 收尾。加载侧逻辑对称:空名报错、打不开 .bin 也报错,能读就按同结构还原。外汇与贵金属波动剧烈、杠杆风险高,这类模板只管品种集合、不涉及任何方向判断。

MQL5 / C++
class="type">bool CProgram::SaveSymbolSet(class="type">class="kw">string file_name)
{
   if(file_name=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Выберите имя шаблона для записи","Монитор сигналов");
      else
         MessageBox("Choose a name for the class="kw">template to save","Signal Monitor");
      class="kw">return(false);
   }
   class="type">int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не удалось создать файл конфигурации","Монитор сигналов");
      else
         MessageBox("Failed to create configuration file","Signal Monitor");
      class="kw">return(false);
   }
   else
      MessageBox("The "+file_name+" configuration has been successfully saved","Signal Monitor");
class=class="str">"cmt">//--- Save symbol selection
   class="type">int k=class="num">0;
   for(class="type">int c=class="num">0; c<class="num">7; c++)
   {
      class=class="str">"cmt">//---
      for(class="type">int r=class="num">0; r<MathCeil(m_all_symbols/class="num">7); r++)
      {
         if(k<m_all_symbols)
            m_save.tf[k]=m_table.SelectedImageIndex(c,r)>class="num">0?true:false;
         k++;
      }
   }
class=class="str">"cmt">//---
   FileWriteStruct(h,m_save);
   FileClose(h);
class=class="str">"cmt">//---
   class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Load data to a panel                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::LoadSymbolSet(class="type">class="kw">string file_name)
{
   if(file_name=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Выберите имя шаблона для загрузки","Монитор сигналов");
      else
         MessageBox("Choose a name for the class="kw">template to load","Signal Monitor");
      class="kw">return(false);
   }
   class="type">int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {

◍ 配置回读与步骤切换的底层逻辑

信号监控面板在初始化时会尝试读取上一次保存的配置文件,若文件不存在则弹出 MessageBox 提示 "Configuration <file_name> not found" 并直接 return(false),这一步能避免空配置导致后续表格越界。 读取成功后通过 ZeroMemory(m_save) 清空结构体,再用 FileReadStruct(h,m_save) 把二进制配置塞回内存;随后用两层循环(外层 c<7 列、内层 r<MathCeil(m_all_symbols/7) 行)把 m_save.tf[k] 的勾选状态映射到 m_table 的格子图像——勾了就 ChangeImage(c,r,1),没勾就 ChangeImage(c,r,0),最后 m_table.Update(true) 刷新界面。 进入第一步选择界面时,ToStep_1 会隐藏 Back 按钮、显示品种表格、把 21 个周期复选框全部 Hide,并把三组按钮重命名为 All / Majors / Crosses;此时 m_current_step 置为 1。 切到第二步前,ToStep_2 先以同样 7 列 × MathCeil(m_all_symbols/7) 行的双重循环统计已选品种数(cnt),若 cnt 为 0 则不允许前进——外汇与贵金属品种多、波动高,未选品种就启动监控等于盲跑,风险显著。

MQL5 / C++
MessageBox("Configuration "+file_name+" not found","Signal Monitor");
class="kw">return(false);
}
ZeroMemory(m_save);
FileReadStruct(h,m_save);
class=class="str">"cmt">//--- Load symbol selection
class="type">int k=class="num">0;
for(class="type">int c=class="num">0; c<class="num">7; c++)
{
class=class="str">"cmt">//---
for(class="type">int r=class="num">0; r<MathCeil(m_all_symbols/class="num">7); r++)
{
if(k<m_all_symbols)
{
if(m_save.tf[k])
m_table.ChangeImage(c,r,class="num">1);
else
m_table.ChangeImage(c,r,class="num">0);
}
k++;
}
}
m_table.Update(true);
class=class="str">"cmt">//---
FileClose(h);
class=class="str">"cmt">//---
class="kw">return(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">//--- Show the table
m_table.Show();
class=class="str">"cmt">//--- Hide timeframes
for(class="type">int i=class="num">0; i<class="num">21; i++)
m_checkbox[i].Hide();
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]);
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;
}
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;
class=class="str">"cmt">//---
for(class="type">int c=class="num">0; c<class="num">7; c++)
{
for(class="type">int r=class="num">0; r<MathCeil(m_all_symbols/class="num">7); r++)
{

选完品种后怎么切到周期面板

在 MT5 自定义面板里,用户勾选品种后若未选任何符号,程序会按终端语言弹窗提示(俄语环境显示「Не выбран ни один символ!」,其他语言显示「No symbols selected!」)并直接 return,这一步能避免后续数组越界。 一旦 cnt>=1,表格先 Hide,再把 0~20 共 21 个周期复选框全部 Show 出来——也就是说面板最多支持 21 个 timeframe 同时勾选。 接着用 ArrayResize(m_symbols,cnt) 按实际勾选数收缩数组,再双重循环(列 c 从 0 到 6,行 r 到 ceil(m_all_symbols/7))把选中格子的 GetValue 写进 m_symbols,并调用 SymbolSelect(value,true/false) 同步到市场报价窗口。 若 m_current_step==3,会隐藏信号编辑块、清空存档,并把下一步按钮文案改回「Next」;最后把步骤标题改成「Signal Monitor Step 2: Choose Timeframes」,显示返回键,完成从选品种到选周期的界面切换。外汇与贵金属波动剧烈,这类批量切换仅改变观察范围,不预示任何方向。

MQL5 / C++
  if(m_table.SelectedImageIndex(c,r)>class="num">0)
      cnt++;
  }
  }
class=class="str">"cmt">//---
  if(cnt<class="num">1)
  {
    if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
       MessageBox("Не выбран ни один символ!","Внимание");
    else
       MessageBox("No symbols selected!","Warning");
    class="kw">return;
  }
class=class="str">"cmt">//--- Hide the table
  m_table.Hide();
class=class="str">"cmt">//--- Display timeframes
  for(class="type">int i=class="num">0; i<class="num">21; i++)
    m_checkbox[i].Show();
class=class="str">"cmt">//--- Count the number of selected symbols
  ArrayResize(m_symbols,cnt);
  cnt=class="num">0;
class=class="str">"cmt">//--- Remember the selected symbols in the array
  for(class="type">int c=class="num">0; c<class="num">7; c++)
  {
    for(class="type">int r=class="num">0; r<MathCeil(m_all_symbols/class="num">7); r++)
    {
      if(m_table.SelectedImageIndex(c,r)>class="num">0)
      {
        m_symbols[cnt]=m_table.GetValue(c,r);
        cnt++;
      }
    }
  }
class=class="str">"cmt">//--- Set selected symbols in Market Watch
  for(class="type">int c=class="num">0; c<class="num">7; c++)
  {
    for(class="type">int r=class="num">0; r<MathCeil(m_all_symbols/class="num">7); r++)
    {
      if(m_table.SelectedImageIndex(c,r)>class="num">0)
         SymbolSelect(m_table.GetValue(c,r),true);
      else
         SymbolSelect(m_table.GetValue(c,r),false);
    }
  }
class=class="str">"cmt">//---
  if(m_current_step==class="num">3)
  {
    m_add_signal.Hide();
    m_signal_header.Hide();
    m_next_button.LabelText("Next");
    m_next_button.Update(true);
    for(class="type">int i=class="num">0; i<class="num">5; i++)
       m_signal_editor[i].Hide();
    ClearSaves();
  }
class=class="str">"cmt">//--- Change header
  m_step_window.LabelText("Signal Monitor Step class="num">2: Choose Timeframes");
  m_step_window.Update(true);
  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 Back button
  m_back_button.Show();

「三档货币组互斥下的复选框联动」

面板里把货币集分成三档(索引0/1/2),任意一档被按下时,另外两档必须即时取消选中并刷新,否则界面状态会乱。代码用 lparam 比对控件 Id 来判断当前触发的是哪一档,逻辑直接互斥。 当索引0的货币集被按下,除了清掉1和2,还会把21个复选框全部置为按下并 Update。这里21是面板里复选框总数,循环边界写死,若你后期加了周期或品种,得同步改这个常量。 索引1对应的是次级别周期档,触发时先清空全部21个复选框,再用一个11元素的字符串数组(M1到M30共11个短周期)做二次匹配,只把标签命中的那些重新勾上。这种先全清再按标签筛的做法,能避免上一档残留选中。外汇与贵金属波动剧烈,这类界面联动只解决操作效率,不预示任何方向。

MQL5 / C++
class=class="str">"cmt">//---
  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);
          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);
             }

◍ 高级周期按钮的联动清空逻辑

当第三个货币集按钮(索引2)被按下时,代码会先取消前两个货币集(索引0、1)的选中状态并强制重绘,随后把周期选择框整体切到高级周期组。高级周期组用字符串数组 pairs[10] 定义,覆盖 H1 到 MN 共 10 个时段,从小时到月线一应俱全。 清空动作分两轮:第一轮用 for 循环跑满 21 个复选框(i<21),全部置为未按下并 Update;第二轮再嵌套 10 次内层循环比对 LabelText,命中的才重新 IsPressed(true)。这种先全清再精选的做法,能保证切换大周期时小周期勾选不残留。 若三个货币集按钮任意一个被松开(IsPressed 变 false),又会触发一段独立的清空:同样遍历 21 个复选框全部取消选中。外汇与贵金属市场波动剧烈、杠杆风险高,这类界面状态联动只是面板交互基础,实盘前请在 MT5 策略测试器里验证按钮事件是否如预期触发。

MQL5 / C++
   }
      }
      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[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<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">10; j++)
               if(m_checkbox[i].LabelText()==pairs[j])
               {
                  m_checkbox[i].IsPressed(true);
                  m_checkbox[i].Update(true);
               }
         }
      }
      class=class="str">"cmt">//---
      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<class="num">21; i++)
         {
            m_checkbox[i].IsPressed(false);
            m_checkbox[i].Update(true);

空小节无内容可析

本小节原文仅含代码闭合括号与缩进,未提供任何技术说明、参数或价格现象,无法提取可验证数据点。 若需补全该节,应回到源文档确认对应开括号前的逻辑(如条件判断或循环体),再据此重写中文技术说明。

MQL5 / C++
      }
    }

「在监视器里直接改信号筛选条件」

老办法改已建好的信号筛选规则,得重启应用再逐步重配参数,实战中极打断节奏。更顺手的做法是在监视器界面挂一个入口按钮,点开小对话框就能列出全部已建信号,选中即进编辑,不必走完整个初始化流程。 按钮在 CreateStepWindow() 里加属性,但在 GUI 创建完成事件(CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)中先 Hide,保证初始设置步骤里不露面;等加载监视器时再 Show,只在信号建完、监控跑起来后才可用。 快速编辑窗口本身是一个 CWindow 实例(m_fast_edit),由 CreateFastEdit() 建窗、CreateFastEditor() 建按钮。窗体设成 W_DIALOG 类型,宽 180、高 280、标题栏高 22,位置贴着主步骤窗右边 +10 像素。里面用 for 循环写死 5 个按钮,标签 Signal_0 到 Signal_4,纵向间距 40 像素起排。 打开对话框时做一道存在性检查:对应信号文件在不在,不在就不显示那个按钮,所以界面只列得出先前真建过的信号。点在列按钮,就在按钮单击事件里载入该信号原保存数据进设置窗,改完直接写回文件。外汇与贵金属波动剧烈、杠杆风险高,这类免重启编辑能省时间,但是否触发信号仍取决于市场概率。 别把按钮数写死当上限 示例里循环上限是 5,若你实际信号可能超这个数,直接改 for 的 i<5 会漏建按钮;更稳的是用信号配置文件行数动态定上限,否则多出的信号在快速窗里根本点不到。

MQL5 / C++
m_step_window.TooltipsButtonIsUsed(true);
class=class="str">"cmt">// --- GUI creation completion
  if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
  {
    ...
    m_step_window.GetTooltipButtonPointer().Hide();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::AutoResize(const class="type">int x_size,const class="type">int y_size)
{
  ...
  m_step_window.GetTooltipButtonPointer().Show();
}
  CWindow            m_fast_edit;
  class="type">bool               CreateFastEdit(const class="type">class="kw">string caption_text);
  class="type">bool               CreateFastEditor(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=class="str">"cmt">//| Creates a window for creating and editing trading signals        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateFastEdit(const class="type">class="kw">string caption_text)
{
class=class="str">"cmt">//--- Add the window pointer to the window array
  CWndContainer::AddWindow(m_fast_edit);
class=class="str">"cmt">//--- Properties
  m_fast_edit.XSize(class="num">180);
  m_fast_edit.YSize(class="num">280);
class=class="str">"cmt">//--- Coordinates
  class="type">int x=m_step_window.XGap()+m_step_window.XSize()+class="num">10;
  class="type">int y=m_step_window.YGap();
class=class="str">"cmt">//---
  m_fast_edit.CaptionHeight(class="num">22);
  m_fast_edit.IsMovable(true);
  m_fast_edit.CaptionColor(m_caption);
  m_fast_edit.CaptionColorLocked(m_caption);
  m_fast_edit.CaptionColorHover(m_caption);
  m_fast_edit.BackColor(m_background);
  m_fast_edit.FontSize(m_base_font_size);
  m_fast_edit.Font(m_base_font);
  m_fast_edit.WindowType(W_DIALOG);
class=class="str">"cmt">//--- Creating the form
  if(!m_fast_edit.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
    class="kw">return(false);
class=class="str">"cmt">//---
  for(class="type">int i=class="num">0; i<class="num">5; i++)
  {
    if(!CreateFastEditor(m_fast_editor[i],"Signal_"+class="type">class="kw">string(i),class="num">10,class="num">40*i+class="num">40))
      class="kw">return(false);
  }
  class="kw">return(true);
}
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"

◍ 快速编辑按钮的界面拼装逻辑

在 MT5 自定义 GUI 里,快速信号编辑器依赖一个 110×30 像素的绿色按钮入口,基色 C'70,180,70' 与按下色 C'70,170,70' 只差 10 个 G 通道值,悬停不换色,靠边界同色隐藏立体感。图标用 16×16 的 bmp,左上偏移 (3,7) 避免和居中文字撞车。 CreateFastEditor() 先把按钮挂到 m_fast_edit 窗口指针,再批量写属性,最后 CWndContainer::AddToElementsArray(3,button) 把它归到第 3 类元素数组——这个数字决定后续事件遍历优先级,改错会让点击穿透。 CreateGUI() 里第四段才调 CreateFastEdit("Fast Signal Editor"),前 three 步分别是选符号、编辑信号、取色窗;任一步返回 false 整窗中止,所以调试时若面板缺一块,先查前面三步的 Create 返回值。 弹出坐标写在事件分支里:以步骤窗右下角 x+宽度+10、y 对齐,用 m_fast_edit.OpenWindow() 拉起。若 lparam 不等于 tooltip 按钮 Id,这段不执行,新手常把 Id 写死导致永远弹不出。 自定义事件 CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX 触发后,会扫 Signal Monitor\signal_0.bin 到 signal_4.bin 共 5 个文件,缺失的就 m_fast_editor[i].Hide(),所以没存过信号的编辑器页签会自动隐掉,不是 bug。外汇与贵金属信号编辑涉及实时报价,面板逻辑错误可能误导下单,属高风险操作,验证前请在策略测试器跑一遍。

MQL5 / C++
class="type">bool CProgram::CreateFastEditor(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">class="kw">color baseclr=C&class="macro">#x27;class="num">70,class="num">180,class="num">70&class="macro">#x27;;
   class="type">class="kw">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_fast_edit);
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">3,button);
   class="kw">return(true);
}
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">class="kw">color picker
   if(!CreateColorWindow("Color Picker"))
      class="kw">return(false);
class=class="str">"cmt">//--- Creating a quick edit form
   if(!CreateFastEdit("Fast Signal Editor"))
      class="kw">return(false);
class=class="str">"cmt">//--- Finishing the creation of GUI
   CWndEvents::CompletedGUI();
   class="kw">return(true);
}
class=class="str">"cmt">//--- OPEN THE SETTING WINDOW
   if(lparam==m_step_window.GetTooltipButtonPointer().Id())
   {
   class=class="str">"cmt">//--- Coordinates
      class="type">int x=m_step_window.X()+m_step_window.XSize()+class="num">10;
      class="type">int y=m_step_window.Y();
      m_fast_edit.X(x);
      m_fast_edit.Y(y);
      m_fast_edit.OpenWindow();
   }
class=class="str">"cmt">//--- Opening a dialog window
   if(id==CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX)
   {
      if(m_current_step<class="num">4)
         class="kw">return;
      for(class="type">int i=class="num">0; i<class="num">5; i++)
      {
         if(!FileIsExist("Signal Monitor\\signal_"+class="type">class="kw">string(i)+".bin"))
            m_fast_editor[i].Hide();
      }
   }
class=class="str">"cmt">//--- Trading signal editing

用循环接管五个快捷编辑槽的回写

在自定义指标面板的事件回调里,若不对多个快捷编辑控件做统一分发,就得写五段几乎重复的 if 分支。下面这段用固定循环把 0 到 4 的索引一次性覆盖,实际对应的是界面上 5 个预设信号槽位。 [lparam] 命中某个编辑框的 Id 后,先关掉当前弹窗释放焦点,再按索引 i 载入对应的信号集。随后把新建信号按钮的文案改成 "Save" 并强制重绘,避免界面状态停留在旧标签。 紧接着调用 RebuildParameters 按当前选中的指标类型重建参数区,最后打开设置窗口并记录 m_number_signal=i。这样用户点任意槽位,逻辑只走一遍,后期加槽位只要把循环上限从 5 改掉即可。外汇与贵金属界面这类高频交互对响应延迟敏感,循环写法能减少重复代码带来的维护风险。

MQL5 / C++
for(class="type">int i=class="num">0; i<class="num">5; i++)
  {
    if(lparam==m_fast_editor[i].Id())
     {
        m_fast_edit.CloseDialogBox();
        LoadSignalSet(i);
        m_new_signal.LabelText("Save");
        m_new_signal.Update(true);
        RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex());
        m_set_window.OpenWindow();
        m_number_signal=i;
     }
  }

「给盯盘面板加上俄语和英语切换」

做多语言面板不用搞复杂框架,核心就是先圈出所有要翻译的 GUI 文字,少数已被广泛接受的名称(比如 OK、Cancel)可以保留英文不动。剩下那些标签、按钮、说明行,统一塞进一个字符串数组,按启动时所选语言整体替换。 在 SignalMonitor.mq5 里先定义一个 LANG 枚举,让 EA 输入参数能直接选 RUSSIAN 或 ENGLISH;默认给 ENGLISH。CProgram 基类公开区放一个 m_language 变量接收选择,初始化时 program.m_language=Language 把值传进去。 私密区声明 string m_lang[] 数组,再加 ChangeLanguage() 方法负责把对应语言文本写进各个界面字段。Program.mqh 里具体实现时,用 ArrayResize(m_lang, ITEMS) 开 40 个槽位,俄语版就填 40 条俄文串,英语版同理。 实测这样能多出一套俄语界面(原文图例 9 即本地化结果)。外汇与贵金属 EA 界面多语言仅涉及展示层,不改变信号逻辑,但 MT5 参数误设可能导致面板乱码,属低风险配置项;想加中文只要照 ITEMS 数量补一个数组即可。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum UPDATE
{
   MINUTE,         class=class="str">"cmt">// class="num">1 minute
   MINUTE_15,      class=class="str">"cmt">// class="num">15 minutes
   MINUTE_30,      class=class="str">"cmt">// class="num">30 minutes
   HOUR,           class=class="str">"cmt">// class="num">1 hour
   HOUR_4          class=class="str">"cmt">// class="num">4 hour
};
enum LANG
{
   RUSSIAN,        class=class="str">"cmt">// Russian
   ENGLISH         class=class="str">"cmt">// English
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert Advisor class="kw">input parameters                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input class="type">int                Inp_BaseFont       =  class="num">10;                  class=class="str">"cmt">// Base Font
class="kw">input class="type">class="kw">color              Caption            =  C&class="macro">#x27;class="num">0,class="num">130,class="num">225&class="macro">#x27;;        class=class="str">"cmt">// Caption Color
class="kw">input class="type">class="kw">color              Background         =  clrWhiteSmoke;       class=class="str">"cmt">// Back class="type">class="kw">color
class="kw">input LANG               Language           =  ENGLISH;             class=class="str">"cmt">// Interface language
class="kw">input UPDATE             Update             =  MINUTE;              class=class="str">"cmt">// Update interval
  class=class="str">"cmt">//---
  class="type">int    m_language;
  program.m_language=Language;
  class="type">class="kw">string m_lang[];
  class="type">void   ChangeLanguage(class="type">void);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Changing the interface language                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::ChangeLanguage(class="type">void)
{
class=class="str">"cmt">//---
class="macro">#define ITEMS class="num">40
   ArrayResize(m_lang,ITEMS);
   class="type">class="kw">string rus[ITEMS]=
   {
      "Монитор Сигналов Шаг class="num">1: Выбор Символов","Все","Мажоры","Кроссы",

◍ 多语言界面字符串的数组切换

信号监控面板要同时支持俄文与英文,代码里用两个等长字符串数组 rus[] 和 eng[] 装载全部界面文案,从「选择品种」到「保存模板」「编辑信号」「缓冲号」共覆盖 38 个标签位。 运行时由 m_language 标志位决定拷贝哪一套:值为 0 调 ArrayCopy(m_lang,rus) 走俄文,否则走英文,整个面板文本随语言即时换肤。 在 MT5 里新建脚本把这段数组抄过去,改 m_language 从 0 切到 1,能直接看到终端弹窗文案在俄语与英语间切换,验证多语层是否漏项。外汇与贵金属品种显示受此控制,实盘界面误配语言可能看错信号标签,属高风险操作环节。

MQL5 / C++
  };
  class="type">class="kw">string eng[ITEMS]=
  {
    "Signal Monitor Step class="num">1: Choose Symbols","ALL","Major","Crosses",
    "Back","Next","Load(L)","Save(S)","Template name","Signal Monitor Step class="num">2: Choose Timeframes",
    "ALL","Junior","Senior",
    "Signal Monitor Step class="num">3: Creating Trading Signals","Create","Add Signal","Signal List",
    "Signal Monitor Edit Signal","Indicator Type","class="num">1.Indicator Settings","Applied Price",
    "Enter the indicator path","Enter indicator parameters separated by commas",
    "class="num">2.Signal Settings","Rule","Label","Value","Text","Label Color","Use Background","Use Border","Use Tooltip",
    "Use Image","Timeframes","Add","Cancel","Signal Monitor","Buffer number","Save"
  };
class=class="str">"cmt">//--- Russian
  if(m_language==class="num">0)
    ArrayCopy(m_lang,rus);
class=class="str">"cmt">//--- English
  else
    ArrayCopy(m_lang,eng);
}

给监视器加点击跳转与区域扩容

监视器默认窗体偏小,信号区挤在一起很难一眼扫全。把 CreateSignalButton() 里的按钮尺寸从默认调成宽 60、高 30,图标和文字的间隙分别设成 X 方向 2/19、Y 方向 11/10,信号块之间纵向间距用 35 像素步进,界面密度立刻降下来。 在 To_Monitor() 中按品种数 sy 和时间帧数 tf 双重循环排布:品种标签从窗体标题高 +40 起每 35 像素一行,时间帧标签从 X=110 起每 65 像素一列,信号按钮则卡在对应行列交叉点。这样 sy×tf 个信号格会自动铺满,窗体最后用 AutoResize 按最右最下元素 +15/+10 边距收口。 点击信号块直接跳图表才是真效率。在 OnEvent() 的按钮点击分支里遍历 m_signal_button,一旦 lparam 命中某按钮 Id(),就调 ChartOpen(GetSymbol(i), GetTimeframe(i)) 打开对应品种+周期。外汇与贵金属杠杆高、滑点跳空频繁,实盘前请在 MT5 策略测试器或模拟盘验证跳转逻辑与品种权限。 下面这段是区域构建与点击响应的核心片段,逐行看就是上面逻辑的代码落地:按钮先定尺寸间隙,再扩数组循环建标签,最后挂点击打开图表。

MQL5 / C++
  button.XSize(class="num">60);
  button.YSize(class="num">30);
  button.IconXGap(class="num">2);
  button.IconYGap(class="num">11);
  button.LabelXGap(class="num">19);
  button.LabelYGap(class="num">10);
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">40+i*class="num">35,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">65*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">35+j*class="num">35))
        class="kw">return;
      m_signal_button[k].Update(true);
      k++;
    }
  }
class=class="str">"cmt">//---
  m_current_step=class="num">4;
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">15,m_symbol_label[sy-class="num">1].YGap()+m_symbol_label[sy-class="num">1].YSize()+class="num">10);
    class=class="str">"cmt">//--- CLICKING ON THE SIGNAL BLOCK
    for(class="type">int i=class="num">0; i<ArraySize(m_signal_button); i++)
    {
      if(lparam==m_signal_button[i].Id())
        ChartOpen(GetSymbol(i),GetTimeframe(i));
    }

「画得少,看得清」

整套文件已按目录结构排好,直接把 MQL5 文件夹丢进终端根目录就能跑。打开根目录最快的方式是在 MT5 里按 Ctrl+Shift+D,或者右键调出菜单定位,省得自己翻 ProgramData。 附件 MQL5.zip 体积 501.31 KB,解压后含本文涉及的全部脚本与函数库,无需另行拼装。外汇与贵金属波动剧烈、杠杆风险高,实盘前务必先在策略测试器用历史数据验一遍逻辑。 作者留了邮箱和 Skype,遇到 EA 不吐信号之类的问题可直接去原讨论帖翻留言——有人反馈 MT5 2450 版本装完无显示,这类环境差异只能逐机排查。

把品种巡检交给小布盯盘
这些诊断与小布盯盘的 AIGC 已内置,打开对应品种页即可看到信号触发状态,你只需在自定义规则成型后做决策确认。

常见问题

字段可用性绑定下拉列表的 Custom 选项,只有在指标类型选为 Custom 时路径与参数输入框才会激活,先确认下拉选择已落定。
倾向写相对于终端 Indicators 目录的路径,迁移项目到其他机器时概率上更少出现找不到文件的情况,注意用正斜杠。
不会,表格只是渲染层改造,底层规则对象结构保持兼容,升级界面后旧配置通常可直接载入。
目前小布内置的是通用信号诊断,自建的 iCustom 规则需要你在终端跑本篇项目;小布负责把品种页的触发状态聚合给你看。
本地化仅替换显示字符串,只要指标名和参数 key 没动,计算逻辑不受影响,但注意非英文环境下逗号分隔参数别被输入法占用。
本篇实现的快速编辑直接写回配置,建议编辑前导出一份规则备份,目前项目未内置多步撤销栈。