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

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

创建 EA 交易优化的自定义标准

2020来了发表于:4 月 17 日 17:49回复(1)

简介

MetaTrader 5 客户端提供了各种机会来优化 EA 交易的参数。除了策略测试程序中包含的优化标准以外,开发人员还有机会创建自己的标准。这样一来,EA 交易的测试和优化便具有了无限的可能性。本文介绍了创建此类标准的实用方法,既适用于复杂标准,也适用于简单标准。


1. 策略测试程序的功能回顾

由于这一主题已多次探讨过,因此我只打算列出相关文章,并对其进行简要介绍。建议您在阅读本文之前熟悉以下资料。

  • “MetaTrader 5 中的测试原理”。该部分详细阐述了 EA 交易测试的所有技术细节,包括数据生成模式以及开盘价和 M1 柱的处理。还说明了测试期间指标的用途、环境变量的模拟以及标准事件的处理。此外,该部分还介绍了多货币测试的基础知识。
  • “利用 MQL5 测试与优化 EA 交易指南”。该部分涵盖了 EA 交易输入参数测试和优化方面的问题,还介绍了参数拟合过程、测试结果的解释以及最佳参数的选择。
  • “使用 TesterWithdrawal() 函数模拟利润提取”。该部分说明了如何使用 TesterWithdrawal 函数在策略测试程序中从一个帐户建立提款模型,还说明了该函数如何在策略测试程序中影响资产净值亏损的算法。

当然,首先您需要熟悉随客户端一起提供的说明文档。


2. 策略测试程序中内嵌的优化标准

如果您在说明文档中查找,将会找到以下说明内容:优化标准是某种因数,其值确定了所测试参数集的质量。优化标准的值越高,则给定参数集的测试结果就被认为越好。

这里应该指出一个重要的注意事项:优化标准只能用于优化的遗传算法模式中。显然,在仔细检查所有可能的参数值组合时,就会发现无法通过任何因数来选择某个 EA 交易的最佳参数。另一方面,我们可以保存测试结果,然后对结果进行处理,以确定最佳参数组合。

正如说明文档中所写的,策略测试程序包含了与遗传算法一起使用的以下优化标准:

  • Balance max(最大余额) - 余额的最大值;
  • Balance + max Profit Factor(余额 + 最大获利系数) -余额和获利系数之积的最大值;
  • Balance + max Expected Payoff(余额 + 最大预计获利) -余额和预计获利之积的值;
  • Balance + min Drawdown(余额 + 最小亏损) - 在这种情形下,考虑余额值和亏损水平:(100% - 亏损水平) x 余额;
  • Balance + max Recovery Factor(余额 + 最大回收系数) - 余额和回收系数之积;
  • Balance + max Sharpe Ratio(余额 + 最大夏普比率) - 余额和夏普比率之积;
  • Custom max(自定义最大值) - 自定义的优化标准。这里的优化标准是 EA 交易中 OnTester() 函数的值。该参数允许使用任何自定义值来优化 EA 交易。

如图 1 所示,可以从策略测试程序的 Settings (设置)选项卡中选择优化标准:

选择 EA 交易的优化标准

图 1. 选择 EA 交易的优化标准

列表中的最后一个标准 Custom max(自定义最大值)最能让我们感兴趣,该标准运用于本文主题中。


3. 创建自定义优化标准

首先要做的是,向用户说明是否有可能实现参数的自由组合(不限于图 1 所示的组合,而是自定义组合);每次 EA 交易完成后策略测试程序都会计算这些参数。

例如,以下组合就很有趣:Balance max + min Drawdown + Trades Number(最大余额 + 最小亏损 + 交易次数) - 交易次数越多,结果就越可靠。或者下面一个 - Balance max + min Drawdown + max Profit Factor(最大余额 + 最小亏损 + 最大获利系数)。当然,还有不少其他有趣的组合没有包含在策略测试程序的设置中。

让我们将此类标准组合称为简单优化标准吧。

但是仅凭这些标准还无法对交易系统做出可靠的估计。如果从“以最低风险获利”这一交易观点来看,我们可以假定以下标准:我们可以优化参数以获得一条最平滑的平衡曲线,其单独交易结果与直线的偏离最小。

