带有图形界面的通用通道(基础篇)
给通道指标加一个能拖的面板
在 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 周期,通道实时重绘;外汇与贵金属波动跳变快,这类实时联动对风控有帮助,但杠杆品种高风险,参数误设可能放大亏损。
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 ¢er[], 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 策略测试器先跑历史数据验证稳定性。
<span class="keyword">class</span> CChannelUniWidth{ <span class="keyword">class="kw">protected</span>: <span class="keyword">class="type">int</span> m_handle; <span class="comment">class=class="str">"cmt">// 指标句柄</span> <span class="keyword">class="type">class="kw">string</span> m_name; <span class="comment">class=class="str">"cmt">// 指标名称</span> <span class="keyword">class="type">class="kw">string</span> m_label1; <span class="comment">class=class="str">"cmt">// 缓存区 class="num">1 的名称 </span> <span class="keyword">class="type">class="kw">string</span> m_help; <span class="comment">class=class="str">"cmt">// 指标参数帮助</span> <span class="keyword">class="type">class="kw">double</span> m_width; <span class="comment">class=class="str">"cmt">// 通道宽度</span> <span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">// 构造器</span> <span class="keyword">class="type">void</span> CChannelUniWidth(){ m_handle=<span class="macro">INVALID_HANDLE</span>; } <span class="comment">class=class="str">"cmt">// 析构器</span> <span class="keyword">class="type">void</span> ~CChannelUniWidth(){ <span class="keyword">if</span>(m_handle!=<span class="macro">INVALID_HANDLE</span>){ <span class="functions">IndicatorRelease</span>(m_handle); } } <span class="comment">class=class="str">"cmt">// 从指标的 OnCalculate() 函数里调用的主要方法</span> <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, <span class="keyword">const</span> <span class="keyword">class="type">int</span> prev_calculated, <span class="keyword">class="type">class="kw">double</span> & bufferCentral[], <span class="keyword">class="type">class="kw">double</span> & bufferUpper[], <span class="keyword">class="type">class="kw">double</span> & bufferLower[], ){ <span class="keyword">class="kw">return</span>(rates_total); } <span class="comment">class=class="str">"cmt">// 得到加载的指标句柄</span> <span class="keyword">class="type">int</span> Handle(){ <span class="keyword">class="kw">return</span>(m_handle); } <span class="comment">class=class="str">"cmt">// 句柄检查方法, 允许检查指标是否已成功加载 </span> <span class="keyword">class="type">bool</span> CheckHandle(){ <span class="keyword">class="kw">return</span>(m_handle!=<span class="macro">INVALID_HANDLE</span>); } <span class="comment">class=class="str">"cmt">// 得到指标名称</span> <span class="keyword">class="type">class="kw">string</span> Name(){ <span class="keyword">class="kw">return</span>(m_name); } <span class="comment">class=class="str">"cmt">// 得到缓存区标签文本</span> <span class="keyword">class="type">class="kw">string</span> Label1(){ <span class="keyword">class="kw">return</span>(m_label1); } <span class="comment">class=class="str">"cmt">// 得到参数提示</span> <span class="keyword">class="type">class="kw">string</span> Help(){ <span class="keyword">class="kw">return</span>(m_help); } }; <span class="keyword">class</span> CChannelUni_Calculate1:<span class="keyword">class="kw">public</span> CChannelUni{ <span class="keyword">class="kw">public</span>: