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

量化交易吧 /  量化策略 帖子:3364694 新帖:15

自适应行情跟踪方法的实际评估

爱德华发表于:4 月 17 日 16:39回复(1)

概述

文中提出的交易策略, 由弗拉基米尔·克拉夫丘克 (Vladimir Kravchuk) 首先在 2001 - 2002 年的 "货币投机者 (Currency speculator)" 杂志上进行了阐述。该系统基于数字滤波器的使用, 以及离散时间序列的频谱估值。

实时报价图表的变化也许会有任何形式。在数学中, 这被称为非分析性函数。著名的傅利叶定理意味着有限时间间隔上的任何函数均可以表示为无限累加的正弦函数。因此, 任何时间信号都可以由频率函数唯一地表示, 此即被称为它们的频谱。

对于非随机信号, 使用傅利叶变换来进行从时域到频域表达的变换 (即, 频谱的计算)。随机过程是由过程的 "功率谱密度 (PSD)" 表达, 这不是随机过程自身的傅利叶变换, 而是其自相关函数。

1. 策略的理论层面

请记住, 滤波是对信号频谱在正确方向上的一种修正。这样的转换可以在一定范围内放大或削弱频率分量, 抑制或隔离它们中的任何一种。数字滤波器是用来转换仅在离散时刻所定义信号的数字系统。

当使用数字滤波器和离散时间序列操作时, 有一些重要的层面。 

首先, 众多流行的技术工具 (MA, RSI, Momentum, Stochastic, 等) 均是基于信号频谱的变化, 因此是数字滤波器。其转移函数的增益取决于频率。然而, 这种转移函数被许多人所忽视。因此, 大多数用户不知道信号频谱波动的方向, 因此不了解指标对信号的影响性质。这对于指标的调整和其价值观的解释变得复杂化。

第二, 货币报价的运动过程看起来总是像一个离散的信号, 在开发技术指标时必须考虑其一般性质。例如, 离散信号的频谱总是周期性函数。忽略此属性也许会导致输入的时间序列不可挽回的失真。

第三, 价格走势的频谱密度在不同的行情上也有很大的差别。在这种情况下, 用户没有配置指标参数的不同算法: 无奈之下他们不得不任意选择参数并在实践中测试其持续性。

经常有这样的情形, 一款优化过的指标或智能交易系统, 昨天还运作良好, 但今天却出现异常糟糕的结果。这是由于时间序列的不稳定性。实际上, 当比较同一行情在两个时间帧内计算的两个 PSD 估值时, 频谱峰值的幅度发生偏移并改变其形式。这可以解释为多普勒效应的体现, 即移动的谐波源相对于接收者改变了波长。这再次证明了趋势运动存在于行情中。

自适应性行情跟踪方法的目的是寻找合理的最小技术工具, 这将允许以最小的风险创建具有最大盈利能力的交易算法。这是通过若干连续的步骤实现的。

  • 研究特定行情价格波动的频谱组成。
  • 非递归数字滤波器被自适应地配置。此过程的结果即产生一组优化的脉冲响应 (脉冲响应函数, IRF)。
  • 输入的时间序列被过滤, 最后确定一组指标, 这在文章中会进一步研究。
  • 开发交易算法。
自适应方法可以应用于任何行情。但应当指出的是, 没有效率损失的最大持仓规模将受到所选择的特定市场的资本化和流动性的影响。 

1.1. 选择频谱分析方法

开发基于自适应行情跟踪方法的交易系统, 始于对特定金融工具的价格走势频谱的研究。显然, 整个系统的最终效果取决于这个阶段的结果。

该解决方案看起来很浅显: 必需进行频谱或谐波分析。但是选择哪种方法?如今, 已知两种主要的频谱分析方法: 参数和非参数。

光谱分析的参数化方法 是定义某种频谱密度模型, 且其参数基于有限时间段内相应过程的观测结果来估值。在这一点上, 原来的模型可以有多种形式。

特别地, 时间序列的频谱密度表现为有理函数, 可以作为源模型。在这种情况下, 可以实现自回归模型, 移动平均模型, 和自回归移动平均模型。所以, 当评估模型参数时要使用不同的方法论。

为了解决这一问题, 我们还可以使用变分原理和相应的质量评估功能。此处, 将采用拉格朗日乘数作为评估参数。此方法应用在通过最大熵方法评估频谱密度, 其需要根据相关函数的已知单独数值来最大化过程的熵。

频谱分析的非参数方法, 与参数化不同, 不必有任何预定的模型。它们当中最流行的方法, 是在初始阶段确定过程的周期性 (即, 现有实现的傅利叶变换的绝对值的平方)。之后, 任务就降低到选择合适的窗口, 满足某种需求。