让我们将这一标准称为按平衡曲线进行优化的标准

我们要使用的下一个优化标准是交易系统的安全系数。“协调一致”一文中介绍了此系数。它描述了交易系统与市场的对应关系,这也是我们在参数优化期间需要确定的。让我们将其称为按交易系统的安全系数进行优化的标准 (CSTS) 吧。

此外,我们还能够对所描述的标准进行自由组合。


4. OnTester() 函数

在编写代码部分之前,我们来看看在策略测试程序中使用自定义 EA 优化标准的组织方式。

预定义函数 OnTester() 用于创建自定义优化标准。该函数在每次指定时间段的 EA 交易测试快结束时都会被自动调用。调用 OnDeinit() 函数之前就会调用此函数。

请再次注意,要使用 OnTester() 函数,您应当启用 Fast genetic base algorithm(快速遗传算法)的优化模式,如图 1 所示。

该函数具有 double(双精度)格式的返回值,该值用于在策略测试程序中进行优化。

请再次看下说明文档:

在遗传优化中,降序用来表示一代以内的结果,即从优化标准的角度来看,最佳结果就是具有最大值的结果。在此类排序中,最差值位于最后并被抛弃,不参与下一代的生成过程

因此,在创建自定义优化标准时,我们需要获得一个完整值,该值将用于估计 EA 交易。该值越大,EA 交易就越理想。


5. 编写试验 EA 交易

现在,是时候编写一个将在策略测试程序中进行优化的 EA 交易了。在这种情况下,对该交易的主要要求是简单快速,不用花太多时间来处理优化的例行过程。当然,也希望该 EA 交易不会完全无利可图。

让我们采用在“在 MQL5 中寻找趋势的几种方法”一文中介绍的 EA 交易作为试验交易程序并加以改进。显然,EA 交易以三条移动平均线构成的“扇形”为基础。改进措施包括不再使用指标以提高运行速度,以及去掉 EA 交易内代码的计算部分。这样便允许显著提高测试速度(在两年区间内几乎提高了三倍)。

设置输入参数的部分较为简单:

input double Lots = 0.1; 
input int  MA1Period = 200; // period of the greatest moving average
input int  MA2Period = 50;  // period of the medium moving average
input int  MA3Period = 21;  // period of the smallest moving average

移动平均线周期是我们要优化的周期。

上述文章详细说明了 EA 交易的结构和运行,因此我们在这里略过不提。主要创新在于另一测试完成时的事件处理程序 - OnTester() 函数。目前,该函数为空并返回控制。

//---------------------------------------------------------------------
//  The handler of the event of completion of another test pass:
//---------------------------------------------------------------------
double OnTester()
{
  return(0.0);
}

本文随附了 EA 交易的文件 FanExpert.mq5。从交易执行的角度来看,我们可以确定其与 FanTrendExpert.mq5 EA 交易相一致。在图表上建立新柱时检查信号是否存在以及信号方向如何。

为了获得每次运行时计算出的测试结果,可使用 TesterStatistics() 函数;该函数会返回被计算为测试结果的请求统计值。它只能从 OnTester() 和 OnDeinit() 函数调用,否则结果会是未定义的。

现在,让我们来添加一个自定义优化标准。假定我们需要根据回收系数的最大值(最大回收系数)确定最佳结果。为此,我们需要知道余额的最大亏损金额以及测试结束时的毛利的值。回收系数通过将获利除以最大亏损金额进行计算。

它仅作为一个示例予以完成,因为回收系数已经包含在计算出的测试统计结果清单中。

为此,将以下简单代码添加到 OnTester() 函数中

//---------------------------------------------------------------------
//  The handler of the event of completion of another test pass:
//---------------------------------------------------------------------
double OnTester()
{
  double  profit = TesterStatistics(STAT_PROFIT);
  double  max_dd = TesterStatistics(STAT_BALANCE_DD);
  double  rec_factor = profit/max_dd;

  return(rec_factor);
}

为简单起见,代码中未包含除零检查。由于最大亏损可能等于零,因此必须在实际 EA 交易中进行该检查。

现在,让我们来创建上述标准:最大余额 + 最小亏损 + 交易次数 - 余额 + 最小亏损 + 交易次数

