MQL5 酷客宝典 - 轴点交易信号

1. 轴点(反转水平)指标

对于这个策略,我们将使用指标来绘制潜在的反转水平,绘图是通过图形方式构建的,而没有使用图形对象。这种方法的主要优点是可以在优化模式下参考指标,另一方面,图形化构建不能超出指标的缓冲区,也就意味着没有出现在将来的线。

有几种方法可以计算水平,关于这个主题的更多信息可以在文章 "基于轴点分析的交易策略" 中找到。

让我们现在考虑一下标准方法 (使用以下公式定义水平线):

编辑

添加图片注释,不超过 140 字(可选)

RES 是一个阻力水平,而 SUP 是支撑水平。一共有1个主反转水平 (PP), 6个阻力水平 (RES) 和6个支撑水平(SUP)。

所以,从视觉上指标看起来就像一系列在不同价格上画出的水平线。当在图表上第一次载入时,指标只会在当前日内绘制水平线 (图1).

编辑切换为居中

添加图片注释,不超过 140 字(可选)

图1. 轴点指标: 绘制当前日

让我们分块查看指标代码,从第一个开始计算,

当新的一天开始时,我们需要计算所有的反转水平。

 
 

//--- 如果是新的一天 if(gNewDay.isNewBar(today)) { PrintFormat("新的一天: %s",TimeToString(today)); //--- 规范化价格 double d_high=NormalizeDouble(daily_rates[0].high,_Digits); double d_low=NormalizeDouble(daily_rates[0].low,_Digits); double d_close=NormalizeDouble(daily_rates[0].close,_Digits); //--- 保存价格 gYesterdayHigh=d_high; gYesterdayLow=d_low; gYesterdayClose=d_close; //--- 1) pivot: PP = (HIGH + LOW + CLOSE) / 3 gPivotVal=NormalizeDouble((gYesterdayHigh+gYesterdayLow+gYesterdayClose)/3.,_Digits); //--- 4) RES1.0 = 2*PP - LOW gResVal_1_0=NormalizeDouble(2.*gPivotVal-gYesterdayLow,_Digits); //--- 5) SUP1.0 = 2*PP – HIGH gSupVal_1_0=NormalizeDouble(2.*gPivotVal-gYesterdayHigh,_Digits); //--- 8) RES2.0 = PP + (HIGH -LOW) gResVal_2_0=NormalizeDouble(gPivotVal+(gYesterdayHigh-gYesterdayLow),_Digits); //--- 9) SUP2.0 = PP - (HIGH – LOW) gSupVal_2_0=NormalizeDouble(gPivotVal-(gYesterdayHigh-gYesterdayLow),_Digits); //--- 12) RES3.0 = 2*PP + (HIGH – 2*LOW) gResVal_3_0=NormalizeDouble(2.*gPivotVal+(gYesterdayHigh-2.*gYesterdayLow),_Digits); //--- 13) SUP3.0 = 2*PP - (2*HIGH – LOW) gSupVal_3_0=NormalizeDouble(2.*gPivotVal-(2.*gYesterdayHigh-gYesterdayLow),_Digits); //--- 2) RES0.5 = (PP + RES1.0) / 2 gResVal_0_5=NormalizeDouble((gPivotVal+gResVal_1_0)/2.,_Digits); //--- 3) SUP0.5 = (PP + SUP1.0) / 2 gSupVal_0_5=NormalizeDouble((gPivotVal+gSupVal_1_0)/2.,_Digits); //--- 6) RES1.5 = (RES1.0 + RES2.0) / 2 gResVal_1_5=NormalizeDouble((gResVal_1_0+gResVal_2_0)/2.,_Digits); //--- 7) SUP1.5 = (SUP1.0 + SUP2.0) / 2 gSupVal_1_5=NormalizeDouble((gSupVal_1_0+gSupVal_2_0)/2.,_Digits); //--- 10) RES2.5 = (RES2.0 + RES3.0) / 2 gResVal_2_5=NormalizeDouble((gResVal_2_0+gResVal_3_0)/2.,_Digits); //--- 11) SUP2.5 = (SUP2.0 + SUP3.0) / 2 gSupVal_2_5=NormalizeDouble((gSupVal_2_0+gSupVal_3_0)/2.,_Digits); //--- 当前日的开始柱 gDayStart=today; //--- 寻找活动时段的开始柱 //--- 形式为时间序列 for(int bar=0;bar<rates_total;bar++) { //--- 选中的柱的时间 datetime curr_bar_time=time[bar]; user_date.DateTime(curr_bar_time); //--- 选中的柱的日期 datetime curr_bar_time_of_day=user_date.DateOfDay(); //--- 如果当前柱是前一天的柱 if(curr_bar_time_of_day<gDayStart) { //--- 保存起始柱 gBarStart=bar-1; break; } } //--- 重置本地计数器 prev_calc=0; }

