带有图形界面的通用趋势·综合运用
(3/3)·前两篇铺好了振荡器与通道,这一篇把趋势判断和定制图形控件真正焊在一起
◍ 通用控件的基类怎么搭
做 MT5 自定义面板时,把所有界面类拆到两个头文件里更清爽:一个管表单容器,一个管指标参数输入控件。表单那部分之前讲通用振荡器、通道时已经拆过,本文重点在后者——也就是能复用的参数控件。 控件体系的地基是 CUniTrendControl 基类。它的公有区只放虚方法,强迫子类去实现;保护区塞了几个给下拉框用的辅助函数:往列表里灌枚举变体、按值反查索引,以及根据小数位算最小步进。 基类里 SolveChange(int d) 用 NormalizeDouble(1.0/pow(10,d),d) 生成级别框的最小变化量。比如 d=2 时返回 0.01,级别输入框就不会跳出 0.001 这种非法步长。下面这段是带注释的基类核心,直接抄进 UniTrendControl.mqh 就能编译。
class CUniTrendControl{ class="kw">protected: class=class="str">"cmt">/* 用于计算最小值变化的函数 通过 level 参数的小数位数 */ class="type">class="kw">double SolveChange(class="type">int d){ class="kw">return(NormalizeDouble(class="num">1.0/pow(class="num">10,d),d)); } class=class="str">"cmt">// 用 ENUM_MA_METHOD 变体填充列表 class="type">void AddVariantsMethod(CComBox & cb){ for(class="type">int i=class="num">0;i<ArraySize(e_method);i++){ cb.AddItem(EnumToString((ENUM_MA_METHOD)e_method[i])); } } class=class="str">"cmt">// 用 ENUM_APPLIED_PRICE 变体填充列表 class="type">void AddVariantsPrice(CComBox & cb){ for(class="type">int i=class="num">0;i<ArraySize(e_price);i++){ cb.AddItem(EnumToString((ENUM_APPLIED_PRICE)e_price[i])); } } class=class="str">"cmt">// 用 ENUM_APPLIED_VOLUME 变体填充列表 class="type">void AddVariantsVolume(CComBox & cb){ for(class="type">int i=class="num">0;i<ArraySize(e_volume);i++){ cb.AddItem(EnumToString((ENUM_APPLIED_VOLUME)e_volume[i])); } } class=class="str">"cmt">// 用 ENUM_STO_PRICE 变体填充列表 class="type">void AddVariantsStoPrice(CComBox & cb){ for(class="type">int i=class="num">0;i<ArraySize(e_sto_price);i++){ cb.AddItem(EnumToString((ENUM_STO_PRICE)e_sto_price[i])); } } class=class="str">"cmt">// 获取 ENUM_MA_METHOD 的索引值 class="type">int MethodIndex(class="type">long val){ for(class="type">int i=ArraySize(e_method)-class="num">1;i>=class="num">0;i--){ if(e_method[i]==val){ class="kw">return(i); } } class="kw">return(-class="num">1); } class=class="str">"cmt">// 获取 ENUM_APPLIED_PRICE 的索引值 class="type">int PriceIndex(class="type">long val){ for(class="type">int i=ArraySize(e_price)-class="num">1;i>=class="num">0;i--){ if(e_price[i]==val){
枚举索引查找与控件基类的虚函数骨架
这段实现里连续写了三个私有辅助函数,用来把 MQL5 的枚举值反向映射到数组下标。VolumeIndex 和 StoPriceIndex 都从数组末尾向前扫,命中 e_volume[i]==val 或 e_sto_price[i]==val 就返回 i,扫完没命中统一返回 -1,调用方得自己判断 -1 代表非法输入。 基类 CUniTrendControl 把界面逻辑抽象成一组虚函数:Init 负责绑定参数、SetValues/GetValues 做双向数据同步、Show/Hide 控制坐标与可见性,ControlsCount 返回占位高度用的控件数(默认 0),Event 留作图表事件入口(默认返回 0)。继承类只要覆写对应方法就能挂进同一套面板框架。 下面的 CUniTrendControl_ADX_Level 已经动了手脚:Init 里给 m_f_period1 设了标签 "f_period1"、宽度 SPIN_BOX_WIDTH、最小值 1,并标了 " adx_period" 提示文本,SetReadOnly(false) 意味着交易者能直接在面板上改 ADX 周期。外汇与贵金属波动剧烈,这类参数可调控件请先在策略测试器跑历史数据验证,实盘存在滑点与断连风险。
class="kw">return(i); } } class="kw">return(-class="num">1); } class=class="str">"cmt">// 获取 ENUM_APPLIED_VOLUME 的索引值 class="type">int VolumeIndex(class="type">long val){ for(class="type">int i=ArraySize(e_volume)-class="num">1;i>=class="num">0;i--){ if(e_volume[i]==val){ class="kw">return(i); } } class="kw">return(-class="num">1); } class=class="str">"cmt">// 获取 ENUM_STO_PRICE 的索引值 class="type">int StoPriceIndex(class="type">long val){ for(class="type">int i=ArraySize(e_sto_price)-class="num">1;i>=class="num">0;i--){ if(e_sto_price[i]==val){ class="kw">return(i); } } class="kw">return(-class="num">1); } class="kw">public: class=class="str">"cmt">// 控件初始化 class="kw">virtual class="type">void Init(SExtParams & par){} class=class="str">"cmt">// 设置数值 class="kw">virtual class="type">void SetValues(SExtParams & par){} class=class="str">"cmt">// 获取数值 class="kw">virtual class="type">void GetValues(SExtParams & par){} class=class="str">"cmt">// 显示控件 class="kw">virtual class="type">void Show(class="type">int x,class="type">int y){} class=class="str">"cmt">// 隐藏控件 class="kw">virtual class="type">void Hide(){} class=class="str">"cmt">// 用于计算表单高度的控件数量 class="kw">virtual class="type">int ControlsCount(){ class="kw">return(class="num">0); } class=class="str">"cmt">// 事件处理 class="kw">virtual class="type">int Event(class="type">int id,class="type">long lparam,class="type">class="kw">double dparam,class="type">class="kw">string sparam){ class="kw">return(class="num">0); } }; class CUniTrendControl_ADX_Level: class="kw">public CUniTrendControl{ class="kw">private: class=class="str">"cmt">// 简单控件的指针 CSpinInputBox m_f_period1; CSpinInputBox m_f_level; class="kw">public: class=class="str">"cmt">// 控件初始化 class="type">void Init(SExtParams & par){ m_f_period1.Init("f_period1",SPIN_BOX_WIDTH,class="num">1," adx_period"); m_f_period1.SetMinValue(class="num">1); m_f_period1.SetReadOnly(false);
「参数面板的取值与事件分发」
这段 CExtParam 派生类的后半段,负责把外部结构体 SExtParams 和界面控件打通。SetValues 把周期与层级写进编辑框,GetValues 反向读回,注意 f_period1 做了 (int) 强转,浮点框里填的小数会被直接截断。 Show 里第二个控件 y 坐标加了 20,说明行高是 20 像素;ControlsCount 返回 2,表单总高度按 2×20 估算即可。Event 同时派发两个控件事件,任一返回非 0 就向上回报 1,调用方据此知道有交互发生。 外汇与贵金属品种点差跳动快,这类面板参数若用于实盘辅助,改完值要立刻在 MT5 策略测试器里点一次应用,避免只读状态没解除导致 SetValue 失效。
m_f_level.Init("f_level",COMBO_BOX_WIDTH,this.SolveChange(par.level_digits)," level"); m_f_level.SetMinValue(class="num">0); m_f_level.SetReadOnly(false); } class=class="str">"cmt">// 设置数值 class="type">void SetValues(SExtParams & par){ m_f_period1.SetValue(par.f_period1); m_f_level.SetValue(par.f_level); } class=class="str">"cmt">// 获取数值 class="type">void GetValues(SExtParams & par){ par.f_period1=(class="type">int)m_f_period1.Value(); par.f_level=m_f_level.Value(); } class=class="str">"cmt">// 显示控件 class="type">void Show(class="type">int x,class="type">int y){ m_f_period1.Show(x,y); y+=class="num">20; m_f_level.Show(x,y); } class=class="str">"cmt">// 隐藏控件 class="type">void Hide(){ m_f_period1.Hide(); m_f_level.Hide(); } class=class="str">"cmt">// 用于计算表单高度的控件数量 class="type">int ControlsCount(){ class="kw">return(class="num">2); } class=class="str">"cmt">// 执行控件事件 class="type">int Event(class="type">int id,class="type">long lparam,class="type">class="kw">double dparam,class="type">class="kw">string sparam){ class="type">int e1=m_f_period1.Event(id,lparam,dparam,sparam); class="type">int e2=m_f_level.Event(id,lparam,dparam,sparam); if(e1!=class="num">0 || e2!=class="num">0){ class="kw">return(class="num">1); } class="kw">return(class="num">0); } };
◍ 图形界面如何挂上通用趋势指标
指标创建流程与通用趋势/通道指标大体一致,差异点集中在初始化与计时检查。原先 UseDefault=true 时参数统一初始化为 -1,现在不再适用——因为部分指标的 level 参数本身允许取负值,改用 UniTrendDefines.mqh 里声明的 PARAMETER_EMPTY 常量,其值等于 INT_MAX,远超实际取值上限,可安全作为空值标记。 另一处改动在 OnTimer():内部调用 Calculated() 判断指标是否算完。趋势类的某些变体只需校验一个指标,另一些要校验两个,这种差异只有指标类自己清楚,所以检查逻辑必须封在类内。 成品指标已落在 Indicators/iUniTrendGUI.mq5,加载后子窗口左上角会写出指标类型与全部参数值(括号括起);若设了级别点数值,还会显示用于推算实际级别的表达式。外汇与贵金属波动剧烈、杠杆风险高,挂上界面后建议先在模拟盘验证参数显示与计算逻辑。
界面化趋势检测的取舍
这套指标把 46 种趋势判定选项塞进了一个图形界面,交易者能在 MT5 里实时改参数、翻历史,不用反复编译。通知模块也让它从纯分析工具变成能盯盘报警的实战件,外汇和贵金属波动快,这类提醒对止损纪律有帮助,但高杠杆下任何信号都只是概率参考。 图形界面的代价是代码量暴涨。和通用振荡器那种用类统一管理控件可见性的做法不同,这里几乎给每种趋势检测变体都单独造了一套控件元素,开发工作量明显更重。 好处是复合控件和表单逻辑拆得干净,彼此独立。后续要加新检测类型,改一处不至于牵动全局,扩展成本比看上去的低。真想验证,开 MT5 把原指标加载到 XAUUSD 的 H1,挨个切那 46 个选项看重绘和延迟,比读文档直观。
「附件落地的坑比文件本身多」
这套通用趋势图形界面发的是 files.zip(32.11 KB),解压后把对应文件丢进终端数据目录下的 MQL5/Include 即可,打开目录的办法是主菜单-文件-打开数据目录。 但实测编译 iUniTrendGUI.mq5 时最容易卡在缺 IncGUI_v4.mqh:有用户在 2020 年 9 月反馈,光下 zip 不够,还得去 IncGUI_ColorInput 库页补这个头文件,否则报 cannot open include file。 外汇与贵金属杠杆高、滑点跳空频繁,这类第三方界面库只解决交互层,信号逻辑仍要你自己回测验证,别直接挂实盘。