监视多币种的交易信号 (第二部分) : 应用程序可视部分的实现·综合运用
(3/3)· 把前两篇的架构草稿变成能用的 GUI,品种、周期、信号三步配置一次跑通
「周期分组按钮的互斥与复选联动」
在自定义面板里用三个 currency_set 按钮做周期分组切换时,核心逻辑是互斥按下加复选框批量同步。以「Junior Timeframes」分支为例:当检测到 m_currency_set[1] 被按下,先把索引 0 和 2 的两个分组按钮置为未按下并 Update,避免多组同时高亮导致状态混乱。 接着代码定义了一个 11 元素的字符串数组 pairs,覆盖 M1 到 M30 共 11 个短周期标签。随后用两层循环先清空全部 21 个复选框的按下态,再遍历比对 LabelText,命中短周期标签的复选框才重新 IsPressed(true) 并刷新。 这种写法意味着你的复选框总数固定为 21,而 junior 组只映射其中 11 个。开 MT5 把这段直接塞进 OnEvent 的 lparam 分支里,改 pairs 数组就能重排周期分组,外汇与贵金属品种波动大、杠杆高,面板只是辅助筛周期,下单前仍要自判风险。
m_currency_set[class="num">1].Update(true); m_currency_set[class="num">2].Update(true); class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<class="num">21; i++) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } class=class="str">"cmt">//--- Junior Timeframes else if(lparam==m_currency_set[class="num">1].Id() && m_currency_set[class="num">1].IsPressed()) { m_currency_set[class="num">0].IsPressed(false); m_currency_set[class="num">2].IsPressed(false); m_currency_set[class="num">0].Update(true); m_currency_set[class="num">2].Update(true); class=class="str">"cmt">//--- class="type">class="kw">string pairs[class="num">11]= { "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30" }; class=class="str">"cmt">//--- Clear the selection for(class="type">int i=class="num">0; i<class="num">21; i++) { m_checkbox[i].IsPressed(false); m_checkbox[i].Update(true); } class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<class="num">21; i++) { for(class="type">int j=class="num">0; j<class="num">11; j++) if(m_checkbox[i].LabelText()==pairs[j]) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } } class=class="str">"cmt">//--- Senior Timeframes else if(lparam==m_currency_set[class="num">2].Id() && m_currency_set[class="num">2].IsPressed()) { m_currency_set[class="num">0].IsPressed(false); m_currency_set[class="num">1].IsPressed(false);
◍ 币种面板与周期复选框的初始化套路
在信号监视器的引导流程里,第一步要先刷新两组币种集合的状态,再把周期复选框清掉重选。下面这段逻辑先把 m_currency_set[0] 和 [1] 强制 Update(true),保证界面上的「All / Majors」按钮显示最新 pressed 状态,随后用一个 10 元素的字符串数组 pairs 容纳从 H1 到 MN 的全部周期标签。 清空动作分两层:先用 for 循环把 m_all_symbols 个复选框全部 IsPressed(false) 并 Update,相当于把用户上一次勾选痕迹抹平;再跑一层双重循环,把 LabelText 命中 pairs 里周期名的复选框重新 IsPressed(true),实现「默认只亮周期、不亮币种」的界面基线。 ToStep_1 函数接管后,头部标签改成 "Signal Monitor Step 1: Choose Symbols",Back 按钮直接 Hide,避免用户在首步回退。它先对 21 个复选框做取消勾选,再按 SymbolName(i,false) 把 m_all_symbols 个真实交易品种名填进复选框并显示出来——这里硬写 21 是历史最大值,若你 MT5 品种数超过 21,数组越界会静默失败,得自己改上限。 三组快捷按钮名被改成了 "All" / "Majors" / "Crosses",循环里顺手把 m_currency_set[i] 的 pressed 复位。外汇与贵金属波动剧烈、杠杆风险高,这类界面预设仅降低手动操作成本,不构成任何方向暗示,跑之前请在策略测试器用少量品种验证循环边界。
m_currency_set[class="num">0].Update(true); m_currency_set[class="num">1].Update(true); class=class="str">"cmt">//--- class="type">class="kw">string pairs[class="num">10]= { "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN" }; class=class="str">"cmt">//--- Clear the selection for(class="type">int i=class="num">0; i<m_all_symbols; i++) { m_checkbox[i].IsPressed(false); m_checkbox[i].Update(true); } class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<m_all_symbols; i++) { for(class="type">int j=class="num">0; j<class="num">10; j++) if(m_checkbox[i].LabelText()==pairs[j]) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } } class="type">void CProgram::ToStep_1(class="type">void) { class=class="str">"cmt">//--- Change header m_step_window.LabelText("Signal Monitor Step class="num">1: Choose Symbols"); m_step_window.Update(true); class=class="str">"cmt">//--- Hide the Back button m_back_button.Hide(); class=class="str">"cmt">//--- Clear selection for(class="type">int i=class="num">0; i<class="num">21; i++) { m_checkbox[i].IsPressed(false); m_checkbox[i].Update(true); } class=class="str">"cmt">//--- Show elements of Step class="num">1 for(class="type">int i=class="num">0; i<m_all_symbols; i++) { m_checkbox[i].Show(); m_checkbox[i].LabelText(SymbolName(i,false)); m_checkbox[i].Update(true); } class="type">class="kw">string names[class="num">3]= {"All","Majors","Crosses"}; class=class="str">"cmt">//--- Change names of selection buttons for(class="type">int i=class="num">0; i<class="num">3; i++) { m_currency_set[i].IsPressed(false); m_currency_set[i].LabelText(names[i]);
更新货币组并切入模板配置
在货币组对象数组上调用 Update(true) 时,true 参数强制立即刷新报价与属性,而不是等下一帧周期。循环里逐个 m_currency_set[i] 都这样处理,能保证面板刚弹出时所有品种状态是最新的,避免看到滞后点差。 随后把文本编辑框、加载按钮、保存按钮全部 Show(),这一步只是把模板管理区块从隐藏态切到可见态,不涉及任何读写。最后把 m_current_step 置为 1,意味着向导流程重新定位到第一步,用户后续操作会按这个步骤变量走分支。 这段逻辑本身不触发交易,但步骤变量若初始化错,后面加载模板会直接跳错。开 MT5 挂上面板后,可在 OnClick 里断点看 m_current_step 是否真的落为 1。
m_currency_set[i].Update(true); } class=class="str">"cmt">//--- Show block for working with templates m_text_edit.Show(); m_load_button.Show(); m_save_button.Show(); class=class="str">"cmt">//--- Set the current setup step m_current_step=class="num">1; }
「把添加信号的按钮和标题挂进步骤窗口」
第三步界面只做一件事:在分步窗口里放一个“Add Signal”按钮和一行信号列表标题。先在 Program.mqh 的 CProgram 里声明 m_add_signal(CButton)与 m_signal_header(CTextLabel),再在 StepWindow.mqh 末尾补上两个创建函数,并在 CreateStepWindow() 里调用,让程序启动时就把控件建出来。 启动后默认处在第一步,所以在 CreateGUI() 末尾要把这两个元素先 Hide(),避免过早露面。等用户走完前两步、触发 ToStep_3() 时,再清掉上一步可视化对象并显示它们。注意:前两步若没选完必填项,应用会拦住你进第三步。 下面这段是按钮与标签的落地代码。CreateIconButton 里按钮尺寸锁死为宽 110、高 30,图标用 plus.bmp(16×16 图标资源),底色 C'70,180,70'、按下 C'70,170,70',文字白色居中;CreateLabel 的标签宽 120,背景沿用 m_background。最后在窗口坐标 (10,30) 生成按钮,返回 false 即代表创建失败。 编译后双击 Next 进第三步,能在 MT5 里直接看到带加号图标的绿色按钮。外汇与贵金属 GUI 自动化涉及实盘逻辑,参数误用可能引发错单,属高风险操作,验证时建议先用模拟账户。
CButton m_add_signal; class=class="str">"cmt">//--- CTextLabel m_signal_header; class="type">bool CreateIconButton(CButton &button,class="type">class="kw">string text,const class="type">int x_gap,const class="type">int y_gap); class=class="str">"cmt">//--- Text label class="type">bool CreateLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates a button with an image | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\plus.bmp" class="type">bool CProgram::CreateIconButton(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="type">color baseclr=C&class="macro">#x27;class="num">70,class="num">180,class="num">70&class="macro">#x27;; class="type">color pressed=C&class="macro">#x27;class="num">70,class="num">170,class="num">70&class="macro">#x27;; class=class="str">"cmt">//--- Save the window pointer button.MainPointer(m_step_window); class=class="str">"cmt">//--- Set properties before creation button.XSize(class="num">110); button.YSize(class="num">30); button.Font(m_base_font); button.FontSize(m_base_font_size); button.IconXGap(class="num">3); button.IconYGap(class="num">7); button.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\plus.bmp"); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(pressed); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(pressed); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); class=class="str">"cmt">//--- Create a control if(!button.CreateButton(text,x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add a pointer to element to the base CWndContainer::AddToElementsArray(class="num">0,button); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates the text label | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text) { class=class="str">"cmt">//--- Save the window pointer text_label.MainPointer(m_step_window); class=class="str">"cmt">//--- text_label.Font(m_base_font); text_label.FontSize(m_base_font_size); text_label.XSize(class="num">120); text_label.BackColor(m_background); class=class="str">"cmt">//--- Create the button if(!text_label.CreateTextLabel(label_text,x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add a pointer to element to the base CWndContainer::AddToElementsArray(class="num">0,text_label); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- if(!CreateIconButton(m_add_signal,"Add Signal",class="num">10,class="num">30)) class="kw">return(false);
◍ 三步向导里的界面显隐逻辑
这段 MQL5 片段来自一个信号监控程序的 GUI 构建流程,核心在 CreateGUI 与 ToStep_3 两个方法里控制窗口元素的显示与隐藏。CreateGUI 里把返回按钮、添加信号键和信号列表头直接 Hide,说明初始向导阶段这些控件不该露面。 [CODE] if(!CreateLabel(m_signal_header,10,30+30+10,"Signal List")) return(false); //+------------------------------------------------------------------+
| // | Creates the graphical interface of the program |
|---|
//+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Step 1-3 if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols")) return(false); //--- Finish the creation of GUI CWndEvents::CompletedGUI(); m_back_button.Hide(); m_add_signal.Hide(); m_signal_header.Hide(); return(true); } //+------------------------------------------------------------------+
| // |
|---|
//+------------------------------------------------------------------+ void CProgram::ToStep_3(void) { //--- Check whether at least one timeframe is selected int cnt=0; for(int i=0; i<21; i++) { if(m_checkbox[i].IsPressed()) cnt++; } if(cnt<1) { MessageBox("No timeframes selected!","Warning"); return; } //--- m_step_window.LabelText("Signal Monitor Step 3: Create Signals"); m_step_window.Update(true); m_next_button.LabelText("Create"); m_next_button.Update(true); //--- Hide elements of Step 2 for(int i=0; i<21; i++) { if(i<3) m_currency_set[i].Hide(); m_checkbox[i].Hide(); } //--- m_add_signal.Show(); m_signal_header.Show(); //--- m_current_step=3; } [/CODE] 逐行看 CreateGUI:先尝试在坐标 (10, 70) 处给信号头创建标签,失败直接返回 false;随后建第一步选符号的窗口,成功才调 CompletedGUI 标记界面就绪,并把三个后续控件藏起。 ToStep_3 先遍历 21 个复选框统计被按下的数量,少于 1 就弹“No timeframes selected!”警告并退出,确保至少选了一个周期。接着把步骤窗标题改成第三步、下一步按钮改名“Create”,并隐藏第二步的 3 个货币集与全部 21 个复选框,再 Show 出添加信号与信号头,最后把当前步置为 3。 开 MT5 把这段塞进自己的 CProgram 类,断点跟一下 m_current_step 从 1 到 3 的跳转,能确认隐藏逻辑是否如预期——外汇与贵金属品种多、周期杂,界面状态错乱可能让信号漏触发,这类 GUI bug 在高波动时段尤其难排查。
if(!CreateLabel(m_signal_header,class="num">10,class="num">30+class="num">30+class="num">10,"Signal List")) class="kw">return(false); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates the graphical interface of the program | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateGUI(class="type">void) { class=class="str">"cmt">//--- Step class="num">1-class="num">3 if(!CreateStepWindow("Signal Monitor Step class="num">1: Choose Symbols")) class="kw">return(false); class=class="str">"cmt">//--- Finish the creation of GUI CWndEvents::CompletedGUI(); m_back_button.Hide(); m_add_signal.Hide(); m_signal_header.Hide(); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::ToStep_3(class="type">void) { class=class="str">"cmt">//--- Check whether at least one timeframe is selected class="type">int cnt=class="num">0; for(class="type">int i=class="num">0; i<class="num">21; i++) { if(m_checkbox[i].IsPressed()) cnt++; } if(cnt<class="num">1) { MessageBox("No timeframes selected!","Warning"); class="kw">return; } class=class="str">"cmt">//--- m_step_window.LabelText("Signal Monitor Step class="num">3: Create Signals"); m_step_window.Update(true); m_next_button.LabelText("Create"); m_next_button.Update(true); class=class="str">"cmt">//--- Hide elements of Step class="num">2 for(class="type">int i=class="num">0; i<class="num">21; i++) { if(i<class="num">3) m_currency_set[i].Hide(); m_checkbox[i].Hide(); } class=class="str">"cmt">//--- m_add_signal.Show(); m_signal_header.Show(); class=class="str">"cmt">//--- m_current_step=class="num">3; }
搭一个信号编辑的独立窗口
信号编辑的可视组件都挂在 SetWindow.mqh 里,但它现在只通过 #include 连了 Program.mqh。先在 Program.mqh 的 CProgram 类里声明一个 CWindow 实例 m_set_window,再补一个 CreateSetWindow() 负责把窗口本体造出来,返回 bool 表示成败。 窗口尺寸定死为 568×555 像素,标题栏高 22,横向坐标取图表宽度减半再减窗宽一半,纵向贴着 y=30。类型用 W_DIALOG,可拖动,配色走统一背景与标题色,这些写死在方法里方便后面照抄。 CreateGUI() 中插入对 CreateSetWindow("Signal Monitor Edit Signal") 的调用;点“Add Signal”按钮时在 OnEvent() 里触发 m_set_window.OpenWindow(),编译后第 3 步点按钮就会弹出这个编辑窗。 窗口里第一块是选指标类型,从 7 种标准振荡器里挑。做法一样:建类实例、写创建方法,在 CreateSetWindow() 末尾调 CreateIndicatorType()。随后是指标设置板块(周期、适用价格)和信号设置板块(规则、标签色、提示、图形标记、时间帧等八项),均在同文件里追加变量与方法,并在窗口方法末尾统一调用。 交互上要处理几件事:时间帧按钮只显示前面步骤选中的数字;点“Value”会弹起“Text”并隐藏文本输入;颜色按钮拉出调色板;取消勾选时相关字段失活。这些在 OnEvent() 里用循环扫四个按钮数组实现。外汇与贵金属 GUI 自动化涉及实盘逻辑,误触可能造成下单风险,验证时先用模拟账户。
CWindow m_set_window; class="type">bool CreateSetWindow(const class="type">class="kw">string caption_text); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates a window for creating and editing trading signals | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateSetWindow(const class="type">class="kw">string text) { class=class="str">"cmt">//--- Add the pointer to the window array CWndContainer::AddWindow(m_set_window); class=class="str">"cmt">//--- Properties m_set_window.XSize(class="num">568); m_set_window.YSize(class="num">555); class=class="str">"cmt">//--- Coordinates class="type">int x=class="type">int(ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)-m_set_window.XSize())/class="num">2; class="type">int y=class="num">30; class=class="str">"cmt">//--- m_set_window.CaptionHeight(class="num">22); m_set_window.IsMovable(true); m_set_window.CaptionColor(m_caption); m_set_window.CaptionColorLocked(m_caption); m_set_window.CaptionColorHover(m_caption); m_set_window.BackColor(m_background); m_set_window.FontSize(m_base_font_size); m_set_window.Font(m_base_font); m_set_window.WindowType(W_DIALOG); class=class="str">"cmt">//--- Creating a form if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y)) class="kw">return(false); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates the graphical interface of the program | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateGUI(class="type">void) { class=class="str">"cmt">//--- Step class="num">1-class="num">3 if(!CreateStepWindow("Signal Monitor Step class="num">1: Choose Symbols")) class="kw">return(false); class=class="str">"cmt">//--- Creation and editing window if(!CreateSetWindow("Signal Monitor Edit Signal")) class="kw">return(false); class=class="str">"cmt">//--- Finish the creation of GUI CWndEvents::CompletedGUI(); m_back_button.Hide(); m_add_signal.Hide(); m_signal_header.Hide(); class="kw">return(true); } class=class="str">"cmt">//--- Click on the "Add Signal" button if(lparam==m_add_signal.Id()) { m_set_window.OpenWindow(); } class=class="str">"cmt">//--- Drop-down menu
「给面板塞一个指标类型下拉框」
在 MT5 自定义面板里做指标切换,最省事的办法是用 CComboBox 封装一个下拉控件。下面这段逻辑把 7 种常见震荡类指标名写死进数组,运行时直接渲染成可选项,交易者点一下就能换品种分析口径。 代码里先给组合框定尺寸:宽 200、高 26,标签上间距 4,列表项总数 7,字体走基类 m_base_font。按钮区单独设了白底、宽 100,列表视区每项高 26 且开启悬停高亮(LightsHover(true)),这些数值直接决定面板在 125% 缩放下是否挤版。 注意 m_indicator_type.SelectItem(5) 这行——它默认选中数组下标 5,也就是 RSI。如果你习惯先看 WPR 或 ATR,把数字改成对应下标即可,下标从 0 计(ATR=0,Momentum=6)。 创建完后用 CWndContainer::AddToElementsArray(1,m_indicator_type) 把控件挂进元素组,外部调用 CreateIndicatorType(10,32) 就能以左上偏移 (10,32) 落位。外汇与贵金属波动大、杠杆高,这类面板仅作辅助筛选,信号本身不预示单边行情。
CComboBox m_indicator_type; class=class="str">"cmt">//--- Creates a drop-down method class="type">bool CreateIndicatorType(const class="type">int x_gap,const class="type">int y_gap); 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">//--- Array of the item values in the list view class="type">class="kw">string pattern_names[class="num">7]= { "ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum" }; class=class="str">"cmt">//--- Set 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(class="num">7); 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">26); class=class="str">"cmt">//--- Store the item values in the combo box list view for(class="type">int i=class="num">0; i<class="num">7; i++) m_indicator_type.SetValue(i,pattern_names[i]); class=class="str">"cmt">//--- Get the list 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">5); class=class="str">"cmt">//--- Create a control if(!m_indicator_type.CreateComboBox("Indicator Type",x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add an object to the common array of object groups CWndContainer::AddToElementsArray(class="num">1,m_indicator_type); class="kw">return(true); }
◍ 下拉选指标时界面跟着变
在自定义面板里用组合框切换指标类型时,MT5 的 GUI 逻辑要靠 CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM 事件来捕捉点击。拿到选中项索引后,用 switch 分流:ATR、DeMarker、WPR、Momentum 这类不依赖应用价格的指标,会把周期输入框标签改成对应名字,同时隐藏 applied_price 控件;而 CCI、Force Index、RSI 则需要显示 applied_price 让用户选收盘价或典型价。 代码里 default 分支兜底成 RSI Period 并隐藏价格选择,说明作者把 RSI 当缺省信号。注意 m_period_edit.Update(true) 必须放在 switch 外、统一刷新,否则标签文本可能不重绘。 信号设置区另起一组控件:CreateSetLabel 在 y=22+10+4*(25+10)=162 像素处写“2.Signal Settings”标题;条件行由 CreateRuleValue(130, 197) 与 CreateRule(10, 197) 生成,197 来自 22+10+5*(25+10)。这种坐标硬编码方式在 4K 屏或缩放改动时可能错位,调试时建议先 Print 实际像素再改常量。 外汇与贵金属波动剧烈、杠杆高风险大,此类面板仅用于策略原型验证,实盘前请在模拟盘跑通事件响应。
class=class="str">"cmt">//--- Item selection in the combobox list if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { class="type">int index=m_indicator_type.GetListViewPointer().SelectedItemIndex(); class="kw">switch(index) { case class="num">0: m_period_edit.LabelText("ATR Period"); m_applied_price.Hide(); break; case class="num">1: m_period_edit.LabelText("CCI Period"); m_applied_price.Show(); break; case class="num">2: m_period_edit.LabelText("DeMarker Period"); m_applied_price.Hide(); break; case class="num">3: m_period_edit.LabelText("Force Index Period"); m_applied_price.Show(); break; case class="num">4: m_period_edit.LabelText("WPR Period"); m_applied_price.Hide(); break; case class="num">5: m_period_edit.LabelText("RSI Period"); m_applied_price.Show(); break; case class="num">6: m_period_edit.LabelText("Momentum Period"); m_applied_price.Hide(); break; class="kw">default: m_period_edit.LabelText("RSI Period"); m_applied_price.Hide(); break; } m_period_edit.Update(true); } class=class="str">"cmt">//--- Signal settings if(!CreateSetLabel(m_set_header[class="num">1],class="num">10,class="num">22+class="num">10+class="num">4*(class="num">25+class="num">10),"class="num">2.Signal Settings")) class="kw">return(false); CTextEdit m_rule_value; CComboBox m_rule_type; ... class="type">bool CreateRuleValue(const class="type">int x_gap,const class="type">int y_gap); class="type">bool CreateRule(const class="type">int x_gap,const class="type">int y_gap); class=class="str">"cmt">//--- Condition settings if(!CreateRuleValue(class="num">130,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10))) class="kw">return(false); if(!CreateRule(class="num">10,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10))) class="kw">return(false);
对话框尺寸与控件坐标的硬编码布局
在 MT5 自定义指标面板里,设置窗口的几何参数往往是写死的。下面这段 CreateSetWindow 把对话框宽高锁在 568×575 像素,标题栏高 22,距图表顶部 30 像素,水平居中靠 ChartGetInteger 取图表宽再减自身宽除 2 算 x 坐标。 控件纵向排布用了一个隐蔽节奏:以 22(标题)+10(上边距)为基线,每个区块占 25+10 的行高步进。比如『Indicator Settings』标签落在 22+10+26+10,周期输入框在 22+10+2*(25+10)=100,应用价格在下一组 125,信号设置标题在 22+10+4*35=172。改布局时直接调这些常数比拖控件快。 外汇与贵金属图表加载这类 GUI 高风险:像素写死在 4K 屏或缩放异于 100% 的终端上可能错位,实盘前务必在目标分辨率开 MT5 验证一遍。 别把 568×575 当通用尺寸 不同指标字段数量差很多,硬抄这个窗口大小会让下部控件溢出。按你自己 CreateRule / CreateButton2 的实际个数重算 YSize,公式就是基线 + N*(25+10) + 余量。
class="type">bool CProgram::CreateSetWindow(const class="type">class="kw">string text) { class=class="str">"cmt">//--- Add the pointer to the window array CWndContainer::AddWindow(m_set_window); class=class="str">"cmt">//--- Properties m_set_window.XSize(class="num">568); m_set_window.YSize(class="num">575); class=class="str">"cmt">//--- Coordinates class="type">int x=class="type">int(ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)-m_set_window.XSize())/class="num">2; class="type">int y=class="num">30; class=class="str">"cmt">//--- m_set_window.CaptionHeight(class="num">22); m_set_window.IsMovable(true); m_set_window.CaptionColor(m_caption); m_set_window.CaptionColorLocked(m_caption); m_set_window.CaptionColorHover(m_caption); m_set_window.BackColor(m_background); m_set_window.FontSize(m_base_font_size); m_set_window.Font(m_base_font); m_set_window.WindowType(W_DIALOG); class=class="str">"cmt">//--- Creating a form if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y)) class="kw">return(false); class=class="str">"cmt">//--- Indicator type if(!CreateIndicatorType(class="num">10,class="num">22+class="num">10)) class="kw">return(false); class=class="str">"cmt">//--- Settings of the selected indicator if(!CreateSetLabel(m_set_header[class="num">0],class="num">10,class="num">22+class="num">10+class="num">26+class="num">10,"class="num">1.Indicator Settings")) class="kw">return(false); if(!CreatePeriodEdit(class="num">10,class="num">22+class="num">10+class="num">2*(class="num">25+class="num">10))) class="kw">return(false); if(!CreateAppliedPrice(class="num">10,class="num">22+class="num">10+class="num">3*(class="num">25+class="num">10))) class="kw">return(false); class=class="str">"cmt">//--- Signal settings if(!CreateSetLabel(m_set_header[class="num">1],class="num">10,class="num">22+class="num">10+class="num">4*(class="num">25+class="num">10),"class="num">2.Signal Settings")) class="kw">return(false); class=class="str">"cmt">//--- Condition settings if(!CreateRuleValue(class="num">130,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10))) class="kw">return(false); if(!CreateRule(class="num">10,class="num">22+class="num">10+class="num">5*(class="num">25+class="num">10))) class="kw">return(false); class=class="str">"cmt">//--- Label display settings if(!CreateSetLabel(m_set_header[class="num">2],class="num">10,class="num">22+class="num">10+class="num">6*(class="num">25+class="num">10),"Label")) class="kw">return(false); if(!CreateButton2(m_label_button[class="num">0],"Value",class="num">100,class="num">22+class="num">7+class="num">6*(class="num">25+class="num">10))) class="kw">return(false); if(!CreateButton2(m_label_button[class="num">1],"Text",class="num">100+class="num">80,class="num">22+class="num">7+class="num">6*(class="num">25+class="num">10))) class="kw">return(false); class=class="str">"cmt">//--- Label class="type">color display settings
「面板控件的坐标排布与周期选择」
这段初始化逻辑把自定义面板的控件按纵向网格逐个塞进界面,行距用 (25+10)=35 像素的固定步长算出来,从 y=22+10 开始往下排。 先看标签和文本框:第 7 行位置放了 Label Color 的颜色按钮(x=10),文本框落在 x=270、y=22+7+6*35=282 附近;随后从索引 1 起,颜色按钮和勾选框成对出现,分别管背景、边框、提示框、图片,直到第 11 行 Use Image 配图像滑块。 周期选择段单独拉了一个 header,位置在 y=22+10+12*35=462;之后 y 重算为 22+10+13*35=497,用 k 计数、i 从 0 跑到 20 共 21 个周期按钮,i==11 时 y 跳到 22+20+14*35=532 做换行。 任何一个 Create* 返回 false 就直接 return(false),面板建不全则整个 OnInit 失败。开 MT5 把这段粘进 EA 的面板初始化函数,改 (25+10) 这个步长就能压扁或拉高控件间距。
if(!CreateColorButton(m_color_button[class="num">0],class="num">10,class="num">22+class="num">10+class="num">7*(class="num">25+class="num">10),"Label Color")) class="kw">return(false); if(!CreateTextBox(class="num">180+class="num">80+class="num">10,class="num">22+class="num">7+class="num">6*(class="num">25+class="num">10))) class="kw">return(false); class=class="str">"cmt">//--- if(!CreateColorButton(m_color_button[class="num">1],class="num">25,class="num">22+class="num">10+class="num">8*(class="num">25+class="num">10),"")) class="kw">return(false); if(!CreateSetCheckBox(m_set_param[class="num">0],class="num">10,class="num">22+class="num">10+class="num">8*(class="num">25+class="num">10),"Use Background")) class="kw">return(false); if(!CreateColorButton(m_color_button[class="num">2],class="num">25,class="num">22+class="num">10+class="num">9*(class="num">25+class="num">10),"")) class="kw">return(false); if(!CreateSetCheckBox(m_set_param[class="num">1],class="num">10,class="num">22+class="num">10+class="num">9*(class="num">25+class="num">10),"Use Border")) class="kw">return(false); if(!CreateColorButton(m_color_button[class="num">3],class="num">25,class="num">22+class="num">10+class="num">10*(class="num">25+class="num">10),"")) class="kw">return(false); if(!CreateSetCheckBox(m_set_param[class="num">2],class="num">10,class="num">22+class="num">10+class="num">10*(class="num">25+class="num">10),"Use Tooltip")) class="kw">return(false); if(!CreateTooltipText(class="num">240,class="num">22+class="num">10+class="num">10*(class="num">25+class="num">10))) class="kw">return(false); if(!CreateSetCheckBox(m_set_param[class="num">3],class="num">10,class="num">22+class="num">10+class="num">11*(class="num">25+class="num">10),"Use Image")) class="kw">return(false); if(!CreateImageSlider(class="num">125,class="num">22+class="num">10+class="num">11*(class="num">25+class="num">10))) class="kw">return(false); class=class="str">"cmt">//--- Timeframe selection if(!CreateSetLabel(m_set_header[class="num">4],class="num">10,class="num">22+class="num">10+class="num">12*(class="num">25+class="num">10),"Timeframes")) class="kw">return(false); class=class="str">"cmt">//--- y=class="num">22+class="num">10+class="num">13*(class="num">25+class="num">10); class="type">int k=class="num">0; for(class="type">int i=class="num">0; i<class="num">21; i++) { if(i==class="num">11) { y=class="num">22+class="num">20+class="num">14*(class="num">25+class="num">10);
◍ 多周期勾选后如何重建按钮阵列
面板里最多挂 21 个周期复选框,点「Add Signal」后会先弹窗,可用时调用 RebuildTimeframes() 按勾选状态重排周期按钮。 函数先跑两遍 0~20 的循环:第一遍数出被按下的复选框数量 cnt,用 ArrayResize(m_timeframes,cnt) 把周期数组缩到实际尺寸;第二遍把被按下的复选框 LabelText() 写进 m_timeframes,完成选中周期收集。 接下来把前 cnt 个周期按钮解锁并刷新文字,超出 cnt 的按钮直接 IsLocked(true) 锁死。这样界面上永远只露出当前选中的周期,多余按钮不会再被误触。 点击逻辑里还有一对互斥标签按钮:m_label_button[0] 按下时强制弹起 [1] 并隐藏文本框,[1] 按下时反过来弹起 [0] 并显示文本框。这种二选一写法在 MT5 自定义面板里很实用,复制时留意 Id() 比对必须精确。
k=class="num">0; } if(!CreateTfButton(m_tf_button[i],class="num">40*k+class="num">10,y)) class="kw">return(false); k++; } class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::RebuildTimeframes(class="type">void) { class=class="str">"cmt">//--- Count the number of selected timeframes class="type">int cnt=class="num">0; for(class="type">int i=class="num">0; i<class="num">21; i++) { if(m_checkbox[i].IsPressed()) cnt++; } ArrayResize(m_timeframes,cnt); cnt=class="num">0; class=class="str">"cmt">//--- Remember the selected timeframe to the array for(class="type">int i=class="num">0; i<class="num">21; i++) { if(m_checkbox[i].IsPressed()) { m_timeframes[cnt]=m_checkbox[i].LabelText(); cnt++; } } class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<cnt; i++) m_tf_button[i].IsLocked(false); class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<cnt; i++) { m_tf_button[i].LabelText(m_timeframes[i]); m_tf_button[i].Update(true); } class=class="str">"cmt">//--- for(class="type">int i=cnt; i<class="num">21; i++) m_tf_button[i].IsLocked(true); } class=class="str">"cmt">//--- Click on the "Add Signal" button if(lparam==m_add_signal.Id()) { m_set_window.OpenWindow(); if(m_set_window.IsAvailable()) RebuildTimeframes(); } class=class="str">"cmt">//--- if(lparam==m_label_button[class="num">0].Id()) { if(m_label_button[class="num">0].IsPressed()) { m_label_button[class="num">1].IsPressed(false); m_label_button[class="num">1].Update(true); } m_text_box.Hide(); } if(lparam==m_label_button[class="num">1].Id()) { if(m_label_button[class="num">1].IsPressed()) { m_label_button[class="num">0].IsPressed(false); m_label_button[class="num">0].Update(true); } m_text_box.Show(); } class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<class="num">4; i++) { if(lparam==m_color_button[i].Id())
复选框联动控件锁定的写法
在自定义面板里,复选框状态常常要反向控制其他控件的可用性。上面这段逻辑监听 CHARTEVENT_CUSTOM+ON_CLICK_CHECKBOX 事件,用 lparam 比对控件 Id 来判断点的是哪一个。 循环里对前 3 个参数控件做处理:若某个复选框被按下,则把对应的颜色按钮(索引 i+1)设为非锁定状态,也就是解锁;若第 3 个复选框(索引 2)处于按下,就显示提示文本,否则隐藏。注意数组越界——m_color_button 至少要有 4 个元素,因为 i 最大为 2 时访问的是 i+1=3。 循环外的第 4 个参数控件(索引 3)单独处理:它的按下状态决定图片滑动条 m_pictures_slider 是否锁定。这种把「勾选=解锁关联控件」写成 IsLocked(!IsPressed()) 的反向绑定,在 MT5 面板里能直接抄去用,开 MT5 新建 EA 面板验证即可。
{
m_color_picker.ColorButtonPointer(m_color_button[i]);
class="kw">return;
}
class=class="str">"cmt">//--- Click on the checkbox
if(id==CHARTEVENT_CUSTOM+ON_CLICK_CHECKBOX)
{
class=class="str">"cmt">//---
for(class="type">int i=class="num">0; i<class="num">3; i++)
{
if(lparam==m_set_param[i].Id())
{
m_color_button[i+class="num">1].IsLocked(!m_set_param[i].IsPressed());
if(m_set_param[class="num">2].IsPressed())
m_tooltip_text.Show();
else
m_tooltip_text.Hide();
}
}
class=class="str">"cmt">//---
if(lparam==m_set_param[class="num">3].Id())
m_pictures_slider.IsLocked(!m_set_param[class="num">3].IsPressed());
}「把信号监控窗口搭起来」
开发收尾时要先给交易信号监控器立一个窗口骨架。思路很直接:用第一步挑的信号做行、第二步选的时间帧做列,再按实际元素数量自动撑开窗口尺寸,避免写死宽高导致标签挤成一团。 具体落地是在 CProgram 类里加两个 CTextLabel 数组,分别管时间帧和品种的文本标签,再补 ToMonitor() 与 AutoResize() 两个方法。m_total_signals 用来确认至少建了一个信号,m_symbols[] 存第一步的品种列表,这两个私有变量得在构造函数里把计数清零、数组备好。 代码里两个建标签的方法几乎对称:时间帧标签 XSize 写死 40,品种标签 XSize 给到 100,字体和背景色都从基类 m_base_font、m_background 继承,最后塞进 CWndContainer 的元素数组索引 0。差异只在宽度,品种名普遍比 EURUSD 这种长不了多少,但交叉盘名容易超 40 像素。 别漏了 OnEvent() 里“Create”按钮的单击事件,要点它才调 ToMonitor()。实测流程:第一步选 Crosses,第二步选 Senior,第三步点 Add Signal 关掉子窗再点 Create,监控器就能出来。外汇和贵金属波动剧烈,这种自建面板只是观察辅助,信号触发不代表入场稳赢,仓位仍需自担风险。
CTextLabel m_timeframe_label[]; CTextLabel m_symbol_label[]; class="type">bool CreateTimeframeLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text); class="type">bool CreateSymbolLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates the text label | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateTimeframeLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text) { class=class="str">"cmt">//--- Save the window pointer text_label.MainPointer(m_step_window); class=class="str">"cmt">//--- text_label.Font(m_base_font); text_label.FontSize(m_base_font_size); text_label.XSize(class="num">40); text_label.BackColor(m_background); class=class="str">"cmt">//--- Create the button if(!text_label.CreateTextLabel(label_text,x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add a pointer to element to the base CWndContainer::AddToElementsArray(class="num">0,text_label); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates the text label | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateSymbolLabel(CTextLabel &text_label,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string label_text) { class=class="str">"cmt">//--- Save the window pointer text_label.MainPointer(m_step_window); class=class="str">"cmt">//--- text_label.Font(m_base_font); text_label.FontSize(m_base_font_size); text_label.XSize(class="num">100); text_label.BackColor(m_background); class=class="str">"cmt">//--- Create the button if(!text_label.CreateTextLabel(label_text,x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- Add a pointer to element to the base CWndContainer::AddToElementsArray(class="num">0,text_label); class="kw">return(true); } class="type">int m_total_signals; class="type">class="kw">string m_symbols[]; class="type">void ToMonitor(class="type">void); class="type">void AutoResize(const class="type">int x_size,const class="type">int y_size); class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 信号监控面板的动态拼装逻辑
把多品种多周期的信号做成可视监控,核心不在画图,而在窗口尺寸和控件坐标跟着数据量走。下面这段切换逻辑先判断信号总数,少于 1 个就弹窗撤回,避免空面板占用 MT5 主图资源。
进入监控态时,先隐藏「添加信号」相关的三步控件(m_add_signal、m_signal_header、前后翻页按钮),再把窗口标题改成 Signal Monitor 并强制重绘。品种标签从左上角 (5, 标题高+25) 起逐行排,行距锁死 25 像素;周期标签从 x=110 起,每个占 50 像素宽。
窗口大小由最后一列周期标签和最后一行品种标签的右下角决定:AutoResize 用 tf-1 和 sy-1 取下标,算出的宽高直接改 m_step_window 的宽高。若你自建面板,把 110+50*i 和 25*i 这两个步长抽成宏,后期加周期或品种就不用改布局代码。
导航回退也在这套状态机里:当前步为 2 按返回就回 ToStep_1,为 3 则返回 ToStep_2;外汇与贵金属波动剧烈,这类自选监控仅作概率参考,实盘仍需自担高风险。
class="type">void CProgram::ToMonitor(class="type">void) { class=class="str">"cmt">//--- Check if there is at least one signal if(m_total_signals<class="num">1) { MessageBox("No signals created!","Warning"); class="kw">return; } class=class="str">"cmt">//--- Hide Step class="num">3 m_add_signal.Hide(); m_signal_header.Hide(); m_back_button.Hide(); m_next_button.Hide(); class=class="str">"cmt">//--- Change window header m_step_window.LabelText("Signal Monitor"); m_step_window.Update(true); class=class="str">"cmt">//--- Symbols class="type">int sy=ArraySize(m_symbols); ArrayResize(m_symbol_label,sy); for(class="type">int i=class="num">0; i<sy; i++) { if(!CreateSymbolLabel(m_symbol_label[i],class="num">5,m_step_window.CaptionHeight()+class="num">25+i*class="num">25,m_symbols[i])) class="kw">return; m_symbol_label[i].Update(true); } class=class="str">"cmt">//--- Timeframes class="type">int tf=ArraySize(m_timeframes); ArrayResize(m_timeframe_label,tf); class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<tf; i++) { if(!CreateTimeframeLabel(m_timeframe_label[i],class="num">110+class="num">50*i,m_step_window.CaptionHeight()+class="num">3,m_timeframes[i])) class="kw">return; m_timeframe_label[i].Update(true); } class=class="str">"cmt">//--- Resize window AutoResize(m_timeframe_label[tf-class="num">1].XGap()+m_timeframe_label[tf-class="num">1].XSize()+class="num">5,m_symbol_label[sy-class="num">1].YGap()+m_symbol_label[sy-class="num">1].YSize()+class="num">5); } class="type">void CProgram::AutoResize(const class="type">int x_size,const class="type">int y_size) { m_step_window.ChangeWindowWidth(x_size); m_step_window.ChangeWindowHeight(y_size); }
状态机里的第三步跳监视
这段片段展示了一个多步流程里 step 1 到 3 的衔接逻辑:当 m_current_step 等于 1 时调用 ToStep_2(),等于 2 时调用 ToStep_3(),等于 3 时则进入 ToMonitor()。 注意第 3 个分支被标黄,说明这是从建仓流程切到持仓监控的关口。在 MT5 EA 里,把监控独立成函数能避免 OnTick 里堆满条件判断,回测时 step 3 的触发次数可直接从日志计数验证。 外汇与贵金属杠杆高,状态跳变若漏掉退出条件,可能让监控函数反复重入。复制下面代码到你的类方法里,改 m_current_step 初始值跑一遍就清楚跳转路径。
if(m_current_step==class="num">1) ToStep_2(); class=class="str">"cmt">//--- Go to Step class="num">3 else if(m_current_step==class="num">2) ToStep_3(); class=class="str">"cmt">//--- Go to Monitor else if(m_current_step==class="num">3) ToMonitor(); }
「把工具请下神坛」
整套监控器的附件已打包在 MQL5.zip 里,体积 1626.37 KB,解压后把 MQL5 文件夹直接丢进终端根目录就能跑。想定位根目录,在 MT5 里按 Ctrl+Shift+D,或右键调出菜单,比翻硬盘省事。 有读者在评论里问过:经纪商符号上千个,怎么只盯自己输入的几个,以及怎么加均线交叉这类自定义信号。作者当时回了句「未来部分」,意味着原版没封死扩展口,你得自己改代码里的符号遍历逻辑。 外汇和贵金属行情跳空频繁,这种多品种监控只是把信息噪声压薄,不替你过滤爆仓风险。拿去用之前,先在策略测试器里用历史数据跑一遍,确认报警触发和你肉眼看图表对得上再上实盘。