监视多币种的交易信号(第五部分):复合信号·综合运用
(3/3)· 当趋势指标在横盘里狂发假信号,用 AND/OR 把多个简单信号捆成复合信号才是收口办法
◍ 按钮态与复合信号锁定的代码落点
在 MT5 自定义面板里,按钮的视觉状态靠 BackColor / BorderColor / LabelColor 三族方法区分普通、悬停、按下、锁定四种态。上面这段把锁定态统一设成 clrWhiteSmoke,文字也同步变灰,用户一眼就能看出哪些信号被禁用了。 CreateButton(" — ",x_gap,y_gap) 成功后才用 CWndContainer::AddToElementsArray(4,button) 把指针存进元素库,参数 4 是控件类别标识,漏掉这步后续循环就找不到对象。 简单信号按两行排布:前 5 个横坐标 10+115*i、纵坐标 90;第 6 个起纵坐标 +45,横坐标回到 10+115*(i-5)。115 和 45 是像素间距,改这两个数就能压缩或拉宽面板。 LoadForCompositeSignal 里先遍历把所有 m_simple_signal 锁死并 Update,再统计可见且类型为 "C" 或 "B" 的信号数。若 cnt<2 直接返回 false,说明复合信号至少要有 2 个合格源;通过的才按顺序解锁,cnt 归零重排。外汇与贵金属指标组合高风险,这段代码只解决界面逻辑,不预示任何方向。
button.BackColorHover(baseclr); button.BackColorPressed(pressed); button.BackColorLocked(clrWhiteSmoke); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(pressed); button.BorderColorLocked(clrWhiteSmoke); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.LabelColorLocked(clrWhiteSmoke); button.IsCenterText(true); button.TwoState(true); class=class="str">"cmt">//--- Create a control element if(!button.CreateButton(" — ",x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add a pointer to the element to the database CWndContainer::AddToElementsArray(class="num">4,button); class="kw">return(true); } .... class=class="str">"cmt">//--- Signal name if(!CreateCSignalName(m_c_signal_name,class="num">10,class="num">40)) class="kw">return(false); class=class="str">"cmt">//--- Create a simple signal selection list for(class="type">int i=class="num">0; i<SIMPLE_SIGNALS; i++) { if(i<class="num">5) { if(!CreateSimpleSignal(m_simple_signal[i],class="num">10+class="num">115*i,class="num">90)) class="kw">return(false); } else { if(!CreateSimpleSignal(m_simple_signal[i],class="num">10+class="num">115*(i-class="num">5),class="num">90+class="num">45)) class="kw">return(false); } } ... class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::LoadForCompositeSignal(class="type">void) { class="type">int cnt=class="num">0; class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<SIMPLE_SIGNALS; i++) { m_simple_signal[i].IsLocked(true); m_simple_signal[i].Update(true); } class=class="str">"cmt">//--- Check if there are at least two available signals with the required attributes for(class="type">int i=class="num">0; i<SIMPLE_SIGNALS; i++) if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B")) cnt++; class=class="str">"cmt">//--- if(cnt<class="num">2) class="kw">return(false); else cnt=class="num">0; class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<SIMPLE_SIGNALS; i++) { if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B")) { m_simple_signal[cnt].IsLocked(false); cnt++; } } class=class="str">"cmt">//--- cnt=class="num">0; for(class="type">int i=class="num">0; i<SIMPLE_SIGNALS; i++) {
复合信号编辑器的触发与槽位构造
点击「添加复合信号」按钮(m_add_signal[1])时,程序先跑 LoadForCompositeSignal()。该函数返回 false 表示当前简单信号不足两个,此时弹窗提示:俄语环境显示「Необходимо минимум два простых сигнала…」,其他语言显示「You need at least two simple signals to create a composite!」。外汇与贵金属策略里,复合信号至少要 2 个简单信号叠加,否则逻辑无意义。 若加载成功,编辑器窗口会被定位到按钮下方 40 像素处(Y 轴 m_add_signal[1].Y()+40),随后清空名称输入框并解除所有简单信号的选中态。这段循环遍历 SIMPLE_SIGNALS 个按钮,把 IsPressed 设回 false 并重绘,避免上一次编辑残留选中高亮。 CreateRuleSlot() 负责生成规则槽按钮:固定宽 110、高 40,底色用 clrLightSteelBlue,文字色全状态白。你在 MT5 自定义面板里改复合信号 UI 时,直接调 XSize(110) / YSize(40) 这两个值就能改触发区大小,改 baseclr 可换主题色。 别把简单信号数量当摆设 少于两个简单信号时编辑器根本不会开,调试时若发现点按钮无反应,先打印 LoadForCompositeSignal() 的返回值,而不是去查 OnClick 事件绑定。
if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B")) { m_simple_signal[cnt].LabelText(m_signal_label[i].LabelText()); m_simple_signal[cnt].Update(true); cnt++; } } class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::OpenCompositeSignalEditor(class="type">long lparam) { if(lparam==m_add_signal[class="num">1].Id()) { if(!LoadForCompositeSignal()) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Необходимо минимум два простых сигнала для создания составного!","Внимание"); else MessageBox("You need at least two simple signals to create a composite!","Warning"); } else { m_composite_edit.X(m_add_signal[class="num">1].X()); m_composite_edit.Y(m_add_signal[class="num">1].Y()+class="num">40); m_composite_edit.OpenWindow(); class=class="str">"cmt">//--- m_c_signal_name.SetValue(""); m_c_signal_name.Update(true); class=class="str">"cmt">//--- Clear signal selection for(class="type">int i=class="num">0; i<SIMPLE_SIGNALS; i++) { m_simple_signal[i].IsPressed(false); m_simple_signal[i].Update(true); } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateRuleSlot(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">//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 baseclr=clrLightSteelBlue; class=class="str">"cmt">//--- Store the window pointer button.MainPointer(m_composite_edit); class=class="str">"cmt">//--- Set properties before creation button.XSize(class="num">110); button.YSize(class="num">40); button.Font(m_base_font); button.FontSize(m_base_font_size); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(baseclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(baseclr); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite);
「复合信号面板的按钮与逻辑槽拼装」
在 MT5 自定义面板里拼复合信号,核心是把规则槽和逻辑算子作为独立控件逐个 Create。上面这段用 CButton 派生类做规则选择器,基准色写死成 C'75,190,240',尺寸锁 40×40,三种状态(普通/悬停/按下)背板和边框同色,文字恒白且居中,避免状态切换时的视觉跳变。 CreateRuleSelector() 只接 x_gap、y_gap 两个坐标参数,内部调 CreateButton("AND",x_gap,y_gap) 生成标签为 AND 的按钮,随后 CWndContainer::AddToElementsArray(4,button) 把指针塞进元素库第 4 类——改这个索引号就可能让控件脱离命中检测。 实际布局在主程序里用循环铺:3 个规则槽横排,x 从 67 起每 173 像素一个;2 个逻辑算子插在 189、362 像素处,y 都取 int(90+45*2.5)=202。外汇与贵金属行情跳空频繁,这类面板若绑实时信号,须自测高波动时段控件重绘是否会丢事件。 LogicSwitch() 入口只比 lparam 和 m_rule_selector[i].Id(),说明点击响应靠控件 ID 路由。开 MT5 把这段抄进 EA 的面板类,改 baseclr 或 XSize 就能直观看到算子块对版面的挤压。
button.IsCenterText(true); class=class="str">"cmt">//--- Create a control element if(!button.CreateButton(text,x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add a pointer to the element to the database CWndContainer::AddToElementsArray(class="num">4,button); class="kw">return(true); } class="type">bool CProgram::CreateRuleSelector(CButton &button,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">75,class="num">190,class="num">240&class="macro">#x27;; class=class="str">"cmt">//--- Store the window pointer button.MainPointer(m_composite_edit); class=class="str">"cmt">//--- Set properties before creation button.XSize(class="num">40); button.YSize(class="num">40); button.Font(m_base_font); button.FontSize(m_base_font_size); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(baseclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(baseclr); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); class=class="str">"cmt">//--- Create a control element if(!button.CreateButton("AND",x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add a pointer to the element to the database CWndContainer::AddToElementsArray(class="num">4,button); class="kw">return(true); } class=class="str">"cmt">//--- Header Rule if(!CreateSetLabel1(m_c_set_header[class="num">0],class="num">10,class="num">90+class="num">45*class="num">2,"class="num">1."+m_lang[class="num">24])) class="kw">return(false); class=class="str">"cmt">//--- Create slots for creating a composite signal for(class="type">int i=class="num">0; i<class="num">3; i++) if(!CreateRuleSlot(m_rule_element[i],"Slot "+class="type">class="kw">string(i+class="num">1),class="num">57+class="num">10+class="num">173*i,class="type">int(class="num">90+class="num">45*class="num">2.5))) class="kw">return(false); class=class="str">"cmt">//--- Create logical operators for(class="type">int i=class="num">0; i<class="num">2; i++) if(!CreateRuleSelector(m_rule_selector[i],class="num">189+class="num">173*i,class="type">int(class="num">90+class="num">45*class="num">2.5))) class="kw">return(false); class="type">void CProgram::LogicSwitch(class="type">long lparam) { for(class="type">int i=class="num">0; i<class="num">2; i++) { if(lparam==m_rule_selector[i].Id()) {
◍ 逻辑开关与信号槽的衔接实现
在组合信号面板里,OR/AND 的切换不是靠重画整个界面,而是遍历规则选择器按钮、比对当前标签文字后就地改标签和配色。 下面这段是按钮循环里的核心分支:标签为 "OR" 时改成 "(OR)" 并涂番茄红,标签为 "(OR)" 时翻成 "AND" 用浅蓝(C'75,190,240'),标签为 "AND" 时切回 "OR" 配橙红。这样用户每点一次,逻辑关系就在三种状态里轮转。 [CODE] if(m_rule_selector[i].LabelText()=="OR") SetButtonParam(m_rule_selector[i],"(OR)",clrTomato); else if(m_rule_selector[i].LabelText()=="(OR)") SetButtonParam(m_rule_selector[i],"AND",C'75,190,240'); else if(m_rule_selector[i].LabelText()=="AND") SetButtonParam(m_rule_selector[i],"OR",clrOrangeRed); [/CODE] 信号选择函数 SignalSelection 负责把简单信号填进规则槽。它先比对 lparam 与按钮 Id,若信号处于按下态,就从 0 到 2 找第一个背景色为 clrLightSteelBlue 的空槽(最多 3 个槽),写入信号名并染成 clrDodgerBlue 后 break;若信号被取消选中,则反向把槽内同名文字恢复成 "Slot "+序号、背景退回 clrLightSteelBlue。 [CODE] void CProgram::SignalSelection(long lparam) { for(int i=0; i<SIMPLE_SIGNALS; i++) { if(lparam==m_simple_signal[i].Id()) { if(m_simple_signal[i].IsPressed()) { for(int j=0; j<3; j++) { if(m_rule_element[j].BackColor()==clrLightSteelBlue) { SetButtonParam(m_rule_element[j],m_simple_signal[i].LabelText(),clrDodgerBlue); break; } } } else { for(int j=0; j<3; j++) { if(m_rule_element[j].LabelText()==m_simple_signal[i].LabelText()) { SetButtonParam(m_rule_element[j],"Slot "+string(j+1),clrLightSteelBlue); break; } } } } } } [/CODE] 槽位数硬编码为 3,说明一套复合信号最多挂三个基础信号;如果你在 MT5 里改 SIMPLE_SIGNALS 或把 j<3 放宽,界面数组也得同步扩,否则会越界。外汇与贵金属波动剧烈,这类面板只解决规则拼装,不等于信号胜率有保障,实盘前请用策略测试器跑历史数据验证。
if(m_rule_selector[i].LabelText()=="OR") SetButtonParam(m_rule_selector[i],"(OR)",clrTomato); else if(m_rule_selector[i].LabelText()=="(OR)") SetButtonParam(m_rule_selector[i],"AND",C&class="macro">#x27;class="num">75,class="num">190,class="num">240&class="macro">#x27;); else if(m_rule_selector[i].LabelText()=="AND") SetButtonParam(m_rule_selector[i],"OR",clrOrangeRed); class="type">void CProgram::SignalSelection(class="type">long lparam) { for(class="type">int i=class="num">0; i<SIMPLE_SIGNALS; i++) { if(lparam==m_simple_signal[i].Id()) { if(m_simple_signal[i].IsPressed()) { for(class="type">int j=class="num">0; j<class="num">3; j++) { if(m_rule_element[j].BackColor()==clrLightSteelBlue) { SetButtonParam(m_rule_element[j],m_simple_signal[i].LabelText(),clrDodgerBlue); break; } } } else { for(class="type">int j=class="num">0; j<class="num">3; j++) { if(m_rule_element[j].LabelText()==m_simple_signal[i].LabelText()) { SetButtonParam(m_rule_element[j],"Slot "+class="type">class="kw">string(j+class="num">1),clrLightSteelBlue); break; } } } } } }
信号保存前的几道硬校验
在把复合信号写盘之前,这段代码先卡了四道关:重名、文件句柄、序号上限、名称格式。任何一道没过就弹 MessageBox 并 return(false),配置文件也不会落盘。 第一道用 CheckCSignalNames() 查重,first_save 为真且名字已存在时直接拦下;这里还按 TERMINAL_LANGUAGE 判断俄语环境给俄文提示,否则给英文。第二道 FileOpen() 打开 Signal Monitor\\c_signal_<index>.bin,模式是 FILE_WRITE|FILE_BIN,返回 INVALID_HANDLE 说明目录或权限有问题。 第三道看 index>COMPOSITE_SIGNALS-1 就报错,COMPOSITE_SIGNALS 是编译期定的复合信号总数,超了不让存。第四道校验名称:空串不行;长度用 StringLen() 量,小于 3 也拒;过了才 StringToCharArray() 写进 m_c_signal_set[index].signal_name。 最后一段调 CheckCorrectSlots(),把三个规则元素的 LabelText() 传进去,返回 false 同样中止保存。外汇与贵金属信号组合高风险,这类前置校验能少踩坑,但拦得住格式错、拦不住策略亏。
if(first_save && !CheckCSignalNames(m_c_signal_name.GetValue())) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Это имя уже используется","Монитор сигналов"); else MessageBox("This name is already in use","Signal Monitor"); class="kw">return(false); } class=class="str">"cmt">//--- class="type">int h=FileOpen("Signal Monitor\\c_signal_"+class="type">class="kw">string(index)+".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); } if(index>COMPOSITE_SIGNALS-class="num">1) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Максимальное число сигналов не должно быть больше "+class="type">class="kw">string(COMPOSITE_SIGNALS),"Монитор сигналов"); else MessageBox("Maximum number of signals is "+class="type">class="kw">string(COMPOSITE_SIGNALS),"Signal Monitor"); class="kw">return(false); } class=class="str">"cmt">//--- Save the selection class=class="str">"cmt">//--- Indicator name if(m_c_signal_name.GetValue()=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Введите Имя Сигнала","Монитор сигналов"); else MessageBox("Enter the Signal Name","Signal Monitor"); FileClose(h); class="kw">return(false); } else if(StringLen(m_c_signal_name.GetValue())<class="num">3) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Имя Сигнала должно быть не менее class="num">3 букв","Монитор сигналов"); else MessageBox("Signal Name must be at least class="num">3 letters","Signal Monitor"); FileClose(h); class="kw">return(false); } else StringToCharArray(m_c_signal_name.GetValue(),m_c_signal_set[index].signal_name); class=class="str">"cmt">//--- Slot values if(!CheckCorrectSlots(m_rule_element[class="num">0].LabelText(),m_rule_element[class="num">1].LabelText(),m_rule_element[class="num">2].LabelText())) {
「信号配置落盘时的结构体打包细节」
这段逻辑出现在信号监控面板保存配置的函数尾部:先把界面上的三个信号槽文本通过 StringToCharArray 写进 C_SIGNAL 结构体的 slot_1~slot_3,每个数组固定 50 字节,超长会被截断,所以单槽标签不要超过 49 字符。 逻辑运算符的映射很直白——下拉框选 AND 记 1、OR 记 2、(OR) 记 3,存进 logics[2] 整型数组;标签文本只取前 3 字符塞进 label_value[10],意味着界面上写的『BUY』『SEL』这类短码才适合做显示标识。 配色与开关都是按需写入:背景色和边框色仅在对应按钮被按下时才取色,否则写 clrNONE;tooltip 和 image 两个布尔量控制是否进一步写提示文本或图片索引。最后用 FileWriteStruct 把整个结构体一次性落盘,Print 输出俄文保存成功提示——若终端语言是俄文会弹『Неверно установлено правило』,否则弹英文『Invalid rule』,这是早期多语兼容的笨办法。 外汇与贵金属信号监控涉及高杠杆风险,这套配置只解决『规则怎么存』,不保证信号本身有效,实盘前请在 MT5 策略测试器里跑一遍确认结构体读写无误。
if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Неверно установлено правило","Монитор сигналов"); else MessageBox("Invalid rule","Signal Monitor"); FileClose(h); class="kw">return(false); } StringToCharArray(m_rule_element[class="num">0].LabelText(),m_c_signal_set[index].slot_1); StringToCharArray(m_rule_element[class="num">1].LabelText(),m_c_signal_set[index].slot_2); StringToCharArray(m_rule_element[class="num">2].LabelText(),m_c_signal_set[index].slot_3); class=class="str">"cmt">//--- Values of logical operators for(class="type">int i=class="num">0; i<class="num">2; i++) { if(m_rule_selector[i].LabelText()=="AND") m_c_signal_set[index].logics[i]=class="num">1; else if(m_rule_selector[i].LabelText()=="OR") m_c_signal_set[index].logics[i]=class="num">2; else if(m_rule_selector[i].LabelText()=="(OR)") m_c_signal_set[index].logics[i]=class="num">3; } class=class="str">"cmt">//--- Text label value StringToCharArray(StringSubstr(m_c_text_box.GetValue(),class="num">0,class="num">3),m_c_signal_set[index].label_value); class=class="str">"cmt">//--- Color of the text label m_c_signal_set[index].label_color=m_c_color_button[class="num">0].CurrentColor(); class=class="str">"cmt">//--- Backdrop class="type">class="kw">color if(m_c_set_param[class="num">0].IsPressed()) m_c_signal_set[index].back_color=m_c_color_button[class="num">1].CurrentColor(); else m_c_signal_set[index].back_color=clrNONE; class=class="str">"cmt">//--- Border class="type">class="kw">color if(m_c_set_param[class="num">1].IsPressed()) m_c_signal_set[index].border_color=m_c_color_button[class="num">2].CurrentColor(); else m_c_signal_set[index].border_color=clrNONE; class=class="str">"cmt">//--- Hint value m_c_signal_set[index].tooltip=m_c_set_param[class="num">2].IsPressed(); if(m_c_signal_set[index].tooltip) StringToCharArray(m_c_tooltip_text.GetValue(),m_c_signal_set[index].tooltip_text); class=class="str">"cmt">//--- Selected image m_c_signal_set[index].image=m_c_set_param[class="num">3].IsPressed(); if(m_c_signal_set[index].image) m_c_signal_set[index].img_index=m_c_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex(); class=class="str">"cmt">//--- FileWriteStruct(h,m_c_signal_set[index]); FileClose(h); Print("Конфигурация "+m_c_signal_name.GetValue()+" успешно сохранена"); class=class="str">"cmt">//--- class="kw">return(true); } class="kw">struct C_SIGNAL { class="type">uchar slot_1[class="num">50]; class="type">uchar slot_2[class="num">50]; class="type">uchar slot_3[class="num">50]; class="type">int logics[class="num">2]; class="type">uchar signal_name[class="num">50]; class="type">uchar label_value[class="num">10]; class="type">class="kw">color label_color;
◍ 组合信号槽位的合法性校验逻辑
在 MT5 自定义指标里做多信号合成时,先得拦掉不合理的槽位组合。下面这段 CheckCorrectSlots 用三个布尔量标记 Slot 1~3 是否被占用,规则很直接:Slot 1 一旦被占直接返回 false;Slot 2 占了但 Slot 3 没占也返回 false。 剩下的逻辑是统计三个槽位里「未占用」的数量,若少于 2 个(即占用的超过 1 个且不满足前述配对)同样判不合法。也就是说,系统倾向要求至少空出两个槽位,或 Slot2/Slot3 成对出现才放行。 AddCompositeSignal 则负责把通过校验的新信号写进集合:当内部计数器 m_c_number_signal 小于 0 且 SaveCompositeSignalSet 成功,就关掉编辑弹窗,并把总信号数 m_c_total_signals 往上加。若已达 COMPOSITE_SIGNALS 上限,弹窗提示「Maximum number of composite signals is N」。外汇与贵金属组合信号策略回测波动大,实盘前请在 MT5 策略测试器验证槽位边界。 把这段校验直接拷进你的 EA 工程,改 COMPOSITE_SIGNALS 宏就能限制最大合成信号数,比手写 if 嵌套更不容易漏边界。
class="type">class="kw">color back_color; class="type">class="kw">color border_color; class="type">bool tooltip; class="type">uchar tooltip_text[class="num">100]; class="type">bool image; class="type">int img_index; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CheckCorrectSlots(class="type">class="kw">string name1,class="type">class="kw">string name2,class="type">class="kw">string name3) { class="type">bool slot1=(name1=="Slot class="num">1")?true:false; class="type">bool slot2=(name2=="Slot class="num">2")?true:false; class="type">bool slot3=(name3=="Slot class="num">3")?true:false; class="type">int cnt=class="num">0; class=class="str">"cmt">//--- if(slot1) class="kw">return(false); if(slot2 && !slot3) class="kw">return(false); class=class="str">"cmt">//--- if(!slot1) cnt++; if(!slot2) cnt++; if(!slot3) cnt++; if(cnt<class="num">2) class="kw">return(false); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::AddCompositeSignal(class="type">long lparam) { if(lparam==m_c_new_signal.Id()) { if(m_c_number_signal<class="num">0) { if(SaveCompositeSignalSet(m_c_total_signals)) { m_composite_edit.CloseDialogBox(); if(m_c_total_signals<COMPOSITE_SIGNALS) { m_c_total_signals++; m_c_signal_label[m_c_total_signals-class="num">1].Show(); m_c_signal_label[m_c_total_signals-class="num">1].LabelText(m_c_signal_name.GetValue()); m_c_signal_label[m_c_total_signals-class="num">1].Update(true); m_c_signal_ind[m_c_total_signals-class="num">1].Show(); } else MessageBox("Maximum number of composite signals is "+class="type">class="kw">string(COMPOSITE_SIGNALS),"Signal Monitor");
从 bin 文件还原复合信号配置
当交易者在面板里保存过一组复合信号后,下次启动 EA 需要从本地二进制文件把槽位和逻辑运算符还原出来。LoadCompositeSignalSet 这个函数就是干这件事的,它按索引号去读 Signal Monitor 目录下的 c_signal_*.bin。 文件打不开时直接弹 MessageBox 返回 false,说明配置文件缺失就别继续刷界面了。能打开则用 FileReadStruct 把结构体原样读进 m_c_signal_set[index],再用 CharArrayToString 把保存在结构体里的指标名和三个槽位名转成可读字符串。 槽位按钮的底色有讲究:若槽位是默认的 'Slot 1/2/3' 就用 clrLightSteelBlue,否则用 clrDodgerBlue 高亮,一眼能看出哪条规则被改过。逻辑运算符按 1/2/3 映射成 AND、(OR)、OR 三种,颜色分别是浅蓝、番茄红、橙红——这套配色在 MT5 自定义面板上能降低读错规则的概率。 外汇与贵金属市场波动剧烈、杠杆风险高,这类信号组合仅作辅助参考,实盘前务必在策略测试器里跑一遍验证文件读写与界面同步是否正常。
class="type">bool CProgram::LoadCompositeSignalSet(class="type">int index) { class="type">int h=FileOpen("Signal Monitor\\c_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_c_signal_set[index]); FileReadStruct(h,m_c_signal_set[index]); class=class="str">"cmt">//--- Loading the indicator name m_c_signal_name.SetValue(CharArrayToString(m_c_signal_set[index].signal_name)); m_c_signal_name.GetTextBoxPointer().Update(true); class=class="str">"cmt">//--- Slot values class="type">class="kw">string slot_1=CharArrayToString(m_c_signal_set[index].slot_1); class="type">class="kw">string slot_2=CharArrayToString(m_c_signal_set[index].slot_2); class="type">class="kw">string slot_3=CharArrayToString(m_c_signal_set[index].slot_3); class="type">class="kw">color back=clrDodgerBlue; if(slot_1=="Slot class="num">1") back=clrLightSteelBlue; else back=clrDodgerBlue; SetButtonParam(m_rule_element[class="num">0],slot_1,back); if(slot_2=="Slot class="num">2") back=clrLightSteelBlue; else back=clrDodgerBlue; SetButtonParam(m_rule_element[class="num">1],slot_2,back); if(slot_3=="Slot class="num">3") back=clrLightSteelBlue; else back=clrDodgerBlue; SetButtonParam(m_rule_element[class="num">2],slot_3,back); class=class="str">"cmt">//--- Values of logical operators for(class="type">int i=class="num">0; i<class="num">2; i++) { class="kw">switch(m_c_signal_set[index].logics[i]) { case class="num">1: SetButtonParam(m_rule_selector[i],"AND",C&class="macro">#x27;class="num">75,class="num">190,class="num">240&class="macro">#x27;); break; case class="num">2: SetButtonParam(m_rule_selector[i],"(OR)",clrTomato); break; case class="num">3: SetButtonParam(m_rule_selector[i],"OR",clrOrangeRed); break; class="kw">default:
「信号面板的状态回填逻辑」
在自定义信号编辑器里,选中某个已存信号后要把它的全部属性同步回控制面板,这段代码就是干这个活的。它依次处理文本、颜色、背景、边框、提示框和图片开关,把内存结构体 m_c_signal_set[index] 里的字段逐一映射回 UI 控件。 文本标签先用 ClearTextBox 清空再 AddText 写入,内容来自 CharArrayToString 转换后的 label_value,随后 Update(true) 强制重绘。文字颜色和背景色分别走 m_c_color_button[0] 与 [1],如果背景色等于 clrNONE 就表示不用背景,此时把对应开关 m_c_set_param[0] 设为未按下并锁住颜色按钮。 边框色处理同理:border_color 为 clrNONE 时 m_c_set_param[1] 弹起且 m_c_color_button[2] 锁定,否则解锁并写入实际颜色。tooltip 和 image 两个布尔字段控制的是提示文字框与图片选择滑块的可用状态,false 时相关控件 IsLocked(true) 且参数按钮不按下,true 时才放开并载入 tooltip_text 或图片滑块。 注意原文 tooltip 为 true 的分支里误用了 m_set_param[2] 而非 m_c_set_param[2],编译会报未声明对象,开 MT5 抄这段代码时务必改成带 c 的成员名,否则面板回填直接卡死。外汇与贵金属图表上挂这类自定义面板属于高风险操作,控件状态错乱可能误导下单判断。
break; } } class=class="str">"cmt">//--- Loading a text label m_c_text_box.ClearTextBox(); m_c_text_box.AddText(class="num">0,CharArrayToString(m_c_signal_set[index].label_value)); m_c_text_box.Update(true); class=class="str">"cmt">//--- Loading the class="type">class="kw">color of the text label m_c_color_button[class="num">0].CurrentColor(m_c_signal_set[index].label_color); m_c_color_button[class="num">0].Update(true); class=class="str">"cmt">//--- Loading the background class="type">class="kw">color if(m_c_signal_set[index].back_color==clrNONE) { m_c_set_param[class="num">0].IsPressed(false); m_c_set_param[class="num">0].Update(true); m_c_color_button[class="num">1].IsLocked(true); m_c_color_button[class="num">1].GetButtonPointer().Update(true); } else { m_c_set_param[class="num">0].IsPressed(true); m_c_set_param[class="num">0].Update(true); m_c_color_button[class="num">1].IsLocked(false); m_c_color_button[class="num">1].CurrentColor(m_c_signal_set[index].back_color); m_c_color_button[class="num">1].GetButtonPointer().Update(true); } class=class="str">"cmt">//--- Loading the border class="type">class="kw">color if(m_c_signal_set[index].border_color==clrNONE) { m_c_set_param[class="num">1].IsPressed(false); m_c_set_param[class="num">1].Update(true); m_c_color_button[class="num">2].IsLocked(true); m_c_color_button[class="num">2].GetButtonPointer().Update(true); } else { m_c_set_param[class="num">1].IsPressed(true); m_c_set_param[class="num">1].Update(true); m_c_color_button[class="num">2].IsLocked(false); m_c_color_button[class="num">2].CurrentColor(m_c_signal_set[index].border_color); m_c_color_button[class="num">2].GetButtonPointer().Update(true); } class=class="str">"cmt">//--- Loading the tooltip value if(!m_c_signal_set[index].tooltip) { m_c_set_param[class="num">2].IsPressed(false); m_c_set_param[class="num">2].Update(true); m_c_tooltip_text.IsLocked(true); m_c_tooltip_text.Update(true); } else { m_set_param[class="num">2].IsPressed(true); m_set_param[class="num">2].Update(true); m_c_tooltip_text.IsLocked(false); m_c_tooltip_text.ClearTextBox(); m_c_tooltip_text.AddText(class="num">0,CharArrayToString(m_c_signal_set[index].tooltip_text)); m_c_tooltip_text.Update(true); } class=class="str">"cmt">//--- Loading the image if(!m_c_signal_set[index].image) { m_c_set_param[class="num">3].IsPressed(false); m_c_set_param[class="num">3].Update(true); m_c_pictures_slider.IsLocked(true); m_c_pictures_slider.GetRadioButtonsPointer().Update(true); } else { m_c_set_param[class="num">3].IsPressed(true); m_c_set_param[class="num">3].Update(true); m_c_pictures_slider.IsLocked(false);
◍ 复合信号编辑与简单信号扫描的底层实现
在面板程序里,编辑复合信号由 EditCompositeSignal() 接管:它遍历 COMPOSITE_SIGNALS 个槽位,一旦 lparam 命中某个复合信号控件 Id(),就载入对应信号集、把新建按钮文案切到语言数组第 38 项、弹出编辑窗,并把所有 SIMPLE_SIGNALS 个子信号锁死防止误改。 SearchSignals() 的入口先统计 SIMPLE_SIGNALS 目录下存在的 signal_*.bin 文件数(cnt1),这决定了实际要扫多少条简单信号。注意若某信号 LabelText() 为 "C",代表它仅用于复合信号拼接,会被 continue 直接跳过,不参与逐品种扫描。 扫描循环里对每个品种/周期调用 CheckTimeframe() 做周期兼容性过滤,过了才跑 GetSignal()。命中就 SetVisualSignal() 上色,没命中则 SetDefaultVisual() 复位。外汇与贵金属品种在此类信号监控中波动剧烈,实盘前务必在 MT5 策略测试器用历史数据验证信号触发逻辑。 下面这段是原文里复合信号编辑与搜索的核心片段,逐行看控件交互和文件计数怎么串起来:
class="type">void CProgram::EditCompositeSignal(class="type">long lparam) { for(class="type">int i=class="num">0;i<COMPOSITE_SIGNALS; i++) { if(lparam==m_c_signal_ind[i].Id()) { LoadCompositeSignalSet(i); m_c_new_signal.LabelText(m_lang[class="num">38]); m_c_new_signal.Update(true); m_composite_edit.OpenWindow(); m_c_number_signal=i; for(class="type">int j=class="num">0;j<SIMPLE_SIGNALS; j++) m_simple_signal[j].IsLocked(true); } } } class="type">bool CProgram::SearchSignals(class="type">void) { class="type">int cnt1=class="num">0; for(class="type">int i=class="num">0;i<SIMPLE_SIGNALS; i++) { if(FileIsExist("Signal Monitor\\signal_"+class="type">class="kw">string(i)+".bin")) cnt1++; } SIGNAL signal_set; ZeroMemory(signal_set); for(class="type">int i=class="num">0;i<cnt1; i++) { if(m_signal_type[i].LabelText()=="C") class="kw">continue; if(GetSimpleSignal(signal_set,i)) class="kw">return(false); for(class="type">int j=class="num">0;j<ArraySize(m_signal_button); j++) { class="type">class="kw">string sy=GetSymbol(j); ENUM_TIMEFRAMES tf=GetTimeframe(j); if(!CheckTimeframe(tf,signal_set)) class="kw">continue; if(GetSignal(sy,tf,signal_set)) SetVisualSignal(signal_set,j); else SetDefaultVisual(j); } } }
复合信号怎么从 bin 文件里拼出来
做信号监控时,复合信号(composite signal)并不是实时算的,而是先以 c_signal_i.bin 的形式落在 Signal Monitor 子目录下。代码先用 FileIsExist 扫一遍 COMPOSITE_SIGNALS 个编号,统计实际存在的文件数 cnt2;若 cnt2<1 直接 return(true) 退出,说明当前没有任何复合配置可加载。
扫到文件后,对每个存在的 i 用 FileOpen(..., FILE_READ|FILE_BIN) 打开,读入 C_SIGNAL 结构体 c_signal_set,马上 FileClose 释放句柄。若句柄等于 INVALID_HANDLE 会弹 MessageBox 并报“Configuration not found”,返回 false 中断。
接着在内层循环拿 cnt1 个简单信号(类型非 "S" 的 C/B 类)逐个比对:把 signal_set.signal_name 和复合结构的 slot_1/2/3 转成字符串比较,命中就把下标写进 signal_number[0/1/2]。最后 ArrayPrint(signal_number) 把映射打出来,方便你在 MT5 Experts 日志里确认槽位绑定有没有错。
GetUsedSlots 根据三个 slot 的字符串算出实际占用槽数 used_slots,后面再按 m_signal_button 遍历每个品种+周期,用 signal_number[0]、[1] 取简单信号并调 GetSignal 得出 sig1、sig2 布尔值;当 used_slots==2 时只判断两个槽的共振。外汇与贵金属波动剧烈、杠杆风险高,这段代码仅解决信号装配逻辑,不代表任何方向确定性。
} ..... ..... class=class="str">"cmt">//--- Search for composite signals C_SIGNAL c_signal_set; ZeroMemory(c_signal_set); class="type">int cnt2=class="num">0; class="type">int signal_number[class="num">3]; ArrayInitialize(signal_number,-class="num">1); class=class="str">"cmt">//--- Search for the number of created composite signals for(class="type">int i=class="num">0;i<COMPOSITE_SIGNALS; i++) { if(FileIsExist("Signal Monitor\\c_signal_"+class="type">class="kw">string(i)+".bin")) cnt2++; } class=class="str">"cmt">//-- Exit if there are no signals if(cnt2<class="num">1) class="kw">return(true); class=class="str">"cmt">//--- Search for configurations with composite signals for(class="type">int i=class="num">0;i<cnt2; i++) { class=class="str">"cmt">//--- class="type">int h=FileOpen("Signal Monitor\\c_signal_"+class="type">class="kw">string(i)+".bin",FILE_READ|FILE_BIN); if(h==INVALID_HANDLE) { MessageBox("Configuration not found","Signal Monitor"); class="kw">return(false); } ZeroMemory(c_signal_set); FileReadStruct(h,c_signal_set); FileClose(h); class=class="str">"cmt">//--- Search for simple signals used in the composite one(C or B) for(class="type">int m=class="num">0;m<cnt1; m++) { if(m_signal_type[m].LabelText()!="S") GetSimpleSignal(signal_set,m); class=class="str">"cmt">//--- if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_1)) signal_number[class="num">0]=m; else if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_2)) signal_number[class="num">1]=m; else if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_3)) signal_number[class="num">2]=m; } ArrayPrint(signal_number); } class=class="str">"cmt">//--- class="type">int used_slots=GetUsedSlots(CharArrayToString(c_signal_set.slot_1),CharArrayToString(c_signal_set.slot_2),CharArrayToString(c_signal_set.slot_3)); class=class="str">"cmt">//--- for(class="type">int j=class="num">0;j<ArraySize(m_signal_button); j++) { class=class="str">"cmt">//--- class="type">class="kw">string sy=GetSymbol(j); ENUM_TIMEFRAMES tf=GetTimeframe(j); class=class="str">"cmt">//--- GetSimpleSignal(signal_set,signal_number[class="num">0]); class="type">bool sig1=GetSignal(sy,tf,signal_set); GetSimpleSignal(signal_set,signal_number[class="num">1]); class="type">bool sig2=GetSignal(sy,tf,signal_set); if(used_slots==class="num">2) {
「三信号组合的逻辑分支怎么写」
在 MT5 自定义指标里,当同时挂了 3 个信号槽(used_slots==3)时,组合逻辑不能再像双信号那样只用 AND / OR 一刀切,得靠 logics 数组里的两个值决定优先级。代码里用 c_signal_set.logics[0] 和 [1] 分别表示前两个连接位的运算类型:1 为 AND,2 为普通 OR,3 为带括号优先级的 OR。 比如 logics[0]==1 且 logics[1]==2,含义是「(sig1 AND sig2) OR sig3」,只要前两个信号同真、或第三个独立为真,就触发 SetVisualCompositeSignal。若是 logics[0]==3 且 logics[1]==1,则是「(sig1 OR sig2) AND sig3」,必须第三个信号真且前两个里至少一个真才成立。 实盘里这种细分级联逻辑对贵金属这类跳空频繁的品种尤其有用:用 OR 兜住假突破,用 AND 锁趋势确认。但外汇和贵金属杠杆高、滑点不可控,任何组合信号都只是概率倾向,开 MT5 把下面这段塞进你的信号合成函数里改 logics 参数验证最直观。
class=class="str">"cmt">//--- AND if(c_signal_set.logics[class="num">0]==class="num">1) if(sig1 && sig2) SetVisualCompositeSignal(c_signal_set,j); class=class="str">"cmt">//--- OR if(c_signal_set.logics[class="num">0]>class="num">1) if(sig1 || sig2) SetVisualCompositeSignal(c_signal_set,j); } else if(used_slots==class="num">3) { GetSimpleSignal(signal_set,signal_number[class="num">2]); class="type">bool sig3=GetSignal(sy,tf,signal_set); class=class="str">"cmt">//--- AND OR if(c_signal_set.logics[class="num">0]==class="num">1 && c_signal_set.logics[class="num">1]==class="num">2) { if((sig1 && sig2) || sig3) SetVisualCompositeSignal(c_signal_set,j); } class=class="str">"cmt">//--- AND(OR) else if(c_signal_set.logics[class="num">0]==class="num">1 && c_signal_set.logics[class="num">1]==class="num">3) { if(sig1 && (sig2 || sig3)) SetVisualCompositeSignal(c_signal_set,j); } class=class="str">"cmt">//--- OR AND else if(c_signal_set.logics[class="num">0]==class="num">2 && c_signal_set.logics[class="num">1]==class="num">1) { if(sig1 || (sig2 && sig3)) SetVisualCompositeSignal(c_signal_set,j); } class=class="str">"cmt">//--- (OR) AND else if(c_signal_set.logics[class="num">0]==class="num">3 && c_signal_set.logics[class="num">1]==class="num">1) { if((sig1 || sig2) && sig3) SetVisualCompositeSignal(c_signal_set,j); } class=class="str">"cmt">//--- AND AND else if(c_signal_set.logics[class="num">0]==class="num">1 && c_signal_set.logics[class="num">1]==class="num">1) { if(sig1 && sig2 && sig3) SetVisualCompositeSignal(c_signal_set,j); } class=class="str">"cmt">//--- OR OR else if(c_signal_set.logics[class="num">0]>class="num">1 && c_signal_set.logics[class="num">1]>class="num">1) { if(sig1 || sig2 || sig3) SetVisualCompositeSignal(c_signal_set,j); } } } class="kw">return(true);
◍ 信号槽占用与面板绘制的底层接口
在 MT5 的 EA 面板逻辑里,先得搞清楚三个信号槽到底用了几个。GetUsedSlots 这个函数用最直白的方式计数:只要传入的 name 不等于默认的 "Slot 1" / "Slot 2" / "Slot 3" 字符串,就认为该槽被占用,cnt 加 1,最终返回 0~3 之间的整数。 SetVisualCompositeSignal 负责把复合信号画到界面 block 上。它先写标签文字和颜色,再按 back_color 是否为 clrNONE 决定是否铺背景;边框色若没单独设,就退化成背景色,避免空边框。 tooltip 和 image 两个开关控制鼠标悬停文本与图标:开了就调 SetTooltip / SetIcon 传实际内容,没开图标就传 -1 清掉。外汇与贵金属行情跳变快,这类界面刷新若放在 OnTick 里高频调用,可能拖慢执行,建议在信号状态变更时触发而非每 tick 重绘。
class="type">int CProgram::GetUsedSlots(class="type">class="kw">string name1,class="type">class="kw">string name2,class="type">class="kw">string name3) { class="type">int cnt=class="num">0; if(name1!="Slot class="num">1") cnt++; if(name2!="Slot class="num">2") cnt++; if(name3!="Slot class="num">3") cnt++; class="kw">return(cnt); } class="type">void CProgram::SetVisualCompositeSignal(C_SIGNAL &signal_set,class="type">int block) { class=class="str">"cmt">//--- SetLabel(block,CharArrayToString(signal_set.label_value),signal_set.label_color); class=class="str">"cmt">//--- if(signal_set.back_color!=clrNONE) SetBackground(block,signal_set.back_color); class=class="str">"cmt">//--- if(signal_set.border_color!=clrNONE) SetBorderColor(block,signal_set.border_color); else SetBorderColor(block,signal_set.back_color); class=class="str">"cmt">//--- if(signal_set.tooltip) SetTooltip(block,CharArrayToString(signal_set.tooltip_text)); class=class="str">"cmt">//--- if(signal_set.image) SetIcon(block,signal_set.img_index); else SetIcon(block,-class="num">1); }
把档案丢进终端根目录就活了
这套附件里列的文件都已按对应目录排好,真正要跑起来只需做一步:把其中的 MQL5 文件夹整体拷到终端根目录里,覆盖同名结构即可。 定位终端根目录最快的办法是在 MT5 里按 Ctrl+Shift+D,或者在导航器右键菜单里选打开目录,图例 14 展示的就是这个入口。 外汇与贵金属品种波动剧烈、杠杆风险高,加载任何第三方脚本前先开模拟账户验一遍再上真仓。
「从 MT5 根目录直插 MQL5 文件夹」
要在 MT5 里自己塞 EA、指标或脚本,第一步不是点导航器,而是先摸到终端实际落地文件的 MQL5 目录。最稳的路径是打开 MetaTrader 5 终端根目录,里面直接躺着 MQL5 文件夹,Experts、Indicators、Scripts 都在其下分级存放。 很多交易者只在客户端里右键「打开数据文件夹」,那进的是用户态 AppData 下的沙盒;若你下载了官方示例包(如 MQL5.zip,体积约 2009.3 KB),解压后应当对照终端根目录的 MQL5 结构手动归位,避免编译器找不到依赖。 实操上,关掉 MT5 再拷文件能避开文件占用锁;重开终端后按 F4 调起 MetaEditor,就能在左侧目录树看到刚放进去的内容,直接编译验证。外汇与贵金属品种波动剧烈、杠杆风险高,任何自行部署的代码先在策略测试器跑历史数据再上实盘。
◍ 高 DPI 下的模糊与枚举冲突修法
有交易者在 4K 屏下把 MT5 的“覆盖高 DPI 缩放”打开后,终端文本反而变模糊;MetaTrader 5 本身已针对高 DPI 调整,正常 4K 分辨率可直接跑,强行覆盖缩放大概率画质劣化。
- 年 9 月有人反馈基于 PanelDialog.mqh 的面板在高 DPI 下疑似不支持,同期编译报错三处:Enums.mqh 132 行“ENUM_SORT_MODE 标识符已被使用”、Table.mqh 355 行与 2226 行“SORT_ASCEND 枚举器不适当”。
到 2025 年 2 月,有用户手动把枚举名由 ENUM_SORT_MODE 改为 ENUM_SORT_MODE_CUSTOM,并同步重命名 CanvasTable.mqh 与 Table.mqh 里所有引用,三个冲突即消失。若你拉取老版 MQL5 界面库编译报同类错,直接全局替换枚举名比改调用处更稳。 外汇和贵金属杠杆高、滑点无常,改库文件前务必另存原版;编译通了也先在策略测试器空跑,确认面板渲染无模糊再上实盘。