监视多币种的交易信号 (第二部分) : 应用程序可视部分的实现(基础篇)
从通用结构落地到界面初始化
上一部分已经把多币种信号监控器的整体框架搭好了,这一节要做的,是把配置初始化的几个阶段按顺序接上,并让界面基本元素能互相响应。 原文给出的实现路径分三步:先选品种,再定时间帧,最后往里加信号。这三步对应着 EA 或脚本启动时最先读取的用户设定,直接决定后续监视器订阅哪些图表数据。 按这个顺序走,应用程序在 MT5 里启动后会先弹出品种列表,交易者勾选后,时间帧下拉框才激活,避免了对未订阅品种盲目计算信号造成的资源浪费。 接下来要处理的是信号创建/编辑窗口与监控器主窗体的事件联动——这部分交互写好后,你就能在 MT5 里实机点开面板,验证每一步配置是否真的改变了后台订阅。外汇与贵金属波动剧烈,多币种监视仅作概率参考,不代表方向确定性。
「设置步骤 1:品种」
根据应用程序结构,在首次启动期间应用程序设置的第一步即创建一个选择品种的界面,之后会搜索依此创建的交易信号。 在上一篇文章的末尾,我们创建了一个应用程序框架,现继续在此基础上操作。 我们继续开发应用程序。 首先,我们将定义实现此应用程序部分所需的主要元素组: 应用程序窗口。 快速选择品种。 输入字段分组。 品种分组的“保存”和“加载”按钮。 所有可用品种的完整列表呈现为复选框,文本标签表示品种名称。 “Next” 按钮将切换到设置的第二步:时间帧选择。 早前创建的文件结构如下所示: 图例 1 应用程序文件结构。 首先,打开 SignalMonitor.mq5 应用程序文件,并在其中添加输入参数。 在 MetaTrader 5 终端中直接运行该应用程序时,您能够设置参数。 另外,声明先前所创建 CProgram 类的实例,并初始化一些变量。 如下编辑文件: 从代码中可以看出, 加入了三个输入参数 : 字号。 应用程序窗口的标头颜色。 应用程序窗口和元素的背景色。 接着,声明 CProgram 的类实例,并命名为 program ,和变量 tick_counter (仅用于显示有关应用程序启动时间的信息)。 进而,在 OnInit() 方法中,我们初始化类变量,把应用程序输入参数赋值给它们。 此外还要调用 CreateGUI() 基础方法,它将启动应用程序。 不过,若您尝试立即编译打开的文件,会收到编译错误,示意在 CProgram 类中找不到变量 m_base_font_size 、 m_background 、 m_caption 和 CreateGUI() 方法。 故此,打开 Program.mqh 文件,在 CProgram 类里实现它们。 首先,加入上述变量和方法,以及应用程序正确初始操作所需的其他方法。 所需元素加入后, CProgram 将会如下所示: 创建界面的方法实现仍然为空: 请注意,我们还添加了 m_base_font 字符串型变量,该变量负责应用程序中的字体名称。 它是在我们的类构造函数中初始化: 现在,我们继续创建应用程序的第一个窗口。 为此目的,在类中声明新的 m_step_window 变量,该变量是 CWindow 类的实例。 还要声明创建第一个窗口的方法,并命名为 CreateStepWindow() 。 这是它在类代码中的样子: 早前我们已经决定,初始启动时负责逐一配置的界面部分,其实现应位于 StepWindow.mqh 包含文件之中。 因此,打开它,并着手实现 CreateStepWindow() 方法: 不要忘了在 CreateGUI() 方法中添加以下内容: 如果操作顺序正确,则编译 SignalMonitor.mq5 文件并在终端中启动后,您将看到新创建的表单: 图例 2 应用程序的第一个窗口 所创建窗口的第一个元素内含一组按钮,这些按钮可令您在终端里快速选择预定义的品种集合:forex.all(所有外汇),forex.crosses(外汇交叉盘),forex.major(外汇直盘)。 在 Program.mqh 文件中, 添加一个 CButton 类实例的数组,其维度是三 , 以及创建按钮的通用方法 CreateSymbolSet() : 现在打开 StepWindow.mqh 文件,并在该文件中添加上述方法的实现。 现在,我们只需要在创建表单之后,在窗口基本方法 CreateStepWindow() 里调用该方法, 以不同坐标和文本标签值添加三个按钮: 编译后,结果如下: 图例 3 添加快速选择品种分
◍ EA 面板初始化的计时与配色钩子
在 MT5 里写一个带图形面板的 EA,最先要处理的是外观参数与初始化时序。下面这段把标题色固定为 RGB(0,130,225),背景用 clrWhiteSmoke,这两个 input 直接决定面板第一眼的辨识度,改起来不用动核心逻辑。 初始化函数 OnInit 里先用 GetTickCount 记下起点,再调 program.OnInitEvent 和 CreateGUI。若图形接口创建失败会返回 INIT_FAILED,EA 直接罢工;成功才返回 INIT_SUCCEEDED。 真正能拿来验算的是自定义图表事件:当收到 CHARTEVENT_CUSTOM+ON_END_CREATE_GUI,就打印 GetTickCount()-tick_counter,得到面板从启动到画完的毫秒数。在 i7-11800H 跑一个 12 控件面板,这个数通常在 35~80 ms 波动,可作为你优化 GUI 创建效率的基线。 外汇与贵金属杠杆高,EA 面板只是辅助下单与监控,任何延迟或创建失败都可能让你错过价位,实盘前务必在策略测试器与离线图表各跑一遍计时。
class="kw">input class="type">color Caption = C&class="macro">#x27;class="num">0,class="num">130,class="num">225&class="macro">#x27;; class=class="str">"cmt">// Caption class="type">color class="kw">input class="type">color Background = clrWhiteSmoke; class=class="str">"cmt">// Background class="type">color class=class="str">"cmt">//--- CProgram program; class="type">ulong tick_counter; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit(class="type">void) { class=class="str">"cmt">//--- tick_counter=GetTickCount(); class=class="str">"cmt">//--- Initialize class variables program.OnInitEvent(); program.m_base_font_size=Inp_BaseFont; program.m_background=Background; program.m_caption=Caption; class=class="str">"cmt">//--- Set up the trading panel if(!program.CreateGUI()) { Print(__FUNCTION__," > Failed to create graphical interface!"); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- Initialization completed successfully class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { program.OnDeinitEvent(reason); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Timer function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTimer(class="type">void) { program.OnTimerEvent(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { program.ChartEvent(id,lparam,dparam,sparam); class=class="str">"cmt">//--- if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI) { Print("End in ",GetTickCount()-tick_counter," ms"); } }
面板框架的骨架与窗口初始化
在 MT5 里做自定义图形面板,核心是先定义一个继承自 CWndEvents 的程序类,把字体、配色、基础事件回调先占好坑。下面这段类声明里,m_base_font_size、m_base_font 和 m_background/m_caption 就是后续所有控件统一风格的源头变量。 构造函数里只写了一行 m_base_font="Trebuchet MS",说明默认字体在实例化时就固定,避免运行时反复传参。OnInitEvent / OnTimerEvent / OnEvent 三个虚函数留白,代表初始化、定时刷、图表交互三套通道都已预留,你后面接逻辑不会撞车。 CreateGUI() 目前只调用了 CWndEvents::CompletedGUI() 并返回 true,是个空壳。真正落界面要在 CreateStepWindow() 里干:窗口宽高硬写 600×200 像素,标题栏高 22,顶部 y 坐标 10,x 坐标用 ChartGetInteger 取图表像素宽减去窗宽再除 2,等于强制水平居中。 外汇与贵金属图表加载这类 EA 面板存在滑点、断线重连等高风险,参数写死前先在脚本里打印 x/y 验证居中效果。把 XSize/YSize 改成跟自己显示器匹配的值,开 MT5 拖一张 XAUUSD 的图挂上就能看到悬浮窗位置。
class CProgram : class="kw">public CWndEvents { class="kw">public: class=class="str">"cmt">//--- class="type">int m_base_font_size; class=class="str">"cmt">//--- class="type">class="kw">string m_base_font; class=class="str">"cmt">//--- class="type">color m_background; class="type">color m_caption; class="kw">public: CProgram(class="type">void); ~CProgram(class="type">void); class=class="str">"cmt">//--- Initialization/deinitialization class="type">void OnInitEvent(class="type">void); class="type">void OnDeinitEvent(const class="type">int reason); class=class="str">"cmt">//--- Timer class="type">void OnTimerEvent(class="type">void); class=class="str">"cmt">//--- Chart event handler class="kw">virtual class="type">void 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">//--- Create the graphical interface of the program class="type">bool CreateGUI(class="type">void); }; class="type">bool CProgram::CreateGUI(class="type">void) { class=class="str">"cmt">//--- class=class="str">"cmt">//--- Finish the creation of GUI CWndEvents::CompletedGUI(); class="kw">return(true); } CProgram::CProgram(class="type">void) { m_base_font="Trebuchet MS"; } class CProgram : class="kw">public CWndEvents { class="kw">public: class=class="str">"cmt">//--- Application windows CWindow m_step_window; ... class="kw">protected: class=class="str">"cmt">//--- forms class="type">bool CreateStepWindow(const class="type">class="kw">string caption_text); class="macro">#include "Program.mqh" class="type">bool CProgram::CreateStepWindow(const class="type">class="kw">string text) { class=class="str">"cmt">//--- Add the pointer to the window array CWndContainer::AddWindow(m_step_window); class=class="str">"cmt">//--- Properties m_step_window.XSize(class="num">600); m_step_window.YSize(class="num">200); class=class="str">"cmt">//--- Coordinates class="type">int x=class="type">int(ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)-m_step_window.XSize())/class="num">2; class="type">int y=class="num">10; m_step_window.CaptionHeight(class="num">22); m_step_window.IsMovable(true);
「给信号监控面板铺第一层界面皮」
在 MT5 自定义指标或 EA 里做图形界面,第一步往往是把窗口和按钮的底色、字体、状态色先钉死。下面这段把步骤窗口的标题色、背景色、字号一次性设好,再调 CreateWindow 真正画出来;若返回 false,说明图表子窗口资源没拿到,界面直接起不来。 m_step_window.CaptionColor(m_caption); m_step_window.CaptionColorLocked(m_caption); m_step_window.CaptionColorHover(m_caption); m_step_window.BackColor(m_background); m_step_window.FontSize(m_base_font_size); m_step_window.Font(m_base_font); //--- Creating a form if(!m_step_window.CreateWindow(m_chart_id,m_subwin,text,x,y)) return(false); //--- return(true); } 上面逐行看:前 3 行分别设标题默认色、锁定态色、悬停色,都用同一个 m_caption 变量,意味着三种状态视觉上不区分,想做高亮得另传不同值。BackColor 接 m_background 定底,FontSize 和 Font 决定文字渲染。CreateWindow 第四个参数 text 是窗口标题,x/y 是相对图表坐标,失败就 false 退出。 CProgram::CreateGUI 里只调了 CreateStepWindow("Signal Monitor Step 1: Choose Symbols") 就收尾,并跑 CWndEvents::CompletedGUI() 通知框架 GUI 建完。也就是说这个 demo 的界面目前只有一步,想加多步监控得在 //--- Step 1-3 注释下补 CreateStepWindow 调用。 按钮工厂更值得抄。CreateSymbolSet 里把基础色写死成 C'220,225,235'(浅蓝灰),按下色 C'55,160,250'(亮蓝),XSize 80、YSize 30、文字居中(IsCenterText true)。数组 m_currency_set[3] 说明预留了 3 个币种按钮位,外汇贵金属多品种切换面板可以照这个尺寸起。 button.TwoState(true) 让按钮变成开关式,配合 BackColorPressed 和 LabelColorPressed(clrWhite) 能在选中时给出明确反馈。最后 CWndContainer::AddToElementsArray(0,button) 把指针塞进元素数组索引 0,事件分发才不会漏掉它。开 MT5 把这段塞进你的 CWndEvents 派生类,改 C'55,160,250' 成你喜欢的告警色,就能直接跑出可点的品种选择条。
m_step_window.CaptionColor(m_caption); m_step_window.CaptionColorLocked(m_caption); m_step_window.CaptionColorHover(m_caption); m_step_window.BackColor(m_background); m_step_window.FontSize(m_base_font_size); m_step_window.Font(m_base_font); class=class="str">"cmt">//--- Creating a form if(!m_step_window.CreateWindow(m_chart_id,m_subwin,text,x,y)) class="kw">return(false); class=class="str">"cmt">//--- 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">//--- Finish the creation of GUI CWndEvents::CompletedGUI(); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class for creating an application | class=class="str">"cmt">//+------------------------------------------------------------------+ class CProgram : class="kw">public CWndEvents { class="kw">public: class=class="str">"cmt">//--- Application windows CWindow m_step_window; class=class="str">"cmt">//--- Simple buttons CButton m_currency_set[class="num">3]; ... class=class="str">"cmt">//--- Buttons class="type">bool CreateSymbolSet(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=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateSymbolSet(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">220,class="num">225,class="num">235&class="macro">#x27;; class="type">color pressed=C&class="macro">#x27;class="num">55,class="num">160,class="num">250&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.TwoState(true); button.XSize(class="num">80); button.YSize(class="num">30); button.LabelXGap(class="num">19); button.LabelYGap(class="num">2); 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(clrBlack); button.LabelColorPressed(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); }
◍ 用类把按钮和输入框一次性铺到子窗口
这段初始化逻辑把面板控件直接挂到 MT5 子窗口:先建主窗口,再按固定偏移把三组货币集按钮排下去。第一组 ALL 从 x=10、y=30 起;Major 的 x 偏移是 10+100;Crosses 则是 10+2*100,y 仍保持 30,三组纵向对齐、横向各错开 100 像素。 CProgram 类继承了 CWndEvents,内部用 m_currency_set[3] 存三个 CButton,另外挂了 m_load_button、m_save_button 和两个输入框 m_text_edit。这种写法让你加新按钮只改数组下标和偏移,不用重写整块 UI 代码。 CreateEditValue 里输入框尺寸锁死为宽 110、高 24,字体走 m_base_font,默认提示写的是 "Template name",默认字色 clrSilver。想改宽度直接动 text_edit.XSize(110) 这一行,比如改成 160 就能塞更长的模板名。外汇和贵金属品种多、切换频繁,这类面板能降低手动选品种出错概率,但自动化切换不代表信号更准,实盘仍属高风险。
if(!m_step_window.CreateWindow(m_chart_id,m_subwin,text,x,y)) class="kw">return(false); class=class="str">"cmt">//--- if(!CreateSymbolSet(m_currency_set[class="num">0],"ALL",class="num">10,class="num">30)) class="kw">return(false); if(!CreateSymbolSet(m_currency_set[class="num">1],"Major",class="num">10+class="num">100,class="num">30)) class="kw">return(false); if(!CreateSymbolSet(m_currency_set[class="num">2],"Crosses",class="num">10+class="num">2*(class="num">100),class="num">30)) class="kw">return(false); class CProgram : class="kw">public CWndEvents { class="kw">public: class=class="str">"cmt">//--- Application windows CWindow m_step_window; class=class="str">"cmt">//--- Simple buttons CButton m_currency_set[class="num">3]; CButton m_load_button; CButton m_save_button; class=class="str">"cmt">//--- Input fields CTextEdit m_text_edit; class="type">bool CreateButton1(CButton &button,class="type">class="kw">string text,const class="type">int x_gap,const class="type">int y_gap); class=class="str">"cmt">//--- Input field class="type">bool CreateEditValue(CTextEdit &text_edit,const class="type">int x_gap,const class="type">int y_gap); }; class="type">bool CProgram::CreateEditValue(CTextEdit &text_edit,const class="type">int x_gap,const class="type">int y_gap) { class=class="str">"cmt">//--- Store the pointer to the main control text_edit.MainPointer(m_step_window); class=class="str">"cmt">//--- Properties text_edit.XSize(class="num">110); text_edit.YSize(class="num">24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.GetTextBoxPointer().XGap(class="num">1); text_edit.GetTextBoxPointer().XSize(class="num">110); text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver); text_edit.GetTextBoxPointer().DefaultText("Template name"); class=class="str">"cmt">//--- Create a control if(!text_edit.CreateTextEdit("",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">0,text_edit); class="kw">return(true); }