English Русский Deutsch 日本語
preview
MQL5 简介(第 19 部分):沃尔夫波浪自动检测

MQL5 简介(第 19 部分):沃尔夫波浪自动检测

MetaTrader 5交易系统 |
27 0
Israel Pelumi Abioye
Israel Pelumi Abioye

概述

欢迎回到 MQL5 入门系列第 19 部分!在第 18 部分中,我向大家介绍了沃尔夫波浪形态,这是一种独特的五浪结构,用于精确地预测价格反转。我们讨论了沃尔夫波浪理论的两种主要类型:预示价格可能下跌的看跌形态和暗示即将上涨的看涨形态。您还学习了如何获取烛形数据,这对于价格行为分析至关重要,我们还概述了根据这种形态发现有效交易机会的逻辑。

在这一部分,我们将从理论转向实现。我们将探索如何通过编程方式识别沃尔夫波浪结构,并使用 MQL5 根据这些结构执行交易。这包括检测关键的波动点、验证形态规则,以及让 EA 根据它发现的信号采取行动。

在本文中,您将学习:

  • 如何利用历史价格数据检测沃尔夫波浪理论中的经典五浪结构。
  • 通过程序识别波动的高点和低点,以确定波浪点。
  • 了解如何应用和修改斐波那契展开水平来验证波浪形成,特别是第 3 浪和第 5 浪。
  • 文章展示了如何绘制三条重要的趋势线(第 1至 3 浪、第 2 至 4 浪和第 1 至 4 浪),这些趋势线对于确认形态和做出交易决策至关重要。
  • 如何检查第 1 至 2 波和第 3 至 4 波之间的对称性和比例关系,以提高形态可靠性。
  • 学习如何使用 MQL5 直接在图表上标记波浪并绘制文本、矩形和趋势线等视觉对象。
  • 实现逻辑,在下单前等待确认,避免错误入场。
  • 如何设置自动交易退出机制,当市场触及第 1 浪和第 4 浪之间的趋势线时,帮助管理风险。


识别看跌沃尔夫波浪形态

在上一篇文章中,我们已经详细讨论了看跌沃尔夫波浪形态的结构和规则。现在,在本节中,我们将重点介绍如何以编程方式实现该逻辑。 如前所述,看跌的沃尔夫波浪由五个波浪组成,这些波浪必须遵循一定的顺序并满足一定的结构要求。第 2 浪必须是位于第 1 浪下方的低点,而第 1 浪必须是高点。另一个波动高点,但这次高于第 1 浪,并且位于第 1 浪到第 2 浪的斐波那契延伸线内,由第 3 浪形成。然后识别出第 4 浪,即跌破第 3 浪但保持在第 2 浪之上的低点。第 5 浪完成该形态,其波动幅度高于第 3 浪,并落入第 3 浪到第 4 浪运动的预定斐波那契扩展范围内。

记住第 1 浪和第 2 浪的波峰必须与第 3 浪和第 4 浪的波峰大小相当也是至关重要的。理想情况下,第 3 浪和第 4 浪的长度至少应为第 1 浪和第 2 浪的 70%。这种对称性赋予了结构可信度,同时也验证了该形态的有效性。本节将使用波动检测函数定位五个位置,并进行检查以确保它们的距离和连接符合沃尔夫波浪的要求。

识别第 1 浪和第 2 浪

准确识别第 1 浪和第 2 浪是发现看跌沃尔夫波浪形态的第一步。如果没有这两个基本要素,就很难准确定位剩余的波浪。本部分将重点寻找符合图表上第 1 浪和第 2 浪标准的合格高点和低点。一旦检测到这两个波浪,我们将立即在图表上清楚地标记它们,以便进行视觉参考。

在第 1 浪和第 2 浪之间,我们还将提供一个斐波那契扩展对象。通过根据形态规则建立适当的价格水平,这种扩展将有助于指导第 3 浪和第 5 浪的检测。通过尽早实施,我们为波浪识别过程的剩余部分奠定了坚实的基础。

示例:
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;
int bars_check  = 500;
datetime time_bar;
double total_symbol_bars;

double open[];
double close[];
double low[];
double high[];
datetime time[];

double wv1;
datetime wv1_time;
string   wv1_txt;

double wv2;
datetime wv2_time;
string   wv2_txt;

string fib_ext_wv1_wv2;

ulong chart_id = ChartID();

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

   ObjectsDeleteAll(chart_id);

  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   total_symbol_bars = Bars(_Symbol, timeframe);
   time_bar = iTime(_Symbol,timeframe,0);

   CopyOpen(_Symbol, timeframe, time_bar, bars_check, open);
   CopyClose(_Symbol, timeframe, time_bar, bars_check, close);
   CopyLow(_Symbol, timeframe, time_bar, bars_check, low);
   CopyHigh(_Symbol, timeframe, time_bar, bars_check, high);
   CopyTime(_Symbol, timeframe, time_bar, bars_check, time);

   if(total_symbol_bars >= bars_check)
     {
      for(int i = 7; i < bars_check - 7; i++)
        {
         if(IsSwingHigh(high, i, 7))
           {
            wv1 = high[i];
            wv1_time = time[i];
            wv1_txt = StringFormat("WAVE 1 %d", i);

            for(int j = i; j < bars_check - 7; j++)
              {
               if(IsSwingLow(low, j, 7) && low[j] < wv1)
                 {
                  wv2 = low[j];
                  wv2_time = time[j];
                  wv2_txt = StringFormat("WAVE 2 %d", j);

                  ObjectCreate(chart_id, wv1_txt, OBJ_TEXT, 0, wv1_time, wv1);
                  ObjectSetString(chart_id, wv1_txt, OBJPROP_TEXT, "WV1");
                  ObjectSetInteger(chart_id, wv1_txt, OBJPROP_COLOR, clrBlue);

                  ObjectCreate(chart_id, wv2_txt, OBJ_TEXT, 0, wv2_time, wv2);
                  ObjectSetString(chart_id, wv2_txt, OBJPROP_TEXT, "WV2");
                  ObjectSetInteger(chart_id, wv2_txt, OBJPROP_COLOR, clrBlue);

                  fib_ext_wv1_wv2 = StringFormat("FIBO EXTENSION WAVE 1 AND 2 %d", i);
                  ObjectCreate(chart_id, fib_ext_wv1_wv2, OBJ_EXPANSION, 0, wv2_time, wv2, wv1_time, wv1, wv2_time, wv2);
                  ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_COLOR, clrBlue);

                  for(int i = 0; i <= 2; i++)
                    {
                     ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_LEVELCOLOR, i, clrBlue);
                    }

                  break;
                 }
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| FUNCTION FOR SWING LOW                                           |
//+------------------------------------------------------------------+
bool IsSwingLow(const double &low_price[], int index, int lookback)
  {
   for(int i = 1; i <= lookback; i++)
     {
      if(low_price[index] > low_price[index - i] || low_price[index] > low_price[index + i])
         return false;
     }
   return true;
  }

//+------------------------------------------------------------------+
//| FUNCTION FOR SWING HIGH                                          |
//+------------------------------------------------------------------+
bool IsSwingHigh(const double &high_price[], int index, int lookback)
  {
   for(int i = 1; i <= lookback; i++)
     {
      if(high_price[index] < high_price[index - i] || high_price[index] < high_price[index + i])
         return false;
     }
   return true;
  }

输出:

图1. 第 1 浪和第 2 浪

解释:

声明某些变量是为了保留有关前两浪的关键信息,以便有效地识别和描述图表上的看跌沃尔夫波浪等模式。为第 1 浪添加了三个变量:wv1 用于保存价格水平,wv1_time 用于记录价格发生的精确时刻,wv1_txt 用于作为图表上波浪图形表示的文本标签。对于第 2 浪,变量 wv2、wv2_time 和 wv2_txt 具有相同的功能。 

因为它们使 EA 交易能够保存并随后查阅检测到显著波动高点或低点的精确价格点和时间戳,所以这些数据至关重要。借助字符串标签,交易者可以更容易地确定图表上重点显示的是哪一个浪,这对于视觉清晰度尤其有帮助。

除了波浪数据之外,还声明了一个名为 fib_ext_wv1_wv2 的字符串变量,用于存储斐波那契扩展对象的名称。该变量确保表示从第 1 浪到第 2 浪的扩展的项目可以根据需要进行适当的生成、更改或删除,因为图表上的每个对象都需要一个唯一的标识。为了保持每个实例的唯一性,波浪索引通常会包含在名称中,尤其是在 EA 交易查看过去的柱形时创建多个模式的情况下。

此外,该代码还使用 ChartID() 函数获取当前图表的唯一 ID,并将其存储在变量 chart_id 中,以支持所有的对象创建操作。由于 MetaTrader 5 允许同时打开多个图表,因此这一点意义重大。为防止冲突或错位,该应用程序通过明确引用相关的图表 ID 来确保在目标图表上生成和更改图形元素。

OnDeinit() 函数中的 ObjectsDeleteAll(chart_id); 行包含一个清理函数。当移除 EA 交易或关闭图表时,此函数会从图表中移除 EA 交易先前绘制的所有对象。这样可以避免杂乱,并确保 EA 运行完毕后,不会留下过时的绘图元素,例如波浪标签或斐波那契线。

EA 会进行关键检查,以确保在形态检测逻辑启动之前有足够的柱形可用。它将计划扫描的柱形数量(bars_check)与图表上的柱形总数(total_symbol_bars)进行比较。程序开始扫描价格数据,以寻找看跌沃尔夫波浪的早期迹象,前提是显示屏上已加载足够的历史柱形(如果(total_symbol_bars >= bars_check))。

此检测过程的第一步是使用 for 循环:

for(int i = 7; i < bars_check - 7; i++)

为了安全地检查波动高点和低点,循环从索引 7 而不是 0 开始。为了确定价格是否真的是波动点,波动检测函数(稍后会介绍)会向前检查 7 根柱形,向后检查 7 根柱形。如果循环从索引 0 开始,则不会有任何先前的柱形可供比较,从而导致越界错误。类似地,在 bars_check -7 处停止循环可以保证有足够的后续柱形进行比较。 

这个外部循环正在寻找沃尔夫波浪模式的第 1 浪,即一个波动高点。该代码判断索引 i 处的最高价格是否为局部最大值,即是否高于周围的烛形价格。每个索引 i 代表一根历史烛形。

if(IsSwingHigh(high, i, 7))

如果函数结果为真,EA 会将这根烛形视为潜在的第 1 浪候选者,表明它是一个合个的波动高点。创建一个标签字符串(wv1_txt)以便稍后在图表中使用,并将相关的最高价格和时间保存到变量 wv1 和 wv1_time 中。 

一旦找到有效的第 1 浪(高点),代码就会启动一个内部循环,向前寻找下一个有效的低点,该低点将代表第 2 浪:

for(int j = i; j < bars_check - 7; j++)

该循环从与外层循环相同的索引 (i) 开始,并向前移动。它会扫描符合以下两个条件的柱形:

  • 这是一个波动低点(局部最低点)。
  • 它的价格比第 1 浪的价格低。
if(IsSwingLow(low, j, 7) && low[j] < wv1)

当第 2 浪的两个要求都得到满足时,EA 会将价格、时间和标签记录为 wv2、wv2_time 和 wv2_txt。一旦找到第 1 波和第 2 波,它就会使用 break 语句结束内部循环,在它们之间绘制一个斐波那契扩展对象,并为这两个位置在图表中添加文本标签。这样就停止了让 EA 识别出当前第 1 浪的合适匹配项,然后寻找更多第 2 浪的可能性。

识别第 3 浪和第 4 浪

要构建沃尔夫波浪形态,在正确识别出第 1 浪和第 2 浪之后,识别出第 3 浪和第 4 浪是有意义的。第 1 浪和第 2 浪的位置和组成对后来的这些波浪有很大的影响。特别是第 3 浪需要满足两个基本要求:它需要高于第 1 浪的高点,并且落入根据第 1 浪和第 2 浪计算出的预定斐波那契扩展范围内。

利用从第 1 浪到第 2 浪的价格走势绘制斐波那契扩展线,以确定该区间。根据第 1 浪结构的幅度和动量,该工具可以帮助预测未来可能的价格水平。预计价格在反转之前将达到的区间是 127.2% 至 161.8% 的扩展位,这通常是第 3 浪的可接受区域。如果价格在这个区间内大幅波动,则被认为是第 3 浪的有力竞争者。

第 3 浪除了要符合价格水平外,还必须符合形态的对称性。第 1 浪和第 2 浪的结构和规模应该在第 3 浪和第 4 浪中得到适当的反映或呼应。正是这种几何平衡区分了合个的沃尔夫波浪形态。为了本研究的目的,我们将以直接、可量化的术语定义对称性。从第 1 浪到第 2 浪的总波动幅度至少应为第 3 浪和第 4 浪总波动幅度的 70%。随着我们不断开发 EA 交易,该百分比为形态验证提供了一个坚实的框架,并有助于保持沃尔夫波浪的结构和视觉完整性。

确定第 1 浪和第 2 浪之间距离的70%。

首先,让我们确定第 1 浪和第 2 浪之间距离的 70%。

示例:

double wv1_wv2_size;
double wv1_wv2_70p;
if(total_symbol_bars >= bars_check)
  {
   for(int i = 7; i < bars_check - 7; i++)
     {
      if(IsSwingHigh(high, i, 7))
        {
         wv1 = high[i];
         wv1_time = time[i];
         wv1_txt = StringFormat("WAVE 1 %d", i);

         for(int j = i; j < bars_check - 7; j++)
           {
            if(IsSwingLow(low, j, 7) && low[j] < wv1)
              {
               wv2 = low[j];
               wv2_time = time[j];
               wv2_txt = StringFormat("WAVE 2 %d", j);

               ObjectCreate(chart_id, wv1_txt, OBJ_TEXT, 0, wv1_time, wv1);
               ObjectSetString(chart_id, wv1_txt, OBJPROP_TEXT, "WV1");
               ObjectSetInteger(chart_id, wv1_txt, OBJPROP_COLOR, clrBlue);

               ObjectCreate(chart_id, wv2_txt, OBJ_TEXT, 0, wv2_time, wv2);
               ObjectSetString(chart_id, wv2_txt, OBJPROP_TEXT, "WV2");
               ObjectSetInteger(chart_id, wv2_txt, OBJPROP_COLOR, clrBlue);

               fib_ext_wv1_wv2 = StringFormat("FIBO EXTENSION WAVE 1 AND 2 %d", i);
               ObjectCreate(chart_id, fib_ext_wv1_wv2, OBJ_EXPANSION, 0, wv2_time, wv2, wv1_time, wv1, wv2_time, wv2);
               ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_COLOR, clrBlue);

               for(int i = 0; i <= 2; i++)
                 {
                  ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_LEVELCOLOR, i, clrBlue);
                 }

               wv1_wv2_size = MathAbs(wv1 - wv2);
               wv1_wv2_70p = (wv1_wv2_size / 100) * 70;

               string luh = StringFormat("bb 2 %d", j);
               ObjectCreate(chart_id, luh, OBJ_TREND, 0, wv1_time, wv1 - wv1_wv2_70p, wv2_time, wv1 - wv1_wv2_70p);

               break;
              }
           }
        }
     }
  }

输出:

图 2. 第 1 浪和第 2 浪之间距离的 70%。

解释:

第 3 浪除了要符合价格水平外,还必须符合形态的对称性。第 1 浪和第 2 浪的结构和规模应该在第 3 浪和第 4 浪中得到适当的反映或呼应。正是这种几何平衡区分了合个的沃尔夫波浪形态。为了本研究的目的,我们将以直接、可量化的术语定义对称性。从第 1 浪到第 2 浪的总波动幅度至少应为第 3 浪和第 4 浪总波动幅度的 70%。随着我们不断开发 EA 交易,该百分比为形态验证提供了一个坚实的框架,并有助于保持沃尔夫波浪的结构和视觉完整性。

为了帮助直观地理解这一标准,在图表上绘制了一条水平趋势线,从第 1 浪到第 2 浪,价格水平比第 1 浪高点低 70%。这条线作为参考点,以确保后续波浪达到最小尺寸要求。

无论第 1 浪和第 2 浪的顺序如何,这里都使用 MathAbs 函数来保证计算出的大小始终为正数。使用 MathAbs 是一种预防措施,尽管在这种特定情况下技术上并非必需,因为在看跌形态中,预计第 1 浪将高于第 2 浪。如果低估或颠倒波浪,这样做有助于防止可能出现的错误。

第 1 浪和第 2 浪的斐波那契扩展

该算法使用 IsSwingHigh 函数,通过搜索位于第 2 浪之后且介于第 1 浪和第 2 浪的 127.2% 和 161.8% 斐波那契扩展之间的波动高点来找到第 3 浪。如果合适,则保存第 3 浪。为了确保我们处理的是正确的形态,我们只在图表上绘制斐波那契工具、70% 线和波浪名称。算法会寻找第 4 浪,它必须是第 3 浪之后的低点。

示例:

input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;
input double max_fib_ext_wv12 = 161.8;
input double min_fib_ext_wv12 = 127.2;

int bars_check = 500;
datetime time_bar;
double total_symbol_bars;

double open[];
double close[];
double low[];
double high[];
datetime time[];

double wv1;
datetime wv1_time;
string wv1_txt;

double wv2;
datetime wv2_time;
string wv2_txt;

string fib_ext_wv1_wv2;

ulong chart_id = ChartID();

double wv1_wv2_size;
double wv1_wv2_70p;
string perc_70;
double fib_ext_1_2_161_8;
double fib_ext_1_2_127_2;
string fib_ext_range;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(chart_id);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   total_symbol_bars = Bars(_Symbol, timeframe);
   time_bar = iTime(_Symbol, timeframe, 0);

   CopyOpen(_Symbol, timeframe, time_bar, bars_check, open);
   CopyClose(_Symbol, timeframe, time_bar, bars_check, close);
   CopyLow(_Symbol, timeframe, time_bar, bars_check, low);
   CopyHigh(_Symbol, timeframe, time_bar, bars_check, high);
   CopyTime(_Symbol, timeframe, time_bar, bars_check, time);

   if(total_symbol_bars >= bars_check)
     {
      for(int i = 7; i < bars_check - 7; i++)
        {
         if(IsSwingHigh(high, i, 7))
           {
            wv1 = high[i];
            wv1_time = time[i];
            wv1_txt = StringFormat("WAVE 1 %d", i);

            for(int j = i; j < bars_check - 7; j++)
              {
               if(IsSwingLow(low, j, 7) && low[j] < wv1)
                 {
                  wv2 = low[j];
                  wv2_time = time[j];
                  wv2_txt = StringFormat("WAVE 2 %d", j);

                  ObjectCreate(chart_id, wv1_txt, OBJ_TEXT, 0, wv1_time, wv1);
                  ObjectSetString(chart_id, wv1_txt, OBJPROP_TEXT, "WV1");
                  ObjectSetInteger(chart_id, wv1_txt, OBJPROP_COLOR, clrBlue);

                  ObjectCreate(chart_id, wv2_txt, OBJ_TEXT, 0, wv2_time, wv2);
                  ObjectSetString(chart_id, wv2_txt, OBJPROP_TEXT, "WV2");
                  ObjectSetInteger(chart_id, wv2_txt, OBJPROP_COLOR, clrBlue);

                  fib_ext_wv1_wv2 = StringFormat("FIBO EXTENSION WAVE 1 AND 2 %d", i);
                  ObjectCreate(chart_id, fib_ext_wv1_wv2, OBJ_EXPANSION, 0, wv2_time, wv2, wv1_time, wv1, wv2_time, wv2);
                  ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_COLOR, clrBlue);

                  for(int i = 0; i <= 2; i++)
                    {
                     ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_LEVELCOLOR, i, clrBlue);
                    }

                  wv1_wv2_size = MathAbs(wv1 - wv2);
                  wv1_wv2_70p = (wv1_wv2_size / 100) * 70;

                  perc_70 = StringFormat("70 PERCENT %d", j);
                  ObjectCreate(chart_id, perc_70, OBJ_TREND, 0, wv1_time, wv1 - wv1_wv2_70p, wv2_time, wv1 - wv1_wv2_70p);

                  fib_ext_1_2_127_2 = MathAbs((((wv1 - wv2) / 100) * (min_fib_ext_wv12 - 100)) + wv1);
                  fib_ext_1_2_161_8 = MathAbs((((wv1 - wv2) / 100) * (max_fib_ext_wv12 - 100)) + wv1);

                  fib_ext_range = StringFormat("Fibo EXPENSION RANGE%d", j);
                  ObjectCreate(chart_id, fib_ext_range, OBJ_RECTANGLE, 0, wv1_time, fib_ext_1_2_161_8, wv2_time, fib_ext_1_2_127_2);

                  break;
                 }
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| FUNCTION FOR SWING LOW                                           |
//+------------------------------------------------------------------+
bool IsSwingLow(const double &low_price[], int index, int lookback)
  {
   for(int i = 1; i <= lookback; i++)
     {
      if(low_price[index] > low_price[index - i] || low_price[index] > low_price[index + i])
         return false;
     }
   return true;
  }

//+------------------------------------------------------------------+
//| FUNCTION FOR SWING HIGH                                          |
//+------------------------------------------------------------------+
bool IsSwingHigh(const double &high_price[], int index, int lookback)
  {
   for(int i = 1; i <= lookback; i++)
     {
      if(high_price[index] < high_price[index - i] || high_price[index] < high_price[index + i])
         return false;
     }
   return true;
  }

输出:

图3. 第 1 浪和第 2 浪的斐波那契扩展

识别第 3 浪

该代码从第 2 浪继续向前,发现第 1 浪和第 2 浪的 127.2% 到 161.8% 扩展范围内的波动高点,从而将第 3 浪识别为看跌的沃尔夫波浪。如果在该区域发现有效的波动,则该区域被指定为第 3 浪。 

示例:
double wv3;
datetime wv3_time;
string wv3_txt;
if(total_symbol_bars >= bars_check)
     {
      for(int i = 7; i < bars_check - 7; i++)
        {
         if(IsSwingHigh(high, i, 7))
           {
            wv1 = high[i];
            wv1_time = time[i];
            wv1_txt = StringFormat("WAVE 1 %d", i);

            for(int j = i; j < bars_check - 7; j++)
              {
               if(IsSwingLow(low, j, 7) && low[j] < wv1)
                 {
                  wv2 = low[j];
                  wv2_time = time[j];
                  wv2_txt = StringFormat("WAVE 2 %d", j);

                  wv1_wv2_size = MathAbs(wv1 - wv2);
                  wv1_wv2_70p = (wv1_wv2_size / 100) * 70;

                  fib_ext_1_2_127_2 = MathAbs((((wv1 - wv2) / 100) * (min_fib_ext_wv12 - 100)) + wv1);
                  fib_ext_1_2_161_8 = MathAbs((((wv1 - wv2) / 100) * (max_fib_ext_wv12 - 100)) + wv1);

                  for(int k = j; k < bars_check - 7; k++)
                    {
                     if(IsSwingHigh(high, k, 7) && time[k] > wv2_time)
                       {
                        wv3 = high[k];
                        wv3_time = time[k];
                        wv3_txt = StringFormat("WAVE 3 %d", k);

                        if(wv3 >= fib_ext_1_2_127_2 && wv3 <= fib_ext_1_2_161_8)
                          {
                           ObjectCreate(chart_id, wv1_txt, OBJ_TEXT, 0, wv1_time, wv1);
                           ObjectSetString(chart_id, wv1_txt, OBJPROP_TEXT, "WV1");
                           ObjectSetInteger(chart_id, wv1_txt, OBJPROP_COLOR, clrBlue);

                           ObjectCreate(chart_id, wv2_txt, OBJ_TEXT, 0, wv2_time, wv2);
                           ObjectSetString(chart_id, wv2_txt, OBJPROP_TEXT, "WV2");
                           ObjectSetInteger(chart_id, wv2_txt, OBJPROP_COLOR, clrBlue);

                           fib_ext_wv1_wv2 = StringFormat("FIBO EXTENSION WAVE 1 AND 2 %d", i);
                           ObjectCreate(chart_id, fib_ext_wv1_wv2, OBJ_EXPANSION, 0, wv2_time, wv2, wv1_time, wv1, wv2_time, wv2);
                           ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_COLOR, clrBlue);

                           for(int i = 0; i <= 2; i++)
                             {
                              ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_LEVELCOLOR, i, clrBlue);
                             }

                           perc_70 = StringFormat("70 PERCENT %d", j);
                           ObjectCreate(chart_id, perc_70, OBJ_TREND, 0, wv1_time, wv1 - wv1_wv2_70p, wv2_time, wv1 - wv1_wv2_70p);

                           fib_ext_range = StringFormat("Fibo EXPENSION RANGE%d", j);
                           ObjectCreate(chart_id, fib_ext_range, OBJ_RECTANGLE, 0, wv1_time, fib_ext_1_2_161_8, wv2_time, fib_ext_1_2_127_2);

                           ObjectCreate(chart_id, wv3_txt, OBJ_TEXT, 0, wv3_time, wv3);
                           ObjectSetString(chart_id, wv3_txt, OBJPROP_TEXT, "WV3");
                           ObjectSetInteger(chart_id, wv3_txt, OBJPROP_COLOR, clrBlue);
                          }

                        break;
                       }
                    }

                  break;
                 }
              }
           }
        }
     }

