沃尔夫波形 (Wolfe Waves)

沃尔夫波形 (Wolfe Wave) 是 Bill Wolfe 发现并描述的图形分析形态。此图案看起来像一个三角形或楔形 (沃尔夫称之为 '上升的楔子'), 并具有一些特殊的细微差别。比尔·沃尔夫 (Bill Wolfe) 提出的图形化方法可以检测到一种形态, 根据此形态可以找到入场的时刻和方向, 并且还有益于预测价格应达到的目标, 以及达到目标的时间。

在本文中, 我们将详细研究沃尔夫波形的检测和解释规则。我们将根据 通用之字折线 文章中的之字折线指标, 创建自动检测并显示波形的指标。我们还将根据结果指标创建一个简单的专家交易系统。此 EA 将允许我们测试指标绩效, 并得到比尔·沃尔夫所提出的图形分析的第一印象, 之后会在本文中讨论。 

检测沃尔夫波形的规则

我们来研究买入示例中的沃尔夫波形 (图例.1)。价格形成两个连续下降的低峰 (蓝线, 点 1 和 3), 以及两个连续下降的高峰 (点 2 和 4)。在点 4 逆转并形成高峰之后, 价格继续下滑。一旦价格触及点 1 — 点 3 的延长线, 就进行买入操作 (点 5)。 


图例. 1. 买入沃尔夫波形。蓝线是价格, 红线是形成的检测目标。在点 5 执行入场, 目标是点 7

1—3 和 2—4 延长线的交汇点 6 则为检测目标的到达时间。目标价位 (点 7) 的定义是 1—4 延长线与通过点 6 绘制的垂直线的交点。此方法不提供止损计算算法, 通常建议是您自行决定使用止损。以上是沃尔夫书中讲述的波形检测规则。 

当开发本文的指标时, 还会发现更多的规则。

  1.  点 3 必须远低于点 1, 以下条件应予以检查:

    v3<v1-d1

    此处:

    • v3 — 点 3 的价位;
    • v1 — 点 1 的价位;
    • d1 — 点 1 和 点 2 (线段 1''-2'') 之间的垂直距离乘以 K1 (K1 是属性窗口的参数, 其默省缺值为 0.1)。

  2. 检测目标的 1—4 延长线必须向上, 即点 4 必须远高于 点 1。以下条件应予以检查:

    v4>v1+d1;

    v4 — 点 4 的价位。 

  3. 点 4 必须远低于点 2, 以下条件应予以检查:

    v4<v2-d2;

    此处 v2 是点 2 的价位, d2 是点 2 和点 3 (线段 2''-3'') 之间的垂直距离乘以 K2 (K2 是属性窗口的参数, 其默省缺值为 0.1)。

  4. 检测目标到达时间的 2-4 与 1-3 延长线必须交汇点于右侧, 所以 2-2' 的高度必须远高于 4-4' 的高度。在此必须执行以下检查:

    h2-h4>K3*h2;

    此处 h2 是线段 2-2' 的高度, h4 是线段 4-4' 的高度, K3 是比率 (K3 是属性窗口的参数, 其默省缺值为 0.1)。

这些规则声明并未假定绝对正确。以后我们在指标的创建过程中还要详细描述。基于这些素材, 您可以根据自己的想法调整代码。 

选择使用的之字折线

开始之前, 我们下载 附件, 它包含许多 通用之字折线 一文中的多种版本之字折线指标。我们需要从中选择一个在我们的文章中使用。我们不使用 iUniZigZagPrice 和 iUniZigZagPriceSW, 它们设计时基于图表上运行的其它指标进行计算, 因此它们仅对视觉分析有用。其它指标似乎更有趣。它们当中的每一个都可用来创建专家交易系统。此外, 我们不会使用 iCloseZigZag 和 iHighLowZigZag, 它们只是如何创建之字折线的初始示例。剩下两个版本, 即 iUniZigZag 和 iUniZigZagSW。在子窗口中工作的 iUniZigZagSW 指标更适合我们, 因为它提供了更广泛的功能。附件中也包含 iUniZigZagSWEvents 指标, 并展示了使用 iCustom() 函数访问 iUniZigZagSW 指标的示例。我们将使用此变体, 因为它将允许我们使用 iUniZigZagSW 指标的所有可能性, 且可另行将沃尔夫波形的检测代码与之字折线代码分离。

iUniZigZagSWEvents 指标显示在价格图表上, 四个缓冲区用于绘制指标: 两个带箭头的缓冲区, 另外两个画点。这些就是我们检测定沃尔夫波形所需要的。箭头将指示形态识别位置, 点则用于目标。我们的指标将用到 图形对象, 特别是 趋势线 绘制波形和构型以检测目标。如果您将其绘制为线段而非延伸射线, 那么它会是显示不同构型的非常方便的工具。  

除了检测入场时刻和方向外, 沃尔夫波形也用于预测目标。所以, 当使用 iUniZigZagSW 时会出现困难。指标带有 SrcSelect 参数, 可以根据所绘制的之字折线选择分析数据的来源。可以选择以下四个选项之一:

  • Src_HighLow — 按照最高价和最低价;
  • Src_Close — 按照收盘价;
  • Src_RSI — 按照 RSI 指标;
  • Src_MA — 按照移动均线。

