带有图形界面的通用通道(基础篇)
📘

带有图形界面的通用通道(基础篇)

第 1/3 篇

给通道指标加一个能拖的面板

在 MT5 里做通道类指标,最常见的问题是参数调起来麻烦:每次改斜率、宽度都要进指标设置窗口。给指标挂一个图形界面(GUI),可以直接在图上拖滑块、点按钮实时改参数,省掉反复开对话框的步骤。 这类通用通道通常基于线性回归或标准偏差画出上下轨,界面层只负责把用户输入映射成指标缓冲区宽度。Dmitry Fedoseev 在 2017-04-10 发布的示例累计被查看 11 665 次,说明手动调通道的需求一直有。 打开 MT5 按 F4 进 MetaEditor,新建一个带 Custom Indicators + Use graphical interface 模板的脚本,就能看到系统自动生成的面板骨架。先跑通空面板,再往里绑 iCustom 的通道句柄,比从头写窗体更快。

◍ 把通道拆成可计算的零件

做通道类指标,先别急着画线,得把结构拆开:中心线、边界、宽度算法各管一摊。中心线可以是 SMA、EMA 或线性回归,边界则分固定点数、ATR 倍数、标准差三类,类型选错整条通道就失真。 MQL5 里通常用独立类封装通道绘制,比如 CChannel 负责算宽度和渲染,不直接耦合到某根均线。这样后续换中心线算法,只改一个成员函数。 下面这段是通用通道指标里抽取的宽度计算类骨架,入参是已算好的中心线数组与周期,返回上下轨缓冲: 实际落地时,把指标缓冲区与 GUI 控件的 Edit 框绑定,交易者在面板上改 ATR 周期,通道实时重绘;外汇与贵金属波动跳变快,这类实时联动对风控有帮助,但杠杆品种高风险,参数误设可能放大亏损。

MQL5 / C++
class CChannelWidth
{
class="kw">protected:
   class="type">class="kw">double m_atr_period;
class="kw">public:
   class="type">void SetAtrPeriod(class="type">class="kw">double p){ m_atr_period=p; }
   class="type">void Calculate(const class="type">class="kw">double &center[], class="type">int rates_total, class="type">class="kw">double &upper[], class="type">class="kw">double &lower[])
   {
      for(class="type">int i=class="num">0;i<rates_total;i++)
      {
         class="type">class="kw">double w = iCustom(NULL,class="num">0,"ATR",(class="type">int)m_atr_period,i)*class="num">1.5;
         upper[i]=center[i]+w;
         lower[i]=center[i]-w;
      }
   }
};

「从通用振荡器到通用通道的构建思路」

之前关于通用振荡器的文章面向有经验的程序员,代码逻辑偏重图形界面细节,新人直接改起来门槛高。本文在已实现的振荡器框架上抽离出通道结构,新手只需替换素材模块就能拼出自己的通道指标,不必通读全部 GUI 底层。 所有通道指标都画三条线:中心线、顶部线、底部线。中心线绘制原理接近移动平均线,顶底线距中心线等距,距离可按点值、价格百分比(包络指标)、标准偏差(布林带)或 ATR(Keltner 通道)确定。因此通用通道拆成两个独立模块:中心线计算模块 + 通道宽度定义模块。 Donchian 通道常规是先取价格区间画边界再算中线,本文反过来先画中线(区间中价)再向两侧偏移半区间宽。这样计算量比常规做法大,但能组合出非常规通道,比如在价格通道里套用布林带的标准偏差边界,组合数量显著增加。外汇与贵金属杠杆高,通道突破信号假突破概率不低,参数组合需先在 MT5 回测验证。

中心线该挂哪种均线

做通道类指标时,中心线不是只有简单 MA 一种选法。MT5 里能直接调用的自适应、双重指数、分形自适应、标准 MA、三重指数、VIDyA 以及价格通道中线,各自参数数量从 1 个到 5 个不等。 表 1 里 iAMA 要填 5 个参数(ama_period、fast_ma_period、slow_ma_period、ama_shift、applied_price),而价格通道中心线只要 1 个 period,差异直接决定你后面属性面板要暴露多少输入框。 把表 1 的“参数”列横向压一遍,能抽出一个最小通用集:period1、period2、period3、shift、ma_method、price(表 2)。也就是说,无论挂哪种均线,这 6 个字段足够覆盖绝大多数变体。 实战里所有要在指标属性窗口露出来的中心线参数,统一加 "c_" 前缀,比如 c_period1、c_shift,避免和通道带宽类参数撞名。外汇与贵金属波动大,均线周期错配会放大假突破概率,上 MT5 改前缀后编译验证一次最稳妥。

◍ 通道边界宽度的五种算法口径

