利用解析入场点为指标的技术创建新的交易策略

20 十二月 2017, 06:16
Dmitriy Gizlyk
0
6 460

引言

众所周知, 只有 5% 的交易者在金融市场上获得了稳定的盈利, 但其实 100% 的人都想实现这一目标。 

若要成功的进行交易, 您需要一个可盈利的交易策略。与主题相关的网站和交易文献描述了数以百计的各种交易策略。所有指标都附有详尽的信号解释, 但是统计数据依旧保持不变: 5% 的数字既没有变成 100, 也未能到达至少 10。交易思想家们指责市场不稳定, 这就意味着早期的可盈利策略均会失去效用。

在我 之前的文章 中我已谈过有关解析入场信息为指标, 并展示了一个提升已有策略的示例。现在我提议使用这种特别的技术, "用一张白纸" 创建自定义策略。这令我们能够以 "全新的眼光" 来审视您所知道的指标, 收集自定义指标模板, 并反思它们的信号。所建议技术的应用, 意味着解释指标信号的创造性方法, 令每个用户均能创建自己独具特色的策略。

1. 创建一个测试和分析模型

我们在交易终端看到的第一件事就是持续价格走势。潜意识里, 随时开单交易, 我们都会盈利。但您如何判定下一刻价格将会在哪里以及如何汇集呢?交易者试图在技术和基本面分析中找到这个问题的答案。为了进行技术分析, 各项指标不断被发明并完善。本文的新颖之处在于解释这些指标信号; 它也许与常见的不同。

因此, 将入场信点解析为指标的技术意即将开仓与指标值进行比较。一旦再次出现同等状况, 或许, 那时我们还能盈利。在这些输入数据的基础上, 在每根蜡烛开盘时依据预设参数双向开仓。然后, 分析每笔交易的盈利因子如何依赖指标值。

为了解决这个问题, 需要进行一些少量的准备工作。

1.1. 创建虚拟订单类

我使用净持帐户。因此, 若要双向开单, 我只能创建虚拟订单, 即终端 (根据账户设置) 不会跟踪订单, 而是由智能交易系统跟踪交易。为此目的而创建 CDeal 类。当初始化一个类实例时, 我们将传递给它: 品名, 仓位类型, 开仓时间和价格, 以及止损和止盈。仓量被故意省略, 因为我们对此没有兴趣。对我们来说重要的是价格走势, 因此利润/损失将以点数计算而非币值。

为了检查仓位状态, 类中增加了一些服务函数:

  • IsClosed — 返回逻辑值, 仓位是否已平仓;
  • Type - 返回仓位类型;
  • GetProfit — 返回已平仓的利润 (亏损值将是负数);
  • GetTime — 返回开仓时间。

class CDeal          :  public CObject
  {
private:
   string               s_Symbol;
   datetime             dt_OpenTime;         // 开仓时间
   double               d_OpenPrice;         // 开仓价格
   double               d_SL_Price;          // 仓位的止损位
   double               d_TP_Price;          // 仓位的止盈位
   ENUM_POSITION_TYPE   e_Direct;            // 开仓方向
   double               d_ClosePrice;        // 平仓价格
   int                  i_Profit;            // 仓位盈利的点值
//---
   double               d_Point;
   
public:
                     CDeal(string symbol, ENUM_POSITION_TYPE type,datetime time,double open_price,double sl_price, double tp_price);
                    ~CDeal();
   //--- 检查状态
   bool              IsClosed(void);
   ENUM_POSITION_TYPE Type(void)    {  return e_Direct;    }
   double            GetProfit(void);
   datetime          GetTime(void)  {  return dt_OpenTime;  }
   //---
   void              Tick(void);
  };

到达的分笔报价由 Tick 函数处理, 按照止损位或止盈位检查何处必须平仓, 并保存累计利润。

void CDeal::Tick(void)
  {
   if(d_ClosePrice>0)
      return;
   double price=0;
   switch(e_Direct)
     {
      case POSITION_TYPE_BUY:
        price=SymbolInfoDouble(s_Symbol,SYMBOL_BID);
        if(d_SL_Price>0 && d_SL_Price>=price)
          {
           d_ClosePrice=price;
           i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point);
          }
        else
          {
           if(d_TP_Price>0 && d_TP_Price<=price)
             {
              d_ClosePrice=price;
              i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point);
             }
          }
        break;
      case POSITION_TYPE_SELL:
        price=SymbolInfoDouble(s_Symbol,SYMBOL_ASK);
        if(d_SL_Price>0 && d_SL_Price<=price)
          {
           d_ClosePrice=price;
           i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point);
          }
        else
          {
           if(d_TP_Price>0 && d_TP_Price>=price)
             {
              d_ClosePrice=price;
              i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point);
             }
          }
        break;
     }
  }

1.2. 创建处理指标的类

为了保存和分析指标数据, 我利用了 前一篇文章 中详细介绍过的类。还有, 在此我创建了 CDealsToIndicators 类, 囊括了所有的指标类。它将存储指标类数组并分配它们的功能。

class CDealsToIndicators
  {
private:
   CADX              *ADX[];
   CAlligator        *Alligator[];
   COneBufferArray   *OneBuffer[];
   CMACD             *MACD[];
   CStaticOneBuffer  *OneBufferStatic[];
   CStaticMACD       *MACD_Static[];
   CStaticADX        *ADX_Static[];
   CStaticAlligator  *Alligator_Static[];
   
   template<typename T>
   void              CleareArray(T *&array[]);

public:
                     CDealsToIndicators();
                    ~CDealsToIndicators();
   //---
   bool              AddADX(string symbol, ENUM_TIMEFRAMES timeframe, int period, string name);
   bool              AddADX(string symbol, ENUM_TIMEFRAMES timeframe, int period, string name, int &handle);
   bool              AddAlligator(string symbol,ENUM_TIMEFRAMES timeframe,uint jaw_period, uint jaw_shift, uint teeth_period, uint teeth_shift, uint lips_period, uint lips_shift, ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price, string name);
   bool              AddAlligator(string symbol,ENUM_TIMEFRAMES timeframe,uint jaw_period, uint jaw_shift, uint teeth_period, uint teeth_shift, uint lips_period, uint lips_shift, ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price, string name, int &handle);
   bool              AddMACD(string symbol, ENUM_TIMEFRAMES timeframe, uint fast_ema, uint slow_ema, uint signal, ENUM_APPLIED_PRICE applied_price, string name);
   bool              AddMACD(string symbol, ENUM_TIMEFRAMES timeframe, uint fast_ema, uint slow_ema, uint signal, ENUM_APPLIED_PRICE applied_price, string name, int &handle);
   bool              AddOneBuffer(int handle, string name);
   //---
   bool              SaveNewValues(long ticket);
   //---
   bool              Static(CArrayObj *deals);
  };

1.3. 创建智能交易系统进行测试

所有准备就绪。现在, 开始创建将要在策略测试器中工作的 EA。首先, 定义所应用的指标列表及其参数。为了演示技术, 我选取了以下指标:

  • ADX;
  • Alligator;
  • CCI;
  • Chaikin;
  • Force Index;
  • MACD.

它们当中的每一个, 均要创建三组参数, 在三个时间帧上跟踪数据。

止损和止盈交易必须与 ATR 指标值绑定, 并通过盈利风险比率来设定。

//--- 输入参数
input double            Reward_Risk    =  1.0;
input int               ATR_Period     =  288;
input ENUM_TIMEFRAMES   TimeFrame1     =  PERIOD_M5;
input ENUM_TIMEFRAMES   TimeFrame2     =  PERIOD_H1;
input ENUM_TIMEFRAMES   TimeFrame3     =  PERIOD_D1;
input string            s1                =  "ADX"                ;  //---
input uint              ADX_Period1       =  14                   ;
input uint              ADX_Period2       =  28                   ;
input uint              ADX_Period3       =  56                   ;
input string            s2                =  "Alligator"          ;  //---
input uint              JAW_Period1       =  13                   ;
input uint              JAW_Shift1        =  8                    ;
input uint              TEETH_Period1     =  8                    ;
input uint              TEETH_Shift1      =  5                    ;
input uint              LIPS_Period1      =  5                    ;
input uint              LIPS_Shift1       =  3                    ;
input uint              JAW_Period2       =  26                   ;
input uint              JAW_Shift2        =  16                   ;
input uint              TEETH_Period2     =  16                   ;
input uint              TEETH_Shift2      =  10                   ;
input uint              LIPS_Period2      =  10                   ;
input uint              LIPS_Shift2       =  6                    ;
input uint              JAW_Period3       =  42                   ;
input uint              JAW_Shift3        =  32                   ;
input uint              TEETH_Period3     =  32                   ;
input uint              TEETH_Shift3      =  20                   ;
input uint              LIPS_Period3      =  20                   ;
input uint              LIPS_Shift3       =  12                   ;
input ENUM_MA_METHOD    Alligator_Method  =  MODE_SMMA            ;
input ENUM_APPLIED_PRICE Alligator_Price  =  PRICE_MEDIAN         ;
input string            s5                =  "CCI"                ;  //---
input uint              CCI_Period1       =  14                   ;
input uint              CCI_Period2       =  28                   ;
input uint              CCI_Period3       =  56                   ;
input ENUM_APPLIED_PRICE CCI_Price        =  PRICE_TYPICAL        ;
input string            s6                =  "Chaikin"            ;  //---
input uint              Ch_Fast_Period1   =  3                    ;
input uint              Ch_Slow_Period1   =  14                   ;
input uint              Ch_Fast_Period2   =  6                    ;
input uint              Ch_Slow_Period2   =  28                   ;
input uint              Ch_Fast_Period3   =  12                   ;
input uint              Ch_Slow_Period3   =  56                   ;
input ENUM_MA_METHOD    Ch_Method         =  MODE_EMA             ;
input ENUM_APPLIED_VOLUME Ch_Volume       =  VOLUME_TICK          ;
input string            s7                =  "Force Index"        ;  //---
input uint              Force_Period1     =  14                   ;
input uint              Force_Period2     =  28                   ;
input uint              Force_Period3     =  56                   ;
input ENUM_MA_METHOD    Force_Method      =  MODE_SMA             ;
input ENUM_APPLIED_VOLUME Force_Volume    =  VOLUME_TICK          ;
input string            s8                =  "MACD"               ;  //---
input uint              MACD_Fast1        =  12                   ;
input uint              MACD_Slow1        =  26                   ;
input uint              MACD_Signal1      =  9                    ;
input uint              MACD_Fast2        =  24                   ;
input uint              MACD_Slow2        =  52                   ;
input uint              MACD_Signal2      =  18                   ;
input uint              MACD_Fast3        =  48                   ;
input uint              MACD_Slow3        =  104                  ;
input uint              MACD_Signal3      =  36                   ;
input ENUM_APPLIED_PRICE MACD_Price       =  PRICE_CLOSE          ;

在全局变量模块中声明:

  • 保存成交类的数组 Deals,
  • 处理指标的类实例 IndicatorsStatic,
  • 存储 ATR 指标句柄的变量,
  • 存储最后已处理柱线 (last_bar) 和最后一笔平仓订单 (last_closed_deal) 时间的两个变量。我们稍后将需要它们, 以便无需在每个分笔报价来临时遍历已平仓位。

在 OnInit 函数中, 执行全局变量和所需指标类的初始化。

int OnInit()
  {
//---
   last_bar=0;
   last_closed_deal=0;
//---
   Deals =  new CArrayObj();
   if(CheckPointer(Deals)==POINTER_INVALID)
      return INIT_FAILED;
//---
   IndicatorsStatic  =  new CDealsToIndicators();
   if(CheckPointer(IndicatorsStatic)==POINTER_INVALID)
      return INIT_FAILED;
//---
   atr=iATR(_Symbol,TimeFrame1,ATR_Period);
   if(atr==INVALID_HANDLE)
      return INIT_FAILED;
//---
   AddIndicators(TimeFrame1);
   AddIndicators(TimeFrame2);
   AddIndicators(TimeFrame3);
//---
   return(INIT_SUCCEEDED);
  }

我们将在三个不同的时间帧内使用同一套指标。这就是为什么将指标类的初始化放入单独的函数 AddIndicators 是合理的。在其参数中将指定所需的时间帧。

bool AddIndicators(ENUM_TIMEFRAMES timeframe)
  {
   if(CheckPointer(IndicatorsStatic)==POINTER_INVALID)
     {
      IndicatorsStatic  =  new CDealsToIndicators();
      if(CheckPointer(IndicatorsStatic)==POINTER_INVALID)
         return false;
     }
   string tf_name=StringSubstr(EnumToString(timeframe),7);
   string name="ADX("+IntegerToString(ADX_Period1)+") "+tf_name;
   if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period1, name))
      return false;
   name="ADX("+IntegerToString(ADX_Period2)+") "+tf_name;
   if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period2, name))
      return false;
   name="ADX("+IntegerToString(ADX_Period3)+") "+tf_name;
   if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period3, name))
      return false;
   name="Alligator("+IntegerToString(JAW_Period1)+","+IntegerToString(TEETH_Period1)+","+IntegerToString(LIPS_Period1)+") "+tf_name;
   if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period1, JAW_Shift1, TEETH_Period1, TEETH_Shift1, LIPS_Period1, LIPS_Shift1, Alligator_Method, Alligator_Price, name))
      return false;
   name="Alligator("+IntegerToString(JAW_Period2)+","+IntegerToString(TEETH_Period2)+","+IntegerToString(LIPS_Period2)+") "+tf_name;
   if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period2, JAW_Shift2, TEETH_Period2, TEETH_Shift2, LIPS_Period2, LIPS_Shift2, Alligator_Method, Alligator_Price, name))
      return false;
   name="Alligator("+IntegerToString(JAW_Period3)+","+IntegerToString(TEETH_Period3)+","+IntegerToString(LIPS_Period3)+") "+tf_name;
   if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period3, JAW_Shift3, TEETH_Period3, TEETH_Shift3, LIPS_Period3, LIPS_Shift3, Alligator_Method, Alligator_Price, name))
      return false;
   name="MACD("+IntegerToString(MACD_Fast1)+","+IntegerToString(MACD_Slow1)+","+IntegerToString(MACD_Signal1)+") "+tf_name;
   if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast1, MACD_Slow1, MACD_Signal1, MACD_Price, name))
      return false;
   name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name;
   if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name))
      return false;
   name="MACD("+IntegerToString(MACD_Fast3)+","+IntegerToString(MACD_Slow3)+","+IntegerToString(MACD_Signal3)+") "+tf_name;
   if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast3, MACD_Slow3, MACD_Signal3, MACD_Price, name))
      return false;
   name="CCI("+IntegerToString(CCI_Period1)+") "+tf_name;
   int handle = iCCI(_Symbol, timeframe, CCI_Period1, CCI_Price);
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   name="CCI("+IntegerToString(CCI_Period2)+") "+tf_name;
   handle = iCCI(_Symbol, timeframe, CCI_Period2, CCI_Price);
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iCCI(_Symbol, timeframe, CCI_Period3, CCI_Price);
   name="CCI("+IntegerToString(CCI_Period3)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iForce(_Symbol, timeframe, Force_Period1, Force_Method, Force_Volume);
   name="Force("+IntegerToString(Force_Period1)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iForce(_Symbol, timeframe, Force_Period2, Force_Method, Force_Volume);
   name="Force("+IntegerToString(Force_Period2)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume);
   name="Force("+IntegerToString(Force_Period3)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   name="CHO("+IntegerToString(Ch_Slow_Period1)+","+IntegerToString(Ch_Fast_Period1)+") "+tf_name;
   handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period1, Ch_Slow_Period1, Ch_Method, Ch_Volume);
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period2, Ch_Slow_Period2, Ch_Method, Ch_Volume);
   name="CHO("+IntegerToString(Ch_Slow_Period2)+","+IntegerToString(Ch_Fast_Period2)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period3, Ch_Slow_Period3, Ch_Method, Ch_Volume);
   name="CHO("+IntegerToString(Ch_Slow_Period3)+","+IntegerToString(Ch_Fast_Period3)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   return true;
  }

在 OnTick 中执行的操作可分为两个部分: 检查持仓和开新仓。

第一个操作模块在每个分笔报价来临时执行。其中, 所有以前开单的交易可从数组中检索, 并为它们当中的每一笔调用 Tick 函数。它检查持仓是否触发止损和止盈, 必要时按照当前价格平仓并保存产生的利润。为了避免重复检查先前的已平仓交易, 在变量 last_closed_deal 中保存了第一笔未平仓交易之前的交易数量。 

void OnTick()
  {
//---
   int total=Deals.Total();
   CDeal *deal;
   bool found=false;
   for(int i=last_closed_deal;i<total;i++)
     {
      deal  =  Deals.At(i);
      if(CheckPointer(deal)==POINTER_INVALID)
         continue;
      if(!found)
        {
         if(deal.IsClosed())
           {
            last_closed_deal=i;
            continue;
           }
         else
            found=true;
        }
      deal.Tick();
     }

第二个操作模块从检查新柱线出现开始。在每根柱线的开头, 下载最后一个已收盘蜡烛的 ATR 指标值, 根据设定的参数计算止损位和止盈位, 并开虚拟仓位。对于每笔仓位保存指标数据, 并调用我们类中的 SaveNewValues 函数来处理指标。

//---
   datetime cur_bar=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);
   datetime cur_time=TimeCurrent();
   if(cur_bar==last_bar || (cur_time-cur_bar)>10)
      return;
   double atrs[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0)
      return;

   last_bar=cur_bar;
   double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
   double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point);
   double sl=NormalizeDouble(stops,_Digits);
   double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits);
   deal  =  new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp);
   if(CheckPointer(deal)!=POINTER_INVALID)
      if(Deals.Add(deal))
         IndicatorsStatic.SaveNewValues(Deals.Total()-1);
   deal  =  new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp);
   if(CheckPointer(deal)!=POINTER_INVALID)
      if(Deals.Add(deal))
         IndicatorsStatic.SaveNewValues(Deals.Total()-1);
   return;
  }

在 OnTester 中, 收集测试运行的结果并构建用于分析的图表。为此目的, 调用类的静态函数来处理指标。

确保在 OnDeinit 函数中清理内存!

附件中提供了完整的 EA 代码和使用的类。

2. 分析测试结果

那么, 我们已经创建了测试 EA。现在, 我们来分析一下这段区间。当选择一段区间时, 要考虑到它应足够长, 以便提供分析的客观性。另一个对于区间的要求: 不仅要包括单向走势, 还应包括双向趋势走势, 以及盘整 (横盘) 走势。这种方式能够创建一个在任何走势区间均可产生利润的交易策略。我的例子分析了自 2016 年 1 月 1 日至 2017 年 1 月 10 日期间的货币对 EURUSD。

测试区间测试参数

迄今为止, 我们的流程将具有反复特性, 我建议为测试 EA 设置好所有必要参数以后, 保存参数为 set-文件以便未来工作。

每个测试阶段将采用利润/风险比率等于 1/1 和 15/1 运行 2 次。第一次运行, 我们将评估方向性走势的概率, 而第二次 - 运动的力度。

EA 显示了大量的分析图表, 在本文中并未全部提供 - 所有报告均已在附件中提供。在此, 我们只显示图表, 通过这些图表来决定在新策略中将会使用哪个指标。

2.1. 阶段一

正如我们所预期的那样, 第一个测试阶段并没有显示出明确的可盈利区域。但请同时注意力度指数指标。在 M5 时间帧, 交易利润图表与指标值的依赖性在零区域下滑。观察到的这一重要性由这样一个事实所证明, 即这个现象出现在所有参数都参与测试的分析指标图表上。为我们的模板所选择参数, 应具有最明显的表征 (最大回撤)。

力度指标分析图表, 时间帧为 M5

我们来放大分析的图表。正如您所看到的, 这个因素的影响在 -0.01 到 0.01 之间。观察到的现象对买入和卖出交易同样适用。

这一观察结果也许是由于在所观测数值范围内缺乏波动性。对于我们的策略, 请确保在此范围内禁止任何开单。

利润依赖力度指标在零值附近。

添加这个滤波器到我们的 EA。为此, 首先添加用于存储指标句柄的全局变量。

int                  force;

至此, 作为滤波器所需的指标已经加入到 EA, 我们就不必再将其挂载到图表上。只需在 AddIndicators 中将其句柄复制到我们的全局变量中即可。但请确认, 不要忘记针对不同时间帧调用这个指标的初始化函数三次。因此, 在复制指标句柄之前, 我们应检查时间帧是否合规。

   handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume);
   if(timeframe==TimeFrame1)
      force=handle;

现在, 立即将滤波器添加到 OnTick。在建筑图表时请记住, 在分析图表的函数里, 构建数据应被舍入。因此, 当过滤交易的指标值时, 也应初步舍入。

   double atrs[];
   double force_data[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0)
      return;      // 加载指标数据时的一些错误

   last_bar=cur_bar;
   double d_Step=_Point*1000;
   if(MathAbs(NormalizeDouble(force_data[0]/d_Step,0)*d_Step)<=0.01)
      return;    // 力度指标过滤

完整的 EA 代码在本文的附件中提供。

在 EA 中添加滤波器后, 执行第二个测试阶段。在测试之前, 请确保下载先前保存的参数。

2.2. 阶段二

经过多轮 EA 测试, 我关注 MACD 指标。图表上出现了盈利区域。

利润对 MACD 直方条数值的依赖图表。

在盈利/风险比率为 15/1 的图表中, 这些区域更为明确; 这也许能说明这些范围内信号的潜力。

利润对 MACD 直方条数值的依赖图表 (profit/risk = 15/1)

这个滤波器也应被添加到我们的 EA 代码中。添加滤波器的逻辑与阶段一描述中所提供的逻辑类似。

加入全局变量:

int                  macd;

加入 AddIndicators 函数:

   name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name;
   if(timeframe==TimeFrame1)
     {
      if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name, macd))
         return false;
     }
   else
     {
      if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name))
         return false;
     }

加入 OnTick:

   double macd_data[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0)
      return;

   double macd_Step=_Point*50;
   macd_data[0]=NormalizeDouble(macd_data[0]/macd_Step,0)*macd_Step;
   if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035)
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }
   if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035))
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }

将滤波器偏移添加到第三测试阶段之后。

2.3. 阶段三

在阶段三, 我关注 Chaikin 振荡器。在时间帧 D1 上的振荡器分析图表中, 我们看到多头仓位的利润在数值降低的情况下增长, 而在指标值增长的情况下, 空头仓位的利润增长。

利润对 Chaikin 振荡器数值的依赖图表。

在分析盈利/风险比率等于 15/1 的图表时, 我的观测也得到了证实。

利润对 Chaikin 振荡器数值的依赖图表。

将我们的观测添加到 EA 代码中。

加入全局变量:

int                  cho;

加入 AddIndicators 函数:

   handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period2, Ch_Slow_Period2, Ch_Method, Ch_Volume);
   name="CHO("+IntegerToString(Ch_Slow_Period2)+","+IntegerToString(Ch_Fast_Period2)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   if(timeframe==TimeFrame3)
      cho=handle;

加入 OnTick:

   double cho_data[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0
      || CopyBuffer(cho,0,1,2,cho_data)<2)
      return;

   if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0)
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }
   if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0)
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }

继续下面的阶段。

2.4. 阶段四

再次测试 EA 之后, 我的注意力再次被时间帧 D1 所吸引。这一次, 我考察了 CCI 指标。其分析图表显示, 在指标值下降时空头仓位利润增长, 而多头仓位利润增长的情况 - 随着指标值的增长。这一趋势在所有的三个研究期间内都被观察到, 但是当振荡器使用的周期为 14 时, 利润达到了最大值。

利润与 CCI 指标值有依赖关系。

在测试中得到的分析图表, 盈利/风险比率等于 15/1 证实了我们的观测。

利润与 CCI 指标值有依赖关系。

将这次观测也添加到测试 EA 代码当中。

加入全局变量:

int                  cci;

至 AddIndicators:

   name="CCI("+IntegerToString(CCI_Period1)+") "+tf_name;
   int handle = iCCI(_Symbol, timeframe, CCI_Period1, CCI_Price);
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   if(timeframe==TimeFrame3)
      cci=handle;

至 OnTick:

   double cci_data[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0
      || CopyBuffer(cho,0,1,2,cho_data)<2 || CopyBuffer(cci,0,1,2,cci_data)<2)
      return;

   if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0 && (cci_data[1]-cci_data[0])>0)
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }
   if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0 && (cci_data[1]-cci_data[0])<0)
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }

所有阶段的完整 EA 代码已在文章的附件中提供。

3. 在选定的信号上创建和测试 EA

完美无极限, 您可以持续分析和增加滤波器来提升策略的盈利因子。但是我相信这四个阶段对技术示范来说足够了。下一步创建的简单 EA, 将在测试器里检验我们的策略。这令我们能够评估自我策略的盈利因子和回撤, 以及余额变化的进度。

在策略中, 我们选用了四个指标制定交易决策, 并利用 ATR 指标设置止损和止盈。因此, 在 EA 输入参数中, 我们应设置指标所需的所有输入信息。在这个阶段, 我们不会创建资金管理, 所有的订单都使用固定交易量。

//--- 输入参数
input double            Lot               =  0.1                  ;
input double            Reward_Risk       =  15.0                 ;
input ENUM_TIMEFRAMES   ATR_TimeFrame     =  PERIOD_M5            ;
input int               ATR_Period        =  288                  ;
input string            s1                =  "CCI"                ;  //---
input ENUM_TIMEFRAMES   CCI_TimeFrame     =  PERIOD_D1            ;
input uint              CCI_Period        =  14                   ;
input ENUM_APPLIED_PRICE CCI_Price        =  PRICE_TYPICAL        ;
input string            s2                =  "Chaikin"            ;  //---
input ENUM_TIMEFRAMES   Ch_TimeFrame      =  PERIOD_D1            ;
input uint              Ch_Fast_Period    =  6                    ;
input uint              Ch_Slow_Period    =  28                   ;
input ENUM_MA_METHOD    Ch_Method         =  MODE_EMA             ;
input ENUM_APPLIED_VOLUME Ch_Volume       =  VOLUME_TICK          ;
input string            s3                =  "Force Index"        ;  //---
input ENUM_TIMEFRAMES   Force_TimeFrame   =  PERIOD_M5            ;
input uint              Force_Period      =  56                   ;
input ENUM_MA_METHOD    Force_Method      =  MODE_SMA             ;
input ENUM_APPLIED_VOLUME Force_Volume    =  VOLUME_TICK          ;
input string            s4                =  "MACD"               ;  //---
input ENUM_TIMEFRAMES   MACD_TimeFrame    =  PERIOD_M5            ;
input uint              MACD_Fast         =  12                   ;
input uint              MACD_Slow         =  26                   ;
input uint              MACD_Signal       =  9                    ;
input ENUM_APPLIED_PRICE MACD_Price       =  PRICE_CLOSE          ;

在全局变量中声明:

  • 执行交易操作的类实例,
  • 保存指标句柄的变量,
  • 记录最后的处理的柱线和上笔交易日期的辅助变量,
  • 存储最大和最小时间帧的变量。

在 OnInit 函数中, 初始化指标并设置变量的初始值。

int OnInit()
  {
//---
   last_bar=0;
   last_deal=0;
//---
   atr=iATR(_Symbol,ATR_TimeFrame,ATR_Period);
   if(atr==INVALID_HANDLE)
      return INIT_FAILED;
//---
   force=iForce(_Symbol,Force_TimeFrame,Force_Period,Force_Method,Force_Volume);
   if(force==INVALID_HANDLE)
      return INIT_FAILED;
//---
   macd=iMACD(_Symbol,MACD_TimeFrame,MACD_Fast,MACD_Slow,MACD_Signal,MACD_Price);
   if(macd==INVALID_HANDLE)
      return INIT_FAILED;
//---
   cho=iChaikin(_Symbol,Ch_TimeFrame,Ch_Fast_Period,Ch_Slow_Period,Ch_Method,Ch_Volume);
   if(cho==INVALID_HANDLE)
      return INIT_FAILED;
//---
   cci=iCCI(_Symbol,CCI_TimeFrame,CCI_Period,CCI_Price);
   if(cci==INVALID_HANDLE)
      return INIT_FAILED;
//---
   MaxPeriod=fmax(Force_TimeFrame,MACD_TimeFrame);
   MaxPeriod=fmax(MaxPeriod,Ch_TimeFrame);
   MaxPeriod=fmax(MaxPeriod,CCI_TimeFrame);
   MinPeriod=fmin(Force_TimeFrame,MACD_TimeFrame);
   MinPeriod=fmin(MinPeriod,Ch_TimeFrame);
   MinPeriod=fmin(MinPeriod,CCI_TimeFrame);
//---
   return(INIT_SUCCEEDED);
  }

