我们都知道,市场上价格的方向可以在图表上使用趋势来指示,或者在没有趋势时就是平缓的市场。当在平缓市场上交易时,操作属于震荡指标组的技术指标会更加有效。然而,在有趋势时,价格也有一定的波动范围。
在本文中,我将重点介绍创建等距通道,即常常被称为滑动通道的一种动态化方法。需要注意的是,使用这类通道的常用策略之一就是 Victor Barishpolts 的策略,我们将介绍他的策略中与创建滑动通道规则有关的各个方面。而且我们还将尝试扩展这些规则,因为按照作者的观点,这将增加通道系统的灵活性。
首先,我们将把概要图作为等距通道编程的框架, 我想推荐您使用帮助阅读其中的"等距通道"技术分析工具。
我们知道,通道是由三个点构成的,它们中的每一个都有价格和时间坐标。 首先,我们要注意时间坐标,因为它们的顺序会影响通道的类型。我们将使用由构建在两个局部最小值的主线形成的通道作为例子,第三个点将是局部的最大值, 点的位置可以用做通道分类的标准。
当绘制通道时,不会使用向左或者向右的射线,除非另有声明。
第一类的情况是,最小值首先出现,之后是最大值,然后又是最小值。这种状况的概要预览在图1中有所展示。
图1 第一种类型的点集,概览
以下是第一种类型在图表上的展示(图2).
图2 第一种类型的点集, 价格图表
第二种类型是指这样的情况,当最大值,最小值和最小值在图表上接连出现(图3)。
图3 第二种类型的点集,概览
最初出现的局部最大值最终会成为第三个点,它的后面是构成主线的一对最小值。
第三种类型是基于"最小值-最小值-最大值"架构构造的,在这种情况下,主线会一直等待直到形成局部最大值 (图 4).
图4 第三种类型的点集,概览
最后的两种类型更像是特殊情况。
当第三个点和第一个点在构造时间上匹配时就形成了第四种情况。(图5).
图5 第四种类型的点集,概览
另外,最终得第五种类型所出现的条件是,第二个点和第三个点的时间坐标相匹配 (图6).
图6 第五种类型的点集,概览
这就是我们将要处理的五种类型的等距通道,在下面的章节中我们会尝试针对这些构成通道线的点进行编程。
用于绘制通道趋势线的点通常是分形,通过这种方式,一个点同时是分形和绘制直线的基础。
我们现在将会使用OOP(面向对象的编程)对分形点进行总结和编程。
2.1分形点的类
这个类的特性是,它代表了用于构建等距通道的点,
我们将把这个类命名为CFractalPoint,而且,作为 MQL5 语言的好传统,我们将使这个类继承于CObject接口类。
//+------------------------------------------------------------------+ //| 分形点的类 | //+------------------------------------------------------------------+ class CFractalPoint : public CObject { //--- === 数据成员 === --- private: datetime m_date; // 日期和时间 double m_value; // 数值 ENUM_EXTREMUM_TYPE m_extreme_type; // 极值类型 int m_idx; // 索引 (从 0 到 2) //--- === 方法 === --- public: //--- 构造函数/析构函数 void CFractalPoint(void); void CFractalPoint(datetime _date,double _value, ENUM_EXTREMUM_TYPE _extreme_type,int _idx); void ~CFractalPoint(void){}; //--- get-方法 datetime Date(void) const {return m_date;}; double Value(void) const {return m_value;}; ENUM_EXTREMUM_TYPE FractalType(void) const {return m_extreme_type;}; int Index(void) const {return m_idx;}; //--- set-方法 void Date(const datetime _date) {m_date=_date;}; void Value(const double _value) {m_value=_value;}; void FractalType(const ENUM_EXTREMUM_TYPE extreme_type) {m_extreme_type=extreme_type;}; void Index(const int _bar_idx){m_idx=_bar_idx;}; //--- 服务 void Copy(const CFractalPoint &_source_frac); void Print(void); }; //+------------------------------------------------------------------+
这个类有四个用于传输数据的成员:
ENUM_EXTREMUM_TYPE 枚举将用于代表极值的类型:
//+------------------------------------------------------------------+ //| 极值类型 | //+------------------------------------------------------------------+ enum ENUM_EXTREMUM_TYPE { EXTREMUM_TYPE_MIN=0, // 最小值 EXTREMUM_TYPE_MAX=1, // 最大值 };
CFractalPoint 方法的主要目的就是确保以上列出的私有成员数值能够被接收到和刷新。
例如,让我们在 EURUSD, H4 图表上,使用编程的方式在2016年1月26日8:00时创建一个分形点,如图7所示。分形在烛形的最大值处形成,价格是1.08742。
图7 分形的实例
这就是实现此目标的代码。
//--- 分形点数据 datetime pnt_date=D'26.01.2016 08:00'; double pnt_val=1.08742; ENUM_EXTREMUM_TYPE pnt_type=EXTREMUM_TYPE_MAX; int pnt_idx=0; //--- 创建分形点 CFractalPoint myFracPoint(pnt_date,pnt_val,pnt_type,pnt_idx); myFracPoint.Print();
以下出现在记录中:
---=== 分形点数据 ===--- 日期: 2016.01.26 08:00 价格: 1.08742 类型: EXTREMUM_TYPE_MAX 索引: 0
它是指分形点位于日期为2016年1月26日的柱,价格为1.08742。这个分形点是局部最大值。零索引的意思是它价格作为点集中的第一个点。
2.2分形点集合的类
现在,我们可以继续创建用于构建等距通道的一系列分形点,为此,我们将创建CFractalSet 类,用于识别这些点并将它们收集到集合中。
这个类将包含在EA交易中而不是指标中,从而通道将使用CChartObjectChannel类型的图形对象来表示, 而不是指标缓冲区。
CFractalSet 是一个继承于标准程序库的CArrayObj类的类,我选择了保护类型的继承,以使得这个类的接口高度专有。
//+------------------------------------------------------------------+ //| 分形点集合的类 | //+------------------------------------------------------------------+ class CFractalSet : protected CArrayObj { //--- === 数据成员 === --- private: ENUM_SET_TYPE m_set_type; // 点集的类型 int m_fractal_num; // 固定的点的数目 int m_fractals_ha; // 分形指标的句柄 CisNewBar m_new_bar; // 新柱的对象 CArrayObj m_channels_arr; // 指标数组对象 color m_channel_colors[4]; // 通道的颜色 bool m_is_init; // 初始化标志 //--- 通道设置 int m_prev_frac_num; // 前面的分形 int m_bars_beside; // 分形左右的柱数 int m_bars_between; // 中间的柱数 bool m_to_delete_prev; // 是否删除之前的通道? bool m_is_alt; // 另一个分形指标? ENUM_RELEVANT_EXTREMUM m_rel_frac; // 相关点 bool m_is_array; // 是否画箭头? int m_line_wid; // 线宽 bool m_to_log; // 保留记录? //--- === 方法 === --- public: //--- 构造函数/析构函数 void CFractalSet(void); void CFractalSet(const CFractalSet &_src_frac_set); void ~CFractalSet(void){}; //--- void operator=(const CFractalSet &_src_frac_set); //--- 处理函数 bool Init( int _prev_frac_num, int _bars_beside, int _bars_between=0, bool _to_delete_prev=true, bool _is_alt=false, ENUM_RELEVANT_EXTREMUM _rel_frac=RELEVANT_EXTREMUM_PREV, bool _is_arr=false, int _line_wid=3, bool _to_log=true ); void Deinit(void); void Process(void); //--- 服务 CChartObjectChannel *GetChannelByIdx(const int _ch_idx); int ChannelsTotal(void) const {return m_channels_arr.Total();}; private: int AddFrac(const int _buff_len); int CheckSet(const SFracData &_fractals[]); ENUM_SET_TYPE GetTypeOfSet(void) const {return m_set_type;}; void SetTypeOfSet(const ENUM_SET_TYPE _set_type) {m_set_type=_set_type;}; bool PlotChannel(void); bool Crop(const uint _num_to_crop); void BubbleSort(void); }; //+------------------------------------------------------------------+
这里是这个类的成员列表。
处理点集类型的枚举显示如下:
//+------------------------------------------------------------------+ //| 极值点集的类型 | //+------------------------------------------------------------------+ enum ENUM_SET_TYPE { SET_TYPE_NONE=0, // 未设置 SET_TYPE_MINMAX=1, // 最小-最大-最小 SET_TYPE_MAXMIN=2, // 最大-最小-最大 };
在本例中,SET_TYPE_MAXMIN 的值对应着以下的分形点序列:最大,最小,以及最大(图8)。
图8 "最大-最小-最大"类型的点集
我要赶紧说一下,这些点的顺序并非永远按照这些规则的,有的时候,可能在第一个最小值出现后又跟着第二个最小值。我们可以参照在第一部分中描述过的第三种类型的点集(图4)作为例子,在任何情况下,如果有了一组最大值和最小值,或者一组最大值及最小值,我们都会认为点集已经完成。
用于处理相关点类型的枚举的格式如下:
//+------------------------------------------------------------------+ //| 相关点的类型 | //+------------------------------------------------------------------+ enum ENUM_RELEVANT_EXTREMUM { RELEVANT_EXTREMUM_PREV=0, // 之前 RELEVANT_EXTREMUM_LAST=1, // 最新 };
让我们继续处理方法。首先,我们将列出处理函数。
服务方法:
2.3构建通道的另外机会
让我再次提醒您,我们是使用来自标准程序库的CChartObjectChannel类来构建通道和设置其属性的,我们会探讨某些使用算法实现的点以便提高自动构建通道的灵活性。
2.3.1 线的同步
当在图表上分析通道时,如果两条通道线是从同一个柱开始的,那是最方便的。而第四种通道类型正是对应着这种方式 (图5). 很明显,通道可以属于其它类型。为此,分形点的价格和时间坐标将在 CFractalSet::PlotChannel()方法中被修改,以便调整到第四种通道类型。另外保存通道的角度和宽度也很重要(已经实现),
在价格图表上探讨以下的等距通道(图9).
图9 基于初始点的等距通道
我想开始就澄清一下,它是人工构建的。它有以下的分形点:
如果我们使用CFractalSet类来显示一个类似的通道,我们会取得下面的图形(图10)。
图10 在计算过的点上的等距通道
可以看到,在图10中基于计算得到的点上构建的通道和实际上的相差不大,第二个和第三个点的价格和时间值是通过计算获得的,最后一个点应该与第一个点的时间坐标相匹配。
我将会把在计算所得点上绘制通道的任务分为两个部分,
第一部分会集中在通道起点和终点的时间坐标上,在指定的方法中有以下的代码块:
//--- 1) 时间坐标 //--- 通道的起点 int first_date_idx=ArrayMinimum(times); if(first_date_idx<0) { Print("获取时间坐标出错!"); m_channels_arr.Delete(m_channels_arr.Total()-1); return false; } datetime first_point_date=times[first_date_idx]; //--- 通道的终点 datetime dates[]; if(CopyTime(_Symbol,_Period,0,1,dates)!=1) { Print("获得最新柱的时间出错!"); m_channels_arr.Delete(m_channels_arr.Total()-1); return false; } datetime last_point_date=dates[0];
通过这种方法,所有的点都会有这样的时间坐标:
//--- 最终的时间坐标 times[0]=times[2]=first_point_date; times[1]=last_point_date;
任务的第二部分是关于价格坐标 - 根据第三个或者第一个点来决定新的价格。
我们会首先决定价格在柱与柱之间的变化有多快,以及通道的方向是向上还是向下的。
//--- 2) 价格坐标 //--- 2.1 线的角度 //--- 第一个点和第二个点之间的柱数 datetime bars_dates[]; int bars_between=CopyTime(_Symbol,_Period, times[0],times[1],bars_dates ); if(bars_between<2) { Print("在点之间获取柱数出错!"); m_channels_arr.Delete(m_channels_arr.Total()-1); return false; } bars_between-=1; //--- 普通差 double price_differential=MathAbs(prices[0]-prices[1]); //--- 价格的速度 (第一个柱的价格变化) double price_speed=price_differential/bars_between; //--- 通道的方向 bool is_up=(prices[0]<prices[1]);
现在点的价格坐标可以刷新了,很重要的一点是需要知道,那个点是早期形成的,另外,我们还需要知道通道是指向哪里 - 向上还是向下:
//--- 2.2 第一个点或者第三个点的新价格 if(times[0]!=times[2]) { datetime start,end; start=times[0]; end=times[2]; //--- 如果第三个点早于第一个 bool is_3_point_earlier=false; if(times[2]<times[0]) { start=times[2]; end=times[0]; is_3_point_earlier=true; } //--- 第一个和第三个点之间的柱数 int bars_between_1_3=CopyTime(_Symbol,_Period, start,end,bars_dates ); if(bars_between_1_3<2) { Print("在点之间获取柱数出错!"); m_channels_arr.Delete(m_channels_arr.Total()-1); return false; } bars_between_1_3-=1; //--- 如果通道是上升的 if(is_up) { //--- 如果第三个点更早 if(is_3_point_earlier) prices[0]-=(bars_between_1_3*price_speed); else prices[2]-=(bars_between_1_3*price_speed); } //--- 或者如果通道是向下的 else { //--- 如果第三个点更早 if(is_3_point_earlier) prices[0]+=(bars_between_1_3*price_speed); else prices[2]+=(bars_between_1_3*price_speed); } }之前,在我们的例子中,第一个点更早形成,也就是说第三个点的价格应该被刷新。
最后,我们会刷新第二个点的坐标:
//--- 2.3 第2个点的新价格 if(times[1]<last_point_date) { datetime dates_for_last_bar[]; //--- 第二个点和最后柱之前的柱数 bars_between=CopyTime(_Symbol,_Period,times[1],last_point_date,dates_for_last_bar); if(bars_between<2) { Print("在点之间获取柱数出错!"); m_channels_arr.Delete(m_channels_arr.Total()-1); return false; } bars_between-=1; //--- 如果通道是上升的 if(is_up) prices[1]+=(bars_between*price_speed); //--- 或者如果通道是向下的 else prices[1]-=(bars_between*price_speed); }
What we obtain:
通道就可以画出来了,可以选择是否使用向右的箭头。但是,这个选项只是关于当前的通道,图表上所有之前的通道都是有向右箭头的。
2.3.2 之前分形点的考量
在CFractalSet 类中增加了一个选项,可以根据参数来设置是否在历史中搜索分形点,这个选项只是在类的初始化中使用,请记住,m_prev_frac_num成员就是负责"来自过去的点"的。
让我们分析实例 (图11). 假定在TestChannelEAEA交易初始化之后,我们需要在图表上找到几个分形点,它们可以是以下所示的图形:
图11 初始化时的分形点
如果我们使用所有这三个点,我们将能够构造一个通道 (图12):
图12 在初始化中构造的第一个通道
以下是记录中的信息:
2016.02.25 15:49:23.248 TestChannelEA (EURUSD.e,H4) 增加的以前的分形: 3
并不难发现,这些点是从右往左加上的,而通道的构建所收集的点应该是从左往右的。之前的私有排序方法,CFractalSet::BubbleSort(), 实际上, 就是可以用于在画出真实通道前对点进行组织。
黑色的代码就是用于在初始化的CFractalSet::Init()方法中进行点的设置:
//--- 如果加上了之前的分形点 if(m_prev_frac_num>0) { //--- 1) 载入历史 [开始] bool synchronized=false; //--- 循环计数器 int attempts=0; //--- 尝试10次,以等待同步 while(attempts<10) { if(SeriesInfoInteger(_Symbol,0,SERIES_SYNCHRONIZED)) { synchronized=true; //--- 已经建立了同步, 退出 break; } //--- 增加计数器 attempts++; //--- 在下一次迭代之前等待 50 毫秒 Sleep(50); } //--- if(!synchronized) { Print("获取柱数失败,交易品种 ",_Symbol); return false; } int curr_bars_num=Bars(_Symbol,_Period); if(curr_bars_num>0) { PrintFormat("根据交易品种/时段而在终端历史中取得的当前柱数为: %d", curr_bars_num); } //--- 1) 载入历史 [结束] //--- 2) 为所需指标计算数据 [开始] double Ups[]; int i,copied=CopyBuffer(m_fractals_ha,0,0,curr_bars_num,Ups); if(copied<=0) { Sleep(50); for(i=0;i<100;i++) { if(BarsCalculated(m_fractals_ha)>0) break; Sleep(50); } copied=CopyBuffer(m_fractals_ha,0,0,curr_bars_num,Ups); if(copied<=0) { Print("复制上方分形失败. 错误代码 = ",GetLastError(), "i=",i," 复制数量= ",copied); return false; } else { if(m_to_log) Print("复制上方分形成功.", " i = ",i," 复制数量 = ",copied); } } else { if(m_to_log) Print("成功复制上方分形. ArraySize = ",ArraySize(Ups)); } //--- 2) 为所需指标计算数据 [结束] //--- 3) 加入分形点 [开始] int prev_fracs_num=AddFrac(curr_bars_num-1); if(m_to_log) if(prev_fracs_num>0) PrintFormat("之前已加入的分形: %d",prev_fracs_num); //--- 如果可以显示通道 if(prev_fracs_num==3) if(!this.PlotChannel()) Print("显示通道失败!"); //--- 3) 加入分形点 [结束] }
它可以被分成三个字代码块:
通过这种方法,就可以在初始化的时刻画出通道了。它需要一些时间,特别是图表数据没有和服务器数据同步时更是如此。
2.3.3 考虑临近分形点之间的柱
在之前的图表中使用彼此相近的分形点(第一个和第二个, 以及第三个和第四个),为了消除最近的点,您可以增加一些过滤。这个函数可以通过m_bars_between成员来进行 - 临近点之间的柱数。如果您把这个数值设为1,那么第二个点就不会进入集合,而它会被当前的第三个点代替。
图13 考虑了中间柱的第一个通道
我们将根据临近分形点之间至少有一个柱(图13)的条件来构造一个通道(图13),也就是说第一个和第二个点之后的点应该被跳过,它们被使用黄色高亮显示。
例如,第一个略过的点有如下记录:
2016.02.25 16:11:48.037 TestChannelEA (EURUSD.e,H4) 之前被跳过的点: 2016.02.24 12:00 2016.02.25 16:11:48.037 TestChannelEA (EURUSD.e,H4) 中间柱数不足,将会跳过一个点。
找到的通道就会变窄,并且可能从交易者的角度看不是那么有用。
在代码中,对允许的中间柱数的检查是在CFractalSet::CheckSet() 私有方法中进行的。
//--- 当检查最后与当前点之间的柱数时 if(m_bars_between>0) { curr_fractal_num=this.Total(); if(curr_fractal_num>0) { CFractalPoint *ptr_prev_frac=this.At(curr_fractal_num-1); if(CheckPointer(ptr_prev_frac)!=POINTER_DYNAMIC) { Print("从集合中获取分形点对象出错!"); return -1; } datetime time1,time2; time1=ptr_prev_frac.Date(); time2=ptr_temp_frac.Date(); //--- 点之间的柱数 datetime bars_dates[]; int bars_between=CopyTime(_Symbol,_Period, time1,time2,bars_dates ); if(bars_between<0) { Print("从柱开启时间获取数据失败!"); return -1; } bars_between-=2; //--- 在不同的柱 if(bars_between>=0) //--- 如果中间柱数不够 if(bars_between<m_bars_between) { bool to_delete_frac=false; if(m_to_log) Print("中间柱数不足,会跳过一个点。"); // ... } } }
bars_between变量所接收的就是临近分形点之间的柱数,如果它的数值低于可接受值,那么就会跳过一个点。我们将会在下一节中看到,它是当前点还是前一个点。
2.3.4 相关分形点的选择
当中间柱数不够时,必须忽略掉其中一个点,您可以明确指定跳过哪个点。在上面的例子中,从出现时间上看更早的点被忽略掉,因为较新的点被认为是相关分形点。让我们使更早的点成为相关点,看会出现什么情况 (图14).
图14 考虑了中间柱数和前一相关点的第一个通道
在例子中,我们将在第一个跳过点的地方得到如下记录:
2016.02.25 16:46:06.212 TestChannelEA (EURUSD.e,H4) 将会跳过当前点: 2016.02.24 16:00 2016.02.25 16:46:06.212 TestChannelEA (EURUSD.e,H4) 中间柱数不足,将会跳过一个点。
也许,这个通道看起来更有用,因为它限制了所有临近的柱。很难进一步说,在绘制通道时,是前一个点还是最近的点会更加有用。
如果我们查阅代码 (它就在CFractalSet::CheckSet())私有方法中, 我们会看到有两个因素会影响此方法的行为: 选择真实点的类型和初始化标志。
//--- 如果中间柱数不够 if(bars_between<m_bars_between) { bool to_delete_frac=false; if(m_to_log) Print("中间柱数不足,会跳过一个点。"); //--- 如果前一点是相关的 if(m_rel_frac==RELEVANT_EXTREMUM_PREV) { datetime curr_frac_date=time2; //--- 如果进行了初始化 if(m_is_init) { continue; } //--- 如果没有进行初始化 else { //--- 删除当前点 to_delete_frac=true; curr_frac_date=time1; } if(m_to_log) { PrintFormat("当前点将会被忽略: %s", TimeToString(curr_frac_date)); } } //--- 如果最新点是相关点 else { datetime curr_frac_date=time1; //--- 如果进行了初始化 if(m_is_init) { //--- 删除之前的点 to_delete_frac=true; } //--- 如果没有进行初始化 else { curr_frac_date=time2; } if(m_to_log) PrintFormat("之前点被跳过: %s", TimeToString(curr_frac_date)); if(curr_frac_date==time2) continue; } //--- 如果点被删除 if(to_delete_frac) { if(!this.Delete(curr_fractal_num-1)) { Print("从集合中删除最新点出错!"); return -1; } } }
在下一部分中我们将深入了解等距通道的集合并通过变换它们的参数得到价格滑动的图片。
这个版本的EA交易叫做ChannelsPlotter,创建它是用于测试通道的绘制的,EA交易运行的结果显示在图15中。很明显,在没有明显市场趋势时,因为经常出现分形,通道会开始不断"闪烁",所以,当极值点距离的柱数接近时,需要增加一个可替换的分形指标。我们从源代码库中借用 X-bars 分形指标。
图15 基于普通分形的滑动通道
如果在运行EA交易时选择了替换的分形指标,就会得到令人满意的结果,极值点之间的柱数会增加。从而,如果我们处理一组包含23个柱的分形,结果就如图16中所显示:
图16 基于替换分形指标的滑动通道
通过这种方式,在判断分形时临近的柱数越少,在价格图表上"通道"的干扰就越多。
在本文中,我尝试了用于等距通道系统编程的方法,探讨了构建这种通道的一些细节,使用了 Victor Barishpoltz 的思路作为框架。在我下面的文章中,我将分析根据滑动通道生成的交易信号。
文件的位置:
以我的观点,在项目文件夹下创建和保存文件是最方便的,例如,位置可以如下: <data folder>\MQL5\Projects\ChannelsPlotter。请不要忘记编译替换的分形指标 — X-bars_Fractals。该指标的源代码应该位于指标文件夹 — <data folder>\MQL5\Indicators.本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程