如何编写快速非重绘锯齿形调整浪

Candid | 17 三月, 2016

简介

在所有可能的锯齿形调整浪绘图算法中,我们可以分离出其中一类,作者将其命名为“突破减速水平时转向的锯齿形调整浪”。该类,无论是整体还是部分,包含了大多数已存在的锯齿形调整浪。类名称本身实际上是算法模板。要从中得到指标,只要添加能够检测减速水平的函数就足够了。该函数的算法多样性仅受限于作者对未来锯齿形调整浪的想象力。

一般方法

首先,我们来尝试制定编写指标的一般方法。于是:

- 任何指标(以及任何 EA)的 start() 函数是一种回调函数,即调用来处理一个特定事件的函数。也就是说,处理一个价格变动。

- 编写指标的目的,一般来说,是计算一个或多个市场特征。跟计算所需要的辅助量一起,它们形成了给定指标的关键变量集。我们定义指标的状态为那些关键变量在特定时间的数值集。基于该定义,我们可以表述如下:

- 如此说来,编写指标的过程就成了确定描述其状态(状态变量)的量集,以及编写在新的价格变动出现时将指标转换为新状态的运算符。状态变量的初始化变成指标算法的基本部分。我们将以某种类型的锯齿形调整浪演示如何实现。

所讨论的是什么锯齿形调整浪

如本文以上所述,我们对于在突破减速水平时转向的锯齿形调整浪感兴趣。什么是“减速水平”?假设我们要编写一个锯齿形调整浪,当价格从峰值移动 H 个点时其峰值是固定的。峰值固定意味着某段锯齿形调整浪转到相反方向。我们只固定最小值,现在就处于上升段。为一个不完整的上升段引入一个价格时间最大值的变量,TempMax。如果价格突破以下水平,我们将固定该最大值(并转向):

SwitchLevel = TempMax - H *Point

如果时间最大值在转向前更新,我们就必须计算 SwitchLevel 的新值。这样,SwitchLevel 将跟随时间最大值,位于其后 H 个点处。

对于下降段,情形完全对称。SwitchLevel 将跟随时间最小值(TempMin),同样位于其后 H 个点。但现在,我们有:

SwitchLevel = TempMin + H *Point

事实上,我们仅描述了将要创建的锯齿形调整浪减速水平的计算方法。显然,这不是唯一的算法。例如,如果我们将通道的上下边界视为减速水平,我们将得到跟通道计算方法同样多的锯齿形调整浪。此外,经过仔细考虑后发现,作者所知的绝大多数锯齿形调整浪均完全或部分的包含在考虑的类别中。但并非全部。例如,在威廉姆的分形上计算的锯齿形调整浪无法包含在此类别中。

锯齿形调整浪模型

我们现在决定锯齿形调整浪状态的变量。

首先,这将会是当前段的方向。我们将相应的变量命名为 UpZ,对上升段赋值为true,对下降段赋值为false

显然,我们应该将上面引入的 TempMaxTempMin 添加到列表。我们还将添加其时间坐标。但是,在定义测量单位时,我们有一些自由度。对于时间坐标,我们将使用从图表头部开始的柱数,即我们将使用跟 MT4 中接受的刚好相反的编号系统。这会简化代码并提高执行率。这样,列表将添加 TempMaxBarTempMinBar 变量。

我们计划在图表上绘制锯齿形调整浪并使用。所以我们将向列表添加最后固定的锯齿形调整浪峰值坐标:CurMax, CurMaxBar, CurMin, CurMinBar

这就是列表要添加的内容。具体锯齿形调整浪的个体作者可以根据他/她对锯齿形调整浪的处理自由补充列表。例如,可能需要添加之前峰值的坐标:PreMax、PreMaxBar、PreMin、PreMinBar。或者,在这种情况下,你可能需要使用数组添加预定义数量的之前峰值的坐标。

转移运算符

