监视多币种的交易信号(第四部分):增强功能并改善信号搜索系统(基础篇)
📘

监视多币种的交易信号(第四部分):增强功能并改善信号搜索系统(基础篇)

第 1/3 篇

◍ 从零搭一个多品种信号监视框架

在 MT5 里做跨品种信号监控,核心是先写一个自定义指标把单品种的入场逻辑封装进去,再用一个独立 EA 或脚本去轮询多个品种。原文作者 Alexander Fedosov 在 2020-07-16 发布的系列第四部分里,把这套架构拆成了指标生成、规则扩展、品种表化三步,实测可同时盯 3 783 次浏览量对应的社区关注度的那类多币组合。 自定义指标负责输出原始信号值,比如突破通道或均线交叉的布尔状态;EA 侧维护一个 Symbol 数组,用 SymbolName(i) 遍历市场报价窗口里的品种,逐个调用 iCustom 取指标缓冲。这样你开一个图表就能扫全市场,不用每张图挂一个指标。 原文把品种列表做成可编辑表格,并在监视器面板里直接改搜索规则(如阈值、时间框架),省掉了反复编译。外汇与贵金属杠杆高、滑点跳空频繁,这类自动扫描只解决‘看见信号’,不等于信号能稳定兑现,实盘前务必在策略测试器用真实点差回放。

「从基础信号系统到可视监控的演进」

前序第三部分里搭建的信号搜索系统,只依赖少数几个指标和一套简单规则,实盘里能跑但观察维度太薄。 有用户反馈,希望在交易监视器的可视区域直接看到信号状态,而不是切到日志或数据窗口去翻。这类改动不涉及策略内核,属于前端可用性补强。 本小节要落地的,正是把原有信号系统的输出接到监视器可视层,让交易者一眼能分辨当前品种是否触发条件。外汇与贵金属波动剧烈,这类可视辅助只降低观察成本,不替代风控。

把自定义指标塞进信号编辑窗口

在 MT5 里做信号工具,标准指标用腻了之后,真正能拉开差距的是接进 iCustom() 调用自己编译的 *.ex5。上一阶段的基类只认内置指标,这一节要改 SetWindow.mqh 里的 CreateIndicatorType(),把下拉列表扩成可选项。 先别散着改数组长度,用 #define SIZE 10 把列表元素数锁在单一宏里,以后加指标只动这一行。原 9 个内置名后面拼一个 "Custom",下拉就多出一格,代码里 pattern_names[SIZE] 末位填 "Custom" 即可。 iCustom() 要的是品种、周期、编译指标路径、逗号分隔参数。前两个从程序前两步设置里自动接,后两个得让用户在界面填。所以在 CProgram 基类加两个变量加一个方法,SetWindow.mqh 里用 CreateCustomEdit() 生成两个输入框,塞进 Selected indicator settings 段——图例 2 就是这两格刚出来的样子,此时还是灰的。 灰是因为可用性绑了下拉选择:只有选了 Custom,RebuildParameters() 才把路径和参数框点亮。顺手在 OnEvent() 下拉事件里加类型甄选检查,编译后图例 3 的框就随选随活。外汇和贵金属波动大、杠杆高,这种自定义信号组合只提高筛选维度,不保证胜率,实盘前务必在策略测试器跑历史数据。 下面这段是改完的下拉创建核心,逐行看:宏 SIZE 定 10 项;pattern_names 数组末位 "Custom";组合框宽 200 高 26,列表视项高 25、总高 200;循环把名写进组合框;默认选索引 1(即 CCI);最后建好归进元素数组。iCustom 原型也贴了,symbol/period/name 加变长参数,就是后面接自定义计算的入口。

MQL5 / C++
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">//---
class="macro">#define SIZE class="num">10
class=class="str">"cmt">//--- Array of the item values in the list view
   class="type">class="kw">string pattern_names[SIZE]=
   {
      "ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum","ADX","ADX Wilder","Custom"
   };
class=class="str">"cmt">//--- Set up 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(SIZE);
   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">25);
   m_indicator_type.GetListViewPointer().YSize(class="num">200);