在 OnDeinit 函数中, 关闭使用的指标。

void OnDeinit(const int reason)
  {
//---
   if(atr!=INVALID_HANDLE)
      IndicatorRelease(atr);
//---
   if(force==INVALID_HANDLE)
      IndicatorRelease(force);
//---
   if(macd==INVALID_HANDLE)
      IndicatorRelease(macd);
//---
   if(cho==INVALID_HANDLE)
      IndicatorRelease(cho);
//---
   if(cci==INVALID_HANDLE)
      IndicatorRelease(cci);
  }

主要动作在 OnTick 中执行。在函数的开始, 检查一根新柱线的产生。当新柱线开盘时, 只能在当前柱线的最短时延 (我限制为自开盘 10 秒) , 和最大时延范围内才可开仓。以这种方式, 我限制一个信号只能开一单。

void OnTick()
  {
//---
   datetime cur_bar=(datetime)SeriesInfoInteger(_Symbol,MinPeriod,SERIES_LASTBAR_DATE);
   datetime cur_max=(datetime)SeriesInfoInteger(_Symbol,MaxPeriod,SERIES_LASTBAR_DATE);
   datetime cur_time=TimeCurrent();
   if(cur_bar<=last_bar || (cur_time-cur_bar)>10 || cur_max<=last_deal)
      return;

进而, 获得所用指标的数据。若是指标之一的数据接收错误则退出该函数。

   last_bar=cur_bar;
   double atrs[];
   double force_data[];
   double macd_data[];
   double cho_data[];
   double cci_data[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0
      || CopyBuffer(cho,0,1,2,cho_data)<2 || CopyBuffer(cci,0,1,2,cci_data)<2)
     {
      return;
     }

然后, 按照我们的策略检查力度指数值。如果它不能满足我们对滤波器的要求, 退出该函数, 直到下一根柱线开盘。

   double force_Step=_Point*1000;
   if(MathAbs(NormalizeDouble(force_data[0]/force_Step,0)*force_Step)<=0.01)
      return;

到下一个阶段, 检查多头开仓信号。如果出现正信号, 则检查是否已有持仓。若有且为空头, 平仓。如果有多头持仓且处于亏损状态, 则忽略该信号并退出该函数。

之后, 计算新仓位的参数并发送订单。

空头持仓执行相同的操作。

   double macd_Step=_Point*50;
   macd_data[0]=NormalizeDouble(macd_data[0]/macd_Step,0)*macd_Step;
   if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0 && (cci_data[1]-cci_data[0])>0)
     {
      if(PositionSelect(_Symbol))
        {
         switch((int)PositionGetInteger(POSITION_TYPE))
           {
            case POSITION_TYPE_BUY:
              if(PositionGetDouble(POSITION_PROFIT)<=0)
                 return;
              break;
            case POSITION_TYPE_SELL:
              Trade.PositionClose(_Symbol);
              break;
           }
        }
      last_deal=cur_max;
      double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point);
      double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      double sl=NormalizeDouble(stops,_Digits);
      double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits);
      double SL=NormalizeDouble(bid-sl,_Digits);
      double TP=NormalizeDouble(ask+tp,_Digits);
      if(!Trade.Buy(Lot,_Symbol,ask,SL,TP,"New Strategy"))
         Print("Error of open BUY ORDER "+Trade.ResultComment());
     }
   if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0 && (cci_data[1]-cci_data[0])<0)
     {
      if(PositionSelect(_Symbol))
        {
         switch((int)PositionGetInteger(POSITION_TYPE))
           {
            case POSITION_TYPE_SELL:
              if(PositionGetDouble(POSITION_PROFIT)<=0)
                 return;
              break;
            case POSITION_TYPE_BUY:
              Trade.PositionClose(_Symbol);
              break;
           }
        }
      last_deal=cur_max;
      double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point);
      double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      double sl=NormalizeDouble(stops,_Digits);
      double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits);
      double SL=NormalizeDouble(ask+sl,_Digits);
      double TP=NormalizeDouble(bid-tp,_Digits);
      if(!Trade.Sell(Lot,_Symbol,bid,SL,TP,"New Strategy"))
         Print("Error of open SELL ORDER "+Trade.ResultComment());
     }
   return;
  }

完整的 EA 代码在附件中提供。

执行 EA 之后, 我们可以测试我们的策略。为了避免 "策略区间匹配", 扩展测试的时间: 策略测试自 2015 年 1 月 1 日到 2017 年 1 月 12 日。测试的初始资本是 10,000 美元, 交易规模 - 1 手。

策略测试。 

根据测试结果, EA 在最大余额回撤12.4%, 以及净值回撤 23.8% 情况下获利 74.8%。共完成了 44 笔交易 (空头 22 笔, 多头 22 笔)。获利仓位的份额占 18.2%, 空头和多头相等。如此低的盈利百分比是因为所规定的预期盈利/风险比值较高 (15:1), 这为进一步改善策略留下了空间。

