当然,每位交易者都清楚,峰谷指标旨在对给定或更大幅度的价格波动进行分析。峰谷线是一条折线,节点分别位于价格图表的最高价和最低价处。
该指标有许多变体: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 或更多带有不同输入数据的指标池。在主指标线平均周期和价格使用(高峰为高,低谷为低)方面,它们会有所区别。我们引入下述标记和公式:
我们有两个指标池:一个用于高峰,另一个用于低谷(每个约有 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; } } } } //+------------------------------------------------------------------+
在此,我们需要澄清几件事:
如此一来,产生的指标就会标绘最后 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. 高级峰谷指标会预测节点:当前节点与下一节点。
根据指标观察结果所示:
如此一来,我们作为结果得到的,就是一个可在交易策略中尝试的非常有趣的指标!
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程