为此,按以下方式更改 OnTester() 函数:

double OnTester()
{
  double  param = 0.0;

//  Balance max + min Drawdown + Trades Number:
  double  balance = TesterStatistics(STAT_PROFIT);
  double  min_dd = TesterStatistics(STAT_BALANCE_DD);
  if(min_dd > 0.0)
  {
    min_dd = 1.0 / min_dd;
  }
  double  trades_number = TesterStatistics(STAT_TRADES);
  param = balance * min_dd * trades_number;

  return(param);
}

这里可以使用一个与亏损相反的值,因为假定在其他条件相同的情况下,亏损越小,情形就越好。采用所创建的优化标准对 FanExpert EA 交易进行优化,其中 MA1Period 参数使用 2009.06.01 - 2011.06.03 的时间范围和 Н1 时间表。将移动平均线的取值范围设置为 100 至 2000。

在优化结束时,您将得到以下按最佳参数排序的值所组成的列表:

按最大余额 + 最小亏损 + 交易次数标准进行优化的最佳结果

图 2. 按最大余额 + 最小亏损 + 交易次数标准进行优化的最佳结果

其中列出了最佳参数(按 Result (结果)列排序)。

现在,让我们来看下最差的参数:


图 3. 按最大余额 + 最小亏损 + 交易次数标准进行优化的最差参数

比较两张表格后,可以看到,亏损和获利与交易次数都同时考虑到了,也就是说,我们的优化标准正在发挥作用。此外,我们还可以看下优化图(线性):

优化图

图 4. 按最大余额 + 最小亏损 + 交易次数标准进行优化的优化图

水平轴显示优化参数,垂直轴显示优化标准。我们可以清楚看到设定标准的最大值;它位于 980 至 1200 的时间范围内。

您应该领会这是参数的遗传优化,而不是完整搜索。这就是为什么图 2 和图 3 中的表格中包含了最“有生命力的”、经历几代传递自然选择的参数。或许放弃了某些成功的情形。

针对 1106 个周期的余额/资产净值曲线如下所示:

针对 MA1Period = 1106 个周期的余额/资产净值曲线

图 5. 针对 MA1Period = 1106 个周期的余额/资产净值曲线


6. 创建自定义优化标准的类

这样,我们已经学习了如何创建和使用简单优化标准。现在,让我们创建一个类,以简化其在 EA 交易中的运用。除易于使用以外,对该类的主要要求之一便是运行速度。必须快速执行优化标准的计算,否则要等很长时间才能获得结果。

MetaTrader 5 允许使用云计算技术来进行优化。这是一个重大突破,因为处理大量参数时需要具备强大的计算能力。因此,为了开发类,我们打算使用最简单快速的解决方案,即使从编程的观点来看该方案并不够优秀。

基于开发目的,我们打算使用随客户端提供的标准数据组织类。

首先,让我们对所计算的测试统计结果进行分类:

  • 测试结果的值与优化标准成正比的浮点型和整数型。

换言之,测试结果的值越大,则优化标准的值就越好,同时也越大。测试结束时的毛利 STAT_PROFIT 就是此类测试结果的一个典型示例。其值为浮点类型,取值范围从负无穷大(实际上受限于预付款的值)到正无穷大。

交易次数 STAT_TRADES 是此类测试结果的另一个示例。一般而言,交易次数越多,优化结果就越可靠。其值为整数类型,取值范围从零至无穷大。

  • 测试结果的值与优化标准成反比的浮点型和整数型。

换言之,测试结果的值越小,则优化标准的值就越好,同时也越大。余额的最大亏损金额 STAT_BALANCE_DD 以及任何其他亏损就是此类测试结果的一个示例。

为了获得此类测试结果,我们打算使用反向值来计算优化标准的值。当然,我们需要进行除零检查以避免相应的错误。

创建自定义优化标准 TCustomCriterion 的基类非常简单。其目的在于确定基本功能。其外观如下所示:

class TCustomCriterion : public CObject
{
protected:
  int     criterion_level;        // type of criterion

public:
  int   GetCriterionLevel();
  virtual double  GetCriterion();  // get value of the result of optimization
};