在这种方法中,为锯齿形调整浪编写转移运算符变为相当简单的任务。我们只需将感兴趣的锯齿形调整浪类别的定义转化为 MQL4。呈现如下:

// First, process the case of an up-segment
    if (UpZ) {
// Check whether the current maximum has changed
      if (High[pos]>TempMax) {
// If yes, then correct the corresponding variables
        TempMax = High[pos];
        TempMaxBar = Bars-pos;  // Here switching to direct numbering
      } else {
// If not, then check whether the slowing level has been broken through
        if (Low[pos]<SwitchLevel()) {
// If yes, then fix the maximum
          CurMax = TempMax;
          CurMaxBar = TempMaxBar;
// And draw a peak
          ZZ[Bars-CurMaxBar]=CurMax;  // Here switching to reverse numbering
// Correct the corresponding variables
          UpZ = false;
          TempMin = Low[pos];
          TempMinBar = Bars-pos;  // Here switching to direct numbering
        }
      }
    }  else {
// Now processing the case of down-segment
// Check whether the current minimum has changed
      if (Low[pos]<TempMin) {
// If yes, then correct the corresponding variables
        TempMin = Low[pos];
        TempMinBar = Bars-pos;  // Here switching to direct numbering
      } else {
// If not, then check whether the slowing level has been broken through
        if (High[pos]>SwitchLevel()) {
// If yes, then fix the minimum
          CurMin = TempMin;
          CurMinBar = TempMinBar;
// And draw a peak
          ZZ[Bars-CurMinBar]=CurMin;  // Here switching to reverse numbering
// Correct the corresponding variables
          UpZ = true;
          TempMax = High[pos];
          TempMaxBar = Bars-pos;  // Here switching to direct numbering
       }
      }
    }

转移运算符已经就绪。现在我们可以随时引用指标的状态变量。

但是,这种运算符有一种特性,在绘制锯齿形调整浪时会被认为是错误。让我们更加详细的讨论以下片段:

      if (High[pos]>TempMax) {
// If yes, then correct the corresponding variables
        TempMax = High[pos];
        TempMaxBar = Bars-pos;  // Here switching to direct numbering
      } else {
// If not, then check whether the slowing level has been broken through
        if (Low[pos]<SwitchLevel()) {
// If yes, then fix the maximum
          CurMax = TempMax;
          CurMaxBar = TempMaxBar;

使用 if - else 语句对意味着不考虑包含 TempMax 的柱的 Low 。当出现价格变为低于下一个固定最小值的情况时,可以认为是锯齿形调整浪绘制错误。这是错误吗?

考虑到对于历史记录和实时中效果必须一致,作者认为这不是错误。事实上,在一个时间范围内,我们将永远不会知道之前历史发生的事情,柱的最高值或最低值。这里使用 if - else 结构意味着进行刻意的决策:我们喜欢动量。这意味着牺牲上升段的最小值和下降段的最大值。时间范围越小,这种两难困境发生的频率就越低,这是有道理的。

另一个片段需要一些注释:

// Correct the corresponding variables
          UpZ = false;
          TempMin = Low[pos];
          TempMinBar = Bars-pos;  // Here switching to direct numbering
        }
      }

事实上,这里时间最小值检验的起始点是为区段之间的转向时刻设置的。它对于给出的示例是有道理的,但在一般情况下,你一定不要这样做。在从固定最大值到当前位置(即到转向时刻)的最小时间间隔设置临时最小值更加合理。代码可如下所示:

   // Correct the corresponding variables
          UpZ = false;
          TempMinBar = CurMaxBar+1;
          TempExtPos = Bars - TempMinBar;  // Here switching to reverse numbering
          TempMin = Low[TempExtPos];
          for (i=TempExtPos-1;i>=pos;i--) {
            if (Low[i]<TempMin) {
              TempMin = Low[i];
              TempMinBar = Bars-i;  // Here switching to direct numbering
            }
          }

这里,最小值已经固定的柱的 Low 不再予以考虑。