输出:

图 4. 第 3 浪

解释:

代码中使用了三个变量来定义第 3 浪:wv3 用于价格水平,wv3_time 用于时间,wv3_txt 用于标签。通过使用 IsSwingHigh 函数来定位第 2 浪之后的合格波动高点,可以从第 2 浪的位置向前扫描,从而确定第 3 浪。之前,所有对象都是在检测到 wv2 之后绘制的。现在,只有当 wv3 落在 wv1 和 wv2 的指定扩展范围内时,这些对象才会出现。

识别第 4 浪

找到第 4 浪(必须是第 3 浪之后形成的低点)是下一个阶段。我们必须验证从第 3 浪到第 4 浪的价格走势与从第 1 浪到第 2 浪的走势是否保持良好的对称性,以确认该形态仍然有效。本项目采用简单的对称性准则,即第 3 浪至第 4 浪的幅度必须至少为第 1 浪至第 2 浪的 70%。通过防止不成比例或浅回撤,这一要求有助于维持沃尔夫波浪形态的结构平衡。接下来,我们将通过寻找第 3 浪之后的合格低点,并确认其满足最小规模标准,来实践这一推理。

示例:
double wv4;
datetime wv4_time;
string wv4_txt;
double wv3_wv4_size;
if(total_symbol_bars >= bars_check)
     {
      for(int i = 7; i < bars_check - 7; i++)
        {
         if(IsSwingHigh(high, i, 7))
           {
            wv1 = high[i];
            wv1_time = time[i];
            wv1_txt = StringFormat("WAVE 1 %d", i);

            for(int j = i; j < bars_check - 7; j++)
              {
               if(IsSwingLow(low, j, 7) && low[j] < wv1)
                 {
                  wv2 = low[j];
                  wv2_time = time[j];
                  wv2_txt = StringFormat("WAVE 2 %d", j);

                  wv1_wv2_size = MathAbs(wv1 - wv2);
                  wv1_wv2_70p = (wv1_wv2_size / 100) * 70;

                  fib_ext_1_2_127_2 = MathAbs((((wv1 - wv2) / 100) * (min_fib_ext_wv12 - 100)) + wv1);
                  fib_ext_1_2_161_8 = MathAbs((((wv1 - wv2) / 100) * (max_fib_ext_wv12 - 100)) + wv1);

                  for(int k = j; k < bars_check - 7; k++)
                    {
                     if(IsSwingHigh(high, k, 7) && time[k] > wv2_time)
                       {
                        wv3 = high[k];
                        wv3_time = time[k];
                        wv3_txt = StringFormat("WAVE 3 %d", k);

                        if(wv3 >= fib_ext_1_2_127_2 && wv3 <= fib_ext_1_2_161_8)
                          {
                           for(int l = k; l < bars_check - 7; l++)
                             {
                              if(IsSwingLow(low, l, 7) && time[l] > wv3_time)
                                {
                                 wv4 = low[l];
                                 wv4_time = time[l];
                                 wv4_txt = StringFormat("WAVE 4 %d", l);

                                 wv3_wv4_size = MathAbs(wv3 - wv4);

                                 if(wv3_wv4_size >= wv1_wv2_size && wv4 > wv2 && wv4 < wv3)
                                   {
                                    ObjectCreate(chart_id, wv1_txt, OBJ_TEXT, 0, wv1_time, wv1);
                                    ObjectSetString(chart_id, wv1_txt, OBJPROP_TEXT, "WV1");
                                    ObjectSetInteger(chart_id, wv1_txt, OBJPROP_COLOR, clrBlue);

                                    ObjectCreate(chart_id, wv2_txt, OBJ_TEXT, 0, wv2_time, wv2);
                                    ObjectSetString(chart_id, wv2_txt, OBJPROP_TEXT, "WV2");
                                    ObjectSetInteger(chart_id, wv2_txt, OBJPROP_COLOR, clrBlue);

                                    fib_ext_wv1_wv2 = StringFormat("FIBO EXTENSION WAVE 1 AND 2 %d", i);
                                    ObjectCreate(chart_id, fib_ext_wv1_wv2, OBJ_EXPANSION, 0, wv2_time, wv2, wv1_time, wv1, wv2_time, wv2);
                                    ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_COLOR, clrBlue);

                                    for(int i = 0; i <= 2; i++)
                                      {
                                       ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_LEVELCOLOR, i, clrBlue);
                                      }

                                    perc_70 = StringFormat("70 PERCENT %d", j);
                                    ObjectCreate(chart_id, perc_70, OBJ_TREND, 0, wv1_time, wv1 - wv1_wv2_70p, wv2_time, wv1 - wv1_wv2_70p);

                                    fib_ext_range = StringFormat("Fibo EXPENSION RANGE%d", j);
                                    ObjectCreate(chart_id, fib_ext_range, OBJ_RECTANGLE, 0, wv1_time, fib_ext_1_2_161_8, wv2_time, fib_ext_1_2_127_2);

                                    ObjectCreate(chart_id, wv3_txt, OBJ_TEXT, 0, wv3_time, wv3);
                                    ObjectSetString(chart_id, wv3_txt, OBJPROP_TEXT, "WV3");
                                    ObjectSetInteger(chart_id, wv3_txt, OBJPROP_COLOR, clrBlue);

                                    ObjectCreate(chart_id, wv4_txt, OBJ_TEXT, 0, wv4_time, wv4);
                                    ObjectSetString(chart_id, wv4_txt, OBJPROP_TEXT, "WV4");
                                    ObjectSetInteger(chart_id, wv4_txt, OBJPROP_COLOR, clrBlue);
                                   }

                                 break;
                                }
                             }
                          }

                        break;
                       }
                    }

                  break;
                 }
              }
           }
        }
     }

输出:

图 5. 第 4 浪

解释:

程序识别出 wv3 之后,下一步就是识别 wv4。它将查找 wv3 和 wv4 之间的距离,并将其存储在 wv3_wv4_size 中。程序检测到波动低点后,会将柱形时间和价格分别存储为 wv4_time 和 wv4。该程序确保 wv3 和 wv4 与 wv1 和 wv2 相似,同时确保第 4 浪保持在第 2 浪之上、第 3 浪之下,以保持正确的结构。该程序检查 wv3 是否属于指定的扩展范围。在所有检查都成功后,图表上会标记出利用波动逻辑、斐波那契滤波器和对称性标准完成形态设置的第 4 浪。

图 6. 第 3 浪最高点

图 7. 第 1 浪最高点

从第一张图片(图 11)可以看出,从第 3 浪过渡到第 4 浪时,第 3 浪并不是最高的波峰。第 3 浪应该是跌入第 4 浪之前最重要的峰值,所以这很成问题。如果在我们指定的第 3 浪之后出现更高的峰值,则说明我们的检测机制没有捕捉到正确的结构。第二幅图(图 12)也显示,第 1 浪不是第 1 浪和第 2 浪之间区域的最高点。

此外,这也违反了形态规则,因为第 1 浪应该是该浪段中的主要波动高点。如果我们的算法不断选择根本错误的波浪点,我们就无法获得可靠的沃尔夫波浪形态结果。为了提高检测逻辑的可靠性,必须解决这个问题。

现在,我们将为每个浪增加一个额外的验证步骤,以解决我们在前面的例子中看到的问题。为了确保第 3 浪准确地描绘了下跌前的峰值,它必须是第 3 浪和第 4 浪之间的最高点。第 2 浪必须是第 2 浪和第 3 浪之间的最低点,才能真正反映下一轮上涨趋势之前的底部。第 1 浪必须是第 1 浪和第 2 浪之间的最高点,才能容易地被识别为该形态段中的重要波动高点。

示例:

int wv3_wv4_bars;
int wv3_highest_index;
double wv3_hh;
datetime wv3_hh_t;

int wv2_wv3_bars;
int wv2_lowest_index;
double wv2_ll;
datetime wv2_ll_t;

int wv1_wv2_bars;
int wv1_highest_index;
double wv1_hh;
datetime wv1_hh_t;
for(int l = k; l < bars_check - 7; l++)
  {
   if(IsSwingLow(low, l, 7) && time[l] > wv3_time)
     {
      wv4 = low[l];
      wv4_time = time[l];
      wv4_txt = StringFormat("WAVE 4 %d", l);

      wv3_wv4_bars = Bars(_Symbol, timeframe, wv3_time, wv4_time);
      wv3_highest_index = ArrayMaximum(high, k, wv3_wv4_bars);
      wv3_hh = high[wv3_highest_index];
      wv3_hh_t = time[wv3_highest_index];

      wv2_wv3_bars = Bars(_Symbol, timeframe, wv2_time, wv3_time);
      wv2_lowest_index = ArrayMinimum(low, j, wv2_wv3_bars);
      wv2_ll = low[wv2_lowest_index];
      wv2_ll_t = time[wv2_lowest_index];

      wv1_wv2_bars = Bars(_Symbol, timeframe, wv1_time, wv2_time);
      wv1_highest_index = ArrayMaximum(high, i, wv1_wv2_bars);
      wv1_hh = high[wv1_highest_index];
      wv1_hh_t = time[wv1_highest_index];

      wv1_wv2_size = MathAbs(wv1_hh - wv2_ll);
      wv1_wv2_70p = (wv1_wv2_size / 100) * 70;

      fib_ext_1_2_127_2 = MathAbs((((wv1_hh - wv2_ll) / 100) * (min_fib_ext_wv12 - 100)) + wv1_hh);
      fib_ext_1_2_161_8 = MathAbs((((wv1_hh - wv2_ll) / 100) * (max_fib_ext_wv12 - 100)) + wv1_hh);

      wv3_wv4_size = MathAbs(wv3_hh - wv4);

      if(wv3_wv4_size >= wv1_wv2_size && wv4 > wv2_ll && wv4 < wv3_hh && wv3_hh >= fib_ext_1_2_127_2 && wv3_hh <= fib_ext_1_2_161_8)
        {
         ObjectCreate(chart_id, wv1_txt, OBJ_TEXT, 0, wv1_hh_t, wv1_hh);
         ObjectSetString(chart_id, wv1_txt, OBJPROP_TEXT, "WV1");
         ObjectSetInteger(chart_id, wv1_txt, OBJPROP_COLOR, clrBlue);

         ObjectCreate(chart_id, wv2_txt, OBJ_TEXT, 0, wv2_ll_t, wv2_ll);
         ObjectSetString(chart_id, wv2_txt, OBJPROP_TEXT, "WV2");
         ObjectSetInteger(chart_id, wv2_txt, OBJPROP_COLOR, clrBlue);

         fib_ext_wv1_wv2 = StringFormat("FIBO EXTENSION WAVE 1 AND 2 %d", i);
         ObjectCreate(chart_id, fib_ext_wv1_wv2, OBJ_EXPANSION, 0, wv2_ll_t, wv2_ll, wv1_hh_t, wv1_hh, wv2_ll_t, wv2_ll);
         ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_COLOR, clrBlue);

         for(int i = 0; i <= 2; i++)
            ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_LEVELCOLOR, i, clrBlue);

         perc_70 = StringFormat("70 PERCENT %d", j);
         ObjectCreate(chart_id, perc_70, OBJ_TREND, 0, wv1_hh_t, wv1_hh - wv1_wv2_70p, wv2_time, wv1 - wv1_wv2_70p);

         fib_ext_range = StringFormat("Fibo EXPENSION RANGE%d", j);
         ObjectCreate(chart_id, fib_ext_range, OBJ_RECTANGLE, 0, wv1_hh_t, fib_ext_1_2_161_8, wv2_ll_t, fib_ext_1_2_127_2);

         ObjectCreate(chart_id, wv3_txt, OBJ_TEXT, 0, wv3_hh_t, wv3_hh);
         ObjectSetString(chart_id, wv3_txt, OBJPROP_TEXT, "WV3");
         ObjectSetInteger(chart_id, wv3_txt, OBJPROP_COLOR, clrBlue);

         ObjectCreate(chart_id, wv4_txt, OBJ_TEXT, 0, wv4_time, wv4);
         ObjectSetString(chart_id, wv4_txt, OBJPROP_TEXT, "WV4");
         ObjectSetInteger(chart_id, wv4_txt, OBJPROP_COLOR, clrBlue);
        }
      break;
     }
  }