使用红色 突出显示的字符串是重新计算的水平线。下面,我们应该找到当前时段的柱作为绘制水平的起始点,它的数值是在 gBarStart 变量中定义的。SUserDateTime 自定义结构 (派生于CDateTime结构)是在操作日期和时间时用于搜索的,

现在,让我们集中精力于填充当前时段柱的缓冲区数值模块。

 
 

//--- 活动时段中是否有新柱 if(gNewMinute.isNewBar(time[0])) { //--- 进行计算的柱的编号 int bar_limit=gBarStart; //--- 如果这不是第一次载入 if(prev_calc>0) bar_limit=rates_total-prev_calc; //--- 计算缓冲区 for(int bar=0;bar<=bar_limit;bar++) { //--- 1) 轴点 gBuffers[0].data[bar]=gPivotVal; //--- 2) RES0.5 if(gToPlotBuffer[1]) gBuffers[1].data[bar]=gResVal_0_5; //--- 3) SUP0.5 if(gToPlotBuffer[2]) gBuffers[2].data[bar]=gSupVal_0_5; //--- 4) RES1.0 if(gToPlotBuffer[3]) gBuffers[3].data[bar]=gResVal_1_0; //--- 5) SUP1.0 if(gToPlotBuffer[4]) gBuffers[4].data[bar]=gSupVal_1_0; //--- 6) RES1.5 if(gToPlotBuffer[5]) gBuffers[5].data[bar]=gResVal_1_5; //--- 7) SUP1.5 if(gToPlotBuffer[6]) gBuffers[6].data[bar]=gSupVal_1_5; //--- 8) RES2.0 if(gToPlotBuffer[7]) gBuffers[7].data[bar]=gResVal_2_0; //--- 9) SUP2.0 if(gToPlotBuffer[8]) gBuffers[8].data[bar]=gSupVal_2_0; //--- 10) RES2.5 if(gToPlotBuffer[9]) gBuffers[9].data[bar]=gResVal_2_5; //--- 11) SUP2.5 if(gToPlotBuffer[10]) gBuffers[10].data[bar]=gSupVal_2_5; //--- 12) RES3.0 if(gToPlotBuffer[11]) gBuffers[11].data[bar]=gResVal_3_0; //--- 13) SUP3.0 if(gToPlotBuffer[12]) gBuffers[12].data[bar]=gSupVal_3_0; } }

当新柱在载入指标的图表上出现时开始缓冲区的计算,黄色突出显示了直到需要计算的缓冲区的柱的编号,已计算的柱数的本地计数器就是用于它的,我们需要它是因为在新一天的开始时把 prev_calculated 常量重设为0, 尽管这样的重置是有必要的。

轴点指标的完整代码可以在 Pivots.mq5 文件中找到。

2. 基本策略

让我们根据所描述的指标来开发一个简单的基本策略,让我们根据开盘价格相对中心轴点的位置来作为建仓信号,价格触碰到轴点水平作为确认信号。

EURUSD M15 图表 (图2) 显示了低于中心轴点的开盘价格的一天 (2015年1月15日)。但是,在这一天的晚些时候,价格上升达到了轴点水平,这样,就有了一个卖出信号。如果没有激活止损或者获利,就在第二天的开始退出市场。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

图2基本策略:卖出信号