Blackman-Tukey 方法也被广泛使用。它查找所分析时间序列相关序列的加权估值的傅利叶变换。

另一种方法是减少评估时间序列频谱密度的问题来解决基本积分方程, 通过具有正交增量的随机过程来描述所分析时间序列的傅利叶变换。

根据提议交易系统的作者, 使用基于时间序列离散傅利叶变换计算的经典非参数频谱估值方法, 不可能定性地评估汇率波动功率谱密度。唯一的方法是使用频谱分析的参数化方法, 它们能够在相对短的离散时间样本中获得 PSD 的一致评估, 其中过程即可是静止的, 亦或可以通过去除线性趋势来进行。在频谱评估的各种参数化方法中, 最大熵方法得到最高程度的关注。

1.2. 应用技术分析工具

所提议策略的主要区别是自适应趋势线。其方向指示当前趋势方向。

自适应趋势线 是输入时间序列的低频分量。它是经低通滤波器 (LPF) 获得。LPF 的截止频率 fc 越低, 趋势线越平滑。

自适应趋势线的端点之间存在内部连接, 其强度与它们之间的距离成反比。如果点间距离大于等于所谓的奈奎斯特间隔 TN=1/(2 fc) 则它们之间的连接不存在。因此, 过滤器截止频率的降低增强了这种连接, 并将趋势逆转的时刻推迟。

交易系统采用两种具有不同时间帧的自适应趋势线来识别趋势。

FATL (快速自适应趋势线)。需要 LPF-1 滤波器进行绘制。它可抑制高频噪声和振荡周期非常短的行情轮换。

SATL (慢速自适应趋势线)。需要 LPF-2 滤波器进行绘制。与 LPF-1 不同, 它可令振荡周期较长的行情轮换通过。

上述滤波器的参数 (截止频率 fc 和阻带中的衰减 σ) 基于所分析金融工具的频谱评估计算。LPF-1 和 LPF-2 在阻带中提供至少 40 dB 的衰减。使用它们对通带中的输入信号的幅度和相位没有影响。数字滤波器的这种性质提供了有效的噪声抑制, 与简单的 MA 相比, 它们产生的假信号较少。

从数学角度来看, FATL(k) 的值是 Close(k) 的预期值, 其中 k 是交易日的数量。

RFTL (参考快速趋势线)RSTL (参考慢速趋势线)。它们表示数字滤波器 LPF-1 和 LPF-2 对输入信号响应的输出值, 其延迟等于相应的奈奎斯特间隔。

FTLM (快速趋势线动量)STLM (慢速趋势线动量) 展示 FAT 和 SALT 的偏移。它们的计算类似于动量指标, 但替代收盘价的是, 它们使用已过滤的平滑趋势线。所得到的示意线比常规动量更平滑和规则。

FTLM 和 STLM 示意线根据离散数学规则计算。它是两个独立相邻点之间的差值, 限于过程的频带。在正常计算动量时经常忽略了这一要求, 结果在输入信号的频谱中出现了不可挽回的失真。

RBCI (范围界限通道指数)。它是经由带通滤波器计算的, 它执行以下操作:

  • 删除低频趋势, 由周期大于 T2 = 1/fc2 的低频频谱分量形成;
  • 删除高频噪声, 由周期小于 T1 = 1/fc1 的高频频谱分量形成。

选择周期 Т1 和 Т2, 以便满足条件 Т2 > T1。与此同时, fc1fc2 截止频率应考虑到所有主要行情。

简单地说, RBCI(k) = FATL(k) - SATL(k)。事实上, 当 RBCI 接近局部极值时, 价格接近交易区间的上边界或下边界 (分别取决于是最高价还是最低价)。

PCCI (完美商品通道指数)。其计算公式: PCCI(k) = close(k) – FATL(k)。

其计算方法与商品通道指数 (CCI) 相似。实际上, CCI 是当前价格与其移动平均线之间的常规化差值, PCCI 是每日收盘价与其预期值之间的差值 (如前所述, 取自 FATL 值)。

这意味着 PCCI 指数是价格波动的高频分量, 常规化为标准偏差。

指标示例指标示例


1.3. 指标信号解释规则。

我们来梳理 交易系统的主要原则

  • 它属于基于趋势的系统, 顺势交易。使用 SATL 来识别趋势。
  • 根据 FTLM 和 STLM "快" 和 "慢" 趋势的动态特征判定入场点。
  • 计算使用由 RBCI 指数确定的当前行情状况 (中性, 超买, 超卖, 局部极端)。
  • 入场方向则是使用趋势指标确定的。振荡器只能在横盘的情况下使用。
  • 它会强制设置停止订单 (基于 RBCI, PCCI 指数和行情波动)。