输出:

图 8. 最高点

解释:

我们提出了一套新的计算方法来验证沃尔夫波浪的结构,并确保每个波浪点都能准确地反映其段中最重要的高点或低点。为了识别每个波浪段之间的极端价格点(最高价或最低价),我们首先声明一些变量:变量 wv3_wv4_bars 包含分隔第 3 浪和第 4 浪的柱形数量。然后,使用 ArrayMaximum 对 wv3_wv4_bars 根柱形进行操作,并从索引 k(第 3 浪所在位置)开始,即可获得该范围内的最高值。wv3_highest_index 保留结果。该索引用于提取真实最高价格(wv3_hh)及其时间戳(wv3_hh_t)。

wv2_wv3_bars 涵盖了第 2 浪和第 3 浪之间的区域,尽管其功能类似。这里,我们使用 ArrayMinimum 函数来查找最低价格,该函数会给出最低价格的索引。将此索引存储在 wv2_lowest_index 中后,我们将检索匹配的最低价格 (wv2_ll) 和时间 (wv2_ll_t)。 分隔第 1 浪和第 2 浪的柱形数量由 wv1_wv2_bars 决定。在这种情况下,第 1 浪应该是一个主导峰值;因此我们再次使用 ArrayMaximum 来获取该区域的最高峰值。从存储在 wv1_highest_index 中的结果索引中,我们提取最高价格 (wv1_hh) 和与之对应的日期 (wv1_hh_t)。

wv3_wv4_bars 记录了第 3 浪和第 4 浪之间的柱形数量。为了找到该范围内的最高点,我们使用 ArrayMaximum 遍历 wv3_wv4_bars 根柱子,并从索引 k 开始,k 是第 3 浪所在的位置。结果保留在 wv3_highest_index 中。该索引用于提取时间戳(wv3_hh_t)和实际最高价格(wv3_hh)。 我们通过程序化地强制执行这些限制,提高了沃尔夫波浪识别的准确性,并降低了识别出弱波浪或错误波浪形态的可能性。通过这一额外的验证阶段,可以保证该形态符合技术概念,并适用于实际交易分析。

现在将使用经过验证的高点或低点来构建图表上的对象,而不是最初选择的波浪点。对于第 1 浪,我们将专门用 wv1_hh_t 和 wv1_hh 替换 wv1_time 和 wv1。我们将用 wv2_ll_t 和 wv2_ll 代替 wv2_time,用 wv2 代替 第 2 浪。此外,对于第 3 浪,我们将用 wv3_time 和 wv3 替换 wv3_hh_t 和 wv3_hh。 通过进行这种调整,我们可以确保我们指的是每个部分的实际结构高点和低点,而不仅仅是找到的初始波动点。我们通过在这些已验证的区域绘制图表对象,显著提高了沃尔夫波浪可视化的准确性和可靠性,从而帮助交易者根据更精确的形态结构做出更明智的选择。

只有满足以下条件,才会生成斐波那契扩展、第 1 至 4 浪图表项目和其他图形组件:

if(wv3_wv4_size >= wv1_wv2_size && wv4 > wv2_ll && wv4 < wv3_hh && wv3_hh >= fib_ext_1_2_127_2 && wv3_hh <= fib_ext_1_2_161_8)

这一条件保证了所有基于价格和结构上的合格看跌沃尔夫波浪的要求都得到满足。这证实了第 3 浪和第 4 浪在垂直方向上的距离至少与第 1 浪和第 2 浪之间的距离一样大,保持了对称性。第 4 浪的位置高于第 2 浪和第 3 浪之间的最低点,低于第 3 浪和第 4 浪之间的最高点,这一点得到了进一步证实。此外,与第 1 浪和第 2 浪相比,第 3 浪必须在 127.2% 到 200% 的斐波那契扩展范围内。

在满足所有这些要求之前,软件不会命名波浪、创建斐波那契投影或在图表上指示所需的趋势线。这种严格的验证保证了沃尔夫波浪识别的更高准确性,并有助于防止绘制不准确或不完整的形态。

识别第 5 浪

寻找第 5 浪是我们沃尔夫波浪检测程序的下一个阶段。因为它预示着该形态可能的反转点,所以这一浪至关重要。第 5 浪是技术分析中一个有用的指标,因为交易者经常用它来预测市场方向的转变。但是,我们必须首先构建三条关键的趋势线,以指导第 5 浪的确认,然后才能正确识别它。

初始趋势线将连接第 1 浪和第 3 浪。这条线通常代表看跌沃尔夫波浪形态的上限。它有助于确定形态的结构和方向,并作为阻力位,价格在第 4 浪的构建和第 5 浪的发展过程中都会保持在阻力位内。第二条趋势线将连接第 2 浪和第 4 浪。这条线很重要,因为它阐明了沃尔夫波的通道结构和对称性。当价格接近反转点时,它大致可以指示第 5 浪的预期走势。

连接第 1 浪和第 4 浪的第三条趋势线,其功能与前两条趋势线截然不同。通常情况下,这条线用于决定何时结束交易。在看跌的沃尔夫波浪理论中,一旦发现第 5 浪并且价格开始朝着预期方向移动,第 1 浪到第 4 浪的线就成为止盈目标。一旦市场突破这条趋势线,交易就会平仓,因为它表明预期的走势已经发生,形态已经完成。

示例:
string tline_1_3;
string tline_2_4;
string tline_1_4;
if(wv3_wv4_size >= wv1_wv2_size && wv4 > wv2_ll && wv4 < wv3_hh && wv3_hh >= fib_ext_1_2_127_2 && wv3_hh <= fib_ext_1_2_161_8)
  {
   ObjectCreate(chart_id, wv1_txt, OBJ_TEXT, 0, wv1_hh_t, wv1_hh);
   ObjectSetString(chart_id, wv1_txt, OBJPROP_TEXT, "WV1");
   ObjectSetInteger(chart_id, wv1_txt, OBJPROP_COLOR, clrBlue);

   ObjectCreate(chart_id, wv2_txt, OBJ_TEXT, 0, wv2_ll_t, wv2_ll);
   ObjectSetString(chart_id, wv2_txt, OBJPROP_TEXT, "WV2");
   ObjectSetInteger(chart_id, wv2_txt, OBJPROP_COLOR, clrBlue);

   fib_ext_wv1_wv2 = StringFormat("FIBO EXTENSION WAVE 1 AND 2 %d", i);

   ObjectCreate(chart_id, fib_ext_wv1_wv2, OBJ_EXPANSION, 0, wv2_ll_t, wv2_ll, wv1_hh_t, wv1_hh, wv2_ll_t, wv2_ll);
   ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_COLOR, clrBlue);

   for(int i = 0; i <= 2; i++)
     {
      ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_LEVELCOLOR, i, clrBlue);
     }

   perc_70 = StringFormat("70 PERCENT %d", j);
   ObjectCreate(chart_id, perc_70, OBJ_TREND, 0, wv1_hh_t, wv1_hh - wv1_wv2_70p, wv2_time, wv1 - wv1_wv2_70p);

   fib_ext_range = StringFormat("Fibo EXPENSION RANGE%d", j);
   ObjectCreate(chart_id, fib_ext_range, OBJ_RECTANGLE, 0, wv1_hh_t, fib_ext_1_2_161_8, wv2_ll_t, fib_ext_1_2_127_2);

   ObjectCreate(chart_id, wv3_txt, OBJ_TEXT, 0, wv3_hh_t, wv3_hh);
   ObjectSetString(chart_id, wv3_txt, OBJPROP_TEXT, "WV3");
   ObjectSetInteger(chart_id, wv3_txt, OBJPROP_COLOR, clrBlue);

   ObjectCreate(chart_id, wv4_txt, OBJ_TEXT, 0, wv4_time, wv4);
   ObjectSetString(chart_id, wv4_txt, OBJPROP_TEXT, "WV4");
   ObjectSetInteger(chart_id, wv4_txt, OBJPROP_COLOR, clrBlue);

   tline_1_3 = StringFormat("TREND LINE WAVE 1 AND 3 %d", i);
   ObjectCreate(chart_id, tline_1_3, OBJ_TREND, 0, wv1_hh_t, wv1_hh, wv3_hh_t, wv3_hh);
   ObjectSetInteger(chart_id, tline_1_3, OBJPROP_COLOR, clrBlue);

   tline_2_4 = StringFormat("TREND LINE WAVE 2 AND 4 %d", i);
   ObjectCreate(chart_id, tline_2_4, OBJ_TREND, 0, wv2_ll_t, wv2_ll, wv4_time, wv4);
   ObjectSetInteger(chart_id, tline_2_4, OBJPROP_COLOR, clrBlue);

   tline_1_4 = StringFormat("TREND LINE WAVE 1 AND 4 %d", i);
   ObjectCreate(chart_id, tline_1_4, OBJ_TREND, 0, wv1_hh_t, wv1_hh, wv4_time, wv4);
   ObjectSetInteger(chart_id, tline_1_4, OBJPROP_COLOR, clrBlue);
  }

输出:


图 9. 第 5 浪

解释:

字符串变量 tline_1_3、tline_2_4 和 tline_1_4 的初始声明的目的是唯一标识图表上的趋势线。在 tline_1_3 中,看跌沃尔夫波浪形态的上边界由连接第 1 浪和第 3 浪的趋势线表示。ObjectCreate() 使用第 1 浪和第 3 浪的已确认最高点来创建它,并且为了便于查看,它被着色为蓝色。连接第 2 浪和第 4 浪的第二条趋势线在 tline_2_4 中得以保持。第 2 浪的最低点(wv2_ll_t,wv2_ll)与第 4 浪的点(wv4_time,wv4)之间绘制,后者也以蓝色显示。第 1 浪和第 4 浪由第三条趋势线 tline_1_4 连接起来。

