沃尔夫波形 (Wolfe Waves)(基础篇)
「沃尔夫波形在 MT5 里的真身」
沃尔夫波形(Wolfe Waves)是一套基于供需失衡收敛的价格形态,核心由 1-2-3-4-5 五个点构成推进浪,以及 A-B-C-D-E 的修正浪;其中 1-3-5 与 2-4 的连线形成两条收敛的楔形边界,第 5 点(或 E 点)通常落在边界延长线的交汇区附近。 在 MT5 生态里,这类形态常被封装成自定义指标或脚本自动标注。一个公开示例发布于 2017 年 6 月 23 日 10:36,累计被查看 17 376 次、收藏/跟评 25 次,作者 Dmitry Fedoseev——这至少说明该形态在实盘社群里有稳定检索需求,而非冷门玩具。 外汇与贵金属品种上跑沃尔夫波形,需清醒:杠杆品种波动快,楔形边界常被毛刺刺穿,E 点触发后价格倾向反向但不保证;建议先在 EURUSD 的 H1 周期用历史回放验证边界识别逻辑,再谈参数微调。
◍ 沃尔夫波形的识别前置条件
要在 MT5 里自动抓沃尔夫波形,第一步不是写策略,而是先定清楚之字折线(ZigZag)的参数。之字折线的回溯深度和最小波动幅度直接决定你能抓到几段有效峰值,参数过紧会漏掉延伸浪,过松会把噪声当拐点。 原文给出的规则链是:先选之字折线 → 收集其波峰波谷坐标 → 做一点几何关系校验 → 判定是否构成 1-2-3-4-5 点结构。其中几何校验主要看 1-3 与 2-4 的通道倾斜,以及第 5 点相对通道的穿透位置,这是沃尔夫原书里区分「有效波形」和「随机折返」的核心。 实际在 MT5 验证时,建议先把 ZigZag 的 deviation 设为 2~3 点(标准品种点值),depth 设 12 根 K 线以上,再让 EA 把识别到的 1-5 点连线和目标线画出来。外汇与贵金属波动受杠杆与跳空影响,这类几何识别仅提供概率倾向,不代表后续必然朝目标线运行,交易需自担高风险。
沃尔夫波形到底是什么
沃尔夫波形(Wolfe Wave)由 Bill Wolfe 提出,是一种带特殊细节的三角形或楔形类形态,他本人称上升版本为“上升的楔子”。它不只能提示入场时机和方向,还试图框出价格目标位与到达时间,属于图形分析里偏结构化的一种。 这套方法的实战价值在于可检测、可解释:规则明确,能直接落到 MT5 指标上。本文会基于此前“通用之字折线”里的 ZigZag 指标,写一个名为自动识别并绘制波形的指标,再挂一个简单 EA 做回测验证。 外汇与贵金属波动剧烈、杠杆高风险大,任何形态都只是概率优势,沃尔夫波形也不例外。开 MT5 把指标跑出来,先看它标出的 1-2-3-4-5 点是否和你肉眼数的一致,再谈信不信。
「沃尔夫波形识别的硬约束条件」
买入沃尔夫波形的基础结构很直观:价格先走出两个递降低点(点1、点3)和两个递降高点(点2、点4),点4反转后继续下探,触及1—3延长线时即为点5入场位。1—3与2—4延长线交点为点6,标定目标到达时间;1—4延长线与过点6垂线交点为点7目标位。原书未给止损算法,实盘需自行设定,外汇与贵金属杠杆高,止损缺失极易放大回撤。 在把规则写成MT5指标时,会暴露出几条必须机器校验的几何约束。点3必须明显低于点1,需满足 v3 < v1 - d1,其中 d1 = (点1到点2垂直距) × K1,K1默认0.1。点4须高于点1且低于点2:v4 > v1 + d1 且 v4 < v2 - d2,d2 = (点2到点3垂直距) × K2,K2默认0.1。 时间交汇也有门槛:2—4与1—3延长线必须在右侧相交,即线段2-2'高度 h2 远超 4-4' 高度 h4,校验式 h2 - h4 > K3 × h2,K3默认0.1。这些阈值不是绝对真理,但能在EA里滤掉绝大多数伪波形。打开MT5把K1/K2/K3从0.1往0.2调,会看到信号显著变稀疏。
◍ 子窗口版之字折线为何更顺手
附件里那套通用之字折线指标有十来个变体,先砍掉 iUniZigZagPrice 和 iUniZigZagPriceSW——它们依赖图表上别的指标算,只能看不能跑 EA。iCloseZigZag 和 iHighLowZigZag 只是教学示例,也舍去。剩 iUniZigZag 和 iUniZigZagSW 两个能用的。 iUniZigZagSW 跑在子窗口,功能面比主图版宽,所以我们选它。附件里的 iUniZigZagSWEvents 用 iCustom() 去调 iUniZigZagSW,还把沃尔夫波形检测代码和之字折线本体拆开,这套最干净,直接拿来用。 指标在价格图上占四个缓冲区:两个画箭头(标形态识别位),两个画点(标目标)。箭头指方向,点给目标位。波形和构型我们用趋势线对象画,线段比射线更方便看不同结构。 麻烦点在 SrcSelect 参数:它决定之字折线拿什么数据算,有四个选项——Src_HighLow(高低价)、Src_Close(收盘价)、Src_RSI、Src_MA。要是拿价格(HighLow/Close)建之字折线,预测目标就是价格,能直接挂止盈、图上画线;选了 Src_RSI,目标值变成 RSI 数值,图表上画不出目标价,只能等 RSI 触值市价平。 外汇和贵金属波动大、滑点凶,RSI 触目标时市价平未必成交在缓冲区值上,实战里这种想法可能落空。但不管哪种模式,目标值都留在对应价格缓冲区里给 EA 用,只是不显示而已。
把之字折线峰值塞进结构数组
做 Wolfe Waves 指标的第一步,是把底层之字折线的每个转折点抓出来存好。直接读历史去翻峰值太慢,更好的办法是每当折线调头就往数组里追加一条记录,线段延伸时只改最后一个元素。 峰值需要同时记住三样东西:价格极值、方向、以及它落在哪根柱线上(索引从左往右数)。用结构打包最干净,下面这段就是核心定义。 [CODE] struct SPeackTrough{ double Val; // 峰值 int Dir; // 方向 int Bar; // 柱线索引 }; SPeackTrough PeackTrough[]; [/CODE] 基于最高/最低价的折线,调头时数组加一就行;但若基于收盘价或别的指标数据,当前柱线没走完时方向可能来回变,每次重算都得把数组回滚到前一根柱线的状态。频繁改数组尺寸会拖慢指标,所以引入 PreCount / CurCount 两个变量:前者存上一根柱线的大小,后者存当前计算的大小,只在需要时用块增长,绝不缩小。 新柱线靠时间判断——time[i] 变了就是新 K 线。OnCalculate 里 prev_calculated==0 代表全量计算,此时 CurCount、PreCount、LastTime 全清零;否则只从 prev_calculated-1 那根开始算。柱线重算时把 PreCount 灌回 CurCount,开盘新柱时反过来把 CurCount 交给 PreCount。 方向监控同理,用 PreDir / CurDir 配对。AddNew() 在调头时写入新结构,RefreshLast() 只更新末元素的 Val。现阶段指标存为 iWolfeWaves_Step_1,已经能当任意之字折线形态识别指标的底座,开 MT5 挂上它,调 SrcSelect 参数就能验证不同数据源下的峰值捕获差异。外汇与贵金属波动剧烈,这类指标仅作结构参考,实盘信号出现误判的概率不低。
class="kw">struct SPeackTrough{ class="type">class="kw">double Val; class=class="str">"cmt">// 峰值 class="type">int Dir; class=class="str">"cmt">// 方向 class="type">int Bar; class=class="str">"cmt">// 柱线索引 }; SPeackTrough PeackTrough[]; class="type">int PreCount; class=class="str">"cmt">// 前一根柱线时 PeackTrough 数组的大小 class="type">int CurCount; class=class="str">"cmt">// 当前计算柱线时 PeackTrough 数组的大小 class="type">class="kw">datetime LastTime; class="type">int start; class=class="str">"cmt">// 起始计算的柱线的索引变量 if(prev_calculated==class="num">0){ class=class="str">"cmt">// 计算全部的柱线 start=class="num">1; CurCount=class="num">0; PreCount=class="num">0; LastTime=class="num">0; } else{ class=class="str">"cmt">// 计算新柱线 start=prev_calculated-class="num">1; } for(class="type">int i=start;i<rates_total;i++){ if(time[i]>LastTime){ class=class="str">"cmt">// 计算新 (下一次) 柱线 LastTime=time[i]; PreCount=CurCount; PreDir=CurDir; } else{ class=class="str">"cmt">// 柱线重新计算 CurCount=PreCount; CurDir=PreDir; } UpArrowBuffer[i]=EMPTY_VALUE; DnArrowBuffer[i]=EMPTY_VALUE; UpDotBuffer[i]=EMPTY_VALUE; DnDotBuffer[i]=EMPTY_VALUE; class=class="str">"cmt">// 方向 class="type">class="kw">double dir[class="num">2]; if(CopyBuffer(handle,class="num">3,rates_total-i-class="num">1,class="num">2,dir)<=class="num">0){ class="kw">return(class="num">0); } if(dir[class="num">0]==class="num">1 && dir[class="num">1]==-class="num">1){ DnArrowBuffer[i]=high[i]; c++; } else if(dir[class="num">0]==-class="num">1 && dir[class="num">1]==class="num">1){ UpArrowBuffer[i]=low[i]; c++; } class=class="str">"cmt">// 新的最高价 class="type">class="kw">double lhb[class="num">2]; if(CopyBuffer(handle,class="num">4,rates_total-i-class="num">1,class="num">2,lhb)<=class="num">0){ class="kw">return(class="num">0); } if(lhb[class="num">0]!=lhb[class="num">1]){ UpDotBuffer[i]=high[i]; } class=class="str">"cmt">// 新的最低价 class="type">class="kw">double llb[class="num">2]; if(CopyBuffer(handle,class="num">5,rates_total-i-class="num">1,class="num">2,llb)<=class="num">0){ class="kw">return(class="num">0); }