通用的之字转向指标·综合运用
📐

通用的之字转向指标·综合运用

(3/3)· 从图表异动到EA实盘调用,一套面向对象之字转向如何终结慢、错、难三类老问题

实战向进阶 第 3/3 篇
不少交易者把之字转向当实时抄底摸顶信号,却忽略最后一段在价格反转时仍可能回修。用滞后确认的折点直接下单,外汇贵金属里容易吃假突破的止损。

把之字转向搬回主图

之前做的指标把之字转向画在子窗口,依赖 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 对比着看,能弄清子窗口喂数据与主图喂价格两条路径的差异。

MQL5 / C++
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个之字转向点,做形态统计或批量回测时直接复用。

MQL5 / C++
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 编译跑一遍,比读文档来得快。线画得少一点,转折反而看得清,参数别堆太密。

把多类型之字转向交给小布盯盘
这些通用之字转向的画法切换与EA取数逻辑,小布盯盘的AIGC已内置对照视图,打开对应品种页即可看不同参数下的折点重构,你只管判形态。

常见问题

因为局部顶底需后续若干柱确认,未确认前方向可能随新高低价回修,属指标固有滞后,不是bug。
本文基类拆分后只算必要阶段,合理缓存时速度倾向优于早期慢速实现,但仍受品种tick量影响。
可以,内置视图支持切换基于最高低价或收盘价的类型,省去自行编译ex5的重复劳动。
建议通过统一类暴露最后折点与方向枚举,避免直接读缓冲区,降低耦合与出错概率。
折点重绘特性可能在波动期频繁变向,单独使用误判概率高,须配合量价或波动率过滤且明确高风险。