趋势线仅在两个预先设定的锚点之间生成,这是该方法的一个问题。真正的沃尔夫波浪形态中的每一条线都应该延伸超过其第二个锚点,以表明锚点和价格之间未来可能存在的相互作用。例如,第 1 浪和第 3 浪之间的趋势线不仅仅是这两个点之间的一小段。相反,除非市场突破该水平线后又回落到该水平线以下,否则预计该水平线将延续到第 3 浪之后,并进入未来。该扩展部分更清晰地展示了价格与形态上边界随时间变化的联系。

要保持沃尔夫波浪对称性并预测第 5 浪的可能位置,需要第 2 浪和第 4 浪之间的趋势线与第 1 浪和第 3 浪之间的趋势线具有相同的长度和方向。当价格向趋势线移动时(该趋势线是交易退出目标,并指示何时完成交易),它还必须延伸到第 4 浪之后才能被跟随。换句话说,这三条趋势线都必须适当地向前延伸,才能提供准确的形态确认和交易准备。

示例:

tline_1_3 = StringFormat("TREND LINE WAVE 1 AND 3 %d", i);
ObjectCreate(chart_id,tline_1_3,OBJ_TREND,0,wv1_hh_t,wv1_hh,wv3_hh_t,wv3_hh);
ObjectSetInteger(chart_id, tline_1_3, OBJPROP_COLOR, clrBlue);
ObjectSetInteger(chart_id, tline_1_3, OBJPROP_RAY_RIGHT, true);

tline_2_4 = StringFormat("TREND LINE WAVE 2 AND 4 %d", i);
ObjectCreate(chart_id,tline_2_4,OBJ_TREND,0,wv2_ll_t,wv2_ll,wv4_time,wv4);
ObjectSetInteger(chart_id, tline_2_4, OBJPROP_COLOR, clrBlue);
ObjectSetInteger(chart_id, tline_1_3, OBJPROP_RAY_RIGHT, true);

tline_1_4 = StringFormat("TREND LINE WAVE 1 AND 4 %d", i);
ObjectCreate(chart_id,tline_1_4,OBJ_TREND,0,wv1_hh_t,wv1_hh,wv4_time,wv4);
ObjectSetInteger(chart_id, tline_1_4, OBJPROP_COLOR, clrBlue);
ObjectSetInteger(chart_id, tline_1_3, OBJPROP_RAY_RIGHT, true);

图 10. 趋势线

OBJPROP_RAY_RIGHT 属性确保趋势线超出其锚点位置,这也是一个问题。虽然这对于保持线条可见性可能很有用,但这与我们在沃尔夫波浪分析中寻找的精确行为并不相符。我们不希望目前的趋势线永远持续下去。相反,我们希望它们在特定时刻停止,尤其是在满足特定条件时。

例如,连接第 1 浪和第 3 浪的趋势线,应该只延伸到市场价格突破该趋势线后才会回落到其下方。这样做可以保持准确性,并使图表保持清晰,避免出现多余的、可能无关的趋势线。我们可以根据实际价格互动为每条线设定一个指定的结束时间,这样我们就能更好地控制,并且沃尔夫波浪形态的视觉结构会更加精确和清晰。

