监视多币种的交易信号 (第二部分) : 应用程序可视部分的实现(基础篇)
📘

监视多币种的交易信号 (第二部分) : 应用程序可视部分的实现(基础篇)

第 1/3 篇

从通用结构落地到界面初始化

上一部分已经把多币种信号监控器的整体框架搭好了,这一节要做的,是把配置初始化的几个阶段按顺序接上,并让界面基本元素能互相响应。 原文给出的实现路径分三步:先选品种,再定时间帧,最后往里加信号。这三步对应着 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 面板只是辅助下单与监控,任何延迟或创建失败都可能让你错过价位,实盘前务必在策略测试器与离线图表各跑一遍计时。

MQL5 / C++
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 的图挂上就能看到悬浮窗位置。

MQL5 / C++
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' 成你喜欢的告警色,就能直接跑出可点的品种选择条。

MQL5 / C++
  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 就能塞更长的模板名。外汇和贵金属品种多、切换频繁,这类面板能降低手动选品种出错概率,但自动化切换不代表信号更准,实盘仍属高风险。

MQL5 / C++
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);
}

常见问题

在面板初始化时给每个品种子窗口单独指定配色钩子与计时器,按品种名建独立画布即可避免串色。
可能是窗口初始化时同步加载了全部历史,建议用懒加载只渲染可见品种,其余滚动再建。
可以,小布能按你选的品种自动生成面板骨架与配色,并替你完成按钮和输入框的批量铺设。
不需要,只要品种列表走参数传入,类内部循环创建,新增币种只改配置不改骨架。
先写窗口初始化与骨架坐标,再挂计时与配色钩子,否则控件位置会漂移,高风险品种尤需谨慎。