应在所继承的类中重写虚拟方法 TCustomCriterion::GetCriterion。这是在每次测试 EA 交易结束时返回测试完整结果值的主要方法。

TCustomCriterion::criterion_level 类成员用于存储这一类实例固有的自定义标准的类型。它将进一步用于根据对象的类型区分对象。

现在,我们可以从该类成员继承优化所需的所有类。

TSimpleCriterion 类用于创建与所指定测试统计结果对应的“简单”自定义标准。其定义如下所示:

class TSimpleCriterion : public TCustomCriterion
{
protected:
  ENUM_STATISTICS  stat_param_type;

public:
  ENUM_STATISTICS  GetCriterionType();     // get type of optimized stat. parameter

public:
  virtual double   GetCriterion();           // receive optimization result value
  TSimpleCriterion(ENUM_STATISTICS _stat); // constructor
};

这里我们使用一个带有参数的构造函数;其实现如下所示:

//---------------------------------------------------------------------
//  Constructor:
//---------------------------------------------------------------------
TSimpleCriterion::TSimpleCriterion(ENUM_STATISTICS _stat)
:
stat_param_type( _stat )
{
  criterion_level = 0;
}

在 MQL5 语言中,这个新功能在创建类的实例时很方便使用。我们也会重写虚拟方法 TSimpleCriterion::GetCriterion,该方法用于在每次测试通过时获取优化结果。其实现非常简单:

//---------------------------------------------------------------------
//  Get the result of optimization:
//---------------------------------------------------------------------
double  TSimpleCriterion::GetCriterion()
{
  return(TesterStatistics(stat_param_type));
}

如您所见,该方法只是返回对应的测试统计结果。

使用 TSimpleDivCriterion 类创建另一类“简单”自定义优化标准。它用于测试结果的值与优化标准成反比的标准。

TSimpleDivCriterion::GetCriterion 方法如下所示:

//---------------------------------------------------------------------
//  Get value of the optimization result:
//---------------------------------------------------------------------
double  TSimpleDivCriterion::GetCriterion()
{
  double  temp = TesterStatistics(stat_param_type);
  if(temp>0.0)
  {
    return(1.0/temp);
  }
  return(0.0);
}

该代码无需进行任何其他说明。

使用 TSimpleMinCriterionTSimpleMaxCriterion 类创建其他两类“简单”自定义优化标准。它们分别用于创建具有测试统计结果上限和下限的标准。

如果您需要在优化期间有意放弃参数的错误值,则它们会很有用。例如,您可以限制最少交易次数、最大亏损等。

TSimpleMinCriterion 类的描述如下所示:

class TSimpleMinCriterion : public TSimpleCriterion
{
  double  min_stat_param;

public:
  virtual double  GetCriterion();    // receive optimization result value
  TSimpleMinCriterion(ENUM_STATISTICS _stat, double _min);
};

这里我们使用具有两个参数的构造函数。参数 _min 用于设置测试统计结果的最小值。如果另一次测试导致获得的值小于指定值,则放弃该结果。

方法 TSimpleMinCriterion ::GetCriterion 的实现如下所示:

//---------------------------------------------------------------------
//  Get value of the optimization result:
//---------------------------------------------------------------------
double  TSimpleMinCriterion::GetCriterion()
{
  double  temp = TesterStatistics(stat_param_type);
  if(temp<this.min_stat_param)
  {
    return(-1.0);
  }
  return(temp);
}

TSimpleMaxCriterion 类的创建较为相似,无需另外说明。“简单”自定义标准其他类的创建方法类似于上述类,这些其他类位于本文随附的 CustomOptimisation.mqh 文件中。可使用同样的原理来开发要在优化中使用的任何其他类。


在使用上述类之前,让我们创建一个容器类,从而更方便地处理标准设置。为此,我们也使用标准的数据组织类。由于我们需要对标准进行简单的后续处理,所以最适合的类为 CArrayObj。该类允许组织一个由从 CObject 类继承的对象组成的动态数组。

容器类 TCustomCriterionArray 的描述非常简单:

class TCustomCriterionArray : public CArrayObj
{
public:
  virtual double  GetCriterion( );  // get value of the optimization result
};

