监视多币种的交易信号(第四部分):增强功能并改善信号搜索系统(基础篇)
◍ 从零搭一个多品种信号监视框架
在 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 加变长参数,就是后面接自定义计算的入口。
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”并显示应用价格。外汇贵金属面板这类动态重构,最好先在策略测试器里切几次下拉验证控件不重叠。
); 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 的锁状态就能验证自定义指标录入是否真的放开。
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 不同步会让你误以为信号没触发,实盘前务必在策略测试器里点一遍验证。
m_new_signal.LabelText("Add"); m_new_signal.Update(true); m_indicator_type.SelectItem(class="num">1); m_indicator_type.GetButtonPointer().Update(true); }