示例:
tline_1_3 = StringFormat("TREND LINE WAVE 1 AND 3 %d", i);
if(ObjectCreate(chart_id,tline_1_3,OBJ_TREND,0,wv1_hh_t,wv1_hh,wv3_hh_t,wv3_hh))
  {
   ObjectSetInteger(chart_id, tline_1_3, OBJPROP_COLOR, clrBlue);
   ObjectSetInteger(chart_id, tline_1_3, OBJPROP_RAY_RIGHT, true);
   ObjectSetInteger(chart_id, tline_1_3, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
  }

tline_2_4 = StringFormat("TREND LINE WAVE 2 AND 4 %d", i);
if(ObjectCreate(chart_id,tline_2_4,OBJ_TREND,0,wv2_ll_t,wv2_ll,wv4_time,wv4))
  {
   ObjectSetInteger(chart_id, tline_2_4, OBJPROP_COLOR, clrBlue);
   ObjectSetInteger(chart_id, tline_2_4, OBJPROP_RAY_RIGHT, true);
   ObjectSetInteger(chart_id, tline_2_4, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
  }

tline_1_4 = StringFormat("TREND LINE WAVE 1 AND 4 %d", i);
if(ObjectCreate(chart_id,tline_1_4,OBJ_TREND,0,wv1_hh_t,wv1_hh,wv4_time,wv4))
  {
   ObjectSetInteger(chart_id, tline_1_4, OBJPROP_COLOR, clrBlue);
   ObjectSetInteger(chart_id, tline_1_4, OBJPROP_RAY_RIGHT, true);
   ObjectSetInteger(chart_id, tline_1_4, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
  }


图 11. 隐藏趋势线

解释:

通过设置其属性以在所有时间周期内隐藏它,此代码生成一条不可见的趋势线,该趋势线可在内部使用,而不会在图表上显示。然后它会密切关注价格的重大波动,例如烛形收盘价先高于后低于 1 至 3 浪趋势线。

示例:
double t_1_3_values;
double t_2_4_values;
string tline_1_3_visible;
string tline_2_4_visible;
for(int m = l; m < bars_check - 2; m++)
  {

   t_1_3_values = ObjectGetValueByTime(chart_id, tline_1_3, time[m], 0);
   t_2_4_values = ObjectGetValueByTime(chart_id, tline_2_4, time[m], 0);

   if(close[m] > open[m] && open[m] < t_1_3_values && close[m] > t_1_3_values && time[m] > time[l+4])
     {

      tline_1_3_visible = StringFormat("TREND LINE WAVE 1 AND 3 V %d", i);
      ObjectCreate(chart_id,tline_1_3_visible,OBJ_TREND,0,wv1_hh_t,wv1_hh,time[m],t_1_3_values);
      ObjectSetInteger(chart_id, tline_1_3_visible, OBJPROP_COLOR, clrBlue);

      tline_2_4_visible = StringFormat("TREND LINE WAVE 2 AND 4 V %d", i);
      ObjectCreate(chart_id,tline_2_4_visible,OBJ_TREND,0,wv2_ll_t,wv2_ll,time[m],t_2_4_values);
      ObjectSetInteger(chart_id, tline_2_4_visible, OBJPROP_COLOR, clrBlue);

      break;
     }
  }

输出:

图 12. 可见趋势线

解释:

该代码段通过检查从第 4 浪开始的未来柱形来检测价格何时突破连接第 1 浪和第 3 浪的趋势线。它使用变量来获取每个柱形时间点的趋势线价格水平,并寻找开盘价低于趋势线但收盘价高于趋势线的看涨烛形,这预示着突破,突破必须发生在第 4 浪之后至少 4 个柱形之后。检测到突破后,会生成两条额外的可见趋势线,并使用 StringFormat 动态命名。图表随后显示这些趋势线,一条将第 1 浪与突破点连接起来,另一条将第 2 浪与同一位置连接起来。这有助于更好地观察和评估沃尔夫波浪形态的发展,因为它能清晰地显示价格突破第 1 浪至第 3 浪趋势线的确切柱形。

但这并非我们的目标。尽管它们有助于强调突破,但当市场穿过并收于第 1 至 3 浪趋势线之上时产生的可见趋势线,尚未完成沃尔夫波浪结构。真正的目标是不断延长这些线,直到市场跌破并收于第 1 至 3 浪趋势线下方。 

示例:
double t_1_3_values_2;
for(int m = l; m < bars_check - 2; m++)
  {

   t_1_3_values = ObjectGetValueByTime(chart_id, tline_1_3, time[m], 0);

   if(close[m] > open[m] && open[m] < t_1_3_values && close[m] > t_1_3_values && time[m] > time[l+4])
     {

      for(int n = m; n < bars_check - 1; n++)
        {

         t_1_3_values_2 = ObjectGetValueByTime(chart_id, tline_1_3, time[n], 0);
         t_2_4_values = ObjectGetValueByTime(chart_id, tline_2_4, time[n], 0);

         if(close[n] < open[n] && open[n] > t_1_3_values_2 && close[n] < t_1_3_values_2)
           {

            tline_1_3_visible = StringFormat("TREND LINE WAVE 1 AND 3 V %d", i);
            ObjectCreate(chart_id,tline_1_3_visible,OBJ_TREND,0,wv1_hh_t,wv1_hh,time[n],t_1_3_values_2);
            ObjectSetInteger(chart_id, tline_1_3_visible, OBJPROP_COLOR, clrBlue);

            tline_2_4_visible = StringFormat("TREND LINE WAVE 2 AND 4 V %d", i);
            ObjectCreate(chart_id,tline_2_4_visible,OBJ_TREND,0,wv2_ll_t,wv2_ll,time[n],t_2_4_values);
            ObjectSetInteger(chart_id, tline_2_4_visible, OBJPROP_COLOR, clrBlue);

            break;
           }
        }
      break;
     }
  }

输出:

图 13. 趋势线中断

解释:

但这并非我们的目标。尽管它们有助于强调突破,但当市场穿过并收于第 1 至 3 浪趋势线之上时产生的可见趋势线,尚未完成沃尔夫波浪结构。真正的目标是不断延长这些线,直到市场跌破并收于第 1 至 3 浪趋势线下方。具体来说,内部 for 循环使用变量 t_1_3_values_2 来保存稍后时间第 1 至 3 浪趋势线的价格水平。该循环从突破趋势线的柱形开始向前扫描(m)。它使用 ObjectGetValueByTime 来检索每个柱 n 在该时刻的趋势线值,并将其存储在 t_1_3_values_2 中。

如果开盘价高于趋势线,收盘价低于趋势线,则可以通过表达式 if(close[n] < open[n] && open[n] > t_1_3_values_2 && close[n] < t_1_3_values_2) 来检查,这表明价格现在已经回落到趋势线下方。这一点经常标志着第 5 浪的结束和预期价格反转的开始,因此值得关注。一旦发现第二个交叉点,就会产生两条清晰可见的趋势线;一条将第 1 浪与图表上的这个新位置连接起来,另一条将第 2 浪与同一位置连接起来。这可以更全面、更准确地描绘沃尔夫波浪从开始到突破再到反转的全貌,有效地取代了之前的趋势线。现在,趋势线从它们的起始位置延伸到价格走势验证整个形态的精确点。

下一步是确定第 5 浪的价格水平。为了实现这一目标,我们必须确定两个重要点之间的最高点:第一次是市场突破并收于第 1 浪至第 3 浪趋势线之上,第二次是市场反转并跌破该趋势线。通常,这个范围表示沃尔夫波浪形态的结束阶段。在预期中的反转开始之前,该区域的价格通常会再次上涨。找到这个窗口内的最高点,就能让我们精确地确定第 5 浪的峰值。

示例:
int cross_bars;
int cross_bars_highest;
string wv5_txt;
if(close[m] > open[m] && open[m] < t_1_3_values && close[m] > t_1_3_values && time[m] > time[l+4])
  {

   for(int n = m; n < bars_check - 1; n++)
     {

      t_1_3_values_2 = ObjectGetValueByTime(chart_id, tline_1_3, time[n], 0);
      t_2_4_values = ObjectGetValueByTime(chart_id, tline_2_4, time[n], 0);

      if(close[n] < open[n] && open[n] > t_1_3_values_2 && close[n] < t_1_3_values_2)
        {

         tline_1_3_visible = StringFormat("TREND LINE WAVE 1 AND 3 V %d", i);
         ObjectCreate(chart_id,tline_1_3_visible,OBJ_TREND,0,wv1_hh_t,wv1_hh,time[n],t_1_3_values_2);
         ObjectSetInteger(chart_id, tline_1_3_visible, OBJPROP_COLOR, clrBlue);

         tline_2_4_visible = StringFormat("TREND LINE WAVE 2 AND 4 V %d", i);
         ObjectCreate(chart_id,tline_2_4_visible,OBJ_TREND,0,wv2_ll_t,wv2_ll,time[n],t_2_4_values);
         ObjectSetInteger(chart_id, tline_2_4_visible, OBJPROP_COLOR, clrBlue);

         cross_bars = Bars(_Symbol,timeframe,time[n], time[m]);
         cross_bars_highest = ArrayMaximum(high,m,cross_bars);

         wv5_txt = StringFormat("WAVE 5 %d", i);
         ObjectCreate(chart_id, wv5_txt, OBJ_TEXT, 0, time[cross_bars_highest], high[cross_bars_highest]);
         ObjectSetString(chart_id, wv5_txt, OBJPROP_TEXT, "WV5");
         ObjectSetInteger(chart_id, wv5_txt, OBJPROP_COLOR, clrBlue);

         break;
        }
     }

解释:

本代码段中识别并标记了沃尔夫波浪形态的第 5 浪。该算法确定突破点和反转点之间的柱线数量,以确定价格突破第 1 至 3 浪趋势线并反转回落到其下方后,第 5 浪预计发生的时机。然后,代码从索引 m 开始,使用 ArrayMaximum() 函数遍历 cross_bars 中的柱形数量,以搜索 high[] 价格数组。该函数返回该范围内的最高值索引,即第 5 浪的峰值。变量 cross_bars_highest 用于保存此索引。

为了将这个已知的最高点指定为“WV5”,代码最后在图表上生成一个文本对象。标签的位置是根据 cross_bars_highest 索引处的日期和价格值确定的,颜色设置为蓝色,以类似于前几浪的标签。这样就完成了图表上的沃尔夫波浪结构,保证了第 5 浪在突破和反转之间的最高点得到了恰当的标记。

斐波那契扩展

你可能还记得,我们已经确定第 5 浪必须落在第 3 浪和第 4 浪产生的扩展的特定范围内。在第 3 浪和第 4 浪之间,这一范围通常为价格变化的 127.2% 至 161.8%。

示例:
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; // TIMEFRAME
input double max_fib_ext_wv12 = 161.8; // WAVE 1 AND 2 FIBO EXTENSION MAX LEVEL
input double min_fib_ext_wv12 = 127.2; // WAVE 1 AND 2 FIBO EXTENSION MIN LEVEL
input double max_fib_ext_wv34 = 120.0; // WAVE 3 AND 4 FIBO EXTENSION MAX LEVEL
input double min_fib_ext_wv34 = 200.0; // WAVE 3 AND 4 FIBO EXTENSION MIN LEVEL

string fib_ext_3_4;
double fib_ext_3_4_161_8;
double fib_ext_3_4_127_2;
string fib_ext_3_4_168_127;
string fib_ext_range_3_4;
int no_bars;
for(int l = k; l < bars_check - 7; l++)
  {
   if(IsSwingLow(low, l, 7) && time[l] > wv3_time)
     {
      wv4 = low[l];
      wv4_time = time[l];
      wv4_txt = StringFormat("WAVE 4 %d", l);

      wv3_wv4_bars = Bars(_Symbol, timeframe, wv3_time, wv4_time);
      wv3_highest_index = ArrayMaximum(high, k, wv3_wv4_bars);
      wv3_hh = high[wv3_highest_index];
      wv3_hh_t = time[wv3_highest_index];

      wv2_wv3_bars = Bars(_Symbol, timeframe, wv2_time, wv3_time);
      wv2_lowest_index = ArrayMinimum(low, j, wv2_wv3_bars);
      wv2_ll = low[wv2_lowest_index];
      wv2_ll_t = time[wv2_lowest_index];

      wv1_wv2_bars = Bars(_Symbol, timeframe, wv1_time, wv2_time);
      wv1_highest_index = ArrayMaximum(high, i, wv1_wv2_bars);
      wv1_hh = high[wv1_highest_index];
      wv1_hh_t = time[wv1_highest_index];

      wv1_wv2_size = MathAbs(wv1_hh - wv2_ll);
      wv1_wv2_70p = (wv1_wv2_size / 100) * 70;

      fib_ext_1_2_127_2 = MathAbs((((wv1_hh - wv2_ll) / 100) * (min_fib_ext_wv12 - 100)) + wv1_hh);
      fib_ext_1_2_161_8 = MathAbs((((wv1_hh - wv2_ll) / 100) * (max_fib_ext_wv12 - 100)) + wv1_hh);

      wv3_wv4_size = MathAbs(wv3_hh - wv4);

      if(wv3_wv4_size >= wv1_wv2_size && wv4 > wv2_ll && wv4 < wv3_hh && wv3_hh >= fib_ext_1_2_127_2 && wv3_hh <= fib_ext_1_2_161_8)
        {
         tline_1_3 = StringFormat("TREND LINE WAVE 1 AND 3 %d", i);
         if(ObjectCreate(chart_id,tline_1_3,OBJ_TREND,0,wv1_hh_t,wv1_hh,wv3_hh_t,wv3_hh))
           {
            ObjectSetInteger(chart_id, tline_1_3, OBJPROP_COLOR, clrBlue);
            ObjectSetInteger(chart_id, tline_1_3, OBJPROP_RAY_RIGHT, true);
            ObjectSetInteger(chart_id, tline_1_3, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
           }

         tline_2_4 = StringFormat("TREND LINE WAVE 2 AND 4 %d", i);
         if(ObjectCreate(chart_id,tline_2_4,OBJ_TREND,0,wv2_ll_t,wv2_ll,wv4_time,wv4))
           {
            ObjectSetInteger(chart_id, tline_2_4, OBJPROP_COLOR, clrBlue);
            ObjectSetInteger(chart_id, tline_2_4, OBJPROP_RAY_RIGHT, true);
            ObjectSetInteger(chart_id, tline_2_4, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
           }

         tline_1_4 = StringFormat("TREND LINE WAVE 1 AND 4 %d", i);
         if(ObjectCreate(chart_id,tline_1_4,OBJ_TREND,0,wv1_hh_t,wv1_hh,wv4_time,wv4))
           {
            ObjectSetInteger(chart_id, tline_1_4, OBJPROP_COLOR, clrBlue);
            ObjectSetInteger(chart_id, tline_1_4, OBJPROP_RAY_RIGHT, true);
            ObjectSetInteger(chart_id, tline_1_4, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
           }

         fib_ext_3_4 = StringFormat("FIB EXTENSION WAVE 3 AND 4 %d", i);
         fib_ext_3_4_127_2 = MathAbs((((wv3_hh - wv4) / 100) * (min_fib_ext_wv34 - 100)) + wv3_hh);
         fib_ext_3_4_161_8 = MathAbs((((wv3_hh - wv4) / 100) * (max_fib_ext_wv34 - 100)) + wv3_hh);
         fib_ext_3_4_168_127 = StringFormat("FIB EXTENSION wv3 wv4 %d", i);

         for(int m = l; m < bars_check - 2; m++)
           {
            t_1_3_values = ObjectGetValueByTime(chart_id, tline_1_3, time[m], 0);

            if(close[m] > open[m] && open[m] < t_1_3_values && close[m] > t_1_3_values && time[m] > time[l+4])
              {
               for(int n = m; n < bars_check - 1; n++)
                 {
                  t_1_3_values_2 = ObjectGetValueByTime(chart_id, tline_1_3, time[n], 0);
                  t_2_4_values = ObjectGetValueByTime(chart_id, tline_2_4, time[n], 0);

                  no_bars = Bars(_Symbol, timeframe, wv3_hh_t, time[n]);
                  cross_bars = Bars(_Symbol,timeframe,time[n], time[m]);
                  cross_bars_highest = ArrayMaximum(high,m,cross_bars);

                  if(close[n] < open[n] && open[n] > t_1_3_values_2 && close[n] < t_1_3_values_2 && no_bars < 100
                     && time[n] > time[m]
                     && high[cross_bars_highest] >= fib_ext_3_4_127_2 &&  high[cross_bars_highest] <= fib_ext_3_4_161_8)
                    {
                     ObjectCreate(chart_id, wv1_txt, OBJ_TEXT, 0, wv1_hh_t, wv1_hh);
                     ObjectSetString(chart_id, wv1_txt, OBJPROP_TEXT, "WV1");
                     ObjectSetInteger(chart_id, wv1_txt, OBJPROP_COLOR, clrBlue);

                     ObjectCreate(chart_id, wv2_txt, OBJ_TEXT, 0, wv2_ll_t, wv2_ll);
                     ObjectSetString(chart_id, wv2_txt, OBJPROP_TEXT, "WV2");
                     ObjectSetInteger(chart_id, wv2_txt, OBJPROP_COLOR, clrBlue);

                     fib_ext_wv1_wv2 = StringFormat("FIBO EXTENSION WAVE 1 AND 2 %d", i);

                     ObjectCreate(chart_id, fib_ext_wv1_wv2, OBJ_EXPANSION, 0, wv2_ll_t, wv2_ll, wv1_hh_t, wv1_hh, wv2_ll_t, wv2_ll);

                     ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_COLOR, clrBlue);

                     for(int i = 0; i <= 2; i++)
                       {
                        ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_LEVELCOLOR, i, clrBlue);
                       }

                     perc_70 = StringFormat("70 PERCENT %d", j);
                     ObjectCreate(chart_id, perc_70, OBJ_TREND, 0, wv1_hh_t, wv1_hh - wv1_wv2_70p, wv2_time, wv1 - wv1_wv2_70p);

                     fib_ext_range = StringFormat("Fibo EXPENSION RANGE%d", j);
                     ObjectCreate(chart_id, fib_ext_range, OBJ_RECTANGLE, 0, wv1_hh_t, fib_ext_1_2_161_8, wv2_ll_t, fib_ext_1_2_127_2);

                     ObjectCreate(chart_id, wv3_txt, OBJ_TEXT, 0, wv3_hh_t, wv3_hh);
                     ObjectSetString(chart_id, wv3_txt, OBJPROP_TEXT, "WV3");
                     ObjectSetInteger(chart_id, wv3_txt, OBJPROP_COLOR, clrBlue);

                     ObjectCreate(chart_id, wv4_txt, OBJ_TEXT, 0, wv4_time, wv4);
                     ObjectSetString(chart_id, wv4_txt, OBJPROP_TEXT, "WV4");
                     ObjectSetInteger(chart_id, wv4_txt, OBJPROP_COLOR, clrBlue);

                     tline_1_3_visible = StringFormat("TREND LINE WAVE 1 AND 3 V %d", i);
                     ObjectCreate(chart_id,tline_1_3_visible,OBJ_TREND,0,wv1_hh_t,wv1_hh,time[n],t_1_3_values_2);
                     ObjectSetInteger(chart_id, tline_1_3_visible, OBJPROP_COLOR, clrBlue);

                     tline_2_4_visible = StringFormat("TREND LINE WAVE 2 AND 4 V %d", i);
                     ObjectCreate(chart_id,tline_2_4_visible,OBJ_TREND,0,wv2_ll_t,wv2_ll,time[n],t_2_4_values);
                     ObjectSetInteger(chart_id, tline_2_4_visible, OBJPROP_COLOR, clrBlue);

                     wv5_txt = StringFormat("WAVE 5 %d", i);
                     ObjectCreate(chart_id, wv5_txt, OBJ_TEXT, 0, time[cross_bars_highest], high[cross_bars_highest]);
                     ObjectSetString(chart_id, wv5_txt, OBJPROP_TEXT, "WV5");
                     ObjectSetInteger(chart_id, wv5_txt, OBJPROP_COLOR, clrBlue);

                     ObjectCreate(chart_id, fib_ext_3_4,OBJ_EXPANSION, 0,wv4_time, wv4,wv3_hh_t,wv3_hh,wv4_time,wv4);
                     for(int i = 0; i <= 2; i++)
                       {
                        ObjectSetInteger(chart_id,fib_ext_3_4,OBJPROP_LEVELCOLOR,i,clrBlue);
                        ObjectSetInteger(chart_id,fib_ext_3_4,OBJPROP_COLOR,clrBlue);
                       }

                     fib_ext_range_3_4 =  StringFormat("Fibo EXPENSION RANGE WV3 WV4 %d", i);
                     ObjectCreate(chart_id,fib_ext_range_3_4,OBJ_RECTANGLE,0,wv3_hh_t,fib_ext_3_4_127_2,time[cross_bars_highest],fib_ext_3_4_161_8);

                     break;
                    }
                 }
               break;
              }
           }
        }
      break;
     }
  }

输出:

图 14. 第 4 浪和第 5 浪的扩展

解释:

它采用了第 3 波和第 4 波的斐波那契扩展。通过计算斐波那契水平并计算第 3 浪与价格收于第 1 至 3 浪趋势线下方之间的柱数,可以确保该形态在结构上是合理且紧凑的。该 EA 会寻找条件块内的特定看跌烛形,该烛形突破了第 1 浪至第 3 浪趋势线,然后收盘价又跌破了该趋势线。根据进一步的参数,反转必须在适当的柱形数量内(少于 100 根)发生,并且突破和反转之间的最高价格必须落在允许的斐波那契扩展范围内。如果满足这些条件,第 5 浪或许是合格的。

然后使用第 3 浪和第 4 浪作为基础腿来绘制斐波那契扩展对象。为了便于查看,所有三个扩展级别(0、127.2 和 161.8)均为蓝色。为了在视觉上强调斐波那契目标区域,还画了一个矩形。至关重要的是,只有这个条件块才能生成所有相关的图表对象,例如扩展区域、标签和趋势线。这样可以降低误报率,提高沃尔夫波浪检测逻辑的可靠性,因为它保证只有在所有规则都满足后才会标记完整的模式。

绘制第 1 浪至第 4 浪趋势线

在让 EA 执行交易之前,我们需要画出连接第 1 浪和第 4 浪的趋势线。这条趋势线在沃尔夫波浪形态中起着至关重要的作用,尤其是在制定退出计划方面。
int no_wv1_n_bars;
int no_n_c_bars;
string tline_1_4_visible;
double t_1_4_values;
string tline_1_4_visible_2;
if(close[n] < open[n] && open[n] > t_1_3_values_2 && close[n] < t_1_3_values_2 && no_bars < 100
   && time[n] > time[m]
   && high[cross_bars_highest] >= fib_ext_3_4_127_2 &&  high[cross_bars_highest] <= fib_ext_3_4_161_8)
{

 ObjectCreate(chart_id, wv1_txt, OBJ_TEXT, 0, wv1_hh_t, wv1_hh);
 ObjectSetString(chart_id, wv1_txt, OBJPROP_TEXT, "WV1");
 ObjectSetInteger(chart_id, wv1_txt, OBJPROP_COLOR, clrBlue);

 ObjectCreate(chart_id, wv2_txt, OBJ_TEXT, 0, wv2_ll_t, wv2_ll);
 ObjectSetString(chart_id, wv2_txt, OBJPROP_TEXT, "WV2");
 ObjectSetInteger(chart_id, wv2_txt, OBJPROP_COLOR, clrBlue);

 fib_ext_wv1_wv2 = StringFormat("FIBO EXTENSION WAVE 1 AND 2 %d", i);

 ObjectCreate(chart_id, fib_ext_wv1_wv2, OBJ_EXPANSION, 0, wv2_ll_t, wv2_ll, wv1_hh_t, wv1_hh, wv2_ll_t, wv2_ll);

 ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_COLOR, clrBlue);

 for(int i = 0; i <= 2; i++)
 {
  ObjectSetInteger(chart_id, fib_ext_wv1_wv2, OBJPROP_LEVELCOLOR, i, clrBlue);
 }

 perc_70 = StringFormat("70 PERCENT %d", j);
 ObjectCreate(chart_id, perc_70, OBJ_TREND, 0, wv1_hh_t, wv1_hh - wv1_wv2_70p, wv2_time, wv1 - wv1_wv2_70p);

 fib_ext_range = StringFormat("Fibo EXPENSION RANGE%d", j);
 ObjectCreate(chart_id, fib_ext_range, OBJ_RECTANGLE, 0, wv1_hh_t, fib_ext_1_2_161_8, wv2_ll_t, fib_ext_1_2_127_2);

 ObjectCreate(chart_id, wv3_txt, OBJ_TEXT, 0, wv3_hh_t, wv3_hh);
 ObjectSetString(chart_id, wv3_txt, OBJPROP_TEXT, "WV3");
 ObjectSetInteger(chart_id, wv3_txt, OBJPROP_COLOR, clrBlue);

 ObjectCreate(chart_id, wv4_txt, OBJ_TEXT, 0, wv4_time, wv4);
 ObjectSetString(chart_id, wv4_txt, OBJPROP_TEXT, "WV4");
 ObjectSetInteger(chart_id, wv4_txt, OBJPROP_COLOR, clrBlue);

 tline_1_3_visible = StringFormat("TREND LINE WAVE 1 AND 3 V %d", i);
 ObjectCreate(chart_id,tline_1_3_visible,OBJ_TREND,0,wv1_hh_t,wv1_hh,time[n],t_1_3_values_2);
 ObjectSetInteger(chart_id, tline_1_3_visible, OBJPROP_COLOR, clrBlue);

 tline_2_4_visible = StringFormat("TREND LINE WAVE 2 AND 4 V %d", i);
 ObjectCreate(chart_id,tline_2_4_visible,OBJ_TREND,0,wv2_ll_t,wv2_ll,time[n],t_2_4_values);
 ObjectSetInteger(chart_id, tline_2_4_visible, OBJPROP_COLOR, clrBlue);

 wv5_txt = StringFormat("WAVE 5 %d", i);
 ObjectCreate(chart_id, wv5_txt, OBJ_TEXT, 0, time[cross_bars_highest], high[cross_bars_highest]);
 ObjectSetString(chart_id, wv5_txt, OBJPROP_TEXT, "WV5");
 ObjectSetInteger(chart_id, wv5_txt, OBJPROP_COLOR, clrBlue);

 ObjectCreate(chart_id, fib_ext_3_4,OBJ_EXPANSION, 0,wv4_time, wv4,wv3_hh_t,wv3_hh,wv4_time,wv4);
 for(int i = 0; i <= 2; i++)
 {
  ObjectSetInteger(chart_id,fib_ext_3_4,OBJPROP_LEVELCOLOR,i,clrBlue);
  ObjectSetInteger(chart_id,fib_ext_3_4,OBJPROP_COLOR,clrBlue);
 }

 fib_ext_range_3_4 =  StringFormat("Fibo EXPENSION RANGE WV3 WV4 %d", i);
 ObjectCreate(chart_id,fib_ext_range_3_4,OBJ_RECTANGLE,0,wv3_hh_t,fib_ext_3_4_127_2,time[cross_bars_highest],fib_ext_3_4_161_8);

 no_wv1_n_bars =  Bars(_Symbol, timeframe, wv1_hh_t, time[n]);
 no_n_c_bars = Bars(_Symbol, timeframe, time[n], TimeCurrent());

 if(no_n_c_bars <= no_wv1_n_bars)
 {
  t_1_4_values =  ObjectGetValueByTime(chart_id, tline_1_4, TimeCurrent(), 0);
  tline_1_4_visible = "TL WAVE 1 AND 4 Visible";

  ObjectCreate(chart_id,tline_1_4_visible,OBJ_TREND,0,wv1_hh_t,wv1_hh,TimeCurrent(),t_1_4_values);
  ObjectSetInteger(chart_id,tline_1_4_visible,OBJPROP_STYLE,STYLE_DASH);
  ObjectSetInteger(chart_id,tline_1_4_visible,OBJPROP_COLOR,clrBlue);
 }

 if(no_n_c_bars > no_wv1_n_bars)
 {
  ObjectDelete(chart_id,tline_1_4_visible);

  t_1_4_values =  ObjectGetValueByTime(chart_id, tline_1_4, time[n + no_wv1_n_bars], 0);
  tline_1_4_visible_2 = StringFormat("TL WAVE 1 AND 4 DISPLAY %d", i);

  ObjectCreate(chart_id,tline_1_4_visible_2,OBJ_TREND,0,wv1_hh_t,wv1_hh,time[n+no_wv1_n_bars],t_1_4_values);
  ObjectSetInteger(chart_id,tline_1_4_visible_2,OBJPROP_STYLE,STYLE_DASH);
  ObjectSetInteger(chart_id,tline_1_4_visible_2,OBJPROP_COLOR,clrBlue);
}

break;
}

解释:

使用变量 no_wv1_n_bars 和 no_n_c_bars 确定两个重要地点之间的柱形数量。第 1 浪的时间戳与市场反转(即跌破趋势线)的时间戳之间的柱形数量,由 no_wv1_n_bars 函数具体确定。相反,no_n_c_bars 计算的是图表上当前柱与反转点(第 5 浪)之间的柱数。这种比较有助于确定第 1 浪到第 4 浪的趋势线应该到达固定的未来柱形还是当前市场。

如果当前柱形仍在第 1 浪和反转点(no_n_c_bars <= no_wv1_n_bars)定义的范围内,则在第 1 浪和当前市场时间之间形成一条标记为“TL WAVE 1 AND 4 Visible”的趋势线。ObjectGetValueByTime 用于检索趋势线的当前值,为了便于观察,该线以蓝色虚线显示。

但是,如果反转点之后的柱数大于第 1 浪和反转点之间的范围,则可以使用 ObjectDelete 删除先前绘制的线。为了确保它覆盖与初始预测相同的距离,在这种情况下构建了一条新的趋势线,从第 1 浪延伸到未来的确定点(time[n + no_wv1_n_bars])。这条新线有一个动态标签,并以蓝色和虚线作为装饰。该算法在根据图表实时更新进行明显调整的同时,保证了第 1 浪到第 4 浪的趋势线保持不变且方向明确。

交易执行

截至目前,我们已经成功识别出沃尔夫波浪形态的全部五个波浪,即第 1 浪至第 5 浪,以及连接第 1 浪至第 3 浪、第 2 浪至第 4 浪和第 1 浪至第 4 浪的三条关键趋势线。除了提供对形态的直观确认外,这些趋势线还可以作为我们交易执行决策的参考。

接下来就是按照预先确定的结构执行交易。看跌沃尔夫波浪形态的入场条件与价格如何围绕第 1 浪到第 3 浪趋势线波动密切相关。具体来说,烛形必须先向上穿过这条趋势线,表明上涨行情可能即将结束,然后再向下穿过这条趋势线。第二次走势是建立卖单的好时机,因为它证实了市场可能开始反转下跌。在完成这两项确认之前,EA 不应发起交易。  

示例:

#include <Trade/Trade.mqh>
CTrade trade;
input int MagicNumber = 6160;
input double lot_size = 0.01;

input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; // TIMEFRAME
input double max_fib_ext_wv12 = 161.8; // WAVE 1 AND 2 FIBO EXTENSION MAX LEVEL
input double min_fib_ext_wv12 = 127.2; // WAVE 1 AND 2 FIBO EXTENSION MIN LEVEL
input double max_fib_ext_wv34 = 200.0; // WAVE 3 AND 4 FIBO EXTENSION MAX LEVEL
input double min_fib_ext_wv34 = 120.0; // WAVE 3 AND 4 FIBO EXTENSION MIN LEVEL
datetime time_exe[];
datetime lastTradeBarTime = 0;
double ask_price;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   trade.SetExpertMagicNumber(MagicNumber);
   ArraySetAsSeries(time_exe,true);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

   total_symbol_bars = Bars(_Symbol, timeframe);


   time_bar = iTime(_Symbol,timeframe,0);


   CopyOpen(_Symbol, timeframe, time_bar, bars_check, open);
   CopyClose(_Symbol, timeframe, time_bar, bars_check, close);
   CopyLow(_Symbol, timeframe, time_bar, bars_check, low);
   CopyHigh(_Symbol, timeframe, time_bar, bars_check, high);
   CopyTime(_Symbol, timeframe, time_bar, bars_check, time);

   CopyTime(_Symbol, timeframe, 0, 2, time_exe);
   datetime currentBarTime = iTime(_Symbol, timeframe, 0);
   ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);

                                              
if(time[n] == time_exe[1]  && currentBarTime != lastTradeBarTime)
  {

   trade.Sell(lot_size,_Symbol,ask_price, high[cross_bars_highest],wv2_ll);

   lastTradeBarTime = currentBarTime;

  }

输出:

图 15. 交易执行

解释:

内置的 MQL5 交易库在第一行 #include 中导入,使 CTrade 类能够访问交易管理功能。引入此库后,将创建一个名为 trade 的 CTrade 实例。该对象执行买入、卖出、设定止盈和止损、维护订单等交易指令。MagicNumber 和 lot_size 是接下来定义的两个输入变量。该 EA 的交易通过其 MagicNumber 进行唯一标识,这使得将它们与同一账户上可能进行的其他交易区分开来变得简单。lot_size(在本例中为 0.01 手)表示将要执行的交易量或规模。

然后,声明了一些变量:time_exe[]、lastTradeBarTime 和 ask_price。time_exe 数组将存储最近几根柱形的时间数据。lastTradeBarTime 初始化为零,用于防止 EA 在同一根柱形执行多笔交易。ask_price 用于存储当前交易品种的卖价,即可以执行卖出交易的价格。trade.SetExpertMagicNumber(MagicNumber); 这行代码将幻数赋值给交易对象,以便以后可以识别它进行的每一笔交易。ArraySetAsSeries(time_exe, true); 将 time_exe 数组设置为时间序列,这意味着索引 0 对应于最近的柱形。

接下来,使用 CopyTime(_Symbol, timeframe, 0, 2, time_exe); 将最后两个柱的时间值复制到 time_exe 数组中。然后,datetime currentBarTime = iTime(_Symbol, timeframe, 0); 获取当前正在形成的柱形的开盘时间。ask_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); 这行代码获取交易工具的当前卖价,这是下卖单所必需的。

现在,条件 if (time[n] == time_exe[1] && currentBarTime != lastTradeBarTime) 检查 time[n] 中存储的时间是否与倒数第二个柱的开盘时间 (time_exe[1]) 相匹配,并通过与 lastTradeBarTime 进行比较来确保当前柱上尚未执行交易。如果此条件为 true,EA 将使用 trade.Sell() 执行卖出交易。它以当前的卖价开仓,止损位设置得太高[cross_bars_highest](这可能代表第 5 浪的顶部),止盈位设置到 wv2_ll(第 2 浪的底部,可能是目标水平)。最后,将 lastTradeBarTime 更新为当前柱形时间,以防止在同一根柱形内出现多次入场。

请注意我们清单上的最后一个关键项目:确保交易顺利退出。根据沃尔夫波浪形态执行交易后,我们需要实现逻辑,以便在市场触及连接第 1 浪和第 4 浪的趋势线时自动平仓。这条线是我们的退出目标,因为理论上,价格预计会在该区域附近反转。 除此之外,我们还必须建立防止持仓时间过长的保护机制。如果价格在合理的柱形数量内没有达到趋势线,EA 也应该平仓。这有助于我们在形态失效或市场条件发生变化时避免不必要的风险和损失。

示例:
double low_m[];
double high_m[];
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   trade.SetExpertMagicNumber(MagicNumber);
   ArraySetAsSeries(time_exe,true);

   ArraySetAsSeries(low_m,true);
   ArraySetAsSeries(high_m,true);

//---
   return(INIT_SUCCEEDED);
  }
CopyLow(_Symbol, timeframe, 0, 3, low_m);
CopyHigh(_Symbol, timeframe, 0, 3, high_m);
if(no_n_c_bars <= no_wv1_n_bars)
  {

   t_1_4_values =  ObjectGetValueByTime(chart_id, tline_1_4, TimeCurrent(), 0);
   tline_1_4_visible = "TL WAVE 1 AND 4 Visible";

   ObjectCreate(chart_id,tline_1_4_visible,OBJ_TREND,0,wv1_hh_t,wv1_hh,TimeCurrent(),t_1_4_values);
   ObjectSetInteger(chart_id,tline_1_4_visible,OBJPROP_STYLE,STYLE_DASH);
   ObjectSetInteger(chart_id,tline_1_4_visible,OBJPROP_COLOR,clrBlue);

   if(time[n] == time_exe[1]  && currentBarTime != lastTradeBarTime)
     {

      trade.Sell(lot_size,_Symbol,ask_price, high[cross_bars_highest],wv2_ll);

      lastTradeBarTime = currentBarTime;

     }

   for(int i = 0; i < PositionsTotal(); i++)
     {
      ulong ticket = PositionGetTicket(i);
      datetime positionTime = 0;

      if(PositionSelectByTicket(ticket))
        {

         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id))
           {

            positionTime = (datetime)PositionGetInteger(POSITION_TIME);


            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
              {

               double pos_bars = Bars(_Symbol, timeframe, positionTime, TimeCurrent());


               if((ask_price < t_1_4_values || low_m[1] < t_1_4_values || no_n_c_bars == no_wv1_n_bars) && no_n_c_bars == pos_bars+1)
                 {

                  trade.PositionClose(ticket);

                 }
              }
           }
        }
     }
  }

