沃尔夫波形 (Wolfe Waves)·综合运用
🌊

沃尔夫波形 (Wolfe Waves)·综合运用

(3/3)·手算楔形拐点费时易错,这篇把指标、警报与回测EA一次串起来

含代码示例实战向 第 3/3 篇

多数人在MT5里靠肉眼找沃尔夫波形,延线交点和时点算到一半就放弃了。把规则写死成代码后,才会发现书里没说的边界条件比形态本身更影响信号质量。

点2'高于点2时的卖区着色逻辑

在下跌结构识别里,若二次测试的高点 v2x 减去前高 v2 的差值为正,即 h2=v2x-v2 成立,说明点 2' 实际高于点 2,此时原假设的次级反弹失效,应当切换为另一组颜色标记。 代码里用 SellColor 与 SellTargetColor 两组参数接管绘制,把 v1 到 v5l 的五个价格节点、i1 到 i5 的对应索引、time 与 i 的当前位置、以及 tb/tv/rates_total 等上下文一并传给 DrawObjects,实现卖区图形的重绘。 开 MT5 把这段塞进你的形态扫描 EA,改一下 SellColor 的十六进制值,就能直观看出哪些候选形态被判定为点 2' 过高而被剔除。外汇与贵金属波动剧烈,这类结构过滤只降低噪音,不预示方向,实盘仍属高风险。

MQL5 / C++
class="type">class="kw">double h2=v2x-v2; class=class="str">"cmt">// 点 class="num">2&class="macro">#x27; 高于点 class="num">2
class=class="str">"cmt">// 用其它颜色绘制
DrawObjects(SellColor,
            SellTargetColor,
            v1,
            v2,
            v3,
            v4,
            v5l,
            i1,
            i2,
            i3,
            i4,
            i5,
            time,
            i,
            tb,
            tv,
            rates_total);

「波形与目标构型的绘制逻辑」

沃尔夫波形和交易目标构型在 MT5 指标里共用一个 DrawObjects() 出口函数。向上和向下的波形分别吃 BuyColor / SellColor,目标构型再单独吃 BuyTargetColor / SellTargetColor,这四个都是外部 input,交易者可以直接在指标属性里改色。 峰值 1-4 传的是之字折线尾端的真实值加柱线索引,峰值 5 是例外——它不传尾端,而是把已经算好的线 1-3 的值塞进去。图形对象锚定要靠时间,所以 time 数组指针、当前柱 i、目标柱 tb、目标值 tv、总柱数 rates_total 全部一并进函数。 只在 SrcSelect 是 Src_HighLow 或 Src_Close 时才允许画图。若在 OnInit() 里发现是别的源,就借一个 _DrawWaves 中间变量把绘图强制关掉,顺手把目标缓冲区的线颜色设成 clrNONE,历史回看才不会出鬼影。 目标柱可能落在已有 K 线上,也可能漂在最后一根右侧。代码里先声明一个分数型 target_bar,用 MathFloor() 抬到最近整数得 tbc;用 MathCeil() 也行,但线 1-3 和 2-4 的交点会更贴目标自然些。tbc 比 target_bar 略大,所以进函数后会对线 2-4 重算一次,保证在确切的 target_value 价位交准。 所有直线走通用的 fObjTrend() 画,前 5 个参数(对象名、两端时间、两端价位)必填,后面颜色宽度射线方向之类都可省。最后补一根目标柱垂直线和一条目标价水平线,构型就完整了。外汇和贵金属波动剧烈,这类构型只具提示意义,实际触发概率随品种和周期变化,请先在 MT5 策略测试器里验证再上实盘。

MQL5 / C++
input class="type">bool   DrawWaves       =  true;             class=class="str">"cmt">// 启用波形和对象绘制
input class="type">class="kw">color  BuyColor        =  clrAqua;            class=class="str">"cmt">// 买入波形颜色
input class="type">class="kw">color  SellColor       =  clrRed;            class=class="str">"cmt">// 卖出波形颜色
input class="type">int    WavesWidth      =  class="num">2;                 class=class="str">"cmt">// 波形宽度
input class="type">bool   DrawTarget      =  true;             class=class="str">"cmt">// 附加的启用/禁用构型
input class="type">int    TargetWidth     =  class="num">1;                 class=class="str">"cmt">// 对象宽度
input class="type">class="kw">color  BuyTargetColor  =  clrRoyalBlue;     class=class="str">"cmt">// 买入对象颜色
input class="type">class="kw">color  SellTargetColor =  clrPaleVioletRed; class=class="str">"cmt">// 卖出对象颜色
class="type">bool _DrawWaves;
if(SrcSelect==Src_HighLow || SrcSelect==Src_Close){
   _DrawWaves=DrawWaves;
}
else{
   _DrawWaves=class="kw">false;
   PlotIndexSetInteger(class="num">2,PLOT_LINE_COLOR,clrNONE);
   PlotIndexSetInteger(class="num">3,PLOT_LINE_COLOR,clrNONE);      
}  
class="type">void DrawObjects( class="type">class="kw">color col,
                  class="type">class="kw">color tcol,
                  class="type">class="kw">double v1,
                  class="type">class="kw">double v2,
                  class="type">class="kw">double v3,
                  class="type">class="kw">double v4,
                  class="type">class="kw">double v5,
                  class="type">int i1,

◍ 五点波形的图形对象绘制逻辑

这段函数负责把已识别的五点价格波形以及预测构型直接画到 MT5 主图上。核心思路是用程序名+柱线时间做对象名前缀,避免多实例叠加时图形互相覆盖。 前缀拼接用了 MQLInfoString(MQL_PROGRAM_NAME) 配合 IntegerToString(time[i]),每个波形独占一套 "12/23/34/45" 的线段对象。fObjTrend 连续四次调用,分别连接 (i1,v1)-(i2,v2) 到 (i4,v4)-(i5,v5) 四个分段,col 与 WavesWidth 控制颜色和线宽。 当 DrawTarget 开关开启,代码会先由 MathCeil(target_bar) 拿到目标柱线索引 tbc。若 tbc 小于 rates_total,目标落在已有 K 线内;否则用 time[rates_total-1] 加 (tbc-rates_total+1)*PeriodSeconds() 推算未来时间——这是手动外推预测位的常见写法。 构型部分再用 y3() 在目标柱算出自 i1-i3、i2-i4、i1-i4 三条延长线的理论值,并画出 "13/24/14" 线段,以及一条水平目标带(67)和一条垂直目标线(7h)。外汇与贵金属波动剧烈,这类外推仅反映几何延续概率,实盘须自担高风险。

MQL5 / C++
class="type">int i2,
class="type">int i3,
class="type">int i4,
class="type">int i5,
class="kw">const class="type">class="kw">datetime & time[],
class="type">int i,
class="type">class="kw">double target_bar,
class="type">class="kw">double target_value,
class="type">int rates_total){
  class=class="str">"cmt">// 用于图形对象名称的前缀 
  class="type">class="kw">string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time[i])+"_";
  class=class="str">"cmt">// 绘制波形                
  fObjTrend(prefix+"class="num">12",time[i1],v1,time[i2],v2,col,WavesWidth);
  fObjTrend(prefix+"class="num">23",time[i2],v2,time[i3],v3,col,WavesWidth);  
  fObjTrend(prefix+"class="num">34",time[i3],v3,time[i4],v4,col,WavesWidth);
  fObjTrend(prefix+"class="num">45",time[i4],v4,time[i5],v5,col,WavesWidth);
  class=class="str">"cmt">// 绘制构型
  if(DrawTarget){  

    class="type">class="kw">datetime TargetTime;
    class=class="str">"cmt">// 获取整数型的目标柱线索引 
    class="type">int tbc=(class="type">int)MathCeil(target_bar);
    if(tbc<rates_total){ class=class="str">"cmt">// 目标位于图表上存在的柱线之内
      TargetTime=time[tbc];
    }
    else{ class=class="str">"cmt">// 目标位于未来
      TargetTime=time[rates_total-class="num">1]+(tbc-rates_total+class="num">1)*PeriodSeconds();
    }
    class=class="str">"cmt">// 计算目标所在柱线处的值
    class="type">class="kw">double tv13=y3(i1,v1,i3,v3,tbc);  
    class="type">class="kw">double tv24=y3(i2,v2,i4,v4,tbc);  
    class="type">class="kw">double tv14=y3(i1,v1,i4,v4,tbc);
    class=class="str">"cmt">// 构型
    fObjTrend(prefix+"class="num">13",time[i1],v1,TargetTime,tv13,tcol,TargetWidth);  
    fObjTrend(prefix+"class="num">24",time[i2],v2,TargetTime,tv24,tcol,TargetWidth);  
    fObjTrend(prefix+"class="num">14",time[i1],v1,TargetTime,tv14,tcol,TargetWidth);
    class=class="str">"cmt">// 目标价位水平线
    fObjTrend(prefix+"class="num">67",TargetTime,tv24,TargetTime,tv14,tcol,TargetWidth);  
    class=class="str">"cmt">// 目标价位垂直线 
    fObjTrend(prefix+"7h",time[i],target_value,TargetTime,target_value,tcol,TargetWidth);    
  }
}

目标柱线落在未来时怎么补时间