策略测试结果。

结束语

本文演示利用将入场点解析为指标的方法, "用一张白纸" 创建交易策略的技术。由此产生的策略能够产生长期的利润, 三年来的测试证明了这一点。尽管使用标准 MetaTrader 软件包创建策略指标时, 交易信号与文献中描述的指标相差甚远。所建议的技术能够创造性地在交易策略中使用指标, 且不受限于所采用的指标。您可以使用任何用户指标和变体来评估其信号的质量。

参考资料

  1. 解析入场点为指标
  2. HTML 格式的图表和示意图

本文中使用的程序:

#
 名称
类型 
描述 
  New_Strategy_Gizlyk.zip    
1 NewStrategy1.mq5  EA  实现第一阶段策略创建的 EA
 2 NewStrategy2.mq5   EA 实现第二阶段策略创建的 EA
 3 NewStrategy3.mq5   EA  实现第三阶段策略创建的 EA 
 4 NewStrategy4.mq5   EA
 实现第四阶段策略创建的 EA 
 5 NewStrategy_Final.mq5  EA
 策略测试 EA
6 DealsToIndicators.mqh  类库  处理指标类的类
7 Deal.mqh   类库  保存交易信息的类
8 Value.mqh   类库  保存指标缓冲区状态数据的类
9 OneBufferArray.mqh  类库  保存缓冲区区指标历史数据的类
10 StaticOneBuffer.mqh  类库  收集和分析单缓冲区指标统计数据的类
11 ADXValue.mqh  类库  保存 ADX 指标状态数据的类
12 ADX.mqh  类库  保存 ADX 指标历史数据的类
13 StaticADX.mqh  类库  收集和分析 ADX 指标统计数据的类
14 AlligatorValue.mqh  类库  保存 Alligator 指标状态数据的类
15 Alligator.mqh  类库  保存 Alligator 指标历史数据的类
16 StaticAlligator.mqh  类库  收集和分析 Alligator 指标统计数据的类
17 MACDValue.mqh  类库  保存 MACD 指标状态数据的类
18 MACD.mqh  类库  保存 MACD 指标历史数据的类
19 StaticMACD.mqh  类库  收集和分析 MACD 指标统计数据的类
   Common.zip    
20  NewStrategy1_Report_1to1_2016-17.html  测试报告文件  第一阶段制定的策略分析图表, 盈利/风险 = 1/1
21  NewStrategy1_Report_15to1_2016-17.html  测试报告文件  第一阶段制定的策略分析图表, 盈利/风险 = 15/1
22  NewStrategy2_Report_1to1_2016-17.html   测试报告文件  第二阶段制定的策略分析图表, 盈利/风险 = 1/1
23  NewStrategy2_Report_15to1_2016-17.html  测试报告文件  第二阶段制定的策略分析图表, 盈利/风险 = 15/1
24  NewStrategy3_Report_1to1_2016-17.html   测试报告文件  第三阶段制定的策略分析图表, 盈利/风险 = 1/1
25  NewStrategy3_Report_15to1_2016-17.html   测试报告文件  第三阶段制定的策略分析图表, 盈利/风险 = 15/1
26  NewStrategy4_Report_1to1_2016-17.html   测试报告文件  第四阶段制定的策略分析图表, 盈利/风险 = 1/1
27  NewStrategy4_Report_15to1_2016-17.html   测试报告文件  第四阶段制定的策略分析图表, 盈利/风险 = 15/1
28  NewStrategy_Final_Report.html  测试报告文件  策略测试报告

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/4192

附加的文件 |
Common.zip (1455.62 KB)
将入场信息解析到指标 将入场信息解析到指标

交易者的生活中会出现不同的状况。经常地, 成功交易的历史令我们能够复现策略, 而查看亏损历史, 让我们尝试开发和改进新的策略。在这两种情况下, 我们要将交易与已知指标进行比较。本文推荐了一批拿交易与数个指标进行比较的方法。

利用卡尔曼 (Kalman) 滤波器预测价格方向 利用卡尔曼 (Kalman) 滤波器预测价格方向

为了成功交易, 我们几乎总是需要指标来把主要价格走势与噪音波动剥离。在本文中, 我们考察最有前途的数字滤波器之一, 卡尔曼滤波器。本文将介绍如何绘制和使用滤波器。

利用迪纳波利 (DiNapoli) 等级进行交易 利用迪纳波利 (DiNapoli) 等级进行交易

本文研究使用 MQL5 标准工具依据迪纳波利 (DiNapoli) 等级进行实际交易的智能交易系统变种。对其性能进行了测试并得出结论。

测试当交易货币对篮子时出现的形态第二部分 测试当交易货币对篮子时出现的形态第二部分

我们继续测试形态并尝试在文章中描述的交易货币对篮子的方法。让我们探讨在实际应用中是否可能使用组合 WPR 图与移动平均交叉的形态,如果答案是可以,我们应当考虑适当的使用方法。