if(no_n_c_bars > no_wv1_n_bars)
  {

   ObjectDelete(chart_id,tline_1_4_visible);

   t_1_4_values =  ObjectGetValueByTime(chart_id, tline_1_4, time[n + no_wv1_n_bars], 0);
   tline_1_4_visible_2 = StringFormat("TL WAVE 1 AND 4 DISPLAY %d", i);

   ObjectCreate(chart_id,tline_1_4_visible_2,OBJ_TREND,0,wv1_hh_t,wv1_hh,time[n+no_wv1_n_bars],t_1_4_values);
   ObjectSetInteger(chart_id,tline_1_4_visible_2,OBJPROP_STYLE,STYLE_DASH);
   ObjectSetInteger(chart_id,tline_1_4_visible_2,OBJPROP_COLOR,clrBlue);

  }

输出:

图 16. 平仓交易

解释:

代码的第一部分声明了两个数组:double low_m[]; 和 double high_m[];。这些数组保存了图表中最近的最低价和最高价。与 MetaTrader 5 的典型图表行为类似,ArraySetAsSeries(low_m, true); 和 ArraySetAsSeries(high_m, true); 这几行代码反转了数组的索引,使得索引 0 始终与最近的柱形相关。将近期或当前价格水平与重要趋势线值进行比较需要这样做。