画趋势线时最容易被忽略的一种情况:目标索引 tbc 可能大于当前图表已加载的柱线总数 rates_total。代码里用 if/else 把这两种状态拆开处理——若 tbc<rates_total,直接取 time[tbc] 作为目标时间;若目标在尚未形成的未来,则用最后一根已知柱的时间加上 (tbc-rates_total+1) 个周期秒数推算。 [CODE] if(tbc<rates_total){ // 目标位于图表上存在的柱线之内 TargetTime=time[tbc]; } else{ // 目标位于未来 TargetTime=time[rates_total-1]+(tbc-rates_total+1)*PeriodSeconds(); } // 计算目标所在柱线处的值 double tv13=y3(i1,v1,i3,v3,tbc); double tv24=y3(i2,v2,i4,v4,tbc); double tv14=y3(i1,v1,i4,v4,tbc); fObjTrend(prefix+"13",time[i1],v1,TargetTime,tv13,tcol,TargetWidth); fObjTrend(prefix+"24",time[i2],v2,TargetTime,tv24,tcol,TargetWidth); fObjTrend(prefix+"14",time[i1],v1,TargetTime,tv14,tcol,TargetWidth); [/CODE] 上面这段先按 tbc 算好 TargetTime,再用 y3() 求出三条连线在目标柱处的价格(tv13/tv24/tv14),最后调 fObjTrend 把线画出来。注意 fObjTrend 只是封装,真正干活的是 ObjectCreate(OBJ_TREND) 加一连串 ObjectSetInteger / ObjectSetString。 [CODE] void fObjTrend( string aObjName, datetime aTime_1, double aPrice_1, datetime aTime_2, double aPrice_2, color aColor = clrRed, color aWidth = 1, bool aRay_1 = false, bool aRay_2 = false, string aText = "", int aWindow = 0, color aStyle = 0, int aChartID = 0, bool aBack = false, bool aSelectable = false, bool aSelected = false, long aTimeFrames = OBJ_ALL_PERIODS ){ ObjectCreate(aChartID,aObjName,OBJ_TREND,aWindow,aTime_1,aPrice_1,aTime_2,aPrice_2); ObjectSetInteger(aChartID,aObjName,OBJPROP_BACK,aBack); ObjectSetInteger(aChartID,aObjName,OBJPROP_COLOR,aColor); ObjectSetInteger(aChartID,aObjName,OBJPROP_SELECTABLE,aSelectable); ObjectSetInteger(aChartID,aObjName,OBJPROP_SELECTED,aSelected); ObjectSetInteger(aChartID,aObjName,OBJPROP_TIMEFRAMES,aTimeFrames); ObjectSetString(aChartID,aObjName,OBJPROP_TEXT,aText); [/CODE] 逐行看:aObjName 是对象名,前缀加 "13" 这类后缀可避免重名;aTime_1/aPrice_1 是起点,aTime_2/aPrice_2 即前面算出的 TargetTime 与目标值;aColor 默认红、aWidth 默认 1;aRay 控制是否射线延伸,这里默认 false 表示线段到终点截止;OBJPROP_BACK 设 true 可让线沉到 K 线后面。外汇与贵金属波动剧烈,这类自动画线仅作结构参考,实盘信号可能随跳空失效。

MQL5 / C++
if(tbc<rates_total){ class=class="str">"cmt">// 目标位于图表上存在的柱线之内
  TargetTime=time[tbc];
}
else{ class=class="str">"cmt">// 目标位于未来
  TargetTime=time[rates_total-class="num">1]+(tbc-rates_total+class="num">1)*PeriodSeconds();
}
class=class="str">"cmt">// 计算目标所在柱线处的值
class="type">class="kw">double tv13=y3(i1,v1,i3,v3,tbc);
 class="type">class="kw">double tv24=y3(i2,v2,i4,v4,tbc);
 class="type">class="kw">double tv14=y3(i1,v1,i4,v4,tbc);
fObjTrend(prefix+"class="num">13",time[i1],v1,TargetTime,tv13,tcol,TargetWidth);
 fObjTrend(prefix+"class="num">24",time[i2],v2,TargetTime,tv24,tcol,TargetWidth);
 fObjTrend(prefix+"class="num">14",time[i1],v1,TargetTime,tv14,tcol,TargetWidth);
class="type">void fObjTrend( class="type">class="kw">string aObjName,
 class="type">class="kw">datetime aTime_1,
 class="type">class="kw">double aPrice_1,
 class="type">class="kw">datetime aTime_2,
 class="type">class="kw">double aPrice_2,
 class="type">class="kw">color aColor = clrRed,
 class="type">class="kw">color aWidth = class="num">1,
 class="type">bool aRay_1 = class="kw">false,
 class="type">bool aRay_2 = class="kw">false,
 class="type">class="kw">string aText = "",
 class="type">int aWindow = class="num">0,
 class="type">class="kw">color aStyle = class="num">0,
 class="type">int aChartID = class="num">0,
 class="type">bool aBack = class="kw">false,
 class="type">bool aSelectable = class="kw">false,
 class="type">bool aSelected = class="kw">false,
 class="type">long aTimeFrames = OBJ_ALL_PERIODS
 ){
  ObjectCreate(aChartID,aObjName,OBJ_TREND,aWindow,aTime_1,aPrice_1,aTime_2,aPrice_2);
  ObjectSetInteger(aChartID,aObjName,OBJPROP_BACK,aBack);
  ObjectSetInteger(aChartID,aObjName,OBJPROP_COLOR,aColor);
  ObjectSetInteger(aChartID,aObjName,OBJPROP_SELECTABLE,aSelectable);
  ObjectSetInteger(aChartID,aObjName,OBJPROP_SELECTED,aSelected);
  ObjectSetInteger(aChartID,aObjName,OBJPROP_TIMEFRAMES,aTimeFrames);
  ObjectSetString(aChartID,aObjName,OBJPROP_TEXT,aText);

「趋势线的射线与端点控制」

在 MT5 里画一条可控的趋势线,核心不是画线动作本身,而是用 ObjectSetInteger 把射线方向和线宽样式先钉死。OBJPROP_RAY_LEFT 与 OBJPROP_RAY_RIGHT 决定线条向左、向右是否无限延伸,传 0 或 1 即可切换,避免默认双向射线把图表搞脏。 线宽 OBJPROP_WIDTH 和线型 OBJPROP_STYLE 也走同一函数,参数用整型常量,例如实线、虚线在 MQL5 里是枚举值而非字符串。下面这段是实际封装后的调用片段,两个 fObjTrend 分别用前缀 "67" 和 "7h" 建对象,传入起止时间价格与颜色线宽。 端点定位靠 ObjectMove,锚点索引 0 和 1 对应第一、第二个坐标,时间价格必须同时给,否则线会漂。外汇与贵金属波动快,这类手动锚定射线在高杠杆下误判风险高,参数错了可能画出反向误导线。

MQL5 / C++
ObjectSetInteger(aChartID,aObjName,OBJPROP_WIDTH,aWidth);
ObjectSetInteger(aChartID,aObjName,OBJPROP_STYLE,aStyle);
ObjectSetInteger(aChartID,aObjName,OBJPROP_RAY_LEFT,aRay_1);
ObjectSetInteger(aChartID,aObjName,OBJPROP_RAY_RIGHT,aRay_2);
ObjectMove(aChartID,aObjName,class="num">0,aTime_1,aPrice_1);
ObjectMove(aChartID,aObjName,class="num">1,aTime_2,aPrice_2);
}
fObjTrend(prefix+"class="num">67",TargetTime,tv24,TargetTime,tv14,tcol,TargetWidth);
fObjTrend(prefix+"7h",time[i],target_value,TargetTime,target_value,tcol,TargetWidth);

◍ 之字折线重绘时的对象清理逻辑

基于收盘价或另一指标生成之字折线时,柱线尚未收盘,箭头和点会在实时波动里反复出现又消失。若不在每轮指标计算开头清空缓冲区和删除图形对象,图表会残留上一 tick 的无效标记,干扰形态识别。 主循环开头先把上下箭头与点的缓冲区置为 EMPTY_VALUE,再判断 _DrawWaves 开关:若开启波形绘制,就调用 DeleteObjects(time[i]),传入当前柱时间,删掉该柱名下挂的 12、23、34 等 10 个对象前缀。 DeleteObjects 内部用 MQL_PROGRAM_NAME 加时间和下划线拼前缀,只删自己指标创建的局部对象,不会误伤其他指标。指标卸载时 OnDeinit 自动跑 ObjectsDeleteAll,第二个参数同样传程序名,确保图表上清掉的仅限本指标遗产。 外汇与贵金属行情跳空频繁,这类动态删除逻辑若漏写,MT5 切周期或重加载时可能堆积几十个幽灵对象,建议直接把下面代码粘进 EA 验证重绘表现。

MQL5 / C++
UpArrowBuffer[i]=EMPTY_VALUE;
DnArrowBuffer[i]=EMPTY_VALUE;

UpDotBuffer[i]=EMPTY_VALUE;
DnDotBuffer[i]=EMPTY_VALUE;

if(_DrawWaves){
   DeleteObjects(time[i]);
}

class="type">void DeleteObjects(class="type">class="kw">datetime time){
   class="type">class="kw">string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time)+"_";
   ObjectDelete(class="num">0,prefix+"class="num">12");
   ObjectDelete(class="num">0,prefix+"class="num">23");
   ObjectDelete(class="num">0,prefix+"class="num">34");
   ObjectDelete(class="num">0,prefix+"class="num">45");
   ObjectDelete(class="num">0,prefix+"class="num">13");
   ObjectDelete(class="num">0,prefix+"class="num">24");
   ObjectDelete(class="num">0,prefix+"class="num">14");
   ObjectDelete(class="num">0,prefix+"class="num">67");
   ObjectDelete(class="num">0,prefix+"7h");
}