基于我们现正创建的指标创建专家交易系统。此即为什么如果我们利用价格来构建之字折线, 那么预测的目标就可以用来放置止盈位。在图表上显示目标没有任何问题。但是如果使用RSI (SrcSelect=Src_RSI) 计算之字折线, 则预测目标将是 RSI 指标, 而非价格。所以, 一旦 RSI 指标达到目标值, 我们就需要市价平仓, 而不可能在图表上显示目标价格和附加构型。

当使用基于价格 (Src_HighLow 或 Src_Close) 绘制的之字折线时, 目标价格和附加构型将显示在图表上。在所有其它情况下, 只会显示一个箭头, 表示已发现的结构及其方向。目标值仍然在适相应的价格缓冲区中提供 (为了能够让专家交易系统以市价平仓来应对任何其它目的), 但不会显示。

很有可能在实践中, 当指标达到目标价位时, 市价平仓的想法无法实现。大多数指标的值在一定范围内变化, 目标结果可能在此范围之外。但是, 在任何情况下, 缓冲区都将包含目标值。

收集关于之字折线峰值的数据

我们现在开始创建指标。我们在编辑器中打开 iUniZigZagSWEvents, 文件并将其保存为 iWolfeWaves。我们将操控这个指标。

直接访问所有的之字折线峰值非常方便 — 在此情况下, 我们不必每次都在历史中搜索它们。我们来创建一个数组保存数值。现在, 每当之字折线改变方向时, 一个新的元素将被添加到数组中。如果指标简单地延伸最后一个线段 (更新极值), 则数组的最后一个元素将被更新。

对于每个峰值, 我们将保存峰值、方向和所在柱线的索引 (索引从左到右)。为此目的, 我们将使用一个含有三个字段的 结构:

struct SPeackTrough{
   double   Val; // 峰值
   int      Dir; // 方向
   int      Bar; // 柱线索引
};

我们来创建一个这些结构的数组:

SPeackTrough PeackTrough[];

如果之字折线仅仅基于最高价和最低价 (SrcSelect = Src_HighLow), 当方向改变的情况下将数组递增就足够了, 设置数值并用指标最后延伸的一段更新最后的元素。基于收盘价 (SrcSelect = Src_Close) 或任何其它指标数据的之字折线更难办。在柱线形成期间, 方向改变, 之字折线可以返回原来的状态 (即当前柱线开盘之前)。这意味着对于相同柱线的每次重新计算, 峰值数组需要返回到前一根柱线的初始状态。如果我们经常更改数组大小, 这可能会减慢指标的运行。因此, 我们来引入一个附加的变量, 所用数组大小将被保存其内。必要时, 数组将以块为单位进行修改, 只允许尺寸增长。重新计算同一根柱线之前, 我们要返回此变量的初始值。

我们将使用两个变量来存储数组大小。在一个变量中, 存储前一根柱线时的数组大小。当前计算的柱线时的大小将存储在第二个变量当中:

int PreCount; // 前一根柱线时 PeackTrough 数组的大小
int CurCount; // 当前计算柱线时 PeackTrough 数组的大小

在柱线形成并计算完成后, 或计算历史柱线之后, CurCount 变量的值应转移到 PreCount 变量。然后, 在每次计算新形成的柱线之前, 我们要把数值从 PreCount 移动到 CurCount。只有 CurCount 变量将会用于所有的计算。PreCount 变量只是辅助。关于柱线结构完毕的信息只能在下一根柱线开盘 (或计算切换到历史中的下一根柱线) 时才知道。新柱线的出现将由时间决定: 如果柱线时间已经改变, 则会出现一根新柱线 (或计算历史中的下一根柱线已经开始)。需要一个辅助变量以便确定新的柱线:

datetime LastTime;

PreCount, LastCount 和 LastTime 是指标的全局变量。但是它们也可以在 OnCalculate() 指标函数里声明为静态变量。 

我们转进到 OnCalculate() 函数。基于 prev_calculated 的值, 判断是第一次执行指标计算还是仅计算新的柱线。0 意即全部计算。在此情况下, 变量 PreCount, CurCount 和 LastTime 需要初始化。以下代码位于 OnCalculte() 函数的最上面, 定义计算的柱线范围, 并初始化辅助变量:

int start; // 起始计算的柱线的索引变量

if(prev_calculated==0){ // 计算全部的柱线
   start=1;
   CurCount=0;   
   PreCount=0;
   LastTime=0;
}
else{ // 计算新柱线
   start=prev_calculated-1;
}

现在我们来处理标准的指标循环。在一开始, 我们要将变量 PreCount, CurCount 中的数值组织转移:

for(int i=start;i<rates_total;i++){

   if(time[i]>LastTime){ // 计算新 (下一次) 柱线
      LastTime=time[i];
      PreCount=CurCount;
      PreDir=CurDir;
   }
   else{ // 柱线重新计算
      CurCount=PreCount;
      CurDir=PreDir;
   }


在所有的计算当中, 仅适用 CurCount 变量, 而 PreCount is 仅设计用于维护当前的 CurCount 值。在新柱线开盘伊始, CurCount 首先包含前一根柱线计算后获得的数值。这就是为什么我们要把这个数值移动到 PreCount。在新柱线计算之后, CurCount 的数值可以改变。但是, 我们只能在下一根柱线的开盘时才能确定该值是最终的。这就是为什么在重新计算同一柱线的情况下, PreCount 变量的数值被放置到 CurCount。

主要指标循环应包含取自 iUniZigZagSWEvents 指标的以下代码:

UpArrowBuffer[i]=EMPTY_VALUE;
DnArrowBuffer[i]=EMPTY_VALUE;

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

// 方向

double dir[2];
if(CopyBuffer(handle,3,rates_total-i-1,2,dir)<=0){
   return(0);
}
if(dir[0]==1 && dir[1]==-1){
   DnArrowBuffer[i]=high[i];
   c++;

}
else if(dir[0]==-1 && dir[1]==1){
   UpArrowBuffer[i]=low[i];
   c++;
}

// 新的最高价

double lhb[2];
if(CopyBuffer(handle,4,rates_total-i-1,2,lhb)<=0){
   return(0);
}
if(lhb[0]!=lhb[1]){
   UpDotBuffer[i]=high[i];
}

// 新的最低价

double llb[2];
if(CopyBuffer(handle,5,rates_total-i-1,2,llb)<=0){
   return(0);
}
if(llb[0]!=llb[1]){
   DnDotBuffer[i]=low[i];
}  

绘制箭头的代码部分将不会被使用, 所以我们来删除它。

在操作期间, 由于需要最后一个线段来检测点 5 (见图例.1), 指标将监控之字折线的每次变化, 包括方向改变和最后线段的每次延伸。我们将使用上述片段中的部分代码, 这与绘制新极值有关。

若要监控之字折线的方向并确定其变化, 我们将需要几个类似于 CurCount 和 PreCount 的变量: PreDir 和 CurDir:

int PreDir; // 前一根柱线的之字折线方向
int CurDir; // 当前柱线的之字折线方向

在 OnCalculate() 中它们即可以是全局的, 也可是静态的。在指标计算伊始, 我们还需要初始化这些变量, 并在柱线计算之后移动数值, 类似于 PreCount 和 CurCount。以下是 OnCalculate() 的最终代码, 如同指标创建的当前步骤:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {

   int start; // 用于计算起始柱线的变量
   
   if(prev_calculated==0){ // 全部计算 
      start=1; 
      CurCount=0;
      PreCount=0;
      CurDir=0;
      PreDir=0;      
      LastTime=0;
   }
   else{ // 仅计算新的柱线
      start=prev_calculated-1;
   }

   // 指标主循环
   for(int i=start;i<rates_total;i++){
   
      if(time[i]>LastTime){ // 新柱线
         LastTime=time[i];
         PreCount=CurCount;
         PreDir=CurDir;
      }
      else{ // 柱线重新计算
         CurCount=PreCount;
         CurDir=PreDir;
      }

      // 清除绘制箭头和点的缓冲区
      
      UpArrowBuffer[i]=EMPTY_VALUE;
      DnArrowBuffer[i]=EMPTY_VALUE;
      
      UpDotBuffer[i]=EMPTY_VALUE;
      DnDotBuffer[i]=EMPTY_VALUE;    
      
      // 辅助变量
      
      double hval[1];
      double lval[1];
      
      double zz[1];
      
      // 新的最高价
      
      double lhb[2];
      // 接收缓冲区的两个元素, 内带新的最高价的柱线索引
      if(CopyBuffer(handle,4,rates_total-i-1,2,lhb)<=0){ 
         return(0);
      }
      if(lhb[0]!=lhb[1]){ // 这是新的最高价
         // 获取最高价格 (或基于之字折线计算的数据)
         if(CopyBuffer(handle,0,rates_total-i-1,1,hval)<=0){
            return(0);
         }      
         if(CurDir==1){ // 已知的最后向上方向
            // 更新有关最后一个之字折线拐点的信息
            RefreshLast(i,hval[0]);
         }
         else{ // 之字折线方向已改变
               // 加入新的数值
            AddNew(i,hval[0],1);
         }
         // 此处, 我们将添加检查条件以便识别向下的沃尔夫波形  
      }
      
      // 新的最低价
      
      double llb[2];
      // 接收缓冲区的两个元素, 内带新的最低价的柱线索引
      if(CopyBuffer(handle,5,rates_total-i-1,2,llb)<=0){
         return(0);
      }
      if(llb[0]!=llb[1]){ // 这是新的最低价
         // 获取最低价格 (或基于之字折线计算的数据)
         if(CopyBuffer(handle,1,rates_total-i-1,1,lval)<=0){
            return(0);
         }         
         if(CurDir==-1){ // 已知的最后向下方向
            // 更新有关最后一个之字折线拐点的信息
            RefreshLast(i,lval[0]);
         }
         else{ // 之字折线方向已改变
            // 加入新的数值
            AddNew(i,lval[0],-1);
         }
         // 此处, 我们将添加检查条件以便识别向上的沃尔夫波形 
      }      
   }   
   return(rates_total);
}

此代码包含 AddNew() 和 RefreshLast() 函数。之字折线变化的柱线索引和新的极值被传递给这两个函数。之字折线的方向也一并传递给 AddNew()。

添加新点的 AddNew() 函数:

void AddNew(int i,double v,int d){
   if(CurCount>=ArraySize(PeackTrough)){ // 数组中没有可用元素
      ArrayResize(PeackTrough,ArraySize(PeackTrough)+1024); // 增加数组大小
   }
   PeackTrough[CurCount].Dir=d; // 设置方向
   PeackTrough[CurCount].Val=v; // 设置数值
   PeackTrough[CurCount].Bar=i; // 设置柱线
   CurCount++; // 使用数组占用元素的数量增加变量
   CurDir=d; // 记忆最后的之字折线方向
} 

RefreshLast() 函数用来刷新最后一个点:

void RefreshLast(int i,double v){
   PeackTrough[CurCount-1].Bar=i; // 设置新柱线
   PeackTrough[CurCount-1].Val=v; // 设置新数值
} 

指标可以在现阶段得以保存, 它可作为开发各种定义之字折线形态的指标的基础。在以下附件中, 指标名称为 "iWolfeWaves_Step_1"。

一点几何

当识别沃尔夫波形并添加检测目标的形状时, 我们需要一点几何知识。我们分开看这些问题, 并编写函数来解决它​​们。

问题 #1. 一条直线由一对点 x-y 设置, 其中 x 是柱线索引, y 是数值 (价格或指标值)。我们知道第三点的 x 坐标, 我们需要找到这一点在线上的数值 (图例. 2)。


图例. 2. 给定: X1, Y1, X2, Y2, X3. 我们需要找出 Y3。

问题 #1 解决方案。直线沿 X 轴的数值每增加一个单位, 我们检测一次直线沿 Y 轴的增加值:

D=(Y2-Y1)/(X2-X1)

此处 D 是增量, Y1 是点 1 处的价格或指标值, Y2 是点 2 处的价格或指标值, X1 是点 1 处的柱线索引, X2 是点 2 处的柱线索引。  

检测 Y3:

Y3=Y1+(X3-X1)*D

此处 X3 是点 3 处的柱线索引, Y3 是点 3 处搜寻线的值。

我们得到以下函数:

double y3(double x1,double y1,double x2,double y2,double x3){
   return(y1+(x3-x1)*(y2-y1)/(x2-x1));
}

以下参数需要传递给函数:

  • x1 — 点 1 处的柱线索引;
  • y1 — 点 1 处的数值;
  • x2 — 点 2 处的柱线索引;
  • y2 — 点 2 处的数值。

问题 #2. 两条线由两个 x-y 点设定。我们需要找到它们交汇点的 x 坐标 (图例. 3)。在此可能会出现以下问题: 为什么我们选择了 x 坐标? 在任何情况下, 获得 x 坐标之后, 将计算点 3 的 y 坐标 (使用其中一条线的方程)。因此, 我们可以先获得点 3 的 y 坐标, 然后使用方程找到 x 值。  


图例. 3. 两条给定直线。我们需要找到它们的交叉点

首先, 使用两点的坐标, 得到 y=a+b*x 形式的线方程。

我们来进行预计算。直线斜率值 (x 轴的每一单位与 y 轴每一单位的比值):

D1=(Y12-Y11)/(X12-X11)

此处, D1 是第一条线的期望斜率值 (每根柱线的直线值变化), X11 是第一条线点1 处的柱线索引, X12 是第一条线点 2 处的柱线索引, Y11 是第一条线点 1 处的值, Y12 是第一条线点 2 处值。     

第二条线的斜率:

D2=(Y22-Y21)/(X22-X21)

此处, D2 是第二条线的期望斜率值 (每根柱线的直线值变化), X21 是第二条线点1 处的柱线索引, X22 是第二条线点 2 处的柱线索引, Y21 是第二条线点 1 处的值, Y22 是第二条线点 2 处值。

此处是直线方程。线 1 方程:

Y3=Y11+D1*(X3-X11)

此处 Y3 是交汇点 (点 3) 处的直线值, X3 是点 3 处的柱线索引。

线 2 方程:

Y3=Y21+D2*(X3-X21)

在交点处, 线的值相等。所以我们将线 1 方程与线 2 方程划等号:

Y11+D1*(X3-X11)=Y21+D2*(X3-X21);

使用得到的表达式, 我们发现 X3。作为结果, 我们得到用于检测交点 X 坐标的 TwoLinesCrossX() 函数:

double TwoLinesCrossX(double x11,double y11,double x12,double y12,double x21,double y21,double x22,double y22){
   double k2=(y22-y21)/(x22-x21);
   double k1=(y12-y11)/(x12-x11);
   return((y11-y21-k1*x11+k2*x21)/(k2-k1));
}

以下参数需要传递给函数:

  • x11, 第一条线点 1 处的柱线索引
  • y11, 第一条线点 1 处的值
  • x12, 第一条线点 2 处的柱线索引
  • y12, 第一条线点 2 处的值
  • x21, 第二条线点 1 处的柱线索引
  • y21, 第二条线点 1 处的值
  • x22, 第二条线点 2 处的柱线索引
  • y22, 第二条线点 2 处的值

一旦定义了线-线交点处的 x 坐标, 可使用一条线的两点的坐标, 以及解决问题 #1 时获得的函数 y3() 来求解 y 坐标。

如果我们需要首先获得 y 坐标, 则应该转换直线方程, 以便通过 y 来表示 x 坐标。 以下是一条线的方程:

X3=X11+(Y3-Y11)/D1

第二条线的方程:

X3=X21+(Y3-Y21)/D2

将两个表达式划等号:

X11+(Y3-Y11)/D1=X21+(Y3-Y21)/D2

我们基于以上方程找到 Y3。因此, 我们获得了 TwoLinesCrossY() 函数:

double TwoLinesCrossY(double x11,double y11,double x12,double y12,double x21,double y21,double x22,double y22){
   double k2=(x22-x21)/(y22-y21);
   double k1=(x12-x11)/(y12-y11);
   return((x11-x21-k1*y11+k2*y21)/(k2-k1));
}

函数参数与 TwoLinesCrossX() 的相同。 

检测波形

现在我们可以轻松访问所有之字折线峰值和辅助几何函数, 我们可以继续检测沃尔夫波形。我们需要 "捕捉" 最后一个之字折线的线段穿过直线 1-3 (见图例.1) 的时刻, 即点 5。因此, 当每次出现新的之字折线极值时我们要检查沃尔夫波形条件 (方向改变, 以及最后的线段延伸时)。在上面的 OnCalculate() 函数代码中, 所有应检查条件的地方均有详细的注释。从它们那里会调用 CheckDn() 和 CheckUp() 函数。我们来详细研究 CheckUp() 函数:

void CheckUp(int rates_total,const double & low[],const datetime & time[],int i){

   if(CurCount<5 || CurDir!=-1){ 
      // 如果没有足够的峰值, 或之字折线没有指向下方, 无需检查
      return;
   }   
   
   // 用峰值数据准备 short 变量 

   // 峰值数据变量
   double v1=PeackTrough[CurCount-5].Val;
   double v2=PeackTrough[CurCount-4].Val;
   double v3=PeackTrough[CurCount-3].Val;
   double v4=PeackTrough[CurCount-2].Val;
   double v5=PeackTrough[CurCount-1].Val;
   
   // 峰值柱线变量
   int i1=PeackTrough[CurCount-5].Bar;
   int i2=PeackTrough[CurCount-4].Bar;               
   int i3=PeackTrough[CurCount-3].Bar;
   int i4=PeackTrough[CurCount-2].Bar;
   int i5=PeackTrough[CurCount-1].Bar;
                  
   if(CurLastBuySig!=i4){ // 如果在此之字折线结构中未检测到波形
      double d1=K1*(v2-v1); // 相对于峰值 1 的缩进峰值 3 的最小值
      if(v3<v1-d1){ // 峰值 3 明显低于峰值 1
         if(v4>v1+d1){ // 线 1-4 指而向上
            double d2=K2*(v2-v3); // 相对于峰值 2 的缩进峰值 4 的最小值
            if(v4<v2-d2){ // 峰值 4 明显低于峰值 2
               double v5l=y3(i1,v1,i3,v3,i); // 点 5 处的值
               if(v5<v5l){ // 之字折线的最后线段与线 1-3 交叉
                  double v4x=y3(i1,v1,i3,v3,i4); // 点 4' 处的值
                  double v2x=y3(i1,v1,i3,v3,i2); // 点 2' 处的值
                  double h4=v4-v4x; // 线 4-4' 高
                  double h2=v2-v2x; // 线 2-2' 高
                  if(h2-h4>K3*h2){ // 线 1-3 和 2-4 相遇 
                     double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4); // 线 1-3 与 2-4 交汇处的柱线
                     double tv=y3(i1,v1,i4,v4,tb); // 线 1-3 与 2-4 交汇点处的值
                     UpArrowBuffer[i]=low[i]; // 显示向上箭头
                     UpDotBuffer[i]=tv; // 显示目标价位的点
                     CurLastBuySig=i4; // 记住, 在此之字折线配置中已发现形状
                     if(_DrawWaves){ // 绘制结构
                        DrawObjects(BuyColor,BuyTargetColor,v1,v2,v3,v4,v5l,i1,i2,i3,i4,i5,time,i,tb,tv,rates_total);
                     }
                  }
               }
            }
         }
      }
   }
}

