监视多币种的交易信号 (第二部分) : 应用程序可视部分的实现·进阶篇
(2/3)· 继通用结构之后,本篇把配置初始化与界面元素一步步变成可操作的窗口与按钮
◍ 给面板按钮写一套可复用的创建函数
在 MT5 自定义面板里,按钮若每次手写属性会很啰嗦。下面这个函数把尺寸、字体、配色一次性封进 CButton 派生逻辑,调用方只需传文本和坐标间隙。 [CODE] bool CProgram::CreateButton1(CButton &button,string text,const int x_gap,const int y_gap) { color baseclr=C'70,180,70'; color pressed=C'70,170,70'; button.MainPointer(m_step_window); button.XSize(80); button.YSize(30); button.Font(m_base_font); button.FontSize(m_base_font_size); 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); if(!button.CreateButton(text,x_gap,y_gap)) return(false); CWndContainer::AddToElementsArray(0,button); return(true); } [/CODE] 逐行看:baseclr 用 C'70,180,70' 定义绿色底,pressed 只把 G 通道从 180 降到 170,按下去视觉微暗;XSize(80) 与 YSize(30) 锁死按钮像素盒,避免不同 DPI 下错位。 字体走 m_base_font(构造里设为 Trebuchet MS),文字色恒白且 hover/pressed 不变,配合 IsCenterText(true) 让“Load(L)”“Save(S)”这类短标居中。 实际挂按钮时,x_gap 用窗口宽减偏移算:例如 m_step_window.XSize()-2*(80+10) 把 Load 按钮推到距右边缘两个按钮位,Save 占最右一格,y 统一取标题栏高+10。外汇与贵金属图表加载此类面板属高风险环境,参数错乱可能误触下单逻辑,请在策略测试器先验证坐标。
class="type">bool CProgram::CreateButton1(CButton &button,class="type">class="kw">string text,const class="type">int x_gap,const class="type">int y_gap) { 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;; button.MainPointer(m_step_window); button.XSize(class="num">80); button.YSize(class="num">30); button.Font(m_base_font); button.FontSize(m_base_font_size); 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); if(!button.CreateButton(text,x_gap,y_gap)) class="kw">return(false); CWndContainer::AddToElementsArray(class="num">0,button); class="kw">return(true); }
「多选符号面板的网格排布与点击联动」
在 MT5 自定义面板里批量生成交易品种勾选框,核心不是逐个写坐标,而是用双层循环按 7 列折行。下面这段逻辑里,外层 j 控制行、内层 i 控制列,每格横向步进 80 像素、纵向 25 像素,X 起点 10、Y 起点从标题栏高度加 70 开始——改这几个数就能调面板密度。 CreateCheckBox 函数本身只负责单个控件的样式与挂载:绿色勾选框、未按下状态、基础字体,以及悬停标签色 C'55,160,250'(一种浅蓝)。注意它把指针塞进 m_step_window 主窗体,再经 CWndContainer::AddToElementsArray(0,checkbox) 归入元素数组,缺一步都可能导致界面不刷新。 循环里用 MathCeil(m_all_symbols/7) 算总行数,比如 23 个符号会占 4 行(23/7≈3.29 向上取整为 4)。末尾按最后一个勾选框的 YGap()+60 重设窗体高度,若你加了新的底部按钮(Back/Next 各 80 宽、贴右下),窗体高度不够就会遮住它们。 事件端用 CHARTEVENT_CUSTOM+ON_CLICK_BUTTON 拦截点击:当“全部”按钮被按下,代码强制把另外两组货币集的 IsPressed 置 false 并 Update,等于做了单选互斥。外汇与贵金属品种多、波动快,这类面板若用于实盘切换,请先在策略测试器里验证点击事件响应延迟,高风险操作务必小仓位试。
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">//--- Store the pointer to the main control checkbox.MainPointer(m_step_window); class=class="str">"cmt">//--- Properties checkbox.GreenCheckBox(true); checkbox.IsPressed(false); checkbox.Font(m_base_font); checkbox.FontSize(m_base_font_size); checkbox.BackColor(m_background); checkbox.LabelColorHover(C&class="macro">#x27;class="num">55,class="num">160,class="num">250&class="macro">#x27;); class=class="str">"cmt">//--- Create a control if(!checkbox.CreateCheckBox(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,checkbox); class="kw">return(true); } class=class="str">"cmt">//--- Checkboxes class="type">int k=class="num">0; for(class="type">int j=class="num">0; j<=MathCeil(m_all_symbols/class="num">7); j++) { for(class="type">int i=class="num">0; i<class="num">7; i++) { if(k<m_all_symbols) if(!CreateCheckBox(m_checkbox[k],class="num">10+class="num">80*i,m_step_window.CaptionHeight()+class="num">70+j*class="num">25,SymbolName(k,false))) class="kw">return(false); k++; } } m_step_window.ChangeWindowHeight(m_checkbox[m_all_symbols-class="num">1].YGap()+class="num">30+class="num">30); class=class="str">"cmt">//--- if(!CreateButton1(m_back_button,"Back",m_step_window.XSize()-class="num">2*(class="num">80+class="num">10),m_step_window.YSize()-(class="num">30+class="num">10))) class="kw">return(false); if(!CreateButton1(m_next_button,"Next",m_step_window.XSize()-(class="num">80+class="num">10),m_step_window.YSize()-(class="num">30+class="num">10))) class="kw">return(false); class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::OnEvent(const class="type">int id,const class="type">long &lparam,const class="type">class="kw">double &dparam,const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- Pressing the button event if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { class=class="str">"cmt">//--- All if(lparam==m_currency_set[class="num">0].Id() && m_currency_set[class="num">0].IsPressed()) { m_currency_set[class="num">1].IsPressed(false); m_currency_set[class="num">2].IsPressed(false); m_currency_set[class="num">1].Update(true);
按货币集切换品种勾选的逻辑
面板里做货币集(All / Majors / Crosses)单选时,核心是在按钮事件中互斥置位并刷新对应 checkbox 状态。以 Majors 被按下为例,先取消 All 和 Crosses 两个集的按下态并 Update,保证界面三选一不串味。 随后用字符串数组锁定目标品种:pairs[4] = {"EURUSD","GBPUSD","USDCHF","USDJPY"},先把 m_all_symbols 个 checkbox 全清成未选,再二层循环比对 LabelText,命中就 IsPressed(true) 并 Update。这样 Majors 模式下只亮 4 个主流直盘。 Crosses 分支同理,只是 pairs 扩到 20 个(含 EURGBP、GBPJPY、CADCHF 等交叉),先关掉集 0 和集 1 再跑匹配。你在 MT5 里改 pairs 数组长度或成员,就能自定义任一组的默认品种池,外汇贵金属波动大,参数误配可能漏看信号。
m_currency_set[class="num">2].Update(true); class=class="str">"cmt">//--- for(class="type">int i=class="num">0;i<m_all_symbols; i++) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } class=class="str">"cmt">//--- Majors 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">4]= {"EURUSD","GBPUSD","USDCHF","USDJPY"}; 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">4;j++) if(m_checkbox[i].LabelText()==pairs[j]) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } } class=class="str">"cmt">//--- Crosses 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[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">20]= { "EURUSD","GBPUSD","USDCHF","USDJPY","USDCAD","AUDUSD","AUDNZD","AUDCAD","AUDCHF","AUDJPY", "CHFJPY","EURGBP","EURAUD","EURCHF","EURJPY","EURCAD","EURNZD","GBPCHF","GBPJPY","CADCHF" };
◍ 多货币勾选与步骤回退的控件逻辑
在信号监控面板里,币种复选框的状态切换依赖两组循环:先全量取消已选,再按预设的 20 个货币对字符串比对标签并重新点亮。这种先清后选的写法能避免用户跨预设组切换时残留旧勾选。 [CODE] //--- Clear the selection for(int i=0; i<m_all_symbols; i++) { m_checkbox[i].IsPressed(false); m_checkbox[i].Update(true); } //--- for(int i=0; i<m_all_symbols; i++) { for(int j=0; j<20; j++) if(m_checkbox[i].LabelText()==pairs[j]) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } [/CODE] 上面第一段循环遍历 m_all_symbols 个复选框,调用 IsPressed(false) 取消按下并 Update(true) 立即重绘;第二段对每一个控件再嵌套 0 到 19 的 j 循环,若标签文本等于 pairs[j] 就置为按下。pairs 数组长度固定为 20,超出的预设对不会被自动选中。 当三个货币集按钮(m_currency_set[0~2])中任意一个被弹起时,同样触发全量清空,保证单选集与具体币种勾选互斥。导航上,若当前在步骤 2 点返回则跳 ToStep_1,步骤 3 则返回 ToStep_2,回退链路由 m_current_step 驱动。 CreateGUI 里在创建完第一步窗口后调用 m_back_button.Hide() 隐藏返回键,意味着首步不允许往回走;外汇与贵金属品种波动剧烈,这类界面逻辑仅降低误操作概率,不预示任何收益。
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">20; j++) if(m_checkbox[i].LabelText()==pairs[j]) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } if((lparam==m_currency_set[class="num">0].Id() && !m_currency_set[class="num">0].IsPressed()) || (lparam==m_currency_set[class="num">1].Id() && !m_currency_set[class="num">1].IsPressed()) || (lparam==m_currency_set[class="num">2].Id() && !m_currency_set[class="num">2].IsPressed()) ) { 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="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(); class="kw">return(true); } class="kw">private: class="type">int m_symbol_total; class="type">int m_all_symbols; class="type">int m_current_step; class="type">void ToStep_1(class="type">void); class="type">void ToStep_2(class="type">void); class="type">void ToStep_3(class="type">void); if(lparam==m_back_button.Id()) { if(m_current_step==class="num">2) ToStep_1(); else if(m_current_step==class="num">3) ToStep_2(); }
「向导按钮背后的步骤跳转逻辑」
在 MT5 自定义面板里,用「下一步」按钮驱动多步流程时,核心是一个 lparam 与按钮 Id 的比对。下面这段事件分发代码说明:当点击 m_next_button 且当前处于第 1 步,就调 ToStep_2();处于第 2 步则调 ToStep_3()。 [CODE] //--- Go to Step 2 if(lparam==m_next_button.Id()) { //--- Go to Step 2 if(m_current_step==1) ToStep_2(); //--- Go to Step 3 else if(m_current_step==2) ToStep_3(); } [/CODE] ToStep_2() 里藏着一个容易漏掉的防护:遍历 m_all_symbols 个复选框,统计被按下的数量 cnt,若 cnt<1 就弹 MessageBox 警告并 return,不往下走。 [CODE] void CProgram::ToStep_2(void) { //--- Check whether at least one symbol is selected int cnt=0; for(int i=0; i<m_all_symbols; i++) { if(m_checkbox[i].IsPressed()) cnt++; } if(cnt<1) { MessageBox("No symbols selected!","Warning"); return; } } [/CODE] ToStep_1() 与 ToStep_3() 在原文中函数体为空,仅留注释框架。实盘写向导时,若不在 ToStep_2 做符号校验,后续步骤拿到空品种列表可能直接抛错;开 MT5 把这段粘进 EA 面板类,改 m_all_symbols 长度即可验证弹窗触发条件。
class=class="str">"cmt">//--- Go to Step class="num">2 if(lparam==m_next_button.Id()) { class=class="str">"cmt">//--- Go to Step class="num">2 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">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Go to Step class="num">1 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::ToStep_1(class="type">void) { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Go to Step class="num">2 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::ToStep_2(class="type">void) { class=class="str">"cmt">//--- Check whether at least one symbol is selected class="type">int cnt=class="num">0; for(class="type">int i=class="num">0; i<m_all_symbols; i++) { if(m_checkbox[i].IsPressed()) cnt++; } if(cnt<class="num">1) { MessageBox("No symbols selected!","Warning"); class="kw">return; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Move to Step class="num">3 class="num">3 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::ToStep_3(class="type">void) { class=class="str">"cmt">//--- }
第二步界面里把时间帧按钮亮出来
做信号监控面板的第二步,核心是把步骤一的品种勾选界面改写成时间帧选择界面。步骤一里已经建好的快速按钮、复选框和返回键直接复用,只是在 ToStep_2() 里改标签和显隐逻辑,不用重画对象。 代码里先把步骤一勾中的品种写进市场观察:循环 m_all_symbols 个复选框,按 IsPressed() 状态调 SymbolSelect(),这样 MT5 左侧只会留下你真想盯的品种,外汇和贵金属多品种同屏时这点很省内存。 时间帧集合用三组快捷按钮收口:All、Junior、Senior,分别对应 names[3] 数组。真正铺开的是 21 个具体周期复选框——从 M1 到 MN 共 21 档,代码里 timeframe_names[21] 写死,循环 Show() 出来。改完把 m_current_step 置 2,界面就切到第二步了。 在 OnEvent() 的按钮单击分支里,必须先用 m_current_step 判断当前处于哪步,否则步骤一和步骤二的快捷按钮 Id 会打架。Back 按钮绑到空的 ToStep_1() 上,编译后界面应如原文档例图 7 所示,若按钮重叠多半是 Update(true) 漏写。 外汇与贵金属波动受杠杆和消息面影响大,这类自选监控面板只解决「看哪些周期」的问题,信号真假还得你自己用价格行为过滤。
class=class="str">"cmt">//--- Set selected symbols in Market Watch for(class="type">int i=class="num">0;i<m_all_symbols; i++) { if(m_checkbox[i].IsPressed()) SymbolSelect(m_checkbox[i].LabelText(),true); else SymbolSelect(m_checkbox[i].LabelText(),false); } class=class="str">"cmt">//--- Change header m_step_window.LabelText("Signal Monitor Step class="num">2: Choose Timeframes"); m_step_window.Update(true); class=class="str">"cmt">//--- Hide elements of Step class="num">1 for(class="type">int i=class="num">0;i<m_all_symbols; i++) { m_checkbox[i].IsLocked(false); m_checkbox[i].IsPressed(false); m_checkbox[i].Hide(); } class="type">class="kw">string names[class="num">3]= {"All","Junior","Senior"}; 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].LabelText(names[i]); m_currency_set[i].IsPressed(false); if(m_current_step==class="num">3) m_currency_set[i].Show(); m_currency_set[i].Update(true); } class=class="str">"cmt">//--- Hide block for working with templates m_text_edit.Hide(); m_load_button.Hide(); m_save_button.Hide(); class=class="str">"cmt">//--- Show all timeframes 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" }; for(class="type">int i=class="num">0;i<class="num">21;i++) { m_checkbox[i].LabelText(timeframe_names[i]); m_checkbox[i].Show(); m_checkbox[i].Update(true); } class=class="str">"cmt">//--- Show Back button m_back_button.Show(); class=class="str">"cmt">//--- m_current_step=class="num">2; class=class="str">"cmt">//--- Step class="num">1 if(m_current_step==class="num">1) { ... } class=class="str">"cmt">//--- Step class="num">2 else if(m_current_step==class="num">2) { class=class="str">"cmt">//--- All if(lparam==m_currency_set[class="num">0].Id() && m_currency_set[class="num">0].IsPressed()) { m_currency_set[class="num">1].IsPressed(false); m_currency_set[class="num">2].IsPressed(false);