这两个注意事项也关乎对下降段的处理。

指标

现在只需要完成指标,使其正常作用。不需要在 init()deinit() 上注释,因为一切都很清楚和标准。但是,我们将对函数 start() 做出一个重要决定。我们将只使用完整的柱。这样做的主要原因是可以实现简单紧凑的代码结构。

还有另一个相关的重要考虑。在交易系统上的严肃工作意味着收集历史统计数据。只有实时获得的特征跟从历史获得的特征完全一致时,统计数据才有效(正确)。我们没有真实价格变动的历史记录,所以只能在完整的柱上实时实现完全一致性。对于减小延迟,我们能做的最多是采用较小的时间范围,减小至 M1。

另一个重要的特点是不要使用 IndicatorCounted() 函数。这样做的主要原因是使用的代码需要另一个重要操作 - 指标状态变量的初始化。这无法在函数 init() 完成,因为使用直接编号要求在历史记录提取时重新计算指标,从而要重新初始化状态变量。init() 函数并非在历史记录提取时启动。

因此,我们必须再添加一个“标准”函数 Reset()。最终,使用 IndicatorCounted() 的想法并没有多大帮助,因为影响了对这种类型的指标组织必要的重新计算检验。该检验实现如下:

int start() {
//  Work with completed bars only
  if (Bars == PreBars) return(0);  
//  Check whether there are enough bars on the chart
  if (Bars < MinBars) {
    Alert(": Not enough bars on the chart");
    return(0);
  }  
//  If the history was not pumped, make calculations for the bar just completed
  if (Bars-PreBars == 1 && BarTime==Time[1]) StartPos = 1;
//  Otherwise, count the number of bars specified in function Reset() 
  else StartPos = Reset();
// Modify check variables
  PreBars = Bars;  
  BarTime=Time[0];
// Cycle on history
  for (pos=StartPos;pos>0;pos--) {

Reset() 函数呈现如下:

int Reset() {
  if (MinBars == 0) MinBars = Bars-1;
  StartPos = MinBars;
  PreBars = 0;
  BarTime = 0;
  dH = H*Point;
  UpZ = true;
  TempMaxBar = Bars-StartPos;
  TempMinBar = Bars-StartPos;
  TempMax = High[StartPos];
  TempMin = Low[StartPos];
  StartPos++;
  return(StartPos);
}

这里我们特别注意一下附加变量 dH ,我们将转换为价格范围的锯齿形调整浪转向阀值(H )永久分配给它。可能会产生一个问题:为什么是 UpZ = true,而不是 false?答案很简单。在少量的片段后,不管 UpZ 的初始值如何,指标会得到相同的图形。

最终,减速水平的计算如下:

double SwitchLevel() {
  double SwLvl;
  if (UpZ) SwLvl = TempMax - dH;
  else SwLvl = TempMin + dH;
  return(SwLvl);
}

这里一切都很清楚了。

总结

编写锯齿形调整浪的模板 ZZTemplate 随附本文。你需要做的只是添加必要的代码至函数 SwitchLevel()。要将模板转换为此处作为示例的锯齿形调整浪,你只需要找到以下行及其注释:

//extern int H = 33;
//double dH;
//  dH = H*Point;
//  if (UpZ) SwLvl = TempMax - dH;
//  else SwLvl = TempMin + dH;

最后要注意的是锯齿形调整浪速度。模板意味着通用性。此外,我们希望拥有尽可能透明的结构。我认为,大多数特定的实现方式都可以额外进行优化以增强其运算能力。

一般建议如下:在可能的情况下,把运算放入 if 运算符。作为优化(并非理想的模型)的示例,请找到随附的 HZZ 指标,这是本文所用锯齿形调整浪的另一种实现方法。问题的简易性使我们可以放弃 SwitchLevel() 函数和一些状态变量。作为小福利,我在 HZZ 添加了把锯齿形调整浪峰值写入文件和‘即时’检验锯齿形调整浪的一些统计特征。