为了检测一个波形, 我们至少需要 5 个之字折线峰值。附加条件: 为了检测稍后转而向上的波形, 折线应指向下方:

if(CurCount<5 || CurDir!=-1){ 
   // 如果没有足够的峰值, 或之字折线没有指向下方, 无需检查
   return;
}   

若要获得峰值数据, 我们可以直接对 PeakTrough 数组进行寻址, 但这样很不方便。使用简短名称的辅助变量更为简单:

// 峰值数据变量
double v1=PeackTrough[CurCount-5].Val;
double v2=PeackTrough[CurCount-4].Val;
double v3=PeackTrough[CurCount-3].Val;
double v4=PeackTrough[CurCount-2].Val;
double v5=PeackTrough[CurCount-1].Val;
   
// 峰值柱线变量
int i1=PeackTrough[CurCount-5].Bar;
int i2=PeackTrough[CurCount-4].Bar;               
int i3=PeackTrough[CurCount-3].Bar;
int i4=PeackTrough[CurCount-2].Bar;
int i5=PeackTrough[CurCount-1].Bar;

如果已经检测到一个波形, 且已设置了一个箭头, 则不再需要使用相同的之字折线配置。通过检查峰值 4 的索引 (最后形成的峰值) 来识别之字折线配置:

if(CurLastBuySig!=i4){ // 如果在此之字折线结构中未检测到波形

若要保存配置 ID 的值, 我们使用一对类似于 CurCount 和 PreCount 的变量。

现在我们直接进行波形检测。我们计算点 3 相对于点 1 的最小偏移值, 点 2 相对于点 1 的位移:

double d1=K1*(v2-v1); // 相对于峰值 1 的缩进峰值 3 的最小值


然后检查点的位移:

if(v3<v1-d1){ // 峰值 3 明显低于峰值 1
   if(v4>v1+d1){ // 线 1-4 指而向上

我们计算相对于点 2 的点 4 缩进的最小值:

double d2=K2*(v2-v3); // 相对于峰值 2 的缩进峰值 4 的最小值

检查点 2 和点 4 的位置:

if(v4<v2-d2){ // 峰值 4 明显低于峰值 2

现在我们来计算位于线 1-3 上的点对应于所计算柱线的值:

double v5l=y3(i1,v1,i3,v3,i); // 点 5 处的值

检查是否触及线 1-3:

if(v5<v5l){ // 之字折线的最后线段与线 1-3 交叉

计算点 4' 和点 2' 的值:

double v4x=y3(i1,v1,i3,v3,i4); // 点 4' 处的值
double v2x=y3(i1,v1,i3,v3,i2); // 点 2' 处的值

计算 4-4' 和 2-2' 的高度:

double h4=v4-v4x; // 线 4-4' 高
double h2=v2-v2x; // 线 2-2' 高

利用这些高度, 检查线 1-3 和 2-4 是否在右侧相遇:

if(h2-h4>K3*h2){ // 线 1-3 和 2-4 相遇 

如果此条件满足, 意味着波形已发现。 

定义目标。首先定义目标柱线:

double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4); // 线 1-3 与 2-4 交汇处的柱线


请注意, "double" 变量用于计算精度。     

目标值:

double tv=y3(i1,v1,i4,v4,tb); // 线 1-3 与 2-4 交汇点处的值

 记住图标并 "记住" 之字折线配置的 ID:

UpDotBuffer[i]=tv; // 显示目标价位的点
CurLastBuySig=i4; // 记住, 在此之字折线配置中已发现形状

最后, 我们绘制检测目标的波形和结构:

if(_DrawWaves){ // 绘制结构
   DrawObjects(BuyColor,BuyTargetColor,v1,v2,v3,v4,v5l,i1,i2,i3,i4,i5,time,i,tb,tv,rates_total);
}

绘制波形和构型, 即 DrawObjects() 函数, 将在本文的另一部分中予以研究。 

向下的波形 (对于卖出) 可由 CheckDn 函数检测, 除了连接方向略有差异外, 与 CheckUp 相同。函数代码以及与 CheckUp() 函数的差异如下:

void CheckDn(int rates_total,const double & high[],const datetime & time[],int i){

   // 没有足够的峰值, 或并非指向上方 
   if(CurCount<5 || CurDir!=1){ 
      return;
   }

   double v1=PeackTrough[CurCount-5].Val;
   double v2=PeackTrough[CurCount-4].Val;
   double v3=PeackTrough[CurCount-3].Val;
   double v4=PeackTrough[CurCount-2].Val;
   double v5=PeackTrough[CurCount-1].Val;
   
   int i1=PeackTrough[CurCount-5].Bar;
   int i2=PeackTrough[CurCount-4].Bar;               
   int i3=PeackTrough[CurCount-3].Bar;
   int i4=PeackTrough[CurCount-2].Bar;
   int i5=PeackTrough[CurCount-1].Bar;
   
   if(CurLastSellSig!=i4){               
      double d1=K1*(v1-v2); // 峰值 v1 高于峰值 v2
      if(v3>v1+d1){ // 峰值 v3 高于峰值 v1
         if(v4<v1-d1){ // 峰值 v4 低于峰值 v1                     
            double d2=K2*(v3-v2); // 峰值 v3 高于峰值 v2                     
            if(v4>v2+d2){ // 峰值 v4 高于峰值 v2  
               double v5l=y3(i1,v1,i3,v3,i);
               if(v5>v5l){ // 之字折线突破线 1-3 向上
                  double v4x=y3(i1,v1,i3,v3,i4);
                  double v2x=y3(i1,v1,i3,v3,i2);
                  double h4=v4x-v4; // 点 4' 高于点 4
                  double h2=v2x-v2; // 点 2' 高于点 2
                  if(h2-h4>K3*h2){   
                     double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4);
                     double tv=y3(i1,v1,i4,v4,tb);                              
                     DnArrowBuffer[i]=high[i];
                     DnDotBuffer[i]=tv;
                     CurLastSellSig=i4;   
                     if(_DrawWaves){
                        // 用其它颜色绘制
                        DrawObjects(SellColor,
                                    SellTargetColor,
                                    v1,
                                    v2,
                                    v3,
                                    v4,
                                    v5l,
                                    i1,
                                    i2,
                                    i3,
                                    i4,
                                    i5,
                                    time,
                                    i,
                                    tb,
                                    tv,
                                    rates_total);
                     }
                  }
               }
            }
         }
      }
   }
}

第一个区别是初步检查:

// 没有足够的峰值, 或并非指向上方 
if(CurCount<5 || CurDir!=1){ 
   return;
}

如果没有足够的峰值, 或之字折线指向下方, 则函数操作应该完毕。

对于指向下方, 峰值和波谷改变位置: 点 1, 3 , 5 , 6 位于上方, 点 2, 4, 7 位于下方, 因此公式中某些变量的位置也会发生变化。检测峰值 1, 3 和 1, 4 之间的最小距离:

double d1=K1*(v1-v2); // 峰值 v1 高于峰值 v2

检查峰值 1, 3 的位置:

if(v3>v1+d1){ // 峰值 v3 高于峰值 v1

检查峰值 1, 4 的位置:

if(v4<v1-d1){ // 峰值 v4 低于峰值 v1 

计算峰值 2, 3 之间的最小距离, 并检查它:

double d2=K2*(v3-v2); // 峰值 v3 高于峰值 v2                     
if(v4>v2+d2){ // 峰值 v4 高于峰值 v2  

检查点 5 是否形成 (之字折线突破线 1-3 向上):

if(v5>v5l){ // 之字折线突破线 1-3 向上

计算 2-2' 和 4-4' 高度, 并检查线 1-3 和 2-4 是否在右侧相遇:

double h4=v4x-v4; // 点 4' 高于点 4
double h2=v2x-v2; // 点 2' 高于点 2

波形和构型使用不同的颜色绘制:

// 用其它颜色绘制
DrawObjects(SellColor,
            SellTargetColor,
            v1,
            v2,
            v3,
            v4,
            v5l,
            i1,
            i2,
            i3,
            i4,
            i5,
            time,
            i,
            tb,
            tv,
            rates_total);

绘制波形和目标

所有波形和构型使用单一算法绘制, 所以使用这样的一个 DrawObjects() 函数。元素指向上方和下方会以不同的颜色绘制。为此, 颜色参数 BuyColor 或 SellColor 被传递给函数。波形和定义目标的构型也以不同的颜色绘制, 所以参数 BuyTargetColor 或 SellTargetColor 也传递给函数。这些变量是指标的外部变量, 您可以设置期望的颜色。除了颜色, 还需要更多一些外部参数。以下是所有波形与对象绘制函数所需的附加参数:

input bool   DrawWaves       =  true;             // 启用波形和对象绘制
input color  BuyColor        =  clrAqua;          // 买入波形颜色
input color  SellColor       =  clrRed;           // 卖出波形颜色
input int    WavesWidth      =  2;                // 波形宽度
input bool   DrawTarget      =  true;             // 附加的启用/禁用构型
input int    TargetWidth     =  1;                // 对象宽度
input color  BuyTargetColor  =  clrRoyalBlue;     // 买入对象颜色
input color  SellTargetColor =  clrPaleVioletRed; // 卖出对象颜色

颜色传递之后, 所有峰值柱线的值和索引变量传递给函数。峰值 5 是例外, 为此, 传递已计算的线 1-3 的值, 替代之字折线尾端的值。所有之字折线极点的坐标以柱线索引给出, 而图形对象需要时间, 所以将指向 "time" 数组的指针传递给函数。已计算柱线的索引 — i, 目标柱线索引 — tb, 目标值 — tv, 以及图表之上的柱线总数 — rates_total 一并传递给函数。 

我们已经注意到, 在本文的开头, 只有之字折线采用最高价/最低价 (SrcSelect 设置为 Src_HighLow) 或收盘价 (SrcSelect 设置为 Src_Close) 计算时, 才应绘制波形和对象。所以, 这取决于 SrcSelect 变量, 在 OnInit() 函数里应强制禁用绘图 (DrawWaves 变量)。为此目的, 我们声明使用一个附加变量, 替代 DrawWaves:

bool _DrawWaves;

接下来, 在 OnInit() 函数中, 我们设置 DrawWaves 变量的值, 或将其设置为 false 来禁用它。此外, 为目标绘制缓冲区设置一个不可见的颜色:

if(SrcSelect==Src_HighLow || SrcSelect==Src_Close){
   _DrawWaves=DrawWaves;
}
else{
   _DrawWaves=false;
   PlotIndexSetInteger(2,PLOT_LINE_COLOR,clrNONE);
   PlotIndexSetInteger(3,PLOT_LINE_COLOR,clrNONE);      
}   

我们继续进入 DrawObjects() 函数。首先, 我们提供了完整的函数代码, 然后我们会详细研究它:

void DrawObjects( color col,
                  color tcol,
                  double v1,
                  double v2,
                  double v3,
                  double v4,
                  double v5,
                  int i1,
                  int i2,
                  int i3,
                  int i4,
                  int i5,
                  const datetime & time[],
                  int i,
                  double target_bar,
                  double target_value,
                  int rates_total){

   // 用于图形对象名称的前缀 
   string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time[i])+"_";

   // 绘制波形                   
   fObjTrend(prefix+"12",time[i1],v1,time[i2],v2,col,WavesWidth);
   fObjTrend(prefix+"23",time[i2],v2,time[i3],v3,col,WavesWidth);   
   fObjTrend(prefix+"34",time[i3],v3,time[i4],v4,col,WavesWidth);
   fObjTrend(prefix+"45",time[i4],v4,time[i5],v5,col,WavesWidth);

   // 绘制构型
   if(DrawTarget){   
    
      datetime TargetTime;
      
      // 获取整数型的目标柱线索引 
      int tbc=(int)MathCeil(target_bar);
      
      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);
      
      // 目标价位水平线
      fObjTrend(prefix+"67",TargetTime,tv24,TargetTime,tv14,tcol,TargetWidth);   
      // 目标价位垂直线 
      fObjTrend(prefix+"7h",time[i],target_value,TargetTime,target_value,tcol,TargetWidth);      
   }
}

所有绘图使用若干趋势线进行, 为此首先形成名称的公用前缀:

// 用于图形对象名称的前缀 
string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time[i])+"_";

然后绘制波形, 所有峰值的坐标传递给函数:

// 绘制波形                   
fObjTrend(prefix+"12",time[i1],v1,time[i2],v2,col,WavesWidth);
fObjTrend(prefix+"23",time[i2],v2,time[i3],v3,col,WavesWidth);   
fObjTrend(prefix+"34",time[i3],v3,time[i4],v4,col,WavesWidth);
fObjTrend(prefix+"45",time[i4],v4,time[i5],v5,col,WavesWidth);

绘制定义目标的结构。检查是否启用了绘图:

// 绘制构型
if(DrawTarget){  

如果已启用, 则绘制结构。当在历史数据上显示指标时, 目标柱线最有可能出现在已存在的柱线上, 但如果最近出现的柱线上检测到波形, 目标可能出现在将来, 即最后一根柱线的右侧。因此, 我们需要两个计算目标柱线时间的变体。为此目的我们声明一个变量:

datetime TargetTime;

target_bar 变量有一个分数值, 因此我们将其提升到最接近的整数:

// 获取整数型的目标柱线索引 
int tbc=(int)MathCeil(target_bar);

之后我们使用得到的 tbc 变量。在此, 我们可以使用 MathFloor() 函数, 并获得最近的较低的整数。这不会影响最终的结果, 因为构型只有一个提示性的目的。当使用 MathCeil(), 线 1-3 和 2-4 的端点必然在目标柱线附近相交, 且构型看起来更自然。

我们来检测目标抵达的时间。如果目标位于现存的柱线之一, 我们只需要计算目标柱线的索引, 并从 'time' 数组中获取时间。如果目标是在最后一根柱线的右侧, 那么我们要检测目标距离最后一根柱线多少根柱线, 并计算时间:

if(tbc<rates_total){ // 目标位于图表上存在的柱线之内
   TargetTime=time[tbc];
}
else{ // 目标位于未来
   TargetTime=time[rates_total-1]+(tbc-rates_total+1)*PeriodSeconds();
}

我们来计算所有直线 (1-3, 2-4 和 1-4) 在目标柱线处的值:

// 计算目标所在柱线处的值
double tv13=y3(i1,v1,i3,v3,tbc);   
double tv24=y3(i2,v2,i4,v4,tbc);  
double tv14=y3(i1,v1,i4,v4,tbc); 

尽管事实上早前计算的目标值已被传递到函数 (target_value 变量), 它仍然会重新为线 2-4 计算新的构型。这与事实相连, 替代来自 target_bar 变量的确切值, 我们使用来自 tbc 变量的值, 该值比 target_bar 稍大。经过这些计算, 我们确保在确切的 target_bar 坐标上, 直线将在 target_value 价位准确相交。

我们使用计算出值绘制直线:

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);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值