English Русский Español Deutsch 日本語 Português
preview
使用优化算法即时配置 EA 参数

使用优化算法即时配置 EA 参数

MetaTrader 5测试者 | 18 九月 2024, 09:40
66 0
Andrey Dik
Andrey Dik

目录

1.概述
2.具有自我优化功能的 EA 结构
3.指标虚拟化
4.策略虚拟化
5.测试功能



1.概述

我经常被问到,如何在使用 EA 和策略时应用优化算法。在本文中,我想谈谈使用优化算法的实际问题。

在当今的金融世界里,每一毫秒都可能产生巨大的变化,因此算法交易变得越来越有必要。而优化算法在创建高效交易策略方面发挥着关键作用。也许有些怀疑者认为,优化算法和交易没有共同点。然而,在本文中,我将展示这两个领域如何相互作用,以及从这种相互作用中可以获得什么好处。

对于交易新手来说,了解优化算法的基本原理是寻找盈利交易和最大限度降低风险的有力工具。对于经验丰富的专业人士来说,这方面的深厚知识可以开拓新的视野,帮助他们制定出超出预期的复杂交易策略。

EA 中的自我优化是指 EA 交易根据历史数据和当前市场条件调整其交易策略参数以获得更好性能的过程。该过程可包括以下方面:

  • 数据收集与分析。EA 交易应收集和分析历史市场数据。这可能涉及使用各种数据分析技术,如统计分析、机器学习和人工智能。
  • 设定目标。EA 交易应有明确定义的努力目标。这可能是利润最大化、风险最小化或达到一定的盈利水平。
  • 应用优化算法。为了达到最佳效果,EA 可以使用各种优化算法。这些算法有助于 EA 找到策略参数的最佳值。
  • 测试和验证。优化后,应根据当前市场条件对 EA 交易进行回溯测试和验证,以确保其效率。测试有助于评估 EA 交易的性能及其适应不断变化的市场条件的能力。
  • 监测和更新。对于 EA 交易应持续监控其性能,并在必要时更新其策略参数。市场是不断变化的,EA 交易应该愿意适应新的条件以及趋势和波动的变化。

在交易中使用优化算法主要有几种情况:

  • 优化交易策略的参数。优化算法可用于调整交易策略的参数。利用这些方法,我们可以确定移动平均周期、止损和止盈水平等参数的最佳值,或与交易信号和规则相关的其他参数。
  • 优化进入/退出市场的时间。优化算法可根据历史数据和当前市场条件,帮助确定进入和退出市场的最佳时机。例如,优化算法可用于确定交易信号的最佳时间间隔。
  • 投资组合管理。 优化算法可帮助确定投资组合中的最佳资产配置,以实现既定目标。例如,我们可以利用均值-方差优化等优化技术,在预期收益和风险的条件下找到最有效的资产组合。这可能包括确定股票、债券和其他资产之间的最佳组合,以及优化仓位大小和投资组合的多样化。
  • 制定交易策略。优化算法可用于开发新的交易策略。例如,我们可以使用遗传编程,根据历史数据进化寻找进入和退出市场的最佳规则。
  • 风险管理。优化算法有助于管理交易风险。例如,我们可以使用优化算法来计算最佳仓位大小,或确定动态止损水平,以最大限度地减少潜在损失。
  • 选择最佳交易工具。优化算法有助于选择最佳交易工具或交易资产。例如,优化算法可用于根据盈利能力、波动性或流动性等各种标准对资产进行排序。
  • 预测金融市场。优化算法可用于预测金融市场。优化算法可用于调整预测模型的参数或选择预测模型的最佳组合。

以上只是在交易中使用优化算法的一些示例,总之,优化算法可以帮助自动化和改进交易的各个方面,从寻找最佳策略到风险和投资组合管理。


2.具有自我优化功能的 EA 结构

为了确保 EA 的自我优化,有几种可行的方案,但其中最简单且对实现任何所需的能力和功能要求最低的方案是图 1 所示的方案。

在 "History"(历史) 时间线上,EA 处于 "time now"(现在时间) 点,也就是做出优化决策的时间点。EA 调用管理优化过程的 "Manager function"(管理器函数)。EA 将 "optimization parameters" (优化参数)传递给该函数。

反过来,管理器要求 "optimization ALGO" (优化算法)或 "AO"提供一组参数,这组参数现在和以后都称为 "set"(参数集)。之后,管理者会将这套策略转移到虚拟交易策略 "EA Virt" 中,该策略是真实策略 "EA" 的完整模拟,可以执行交易操作。

"EA Virt" 从历史的 "past"(过去) 点到 "time now"(现在)点进行虚拟交易。管理器运行 "EA Virt" 的次数与 "optimization parameters"(优化参数) 中指定的群体大小相同。而 "EA Virt" 则以 "ff result" 的形式返回历史运行结果。