止损水平是和轴点指标反转水平绑定的,中间的阻力水平 Res0.5,价格 $1.18153 在卖出时作为止损,主支撑水平 Sup1.0,价格 $1.17301 用于作为获利水平。我们将在晚些时候回到1月14日的交易日,同时,让我们看一下实现基本策略部分的代码。

2.1 CSignalPivots 信号类

让我们创建一个信号类来根据价格动态和反转水平指标所形成的各种模式来生成信号。

 
 

//+------------------------------------------------------------------+ //| Class CSignalPivots | //| 目的: 根据轴点生成交易信号的类. | //| CExpertSignal 类的继承类. | //+------------------------------------------------------------------+ class CSignalPivots : public CExpertSignal { //--- === 数据成员 === --- protected: CiCustom m_pivots; // "Pivots(轴点)" 指标对象 //--- adjustable parameters bool m_to_plot_minor; // 画出次级水平 double m_pnt_near; // 阈值 //--- estimated double m_pivot_val; // 轴点数值 double m_daily_open_pr; // 当前日的开盘价 CisNewBar m_day_new_bar; // 每日时段的新柱 //--- 市场模式 //--- 1) 模式 0 "第一次接触到轴点水平" (顶部 - 买入, 底部 - 卖出) int m_pattern_0; // 权重 bool m_pattern_0_done; // 模式结束的标记 //--- === 方法 === --- public: //--- 构造函数/析构函数 void CSignalPivots(void); void ~CSignalPivots(void){}; //--- 设置可以调节的参数的方法 void ToPlotMinor(const bool _to_plot) {m_to_plot_minor=_to_plot;} void PointsNear(const uint _near_pips); //--- 调整市场模式 "权重" 的方法 void Pattern_0(int _val) {m_pattern_0=_val;m_pattern_0_done=false;} //--- 确认设置的方法 virtual bool ValidationSettings(void); //--- 创建指标和时间序列的方法 virtual bool InitIndicators(CIndicators *indicators); //--- 检查是否生成了市场模式的方法 virtual int LongCondition(void); virtual int ShortCondition(void); virtual double Direction(void); //--- 用于侦测进入市场水平的方法 virtual bool OpenLongParams(double &price,double &sl,double &tp,datetime &expiration); virtual bool OpenShortParams(double &price,double &sl,double &tp,datetime &expiration); //--- protected: //--- 用于初始化指标的方法 bool InitCustomIndicator(CIndicators *indicators); //--- 取得轴点水平值 double Pivot(void) {return(m_pivots.GetData(0,0));} //--- 取得主阻力水平的数值 double MajorResistance(uint _ind); //--- 取得所需的次级阻力水平数值 double MinorResistance(uint _ind); //--- 取得主支撑水平数值 double MajorSupport(uint _ind); //--- 取得次级支撑水平数值 double MinorSupport(uint _ind); }; //+------------------------------------------------------------------+

我已经使用了这篇文章 "MQL5 酷客宝典 - 移动通道的交易信号"中的方法: 当价格下跌到线内区域时,价格触线就被确认。m_pnt_near 数据成员设置了反转水平的阈值。

类中提供的信号模式是最重要的角色,基类有一个单独的模式,除了来自权重 (m_pattern_0), 它还含有一个交易日内的结束属性 (m_pattern_0_done).

CExpertSignal 基信号类中有很多虚拟方法,这使得可以在派生类中实现更好的调整,

特别的一点是,我已经重新定义了 OpenLongParams() 和 OpenShortParams() 方法用来计算交易水平,

让我们查看第一个方法的代码 — 定义当买入时候的交易水平数值。

 
 