通道边界不是只能靠固定点数画。MQL5 里至少能分出五种计算口径:iATR 平均真实范围、iStdDev 标准偏差、直接按点数、按价格百分比(包络线用法)、以及相对价格通道实际宽度的缩放比。 表 3 里 iStdDev 要吃四个参数:ma_period 均化周期、ma_shift 水平偏移、ma_method 平滑类型、applied_price 价格类型;而 iATR 只需 ma_period。若选点数或百分比,则只需一个 width 变量。 把表 3 的参数列收拢后,得到一组通用参数(表 4):period、shift、ma_method、price、width。注意这里用 double 型的 width 一并代管点数场景,省掉一个 int 变量,属性窗口里能少一项。 所有要在指标属性窗口露出的边界参数,统一加 w_ 前缀,方便和中枢线参数区分。外汇与贵金属波动剧烈,ATR 口径在重大数据行情中可能瞬时扩宽,需实盘观察边界失效概率。

「把振荡器基类改造成通道中心线」

中心线类的底层思路和带 GUI 的通用振荡器基类一致,所以直接复用 CUniOsc 再动刀最省事。在 MQL5/Include 下建 UniChannel 文件夹,把 CUniOsc.mqh 拷过去改名 CUniChannel.mqh,只留 COscUni 基类、Calculate1 子类及其 ATR 子类,其余类全删。 用编辑器「查找和替换」把 COscUni 逐段换成 CChannelUni,别点「全部替换」——逐一确认实例才不会把继承关系弄断。中心线只画一条实线,删掉和第二个缓冲区相关的方法后,基类里就只剩一个 Calculate 方法。 以 iMA 为例写子类时,把原 CChannelUni_ATR 重命名为 CChannelUni_MA,换指标调用并清掉冗余代码。m_name 是子窗口左上角显示的指标名,而通道画在主图不可见名称,所以把 m_label 赋成和 m_name 一样的详细串,鼠标悬停中心线弹框里就能读到。 价格通道不在终端标准指标集合里,两种做法都行:写 Calculate 子类自己算,或用 iCustom 挂一个独立指标。前者少依赖文件但边界计算会跑两遍(一次定中心线、一次定宽度),后者不重复计算还多出一个可单独用的价格通道指标。 附件里的 CUniChannel.mqh 已含各标准指标子类和 iPriceChannel,其中心线数据在缓冲区 0。若你改类要取非零缓冲区的指标数据,要么新建 Calculate 子类,要么给基类加个缓冲区索引变量并在构造里赋值。外汇与贵金属杠杆高,改完参数请在 MT5 策略测试器先跑历史数据验证稳定性。

MQL5 / C++
<span class="keyword">class</span> CChannelUniWidth{
&nbsp;&nbsp; <span class="keyword">class="kw">protected</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span> m_handle; &nbsp; &nbsp; &nbsp;<span class="comment">class=class="str">"cmt">// 指标句柄</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> m_name; &nbsp; &nbsp; <span class="comment">class=class="str">"cmt">// 指标名称</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> m_label1; &nbsp; <span class="comment">class=class="str">"cmt">// 缓存区 class="num">1&nbsp;的名称&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> m_help; &nbsp; &nbsp; <span class="comment">class=class="str">"cmt">// 指标参数帮助</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> m_width; &nbsp; &nbsp;<span class="comment">class=class="str">"cmt">// 通道宽度</span>
&nbsp;&nbsp; <span class="keyword">class="kw">public</span>:
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 构造器</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">void</span> CChannelUniWidth(){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_handle=<span class="macro">INVALID_HANDLE</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 析构器</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">void</span> ~CChannelUniWidth(){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(m_handle!=<span class="macro">INVALID_HANDLE</span>){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">IndicatorRelease</span>(m_handle);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 从指标的 OnCalculate() 函数里调用的主要方法</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">int</span> Calculate( <span class="keyword">const</span> <span class="keyword">class="type">int</span> rates_total,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">const</span> <span class="keyword">class="type">int</span> prev_calculated,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> &amp; bufferCentral[],
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> &amp; bufferUpper[],
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> &amp; bufferLower[],
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(rates_total);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 得到加载的指标句柄</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span> Handle(){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(m_handle);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 句柄检查方法, 允许检查指标是否已成功加载&nbsp;&nbsp;</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">bool</span> CheckHandle(){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(m_handle!=<span class="macro">INVALID_HANDLE</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 得到指标名称</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> Name(){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(m_name);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 得到缓存区标签文本</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> Label1(){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(m_label1);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 得到参数提示</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> Help(){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(m_help);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
};
<span class="keyword">class</span> CChannelUni_Calculate1:<span class="keyword">class="kw">public</span> CChannelUni{
&nbsp;&nbsp; <span class="keyword">class="kw">public</span>:

常见问题

把通道做成带图形界面的指标,在图表挂一个可拖动面板,拖拽即改边界倍数,无需重编译。
急拉段 EMA 反应更快但易抖,SMA 平滑但滞后;建议先用 EMA(14) 试,再按品种波动调周期。
可以,小布能按你给的五种口径自动跑历史段,标出哪种在近期欧美盘过窄或失效。
ATR 倍数在跳空日更稳,标准差在均值回归段更贴;两者可并列显示做交叉验证。
拆出价格输入、平滑函数、偏移量三块,把输出从涨跌值改为中线值即可复用。