"ff result" 是适应度函数或适应度或优化标准的结果,可以是用户自行决定的任何东西。例如,这可以是一个余额、利润因子、数学期望值或一个复杂的标准,也可以是在 "历史" 时间的许多点上测量的积分或累积微分标准。因此,用户认为适配函数结果或 "ff result" 是衡量交易策略质量的重要指标。

接下来,"ff result",即对特定集合的评估,由管理器传递给优化算法。

当达到停止条件时,管理器会将最佳设置传输给 "EA",之后,"EA" 会使用新更新的参数继续工作(交易),从 "time now" 点到 "reoptimiz" 重新优化点,在这里,它会再次优化到给定的历史深度。

重新优化点的选择有多种原因。它可以是严格定义的历史柱数(如下例),也可以是某些特定条件,例如交易指标下降到某个临界水平。

框架

图 1.EA 自我优化结构

根据 "optimization ALGO" 优化算法方案,无论具体的交易策略、管理者和虚拟策略如何,它都可以被视为一个自主执行工作的 "黑盒子"(然而,外部的一切对它来说也是一个 "黑盒子")。管理器向优化算法请求一组数据,并发回对这组数据的评估。优化算法利用这一评估结果来确定下一组数据。如此循环往复,直到找到满足用户要求的最佳参数集。因此,优化算法会寻找最佳参数,也就是通过 "EA Virt "中的适应度函数来满足用户需求的参数。


3.指标虚拟化

要在历史数据上运行 EA,我们需要创建一个交易策略的虚拟副本,它将执行与在交易账户上运行时相同的交易操作。在不使用指标的情况下,EA 内部逻辑条件的虚拟化变得相对简单。我们只需要根据价格序列中的时间点来描述逻辑行动。同时,指标的使用也是一项较为复杂的工作,大多数情况下,交易策略都依赖于各种指标。

问题在于,在寻找最佳指标参数时,有必要在给定的迭代中用当前设置创建指标句柄。历史数据运行完成后,应删除这些句柄,否则内存可能很快就会被填满,尤其是在一组参数(集)有大量可能选项的情况下。如果在交易品种图表中执行此操作,则不会有问题,但在测试器中不允许删除句柄。

要解决这个问题,我们需要在可执行 EA 中 "虚拟化" 指标的计算,以避免使用句柄。让我们以随机振荡指标为例。

每个指标的计算部分都包含标准的 OnCalculate 函数。这个函数应该改名,比如说,改成 "Calculate",并且几乎保持不变。 

我们需要将指标设计为一个类(结构也适用)。就叫它 "C_Stochastic" 吧。在类声明中,我们需要将主要指标缓冲区注册为公有字段(附加计算缓冲区可以是私有的),并声明需要将指标参数传递给的 Init 初始化函数。

//——————————————————————————————————————————————————————————————————————————————
class C_iStochastic
{
  public: void Init (const int InpKPeriod,       // K period
                     const int InpDPeriod,       // D period
                     const int InpSlowing)       // Slowing
  {
    inpKPeriod = InpKPeriod;
    inpDPeriod = InpDPeriod;
    inpSlowing = InpSlowing;
  }

  public: int Calculate (const int rates_total,
                         const int prev_calculated,
                         const double &high  [],
                         const double &low   [],
                         const double &close []);

  //--- indicator buffers
  public:  double ExtMainBuffer   [];
  public:  double ExtSignalBuffer [];
  private: double ExtHighesBuffer [];
  private: double ExtLowesBuffer  [];

  private: int inpKPeriod; // K period
  private: int inpDPeriod; // D period
  private: int inpSlowing; // Slowing
};
//——————————————————————————————————————————————————————————————————————————————

以及在 "Calculate" 方法中计算指标本身。该指标的计算方法与终端标准发布中的指标完全相同。唯一的区别在于指标缓冲区的大小分布及其初始化。 

这是了解指标虚拟化原理的一个非常简单的例子。计算是针对指标参数中指定的整个周期深度进行的。可以组织只计算最后一个柱形的附加功能,并实施环形缓冲区,但本文的目的是展示一个简单的示例,在将指标转换为虚拟形式时只需最少的干预,而且编程技能最低的用户也可以使用。