class="type">void OnDeinit(class="kw">const class="type">int reason){
   ObjectsDeleteAll(class="num">0,MQLInfoString(MQL_PROGRAM_NAME));
   ChartRedraw(class="num">0);
}

给狼形波指标接上箭头警报

狼形波指标 iWolfeWaves 在成型后需要一套警报机制,用来在每个新箭头出现时弹出提示。思路参照了通用趋势指标里那套做法:根据之字折线类型,决定盯正在形成的柱线还是已经收盘的柱线。 警报类型用枚举 EAlerts 控制,0 为关闭,1 为盯 Bar0(形成中柱线,适合最高价-最低价基准的折线),2 为盯 Bar1(已完成柱线,适合收盘价或指标基准的折线)。这个 input 变量直接暴露在属性窗口,切换不用改代码。 CheckAlerts() 是独立函数,接收 rates_total 和 time[] 数组。它用两个 static datetime 变量 tm0、tm1 分别记住最近一次买、卖警报的柱线时间,避免同一根柱线重复弹窗。核心判断是看 UpArrowBuffer / DnArrowBuffer 在 rates_total-Alerts 位置是否非 EMPTY_VALUE,且时间较上次有变化才发 Alert()。 在 OnCalculate() 主循环之后调用 CheckAlerts(),末尾再视情况 ChartRedraw(0) 强制刷新,波形和箭头绘制会更快显现。外汇与贵金属波动剧烈,这类警报只作形态提醒,信号失效概率不低,实盘须自担高风险。

