简介
当然,每位交易者都清楚,峰谷指标旨在对给定或更大幅度的价格波动进行分析。峰谷线是一条折线,节点分别位于价格图表的最高价和最低价处。
该指标有许多变体:1、2,、3、4、5、6、7、8、9、10、11、12、13、14、15、16。然而,也有大量的 MQL5 程序开发者热衷于创建自己的“理想”峰谷。峰谷指标的主要缺点在于延迟、问题节点(外柱)的不正确标记以及差强人意的性能。
以我看来,最好的峰谷实施,是由 Yuri Kulikov (Yurich) 提出的。此外,还有一些非常不错的 MQL4 文章,比如《Layman’s Notes:ZigZag…》和《Show Must Go On, or Once Again about ZigZag》。该主题似乎已有充分的发掘,可参考的相关出版物亦林林总总。但它还是有种说不清的吸引力。现在,它也勾起了我的兴趣,尤其是在创建高级峰谷指标的可能性方面。
本文讲述的,是利用轨道线指标创建高级峰谷的方法。假设可以找到一系列轨道线输入参数的一种特定组合,大部分峰谷节点均可借此处于轨道线带的界限之中。
创建高级峰谷指标的方法
我们将设定一个目标:找到两个节点的坐标 – 当前与预测节点(图 1)。所谓当前节点,就是指尚未完成、仍在搜索或调整其坐标的节点。而且,它始终都在当前(零)柱上。而在未来状态下,预测节点必须显示下一个峰谷节点的估测价位。
图 1. 预测新峰谷节点:当前节点与下一节点。
如此一来,目标已设定,我们也对如何将移动平均线轨道线作为建立高级指标的基础使用有了了解(图 2)。我们会搜索与峰谷节点偏差最小的轨道线。峰谷指标的高峰与低谷轨道线必须分别搜索,似乎也合情合理。
图 2. 峰谷指标与移动平均线轨道线
为加大预测的统计意义,我们不再只使用1个甚至10个轨道线指标,而要采用一个拥有 100 或更多带有不同输入数据的指标池。在主指标线平均周期和价格使用(高峰为高,低谷为低)方面,它们会有所区别。我们引入下述标记和公式:
- ZZ – 峰谷指标;
- ENV – 轨道线指标的主线(与 iMA 指标一致);
- Envelopes(i) – i’th 柱上轨道线指标主线的值;
- ZZ(High) – 峰谷高峰值;
- ZZ(Low) – 峰谷低谷值;
- ENV(High) – 对应某峰谷高峰的轨道线指标的主线的值;
- ENV(Low) – 对应某峰谷低谷的轨道线指标的主线的值;
- n_high – 峰谷的高峰数量;
- n_low – 峰谷的低谷数量;
我们有两个指标池:一个用于高峰,另一个用于低谷(每个约有 100 个指标)。我们会计算峰谷节点与池中每个指标的轨道线指标主线的偏差,并利用上述公式,找到每个池指标偏差的算术平均值。下图所示,是关于识别节点 ZZ 与 一个指标的主线 ENV 的偏差图。
图 3. ZZ 节点与 ENV 的偏差图。
此偏差算术平均值将被用于确定为绘制轨道线带,轨道线指标主线应被移动到的价位。所以,我们需要与峰谷高峰偏差的算术平均值来绘制上轨线,且需要与峰谷低谷偏差的算术平均值来绘制轨道线指标的下轨线。
这就是我们将用来查找特征点和预测峰谷节点的轨道线上轨与下轨线。我们又对由一组轨道线指标构成的轨道线池产生了兴趣。峰谷节点与某个给定轨道线主线的偏差的算术平均值,都针对每个指标计算出来。在图表中标绘作为结果的池线(上轨和下轨线)后,我们就能够看到下述内容了:
图 4. 平面上的轨道线
如果我们假设每条线都位于一个独立的平面上(尽管它们一起创建了一个平面),上图只会在价格图表平面上显示每个指标的投射。这些线的 3D 图像大致如下:
图 5. 3D 呈现的轨道线
我们现在来快速了解一下几何学。想像轨道线指标的线池是一个 3D 平面。将一个平面垂直放到价格图表上,并于当前(零)柱处切割平面。
结果,我们得到了呈一条曲线的表面横截面(上图所示是曲线成为直线的一种特殊情况)。要做预测,有曲线上每个点的坐标就足够了,而且它们还会被用于进一步的计算当中。
我们将需要下述横截面特征:最大与最小点,以及横截面的重心(所有点值的算术平均值)。获得的特征点将被投向到当前(零)柱上,相关数据则被存储于历史中。这些特征点将充当当前与下一个峰谷节点的基础。
由于包络带搜索分高峰与低谷执行,所以我们要获取两个横截面:一个为高峰,另一个为低谷。
要获取预测,我们将使用最近的特征点。比如说,搜索某峰谷高峰时,我们取轨道线指标上轨线与某个截平面交叉所产生的横截面的特征点。相反,搜索某低谷时,我们则取轨道线指标下轨线与某个截平面交叉所产生的横截面的特征点。
测试新指标
完成了方法的定义,现在我们来创建指标。我们首先会找到峰谷指标的最后节点,并在图表中完成绘制。为此,我们将采用专为手头任务而编写的 AdvancedZigZag 类:
//+------------------------------------------------------------------+ //| AdvancedZigZag.mqh | //| Copyright 2013, DC2008 | //| https://www.mql5.com/ru/users/DC2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2013, DC2008" #property link "https://www.mql5.com/ru/users/DC2008" #property version "1.00" //+------------------------------------------------------------------+ //| GetExtremums.mqh | //+------------------------------------------------------------------+ #include <GetExtremums.mqh> // author of the code Yurich #property copyright "Copyright 2012, Yurich" #property link "https://www.mql5.com/ru/users/Yurich" //+------------------------------------------------------------------+ //| ZigZag node structure | //+------------------------------------------------------------------+ struct MqlZigZag { double price; // Node coordinate datetime t; // Time }; //+------------------------------------------------------------------+ //| The AdvancedZigZag class | //+------------------------------------------------------------------+ class AdvancedZigZag { private: MqlRates rt[]; dextremum zz[]; int history; double amplitude; public: dextremum zHL[]; MqlZigZag zzH[],zzL[]; int Count(const double range); int Read(const int nodes); AdvancedZigZag(const int bars); ~AdvancedZigZag(); }; //+------------------------------------------------------------------+ //| Class constructor | //+------------------------------------------------------------------+ AdvancedZigZag::AdvancedZigZag(const int bars) { history=bars; amplitude=0; } //+------------------------------------------------------------------+ //| The Read method of the class | //+------------------------------------------------------------------+ int AdvancedZigZag::Read(const int nodes) { CopyRates(NULL,0,TimeCurrent(),history,rt); int cnt=GetExtremums(amplitude,rt,zHL,nodes); return(cnt); } //+------------------------------------------------------------------+ //| The Count method of the class | //+------------------------------------------------------------------+ int AdvancedZigZag::Count(const double range) { amplitude=range; CopyRates(NULL,0,TimeCurrent(),history,rt); int cnt=GetExtremums(amplitude,rt,zz); ArrayResize(zzH,cnt); ArrayResize(zzL,cnt); int h=0; int l=0; for(int i=0; i<cnt; i++) { if(zz[i].type>0) { zzH[h]=(MqlZigZag)zz[i]; h++; } else { zzL[l]=(MqlZigZag)zz[i]; l++; } } ArrayResize(zzH,h); ArrayResize(zzL,l); return(cnt); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ AdvancedZigZag::~AdvancedZigZag() { }
共有两种方法:
- 计数法会找到某给定时间周期(柱数)内的所有峰谷节点,并将其保存到各种数组中,实现高峰与低谷的区分。如此一来,轨道线的分析和计算就更简单了;
- 读取法会找到最后的节点,并将其保存到一个单一数组中。我们需要此方法来实现峰谷指标可视化;
GetExtremums 库(由 Yury Kulikov 提供)在搜索节点时亦不可或缺。
我们将该指标放到一个 EA 交易中研究一下。为什么是 EA 交易、而不是指标呢?这当然是个人口味问题,但对我来讲,这种方式似乎更高效。毫无疑问,EA 交易的图形功能是弱了些,但我们收获的却是性能。因为相同交易品种的指标都在一个单一数据流中运行,而每个 EA 都在于自己独立的数据流中运行。我们来看看代码:
//+------------------------------------------------------------------+ //| two_Comets.mq5 | //| Copyright 2013, DC2008 | //| https://www.mql5.com/ru/users/DC2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2013, DC2008" #property link "https://www.mql5.com/ru/users/DC2008" #property version "1.00" #include <AdvancedZigZag.mqh> //--- Depth of history for the indicator calculation input int depth_stories=5000; // Depth stories for calculating the indicator [bars] //--- Minimum ZigZag amplitude value input int amplitude=100; // The minimum value of the amplitude of the indicator [points] //--- Declaring the class AdvancedZigZag Azz(depth_stories); //--- #define NUMBER_MA 227 #define START_MA 5 //--- macros #define SIZE(i) (double)i*0.3<1?1:(int)(i*0.25) #define ObjF1 ObjectSetString(0,name,OBJPROP_FONT,"Wingdings") #define ObjF2 ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_CENTER) #define ObjF3(T) ObjectSetInteger(0,name,OBJPROP_TIME,T) #define ObjF4(P) ObjectSetDouble(0,name,OBJPROP_PRICE,P) #define ObjF5(size) ObjectSetInteger(0,name,OBJPROP_FONTSIZE,size) #define ObjF6(code) ObjectSetString(0,name,OBJPROP_TEXT,CharToString(code)) #define ObjF7(clr) ObjectSetInteger(0,name,OBJPROP_COLOR,clr) #define ObjF8 ObjectSetInteger(0,name,OBJPROP_COLOR,clrMagenta) #define ObjF9 ObjectSetInteger(0,name,OBJPROP_WIDTH,3) #define ObjF10 ObjectSetInteger(0,name,OBJPROP_BACK,true) #define ObjFont ObjF1;ObjF2; #define ObjCoordinates(T,P) ObjF3(T);ObjF4(P); #define ObjProperty(size,code,clr) ObjF5(size);ObjF6(code);ObjF7(clr); #define ObjZZ ObjF8;ObjF9;ObjF10; //--- double MA[1],sumHi[NUMBER_MA],sumLo[NUMBER_MA]; int handle_MA_H[NUMBER_MA],handle_MA_L[NUMBER_MA]; datetime t[1]; int H,L; int t_min,t_max; int err=-1; double sumH[2],maxH[2],minH[2]; double sumL[2],maxL[2],minL[2]; string name; int count; int shift; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { shift=PeriodSeconds()/30; //--- calculation of ZigZag nodes using historical data Azz.Count(amplitude*Point()); H=ArraySize(Azz.zzH); L=ArraySize(Azz.zzL); if(H<30 || L<30) { Print("Not enough data to calculate ZigZag nodes: "+ "increase the depth of history; "+ "or decrease the amplitude value."); return(-1); } //--- for(int i=0; i<NUMBER_MA; i++) { handle_MA_H[i]=iMA(NULL,0,i+START_MA,0,MODE_SMA,PRICE_HIGH); handle_MA_L[i]=iMA(NULL,0,i+START_MA,0,MODE_SMA,PRICE_LOW); } //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0,-1,-1); for(int i=0; i<NUMBER_MA; i++) { IndicatorRelease(handle_MA_H[i]); IndicatorRelease(handle_MA_L[i]); } //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- get the current bar's opening time value CopyTime(NULL,0,0,1,t); //--- ZigZag: last 7 nodes count=Azz.Read(7); for(int i=1; i<count; i++) { name="ZZ"+(string)i; ObjectCreate(0,name,OBJ_TREND,0,0,0); ObjectSetInteger(0,name,OBJPROP_COLOR,clrRed); ObjectSetInteger(0,name,OBJPROP_WIDTH,10); ObjectSetInteger(0,name,OBJPROP_BACK,true); ObjectSetDouble(0,name,OBJPROP_PRICE,0,Azz.zHL[i-1].value); ObjectSetInteger(0,name,OBJPROP_TIME,0,Azz.zHL[i-1].time); ObjectSetDouble(0,name,OBJPROP_PRICE,1,Azz.zHL[i].value); ObjectSetInteger(0,name,OBJPROP_TIME,1,Azz.zHL[i].time); } //--- check for integrity of preliminary calculations if(err<0) { //--- calculate the sums of deviations of the nodes from MA for ZigZag peaks ArrayInitialize(sumHi,0.0); for(int j=H-1; j>=0; j--) { for(int i=0; i<NUMBER_MA; i++) { err=CopyBuffer(handle_MA_H[i],0,Azz.zzH[j].t,1,MA); if(err<0) return; sumHi[i]+=Azz.zzH[j].price-MA[0]; } } //--- calculate the sums of deviations of the nodes from MA for ZigZag troughs ArrayInitialize(sumLo,0.0); for(int j=L-1; j>=0; j--) { for(int i=0; i<NUMBER_MA; i++) { err=CopyBuffer(handle_MA_L[i],0,Azz.zzL[j].t,1,MA); if(err<0) return; sumLo[i]+=MA[0]-Azz.zzL[j].price; } } } } //+------------------------------------------------------------------+
在此,我们需要澄清几件事:
- iEnvelopes 指标被 iMA 指标取代。这里面没有什么错误或误导。事实上,iEnvelopes 的主线与 iMA 一致!因此,使用移动平均线指标会更加方便。
- 我们使用两个移动平均线池,每个均由 227 条线构成,所以一共就有 454 个 iMA 指标!这是多是少呢?基本上,这是多了。但是,首先,必要时我们可以更改指标的数量;其次,我们需要统计资料。搜索几个节点的意义何在?我们至少需要一百个。
- 指标值被载入到 OnTick() 块中,而非 OnInit()。如果数据加载块被放入 OnInit() 中,那么极有可能某些数据就会延迟加载,并导致指标不能准确、完整计算。获取到计算的所有数据后,err 变量就会变成正值,且此代码块也会被排除到运行之外。
如此一来,产生的指标就会标绘最后 7 个峰谷节点,并计算某给定历史阶段的所有其它节点的坐标(图 6)。此计算仅执行一次,而且算得的数据以后还会用到。当然,您也可以按照一种允许数据定期更新的方式来实施,但本文中,我们只考虑单次计算。
图 6. 峰谷指标(7 节点)
接下来,我们来绘制轨道线指标表面的横截面。为此,我们将下述内容添加到 OnTick() 方法:
//--- PEAKS sumH[0]=0.0; maxH[0]=0.0; minH[0]=0.0; for(int i=0; i<NUMBER_MA; i++) { CopyBuffer(handle_MA_H[i],0,t[0],1,MA); double envelope=MA[0]+sumHi[i]/H; if(i==0 || envelope<minH[0]) { minH[0]=envelope; t_min=SIZE(i); } if(envelope>maxH[0]) { maxH[0]=envelope; t_max=SIZE(i); } sumH[0]+=envelope; name="H"+(string)i; ObjectCreate(0,name,OBJ_TEXT,0,0,0); ObjFont ObjCoordinates(t[0]-(NUMBER_MA-i*2)*shift,envelope) ObjProperty(SIZE(i),158,clrBlue) } //--- TROUGHS sumL[0]=0.0; maxL[0]=0.0; minL[0]=0.0; for(int i=0; i<NUMBER_MA; i++) { CopyBuffer(handle_MA_L[i],0,t[0],1,MA); double envelope=MA[0]-sumLo[i]/L; if(i==0 || envelope<minL[0]) { minL[0]=envelope; t_min=SIZE(i); } if(envelope>maxL[0]) { maxL[0]=envelope; t_max=SIZE(i); } sumL[0]+=envelope; name="L"+(string)i; ObjectCreate(0,name,OBJ_TEXT,0,0,0); ObjFont ObjCoordinates(t[0]+(NUMBER_MA-i*2)*shift,envelope) ObjProperty(SIZE(i),158,clrGold) }
为辨别由轨道线形成的表面的横截面点,各个点在大小上有所区分:轨道线指标主线的平均周期越大,点就越大(图 7)。而且,横截面是围绕着一条纵轴转动,以不同的方向穿过当前(零)柱:高峰为向右 90 度,而低谷为向左 90 度。
现在,在价格图表平面上可以看到它们了。首先,它们位于截平面(图 5)且观察不到。我们只能自己想像,对其形状却没有任何了解。横截面线原来是一种非常特殊的形状。这也是为了方便图形分析而为之。看起来,横截面就像是两颗飞行中的彗星:
图 7. 轨道线指标池的横截面
我们继续横截面特征的计算:最大值和最小值,以及重心(算术平均值)。得到的值将作为当前柱上的点显示,而点的大小则与相关特征的大小相对应。此外,我们还将其保存于历史当中,以供进一步分析。所以,我们将下述内容添加到现有代码:
//--- PEAKS ... //--- midi string str=(string)t[0]; name="Hmidi"+str; ObjectCreate(0,name,OBJ_TEXT,0,0,0); ObjFont ObjCoordinates(t[0],sumH[0]/NUMBER_MA) ObjProperty(10,119,clrBlue) //--- max name="Hmax"+str; ObjectCreate(0,name,OBJ_TEXT,0,0,0); ObjFont ObjCoordinates(t[0],maxH[0]) ObjProperty(t_max,158,clrBlue) //--- min name="Hmin"+str; ObjectCreate(0,name,OBJ_TEXT,0,0,0); ObjFont ObjCoordinates(t[0],minH[0]) ObjProperty(t_min,158,clrBlue) ... //--- TROUGHS ... //--- midi name="Lmidi"+str; ObjectCreate(0,name,OBJ_TEXT,0,0,0); ObjFont ObjCoordinates(t[0],sumL[0]/NUMBER_MA) ObjProperty(10,119,clrGold) //--- max name="Lmax"+str; ObjectCreate(0,name,OBJ_TEXT,0,0,0); ObjFont ObjCoordinates(t[0],maxL[0]) ObjProperty(t_max,158,clrGold) //--- min name="Lmin"+str; ObjectCreate(0,name,OBJ_TEXT,0,0,0); ObjFont ObjCoordinates(t[0],minL[0]) ObjProperty(t_min,158,clrGold)
现在我们来看看,以图形呈现时会是什么样子:
图 8. 横截面特征:针对高峰与低谷分别绘制的最大值、最小值及重心。
我们只需要通过查找并标绘高级峰谷节点,来添加最后的点睛之笔。我们通过添加下述内容来强化此代码:
//--- ZigZag: advanced nodes if(Azz.zHL[0].type>0) // peak { ObjectDelete(0,"MIN"); ObjectDelete(0,"MINfuture"); name="MAX"; ObjectCreate(0,name,OBJ_TREND,0,0,0); ObjZZ ObjectSetDouble(0,name,OBJPROP_PRICE,0,Azz.zHL[1].value); ObjectSetInteger(0,name,OBJPROP_TIME,0,Azz.zHL[1].time); ObjectSetInteger(0,name,OBJPROP_TIME,1,t[0]); double price=minH[0]; ObjectSetDouble(0,name,OBJPROP_PRICE,1,price); if(Azz.zHL[0].value>minH[0]) { price=sumH[0]/NUMBER_MA; ObjectSetDouble(0,name,OBJPROP_PRICE,1,price); } if(Azz.zHL[0].value>sumH[0]/NUMBER_MA) { price=maxH[0]; ObjectSetDouble(0,name,OBJPROP_PRICE,1,price); } //--- into the future name="MAXfuture"; ObjectCreate(0,name,OBJ_TREND,0,0,0); ObjZZ ObjectSetDouble(0,name,OBJPROP_PRICE,0,price); ObjectSetInteger(0,name,OBJPROP_TIME,0,t[0]); ObjectSetDouble(0,name,OBJPROP_PRICE,1,maxL[0]); ObjectSetInteger(0,name,OBJPROP_TIME,1,t[0]+NUMBER_MA*shift); if(price<maxL[0]) ObjectSetDouble(0,name,OBJPROP_PRICE,1,sumL[0]/NUMBER_MA); if(price<sumL[0]/NUMBER_MA) ObjectSetDouble(0,name,OBJPROP_PRICE,1,minL[0]); } if(Azz.zHL[0].type<0) // trough { ObjectDelete(0,"MAX"); ObjectDelete(0,"MAXfuture"); name="MIN"; ObjectCreate(0,name,OBJ_TREND,0,0,0); ObjZZ ObjectSetDouble(0,name,OBJPROP_PRICE,0,Azz.zHL[1].value); ObjectSetInteger(0,name,OBJPROP_TIME,0,Azz.zHL[1].time); ObjectSetInteger(0,name,OBJPROP_TIME,1,t[0]); double price=maxL[0]; ObjectSetDouble(0,name,OBJPROP_PRICE,1,price); if(Azz.zHL[0].value<maxL[0]) { price=sumL[0]/NUMBER_MA; ObjectSetDouble(0,name,OBJPROP_PRICE,1,price); } if(Azz.zHL[0].value<sumL[0]/NUMBER_MA) { price=minL[0]; ObjectSetDouble(0,name,OBJPROP_PRICE,1,price); } //--- into the future name="MINfuture"; ObjectCreate(0,name,OBJ_TREND,0,0,0); ObjZZ ObjectSetDouble(0,name,OBJPROP_PRICE,0,price); ObjectSetInteger(0,name,OBJPROP_TIME,0,t[0]); ObjectSetDouble(0,name,OBJPROP_PRICE,1,minH[0]); ObjectSetInteger(0,name,OBJPROP_TIME,1,t[0]+NUMBER_MA*shift); if(price>minH[0]) ObjectSetDouble(0,name,OBJPROP_PRICE,1,sumH[0]/NUMBER_MA); if(price>sumH[0]/NUMBER_MA) ObjectSetDouble(0,name,OBJPROP_PRICE,1,maxH[0]); }
如此一来,我们已经得到了预测新节点位置的高级峰谷指标(图 9)。这些节点本身位于特征横截面点中:最大值、最小值及重心。而该指标的暂定名称就是“双彗星”。
要注意的是,未来下一个节点的完成时间,仍然未知。一般来讲,我们只预测一个节点坐标即可 – 价格。
图 9. 高级峰谷指标会预测节点:当前节点与下一节点。
结果分析以及给开发人员的建议
根据指标观察结果所示:
- 峰谷节点坐标与预测节点的偏差,在可容许的范围内。有大量的节点位于相应横截面的阴影中。当然,这只是一个定性评估。更精确的结果会在之后的文章中提供。
- 轨道线的横截面展示出了市场行为以及预期的价格动量!请注意由最小平均周期点(尺寸最小)构成的慧尾。它所指向是价格的方向。慧尾以最复杂的方式弯曲,而且它向反方向转得越多,看到趋势变化的可能性就越大。只需观察不同幅度、不同时间框架上的指标行为即可。真是有趣极了!
- 横截面的特征点会形成可能显示出对价格变动有强大阻力的线。因此,可将其视作支撑线和阻力线。
- 如果横截面重心的点超过了它(如图 9 中的高峰),则表明存在上升趋势。
如此一来,我们作为结果得到的,就是一个可在交易策略中尝试的非常有趣的指标!
总结
- 本文中讲到的预测峰谷指标节点的方法,允许我们创建新指标 – “双彗星”。
- 高级峰谷指标会显示新节点的可能坐标,尽管这还只是一个预测。
- 本文中讲到的算法,可用于标绘类似的高级指标,而不一定非是峰谷指标,比如分形或信号指标。
- 新 MQL5 程序员可能会感兴趣的是:看到自己在程序中如何创建宏,并减少重复代码量。
本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/646
MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!
免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。