上述工具应该根据这些规则来解释

  • 如果 SATL 指标线方向朝上, 那么行情体现为上涨趋势, 如果朝下 - 下跌。局部极端情况的出现表明趋势开始逆转。与 RSTL 的交汇是趋势彻底逆转的信号。在此情况下, STML 改变其符号。
  • 如果 SATL 指标线是水平的, 或者几乎是水平的, 那么行情就会体现为横盘。
  • STLM 为正则趋势看涨, 为负则趋势看跌。它被认为是一个领先的指标。其局部极端情况总是先于相应的 SATL 极端情况出现。STLM 的绝对值与趋势强度成正比。如果 STLM 和 SATL 向同一方向移动, 趋势正在增强。方向不同表明趋势在减弱。水平的 STLM 指标线意味着趋势完全形成。
  • 如果 "快速" 和 "慢速" 趋势线 (FATL 和 SALT) 具有相同的方向, 则趋势强度较高。否则, 行情即可能处于巩固亦或调整阶段。
  • 如果 FATL 和 SATL 指标线开始向相同的方向移动, 那么趋势就会逆转。如果经历多方向周期之后再次收敛, 那么行情调整已经结束, 价格又开始向 SATL 方向移动。

我们遵循上面的规则制定 主要交易信号

  1. 可靠的反转信号出现在长线趋势开始时: STLM 下降, 参考自适应的收敛, 并参考 "慢速" 趋势线 (SATL 和 RSTL)。在信号形成过程中, 价格波动急剧增加。这是趋势变化的一个特征。因此, 在选择交易入场点时必须考虑 PCCI。在看跌信号时, 如果 PCCI 振荡器在最后一根蜡烛收盘时超过 -100 级别, 则卖出。如果 PCCI 值低于 -100, 不要做单, 等待振荡器超过这个级别。

  2. 经过短暂的修正后, 下一个信号会指明形势延续和趋势强化。照例, 这种情形下的波动比趋势反转时要低。因此, 此处的信号产生条件更加严格, 信号本身更加可靠。
    如果 FATL, FTLM 和 RBCI 指标同步移动, 则进行交易。假信号被 STLM 指标滤出 (其绝对值即可增长, 亦或无变化)。如果 STLM 下降, 则表示 SATL 和 RSTL 收敛。同时, 当 RBCI 收到看跌信号时, 检查行情是否超卖。入场价格的选择要等于或优于信号柱线随后的开盘价。

  3. 该信号基于给定频带中经 RBCI 指数确定的活跃轮换的总和。STLM 线的方向, 以及 SATL 与 RSTL 的收敛/发散被忽略。唯一重要的就是 STLM 值是正数还是负数, 其指示行情的主要趋势形成方向。FATL 指标行的方向和行为作为一个附加的过滤器。
    在中性或长线下降趋势期间, 复合波轮换在强超买区域触及局部最小值时信号形成。在横盘时, 由行情轮换引发的走势潜能将高于由波动引发的走势潜能。如果已形成趋势, 那么基于行情轮换的走势潜能将与长线趋势的潜能叠加。
    交易开单价的选择要等于或优于信号柱线随后的开盘价。

  4. 信号的基础是两个背离: RBCI 的走势方向和 FATL 指标线之间, 以及 RBCI 和 FTLM 指数之间。如果 FATL, RBCI 和 FTLM 指数在某个周期内向不同的方向移动, 则待命状态被激活。看跌信号 — 局部 FTLM 最小, FATL 和 RBCI 未改变它们的移动方向。在图表上, 这看起来好似 FTLM 向 FATL 方向 "点头"。
    该信号最常出现在 "快速" 下降趋势的末段附近 (FTLM 值接近 "0")。该系统为短线趋势走势形成相当准确的信号。
    这是一个领先信号, 前瞻趋势逆转。
    入场价的选择要等于或者优于信号蜡烛随后的开盘价。

  5. 信号在长期趋势中形成。RBCI 和 PCCI 指数同时抵达行情超买值 (对于下降趋势)。这种信号往往形成于趋势的最后阶段, 即报价迅速反向运动, 然后再次 "突破" 原来的主要趋势, 表明依然强劲。
    入场价的选择也要等于或者优于出现信号蜡烛随后的开盘价。

  6. 当长线看跌趋势形成时 (SATL 已经下跌, 但 STLM 值依然为正), PCCI 指数达到 100 以上 (行情超买区域)。反转信号是基于趋势形成时行情的高波动性。
    入场价的选择要等于或者优于信号蜡烛随后的开盘价。

  7. 最强趋势反转信号是在 SATL 指标线向下突破 FATL 指标线之后, 在第一次技术性向上修正结束时观察到的。在这之后要卖出。该技术调整的完成由 FATL 的局部最大值示出。
    卖出价高于或等于信号蜡烛之后开盘价。

  8. 最后, 当两个交叉点同时发生时形成另一个反转信号: "快速" 和 "慢速" 自适应指标线 FATL 和 SATL, 以及 FATL 和 RFTL (下破时卖出, 上破时买入)。这个信号标志着在这个久远且衰弱的趋势中急剧突破的时刻。信号蜡烛之后的卖出价格高于或等于蜡烛的开盘价。