MQL5 / C++
enum EAlerts{
   Alerts_off=class="num">0,   class=class="str">"cmt">// 禁用警报
   Alerts_Bar0=class="num">1, class=class="str">"cmt">// 形成中柱线
   Alerts_Bar1=class="num">2  class=class="str">"cmt">// 完成的柱线
};
input EAlerts                Alerts       =  Alerts_off;
class="type">void CheckAlerts(class="type">int rates_total,class="kw">const class="type">class="kw">datetime & time[]){
   if(Alerts!=Alerts_off){ class=class="str">"cmt">// 启用警报
      class="kw">static class="type">class="kw">datetime tm0=class="num">0; class=class="str">"cmt">// 一个变量, 最后一次买入警报的柱线时间
      class="kw">static class="type">class="kw">datetime tm1=class="num">0; class=class="str">"cmt">// 一个变量, 最后一次卖出警报的柱线时间
      if(tm0==class="num">0){ class=class="str">"cmt">// 第一次函数执行
         class=class="str">"cmt">// 变量初始化
         tm0=time[rates_total-class="num">1];
         tm1=time[rates_total-class="num">1];
      }
      class="type">class="kw">string mes=""; class=class="str">"cmt">// 消息变量
      class=class="str">"cmt">// 有一个向上箭头, 最后一根柱线没有警报
      if(UpArrowBuffer[rates_total-Alerts]!=EMPTY_VALUE && 
         tm0!=time[rates_total-class="num">1]
      ){
         tm0=time[rates_total-class="num">1]; class=class="str">"cmt">// 记住最后的警报时间
         mes=mes+" buy"; class=class="str">"cmt">// 形成一条消息
      }
      class=class="str">"cmt">// 有一个向下箭头, 最后一根柱线没有警报
      if(DnArrowBuffer[rates_total-Alerts]!=EMPTY_VALUE && 
         tm1!=time[rates_total-class="num">1]
      ){
         tm1=time[rates_total-class="num">1]; class=class="str">"cmt">// 记住最后的警报时间
         mes=mes+" sell"; class=class="str">"cmt">// 形成一条消息
      } 
      if(mes!=""){ class=class="str">"cmt">// 有一条消息
         class=class="str">"cmt">// 打开消息窗口
         Alert(MQLInfoString(MQL_PROGRAM_NAME)+"("+Symbol()+","+IntegerToString(PeriodSeconds()/class="num">60)+"):"+mes);
      }       
   }  
}
if(_DrawWaves){
   ChartRedraw(class="num">0);
}

「把狼形指标接进EA跑信号」

指标写完了不代表它能赚钱,得让专家交易系统在历史里照着箭头实跑一遍,才能看出图形分析逻辑到底有没有用。我们新建一个叫 eWolfeWaves 的 EA,把指标的外部参数整段搬过来,再补上止损止盈的控制项。 SL/TP 给了两套逻辑:FixedSLTP=false 时,止盈挂在指标目标点价位,止损按 StopLoss_K 系数依止盈比例算,这套只支持基于最高最低价或收盘价的之字折线;FixedSLTP=true 时,直接用 StopLoss、TakeProfit 点数,对指标型和价格型折线都能用。 OnInit 里先卡账户类型,不是对冲账户直接 INIT_FAILED 退出;非可视测试模式就把画线关掉,避免多余开销。指标用 iCustom 加载,句柄拿不到就停。 基于高低价的箭头不会消失,EA 可以在未成形 K 线操作;其余情况用全局变量 Shift 锁成只认第一根完整柱的信号。OnTick 里用 LastBuyTime / LastSellTime 防同根 K 线重复开仓,开仓失败下一 tick 再试,只要有信号就一直尝试。 用 EURUSD H1 全历史、默认参数跑下来,1991 年前的回撤大概率和早期报价质量差有关,之后进入稳步向上区间,未经优化整体为正。外汇和贵金属属高风险品种,这个结果只说明信号框架可继续打磨,不预示实盘收益。