该类仅有一个方法 - TCustomCriterionArray::GetCriterion,该方法在每次测试完成时都会返回优化标准的值。其实现如下所示:

double  TCustomCriterionArray::GetCriterion()
{
  double  temp = 1.0;
  int     count = this.Total();
  if(count == 0)
  {
    return(0.0);
  }
  for(int i=0; i<count; i++)
  {
    temp *= ((TCustomCriterion*)(this.At(i))).GetCriterion();
    if(temp <= 0.0)
    {
      return(temp);
    }
  }

  return(temp);
}

您应注意以下一个事项:如果在标准处理期间遇到一个负值,则进一步运行循环就没有意义了。此外,它排除了将两个负值相乘后得到一个正值的情形。


7. 使用自定义优化标准的类

如此一来,我们便拥有了在 EA 交易优化期间使用“简单”自定义标准所需的一切。让我们分析改进“试验” EA FanExpert 的步骤序列:

  • 添加包含文件,该文件包含自定义标准的类的描述:
#include <CustomOptimisation.mqh>
  • 添加指向使用了自定义标准的容器类对象的指针:
TCustomCriterionArray*  criterion_Ptr;
  • 初始化指向使用了自定义标准的容器类对象的指针:
  criterion_array = new TCustomCriterionArray();
  if(CheckPointer(criterion_array) == POINTER_INVALID)
  {
    return(-1);
  }

该过程在 OnInit 函数中完成。如果未能成功创建对象,则会返回一个负值。在这种情形下,EA 交易会停止运行。

  • 将所需的优化标准添加到 EA 交易:
  criterion_Ptr.Add(new TSimpleCriterion(STAT_PROFIT));
  criterion_Ptr.Add(new TSimpleDivCriterion(STAT_BALANCE_DD));
  criterion_Ptr.Add(new TSimpleMinCriterion(STAT_TRADES, 20.0));

在本示例中,我们已决定通过最大获利、最小亏损和最多交易次数来优化 EA 交易。此外,我们还放弃了导致交易次数少于二十次的 EA 交易外部参数集。

  • 将相应的调用添加到 OnTester 函数:
  return(criterion_Ptr.GetCriterion());
  • OnDeinit 函数中,添加用于删除容器对象的代码:
  if(CheckPointer(criterion_Ptr) == POINTER_DYNAMIC)
  {
    delete(criterion_Ptr);
  }

优化工作就到此为止。运行优化并确保一切都按预期进行。为此,在策略测试程序的 Settings(设置)选项卡中设置参数,如下图所示:

策略测试程序的设置

图 6. 策略测试程序的设置

图 7 显示了策略测试程序的 Input parameters(输入参数)选项卡中输入参数优化范围的设置情况:

优化后的输入参数

图 7. 优化后的输入参数

使用“云”代理进行优化。为此,在 Agents (代理)选项卡设置以下参数:

测试代理的参数

图 8. 测试代理的参数

现在,单击 Start(开始)按钮(见图 6),然后等待优化完成。采用“云”计算技术时,优化很快就会完成。最后,我们得到根据指定标准优化后的结果:

优化结果

图 9. 优化结果

我们的“试验”EA 交易得到了成功优化。使用“云”代理进行的优化花费了 13 分钟。本文所附的 FanExpertSimple.mq5 文件包含用于检查该标准的 EA 交易。


8. 根据平衡曲线的分析结果创建自定义优化标准类

创建该类的依据是“EA 交易运行期间平衡曲线斜率的控制”一文。该优化标准旨在让平衡曲线最大程度地接近直线。通过交易结果与直线的标准方差值来估计与直线的接近程度。将计算出回归线的直线等式,该回归线根据策略测试程序中的交易结果绘制。

为了抛弃所获余额为负的曲线,设置了其他限制,即所得获利必须大于指定值,且交易次数必须小于指定值。

因此,考虑到获利限额和交易次数,优化标准将与交易结果与直线之间的标准方差值成反比。

为了依据平衡曲线实施优化标准,我们需要用到前文所提的 TBalanceSlope 类。我们将对其进行更改:使用带有参数的构造函数(为方便起见)并添加标准方差的计算,以计算线性回归。本文所附的 BalanceSlope.mqh 文件包含了此代码。