然后,使用 CopyLow(_Symbol, timeframe, 0, 3, low_m); 和 CopyHigh(_Symbol, timeframe, 0, 3, high_m); 方法,将图表中的最后三个最低价和最高价分别添加到 low_m 和 high_m 数组中。在这些点位,将对价格进行分析,以确定其是否触及或跌破特定趋势线,例如第 1 浪至第 4 浪趋势线。

如果该仓位是卖出交易(这是沃尔夫波浪形态在看跌设置中要求的),则代码会使用 Bars(_Symbol, timeframe, positionTime, TimeCurrent()) 计算自开仓以来已经过去了多少根柱形。最后,还有一个条件检查市场是否已到达退出点。具体来说,它会评估:

  • 当前卖价低于第 1 至 4 浪趋势线(t_1_4_values),或者
  • 前一根柱形的最低价(low_m[1])已经跌破了该趋势线,或者
  • 自入场以来的柱形数量等于达到第 1 浪至第 4 浪趋势线所需的柱形数量(no_n_c_bars == no_wv1_n_bars),并且
  • 该仓位已持有该数量的柱形(no_n_c_bars == pos_bars + 1)

如果满足所有这些条件,EA 就会使用该条件来平仓,因为它表明交易要么已经达到预期的退出点,要么已经持有了过长的时间。trade.PositionClose(ticket); 为了确保 EA 将未平仓交易与最新的沃尔夫波浪形态正确关联起来,使用了条件 no_n_c_bars == pos_bars + 1。该 EA 交易的逻辑是,机器人会在预定数量的柱形图上搜索多个沃尔夫波浪形态。但是,只有当 time[n] == time_exe[1] 时才会进行交易。这样可以保证交易会在最新形态得到验证的同时进行。

pos_bars 变量表示自交易开仓以来经过的柱线数量,该变量的计算是为了保持信号与执行之间的一致性。相反,no_n_c_bars 决定了当前时间与沃尔夫波浪确认点 (time[n]) 之间经过的柱形数量。如果这两个值一致,即 no_n_c_bars == pos_bars + 1,则 EA 会被告知该交易是由此特定形态发起的,而不是由先前的形态发起的。

这一点至关重要,因为即使 EA 在图表上看到多个沃尔夫波浪,它也应该只处理并平仓与最新沃尔夫波浪相匹配的交易。如果没有这项要求,机器人可能会无意中停止或干扰先前信号的交易。EA 通过验证这种一致性,保证交易处理的准确性,防止信号冲突,并在执行和风险管理中保持行为的一致性。


识别看涨的沃尔夫波浪形态

看涨的沃尔夫波浪形态与我们之前讨论的看跌的沃尔夫波浪形态正好相反。由于结构和逻辑非常相似,我们就不赘述每个细节了。相反,我们将只突出显示相反或不同的部分。在这种情况下,我们正在寻找一个五浪结构,它暗示着潜在的买入机会,而不是卖出机会。

第 1 浪,即看涨沃尔夫浪的第一个底部,必须是波动低点。在不跌破第 1 浪的情况下,第 3 浪应该在第 2 浪之后产生另一个低点,而第 2 浪必然是一个高点。看涨形态必须由第 5 浪形成最终低点,第 4 浪形成高点(通常低于第 2 浪)才能完成。

第 1 至 3 浪必须是相邻浪之间的最低点或最高点,而第 5 浪应该落在第 3 浪和第 4 浪的 127.2% 和 161.8% 斐波那契扩展之间。用户可以通过改变这些范围来改变模式检测的灵敏度。为了提高入场可靠性,价格必须先跌破第 1 浪和第 3 浪之间的趋势线,然后再向上突破该趋势线。第 1 至 4 浪趋势线经常被用作止盈目标。系统会自动绘制连接第 1 至 3 浪、第 2 至 4 浪和第 1 至 4 浪的重要趋势线,以帮助识别形态和制定交易退出计划。

图 17. 多头仓位


结论

本文介绍了我们如何在 MQL5 中创建一个能够识别看涨和看跌沃尔夫波浪形态的 EA 交易。我们讲解了如何利用波动高点和低点来识别五浪结构,应用斐波那契扩展位,创建重要的趋势线,以及只有在所有要求都满足时才进行交易。虽然 127.2% 到 161.8% 是典型的斐波那契数列范围,但个人可以根据自己的计划调整这些数字。除了实现复杂图表形态的自动化之外,该项目还为开发更复杂的、基于形态的交易系统提供了坚实的基础。 

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18884

附加的文件 |
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
您应当知道的 MQL5 向导技术(第 62 部分):结合 ADX 与 CCI 形态的强化学习 TRPO 您应当知道的 MQL5 向导技术(第 62 部分):结合 ADX 与 CCI 形态的强化学习 TRPO
ADX 振荡器和 CCI 振荡器是趋势跟踪和动量指标,可在开发智能系统时配对。我们延续上一篇文章未竟的话题,实证如何得益于强化学习来实际运用训练、并更新我们已开发的模型。我们正在使用的算法尚未在本系列中涵盖,其称为可信区域政策优化。一如既往,由 MQL5 向导汇编的智能系统令我们能够更快地搭建测试模型,且可配合不同类型信号进行测试、并派发。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
MQL5交易策略自动化(第二十部分):基于CCI和AO指标的多品种策略 MQL5交易策略自动化(第二十部分):基于CCI和AO指标的多品种策略
在本文中,我们将构建一个基于商品通道指数(CCI)和动量震荡指标(AO)的多品种交易策略,用于识别趋势反转。内容涵盖策略设计、MQL5实现及回测过程。文末还将提供优化策略性能的建议。