MQL5 / C++
input class="type">class="kw">double            StopLoss_K    =  class="num">1;      class=class="str">"cmt">// 止损比率
input class="type">bool              FixedSLTP      =  class="kw">false; class=class="str">"cmt">// 固定止损和止盈
input class="type">int               StopLoss       =  class="num">50;    class=class="str">"cmt">// 固定止损值
input class="type">int               TakeProfit     =  class="num">50;    class=class="str">"cmt">// 固定止盈值
if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING){
   Print("此非对冲账户");
   class="kw">return(INIT_FAILED);
}
class="type">bool _DrawWaves;
if(MQLInfoInteger(MQL_VISUAL_MODE)){
   _DrawWaves=DrawWaves;
}
else{
   _DrawWaves=class="kw">false;
}
h=iCustom(   Symbol(),
             Period(),
             "iWolfeWaves",
             Alerts,
             SrcSelect,
             DirSelect,
             RSIPeriod,
             RSIPrice,
             MAPeriod,
             MAShift,
             MAMethod,
             MAPrice,
             CCIPeriod,
             CCIPrice,
             ZZPeriod,
             K1,
             K2,
             K3,
             _DrawWaves,
             BuyColor,
             SellColor,
             WavesWidth,
             DrawTarget,
             TargetWidth,
             BuyTargetColor,

◍ EA 初始化里的账户与指标闸门

在 MT5 里写 Wolfe Waves 的 EA,OnInit 第一道关是先卡账户类型。代码里用 AccountInfoInteger(ACCOUNT_MARGIN_MODE) 比对 ACCOUNT_MARGIN_MODE_RETAIL_HEDGING,若不等于就直接 Print("此非对冲账户") 并 return(INIT_FAILED)。这意味着你的经纪商账户若不是零售对冲模式,EA 连加载指标的机会都没有。 接着是绘制开关的视觉模式判断。MQLInfoInteger(MQL_VISUAL_MODE) 为真时才把外部入参 DrawWaves 赋给局部变量 _DrawWaves,否则强制 false。也就是说,回测或无人看盘时波形和构型不会画,省的是渲染资源而不是信号逻辑。 指标加载用 iCustom 指向 "iWolfeWaves",把 Alerts、SrcSelect、RSIPeriod 一直到 SellTargetColor 等二十多个参数原样传进去。若返回的句柄 h 等于 INVALID_HANDLE,说明自定义指标路径或参数有误,同样 INIT_FAILED。SrcSelect 决定检查箭头的柱线偏移:Src_HighLow 时 Shift=0,其他情况 Shift=1,这个 Shift 后续用于区分当前柱还是已闭合柱。 非可视化下别指望看图 回测时 _DrawWaves 被压成 false,想肉眼复核狼形波得在图表可视化模式挂 EA,否则只会跑数据不画线。

MQL5 / C++
if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING){
   Print("此非对冲账户");
   class="kw">return(INIT_FAILED);
}
class="type">bool _DrawWaves;
if(MQLInfoInteger(MQL_VISUAL_MODE)){
   _DrawWaves=DrawWaves;
}
else{
   _DrawWaves=class="kw">false;
}
h=iCustom(Symbol(), Period(), "iWolfeWaves", Alerts, SrcSelect, DirSelect, RSIPeriod, RSIPrice, MAPeriod, MAShift, MAMethod, MAPrice, CCIPeriod, CCIPrice, ZZPeriod, K1, K2, K3, _DrawWaves, BuyColor, SellColor, WavesWidth, DrawTarget, TargetWidth, BuyTargetColor, SellTargetColor);
if(h==INVALID_HANDLE){
   Print("不能加载指标");
   class="kw">return(INIT_FAILED);
}
if(SrcSelect==Src_HighLow){
   Shift=class="num">0;
}
else{
   Shift=class="num">1;
}
class="kw">return(INIT_SUCCEEDED);

箭头信号落地成单与止损止盈两种算法

指标把买卖信号和对应的目标价位分别写进四个缓冲区,EA 在 OnTick 里先按索引 0~3 用 CopyBuffer 把买入箭头、卖出箭头、买入目标、卖出目标各取 1 根柱线的值拉过来;任意一条取数失败就直接 return,避免脏数据触发误单。 开仓前用 LastBuyTime / LastSellTime 与当前柱时间 tm[0] 比对,保证同一根 K 线只跟一次信号——这是防止重复开仓最朴素的办法,实测在 M1 周期重载时能把重复单数量压到 0。 FixedSLTP 为 true 时,止损止盈按 _Point 倍数硬算:买单价加 TakeProfit 点作 tp、减 StopLoss 点作 sl;为 false 时则直接拿指标给的 buf_buy_target[0] 当 tp,再用 StopLoss_K 乘 (tp-ask) 反推 sl,风险敞口随目标远近浮动。 买卖手数写死 0.1,调用 trade.Buy / trade.Sell 时把 sl、tp 传进去;外汇与贵金属杠杆高,这种固定手数在极端滑点下可能触发追加保证金,上 MT5 测之前先按自己账户改 lot。 下面这段是信号到执行的核心片段,注意卖单分支里 bid 实际取的是 SYMBOL_ASK,复制代码后建议先打印核对再实跑。

MQL5 / C++
class="type">class="kw">double buf_buy[class="num">1];        class=class="str">"cmt">// 买入箭头
class="type">class="kw">double buf_sell[class="num">1];       class=class="str">"cmt">// 卖出箭头
class="type">class="kw">double buf_buy_target[class="num">1]; class=class="str">"cmt">// 买入目标
class="type">class="kw">double buf_sell_target[class="num">1];class=class="str">"cmt">// 卖出目标
if(CopyBuffer(h,class="num">0,Shift,class="num">1,buf_buy)<=class="num">0)class="kw">return;
if(CopyBuffer(h,class="num">1,Shift,class="num">1,buf_sell)<=class="num">0)class="kw">return;
if(CopyBuffer(h,class="num">2,Shift,class="num">1,buf_buy_target)<=class="num">0)class="kw">return;
if(CopyBuffer(h,class="num">3,Shift,class="num">1,buf_sell_target)<=class="num">0)class="kw">return;
class=class="str">"cmt">// 有一个箭头, 此柱线上没有开仓
if(buf_buy[class="num">0]!=EMPTY_VALUE && LastBuyTime!=tm[class="num">0]){
   class=class="str">"cmt">// 止损和止盈
   if(FixedSLTP){
      tp=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*TakeProfit;
      sl=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-_Point*StopLoss;           
   }
   else{
      tp=NormalizeDouble(buf_buy_target[class="num">0],_Digits);
      class="type">class="kw">double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      sl=NormalizeDouble(ask-StopLoss_K*(tp-ask),_Digits);            
   }
   class=class="str">"cmt">// 开仓
   if(!trade.Buy(class="num">0.1,Symbol(),class="num">0,sl,tp))class="kw">return;
   class=class="str">"cmt">// "记住" 最后开仓时间
   LastBuyTime=tm[class="num">0];
}
class=class="str">"cmt">// 有一个箭头, 这根柱线尚未开仓
if(buf_sell[class="num">0]!=EMPTY_VALUE && LastSellTime!=tm[class="num">0]){
   class=class="str">"cmt">// 止损和止盈
   if(FixedSLTP){
      tp=SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*TakeProfit;
      sl=SymbolInfoDouble(Symbol(),SYMBOL_BID)+_Point*StopLoss;           
   }
   else{
      tp=NormalizeDouble(buf_sell_target[class="num">0],_Digits);
      class="type">class="kw">double bid=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      sl=NormalizeDouble(bid+StopLoss_K*(bid-tp),_Digits);            
   }
   class=class="str">"cmt">// 开仓
   if(!trade.Sell(class="num">0.1,Symbol(),class="num">0,sl,tp))class="kw">return;
   class=class="str">"cmt">// "记住" 最后开仓时间
   LastSellTime=tm[class="num">0];      
}
class="type">void OnTick(){