//——————————————————————————————————————————————————————————————————————————————
int C_iStochastic::Calculate (const int rates_total,
                              const int prev_calculated,
                              const double &high  [],
                              const double &low   [],
                              const double &close [])
{
  if (rates_total <= inpKPeriod + inpDPeriod + inpSlowing) return (0);

  ArrayResize (ExtHighesBuffer, rates_total);
  ArrayResize (ExtLowesBuffer,  rates_total);
  ArrayResize (ExtMainBuffer,   rates_total);
  ArrayResize (ExtSignalBuffer, rates_total);

  ArrayInitialize (ExtHighesBuffer, 0.0);
  ArrayInitialize (ExtLowesBuffer,  0.0);
  ArrayInitialize (ExtMainBuffer,   0.0);
  ArrayInitialize (ExtSignalBuffer, 0.0);

  int i, k, start;

  start = inpKPeriod - 1;

  if (start + 1 < prev_calculated)
  {
    start = prev_calculated - 2;
    Print ("start ", start);
  }
  else
  {
    for (i = 0; i < start; i++)
    {
      ExtLowesBuffer  [i] = 0.0;
      ExtHighesBuffer [i] = 0.0;
    }
  }

  //--- calculate HighesBuffer[] and ExtHighesBuffer[]
  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double dmin =  1000000.0;
    double dmax = -1000000.0;

    for (k = i - inpKPeriod + 1; k <= i; k++)
    {
      if (dmin > low  [k]) dmin = low  [k];
      if (dmax < high [k]) dmax = high [k];
    }

    ExtLowesBuffer  [i] = dmin;
    ExtHighesBuffer [i] = dmax;
  }

  //--- %K
  start = inpKPeriod - 1 + inpSlowing - 1;

  if (start + 1 < prev_calculated) start = prev_calculated - 2;
  else
  {
    for (i = 0; i < start; i++) ExtMainBuffer [i] = 0.0;
  }

  //--- main cycle
  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double sum_low  = 0.0;
    double sum_high = 0.0;

    for (k = (i - inpSlowing + 1); k <= i; k++)
    {
      sum_low  += (close [k] - ExtLowesBuffer [k]);
      sum_high += (ExtHighesBuffer [k] - ExtLowesBuffer [k]);
    }

    if (sum_high == 0.0) ExtMainBuffer [i] = 100.0;
    else                 ExtMainBuffer [i] = sum_low / sum_high * 100;
  }

  //--- signal
  start = inpDPeriod - 1;

  if (start + 1 < prev_calculated) start = prev_calculated - 2;
  else
  {
    for (i = 0; i < start; i++) ExtSignalBuffer [i] = 0.0;
  }

  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double sum = 0.0;
    for (k = 0; k < inpDPeriod; k++) sum += ExtMainBuffer [i - k];
    ExtSignalBuffer [i] = sum / inpDPeriod;
  }

  //--- OnCalculate done. Return new prev_calculated.
  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————

此外,我还将举例说明 MACD 指标的虚拟化:

//——————————————————————————————————————————————————————————————————————————————
class C_iMACD
{
  public: void Init (const int InpFastEMA,       // Fast   EMA period
                     const int InpSlowEMA,       // Slow   EMA period
                     const int InpSignalSMA)     // Signal SMA period
  {
    inpFastEMA   = InpFastEMA;
    inpSlowEMA   = InpSlowEMA;
    inpSignalSMA = InpSignalSMA;

    maxPeriod = InpFastEMA;
    if (maxPeriod < InpSlowEMA)   maxPeriod = InpSlowEMA;
    if (maxPeriod < InpSignalSMA) maxPeriod = InpSignalSMA;
  }

  public: int Calculate (const int rates_total,
                         const int prev_calculated,
                         const double &close []);

  //--- indicator buffers
  public:  double ExtMacdBuffer   [];
  public:  double ExtSignalBuffer [];
  private: double ExtFastMaBuffer [];
  private: double ExtSlowMaBuffer [];

  private: int ExponentialMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer []);
  private: int SimpleMAOnBuffer      (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer []);

  private: int inpFastEMA;   // Fast EMA period
  private: int inpSlowEMA;   // Slow EMA period
  private: int inpSignalSMA; // Signal SMA period
  private: int maxPeriod;
};
//——————————————————————————————————————————————————————————————————————————————

指标特定部分:

