基于 Merill(梅里尔) 形态的策略构建器·进阶篇
(2/3)· 前篇看懂了形态,这篇教你用界面把买/卖信号拼成能跑测试的策略
「面板控件的坐标与默认值布局」
这段初始化逻辑把交易面板的各类控件按窗口尺寸动态排布,左列占 35% 宽度、右列占 75% 宽度,均扣掉固定的 150 像素边距再偏移。以 XSize=800 的窗口为例,右列起点横坐标约为 int(0.75*(800-150)-100)=387,左列约为 int(0.35*(800-150)-120)=95。 AppliedType4~6 三个下拉控件依次放在右列 y=80+ygap、y=116+ygap、y=149+ygap 处(50+33*2、50+33*3 推算),任一创建失败直接 return(false) 中断面板构建。 信号勾选框用 for(i=2;i<8) 循环生成 6 个:i<5 时落在左列(Signal 1~3),i>=5 时落在右列(Signal 1~3),纵向间隔 35 像素。 止盈止损编辑框左右各一对,默认值均设为 500、最小步长 0,说明该面板默认以点数(或小数位换算)方式输入,外汇与贵金属品种请自行核对点值风险。 最下方 CreateReportFrame 在左列 60+35*6+ygap 处留一块空报告区,后续由分析逻辑填数。
if(!AppliedType4(class="type">int(class="num">0.75*(m_window[class="num">0].XSize()-class="num">150)-class="num">100),class="num">80+ygap)) class="kw">return(false); if(!AppliedType5(class="type">int(class="num">0.75*(m_window[class="num">0].XSize()-class="num">150)-class="num">100),class="num">50+class="num">33*class="num">2+ygap)) class="kw">return(false); if(!AppliedType6(class="type">int(class="num">0.75*(m_window[class="num">0].XSize()-class="num">150)-class="num">100),class="num">50+class="num">33*class="num">3+ygap)) class="kw">return(false); class=class="str">"cmt">//--- Signal checkboxes for(class="type">int i=class="num">2; i<class="num">8; i++) { if(i<class="num">5) if(!CreateCheckBox(m_checkbox[i],class="type">int(class="num">0.35*(m_window[class="num">0].XSize()-class="num">150)-class="num">120),class="num">50+class="num">35*(i-class="num">1)+ygap,"Signal "+class="type">class="kw">string(i-class="num">1))) class="kw">return(false); if(i>=class="num">5) if(!CreateCheckBox(m_checkbox[i],class="type">int(class="num">0.75*(m_window[class="num">0].XSize()-class="num">150)-class="num">120),class="num">50+class="num">35*(i-class="num">4)+ygap,"Signal "+class="type">class="kw">string(i-class="num">4))) class="kw">return(false); } class=class="str">"cmt">//--- Take Profit and Stop Loss settings if(!CreateEditValue(m_takeprofit1,class="type">int(class="num">0.35*(m_window[class="num">0].XSize()-class="num">150)-class="num">120),class="num">50+class="num">35*class="num">4+ygap,"Take Profit",class="num">500,class="num">0)) class="kw">return(false); if(!CreateEditValue(m_stoploss1,class="type">int(class="num">0.35*(m_window[class="num">0].XSize()-class="num">150)-class="num">120),class="num">50+class="num">35*class="num">5+ygap,"Stop Loss",class="num">500,class="num">0)) class="kw">return(false); if(!CreateEditValue(m_takeprofit2,class="type">int(class="num">0.75*(m_window[class="num">0].XSize()-class="num">150)-class="num">120),class="num">50+class="num">35*class="num">4+ygap,"Take Profit",class="num">500,class="num">0)) class="kw">return(false); if(!CreateEditValue(m_stoploss2,class="type">int(class="num">0.75*(m_window[class="num">0].XSize()-class="num">150)-class="num">120),class="num">50+class="num">35*class="num">5+ygap,"Stop Loss",class="num">500,class="num">0)) class="kw">return(false); class=class="str">"cmt">//--- Report if(!CreateReportFrame(m_frame[class="num">2],"",class="type">int(class="num">0.35*(m_window[class="num">0].XSize()-class="num">150)-class="num">120),class="num">60+class="num">35*class="num">6+ygap)) class="kw">return(false);
◍ 把报告标签和筛选框摆进界面
这段逻辑负责把 6 个文本标签按两列排布,并初始化一个带输入框的品种过滤器。循环里 i<3 的标签挂在左列,x 坐标取主窗口宽度减 150 后的 0.4 倍再减 120;i>=3 的挂在右列,用 0.75 倍系数,纵向间隔固定 35 像素,因此左右两列各占 3 行、行高一致。 CreateSymbolsFilter 里把复选框模式打开,文本框宽度设 100、字号 11,默认填 "USD"。注释里留了 "EUR,USD" 到多品种组合的写法,实盘接行情请求时可直接改这个值来缩减扫描范围,外汇与贵金属波动剧烈,过滤不当可能漏掉异动品种。 CreateSymbolsTable 目前只定义了 1 行,列宽数组写死 119、居中对齐、文本 X 偏移 5。想扩成多品种监控表,先把 ROWS1_TOTAL 和 width 数组长度改掉,否则后面挂数据会越界返回 false。
for(class="type">int i=class="num">0; i<class="num">6; i++) { if(i<class="num">3) if(!CreateTextLabel(m_report_text[i],class="type">int(class="num">0.4*(m_window[class="num">0].XSize()-class="num">150)-class="num">120),class="num">60+class="num">35*(class="num">7+i)+ygap,"",class="num">0)) class="kw">return(false); if(i>=class="num">3) if(!CreateTextLabel(m_report_text[i],class="type">int(class="num">0.75*(m_window[class="num">0].XSize()-class="num">150)-class="num">120),class="num">60+class="num">35*(class="num">7+i-class="num">3)+ygap,"",class="num">0)) class="kw">return(false); m_report_text[i].IsCenterText(false); } class="type">bool CProgram::CreateSymbolsFilter(const class="type">int x_gap,const class="type">int y_gap) { m_symb_filter.MainPointer(m_tabs1); m_tabs1.AddToElementsArray(class="num">0,m_symb_filter); m_symb_filter.CheckBoxMode(true); m_symb_filter.YSize(class="num">25); m_symb_filter.FontSize(class="num">11); m_symb_filter.XSize(class="num">200); m_symb_filter.GetTextBoxPointer().XGap(class="num">20); m_symb_filter.GetTextBoxPointer().XSize(class="num">100); m_symb_filter.GetTextBoxPointer().YSize(class="num">25); m_symb_filter.GetTextBoxPointer().AutoSelectionMode(true); m_symb_filter.SetValue("USD"); if(!m_symb_filter.CreateTextEdit("",x_gap,y_gap)) class="kw">return(false); m_symb_filter.IsPressed(true); CWndContainer::AddToElementsArray(class="num">0,m_symb_filter); if(!CreateRequest(x_gap+class="num">125,y_gap)) class="kw">return(false); class="kw">return(true); } class="type">bool CProgram::CreateSymbolsTable(const class="type">int x_gap,const class="type">int y_gap) { class="macro">#define ROWS1_TOTAL class="num">1 m_table_symb.MainPointer(m_tabs1); m_tabs1.AddToElementsArray(class="num">0,m_table_symb); class="type">int width[class="num">1]= {class="num">119}; ENUM_ALIGN_MODE align[class="num">1]= {ALIGN_CENTER}; class="type">int text_x_offset[class="num">1]= {class="num">5};
用代码拼出面板里的表格与控件
在 MT5 自定义面板里,表格和按钮不是画出来的,是靠 CTable、CTextLabel、CButton 这几个类逐个属性喂出来的。下面这段配置把符号表格的宽度锁在 120 像素、字号 10、表头底色 C'0,130,255'(蓝),并开启斑马纹(clrWhiteSmoke)和自动纵向拉伸(底部留 3 像素),读者可以直接抄进自己的 EA 界面工程。 表格创建失败就直接 return(false),成功才通过 CWndContainer::AddToElementsArray(0,m_table_symb) 挂进全局元素数组——这意味着索引 0 通常是主容器,别在别处误用同一个槽位。 文本标签那边用了 Trebuchet 字体、字号 11、宽 200、居中、颜色 C'0,100,255',并绑定到指定 tab。按钮则固定 100×25 像素、aliceBlue 底、边框 C'0,100,255',标题写死 "Date range"。这三个 Create 函数都返回 bool,调用方必须判返回值,否则界面元素缺失也不会报错。 外汇与贵金属行情波动剧烈、杠杆风险高,这类界面只解决信息展示,不涉及任何方向判断。
m_table_symb.XSize(class="num">120); m_table_symb.TableSize(class="num">1,ROWS1_TOTAL); m_table_symb.ColumnsWidth(width); m_table_symb.TextAlign(align); m_table_symb.FontSize(class="num">10); m_table_symb.TextXOffset(text_x_offset); m_table_symb.ShowHeaders(true); m_table_symb.SelectableRow(true); m_table_symb.IsWithoutDeselect(true); m_table_symb.IsZebraFormatRows(clrWhiteSmoke); m_table_symb.AutoYResizeMode(true); m_table_symb.AutoYResizeBottomOffset(class="num">3); m_table_symb.HeadersColor(C&class="macro">#x27;class="num">0,class="num">130,class="num">255&class="macro">#x27;); m_table_symb.HeadersColorHover(clrCornflowerBlue); m_table_symb.HeadersTextColor(clrWhite); m_table_symb.BorderColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;); class=class="str">"cmt">//--- Creating a control if(!m_table_symb.CreateTable(x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,m_table_symb); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates a text label in the first tab | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateTextLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text,class="type">int tab) { class=class="str">"cmt">//--- Save the window pointer text_label.MainPointer(m_tabs1); class=class="str">"cmt">//--- Attach to tab m_tabs1.AddToElementsArray(tab,text_label); class=class="str">"cmt">//--- text_label.Font("Trebuchet"); text_label.FontSize(class="num">11); text_label.XSize(class="num">200); text_label.LabelColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;); text_label.IsCenterText(true); class=class="str">"cmt">//--- Creation of a button if(!text_label.CreateTextLabel(label_text,x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add the element pointer to the data base CWndContainer::AddToElementsArray(class="num">0,text_label); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates a button to show the date range selection window | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateButton(CButton &button,const class="type">int x_gap,const class="type">int y_gap) { class=class="str">"cmt">//--- Save the pointer to the main control button.MainPointer(m_tabs1); class=class="str">"cmt">//--- Attach to tab m_tabs1.AddToElementsArray(class="num">0,button); class=class="str">"cmt">//--- Properties button.XSize(class="num">100); button.YSize(class="num">25); button.FontSize(class="num">11); button.IsHighlighted(false); button.IsCenterText(true); button.BorderColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;); button.BackColor(clrAliceBlue); class=class="str">"cmt">//--- Creating a control if(!button.CreateButton("Date range",x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add the element pointer to the data base CWndContainer::AddToElementsArray(class="num">0,button); class="kw">return(true); }
「周期下拉与勾选控件的挂载细节」
在自定义面板里塞周期选择器,核心是把 21 个周期字符串先写死进数组再灌给 combobox。MQL5 标准库里从 M1 到 MN 正好 21 档,代码里 timeframe_names[21] 的长度和 ItemsTotal(21) 必须对上,少一个就会在 SetValue 循环里越界。
下拉框本体尺寸定成宽 50 高 25、字号 12,按钮字号压到 10、背景用 clrAliceBlue,悬停高亮靠 lv.LightsHover(true) 打开。默认选中第 0 项即 M1,若想面板一开就停在当前图表周期,得把 SelectItem(0) 换成对应索引。
复选框走另一套 CreateCheckBox:挂到同一个 tab 容器后,设 GreenCheckBox(true) 和 IsPressed(true) 让它默认勾上,标签色用 C'0,100,255' 的蓝。两个控件都通过 CWndContainer::AddToElementsArray(0,...) 进总组,参数 0 代表第一个标签页——外汇和贵金属品种上挂这类 UI 属于轻量改造,但 MT5 自定义面板在实盘重载时可能丢状态,高风险操作前先于模拟盘验证。
class="type">bool CProgram::CreateTimeframe1(const class="type">int x_gap,const class="type">int y_gap) { class=class="str">"cmt">//--- Pass the object to the panel m_timeframe1.MainPointer(m_tabs1); class=class="str">"cmt">//--- Attach to tab m_tabs1.AddToElementsArray(class="num">0,m_timeframe1); class=class="str">"cmt">//--- Array of the item values in the list view 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" }; class=class="str">"cmt">//--- Set properties before creation m_timeframe1.XSize(class="num">50); m_timeframe1.YSize(class="num">25); m_timeframe1.ItemsTotal(class="num">21); m_timeframe1.FontSize(class="num">12); m_timeframe1.LabelColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;); CButton *but=m_timeframe1.GetButtonPointer(); but.FontSize(class="num">10); but.XSize(class="num">50); but.BackColor(clrAliceBlue); but.XGap(class="num">1); m_timeframe1.GetListViewPointer().FontSize(class="num">10); class=class="str">"cmt">//--- Save the item values in the combobox list view for(class="type">int i=class="num">0; i<class="num">21; i++) m_timeframe1.SetValue(i,timeframe_names[i]); class=class="str">"cmt">//--- Get the list view pointer CListView *lv=m_timeframe1.GetListViewPointer(); class=class="str">"cmt">//--- Set the list view properties lv.LightsHover(true); m_timeframe1.SelectItem(class="num">0); class=class="str">"cmt">//--- Creating a control if(!m_timeframe1.CreateComboBox("",x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,m_timeframe1); class="kw">return(true); } class="type">bool CProgram::CreateCheckBox(CCheckBox &checkbox,const class="type">int x_gap,const class="type">int y_gap,const class="type">class="kw">string text) { class=class="str">"cmt">//--- Save the pointer to the main control checkbox.MainPointer(m_tabs1); class=class="str">"cmt">//--- Attach to tab m_tabs1.AddToElementsArray(class="num">0,checkbox); class=class="str">"cmt">//--- Properties checkbox.YSize(class="num">25); checkbox.GreenCheckBox(true); checkbox.IsPressed(true); checkbox.FontSize(class="num">12); checkbox.LabelColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;); checkbox.LabelColorPressed(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;); class=class="str">"cmt">//--- Creating a control if(!checkbox.CreateCheckBox(text,x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add the element pointer to the data base CWndContainer::AddToElementsArray(class="num">0,checkbox); class="kw">return(true); }
◍ 下拉框里塞进 32 种形态与 9 类信号源
在 MT5 自定义面板里做形态筛选器,核心是先建两个组合框:一个装形态名,一个装信号源。第一段代码 PatternType1 用 ITEMS_TOTAL1 宏写死 32 个选项,M1–M16 与 W1–W16 分别覆盖分钟与周线级别的形态标签,下拉列表宽度 200、行高 25,字体 10 号,按钮底色 clrAliceBlue。 循环里把 pattern_names 数组逐项写进组合框列表视图,再用 SelectItem(0) 默认勾中第一项 M1;列表视图开了 LightsHover(true),鼠标悬停会高亮。CreateComboBox 失败直接返回 false,成功才挂到对象容器,这套写法能保证面板初始化时不卡壳。 第二个函数 AppliedType1 只放 9 个信号源:Price、ATR、CCI、DeMarker、Force Ind、WPR、RSI、Momentum、Custom。它绑到 tab 0,尺寸和字体设置和形态框一致,方便交易者在同一行对照「形态 × 信号源」做条件组合。外汇与贵金属波动剧烈,这类面板只是辅助筛样,实盘信号仍需人工复核,杠杆品种高风险不可忽视。
class="type">bool CProgram::PatternType1(const class="type">int x_gap,const class="type">int y_gap,const class="type">int tab) { class=class="str">"cmt">//--- Total number of the list items class="macro">#define ITEMS_TOTAL1 class="num">32 class=class="str">"cmt">//--- Pass the object to the panel m_combobox1.MainPointer(m_tabs1); class=class="str">"cmt">//--- Attach to tab m_tabs1.AddToElementsArray(tab,m_combobox1); class=class="str">"cmt">//--- Array of the item values in the list view class="type">class="kw">string pattern_names[ITEMS_TOTAL1]= { "M1","M2","M3","M4","M5","M6","M7","M8", "M9","M10","M11","M12","M13","M14","M15","M16", "W1","W2","W3","W4","W5","W6","W7","W8", "W9","W10","W11","W12","W13","W14","W15","W16" }; class=class="str">"cmt">//--- Set properties before creation m_combobox1.XSize(class="num">200); m_combobox1.YSize(class="num">25); m_combobox1.ItemsTotal(ITEMS_TOTAL1); m_combobox1.GetButtonPointer().FontSize(class="num">10); m_combobox1.GetButtonPointer().BackColor(clrAliceBlue); m_combobox1.GetListViewPointer().FontSize(class="num">10); class=class="str">"cmt">//--- Save the item values in the combobox list view for(class="type">int i=class="num">0; i<ITEMS_TOTAL1; i++) m_combobox1.SetValue(i,pattern_names[i]); class=class="str">"cmt">//--- Get the list view pointer CListView *lv=m_combobox1.GetListViewPointer(); class=class="str">"cmt">//--- Set the list view properties lv.LightsHover(true); m_combobox1.SelectItem(class="num">0); class=class="str">"cmt">//--- Creating a control if(!m_combobox1.CreateComboBox("",x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,m_combobox1); class="kw">return(true); } class="type">bool CProgram::AppliedType1(const class="type">int x_gap,const class="type">int y_gap) { class=class="str">"cmt">//--- Pass the object to the panel m_applied1.MainPointer(m_tabs1); class=class="str">"cmt">//--- Attach to tab m_tabs1.AddToElementsArray(class="num">0,m_applied1); class=class="str">"cmt">//--- Array of the item values in the list view class="type">class="kw">string pattern_names[class="num">9]= { "Price","ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum","Custom" }; class=class="str">"cmt">//--- Set properties before creation m_applied1.XSize(class="num">200); m_applied1.YSize(class="num">25); m_applied1.ItemsTotal(class="num">9); m_applied1.GetButtonPointer().FontSize(class="num">10); m_applied1.GetButtonPointer().BackColor(clrAliceBlue); m_applied1.GetListViewPointer().FontSize(class="num">10);
把控件塞进标签页的实操写法
下拉框这里先循环把 9 个形态名写进列表,再拿 CListView 指针开 hover 高亮,默认选中第 0 项。CreateComboBox 失败直接返回 false,成功才丢进公共元素数组,这套顺序在 MT5 自定义面板里少一步都会白屏。 文本输入框的宽度锁在 210、高度 24,标签色用 C'0,100,255',字号 12;边界值设了 MinValue(10) 和 MaxValue(1000),SpinEditMode 开true 后用户能用箭头调参,TextBox 的 XGap 给了 100 像素避免和标签重叠。 Frame 容器挂到 tab 索引 1,尺寸 350×500,开了 AutoYResizeMode 且底部留 100 偏移,文本标签宽度单独设 250。外汇和贵金属图表加载这类 UI 高风险,参数越界可能让 EA 在实盘挂掉,建议先在策略测试器里跑一遍。 下面三段是可直接抄的构建函数,改 x_gap/y_gap 就能挪位置,不用动整体结构。
class=class="str">"cmt">//--- Save the item values in the combobox list view for(class="type">int i=class="num">0; i<class="num">9; i++) m_applied1.SetValue(i,pattern_names[i]); class=class="str">"cmt">//--- Get the list view pointer CListView *lv=m_applied1.GetListViewPointer(); class=class="str">"cmt">//--- Set the list view properties lv.LightsHover(true); m_applied1.SelectItem(class="num">0); class=class="str">"cmt">//--- Creating a control if(!m_applied1.CreateComboBox("",x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,m_applied1); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates an input field | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateEditValue(CTextEdit &text_edit,const class="type">int x_gap,const class="type">int y_gap,const class="type">class="kw">string label_text,const class="type">int value,const class="type">int tab) { class=class="str">"cmt">//--- Save the pointer to the main control text_edit.MainPointer(m_tabs1); class=class="str">"cmt">//--- Attach to tab m_tabs1.AddToElementsArray(tab,text_edit); class=class="str">"cmt">//--- Properties text_edit.XSize(class="num">210); text_edit.YSize(class="num">24); text_edit.LabelColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;); text_edit.FontSize(class="num">12); text_edit.MaxValue(class="num">1000); text_edit.MinValue(class="num">10); text_edit.SpinEditMode(true); text_edit.SetValue((class="type">class="kw">string)value); text_edit.GetTextBoxPointer().AutoSelectionMode(true); text_edit.GetTextBoxPointer().XGap(class="num">100); class=class="str">"cmt">//--- Creating a control if(!text_edit.CreateTextEdit(label_text,x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,text_edit); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateFrame(CFrame &frame,const class="type">class="kw">string text,const class="type">int x_gap,const class="type">int y_gap) { class=class="str">"cmt">//--- Save the pointer to the main control frame.MainPointer(m_tabs1); class=class="str">"cmt">//--- Attach to tab m_tabs1.AddToElementsArray(class="num">1,frame); class=class="str">"cmt">//--- frame.XSize(class="num">350); frame.YSize(class="num">500); frame.LabelColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;); frame.BorderColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;); frame.FontSize(class="num">11); frame.AutoYResizeMode(true); frame.AutoYResizeBottomOffset(class="num">100); frame.GetTextLabelPointer().XSize(class="num">250); class=class="str">"cmt">//--- Creating a control if(!frame.CreateFrame(text,x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,frame); class="kw">return(true); }