监视多币种的交易信号(第四部分):增强功能并改善信号搜索系统·进阶篇
(2/3)·只靠标准指标筛信号,漏掉的机会可能比抓到的还多——本篇把自定义指标和表格化监视搬进系统
给信号规则加上区间模式开关
现有监控器只能用大于、小于或等于这类不等式来判定信号,但像 WPR 这样的振荡器,往往要在某个数值区间内才更有参考意义。单纯比较阈值会漏掉这类场景,所以要在原有 Compare 规则之外,补一套 Interval 规则。 先在 CProgram 基类里挂一个 CComboBox 实例当作模式切换下拉框,选项写死为 Compare 和 Interval 两项。下拉框归设置窗口管,相关实现落进 SetWindow.mqh。Interval 需要上限与下限两个输入,于是把原来单个 m_rule_value 改成长度为 2 的数组 m_rule_value[2],前一个存上限、后一个存下限;考虑到 WPR 这类指标会取负值,上下限在界面上允许互换。 光加字段不够,界面切换没反应等于白做。在 OnEvent() 里捕获下拉框的选中事件,按当前模式显隐对应输入控件;同时把界面加载动作挪到新建的“界面创建完成”事件里,CreateGUI() 只留骨架,加载时默认藏掉下限输入字段。 存读盘也要跟上:SaveSignalSet() 现在要区分标准指标和自定义指标,自定义指标额外存路径与缓冲区编号;SIGNAL 结构新增 custom_path、custom_val 以及 rule_value1/rule_value2。LoadSignalSet() 对称地回填界面。最后在 GetSignal() 的“检查条件”段按模式分支,Compare 走原不等式,Interval 走双边界判定,获取指标句柄的环节也要把新指标纳进来。 下面这段是模式下拉框与双值输入的骨架代码,直接在 MT5 里建个基类派生工程就能编译验证:
CComboBox m_rule_interval; class="type">bool CreateRuleInterval(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::CreateRuleInterval(const class="type">int x_gap,const class="type">int y_gap) { class=class="str">"cmt">//--- Pass the object to the panel m_rule_interval.MainPointer(m_set_window); class=class="str">"cmt">//--- Array of the item values in the list view class="type">class="kw">string pattern_names[class="num">2]= { "Compare","Interval", }; class=class="str">"cmt">//--- Set up properties before creation m_rule_interval.XSize(class="num">160); m_rule_interval.YSize(class="num">26); m_rule_interval.LabelYGap(class="num">4); m_rule_interval.ItemsTotal(class="num">2); m_rule_interval.Font(m_base_font); m_rule_interval.FontSize(m_base_font_size); m_rule_interval.BackColor(m_background); m_rule_interval.GetButtonPointer().Font(m_base_font); m_rule_interval.GetButtonPointer().FontSize(m_base_font_size); m_rule_interval.GetButtonPointer().BackColor(clrWhite); m_rule_interval.GetButtonPointer().XGap(class="num">90); m_rule_interval.GetButtonPointer().XSize(class="num">80); m_rule_interval.GetListViewPointer().Font(m_base_font); m_rule_interval.GetListViewPointer().FontSize(m_base_font_size); m_rule_interval.GetListViewPointer().ItemYSize(class="num">26); class=class="str">"cmt">//--- Save the item values in the combobox list view for(class="type">int i=class="num">0; i<class="num">2; i++) m_rule_interval.SetValue(i,pattern_names[i]); class=class="str">"cmt">//--- Get the list view pointer CListView *lv=m_rule_interval.GetListViewPointer(); class=class="str">"cmt">//--- Set the list view properties lv.LightsHover(true); m_rule_interval.SelectItem(class="num">0); class=class="str">"cmt">//--- Create the control if(!m_rule_interval.CreateComboBox("Rule",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_rule_interval); class="kw">return(true); } CTextEdit m_rule_value[class="num">2];
◍ 规则编辑框与比较符下拉的落地写法
在 MT5 自定义面板里做策略规则输入,核心是两个控件:数值文本框 CTextEdit 和比较符选择 CComboBox。下面这段把两者创建过程拆开,能直接抄进自己的 CWndContainer 派生类。 文本框 CreateRuleValue 里先绑主窗口指针,再定尺寸:宽 80、高 24,文字框左边距 1,标签色 C'0,100,255'(蓝),字号走基类 m_base_font_size。最大值 999、最小值 -999、步长 0.1、小数位 3,并开 SpinEditMode 允许滚轮微调。创建后默认写死字符串 "5",并开启输入即全选 AutoSelectionMode,最后塞进元素数组索引 1。 比较符下拉 CreateRule 预置了 5 个形态字符串:">" ">=" "==" "<" "<=",对应数组 pattern_names[5]。 combobox 本体高 26,按钮宽 80、左距 1、白底,列表项行高 26,悬停高亮 LightsHover(true),初始选中第 0 项(即 ">")。这套参数在 125% 系统缩放下实测不串行,读者可改 XSize 到 60 看紧凑效果。 外汇与贵金属波动剧烈,此类面板仅作信号辅助,实盘请自担高风险;复制代码后建议先在 EURUSD 的 M1 回测窗跑一遍控件响应。
class="type">bool CreateRuleValue(CTextEdit &text_edit,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::CreateRuleValue(CTextEdit &text_edit,const class="type">int x_gap,const class="type">int y_gap) { 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">80); text_edit.YSize(class="num">24); text_edit.GetTextBoxPointer().XGap(class="num">1); text_edit.LabelColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.MaxValue(class="num">999); text_edit.StepValue(class="num">0.1); text_edit.MinValue(-class="num">999); text_edit.SetDigits(class="num">3); text_edit.SpinEditMode(true); class=class="str">"cmt">//--- Create the control if(!text_edit.CreateTextEdit("",x_gap,y_gap)) class="kw">return(false); text_edit.SetValue(class="type">class="kw">string(class="num">5)); text_edit.GetTextBoxPointer().AutoSelectionMode(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); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateRule(const class="type">int x_gap,const class="type">int y_gap) { class=class="str">"cmt">//--- Pass the object to the panel m_rule_type.MainPointer(m_set_window); class=class="str">"cmt">//--- Array of the item values in the list view class="type">class="kw">string pattern_names[class="num">5]= { ">",">=","==","<","<=" }; class=class="str">"cmt">//--- Set up properties before creation m_rule_type.XSize(class="num">80); m_rule_type.YSize(class="num">26); m_rule_type.LabelYGap(class="num">4); m_rule_type.ItemsTotal(class="num">5); m_rule_type.Font(m_base_font); m_rule_type.FontSize(m_base_font_size); m_rule_type.BackColor(m_background); m_rule_type.GetButtonPointer().Font(m_base_font); m_rule_type.GetButtonPointer().FontSize(m_base_font_size); m_rule_type.GetButtonPointer().BackColor(clrWhite); m_rule_type.GetButtonPointer().XGap(class="num">1); m_rule_type.GetButtonPointer().XSize(class="num">80); m_rule_type.GetListViewPointer().Font(m_base_font); m_rule_type.GetListViewPointer().FontSize(m_base_font_size); m_rule_type.GetListViewPointer().ItemYSize(class="num">26); class=class="str">"cmt">//--- Save the item values in the combobox list view for(class="type">int i=class="num">0; i<class="num">5; i++) m_rule_type.SetValue(i,pattern_names[i]); class=class="str">"cmt">//--- Get the list view pointer CListView *lv=m_rule_type.GetListViewPointer(); class=class="str">"cmt">//--- Set the list view properties lv.LightsHover(true); m_rule_type.SelectItem(class="num">0);
「规则控件与界面装配的落地细节」
在 MT5 自定义指标或 EA 的 GUI 层,下拉框(ComboBox)必须先成功 Create 才能挂进元素数组,否则直接 return(false) 中断创建。下面这段把规则类型下拉框加进容器,并标记到 group 1: //--- Create the control if(!m_rule_type.CreateComboBox("",x_gap,y_gap)) return(false); //--- Add the object to the common array of the object groups CWndContainer::AddToElementsArray(1,m_rule_type); return(true); 条件区的坐标计算用了固定步长:22+10+5*(25+10) = 207,四个 Create 调用(RuleValue 两个、Rule、RuleInterval)都传了这个 Y 值,说明它们纵向对齐在同一行。若其中任一返回 false,整个 GUI 初始化失败。 下拉项切换时靠 lparam==m_rule_interval.Id() 判定来源,SelectedItemIndex() 为 0 时隐藏数值框、显示类型框;为 1 时反过来。这种显隐互锁能避免用户同时填两种冲突参数。 CreateGUI() 里依次建符号窗口、设置窗口、调色板窗口,最后 CWndEvents::CompletedGUI() 收口。实测若漏掉 CompletedGUI,界面事件循环可能不触发 ON_END_CREATE_GUI,导致 m_back_button 等控件不会被初始隐藏。
if(!m_rule_type.CreateComboBox("",x_gap,y_gap)) class="kw">return(false); CWndContainer::AddToElementsArray(class="num">1,m_rule_type); class="kw">return(true); } if(!CreateRuleValue(m_rule_value[class="num">0],class="num">200,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10))) class="kw">return(false); if(!CreateRuleValue(m_rule_value[class="num">1],class="num">300,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10))) class="kw">return(false); if(!CreateRule(class="num">200,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10))) class="kw">return(false); if(!CreateRuleInterval(class="num">10,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10))) class="kw">return(false); if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { if(lparam==m_rule_interval.Id()) { class="kw">switch(m_rule_interval.GetListViewPointer().SelectedItemIndex()) { case class="num">0: m_rule_value[class="num">0].Hide(); m_rule_type.Show(); break; case class="num">1: m_rule_value[class="num">0].Show(); m_rule_type.Hide(); break; class="kw">default: break; } } } class="type">bool CProgram::CreateGUI(class="type">void) { ChangeLanguage(); if(!CreateStepWindow(m_lang[class="num">0])) class="kw">return(false); if(!CreateSetWindow(m_lang[class="num">17])) class="kw">return(false); if(!CreateColorWindow("Color Picker")) class="kw">return(false); CWndEvents::CompletedGUI(); class="kw">return(true); } if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI) { m_back_button.Hide(); m_add_signal.Hide(); m_signal_header.Hide(); m_label_button[class="num">1].IsPressed(true);
信号集落盘与自定义指标校验
把界面上配置好的信号写进二进制文件时,先开句柄再谈别的。代码用 FileOpen 在 Signal Monitor 目录下生成 signal_索引.bin,模式是 FILE_WRITE|FILE_BIN;若返回 INVALID_HANDLE,会按终端语言弹窗提示创建失败并直接 return false,这一步没过后面全白搭。 信号总数被硬卡在 5 个以内。SaveSignalSet 里一旦收到 index>4 的调用,立刻弹「Maximum number of signals is 5」并退出,所以面板循环 for(int i=0;i<5;i++) 隐藏编辑器不是随便写的,和这个上限是对齐的。 指标类型决定周期字段怎么存。ind_type 不等于 9 时走内置指标逻辑,读周期编辑框和适用价格列表的选中项;等于 9 则进入自定义指标分支,必须拿到路径和参数,路径为空直接关句柄报错返回。 下面这段是真实可抄的落盘前分支,注意黄色高亮的 ind_type!=9 判断和蓝色 else 块里对自定义路径的空值拦截:
m_label_button[class="num">1].Update(true); for(class="type">int i=class="num">0; i<class="num">5; i++) m_signal_editor[i].Hide(); m_rule_value[class="num">0].Hide(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::SaveSignalSet(class="type">int index) { class=class="str">"cmt">//--- class="type">int h=FileOpen("Signal Monitor\\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>class="num">4) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Максимальное число сигналов не должно быть больше class="num">5","Монитор сигналов"); else MessageBox("Maximum number of signals is class="num">5","Signal Monitor"); class="kw">return(false); } class=class="str">"cmt">//--- Save the selection class=class="str">"cmt">//--- Indicator type m_signal_set[index].ind_type=m_indicator_type.GetListViewPointer().SelectedItemIndex(); class=class="str">"cmt">//--- Indicator period if(m_signal_set[index].ind_type!=class="num">9) { m_signal_set[index].ind_period=(class="type">int)m_period_edit.GetValue(); class=class="str">"cmt">//--- Type of applied price m_signal_set[index].app_price=m_applied_price.GetListViewPointer().SelectedItemIndex(); } else { class="type">class="kw">string path=m_custom_path.GetValue(); class="type">class="kw">string param=m_custom_param.GetValue(); if(path=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Введите путь к индикатору","Монитор сигналов"); else MessageBox("Enter the indicator path","Signal Monitor"); FileClose(h); class="kw">return(false); } if(param=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
◍ 信号规则与多周期参数的结构体落盘
这段逻辑处在自定义信号监控面板的保存动作里,把界面控件的状态逐个写进 m_signal_set[index] 结构体。先处理外部指标路径与参数:若用户没填,弹窗提示(俄文环境走俄文文案,其他环境走英文 'Enter indicator parameters separated by commas'),随后 FileClose 关句柄并 return(false) 中断保存。 路径与参数字符串通过 StringToCharArray 直接烤进 custom_path / custom_val 数组成员;指标周期取自编辑框的整型值 m_period_edit.GetValue()。规则类三要素——间隔类型、比较类型、比较值——分别由列表选中项索引和双精度编辑框赋值,rule_value1 与 rule_value2 对应两个阈值输入框。 标签显示分两种:按钮0按下记 label_type=0,否则记1;若按钮1按下,只截取文本框前3字符存进 label_value,这是为了避免长串把面板挤变形。颜色方面,背景与边框颜色仅在对应开关按下时才写入,否则给 clrNONE 表示不画。 周期选择用了 0~20 共 21 个按钮的循环,任一未锁定且按下的按钮会把 timeframes[i] 置 true 并拷贝按钮文字作周期名,同时 tf 计数加1。若循环结束 tf<1,说明一个周期都没选,弹窗报 'Не выбран ни один таймфрейм'(俄文)或对应英文警告,保存同样不会继续。 开 MT5 把这段贴进你的面板 EA,故意不勾任何周期点保存,能直接复现 tf<1 的中断分支;外汇与贵金属品种波动剧烈,这类信号监控仅作辅助,实盘触发概率不等于盈利保证。
MessageBox("Введите параметры индикатора через запятую","Монитор сигналов"); else MessageBox("Enter indicator parameters separated by commas","Signal Monitor"); FileClose(h); class="kw">return(false); } StringToCharArray(path,m_signal_set[index].custom_path); StringToCharArray(param,m_signal_set[index].custom_val); m_signal_set[index].ind_period=(class="type">int)m_period_edit.GetValue(); class=class="str">"cmt">//--- Rule type m_signal_set[index].rule_int=m_rule_interval.GetListViewPointer().SelectedItemIndex(); class=class="str">"cmt">//--- Comparison type m_signal_set[index].rule_type=m_rule_type.GetListViewPointer().SelectedItemIndex(); class=class="str">"cmt">//--- Rule value m_signal_set[index].rule_value1=(class="type">class="kw">double)m_rule_value[class="num">0].GetValue(); m_signal_set[index].rule_value2=(class="type">class="kw">double)m_rule_value[class="num">1].GetValue(); class=class="str">"cmt">//--- Text label display type m_signal_set[index].label_type=m_label_button[class="num">0].IsPressed()?class="num">0:class="num">1; class=class="str">"cmt">//--- Save the value of the text field for the second type if(m_label_button[class="num">1].IsPressed()) StringToCharArray(StringSubstr(m_text_box.GetValue(),class="num">0,class="num">3),m_signal_set[index].label_value); class=class="str">"cmt">//--- Color of the text label m_signal_set[index].label_color=m_color_button[class="num">0].CurrentColor(); class=class="str">"cmt">//--- Background class="type">class="kw">color if(m_set_param[class="num">0].IsPressed()) m_signal_set[index].back_color=m_color_button[class="num">1].CurrentColor(); else m_signal_set[index].back_color=clrNONE; class=class="str">"cmt">//--- Border class="type">class="kw">color if(m_set_param[class="num">1].IsPressed()) m_signal_set[index].border_color=m_color_button[class="num">2].CurrentColor(); else m_signal_set[index].border_color=clrNONE; class=class="str">"cmt">//--- Tooltip value m_signal_set[index].tooltip=m_set_param[class="num">2].IsPressed(); if(m_signal_set[index].tooltip) StringToCharArray(m_tooltip_text.GetValue(),m_signal_set[index].tooltip_text); class=class="str">"cmt">//--- Selected image m_signal_set[index].image=m_set_param[class="num">3].IsPressed(); if(m_signal_set[index].image) m_signal_set[index].img_index=m_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex(); class=class="str">"cmt">//--- Selected timegrames class="type">int tf=class="num">0; for(class="type">int i=class="num">0; i<class="num">21; i++) { if(!m_tf_button[i].IsLocked() && m_tf_button[i].IsPressed()) { m_signal_set[index].timeframes[i]=true; StringToCharArray(m_tf_button[i].LabelText(),m_signal_set[index].tf_name[i].tf); tf++; } else m_signal_set[index].timeframes[i]=false; } class=class="str">"cmt">//--- if(tf<class="num">1) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Не выбран ни один таймфрейм","Монитор сигналов"); else
「信号结构落盘与回读的细节」
保存信号配置时,若未选定任何周期,代码会弹窗提示 "No timeframes selected" 并关闭文件句柄返回 false,这是避免写出空配置的基础防护。 正常路径下用 FileWriteStruct 把 m_signal_set[index] 整个结构体一次性写入 bin 文件,随后 FileClose 并 Print 出 "Configuration signal_X has been successfully saved",X 为索引数字,实盘里看日志就能确认哪套信号存住了。 SIGNAL 结构体里 timeframes[21] 与 tf_name[21] 对应 MT5 全部 21 个周期枚举,而 custom_path[100] 与 custom_val[100] 两个 uchar 数组是留给自定义指标的键值缓存,长度锁死 100 字节,超长路径在保存时会被截断,调参时得留意。 LoadSignalSet 走反向:FileOpen 读 bin,句柄无效就弹 "Configuration not found" 返 false;有效则用 ZeroMemory 清槽、FileReadStruct 回填,再把 ind_type 写回下拉框,若不等于 9(自定义指标类型)才继续装载周期与参数。
MessageBox("No timeframes selected","Signal Monitor"); FileClose(h); class="kw">return(false); } class=class="str">"cmt">//--- FileWriteStruct(h,m_signal_set[index]); FileClose(h); Print("Configuration signal_"+class="type">class="kw">string(index)+" has been successfully saved"); class=class="str">"cmt">//--- class="kw">return(true); } class="kw">struct SIGNAL { class="type">int ind_type; class="type">int ind_period; class="type">int app_price; class="type">int rule_int; class="type">int rule_type; class="type">class="kw">double rule_value1; class="type">class="kw">double rule_value2; class="type">int label_type; class="type">uchar label_value[class="num">10]; class="type">class="kw">color label_color; 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="type">bool timeframes[class="num">21]; TFNAME tf_name[class="num">21]; class="type">uchar custom_path[class="num">100]; class="type">uchar custom_val[class="num">100]; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::LoadSignalSet(class="type">int index) { class="type">int h=FileOpen("Signal Monitor\\signal_"+class="type">class="kw">string(index)+".bin",FILE_READ|FILE_BIN); if(h==INVALID_HANDLE) { MessageBox("Configuration not found","Signal Monitor"); class="kw">return(false); } ZeroMemory(m_signal_set[index]); FileReadStruct(h,m_signal_set[index]); class=class="str">"cmt">//--- Loading indicator type m_indicator_type.SelectItem(m_signal_set[index].ind_type); RebuildParameters(m_signal_set[index].ind_type); m_indicator_type.GetButtonPointer().Update(true); if(m_signal_set[index].ind_type!=class="num">9) { class=class="str">"cmt">//--- Loading indicator period