//——————————————————————————————————————————————————————————————————————————————
int C_iMACD::Calculate (const int rates_total,
                        const int prev_calculated,
                        const double &close [])
{
  if (rates_total < maxPeriod) return (0);

  ArrayResize (ExtMacdBuffer,   rates_total);
  ArrayResize (ExtSignalBuffer, rates_total);
  ArrayResize (ExtFastMaBuffer, rates_total);
  ArrayResize (ExtSlowMaBuffer, rates_total);

  ArrayInitialize (ExtMacdBuffer,   0.0);
  ArrayInitialize (ExtSignalBuffer, 0.0);
  ArrayInitialize (ExtFastMaBuffer, 0.0);
  ArrayInitialize (ExtSlowMaBuffer, 0.0);

  ExponentialMAOnBuffer (rates_total, prev_calculated, 0, inpFastEMA, close, ExtFastMaBuffer);
  ExponentialMAOnBuffer (rates_total, prev_calculated, 0, inpSlowEMA, close, ExtSlowMaBuffer);

  int start;
  if (prev_calculated == 0) start = 0;

  else start = prev_calculated - 1;

  //--- calculate MACD
  for (int i = start; i < rates_total && !IsStopped (); i++) ExtMacdBuffer [i] = ExtFastMaBuffer [i] - ExtSlowMaBuffer [i];

  //--- calculate Signal
  SimpleMAOnBuffer (rates_total, prev_calculated, 0, inpSignalSMA, ExtMacdBuffer, ExtSignalBuffer);

  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————

指数平滑计算完全无需更改:

//——————————————————————————————————————————————————————————————————————————————
int C_iMACD::ExponentialMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer [])
{
  //--- check period
  if (period <= 1 || period > (rates_total - begin)) return (0);

  //--- save and clear 'as_series' flags
  bool as_series_price  = ArrayGetAsSeries (price);
  bool as_series_buffer = ArrayGetAsSeries (buffer);

  ArraySetAsSeries (price, false);
  ArraySetAsSeries (buffer, false);

  //--- calculate start position
  int    start_position;
  double smooth_factor = 2.0 / (1.0 + period);

  if (prev_calculated == 0) // first calculation or number of bars was changed
  {
    //--- set empty value for first bars
    for (int i = 0; i < begin; i++) buffer [i] = 0.0;

    //--- calculate first visible value
    start_position = period + begin;
    buffer [begin] = price [begin];

    for (int i = begin + 1; i < start_position; i++) buffer [i] = price [i] * smooth_factor + buffer [i - 1] * (1.0 - smooth_factor);
  }
  else start_position = prev_calculated - 1;

  //--- main loop
  for (int i = start_position; i < rates_total; i++) buffer [i] = price [i] * smooth_factor + buffer [i - 1] * (1.0 - smooth_factor);

  //--- restore as_series flags
  ArraySetAsSeries (price,  as_series_price);
  ArraySetAsSeries (buffer, as_series_buffer);
  //---
  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————

简单平滑的计算也无需更改:

//——————————————————————————————————————————————————————————————————————————————
int C_iMACD::SimpleMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer [])
{
  //--- check period
  if (period <= 1 || period > (rates_total - begin)) return (0);

  //--- save as_series flags
  bool as_series_price = ArrayGetAsSeries (price);
  bool as_series_buffer = ArrayGetAsSeries (buffer);

  ArraySetAsSeries (price, false);
  ArraySetAsSeries (buffer, false);

  //--- calculate start position
  int start_position;

  if (prev_calculated == 0) // first calculation or number of bars was changed
  {
    //--- set empty value for first bars
    start_position = period + begin;

    for (int i = 0; i < start_position - 1; i++) buffer [i] = 0.0;

    //--- calculate first visible value
    double first_value = 0;

    for (int i = begin; i < start_position; i++) first_value += price [i];

    buffer [start_position - 1] = first_value / period;
  }
  else start_position = prev_calculated - 1;

  //--- main loop
  for (int i = start_position; i < rates_total; i++) buffer [i] = buffer [i - 1] + (price [i] - price [i - period]) / period;

  //--- restore as_series flags
  ArraySetAsSeries (price, as_series_price);
  ArraySetAsSeries (buffer, as_series_buffer);

  //---
  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————


4.策略虚拟化

我的优化算法文章的一位读者路易斯-阿尔贝托-比安努奇(LUIS ALBERTO BIANUCCI)友好地提供了基于随机振荡指标的 EA 代码。他要求我根据这段代码创建一个示例,以演示在 EA 中使用AO Core库安排自学的方法,并在文章中探讨了这个示例。这样,其他用户在自己的开发中连接优化算法时就可以使用这种方法。我想强调的是,这种方法适用于连接我的 "群体优化算法" 系列中讨论的任何优化算法,因为这些算法是以通用形式设计的,可以成功应用于任何用户项目。

我们已经了解了作为 EA 一部分的指标虚拟化。现在,我们开始考虑策略的虚拟化问题。在 EA 代码的开头,我们将声明导入库、标准交易库的包含文件和虚拟随机事件的包含文件。

接下来是 "input" - EA 参数,其中最重要的是 InpKPeriod_P 和 InpUpperLevel_P。它们代表随机振荡指标的周期和水平,需要进行优化。

input string   InpKPeriod_P        = "18|9|3|24";  //STO K period:      有必要优化
input string   InpUpperLevel_P  = "96|88|2|98"; //STO upper level: 有必要优化

参数以字符串类型声明,参数是复合型的,包括默认值、优化初始值步长最终值

//——————————————————————————————————————————————————————————————————————————————
#import "\\Market\\AO Core.ex5"
bool   Init (int colonySize, double &range_min [], double &range_max [], double &range_step []);
//------------------------------------------------------------------------------
void   Preparation    ();
void   GetVariantCalc (double &variant [], int pos);
void   SetFitness     (double value,       int pos);
void   Revision       ();
//------------------------------------------------------------------------------
void   GetVariant     (double &variant [], int pos);
double GetFitness     (int pos);
#import
//——————————————————————————————————————————————————————————————————————————————

#include <Trade\Trade.mqh>;
#include "cStochastic.mqh"


input group         "==== GENERAL ====";
sinput long         InpMagicNumber      = 132516;       //Magic Number
sinput double       InpLotSize          = 0.01;         //Lots

input group         "==== Trading ====";
input int           InpStopLoss         = 1450;         //Stoploss
input int           InpTakeProfit       = 1200;         //Takeprofit

input group         "==== Stochastic ==|value|start|step|end|==";
input string        InpKPeriod_P        = "18|9|3|24";  //STO K period   : it is necessary to optimize
input string        InpUpperLevel_P     = "96|88|2|98"; //STO upper level: it is necessary to optimize

input group         "====Self-optimization====";
sinput bool         SelfOptimization    = true;
sinput int          InpBarsOptimize     = 18000;        //Number of bars in the history for optimization
sinput int          InpBarsReOptimize   = 1440;         //After how many bars, EA will reoptimize
sinput int          InpPopSize          = 50;           //Population size
sinput int          NumberFFlaunches    = 10000;        //Number of runs in the history during optimization
sinput int          Spread              = 10;           //Spread

MqlTick Tick;
CTrade  Trade;

C_iStochastic IStoch;

double Set        [];
double Range_Min  [];
double Range_Step [];
double Range_Max  [];

double TickSize = 0.0;

在 OnInit 函数中初始化 EA 时,我们将根据优化参数的数量设置参数数组的大小:Set - 一组参数,Range_Min - 最小参数值(起始值),Range_Step - 参数步长,Range_Max - 最大参数值。从字符串参数中提取相应的值并将其赋值给数组。

//——————————————————————————————————————————————————————————————————————————————
int OnInit ()
{
  TickSize = SymbolInfoDouble (_Symbol, SYMBOL_TRADE_TICK_SIZE);

  ArrayResize (Set,        2);
  ArrayResize (Range_Min,  2);
  ArrayResize (Range_Step, 2);
  ArrayResize (Range_Max,  2);

  string result [];
  if (StringSplit (InpKPeriod_P, StringGetCharacter ("|", 0), result) != 4) return INIT_FAILED;

  Set        [0] = (double)StringToInteger (result [0]);
  Range_Min  [0] = (double)StringToInteger (result [1]);
  Range_Step [0] = (double)StringToInteger (result [2]);
  Range_Max  [0] = (double)StringToInteger (result [3]);

  if (StringSplit (InpUpperLevel_P, StringGetCharacter ("|", 0), result) != 4) return INIT_FAILED;

  Set        [1] = (double)StringToInteger (result [0]);
  Range_Min  [1] = (double)StringToInteger (result [1]);
  Range_Step [1] = (double)StringToInteger (result [2]);
  Range_Max  [1] = (double)StringToInteger (result [3]);

  IStoch.Init ((int)Set [0], 1, 3);

  //  set magicnumber to trade object
  Trade.SetExpertMagicNumber (InpMagicNumber);

  //---
  return (INIT_SUCCEEDED);
}
//——————————————————————————————————————————————————————————————————————————————

在 EA 代码的 OnTick 函数中,我们插入了自我优化调用块 - "Optimize" 函数,它是图 1 中的 "manager",开始优化。在需要优化的外部变量中使用Set数组中的值。

//——————————————————————————————————————————————————————————————————————————————
void OnTick ()
{
  //----------------------------------------------------------------------------
  if (!IsNewBar ())
  {
    return;
  }

  //----------------------------------------------------------------------------
  if (SelfOptimization)
  {
    //--------------------------------------------------------------------------
    static datetime LastOptimizeTime = 0;

    datetime timeNow  = iTime (_Symbol, PERIOD_CURRENT, 0);
    datetime timeReop = iTime (_Symbol, PERIOD_CURRENT, InpBarsReOptimize);

    if (LastOptimizeTime <= timeReop)
    {
      LastOptimizeTime = timeNow;
      Print ("-------------------Start of optimization----------------------");

      Print ("Old set:");
      ArrayPrint (Set);

      Optimize (Set,
                Range_Min,
                Range_Step,
                Range_Max,
                InpBarsOptimize,
                InpPopSize,
                NumberFFlaunches,
                Spread * SymbolInfoDouble (_Symbol, SYMBOL_TRADE_TICK_SIZE));

      Print ("New set:");
      ArrayPrint (Set);

      IStoch.Init ((int)Set [0], 1, 3);
    }
  }

  //----------------------------------------------------------------------------
  if (!SymbolInfoTick (_Symbol, Tick))
  {
    Print ("Failed to get current symbol tick"); return;
  }

  //data preparation------------------------------------------------------------
  MqlRates rates [];
  int dataCount = CopyRates (_Symbol, PERIOD_CURRENT, 0, (int)Set [0] + 1 + 3 + 1, rates);

  if (dataCount == -1)
  {
    Print ("Data get error");
    return;
  }

  double hi [];
  double lo [];
  double cl [];

  ArrayResize (hi, dataCount);
  ArrayResize (lo, dataCount);
  ArrayResize (cl, dataCount);

  for (int i = 0; i < dataCount; i++)
  {
    hi [i] = rates [i].high;
    lo [i] = rates [i].low;
    cl [i] = rates [i].close;
  }

  int calc = IStoch.Calculate (dataCount, 0, hi, lo, cl);
  if (calc <= 0) return;

  double buff0 = IStoch.ExtMainBuffer [ArraySize (IStoch.ExtMainBuffer) - 2];
  double buff1 = IStoch.ExtMainBuffer [ArraySize (IStoch.ExtMainBuffer) - 3];

  //----------------------------------------------------------------------------
  // count open positions
  int cntBuy, cntSell;
  if (!CountOpenPositions (cntBuy, cntSell))
  {
    Print ("Failed to count open positions");
    return;
  }

  //----------------------------------------------------------------------------
  // check for buy
  if (cntBuy == 0 && buff1 <= (100 - (int)Set [1]) && buff0 > (100 - (int)Set [1]))
  {
    ClosePositions (2);

    double sl = NP (Tick.bid - InpStopLoss   * TickSize);
    double tp = NP (Tick.bid + InpTakeProfit * TickSize);

    Trade.PositionOpen (_Symbol, ORDER_TYPE_BUY, InpLotSize, Tick.ask, sl, tp, "Stochastic EA");
  }

  //----------------------------------------------------------------------------
  // check for sell
  if (cntSell == 0 && buff1 >= (int)Set [1] && buff0 < (int)Set [1])
  {
    ClosePositions (1);

    double sl = NP (Tick.ask + InpStopLoss   * TickSize);
    double tp = NP (Tick.ask - InpTakeProfit * TickSize);

    Trade.PositionOpen (_Symbol, ORDER_TYPE_SELL, InpLotSize, Tick.bid, sl, tp, "Stochastic EA");
  }
}
//——————————————————————————————————————————————————————————————————————————————

"Optimize" 函数执行的操作与 "群体优化算法" 系列文章中的优化算法测试脚本通常执行的操作相同:

1.优化算法的初始化。
2.1.群体准备。
2.2.从优化算法中获取一组参数。
2.3.利用传入的参数计算适应度函数。
2.4.更新最佳解决方案。
2.5.从算法中获取最佳解决方案。

//——————————————————————————————————————————————————————————————————————————————
void Optimize (double      &set        [],
               double      &range_min  [],
               double      &range_step [],
               double      &range_max  [],
               const int    inpBarsOptimize,
               const int    inpPopSize,
               const int    numberFFlaunches,
               const double spread)
{
  //----------------------------------------------------------------------------
  double parametersSet [];
  ArrayResize(parametersSet, ArraySize(set));

  //----------------------------------------------------------------------------
  int epochCount = numberFFlaunches / inpPopSize;

  Init(inpPopSize, range_min, range_max, range_step);

  // Optimization-------------------------------------------------------------
  for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++)
  {
    Preparation ();

    for (int set = 0; set < inpPopSize; set++)
    {
      GetVariantCalc (parametersSet, set);
      SetFitness     (VirtualStrategy (parametersSet, inpBarsOptimize, spread), set);
    }

    Revision ();
  }

  Print ("Fitness: ", GetFitness (0));
  GetVariant (parametersSet, 0);
  ArrayCopy (set, parametersSet, 0, 0, WHOLE_ARRAY);
}
//——————————————————————————————————————————————————————————————————————————————