//+------------------------------------------------------------------+ //| 定义买入时的交易水平数值 | //+------------------------------------------------------------------+ bool CSignalPivots::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration) { bool params_set=false; sl=tp=WRONG_VALUE; //--- 如果使用了模式 0 if(IS_PATTERN_USAGE(0)) //--- 如果模式 0 没有完成 if(!m_pattern_0_done) { //--- 开盘价 - 市场 double base_price=m_symbol.Ask(); price=m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit()); //--- 止损价 - Sup0.5 水平 sl=this.MinorSupport(0); if(sl==DBL_MAX) return false; //--- 如果设置了止损价格 sl=m_symbol.NormalizePrice(sl); //--- 获利价格 - Res1.0 水平 tp=this.MajorResistance(0); if(tp==DBL_MAX) return false; //--- 如果设置了获利价格 tp=m_symbol.NormalizePrice(tp); expiration+=m_expiration*PeriodSeconds(m_period); //--- 如果设置了价格 params_set=true; //--- 模式结束 m_pattern_0_done=true; } //--- return params_set; } //+------------------------------------------------------------------+

止损价格是使用 MinorSupport() 方法计算得到的第一个二级支撑水平的数值,获利是使用了 MajorResistance() 方法计算得到的第一个主阻力水平。如果是卖出,方法会分别使用 MinorResistance() 和 MajorSupport() 来对应地替换。

把自定义信号变成主信号,以使得用于定义交易水平的方法可以正常工作,这里就是父类中的方法是如何定义交易水平的,看起来是这样的:

 
 

//+------------------------------------------------------------------+ //| 侦测用于买入的水平 | //+------------------------------------------------------------------+ bool CExpertSignal::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration) { CExpertSignal *general=(m_general!=-1) ? m_filters.At(m_general) : NULL; //--- if(general==NULL) { //--- 如果没有明确指定基础价格,就是用当前市场价格 double base_price=(m_base_price==0.0) ? m_symbol.Ask() : m_base_price; price =m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit()); sl =(m_stop_level==0.0) ? 0.0 : m_symbol.NormalizePrice(price-m_stop_level*PriceLevelUnit()); tp =(m_take_level==0.0) ? 0.0 : m_symbol.NormalizePrice(price+m_take_level*PriceLevelUnit()); expiration+=m_expiration*PeriodSeconds(m_period); return(true); } //--- return(general.OpenLongParams(price,sl,tp,expiration)); } //+------------------------------------------------------------------+

如果没有设置主信号索引,水平就使用默认值,为了避免这样, 在EA代码中初始化信号时,做以下设置:

 
 

//--- CSignalPivots 过滤器 CSignalPivots *filter0=new CSignalPivots; if(filter0==NULL) { //--- 错误 PrintFormat(__FUNCTION__+": 创建 filter0 出错"); return INIT_FAILED; } signal.AddFilter(filter0); signal.General(0);

买入条件的验证方法如下所示:

 
 

//+------------------------------------------------------------------+ //| 检查买入条件 | //+------------------------------------------------------------------+ int CSignalPivots::LongCondition(void) { int result=0; //--- 如果没有使用模式 0 if(IS_PATTERN_USAGE(0)) //--- 如果模式 0 没有完成 if(!m_pattern_0_done) //--- 如果一天的开盘价高于轴点 if(m_daily_open_pr>m_pivot_val) { //--- 当前柱的最低价格 double last_low=m_low.GetData(1); //--- 如果得到了价格 if((last_low>WRONG_VALUE) && (last_low<DBL_MAX)) //--- 如果有上方的接触 (考虑到阈值) if(last_low<=(m_pivot_val+m_pnt_near)) { result=m_pattern_0; //--- 写到日志中 Print("\n---== 价格在上方接触到轴点水平 ==---"); PrintFormat("价格: %0."+IntegerToString(m_symbol.Digits())+"f",last_low); PrintFormat("轴点: %0."+IntegerToString(m_symbol.Digits())+"f",m_pivot_val); PrintFormat("阈值: %0."+IntegerToString(m_symbol.Digits())+"f",m_pnt_near); } } //--- return result; } //+------------------------------------------------------------------+

很容易可以看到,从上方的接触在考虑阈值时做了检查 last_low<=(m_pivot_val+m_pnt_near).

除了其他的一些,如果基本模式没有完成,Direction() 方法定义了"有权重的"方向检查 。

 
 

