通用的之字转向指标·综合运用
(3/3)· 从图表异动到EA实盘调用,一套面向对象之字转向如何终结慢、错、难三类老问题
把之字转向搬回主图
之前做的指标把之字转向画在子窗口,依赖 RSI 作为数据源,挂在独立窗口里。想在价格主图直接看拐点,就得放弃 RSI 这条源,另存一份叫 iUniZigZag 的副本。 具体改法:把文件里的 indicator_separate_window 改成 indicator_chart_window,让指标附着到主图;从 ESorce 枚举中删掉 Src_RSI;再把 OnInit() 里所有 RSI 相关初始化逻辑清掉。这样编译出来的版本只认价格本身推算之字转向。 完整代码在原始附件里可取到,读者在 MT5 里新建指标、按上面三步改完,主图就会叠加之字折线,无需切到子窗口比对。外汇与贵金属波动剧烈,主图叠加仅作结构参考,实际进出场仍需结合其他确认,杠杆品种高风险。
「让之字转向吃上别的指标输出」
在 MT5 里给自定义指标挂到别的指标之上,关键不在数据源写死,而在 OnCalculate 的入参。把指标附加到子窗口后,Apply to 选 Previous indicator's data 或 First indicator's data,终端就会把上游指标算好的 double 数组直接喂进 price[],而不是 OHLC。 下面这段是从 iUniZigZagSW 改出来的 iUniZigZagPriceSW 核心:丢掉 CSorceData 类,OnCalculate 改成接收 const double &price[],开头用循环把 price 同时写进 HighBuffer 和 LowBuffer,后续之字转向逻辑不变。 [CODE] int OnCalculate ( const int rates_total, const int prev_calculated, const int begin, const double &price[] ) { int start; if (prev_calculated==0) // 首次计算从0开始 { start=0; } else // 后续计算只补未处理部分 { start=prev_calculated-1; } for (int i=start;i<rates_total;i++) // 把上游指标数值灌入高低缓冲区 { HighBuffer[i]=price[i]; LowBuffer[i]=price[i]; } int rv; rv=dir.Calculate(rates_total, // 方向计算 prev_calculated, HighBuffer, LowBuffer, DirectionBuffer); if (rv==0) return(0); // 失败则返回0终止 zz.Calculate(rates_total, // 之字转向计算 prev_calculated, HighBuffer, LowBuffer, DirectionBuffer, LastHighBarBuffer, LastLowBarBuffer, ZigZagBuffer); return (rates_total); } [/CODE] 若想让它直接画在主图价格上而非子窗口,只要把 indicator_separate_window 改成 indicator_chart_window。外汇与贵金属波动剧烈,这类叠加指标仅用于结构识别,信号失效概率不低,实盘前务必在策略测试器用历史数据验证。 附件里的 iUniZigZagPrice 就是基于价格数组在主图工作的版本,和改完的 iUniZigZagPriceSW 对比着看,能弄清子窗口喂数据与主图喂价格两条路径的差异。
class="type">int OnCalculate( class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">int begin, class="kw">const class="type">class="kw">double &price[] ) { class="type">int start; if (prev_calculated==class="num">0) { start=class="num">0; } else { start=prev_calculated-class="num">1; } for (class="type">int i=start;i<rates_total;i++) { HighBuffer[i]=price[i]; LowBuffer[i]=price[i]; } class="type">int rv; rv=dir.Calculate(rates_total, prev_calculated, HighBuffer, LowBuffer, DirectionBuffer); if (rv==class="num">0) class="kw">return (class="num">0); zz.Calculate(rates_total, prev_calculated, HighBuffer, LowBuffer, DirectionBuffer, LastHighBarBuffer, LastLowBarBuffer, ZigZagBuffer); class="kw">return (rates_total); }
◍ EA里怎么快速扒之字转向的点
传统做法里,EA在循环里逐根柱子扫缓冲去定位最近顶底,随着K线增多会变慢。本文的之字转向改法多加了几个缓冲区:DirectionBuffer存最近一段方向,LastHighBarBuffer和LastLowBarBuffer分别存最近顶部、底部的柱索引,省掉了反复迭代。 MT5的CopyBuffer是从右往左数的,而指标内部从左往右计数,所以拿到LastHighBarBuffer里的索引后要用 bars - (int)lhb[0] - 1 换算。知道索引就能直接取ZigZagBuffer里那个柱的数值,不用再跑一遍全图扫描。 下面这段在OnInit里挂指标、OnTick里读方向并打到图表左上角注释,复制去MT5建EA就能跑。注意外汇和贵金属杠杆高、滑点大,这类读取只反映历史形态,不预示后续必走。 方向向上时,从缓冲区4取最近顶部索引、换算后取ZigZag数值,再从缓冲区5取前一个底部索引继续换数,交替调LastHighBar和LastLowBar就能顺藤摸瓜拉出整条折线上的顶底。附件里的eUniZigZagSW在注释第二行打最近两个点,第三行是GetTickCount返回的计数,证明EA在实时刷。 只取第0位置也能拿最近点,但上面这套写法的价值是可以任意展开N个之字转向点,做形态统计或批量回测时直接复用。
handle= iCustom( Symbol(), Period(), "iUniZigZagSW" ,SrcSelect, DirSelect, RSIPeriod, RSIPrice, MAPeriod, MAShift, MAMethod, MAPrice, CCIPeriod, CCIPrice, ZZPeriod); class="type">class="kw">string cs= "" ; class=class="str">"cmt">// 方向 class="type">class="kw">double dir[ class="num">1 ]; if ( CopyBuffer(handle, class="num">3 , class="num">0 , class="num">1 ,dir)<= class="num">0 ) { Print( "从之字转向中获取数据出错" ); class="kw">return ; } if (dir[ class="num">0 ]== class="num">1 ) { cs=cs+ "方向向上" ; } if (dir[ class="num">0 ]==- class="num">1 ) { cs=cs+ "方向向下" ; } Comment(cs, "\n" , GetTickCount()); if (dir[ class="num">0 ]== class="num">1 ) { class=class="str">"cmt">// 从左开始数最近顶部的柱的索引 if ( CopyBuffer(handle, class="num">4 , class="num">0 , class="num">1 ,lhb)<= class="num">0 ) { Print( "从 ZigZag 2中获取数据出错" ); class="kw">return ; } class=class="str">"cmt">// 从右开始数柱的索引 ind=bars-( class="type">int )lhb[ class="num">0 ]- class="num">1 ; class=class="str">"cmt">// 这个柱的 ZigZag 数值 if ( CopyBuffer(handle, class="num">2 ,ind, class="num">1 ,zz)<= class="num">0 ) { Print( "从 ZigZag 3中获取数据出错" ); class="kw">return ; } class=class="str">"cmt">//=== class=class="str">"cmt">// 这个顶部之前底部的索引 if ( CopyBuffer(handle, class="num">5 ,ind, class="num">1 ,llb)<= class="num">0 ) { Print( "从 ZigZag 4中获取数据出错" ); class="kw">return ; } class=class="str">"cmt">// 当从右开始数时柱的索引 ind=bars-( class="type">int )llb[ class="num">0 ]- class="num">1 ; class=class="str">"cmt">// 在索引处柱的 ZigZag 数值 if ( CopyBuffer(handle, class="num">2 ,ind, class="num">1 ,zz1)<= class="num">0 ) { Print( "从 ZigZag 5获取数据出错" ); class="kw">return ; } cs=cs+ "\n" +( class="type">class="kw">string )zz1[ class="num">0 ]+ " " +( class="type">class="kw">string )zz[ class="num">0 ]; } else if (dir[ class="num">0 ]==- class="num">1 ) { }
跨指标取数时的缓冲区陷阱
想在自编指标里叠加另一个指标的信号,最省事的办法是 iCustom() 直接拉数据。本文的 UniZigZagSW 就设计成可被其它指标通过 iCustom 访问,不用把源码再抄一遍。 但回看历史时要小心:之字转向在历史里会被重算,旧柱上的转折点位置未必和实时一致。它额外留了 LastHighBarBuffer 与 LastLowBarBuffer 两个缓冲区,存的是转向过程中的中间状态,比单纯读转向线更稳。 我们顺手做了一个轻量指标 iUniZigZagSWEvents:当 DirectionBuffer 数值变向时在图上画箭头,当 LastHighBarBuffer / LastLowBarBuffer 出现新高低时画点。完整代码在附件同名文件里,图 8 是它的实际外观——外汇与贵金属波动大,这类事件标记只作概率参考,实盘前请在 MT5 用历史数据验证重绘表现。
「别把通用类逼成参数怪兽」
教学归教学,这套指标只取了最小范围的源数据和类型来定方向,不是拿来直接跑生产的完整方案。但创建流程拆得很细,读懂后你完全可以自己派生需要的子类。 想做「一个通吃所有场景」的指标,难点不在写代码,而在后期维护和使用。往方向判定里塞越多外部指标,属性窗口要填的参数就越膨胀。 参数一多,拖到图表上反而没法用。更顺手的路子是用文中通用基类,各自派生独立指标,而不是堆参数做超级指标。外汇和贵金属波动大、杠杆高,这类定制指标也只是辅助,信号错了照样亏。
◍ 画得少,看得清
这套之字转向的附件里,iHighLowZigZag.mq5 和 iCloseZigZag.mq5 是两个最轻量的起点:前者抓每根 K 的高低价转折,后者只看收盘价收敛噪声。底层把取数、判方向、绘图拆成了 CSorceData.mqh / CZZDirection.mqh / CZZDraw.mqh 三个类,想改逻辑直接动对应头文件就行。 通用版 iUniZigZag 系列能挂主图也能丢子窗口,EA 里用 iCustom() 调 iUniZigZagSW 即可接信号。有用户在 MT5 2306 测试仪反馈,标准 ZigZag 偶尔画错,而这个高/低变体动态回测不出错——外汇和贵金属波动大,拿去验证时仍要留意外盘跳空带来的假转折。 真要省事,下 mql5.zip(18.42 KB)把例子都拖进 MT5 编译跑一遍,比读文档来得快。线画得少一点,转折反而看得清,参数别堆太密。