class=class="str">"cmt">//--- Save the item values in the combobox list view
   for(class="type">int i=class="num">0; i<SIZE; i++)
      m_indicator_type.SetValue(i,pattern_names[i]);
class=class="str">"cmt">//--- Get the list view 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">1);
class=class="str">"cmt">//--- Create the control
   if(!m_indicator_type.CreateComboBox("Indicator Type",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">1,m_indicator_type);
   class="kw">return(true);
}
class="type">int  iCustom(
   class="type">class="kw">string           symbol,       class=class="str">"cmt">// symbol name
   ENUM_TIMEFRAMES  period,       class=class="str">"cmt">// period
   class="type">class="kw">string           name          class=class="str">"cmt">// folder/custom indicator_name
   ...                            class=class="str">"cmt">// the list of indicator class="kw">input parameters

◍ 自定义指标输入框的封装与联动

在 MT5 的 GUI 程序里,给自定义指标留两个文本输入框(路径和参数)比想象中啰嗦。上面这段把创建逻辑收进 CreateCustomEdit,框体固定宽 100、高 24,内部文本框宽 325、左边距 1,默认文字颜色用 clrSilver,边框 clrBlack,创建后直接 IsLocked(true) 锁住防误拖。 调用时路径框放在 x=240、y=22+10+2*(25+10)=102,参数框 y=22+10+3*(25+10)=137,提示语分别是“Enter the indicator path”和“Enter indicator parameters separated by commas”。这两个坐标差正好是一个控件占 25 高加 10 间距,改布局时直接调倍数就行。 下拉框选了指标类型会触发 CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM,lparam 命中 m_indicator_type.Id() 就跑 RebuildParameters。里面用 switch 重设标签:index 0 把周期框改名“ATR Period”并隐藏应用价格、锁死自定义框;index 1 改叫“CCI Period”并显示应用价格。外汇贵金属面板这类动态重构,最好先在策略测试器里切几次下拉验证控件不重叠。

MQL5 / C++
  );
  CTextEdit              m_custom_path;
  CTextEdit              m_custom_param;
  class="type">bool                  CreateCustomEdit(CTextEdit &text_edit,const class="type">int x_gap,const class="type">int y_gap,const class="type">class="kw">string default_text);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Input field for a custom indicator                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateCustomEdit(CTextEdit &text_edit,const class="type">int x_gap,const class="type">int y_gap,const class="type">class="kw">string default_text)
{
class=class="str">"cmt">//--- Save the pointer to the main control
  text_edit.MainPointer(m_set_window);
class=class="str">"cmt">//--- Properties
  text_edit.XSize(class="num">100);
  text_edit.YSize(class="num">24);
  text_edit.Font(m_base_font);
  text_edit.FontSize(m_base_font_size);
  text_edit.GetTextBoxPointer().AutoSelectionMode(true);
  text_edit.GetTextBoxPointer().XGap(class="num">1);
  text_edit.GetTextBoxPointer().XSize(class="num">325);
  text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
  text_edit.GetTextBoxPointer().DefaultText(default_text);
  text_edit.GetTextBoxPointer().BorderColor(clrBlack);
class=class="str">"cmt">//--- Create the control
  if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      class="kw">return(false);
  text_edit.IsLocked(true);
class=class="str">"cmt">//--- Add the object to the common array of the object groups
  CWndContainer::AddToElementsArray(class="num">1,text_edit);
  class="kw">return(true);
}
  if(!CreateCustomEdit(m_custom_path,class="num">240,class="num">22+class="num">10+class="num">2*(class="num">25+class="num">10),"Enter the indicator path"))
      class="kw">return(false);
  if(!CreateCustomEdit(m_custom_param,class="num">240,class="num">22+class="num">10+class="num">3*(class="num">25+class="num">10),"Enter indicator parameters separated by commas"))
      class="kw">return(false);
class=class="str">"cmt">//--- Selecting an item in the combobox drop-down list
  if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
  {
    class=class="str">"cmt">//--- Indicator type
    if(lparam==m_indicator_type.Id())
      RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::RebuildParameters(class="type">int index)
{
  class="kw">switch(index)
  {
  case  class="num">0:
    m_period_edit.LabelText("ATR Period");
    m_applied_price.Hide();
    m_custom_param.IsLocked(true);
    m_custom_path.IsLocked(true);
    break;
  case  class="num">1:
    m_period_edit.LabelText("CCI Period");
    m_applied_price.Show();
    m_custom_param.IsLocked(true);

「信号类型切换时的界面锁控逻辑」

在自定义信号面板里,根据指标类型切换 case 时,界面控件的可见性与可编辑状态是被硬编码锁死的。除 case 9(自定义指标缓冲区)外,m_custom_param 与 m_custom_path 全部 IsLocked(true),意味着选 RSI、ADX、Momentum 等内置振荡器时,路径与额外参数栏直接灰掉不可改。 只有 case 5(RSI)调用了 m_applied_price.Show(),其余如 DeMarker、Force Index、WPR、ADX、ADXW 均 Hide(),说明这些指标不暴露应用价格选项,而 RSI 允许选收盘价/典型价等。case 9 把两个锁都放开(IsLocked(false)),对应要手填自定义指标路径和缓冲区编号的场景。 添加信号按钮做了硬上限:m_total_signals 大于 4 就弹 MessageBox 提示“Maximum number of signals is 5”并 return,也就是面板最多挂 5 个信号。开 MT5 把这段塞进 EA 面板类里,改 case 9 的锁状态就能验证自定义指标录入是否真的放开。

MQL5 / C++
      m_custom_path.IsLocked(true);
      break;
   case  class="num">2:
      m_period_edit.LabelText("DeMarker Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  class="num">3:
      m_period_edit.LabelText("Force Index Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  class="num">4:
      m_period_edit.LabelText("WPR Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  class="num">5:
      m_period_edit.LabelText("RSI Period");
      m_applied_price.Show();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  class="num">6:
      m_period_edit.LabelText("Momentum Period");
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  class="num">7:
      m_period_edit.LabelText("ADX Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  class="num">8:
      m_period_edit.LabelText("ADXW Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  class="num">9:
      m_period_edit.LabelText("Buffer Number");
      m_applied_price.Hide();
      m_custom_param.IsLocked(false);
      m_custom_path.IsLocked(false);
      break;
   class="kw">default:
      m_period_edit.LabelText("Ind Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   }
   m_period_edit.Update(true);
}
         class=class="str">"cmt">//--- Add Signal button click event
         if(lparam==m_add_signal.Id())
         {
            if(m_total_signals>class="num">4)
            {
               MessageBox("Maximum number of signals is class="num">5","Signal Monitor");
               class="kw">return;
            }
            m_set_window.OpenWindow();
            RebuildParameters(class="num">1);
            m_number_signal=-class="num">1;
            RebuildTimeframes();

把信号按钮塞进面板的那几行

上面这段是给自定义指标面板动态加信号入口的收尾代码,跑在用户点选指标类型之后。 m_new_signal.LabelText("Add") 先把按钮文字写成 Add,Update(true) 强制重绘,否则 MT5 面板上经常看不到即时变化。 m_indicator_type.SelectItem(1) 把下拉框锁到第 1 项,紧跟的 GetButtonPointer().Update(true) 让关联按钮同步刷新。外汇与贵金属行情跳变快,这类 UI 不同步会让你误以为信号没触发,实盘前务必在策略测试器里点一遍验证。

MQL5 / C++
   m_new_signal.LabelText("Add");
   m_new_signal.Update(true);
   m_indicator_type.SelectItem(class="num">1);
   m_indicator_type.GetButtonPointer().Update(true);
}

常见问题

用多品种信号监视框架把品种和信号类型做成面板按钮,一键切换显示,不用逐个开图表。
可以,把指标封装进自定义输入框并做联动,就能在信号编辑窗口直接改参数出信号。
小布可内置多品种信号诊断,打开对应品种页自动跑框架逻辑,你只管看结论和决策。
加界面锁控逻辑,切换信号类型时临时锁住输入与按钮,避免参数被改乱。
检查按钮行高与坐标循环赋值,确保每行品种数固定、Y偏移按行累加即可对齐。