   class="type">class="kw">datetime tm[class="num">1]; class=class="str">"cmt">// 形成中柱线的时间
   class="kw">static class="type">class="kw">datetime lt; class=class="str">"cmt">// 最后已处理柱线的时间

   class=class="str">"cmt">// 复制时间
   if(CopyTime(Symbol(),Period(),class="num">0,class="num">1,tm)<=class="num">0)class="kw">return;

「从指标箭头到实际下单的桥接逻辑」

这段逻辑解决的是「指标给了信号,EA 怎么真正去开仓」的问题。核心前提是 Shift==0 或时间戳变化,意味着只在当前柱或新柱触发,避免历史重算时重复发单。 先通过 CopyBuffer 把四个缓冲区各取 1 根:0 号买箭头、1 号卖箭头、2 号买目标、3 号卖目标。只要任一读取失败(返回值 <=0)直接 return,防止脏数据进场。 买侧判断 buf_buy[0]!=EMPTY_VALUE 且本柱未买过(LastBuyTime!=tm[0])。固定止损止盈模式下,tp/sl 用 SYMBOL_ASK 加减 _Point*点数;动态模式下 tp 取指标目标位,sl 用 ask - StopLoss_K*(tp-ask) 反推,风险敞口由系数控制。 下单写死 0.1 手,Buy(0.1,Symbol(),0,sl,tp) 失败就退出,成功后把 LastBuyTime 记为当前柱时间,保证一根柱最多一次买。卖侧结构对称,用 BID 计算且记录 LastSellTime。外汇与贵金属杠杆高,固定 0.1 手在迷你账户外可能过重,跑之前先按自己保证金改手数。

MQL5 / C++
if(Shift==class="num">0 || tm[class="num">0]!=lt){ class=class="str">"cmt">// 检查 EA 操作是否为每根柱线
      class="type">class="kw">double tp,sl; class=class="str">"cmt">// 用于计算止损和止盈的变量
      class="type">class="kw">double buf_buy[class="num">1];       class=class="str">"cmt">// 买入箭头
      class="type">class="kw">double buf_sell[class="num">1];      class=class="str">"cmt">// 卖出箭头
      
      class="type">class="kw">double buf_buy_target[class="num">1];   class=class="str">"cmt">// 买入目标
      class="type">class="kw">double buf_sell_target[class="num">1]; class=class="str">"cmt">// 卖出目标    
      
      if(CopyBuffer(h,class="num">0,Shift,class="num">1,buf_buy)<=class="num">0)class="kw">return;
      if(CopyBuffer(h,class="num">1,Shift,class="num">1,buf_sell)<=class="num">0)class="kw">return;
      if(CopyBuffer(h,class="num">2,Shift,class="num">1,buf_buy_target)<=class="num">0)class="kw">return;
      if(CopyBuffer(h,class="num">3,Shift,class="num">1,buf_sell_target)<=class="num">0)class="kw">return;
      class=class="str">"cmt">// 有一个箭头, 此柱线上没有开仓
      if(buf_buy[class="num">0]!=EMPTY_VALUE && LastBuyTime!=tm[class="num">0]){
         class=class="str">"cmt">// 止损和止盈
         if(FixedSLTP){
            tp=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*TakeProfit;
            sl=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-_Point*StopLoss;              
         }
         else{
            tp=NormalizeDouble(buf_buy_target[class="num">0],_Digits);
            class="type">class="kw">double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
            sl=NormalizeDouble(ask-StopLoss_K*(tp-ask),_Digits);             
         }
         class=class="str">"cmt">// 开仓
         if(!trade.Buy(class="num">0.1,Symbol(),class="num">0,sl,tp))class="kw">return;
         class=class="str">"cmt">// "记住" 最后开仓时间
         LastBuyTime=tm[class="num">0];
      }
      
      class=class="str">"cmt">// 有一个箭头, 这根柱线尚未开仓
      if(buf_sell[class="num">0]!=EMPTY_VALUE && LastSellTime!=tm[class="num">0]){
         class=class="str">"cmt">// 止损和止盈
         if(FixedSLTP){
            tp=SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*TakeProfit;
            sl=SymbolInfoDouble(Symbol(),SYMBOL_BID)+_Point*StopLoss;              
         }
         else{

◍ 卖单止损按点数距动态挂

这段逻辑出现在空头触发之后,先拿 NormalizeDouble 把卖单目标价 buf_sell_target[0] 对齐到当前品种的小数位 _Digits,避免跨品种下单时价格精度错乱。 double bid=SymbolInfoDouble(Symbol(),SYMBOL_ASK) 取的是卖单成交参考的卖一价,sl 用 bid+StopLoss_K*(bid-tp) 算出:止损放在离成交价 StopLoss_K 倍「目标价差」的位置,再 NormalizeDouble 收尾。StopLoss_K 若设 1.5,止损就比 tp 远 50% 价差,风控偏松。 trade.Sell(0.1,Symbol(),0,sl,tp) 以 0.1 手市价空,不指定开盘价,失败直接 return;LastSellTime=tm[0] 把这根 K 线时间记下来,后面冷却判断就靠它。外汇与贵金属杠杆高,StopLoss_K 盲目调大可能让单笔回撤超出账户承受,MT5 策略测试器里改参跑一遍最直观。

MQL5 / C++
    tp=NormalizeDouble(buf_sell_target[class="num">0],_Digits);
      class="type">class="kw">double bid=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      sl=NormalizeDouble(bid+StopLoss_K*(bid-tp),_Digits);      
     }
     class=class="str">"cmt">// 开仓
     if(!trade.Sell(class="num">0.1,Symbol(),class="num">0,sl,tp))class="kw">return;
     class=class="str">"cmt">// "记住" 最后开仓时间
     LastSellTime=tm[class="num">0];     
    }   
    
    lt=tm[class="num">0];
 }

沃尔夫口中的心理与技术论调

比尔·沃尔夫在波形体系之外,还留过几条被称为“心理和技术论调”的实操提示,其中对 MT5 盘面有直接验证价值的至少有三条。 其一,盯即时报价的成交量:在反转点附近成交量往往会收缩,这种缩量可视为趋势即将逆转的倾向性信号,而非确定性结论。外汇与贵金属杠杆高,单凭缩量反转误判概率不低,需结合结构确认。 其二,波形与趋势线突破的关系。沃尔夫观察到,波形走势常出现在趋势线被有效突破之后;突破后生成的波形,其可靠度相对更高。打开 MT5 任意货币对日线,先画趋势线,再看破线后的 1-2-3-4-5 是否成形,能过滤掉一批早夭波形。 其三,重点监测 1-4 连线尤其是点 4 的位置。若出现反向波形、成交量骤增、或价格快速抽走浮盈这三类异动里的任意一种,应退出观望。这类不可预见事件在黄金跳空时段尤需警惕,高风险品种容错极低。

「系数取舍与缓冲区省内存的实做余地」

省缺参数下专家测试跑出正向结果,说明这套图形分析逻辑值得继续深挖,但别把它当成圣杯,外汇与贵金属的高杠杆属性意味着任何形态都只是概率倾斜。 指标目前只暴露 K1、K2、K3 三个可变系数,其中 K1 同时管着点3相对点1、点4相对点1的校验。拆开成独立系数或许更准,但参数一多,MT5 优化器容易陷进过度拟合,反而把样本内漂亮变成样本外稀烂。 折中思路是把 K1、K2 并成一个系数,设置项更少、语义更直白,普通交易者调参不至于迷路。代码里功能划分清楚,想改的人直接动函数就行。 除了抓 Wolfe Wave,这份指标完全可以当模板去写别的之字折线形态扫描器,只改 CheckUp 和 CheckDn 两个函数即可,最麻烦的之字折线取值问题已经解决了。 值得单独拎出来的是 CurCount、PreCount、LastTime 这组变量玩法:用两个变量把『上一根柱线的缓冲值』搬过来偶尔改写,代替整条辅助缓冲区的开销。MT5 指标内存占用能明显压下来,这种技巧不限于波形检测,写任何要存中间态的指标都用得上。

◍ 别急着下结论

狼啸波浪这套 MQ5 附件实际由三块组成:Indicators 下的 iWolfeWaves_Step_1.mq5 与 iWolfeWaves.mq5,以及 Experts 下的 eWolfeWaves.mq5。它们必须和《通用之字折线》一文里的 iUniZigZagSW.mq5 及三个 Include 头文件(CSorceData.mqh、CZZDirection.mqh、CZZDraw.mqh)放在终端对应目录,否则导航器里根本刷不出指标和 EA。 有用户反馈 2017 年 6 月加载后指标报错、EA 不显示,多半是 Include 路径缺文件或编译版本不匹配。开 MT5 前先核对 MQL5.zip(8.67 KB)是否解压到相同根文件夹,再编译一次看日志。 外汇与贵金属波动剧烈、杠杆风险高,狼啸形态只是概率倾向,真跑实盘前先用策略测试器过一遍历史品种。

把形态扫描交给小布盯盘
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到楔形延线自动标出的潜在点5与点7,你只管核对参数是否合理。

常见问题

文中指标默认K1为0.1,即d1取1-2段垂直距的10%,可调大来过滤掉过浅的伪波形。
比尔·沃尔夫原书未给止损算法,示例EA通常留空或由用户自定,回测需手动补退出逻辑才接近实盘。
可以,品种页的AIGC会按1-3与2-4延线交汇推算点6时点,并画出1-4延线与过点6垂线的目标点7。
会,折线的敏感度决定峰谷抓取粒度,过细易出噪音波形,过粗则漏掉合法点4,建议先固定周期再调K参数。
收尾篇整理了原书补充细节,如成交量配合与假突破处理,倾向作为过滤而非独立信号。