请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3364712 新帖:0

区域方法

有事您说话发表于:4 月 17 日 18:12回复(1)

目录

  • 简介
  • 1. 评估RSI指标的通常方法
  • 2. 区域方法
  • 3. RSIAreaIndicator_v1
    • 3.1. 创建指标的草稿
    • 3.2. 填充指标头部代码行
    • 3.3. 编辑指标的OnInit()函数
    • 3.4. 创建额外的指标函数
    • 3.5. 创建指标的主运行代码  
  • 4. RSIAreaExpert 版本 1.00 — EA交易
    • 4.1. 编辑EA交易的开头部分
    • 4.2. 另外的函数 — RSIAreaFunc
    • 4.3. 测试 CopyBuffer 的代码
    • 4.4. 进一步编辑其他函数
    • 4.5. EA交易的 OnTick() 函数
  • 5. 在不同的时段和交易品种中测试 RSIAreaExpert 版本 1.00
  • 结论
  • 参考资料列表 

简介

如何正确设置文章末尾的EA交易中的指标: "Indicators.zip" 和 "Experts.zip" 档案必须解压缩,然后保存到 <data catalog>\MQL5\ 目录下。

区域方法的描述最早发布于2004年 [1]. 这个方法的迷人之处在于它看待RSI指标数据不同寻常的角度:它建议评估该震荡指标在上一次交叉50线之后超越/跌破50线的区域。考虑到市场自2004年以来有剧烈的波动, 而且已经发明了MQL语言,我们就用MQL5语言在现代市场上实现这种策略。



1. 评估RSI指标的通常方法

通常的基于RSI指标的交易方法是根据指标显示的超买/超卖水平,寻找指标数据与价格的背离,在指标达到超买/超卖区域,价格出现失败摇摆时进行交易。这样,使用RSI震荡指标做技术分析时至少需要四个信号,这是一个复杂的决策系统。

我们也知道,RSI指标不可能待在超买区域(超过 70)/超卖区域 (低于 30) 很长时间 – 它总会返回并穿过50中线

RSI

图 1. RSI震荡指标总会从超买/超卖区域返回

图1显示了震荡指标在超买/超卖区域所花费的时间与其它时间相比非常少,另外,RSI在进入超买/超卖区域之后会与50中线相交叉。这种RSI震荡指标总是返回并与50中线交叉的事实,以及基于RSI震荡指标的技术分析,就简单构成了区域方法开发的基础。


2. 区域方法

区域方法建议,基于一个标准评估RSI震荡指标的读数:区域根据震荡指标超过/低于50线而构成,而这个值将用于描述超买/超卖水平:

区域图片

图 2. 区域方法 - 根据超过/低于50估算区域

在这种情况下,建立仓位的信号是50线上一次与RSI指标交叉后的超过/低于50线区域的大小。   

  • 当RSI超过50线较长一段时间之后,当超过了某一区域值(例如, 300),就会建立一个卖出仓位: 

卖出信号 

图 3. 当区域达到300时生成建立卖出仓位的信号

  • 对应地,当RSI低于50线较长时间并且超过某一区域数值后,就会生成买入信号。

平仓信号是在RSI震荡指标穿过50线,形成了局部的最大/最小值并反转达到4%时而生成的。

  • 例如,在超过50线很长时间之后,在某个点我们建立了一个卖出仓位,然后,指标值开始下降并达到了40线,然后指标值开始上升(也就是说形成了局部最小值),当指标值达到44的时候,这将成为平仓信号: 

关闭卖出信号 

图 4. 在形成最小值并回滚4%之后生成卖出仓位的平仓信号

  • 根据同样的逻辑,当指标低于50线较长时间,只有那时我们才会等待局部最小值形成

RSIAreaIndicator 将用于辅助使得超过/低于50线的区域可视化。 


3. RSIAreaIndicator_v1

RSIAreaIndicator 指标是在 RSI 震荡指标的基础上构造的。主要的区别是,RSIAreaIndicator 指标有两个缓冲区,一个缓冲区的建造风格是DRAW_HISTOGRAM, 另外一个缓冲区是 — DRAW_LINE. 缓冲区数值是使用以下公式获得的

RSIAreaIndicaor 公式

RSIAreaIndicator 版本 1.00的外观:

RSIAreaIndicator 

图 5. RSIAreaIndicator _v1

3.1. 创建指标的草稿 

我建议把您创建的指标放到独立的文件夹下,我就有这样的文件夹,叫做"MyInd"。为了开始写一个指标,应该在MetaEditor代码编辑器中使用MQL5 向导创建一个草稿. 创建草稿的初始步骤在这个视频中有所描述:


您可以在文章末尾看到所创建的草稿 - 保存指标的文件名称是 "RSIAreaIndicatorStep1.mq5"。 

3.2. 填充指标头部代码行

下一步就是加上指标的描述。随后,它将总会出现在“通用”页面的指标属性中。增加的全部代码在文章中都会高亮显示以便阅读:

#property version   "1.00"
#property description   "显示RSI超过/低于50线的指标"
#property indicator_separate_window

我们记得,RSIAreaIndicator 指标有两个指标缓冲区。除了那些,我们还将需要一个额外的缓冲区。这样,在指标中将一共有三个缓冲区。让我们从指标头部代码行开始编辑代码:

#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   2
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_type2   DRAW_LINE
#property indicator_color1  clrGray
#property indicator_color2  clrGray
//--- 输入参数
input int      ExtRSIPeriod=13;

现在,我们必须声明三个数组,用于保存指标和额外缓冲区的数值:

//--- 输入参数
input int      ExtRSIPeriod=13;
//---- 缓冲区
double ExtMapBuffer1[];
double ExtMapBuffer2[];
double ExtMapBuffer3[];
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                      |

下面,因为我们的 RSIAreaIndicator 指标是基于标准的 RSI 指标计算的,而且我们会取得指标值,所以需要一个变量来保存相对强弱指数指标的句柄:

double ExtMapBuffer3[];
//--- 用于保存 iRSI 指标句柄的变量 
int    handle;
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                      |

然后,必须在头部代码行中定义三个服务变量。name 变量用于保存指标启动时交易品种的名称,short_name 变量 — 指标的短名称,以及 bars_calculated 变量 — 计算RSI指标的柱数:

int    handle;
//--- 用于保存的变量 
string name=Symbol();
//--- 图表上指标的名称 
string short_name;
//--- 我们将会记录相对强弱指数指标值得数量 
int    bars_calculated=0;
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                      |

指标头部代码行已经填充完毕,这样我们现在就可以继续编辑 OnInit() 函数了。

3.3. 编辑指标的OnInit()函数 

因为我们在开发指标,我们指标的缓冲区必须与之前声明的双精度浮点类型的动态数组相联系:

int OnInit()
  {
//--- 指标缓冲区映射
   SetIndexBuffer(0,ExtMapBuffer1,INDICATOR_DATA);
   SetIndexBuffer(1,ExtMapBuffer2,INDICATOR_DATA);
   SetIndexBuffer(2,ExtMapBuffer3,INDICATOR_CALCULATIONS);
//---
   return(INIT_SUCCEEDED);

然后,在连接缓冲区与数组之后,我们将设置数组元素在时间序列中的索引方式(在ArraySetAsSeries中有很好的例子),最右边的数组元素的索引为 "0" :

   SetIndexBuffer(1,ExtMapBuffer2,INDICATOR_DATA);
   SetIndexBuffer(2,ExtMapBuffer3,INDICATOR_CALCULATIONS);
   ArraySetAsSeries(ExtMapBuffer1,true);
   ArraySetAsSeries(ExtMapBuffer2,true);
   ArraySetAsSeries(ExtMapBuffer3,true);
//---
   return(INIT_SUCCEEDED);

现在,让我们定义渲染的校正 - 指标将以两位小数的精度进行显示:

   ArraySetAsSeries(ExtMapBuffer3,true);
//--- 设置精度 
   IndicatorSetInteger(INDICATOR_DIGITS,2);
//---
   return(INIT_SUCCEEDED);

 还需要在 OnInit() 函数中做以下部分: 取得RSI指标的句柄, 填充short_name变量以及给我们的指标赋予一个短名称:

   ArraySetAsSeries(ExtMapBuffer2,true);
   ArraySetAsSeries(ExtMapBuffer3,true);
//--- 设置精度 
   IndicatorSetInteger(INDICATOR_DIGITS,2);
   handle=iRSI(name,0,ExtRSIPeriod,PRICE_CLOSE);
//--- 如果句柄没有创建成功 
   if(handle==INVALID_HANDLE)
     {
      //--- 通知失败消息并输出错误编号 
      PrintFormat("在交易品种 %s/%s 上创建iRSI指标句柄失败, 错误编号 %d",
                  name,
                  EnumToString(PERIOD_CURRENT),
                  GetLastError());
      //--- 指标提前退出 
      return(INIT_FAILED);
     }
//--- 在交易品种/时段图表上显示计算出来的RSI区域指标 
   short_name=StringFormat("RSIArea(%d)",ExtRSIPeriod);
   IndicatorSetString(INDICATOR_SHORTNAME,short_name);
//--- 指标正常初始化 
   return(INIT_SUCCEEDED);

这样,我们已经完成了指标头部代码行以及OnInit()函数。您可以在文章末尾看到编辑过的代码 - 指标文件保存的名称是 "RSIAreaIndicatorStep2.mq5".  

3.4. 创建额外的指标函数 

对于RSIAreaIndicator 的运行, 每次进入OnCalculate()函数时必须取得来自RSI指标的数据,提供条件使得代码阅读更加方便以及把程序的功能做划分是同等重要的,因此,用于取得RSI数值并把它们复制到 RSIAreaIndicator 缓冲区的另外代码移动到独立的函数中,名称为 FillArrayFromBuffer()。它将放在 OnCalculate() 之后。数值的复制是使用CopyBuffer函数的。

//--- 返回prev_calculated的值以便下一次调用
   return(rates_total);
  }
//+------------------------------------------------------------------+ 
//| 使用 iRSI 指标值填充指标缓冲区                             | 
//+------------------------------------------------------------------+ 
bool FillArrayFromBuffer(double &rsi_buffer[],  // 用于相对强弱指数值的指标缓冲区 
                         int ind_handle,        // iRSI 指标的句柄 
                         int amount             // 复制数值的数量 
                         )
  {
//--- 重设错误代码 
   ResetLastError();
//--- 使用索引为0开始的指标缓冲区值填充iRSIBuffer数组 
   if(CopyBuffer(ind_handle,0,0,amount,rsi_buffer)<0)
     {
      //--- 如果复制失败输出错误编号 
      PrintFormat("从iRSI指标复制数据失败, 错误编号 %d",GetLastError());
      //--- 使用零结果退出 - 这是意味着指标还没有计算 
      return(false);
     }
//--- 一切正常 
   return(true);
  }
//+------------------------------------------------------------------+

3.5. 创建指标的主运行代码 

RSIAreaIndicator 的主运行代码(或逻辑)在 OnCalculate() 函数之中,主要的values_to_copy变量就在此声明。然后, values_to_copy 将保存必须从RSI指标复制数值的数量。

                const int &spread[])
  {
//--- 从iRSI指标复制数值的数量 
   int values_to_copy;
//--- 确定指标中已经计算的数值数量 
   int calculated=BarsCalculated(handle);
   if(calculated<=0)
     {
      PrintFormat("BarsCalculated() 返回 %d, 错误编号 %d",calculated,GetLastError());
      return(0);
     }
//--- 返回prev_calculated的值以便下一次调用
   return(rates_total);
  }

values_to_copy的计算:

      PrintFormat("BarsCalculated() 返回 %d, 错误编号 %d",calculated,GetLastError());
      return(0);
     }
//--- 如果这是第一次计算指标值或者iRSI指标值数量改变 
//---或者有需要计算两个以上柱的指标值 (意思是价格历史有变) 
   if(prev_calculated==0 || calculated!=bars_calculated || rates_total>prev_calculated+1)
     {
      //--- 如果iRSIBuffer数组大于对于某交易品种/时段的iRSI指标值的数量,则不复制任何内容 
      //--- 否则,我们复制少于指标缓冲区大小的部分 
      if(calculated>rates_total) values_to_copy=rates_total;
      else                       values_to_copy=calculated;
     }
   else
     {
      //--- 这里的意思是指标不是第一次计算,而是从上次调用OnCalculate()开始的 
      //--- 要多计算一个柱 
      values_to_copy=(rates_total-prev_calculated)+1;
     }
//--- 返回prev_calculated的值以便下一次调用
   return(rates_total);

为什么values_to_copy要用这种方法特别计算呢?在一个MQL5指标中,数组元素 (time[], open[], high[], low[], close[], tick_volume[], volume[] 以及 spread[]) 被传送到 OnCalculate() 函数, 是从开始到结束有索引的。这就是它在图表上看起来的样子:

数组与序列不同

图 6. 数组元素的索引, 如果数组不是时间序列的话。 

它的意思是数组最右边的元素索引值最大,和时间序列不同. 这在进行数学计算时必须考虑到。

现在计算了values_to_copy,您就可以调用 FillArrayFromBuffer() 函数了, 并且使用数值填充ExtMapBuffer1[] 和 ExtMapBuffer2[]指标缓冲区:

      //--- 要多计算一个柱 
      values_to_copy=(rates_total-prev_calculated)+1;
     }
//--- 使用iRSI指标的值填充数组 
//--- 如果 FillArrayFromBuffer 返回 false, 意思是信息还没有准备好,就要退出运行 
   if(!FillArrayFromBuffer(ExtMapBuffer3,handle,values_to_copy)) return(0);
//---
   for(int i=0;i<values_to_copy;i++)
     {
      ExtMapBuffer1[i]=ExtMapBuffer2[i]=ExtMapBuffer3[i]-50.0;
     }
//--- 记住相对强弱指数指标中数值的数量 
   bars_calculated=calculated;
//--- 返回prev_calculated的值以便下一次调用
   return(rates_total);

RSIAreaIndicator 版本 1.00 已经准备好了,您可以在文章末尾下载名为 "RSIAreaIndicatorv1.mq5" 的指标。现在,我们可以继续开发将使用区域方法的 RSIAreaEA 版本 1.00 EA交易。


4. RSIAreaExpert 版本 1.00 EA交易

与指标的方法相同,我建议把创建的EA交易移动到独立的目录中,比如,我的EA交易文件夹就叫作 "MyExp"。与指标类似,我们将创建一个 RSIAreaExpert_v1 EA交易的草稿。重要提醒: 所有的选项在步骤中都不要勾选:


 

 图 7. 创建EA交易的设置

取得的EA交易草稿可以在文章末尾找到 - EA交易名为 "RSIAreaExpert_v1_Step1.mq5"。 

4.1. 编辑EA交易的开头部分

让我们加上EA交易的描述,它将出现在EA交易的“通用”页面:

#property version   "1.00"
#property description "EA trades on \"Method areas\""
//+------------------------------------------------------------------+
//| EA 初始化函数                              |

立即写下描述看起来可能有些麻烦,但是最终这是很有帮助的。 

本EA交易将使用标准库 (CTrade类) 来进行交易。为此,必须包含CTrade类,还要声明my_trade变量:

#property description "EA trades on \"Method areas\""
#include <Trade\Trade.mqh>
//--- 全局变量
CTrade      my_trade;
//+------------------------------------------------------------------+
//| EA 初始化函数                              |

我们将增加三个变量 (用来保存相对强弱指数指标的句柄,当前计算的区域以及一个辅助变量):

//--- 全局变量
CTrade      my_trade;
int         handle;     // 用于保存iRSI指标句柄的变量 
double      RSIArea;    // 计算的区域
double      RSIOpen;    // 辅助变量
//+------------------------------------------------------------------+
//| EA 初始化函数                              |

编辑EA交易头部代码的最后一步是添加输入参数:

double      RSIOpen;    // 辅助变量
//--- 输入参数
input int   ExtRSIPeriod=13;    // RSI周期数
input int   AreaCondition=300;  // 区域
input ENUM_TIMEFRAMES period=PERIOD_M15;
//+------------------------------------------------------------------+
//| EA 初始化函数                              |

引入period变量只是为了方便在策略测试器的测试 - 通过这种方式我们可以设置各种不同的测试周期范围:


图 8. period 变量时的EA交易可以在交大的时段范围中进行测试。 

EA交易的头部代码已经写好了,下面我们切换到 OnInit() 函数,OnInit() 只有一个操作 - 根据当前的交易品种 (Symbol()) 和提供的时段 (period)取得指标的句柄:

int OnInit()
  {
//---
   handle=iRSI(Symbol(),period,ExtRSIPeriod,PRICE_CLOSE);
//--- 如果句柄没有创建成功 
   if(handle==INVALID_HANDLE)
     {
      //--- 通知失败消息并输出错误编号 
      PrintFormat("在交易品种 %s/%s 上创建iRSI指标句柄失败, 错误编号 %d",
                  Symbol(),
                  EnumToString(period),
                  GetLastError());
      //--- 指标提前退出 
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }

所有在编辑EA交易过程中的修改都可以在"RSIAreaExpert_v1_Step2.mq5"文件中看到。

4.2. 另外的RSIAreaFunc函数 

用于判断区域的 RSIAreaFunc() 函数分为几个功能部分,我们将渐进地增加功能。第一个模块 (在代码之后做解释):

void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+
//| 区域计算                             |
//+------------------------------------------------------------------+
double RSIAreaFunc(int &RSIAreaShift,int BeginShift)
  {
   int    shift,limit;
   double rsivalue,result;
//--- 取得当前的RSI 
   limit=Bars(Symbol(),period)-ExtRSIPeriod;
   if(limit>100)
      limit=100;
   double   arr_rsi[];
   ArrayResize(arr_rsi,limit);
   ArraySetAsSeries(arr_rsi,true);
   if(CopyBuffer(handle,0,0,limit,arr_rsi)==-1)
     {
      Print("从iRSI CopyBuffer 失败,没有数据。");
      return(0);
     }
   return(result);
  }

limit变量是我们将使用CopyBuffer函数把iRSI指标值复制到arr_rsi[]数组中的指标值的数量。我们把limit变量限制设为 "100" — 也就是说,我们总是复制iRSI指标最近的100个指标值。编辑EA交易所做的修改可以参见 "RSIAreaExpert_v1_Step3.mq5"。

4.3测试 CopyBuffer 的代码 

如果您还没有完全理解CopyBuffer是如何运作的,以及还不清楚数组中"0"索引是什么值,您可以写一个简单的测试代码:我们将在OnTick()中调用所增加的RSIAreaFunc()函数。

void OnTick()
  {
//---
   static int RSIAreaShift=0;
   RSIAreaFunc(RSIAreaShift,0);
  }
//+------------------------------------------------------------------+
//| 区域计算                             |
//+------------------------------------------------------------------+
double RSIAreaFunc(int &RSIAreaShift,int BeginShift)

在RSIAreaFunc()函数之后,我们将加上注释的显示 - arr_rsi[]数组中第一个和最后一个元素的值:

   if(CopyBuffer(handle,0,0,limit,arr_rsi)==-1)
     {
      Print("从iRSI CopyBuffer 失败,没有数据。");
      return(0);
     }
//---
   Comment("arr_rsi[",limit-1,"]=",DoubleToString(arr_rsi[limit-1],2),
           "; arr_rsi[0]=",DoubleToString(arr_rsi[0],2));
   return(result);
  }

这是一段测试代码,只会加到 RSIAreaExpert_v1_Step3_check.mq5文件中, 而不会在主EA交易中出现。还需要进行以下操作以进行测试:

  • 编译(如果还没有做过)EA的文件 — RSIAreaExpert_v1_Step3_check.mq5;
  • 打开任意交易品种的新图表,并且把时段调整到M15(因为默认的输入参数,period=PERIOD_M15);
  • 把RSI指标放到图表上 (菜单 "插入" -> "技术指标" -> "震荡指标" ->  "相对强弱指数", 设置为: "周期数" 13 和 "应用于" 收盘价);
  • RSIAreaExpert_v1_Step3_check.mq5 EA交易添加到图表上。

图表上会立即显示出arr_rsi数组中"0"索引元素的值对应着RSI指标表最右柱的数值:

 检验 CopyBuffer 函数 

图 9. 测试 CopyBuffer 

4.4. 进一步编辑其他函数

RSIAreaFunc() 函数的下一个部分:

   if(CopyBuffer(handle,0,0,limit,arr_rsi)==-1)
     {
      Print("从iRSI CopyBuffer 失败,没有数据。");
      return(0);
     }

   result=arr_rsi[0]-50.0; // 索引为0的柱的值

   for(shift=BeginShift+1;shift<limit;shift++)
     {
      rsivalue=arr_rsi[shift]-50;
      if((result>0 && rsivalue<-3) || (result<0 && rsivalue>3))
        {
         RSIAreaShift=shift;
         break;
        }
      result+=rsivalue;
     }
   return(result);
  }

首先,result变量设为RSI指标中最右边柱减去50的值,然后,后面是arr_rsi数组的循环, 从索引为"1"的元素知道索引为limit-1 的元素。在循环中检查以下的条件: "有与零线交叉的情况吗?". 如果有交叉,柱的索引(从右往左计算)就保存到RSIAreaShift变量中。

4.5. EA交易的 OnTick() 函数

我们已经编辑好了RSIAreaFunc()函数,可以转到主交易函数 OnTick()了,让我们在 OnTick() 中加入以下代码: 

//+------------------------------------------------------------------+
//| EA 订单处理函数                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   static int RSIAreaShift=0;
   int      shift;
   double   RSICurrent,RSILocalMin,RSILocalMax,value;
   double   arr_rsi[1],rsi;
   MqlTick  last_tick;
//---
   if(CopyBuffer(handle,0,0,1,arr_rsi)==-1)
     {
      Print("从iRSI CopyBuffer 失败,没有数据。");
      return;
     }
   rsi=arr_rsi[0];
//--- 
   if(!SymbolInfoTick(Symbol(),last_tick))
      Print("SymbolInfoTick() 失败, 错误编号 = ",GetLastError());
//---
  }

通过使用我们已经熟悉的 CopyBuffer 函数,我们获得了RSI指标最右边柱的值,然后把它赋予 rsi 变量,我们将在代码中多次引用这个变量。然后我们将取得该交易品种的当前价格,并把它们保存到last_tick变量中。

下一部分代码处理的就是我们在这种资产中有一个已有仓位的状况:

   if(!SymbolInfoTick(Symbol(),last_tick))
      Print("SymbolInfoTick() 失败, 错误编号 = ",GetLastError());
//--- 检查建仓条件
   if(!PositionSelect(Symbol()))
     {
      RSIArea=RSIAreaFunc(RSIAreaShift,0);
      //--- 检查建立买入仓位的条件
      if(RSIArea<-AreaCondition)
        {
         my_trade.Buy(1.0,NULL,last_tick.ask,0.0,0.0,NULL);
         RSIOpen=rsi;
         return;
        }
      //--- 检查建立卖出仓位的条件
      if(RSIArea>AreaCondition)
        {
         my_trade.Sell(1.0,NULL,last_tick.bid,0.0,0.0,NULL);
         RSIOpen=rsi;
         return;
        }
      RSIAreaShift=0;
     }
//---
  }
//+------------------------------------------------------------------+
//| 区域计算                             |

在代码中检查建仓条件: 如果在这个点计算的区域 ( RSIArea 变量) 是小于/大于输入参数 (AreaCondition)的, 则会建立 买入/卖出仓位。

RSICurrent变量使用rsi变量的值赋值 (让我提醒您一下,这是RSI指标最右边柱的值),然后在OnTick()函数中检查出场条件:

  • 如果仓位是在 "50" 线(RSIOpen>50)以上建立的, 而我们现在还在 "50" 线(RSICurrent>50)以上;
  • 如果仓位是在 "50" 线(RSIOpen<50)以下建立的, 并且我们现在还在 "50" 线(RSICurrent<50)以下:
      RSIAreaShift=0;
     }
   RSICurrent=rsi;
   if(RSIOpen>50 && RSICurrent>50) return;
   if(RSIOpen<50 && RSICurrent<50) return;

   RSILocalMin = RSICurrent;
   RSILocalMax = RSICurrent;

//---
  }
//+------------------------------------------------------------------+
//| 区域计算                             |

以下的代码是查找局部的最小值/最大值并把它们的值赋予RSILocalMinRSILocalMax变量:

   RSILocalMin = RSICurrent;
   RSILocalMax = RSICurrent;
   