VirtualStrategy 函数在历史数据上执行策略测试(在图 1 中为 "EA Virt")。它使用参数 "set" 数组、"barsOptimize" - 要优化的柱数和 "spread "值。

首先,准备数据。历史数据被载入 "rates" 数组。然后创建计算随机数所需的数组 "hi"、"lo" 和 "cl"。

接下来,根据历史数据对随机振荡指标进行初始化和计算。如果计算失败,函数将返回"-DBL_MAX "值(适应度函数的最差可能值)。

然后在历史数据上测试该策略,其逻辑与 EA 主代码完全一致。创建 "deals" 对象是为了存储交易。然后运行历史数据,根据指标值以及 "upLevel" 和 "dnLevel" 水平检查每个柱形的开仓和平仓条件。如果满足条件,则打开或关闭仓位。

完成历史数据运行后,该功能会检查已完成交易的数量。如果没有交易,函数将返回"-DBL_MAX"值。否则,函数返回最终余额。

VirtualStrategy 的返回值是适应度函数值。在这种情况下,这就是以点数为单位的最终余额值(如前所述,适应度函数可以是余额、利润因子或任何其他基于历史数据的交易操作结果质量指标)。

需要注意的是,虚拟策略应尽可能与 EA 的策略相匹配。在此示例中,交易以开盘价进行,这与主 EA 中的柱形开盘控制相对应。如果交易策略逻辑是在每个分时报价点上执行的,那么用户需要在虚拟测试期间下载分时报价历史记录,并相应调整 VirtualStrategy 函数。