2. 构建低通滤波器

现在我们已经概括描述了策略的主要层面, 现在是进行操作的实施阶段了。当然, 我们从构建一个低通滤波器开始这个工作, 因为这样的滤波器是这个策略的基础。

为了构建一个低通滤波器, 有必要定义其主要参数: 截止频率和衰减。如果策略的作者明确告知滤波器在阻带中应至少提供 40dB 的衰减, 那么为了确定截止频率就需要对金融工具的价格数据进行频谱分析。

如上所述, 作者使用最大熵方法来估算功率谱密度。该方法属于参数化方法, 并根据数学模型执行。数学模型将使用自回归方法来构建。

2.1. 功率谱密度的分析。

将创建 CSpertrum 类来估算频谱密度 (完整的代码可以在附件中找到)。在初始化阶段, 要传递金融工具的名称, 操作时间帧和所需分析的柱线数量。

class CSpectrum
  {
private:
   int               ci_HistoryBars;               //用于分析的柱线
   string            cs_Symbol;                    //品名
   ENUM_TIMEFRAMES   ce_Timeframe;                 //时间帧
   double            cda_AR[];                     //自回归系数
   int               ci_NumberCoeffs;              //系数数量
  
public:
                     CSpectrum(int bars=2880, string symbol=NULL, ENUM_TIMEFRAMES period=PERIOD_CURRENT);
                    ~CSpectrum();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSpectrum::CSpectrum(int bars=2880, string symbol=NULL, ENUM_TIMEFRAMES period=PERIOD_CURRENT)
  {
   ci_HistoryBars =  bars;
   cs_Symbol      =  (symbol==NULL ? _Symbol : symbol);
   ce_Timeframe   =  period;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSpectrum::~CSpectrum()
  {
  }


自回归函数和功率谱密度函数的计算将根据 Victor 提议的方法进行。对这个算法的细节感兴趣的人建议阅读 这篇文章。区域频谱分析函数的结果是一组数据, 为了清楚起见, 可以将其表示为图形。

EURUSD 的功率谱密度

图形上的爆发说明在一定频率下信号功率的增加。需要找出这些爆发来检测滤波器的频率响应。为此, 将一个公有函数 GetPeriods 添加到 CSpecrum 类中, 调用时返回 FATL 和 SATL 的周期。

在函数内部, 爆发将被定义为分形。对于 SATL 滤波器的周期, 我们检测第一次爆发, 它显示了最低频率的脉冲。对于 FATL, 我们搜索最高频率和功率大于 -40dB 的脉冲, 消除小振幅和高频率的 "噪声" 脉冲。如果函数无法找到过滤器的周期, 则返回 false。

bool CSpectrum::GetPeriods(int &FAT,int &SAT)
  {
   if(!Spectrum())
      return false;
   FAT=SAT=0;
   int total=ArraySize(cad_Spectr)-1;
   for(int i=1;(i<total);i++)
     {
      int temp=2*(total+1)/i;
      if(cad_Spectr[i]==0 || temp>(int)ci_HistoryBars/4)
         continue;
      if((cad_Spectr[i]-cad_Spectr[i+1])>=0 && (cad_Spectr[i]-cad_Spectr[i-1])>0)
        {
         if(SAT==0)
            SAT=temp;
         else
           {
            if(cad_Spectr[i]<-40)
              {
               if(FAT==0)
                  FAT=temp;
               break;
              }
            if(temp>=20)
               FAT=temp;
           }
        }
     }
   if(SAT==0 || FAT==0)
      return false;
   return true;
  }


2.2. 计算低通滤波器的系数。

现在, 滤波器的频率已选定了, 是时候构建低通滤波器了。数字滤波器的一般公式如下


其中 y — 滤波器输出; x — 源数据数组; hk — 脉冲响应; N — 脉冲响应数量。

金融工具的价格数据作为源数据, 脉冲响应的数量设置为等于奈奎斯特区间。冲动响应本身还有待计算。可使用公式计算低通滤波器的理想脉冲响应


其中 fcwc 是截止频率。

不幸地是, 我们的世界远非理想。所以, 需要一个 "真实" 的脉冲响应。需要一个权重函数 w (n) 来计算它。有若干种类型的权重函数。这里使用了布莱克曼函数, 它具有这种形式


其中 N 是过滤元素的数量。

为了获得 "真实" 的脉冲响应, 必需将理想脉冲响应与相应的权重函数相乘


现在计算公式已经定义好了, 我们来创建 CFLF 类, 在其中计算脉冲响应, 且输入的数据将被过滤。为了计算脉冲响应的系数, 我们创建一个公有函数 CalcImpulses, 它传递过滤周期。然后函数算法重复上述公式。之后, 它将脉冲响应归一化, 将它们累加到 "1"。

bool CFLF::CalcImpulses(int period)
  {
   if(period<20)
      return false;
   int N=(int)(period/2);
   if(ArraySize(cda_H)!=N)
      if(ArrayResize(cda_H,N)<N)
         return false;
   double H_id[],W[];
   if(ArrayResize(H_id,N)<N || ArrayResize(W,N)<N)
      return false;
  
   cd_Fs=1/(double)period;
   for (int i=0;i<N;i++)
     {
      if (i==0)
         H_id[i] = 2*M_PI*cd_Fs;
      else
         H_id[i] = MathSin(2*M_PI*cd_Fs*i )/(M_PI*i);
      
      W[i] = 0.42 - 0.5 * MathCos((2*M_PI*i) /( N-1)) + 0.08 * MathCos((4*M_PI*i) /( N-1));
      cda_H[i] = H_id[i] * W[i];
     }
      
   //归一化
   double SUM=MathSum(cda_H);
   if(SUM==QNaN || SUM==0)
      return false;
   for (int i=0; i<N; i++)
      cda_H[i]/=SUM; //系数累加等于 1 
   //---
   return true;
  }

2.3. 指标计算 FATL, SATL, RTFL, RSTL。

一旦获得脉冲响应, 我们就可以继续计算脉冲指标值。直接从过滤器类中获取 FATL, SATL, RFTL 和 RSTL 指标的数值将会很方便。

由于快速和慢速过滤器应使用该类的不同实例, 因此在类中创建 AdaptiveTrendLineReferenceTrendLine 函数就足矣了。将使用的金融工具, 时间帧和相对于当前蜡烛的偏移传递给函数。该函数将返回过滤后的数值。

应当注意的是, ReferenceTrendLine 函数与 AdaptiveTrendLine 函数基本相同。仅有的区别在于 ReferenceTrendLine 是用奈奎斯特周期的偏移值计算的。所以, ReferenceTrendLine 应计算奈奎斯特周期并调用 AdaptiveTrendLine, 指定相对于当前柱线的相应偏移。

double CFLF::AdaptiveTrendLine(string symbol=NULL,ENUM_TIMEFRAMES timeframe=0,int shift=1)
  {
   string symb=(symbol==NULL ? _Symbol : symbol);
   int bars=ArraySize(cda_H);
   double values[];
   if(CopyClose(symb,timeframe,shift,bars,values)<=0)
      return QNaN;
   double mean=MathMean(values);
   double result=0;
   for(int i=0;i<bars;i++)
      result+=cda_H[i]*(values[bars-i-1]-mean);
   result+=mean;
   return result;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CFLF::ReferenceTrendLine(string symbol=NULL,ENUM_TIMEFRAMES timeframe=0,int shift=1)
  {
   shift+=(int)(1/(2*cd_Fs));
   return AdaptiveTrendLine(symbol,timeframe,shift);
  }

要用到的其它指标是所获数值的导数, 将在下面进行计算。

3. 利用 MQL5 向导创建交易信号模块

今天, 我决定从往常编写智能交易系统的方式中解脱出来, 并提醒 МetaТrader5 中存在 MQL向导。这个有用的功能是一种类型的构造器, 它从预制模块中组装智能交易系统。这可令您轻松创建新的 EA, 添加新功能或删除未使用的功能。因此, 我建议将所研究的智能交易系统决策算法嵌入到这个模块中。这个方法已经被多次讨论过了 [4], [5]。所以, 本文只考虑与策略有关的层面。

我们来开始创建基于 CExpertSignal 的 CSignalATCF 信号类, 并在其中包含以前创建的类。

class CSignalATCF : public CExpertSignal
  {
private:
   CSpectrum         *Spectrum;     //频谱计算类
   CFLF              *FFLF;         //快速低通滤波器类
   CFLF              *SFLF;         //慢速低通滤波器类

public:                      CSignalATCF();                     ~CSignalATCF();   };

在初始化阶段, 需要为模块传递金融工具名称, 所用时间帧, 分析功率谱密度所需的历史柱线数, 以及平均柱线数 (用于计算 RBCI 和 PCCI 指标)。此外, 必需指定哪些形态将用于开仓。模块描述的一般视图如下所示:

//--- 向导说明开始
//+---------------------------------------------------------------------------+
//| 类描述                                                                     |
//| Title=由 DNG 设计的自适应趋势和轮换跟踪方法的信号                                |
//| Type=SignalAdvanced                                                       |
//| Name=自适应趋势和轮换跟踪方法的信号                                             |
//| ShortName=ATCF                                                            |
//| Class=CSignalATCF                                                         |
//| Page=https://www.mql5.com/ru/articles/3456                                |
//| Parameter=TimeFrame,ENUM_TIMEFRAMES,PERIOD_H4,Timeframe                   |
//| Parameter=HistoryBars,uint,1560,分析用的历史柱线                             |
//| Parameter=AveragePeriod,uint,500, RBCI 和 PCCI 的周期                      |
//| Parameter=Pattern1,bool,true, 使用形态 1                                   |
//| Parameter=Pattern2,bool,true, 使用形态 2                                   |
//| Parameter=Pattern3,bool,true, 使用形态 3                                   |
//| Parameter=Pattern4,bool,true, 使用形态 4                                   |
//| Parameter=Pattern5,bool,true, 使用形态 5                                   |
//| Parameter=Pattern6,bool,true, 使用形态 6                                   |
//| Parameter=Pattern7,bool,true, 使用形态 7                                   |
//| Parameter=Pattern8,bool,true, 使用形态 8                                   |
//+---------------------------------------------------------------------------+
//--- 向导说明结束

现在声明所需的变量和函数:

class CSignalATCF : public CExpertSignal
  {
private:
   ENUM_TIMEFRAMES   ce_Timeframe;     //时间帧
   uint              ci_HistoryBars;   //分析用的历史柱线
   uint              ci_AveragePeriod; //RBCI 和 PCCI 的周期
   CSpectrum         *Spectrum;        //频谱计算类
   CFLF              *FFLF;            //快速低通滤波器类
   CFLF              *SFLF;            //慢速低通滤波器类
   //--- 指标数据
   double             FATL, FATL1, FATL2;
   double             SATL, SATL1;
   double             RFTL, RFTL1, RFTL2;
   double             RSTL, RSTL1;
   double             FTLM, FTLM1, FTLM2;
   double             STLM, STLM1;
   double             RBCI, RBCI1, RBCI2;
   double             PCCI, PCCI1, PCCI2;
   //--- 形态标志
   bool               cb_UsePattern1;
   bool               cb_UsePattern2;
   bool               cb_UsePattern3;
   bool               cb_UsePattern4;
   bool               cb_UsePattern5;
   bool               cb_UsePattern6;
   bool               cb_UsePattern7;
   bool               cb_UsePattern8;
   //---
   datetime           cdt_LastSpectrCalc;
   datetime           cdt_LastCalcIndicators;
   bool               cb_fast_calced;
   bool               cb_slow_calced;
   
   bool              CalculateIndicators(void);
       
public:
                     CSignalATCF();
                    ~CSignalATCF();
   //---
   void              TimeFrame(ENUM_TIMEFRAMES value);
   void              HistoryBars(uint value);
   void              AveragePeriod(uint value);
   void              Pattern1(bool value)                {  cb_UsePattern1=value;   }
   void              Pattern2(bool value)                {  cb_UsePattern2=value;   }
   void              Pattern3(bool value)                {  cb_UsePattern3=value;   }
   void              Pattern4(bool value)                {  cb_UsePattern4=value;   }
   void              Pattern5(bool value)                {  cb_UsePattern5=value;   }
   void              Pattern6(bool value)                {  cb_UsePattern6=value;   }
   void              Pattern7(bool value)                {  cb_UsePattern7=value;   }
   void              Pattern8(bool value)                {  cb_UsePattern8=value;   }
   //--- 设置验证方法
   virtual bool      ValidationSettings(void);
   //--- 创建指标和时间序列的方法
   virtual bool      InitIndicators(CIndicators *indicators);
   //--- 检查行情模型是否形成的方法
   virtual int       LongCondition(void);
   virtual int       ShortCondition(void);
  };

创建一个计算指标值的函数:

bool CSignalATCF::CalculateIndicators(void)
  {
   //--- 检查最后一次计算的时间
   datetime current=(datetime)SeriesInfoInteger(m_symbol.Name(),ce_Timeframe,SERIES_LASTBAR_DATE);
   if(current==cdt_LastCalcIndicators)
      return true;                  // 如果已在此根柱线上计算数据, 则退出
   //--- 检查频谱的重新计算
   MqlDateTime Current;
   TimeToStruct(current,Current);
   Current.hour=0;
   Current.min=0;
   Current.sec=0;
   datetime start_day=StructToTime(Current);
   
   if(!cb_fast_calced || !cb_slow_calced || (!PositionSelect(m_symbol.Name()) && start_day>cdt_LastSpectrCalc))
     {
      if(CheckPointer(Spectrum)==POINTER_INVALID)
        {
         Spectrum=new CSpectrum(ci_HistoryBars,m_symbol.Name(),ce_Timeframe);
         if(CheckPointer(Spectrum)==POINTER_INVALID)
           {
            cb_fast_calced=false;
            cb_slow_calced=false;
            return false;
           }
        }
      
      int fast,slow;
      if(Spectrum.GetPeriods(fast,slow))
        {
         cdt_LastSpectrCalc=(datetime)SeriesInfoInteger(m_symbol.Name(),ce_Timeframe,SERIES_LASTBAR_DATE);
         if(CheckPointer(FFLF)==POINTER_INVALID)
           {
            FFLF=new CFLF();
            if(CheckPointer(FFLF)==POINTER_INVALID)
               return false;
           }
         cb_fast_calced=FFLF.CalcImpulses(fast);
         if(CheckPointer(SFLF)==POINTER_INVALID)
           {
            SFLF=new CFLF();
            if(CheckPointer(SFLF)==POINTER_INVALID)
               return false;
           }
         cb_slow_calced=SFLF.CalcImpulses(slow);
        }
     }
   if(!cb_fast_calced || !cb_slow_calced)
      return false;                       // 错误退出
   
   //--- 计算指标数据
   int shift=StartIndex();
   double rbci[],pcci[],close[];
   if(ArrayResize(rbci,ci_AveragePeriod)<(int)ci_AveragePeriod || ArrayResize(pcci,ci_AveragePeriod)<(int)ci_AveragePeriod ||
      m_close.GetData(shift,ci_AveragePeriod,close)<(int)ci_AveragePeriod)
     {
      return false;
     }
   for(uint i=0;i<ci_AveragePeriod;i++)
     {
      double fatl=FFLF.AdaptiveTrendLine(m_symbol.Name(),ce_Timeframe,shift+i);
      double satl=SFLF.AdaptiveTrendLine(m_symbol.Name(),ce_Timeframe,shift+i);
      switch(i)
        {
         case 0:
            FATL=fatl;
            SATL=satl;
            break;
         case 1:
            FATL1=fatl;
            SATL1=satl;
            break;
         case 2:
            FATL2=fatl;
            break;
        }
      rbci[i]=fatl-satl;
      pcci[i]=close[i]-fatl;
     }
   RFTL=FFLF.ReferenceTrendLine(m_symbol.Name(),ce_Timeframe,shift);
   RSTL=SFLF.ReferenceTrendLine(m_symbol.Name(),ce_Timeframe,shift);
   RFTL1=FFLF.ReferenceTrendLine(m_symbol.Name(),ce_Timeframe,shift+1);
   RSTL1=SFLF.ReferenceTrendLine(m_symbol.Name(),ce_Timeframe,shift+1);
   RFTL2=FFLF.ReferenceTrendLine(m_symbol.Name(),ce_Timeframe,shift+2);
   FTLM=FATL-RFTL;
   STLM=SATL-RSTL;
   FTLM1=FATL1-RFTL1;
   STLM1=SATL1-RSTL1;
   FTLM2=FATL2-RFTL2;
   double dev=MathStandardDeviation(rbci);
   if(dev==0 || dev==QNaN)
      return false;
   RBCI=rbci[0]/dev;
   RBCI1=rbci[1]/dev;
   RBCI2=rbci[2]/dev;
   dev=MathAverageDeviation(pcci);
   if(dev==0 || dev==QNaN)
      return false;
   PCCI=pcci[0]/(dev*0.015);
   PCCI1=pcci[1]/(dev*0.015);
   PCCI2=pcci[2]/(dev*0.015);
   cdt_LastCalcIndicators=current;
  //---
   return true;
  }

然后编写开仓和平仓的形态, 指定相应的权重 (平仓 40 和开仓 80)。以下是开多仓的函数。开空仓的函数也是类似的。

int CSignalATCF::LongCondition(void)
  {
   if(!CalculateIndicators() || m_open.GetData(1)>m_close.GetData(1))
      return 0;
   int result=0;
   //--- 平仓
   if(m_high.GetData(2)<m_close.GetData(1) || (STLM1<=0 && STLM>0) || (PCCI1<PCCI && PCCI1<=PCCI2) || (RBCI>RBCI1 && RBCI1>=RBCI2 && RBCI1<-1) || (RBCI1<=0 && RBCI>0))
      result=40;
   //--- 形态 1
   if(cb_UsePattern1 && FTLM>0 && STLM>STLM1 && PCCI<100)
      result=80;
   else
   //--- 形态 2
   if(cb_UsePattern2 && STLM>0 && FATL>FATL1 && FTLM>FTLM1 && RBCI>RBCI1 && (STLM>=STLM1 || (STLM<STLM1 && RBCI<1)))
      result=80;
   else
   //--- 形态 3
   if(cb_UsePattern3 && STLM>0 && FATL>FATL1 && RBCI>RBCI1 && RBCI1<-1 && RBCI1<=RBCI2 && FTLM>FTLM1)
      result=80;
   else
   //--- 形态 4
   if(cb_UsePattern4 && SATL>SATL1 && FATL>FATL1 && RBCI>RBCI1 && FTLM<FTLM1 && FTLM2<=FTLM1)
      result=80;
   else
   //--- 形态 5
   if(cb_UsePattern5 && SATL>SATL1 && STLM>=0 && PCCI1<=-100 && PCCI1<PCCI && PCCI>-100 && RBCI>RBCI1 && RBCI1<=RBCI2 && RBCI1<-1)
      result=80;
   else
   //--- 形态 6
   if(cb_UsePattern6 && SATL>SATL1 && STLM<0 && PCCI1<=-100 && PCCI>-100)
      result=80;
   else
   //--- 形态 7
   if(cb_UsePattern7 && FATL>FATL1 && FATL1<=SATL1 && FATL>SATL && FATL1<=FATL2)
      result=80;
   //--- 形态 8
   if(cb_UsePattern8 && FATL>FATL1 && FATL1<=SATL1 && FATL>SATL && FATL1<=RFTL1 && FATL>RFTL)
      result=80;
   
   return result;
  }


4. 创建自适应行情跟踪的智能交易系统

创建信号模块后, 我们可以继续生成智能交易系统。这篇文章 提供使用向导创建智能交易系统的详细说明。当创建 EA 时, 只使用上述的交易信号模块。另外, 增加了固定点数的尾随停止。测试策略时将使用固定的手数, 这将允许评估生成的信号质量。





5. 测试智能交易系统。

一旦智能交易系统创建完毕, 我们就可以在策略测试器中测试自适应行情跟踪方法。在测试时, 强制设置开仓权重为 60, 平仓权重为 10。

5.1. 测试不用止损, 止盈和尾随停止。

为了评估 EA 所产生的信号质量, 第一次运行测试没有使用止损, 止盈和尾随停止。测试区间为 2017 年的 7个月, H4 时间帧。

测试 1

测试 1

不幸地是, 第一次测试表明, 不使用止损的策略应用是无利可图的。

测试 1。结果

测试 1。结果

测试 1。结果

测试 1。结果

测试 1。结果 

详细分析价格图表上的交易, 表现出该策略的两个弱点:

  1. 智能交易系统无法在快速反弹时及时了结交易, 导致盈利损失, 以及潜在盈利交易在无盈利情况下平仓。
  2. EA 在处理大型走势时良好, 但在横盘期间进行了一些无利可图的交易。

测试 1。图表上的交易

5.2. 使用止损和尾随停止进行测试。

为了减少第一点的损失, 应用止损和尾随停止。

测试 2

测试 2

第二次测试的结果显示持仓时间减少, 盈利交易比率略有上升, 总体趋势偏向盈利。 

测试 2。结果

测试 2。结果

测试 2。结果

测试 2。结果

测试 2。结果

不过, 亏损交易的份额为 39.26%。而第二个问题仍然存在 (在横盘时的亏损交易)。

5.3. 使用停止订单进行测试。

为了减少横盘走势中一系列无盈利交易有关的损失, 使用停止订单进行了测试。

测试 3

测试 3

第三次试运行的结果, 交易数量几乎减半, 盈利总额增加, 盈利交易比例提高到 44.57%。

测试 3。结果

测试 3。结果

测试 3。结果

测试 3。结果

测试 3。结果


结束语

本文研究了自适应行情跟踪方法。测试展示了这一策略的潜力, 但是某些瓶颈需要消除, 才能在真正的市场中使用。无论如何, 这个方法是可行的。源文件和测试结果在文章的附件中提供。

参考

  1. "货币频谱", 2000 年 12 月- 2001 年 6 月。
  2. 时间序列的主要特征分析。
  3. 价格的 AR 推断 - MetaTrader 5 的指标
  4. MQL5 向导: 如何创建一个交易信号模块
  5. 6 步创建您自己的交易机器人!
  6. MQL5 向导新版

文章中使用的程序:

#
 名称
类型 
描述 
 1 Spectrum.mqh 类库 用于估算所分析金融工具功率谱密度的类
 2 FLF.mqh 类库 用于构建低通滤波器并过滤初始数据的类
 3 SignalATCF.mqh 类库 基于自适应行情跟踪方法的交易信号模块
 4 ATCF.mq5 智能交易系统 基于自适应行情跟踪方法的智能交易系统
 5 ACTF_Test.zip 存档 存档包含在策略测试器中 EA 测试的结果。

全部回复

0/140

量化课程

    移动端课程