将该优化标准添加到 EA 交易的步骤与上述步骤相同。现在,优化标准如下所示:

criterion_Ptr.Add(new TBalanceSlopeCriterion(Symbol( ), 10000.0));

除平衡曲线标准以外,我们还可以添加所制定的其他标准。对于读者,我留下了使用不同测试用统计参数集进行试验的可能性。

让我们按设定的标准进行优化吧。为了获得更多交易次数,请使用 H4 时间表、2010.01.01 - 2011.01.01 区间和 EURUSD 交易品种进行优化。我们将得到一组结果:

按平衡曲线进行优化的结果

图 10. 按平衡曲线进行优化的结果

现在,我们需要估计优化的质量。我认为主要标准是 EA 交易在优化区间以外的运行情况。为检查此类运行,在 2010.01.01-2011.06.14 的时间段运行一次测试。

比较两个最佳参数集的结果(其获利几乎相同)后发现,最佳结果是位于中间部分的结果。优化区间以外的结果用红线区别开来:

最佳优化结果

图 11. 最佳优化结果

整体而言,曲线的表现尚未变差。盈利能力从 1.60 稍微下降到 1.56。

中间优化结果

图 12. 中间优化结果

EA 交易在优化区间以外没有获利。盈利能力从 2.17 显著下降到 1.75。

因此,我们可以得出如下结论,即平衡曲线与优化参数运行持续时间之间存在相关性这一假设是有其存在理由的。当然,我们不能排除出现以下特殊情形,即使用该标准的可接受结果对于 EA 交易而言是不可达到的。在这种情况下,我们需要再进行一些分析和试验。

或许,对于此标准,我们需要使用可能(但是合理)的最大区间。本文所附的 FanExpertBalance.mq5 文件包含了用于检查该标准的 EA 交易。


9. 根据交易系统的安全系数 (CSTS) 创建自定义优化标准类

如“协调一致”一文所述,交易系统的安全系数 (CSTS) 使用以下公式计算:

CSTS = Avg.Win / Avg.Loss ((110% - %Win) / (%Win-10%) + 1)

其中:

  • Avg.Win - 获利交易的平均值;
  • Avg.Loss - 损失交易的平均值;
  • %Win - 获利交易的百分比;

如果 CSTS 值小于 1,则交易系统位于交易高风险区;即使较小的值也表示位于不盈利交易区域。CSTS 的值越大,交易系统就越适合市场,且盈利越多。

在策略测试程序中,计算 CSTS 所需的一切统计值都在每次测试通过后进行计算。现在有待创建从 TCustomCriterion 继承的 TTSSFCriterion 类,并在其中实现 GetCriterion() 方法。该方法的实现代码如下所示:

double  TTSSFCriterion::GetCriterion()
{
  double  avg_win = TesterStatistics(STAT_GROSS_PROFIT) / TesterStatistics(STAT_PROFIT_TRADES);
  double  avg_loss = -TesterStatistics(STAT_GROSS_LOSS) / TesterStatistics(STAT_LOSS_TRADES);
  double  win_perc = 100.0 * TesterStatistics(STAT_PROFIT_TRADES) / TesterStatistics(STAT_TRADES);

//  Calculated safe ratio for this percentage of profitable deals:
  double  teor = (110.0 - win_perc) / (win_perc - 10.0) + 1.0;

//  Calculate real ratio:
  double  real = avg_win / avg_loss;

//  CSTS:
  double  tssf = real / teor;

  return(tssf);
}

假定较短区间适合该优化标准。然而,为了避免拟合,我们最好采用在优化结果中处于中间位置的结果。

让我们向读者提供自行优化的可能性。本文所附的 FanExpertTSSF.mq5 文件包含用于检查该标准的 EA 交易。


总结

无论如何您都得承认,与其他情形相比,如此简单的解决方案(采用单个积分率)几乎是完美的,通过该方案有可能创建自定义的优化标准。该方案允许将可靠交易系统的开发门槛提升到更高水平。“云”技术的采用显著减少了所进行优化的限制。

其他演化方式可能涉及不同信息源所描述的标准,这些标准已从数学和统计学角度进行了证实。我们拥有相应的工具。


全部回复

0/140

达人推荐

量化课程

    移动端课程