//——————————————————————————————————————————————————————————————————————————————
double VirtualStrategy (double &set [], int barsOptimize, double spread)
{
  //data preparation------------------------------------------------------------
  MqlRates rates [];
  int dataCount = CopyRates(_Symbol, PERIOD_CURRENT, 0, barsOptimize + 1, rates);

  if (dataCount == -1)
  {
    Print ("Data get error");
    return -DBL_MAX;
  }

  double hi [];
  double lo [];
  double cl [];

  ArrayResize (hi, dataCount);
  ArrayResize (lo, dataCount);
  ArrayResize (cl, dataCount);

  for (int i = 0; i < dataCount; i++)
  {
    hi [i] = rates [i].high;
    lo [i] = rates [i].low;
    cl [i] = rates [i].close;
  }

  C_iStochastic iStoch;
  iStoch.Init ((int)set [0], 1, 3);

  int calc = iStoch.Calculate (dataCount, 0, hi, lo, cl);
  if (calc <= 0) return -DBL_MAX;

  //============================================================================
  //test of strategy on history-------------------------------------------------
  S_Deals deals;

  double iStMain0 = 0.0;
  double iStMain1 = 0.0;
  double upLevel  = set [1];
  double dnLevel  = 100.0 - set [1];
  double balance  = 0.0;

  //running through history-----------------------------------------------------
  for (int i = 2; i < dataCount; i++)
  {
    if (i >= dataCount)
    {
      deals.ClosPos (-1, rates [i].open, spread);
      deals.ClosPos (1, rates [i].open, spread);
      break;
    }

    iStMain0 = iStoch.ExtMainBuffer [i - 1];
    iStMain1 = iStoch.ExtMainBuffer [i - 2];

    if (iStMain0 == 0.0 || iStMain1 == 0.0) continue;

    //buy-------------------------------
    if (iStMain1 <= dnLevel && dnLevel < iStMain0)
    {
      deals.ClosPos (-1, rates [i].open, spread);

      if (deals.GetBuys () == 0) deals.OpenPos (1, rates [i].open, spread);
    }

    //sell------------------------------
    if (iStMain1 >= upLevel && upLevel > iStMain0)
    {
      deals.ClosPos (1, rates [i].open, spread);

      if (deals.GetSels () == 0) deals.OpenPos (-1, rates [i].open, spread);
    }
  }
  //----------------------------------------------------------------------------

  if (deals.histSelsCNT + deals.histBuysCNT <= 0) return -DBL_MAX;
  return deals.balance;
}
//——————————————————————————————————————————————————————————————————————————————