//+------------------------------------------------------------------+ //| 定义 "有权重的" 方向 | //+------------------------------------------------------------------+ double CSignalPivots::Direction(void) { double result=0.; //--- 接收每日历史数据 MqlRates daily_rates[]; if(CopyRates(_Symbol,PERIOD_D1,0,1,daily_rates)<0) return 0.; //--- 如果模式 0 已经完成 if(m_pattern_0_done) { //--- 检查新的一天 if(m_day_new_bar.isNewBar(daily_rates[0].time)) { //--- 重设模式完成标记 m_pattern_0_done=false; return 0.; } } //--- 如果模式 0 没有完成 else { //--- 每日开盘价 if(m_daily_open_pr!=daily_rates[0].open) m_daily_open_pr=daily_rates[0].open; //--- 轴点 double curr_pivot_val=this.Pivot(); if(curr_pivot_val<DBL_MAX) if(m_pivot_val!=curr_pivot_val) m_pivot_val=curr_pivot_val; } //--- 结果 result=m_weight*(this.LongCondition()-this.ShortCondition()); //--- return result; } //+------------------------------------------------------------------+

对于退出信号,重新定义父类方法 CloseLongParams() 和 CloseShortParams(). 买入代码模块例子:

 
 

//+------------------------------------------------------------------+ //| 定义买入时的交易水平 | //+------------------------------------------------------------------+ bool CSignalPivots::CloseLongParams(double &price) { price=0.; //--- 如果使用了模式 0 if(IS_PATTERN_USAGE(0)) //--- 如果模式 0 没有完成 if(!m_pattern_0_done) { price=m_symbol.Bid(); //--- 写到日志中 Print("\n---== 关闭买入仓位的信号 ==---"); PrintFormat("市场价格: %0."+IntegerToString(m_symbol.Digits())+"f",price); return true; } //--- 返回结果 return false; } //+------------------------------------------------------------------+

退出信号的阈值在EA交易代码中应当重置为0。

signal.ThresholdClose(0);

在这种情况下没有进行 方向的检查 。

 
 

//+------------------------------------------------------------------+ //| 生成关闭买入仓位的信号 | //+------------------------------------------------------------------+ bool CExpertSignal::CheckCloseLong(double &price) { bool result =false; //--- "禁止"信号 if(m_direction==EMPTY_VALUE) return(false); //--- 检查是否超过阈值 if(-m_direction>=m_threshold_close) { //--- 有信号 result=true; //--- 尝试取得平仓水平 if(!CloseLongParams(price)) result=false; } //--- 基础价格清零 m_base_price=0.0; //--- 返回结果 return(result); } //+------------------------------------------------------------------+

产生了这样的问题: 在这种情况下怎样检查退出信号呢?首先,检查仓位是否存在 (在 Processing() 方法中 ), 然后,使用 m_pattern_0_done 属性 (在 CloseLongParams() 和 CloseShortParams() 方法中重新定义). 一旦EA侦测到有仓位,并且模式0没有完成,它就会尝试立即关闭它,这在交易日的开始会发生。

我们已经检视了 CSignalPivots 信号类的基础,现在,让我们探讨策略类。

2.2 CPivotsExpert 交易策略类

派生的策略类和移动通道的很类似,第一个不同点是使用了按分钟交易的模式而不是按分时交易的模式。这使你可以在相对较深的历史中快速测试策略。第二,还有对退出的检查。我们已经在EA可能平仓的时候定义了它。

主要的处理方法看起来如下:

 
 

//+------------------------------------------------------------------+ //| 主模块 | //+------------------------------------------------------------------+ bool CPivotsExpert::Processing(void) { //--- 新的分钟柱 if(!m_minute_new_bar.isNewBar()) return false; //--- 计算方向 m_signal.SetDirection(); //--- 如果没有仓位 if(!this.SelectPosition()) { //--- 仓位开启模块 if(this.CheckOpen()) return true; } //--- 如果有仓位 else { //--- 仓位关闭模块 if(this.CheckClose()) return true; } //--- 如果没有交易操作 return false; } //+------------------------------------------------------------------+

就是这样。现在,我们可以运行基本策略了,它的代码在 BasePivotsTrader.mq5 文件中。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

图3. 基本策略: 卖出