//--- 搜索局部最小值/最大值
   if(RSIAreaShift>1)
     {
      double   arr_rsi_1[];
      ArrayResize(arr_rsi_1,RSIAreaShift);
      ArraySetAsSeries(arr_rsi_1,true);
      if(CopyBuffer(handle,0,0,RSIAreaShift,arr_rsi_1)==-1)
        {
         Print("从 iRSI CopyBuffer 失败,没有数据");
         return;
        }
      for(shift=1; shift<RSIAreaShift; shift++)
        {
         value=arr_rsi_1[shift];
         if(value<RSILocalMin && RSIArea>0) RSILocalMin=value;
         if(value>RSILocalMax && RSIArea<0) RSILocalMax=value;
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| 区域计算                             |

然后是最后部分的代码:

         if(value>RSILocalMax && RSIArea<0) RSILocalMax=value;
        }
     }

//--- 检查回滚
   if(PositionSelect(Symbol()))
     {
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
        {
         //--- 检查是否是平仓时间
         if(RSILocalMax>=RSICurrent+4 && RSILocalMax>50)
            my_trade.PositionClose(Symbol(),20);
        }
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
        {
         //--- 检查是否是平仓时间
         if(RSILocalMin<=RSICurrent-4 && RSILocalMin<50)
            my_trade.PositionClose(Symbol(),20);
        }
     }
//---
   return;
  }
//+------------------------------------------------------------------+
//| 区域计算                             |

如果有建立的仓位,平仓条件按照以下原则在此检查:

RSI 震荡指标穿过了 50 线,并形成了局部最小值/最大值,然后回滚尺度达到 4%.
例如,在超过50线很长时间以后,在某个点我们将建立一个卖出仓位,然后,指标值开始下降并达到了40,然后,指标值又开始上升((也就是说,形成了一个局部最小值). 当指标值达到44的时候,就是平仓信号。

现阶段 RSIAreaExpert_v1 EA交易就创建完成了,本文末尾的 "RSIAreaExpert_v1.mq5" 文件可以下载。


5. 在不同的时段和交易品种中测试 RSIAreaExpert 版本 1.00

RSIAreaExpert EA交易的测试最初是在 H1 [1] 图表上进行的, 但是,自2004年以来,市场波动性加大了,所以我们决定在从M10到H6的更宽范围的时段中测试区域方法的效果。另外测试的区域大小也从100猛增到800,测试的时间段从 2015.01.05 到 2016.01.05。

这是在AUDCAD交易品种下测试RSIAreaExpert版本1的EA交易的结果:

 RSIAreaExpert 版本1 AreaCondition 在 AUDCAD 上的获利

图 10.  RSIAreaExpert 版本 1 EA交易的测试结果. AUDCAD 交易品种. 区域间隔 100-800. 时段间隔 M10-H6 

我们看到,在H2时段中结果密度很高,H3 时段也很值得关注,现在,让我们看下图评估在AUDCAD上测试RSIAreaExpert版本1EA交易中一年来交易的数量:

RSIAreaExpert 版本 1 在 AUDCAD 上的获利

图 11.  RSIAreaExpert 版本 1 EA交易的测试结果。AUDCAD 交易品种. 区域间隔 100-800. 时段间隔 M10-H6 

在一年中,H2 和 H3 时段的交易数量达到了50,这不是很多,并且出错率很高,所以,我们可以断定,区域方法在AUDCAD上工作得不够好。

RSIAreaExpert 版本 1 EA交易在AUDUSD上的测试结果: 

RSIAreaExpert 版本 1 AreaCondition 在 AUDUSD 上的获利 

 图 12.  RSIAreaExpert 版本 1 EA交易的测试结果。AUDUSD 交易品种. 区域间隔 100-800. 时段间隔 M10-H6 

在研究本策略在 AUDUSD 上的获利状况时, 在 H2 和 H3 时段上进行交易,AreaCondition 参数在这些时段中在250到400之间变化,为了评价在H2和H3时段的交易,您必须在一年中查看这些时段的交易数量:

RSIAreaExpert 版本1 在 AUDUSD 上的获利状况 

 Fig. 13.  RSIAreaExpert 版本 1 EA交易的测试结果。AUDUSD 交易品种. 区域间隔 100-800. 时段间隔 M10-H6 

我们可以看到, 它的值相当低,所以,也不推荐在AUDUSD上使用区域方法。

EURUSD交易品种中测试 RSIAreaExpert 版本1 EA交易的结果:

RSIAreaExpert 版本1 在 EURUSD 上的获利状况

 图 14. RSIAreaExpert EA交易的测试结果. 区域间隔 100-800. 时段间隔 M10-H6  

在图14中显示, M10时段获利结果密度很高, 范围是400到550,而M12时段 - 范围从300到400,而对于M15时段 - 从300到400。更高的时段就不考虑了,因为它们在一年中的交易数量太少了(参见图15).

图15 中图表依赖关系揭示了EURUSD上利润与交易数量的关系:

RSIAreaExpert 版本 1 在EURUSD上的获利状况

图 15. RSIAreaExpert EA交易的测试结果. 区域间隔 100-800. 时段间隔 M10-H6 

很明显地可以看出,在较高的时段(从H1到H6),应用区域方法时,交易的数量要少得多。然而,在M10, M12, M15上的交易数量已经足以证明,区域方法在这些时段可以获利。EURUSD 是绝对适合基于区域方法做交易的。

GBPUSD上测试RSIAreaExpert版本1 EA交易的结果:

RSIAreaExpert 版本 1 在 GBPUSD 上的获利 

 Fig. 16.  RSIAreaExpert 版本 1 EA交易的测试结果。GBPUSD 交易品种. 区域间隔 100-800. 时段间隔 M10-H6 

在GBPUSD M20时段有密集的获利,AreaCondition 参数是从 300 到 500。

RSIAreaExpert 版本1 在 GBPUSD 上的获利 

Fig. 17.  RSIAreaExpert 版本 1 EA交易的测试结果。GBPUSD 交易品种. 区域间隔 100-800. 时段间隔 M10-H6  

对于 GBPUSD 在 M20 时段每年的交易数量在140到250之间,这频率不算惊人,但是也很可观了。换句话说,在GBPUSD上使用区域方法对每个人都是适合的。 

USDCAD 交易品种中测试 RSIAreaExpert 版本1 EA 交易的结果:

 

图 18.  RSIAreaExpert 版本 1 EA交易的测试结果。USDCAD 交易品种. 区域间隔 100-800. 时段间隔 M10-H6  

对于 USDCAD 交易品种,我将只会考虑 M30 时段,因为只有在这个时段有密集的获利。同时,AreaCondition 参数在280到550之间变化。

RSIAreaExpert 版本1 在USDCAD上的获利 

 Fig. 19.  RSIAreaExpert 版本 1 EA交易的测试结果。USDCAD 交易品种. 区域间隔 100-800. 时段间隔 M10-H6  

这个货币对上在M30时段每年的交易数量在90到200之间,这并不算多,所以我不推荐在USDCAD上使用区域方法,

USDJPY交易品种上测试 RSIAreaExpert 版本1 EA交易的结果:

RSIAreaExpert 版本1 在USDJPY上的获利 

图 20.  RSIAreaExpert 版本 1 EA交易的测试结果。USDJPY 交易品种区域间隔 100-800. 时段间隔 M10-H6  

在USDJPY交易品种中,M10和M30两个时段有很大不同,对于M10时段,AreaCondition 参数在320到650之间, 而对于M30时段是 — 550-600。

RSIAreaExpert 版本1在 USDJPY 上的获利 

图 21.  RSIAreaExpert 版本 1 EA交易的测试结果。USDJPY 交易品种区域间隔 100-800. 时段间隔 M10-H6  

在USDJPY M10上使用区域方法每年交易的数量在150到200之间,而对于M30时段,每年交易在50到150之间,因而,我们就知道了在这个交易品种中我们并不十分推荐区域方法。


结论

可以看出,基于区域方法的交易不能十分随意,事实上,在当前的市场条件下,在H1时段使用本系统会造成亏损,然而,之前[1]还是能够获利的。在当代市场中,在EURUSD的M10, M12 和M15时段使用区域方法被证明是最能够获利和最高效的,测试显示,对于这个特别的货币对,每年能够进行足够数量的交易。

 

参考列表

  1. Morozov I. V., Fatkhullin R.R. 外汇交易:从简单到复杂(FOREX: from simple to complex). "MetaTrader"客户终端的新机遇. - "Teletrade LTD", 2004. - 448 p.
如何从文章末尾的档案中安装指标和EA交易: "Indicators.zip" 和 "Experts.zip" 档案必须解压缩,并移动到 <数据目录>\MQL5\ 文件夹下。

全部回复

0/140

量化课程

    移动端课程