如果我们想使用 "群体优化算法" 系列中的优化算法(文章档案中提供了 "社会群体进化" 算法,或 ESG 算法作为示例),那么我们需要在 EA 中指定算法的路径:

#include "AO_ESG.mqh"

在 Optimize 函数中,声明 ESG 算法对象并配置优化参数的边界值。那么,使用 ESG 时的 Optimize 函数将如下所示:

//——————————————————————————————————————————————————————————————————————————————
void Optimize (double      &set        [],
               double      &range_min  [],
               double      &range_step [],
               double      &range_max  [],
               const int    inpBarsOptimize,
               const int    inpPopSize,
               const int    numberFFlaunches,
               const double spread)
{
  //----------------------------------------------------------------------------
  int epochCount = numberFFlaunches / inpPopSize;

  C_AO_ESG AO;
  
  int    Population_P     = 200;   //Population size
  int    Groups_P         = 100;   //Number of groups
  double GroupRadius_P    = 0.1;   //Group radius
  double ExpansionRatio_P = 2.0;   //Expansion ratio
  double Power_P          = 10.0;  //Power
  
  AO.Init (ArraySize (set), Population_P, Groups_P, GroupRadius_P, ExpansionRatio_P, Power_P);
  
  for (int i = 0; i < ArraySize (set); i++)
  {
    AO.rangeMin  [i] = range_min  [i];
    AO.rangeStep [i] = range_step [i];
    AO.rangeMax  [i] = range_max  [i];
  }
  
  // Optimization-------------------------------------------------------------
  for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++)
  {
    AO.Moving ();
    
    for (int set = 0; set < ArraySize (AO.a); set++)
    {
      AO.a [set].f = VirtualStrategy (AO.a [set].c, inpBarsOptimize, spread);
    }

    AO.Revision ();
  }

  Print ("Fitness: ", AO.fB);
  ArrayCopy (set, AO.cB, 0, 0, WHOLE_ARRAY);
}
//——————————————————————————————————————————————————————————————————————————————