让我们回到2015年1月14日这一天,在这种条件下,模型工作得很好。我们在轴点建立卖出仓位并且在主支撑水平 Sup1.0 平仓。

在策略测试器中从2013年1月7日到2017年1月7日,在 EURUSD M15 中使用以下参数运行:

  • Entry signal threshold(进场信号阈值), [0...100] = 10;

  • Weight(权重), [0...1.0] = 1,0;

  • Fixed volume (固定交易量) = 0,1;

  • Tolerance, points(阈值,点数) = 15.

可以发现,策略交易的结果很稳定。是负面的 (图 4).

编辑切换为居中

添加图片注释,不超过 140 字(可选)

图4. EURUSD: 2013-2016 第一个基本策略的结果

根据结果判断,我们做错了每一件事,我们应当在卖出信号时买入而在买入信号时卖出,但这是真的吗?让我们看看。如果要这样做,我们应当开发一个基本策略并且在信号中做一些改变,在这种情况下,买入条件应当看起来如下:

 
 

//+------------------------------------------------------------------+ //| 检查卖出条件 | //+------------------------------------------------------------------+ int CSignalPivots::LongCondition(void) { int result=0; //--- 如果没有使用模式 0 if(IS_PATTERN_USAGE(0)) //--- 如果模式 0 没有完成 if(!m_pattern_0_done) //--- 如果一天的开盘价低于轴点 if(m_daily_open_pr<m_pivot_val) { //--- 当前柱的最高价 double last_high=m_high.GetData(1); //--- 如果得到了价格 if((last_high>WRONG_VALUE) && (last_high<DBL_MAX)) //--- 如果有上方的接触 (考虑到阈值) if(last_high>=(m_pivot_val-m_pnt_near)) { result=m_pattern_0; //--- 写到日志中 Print("\n---== 价格从下方接触到轴点水平 ==---"); PrintFormat("Price: %0."+IntegerToString(m_symbol.Digits())+"f",last_high); PrintFormat("轴点: %0."+IntegerToString(m_symbol.Digits())+"f",m_pivot_val); PrintFormat("阈值: %0."+IntegerToString(m_symbol.Digits())+"f",m_pnt_near); } } //--- return result; } //+------------------------------------------------------------------+

让我们在策略测试器中运行另一个策略并取得结果:

编辑切换为居中

添加图片注释,不超过 140 字(可选)

图5. EURUSD: 2013-2016 第二个基本策略的运行结果

显然,并没有出现第一个版本的镜像,也许,原因是止损和获利值。另外,如果在交易日中没有触发止损水平,新的一天开始时会关闭仓位。

让我们尝试改变第二个版本的基本策略, 这样在买入的恶时候止损水平就更远了 — 在主支撑水平 Sup1.0 之前, 儿利润大小被限制在中间的阻力水平 Res0.5. 当卖出时,止损就放置在 Res1.0, 而获利位于 — Sup0.5.

在这种情况下, 买入交易水平是这样定义的:

 
 

//+------------------------------------------------------------------+ //| 定义买入交易的水平 | //+------------------------------------------------------------------+ bool CSignalPivots::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration) { bool params_set=false; sl=tp=WRONG_VALUE; //--- 如果使用了模式 0 if(IS_PATTERN_USAGE(0)) //--- 如果模式 0 没有完成 if(!m_pattern_0_done) { //--- 开盘价 - 市场 double base_price=m_symbol.Ask(); price=m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit()); //--- 止损价格e - Sup1.0 水平 sl=this.MajorSupport(0); if(sl==DBL_MAX) return false; //--- 如果设置了止损价格 sl=m_symbol.NormalizePrice(sl); //--- 获利价格 - Res0.5 水平 tp=this.MinorResistance(0); if(tp==DBL_MAX) return false; //--- 如果设置了获利价格 tp=m_symbol.NormalizePrice(tp); expiration+=m_expiration*PeriodSeconds(m_period); //--- 如果设置了价格 params_set=true; //--- 模式结束 m_pattern_0_done=true; } //--- return params_set; } //+------------------------------------------------------------------+

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值