带有图形界面的通用趋势·综合运用
📈

带有图形界面的通用趋势·综合运用

(3/3)·前两篇铺好了振荡器与通道,这一篇把趋势判断和定制图形控件真正焊在一起

含代码示例偏理论 第 3/3 篇
很多人写趋势指标只盯均线穿越,却忽略了 RSI 坡度和定制图形控件的组合空间。把控件对象全交给自动指针管理,后期改外观就是给自己埋坑。换个轻量控件思路,界面和逻辑才能同步迭代。

◍ 通用控件的基类怎么搭

做 MT5 自定义面板时,把所有界面类拆到两个头文件里更清爽:一个管表单容器,一个管指标参数输入控件。表单那部分之前讲通用振荡器、通道时已经拆过,本文重点在后者——也就是能复用的参数控件。 控件体系的地基是 CUniTrendControl 基类。它的公有区只放虚方法,强迫子类去实现;保护区塞了几个给下拉框用的辅助函数:往列表里灌枚举变体、按值反查索引,以及根据小数位算最小步进。 基类里 SolveChange(int d) 用 NormalizeDouble(1.0/pow(10,d),d) 生成级别框的最小变化量。比如 d=2 时返回 0.01,级别输入框就不会跳出 0.001 这种非法步长。下面这段是带注释的基类核心,直接抄进 UniTrendControl.mqh 就能编译。

MQL5 / C++
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 周期。外汇与贵金属波动剧烈,这类参数可调控件请先在策略测试器跑历史数据验证,实盘存在滑点与断连风险。

MQL5 / C++
      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 失效。

MQL5 / C++
  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。 外汇与贵金属杠杆高、滑点跳空频繁,这类第三方界面库只解决交互层,信号逻辑仍要你自己回测验证,别直接挂实盘。

把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到趋势强度和控件状态,你只管判断要不要跟信号。

常见问题

任何依赖价格方向的指标都可纳入,例如 RSI 坡度、随机振荡器主线信号线交汇,重点在提取方向而非传统趋势标签。
前者只实现本文所需方法、对象可见性位置管理更直控;后者靠附加类统一调度,改界面成本更高。
坡度方向改变的柱线对应均线局部上下端极值,倾向标志趋势转折概率,但外汇贵金属高风险需结合过滤。
小布盯盘品种页已内置常见趋势诊断视图,自定义 MQL5 控件指标需自行编译后对接数据层,不强制手搓界面。
容易忘记在重绘事件里同步控件状态和指标缓冲,导致界面显示滞后于实际趋势信号。