我在优化系列文章中介绍的搜索算法策略提供了一种简单明了的方法,用于分析和比较它们最纯粹的形式 - "按原样"。它们不包括加快搜索速度的方法,如消除重复和其他一些技术,因此需要更多的迭代和时间。


5.测试功能

首先,在 SelfOptimization 参数的 "false" 模式下,即不进行自我优化的情况下,让我们用下面截图中的参数对基于随机振荡指标的自我优化 EA 进行为期一年的测试。

photo_

图 2.EA 设置

原始测试

图 3.禁用自我优化的结果

自我优化

图 4.启用自我优化的结果


总结

在本文中,我们探讨了在 EA 中进行自我优化的问题。这种方法非常简单,只需对 EA 源代码进行最少的干预。对于每种特定策略,建议进行一系列实验,以确定历史片段的最佳长度,并在此基础上进行优化和交易。这些价值取决于个人和策略。

重要的是要明白,如果策略本身没有盈利的套路,优化就无法带来积极的结果。如果沙子里本来就没有金子,就不可能从沙子里提炼出金子。优化是提高策略成效的有用工具,但它无法在没有盈利的情况下创造盈利集。因此,首先应制定有盈利潜力的策略,然后利用优化来改进策略。

这种方法的优势在于能够利用前向步行测试对历史数据进行策略测试,并找到与特定策略相对应的合适优化标准。前进式测试使我们能够根据历史数据评估策略的效率,同时考虑到市场条件随时间的变化。这有助于避免过度优化,即某项策略只在某段历史时期内有效,但无法成功地实时应用。因此,前进式测试能更可靠地评估策略性能。

前进式测试(WFT)是一种评估和测试金融市场交易策略的技术。它用于根据历史数据确定交易策略的效率和可持续性,以及其在未来提供盈利的能力。

WFT 的基本思想是将可用数据分为几个阶段:用于开发和调整策略的历史阶段(训练阶段),以及用于评估和测试策略的后续阶段(测试阶段)。这个过程可以重复多次,每次训练期向前推移一步,测试期也向前推移一步。因此,要在不同的时间框架内对该策略进行测试,以确保它能够适应不断变化的市场条件。

前进式测试是评估策略的一种更现实的方法,因为它考虑到了市场条件随时间的变化。它还有助于避免根据历史数据对策略进行过度训练,并更准确地了解策略在现实世界中的表现。

在本文所附的档案中,您可以找到演示如何将优化算法连接到 EA 的示例。您将能够研究代码,并将其应用到您的具体策略中,以实现最佳结果。

由于没有经过必要的检查,所给出的示例并不用于真实账户的交易,其目的只是演示自我优化的可能性。

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/14183

附加的文件 |
种群优化算法:细菌觅食优化 — 遗传算法(BFO-GA) 种群优化算法:细菌觅食优化 — 遗传算法(BFO-GA)
本文释义了一种解决优化问题的新方式,即把细菌觅食优化(BFO)算法和遗传算法(GA)中所用的技术结合到混合型 BFO-GA 算法当中。它用细菌群落来全局搜索最优解,并用遗传运算器来优调局部最优值。与原始的 BFO 不同,细菌现在可以突变,并继承基因。
构建和测试肯特纳通道交易系统 构建和测试肯特纳通道交易系统
在本文中,我们将尝试使用金融市场中一个非常重要的概念 - 波动性 - 来构建交易系统。我们将在了解肯特纳通道(Keltner Channel)指标后提供一个基于该指标的交易系统,并介绍如何对其进行编码,以及如何根据简单的交易策略创建一个交易系统,然后在不同的资产上进行测试。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
神经网络变得简单(第 71 部分):目标条件预测编码(GCPC) 神经网络变得简单(第 71 部分):目标条件预测编码(GCPC)
在之前的文章中,我们讨论了决策转换器方法,以及从其衍生的若干种算法。我们测验了不同的目标设定方法。在测验期间,我们依据各种设定目标的方式进行操作。然而,该模型早期研究时验算过的轨迹,始终处于我们的关注范围之外。在这篇文章中。我想向您介绍一种填补此空白的方法。