English Deutsch 日本語
preview
MQL5自优化智能交易系统(第八部分):多策略分析(2)

MQL5自优化智能交易系统(第八部分):多策略分析(2)

MetaTrader 5示例 |
79 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

在本系列文章中,我们探讨如何创造性地整合不同交易策略,构建一个单一、连贯的集成策略。目标是克服单一策略的局限性,将其转化为更强大的交易工具。

在上次讨论中,我们构建了一个基类,作为所有交易策略的基础。这个基类让我们能够在MQL5中实现第一个策略——移动平均线交叉策略。随后,我们将灵活的策略类与相同策略的硬编码版本进行对比,以验证性能是否匹配。

借助MetaTrader 5策略测试器,我们可以找到最优的参数设置。而人工寻找合适的参数往往颇具挑战。这也正是MetaTrader 5中遗传优化器的价值所在。它能自动化该过程,节省时间和精力。 

我们还采用了前向测试技术,从结果中筛选出稳定的参数集。证实了我们的实现既准确又高效。

今天,我们会更进一步。基于相对强弱指数(RSI)构建第二个策略,并将其与移动平均线(MA)交叉策略合并。通过合并,我们的目标是创建一个更稳健且可能更有利可图的集成策略。我们还将使用MetaTrader 5策略测试器来优化这个新的合并策略。但在深入探讨之前,必须先了解一个关键概念:参数最小化。

随着我们在策略中组件的增加,参数数量可能会迅速膨胀。一个包含过多可变部分的策略,其优化难度会显著增加,有时甚至几乎无法有效优化。这就是为什么尽可能限制参数数量至关重要。通常,将某些参数固定为常数可以让我们专注于优化最具影响力的参数。

为了说明问题,我们最初的移动平均线策略在前向测试中实现了1.29的夏普比率,在101笔交易中获利133.51美元。相比之下,我们今天要构建的新的基于RSI的策略实现了2.68的夏普比率和214.08美元的利润——而且只有52笔交易。

这就意味着新策略不仅盈利更多,交易次数更少,市场风险敞口也更低。这正是我们在交易策略设计中力求达到的性能表现。

通过与策略测试器紧密结合并加以利用,我们能够发掘出更具盈利性的设置。也就是说,管理期望值很重要。MetaTrader 5策略测试器不能神奇地修复一个糟糕的策略。如果一个策略存在根本性缺陷,那么无论进行多少优化,都无法使其盈利。

即使有一个好的策略,优化也不能保证更好的结果——但如果使用得当,它可以显著提高您的成功几率。具备坚实的基础和正确的工具,我们就能无限制地提升性能。


MQL5入门指南

我们的讨论从构建RSI中点策略的类开始。我们需要实现的第一个目标是加载策略类所需的依赖项。第一个依赖项是我们为单缓冲区指标创建的RSI类。此外,我们还需要所有策略的基类,也称为父类。

//+------------------------------------------------------------------+
//|                                                  RSIMidPoint.mqh |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>
#include <VolatilityDoctor\Strategies\Parent\Strategy.mqh>

既然我们已经加载了所需的依赖项,现在就可以开始定义RSI中点策略类及其成员了。请注意,这个中点策略继承自我们所有策略的基类,我们使用冒号语法来表示这种继承关系。接下来,我们定义中点策略类的成员。请注意,我们只需定义几个成员即可。另外,需要注意的是,我们在父策略类中创建的所有虚方法,在RSI中点类中都必须再次声明为虚方法。

class RSIMidPoint : public Strategy
  {
private:
                     //--- The instance of the RSI used in this strategy
                     RSI *my_rsi;

public:
                     //--- Class constructor 
                     RSIMidPoint(string user_symbol,ENUM_TIMEFRAMES user_timeframe,int user_period,ENUM_APPLIED_PRICE user_price);
                     
                     //--- Class destructor
                    ~RSIMidPoint();
                    
                    //--- Class overrides
                    virtual bool Update(void);
                    virtual bool BuySignal(void);
                    virtual bool SellSignal(void);
  };
 

现在,针对RSI策略,我们可以开始考虑如何具体实施每种方法。update方法仅需更新RSI指标值,然后确保从RSI指标获取的当前读数不为0。如果是这样,则一切正常;否则,就说明出现了问题。

//+------------------------------------------------------------------+
//| Our strategy update method                                       |
//+------------------------------------------------------------------+
bool RSIMidPoint::Update(void)
   {
      //--- Set the indicator value
      my_rsi.SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
      
      //--- Check readings are valid
      if(my_rsi.GetCurrentReading() != 0) return(true);
      
      //--- Something went wrong
      return(false);
   }  

接下来,我们将考虑买入和卖出信号的判定条件。当RSI读数低于50时,我们生成买入信号;而当RSI读数高于50时,我们生成卖出信号。

//+------------------------------------------------------------------+
//| Check for our buy signal                                         |
//+------------------------------------------------------------------+
bool RSIMidPoint::BuySignal(void)
   {
      //--- Buy signals when the RSI is below 50
      return(my_rsi.GetCurrentReading()<50);
   }

//+------------------------------------------------------------------+
//| Check for our sell signal                                        |
//+------------------------------------------------------------------+
bool RSIMidPoint::SellSignal(void)
   {
      //--- Sell signals when the RSI is above 50
      return(my_rsi.GetCurrentReading()>50);
   }

最后,我们需要考虑类的构造函数和析构函数。类构造函数将接收RSI指标所需的详细信息。具体而言,我们的构造函数要求用户指定RSI指标应用于哪个交易品种、哪个时间周期、哪个周期长度以及哪类价格。一旦已经所有细节都已指定,我们就会加载这些详细信息并创建一个新的RSI指标实例。

然而,需要注意的是,我们使用的这个特定的RSI指标实例与MetaTrader 5自带的RSI指标实例并不相同。这是我们自定义的一个类型,包含许多其它实用函数,方便我们在后续使用。尽管如此,其整体功能是相同的,如有需要,读者可以自行实现其中的一些方法。

//+------------------------------------------------------------------+
//| Our class constructor                                            |
//+------------------------------------------------------------------+
RSIMidPoint::RSIMidPoint(string user_symbol,ENUM_TIMEFRAMES user_timeframe,int user_period,ENUM_APPLIED_PRICE user_price)
  {
   my_rsi = new RSI(user_symbol,user_timeframe,user_period,user_price);
   Print("RSI-Mid-Point Strategy Loaded.");
  }

最后,在类的析构函数中,我们删除之前创建的自定义RSI对象的指针。

//+------------------------------------------------------------------+
//| Our class destructor                                             |
//+------------------------------------------------------------------+
RSIMidPoint::~RSIMidPoint()
  {
   delete my_rsi;
  }
//+------------------------------------------------------------------+

现在,我们已经定义了封装RSI中点策略的类,接下来需要确保构建该类时没有错误。因此,我们必须首先使用该策略的硬编码版本建立一个基准性能水平,以便我们测试所定义的类,看其是否能恢复与硬编码版本相同的性能水平。 

如果这两种策略是等效的,那么在相同时间段内进行测试时,它们应该具有相同的盈利水平,并产生相同的统计数据。因此,在确定基准性能时,我们首先要列出所需的系统常量。例如,RSI指标所应用的价格、RSI周期以及时间框架。所有这些常量在两次测试中都必须保持固定,以确保我们进行公平的比较。

//+------------------------------------------------------------------+
//|                                          MSA Test 2 Baseline.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Define system constants                                          |
//+------------------------------------------------------------------+
#define RSI_PRICE        PRICE_CLOSE
#define RSI_PERIOD       15
#define RSI_TIME_FRAME   PERIOD_D1
#define HOLDING_PERIOD   5

之后,我们将加载所需的依赖项。在这种情况下,我们只需要大约三个库。

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Time\Time.mqh>

此外,我们还需定义一些全局变量。请注意,我们可以将全局变量分为两类:自定义类型和系统类型。自定义类型由用户定义。在每个MetaTrader 5安装中都有系统定义的类型,例如双精度浮点数和单精度浮点数等。这是我们对全局变量进行分组的一种方式,以便我们的代码更易于维护。

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade    Trade;
TradeInfo *TradeInformation;
Time      *TradeTime;

//--- System Types
double rsi[],ma_close[];
int    rsi_handler;
int    position_timer;

接下来,我们开始构建智能交易系统(EA)的初始化流程。当首次初始化EA时,需要注意的是,我们将自行创建RSI指标处理器,同时还会动态创建一些所需的自定义类型实例。我们特别需要这些自定义类型来跟踪时间,并获取关键的交易信息,例如允许的最小交易手数。随后,我们将确保RSI指标处理器已经安全加载。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Our technical indicator
   rsi_handler = iRSI(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);

//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),RSI_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),RSI_TIME_FRAME);


//--- Safety checks
   if(rsi_handler == INVALID_HANDLE)
      return(false);
   
//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

当EA不再使用时,我们将释放相关的指标,并删除创建的动态对象。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the indicators and dynamic objects
   IndicatorRelease(rsi_handler);

   delete TradeTime;
   delete TradeInformation;

  }
//--- End of Deinit Scope

在OnTick函数中,我们首先会使用编写的相关库函数来检查是否已形成新的日线K线图。如果确实形成了新的K线图,我们将更新技术指标,然后检查是否持有任何未平仓头寸。如果当前没有持仓,我们将确保重置我们的持仓时间,然后检查是否存在交易信号。否则,如果当前有持仓,我们将检查我们的交易是否已达到预期目标或满足平仓条件,是否应该平仓。如果上述情况都不符合,我们将继续持仓。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Check if a new daily candle has formed
   if(TradeTime.NewCandle())
     {

      //--- Update our technical indicators
      Update();

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {
         //--- Reset the position timer
         position_timer = 0;

         //--- Check for a trading signal
         CheckSignal();
        }

      //--- Otherwise
      else
        {
         //--- The position has reached maturity
         if(position_timer == HOLDING_PERIOD)
            Trade.PositionClose(Symbol());

         //--- Otherwise keep holding
         else
            position_timer++;
        }
     }
  }
//--- End of OnTick Scope

我们还需要考虑如何实现之前讨论过的一些方法。例如,更新方法只需调用复制缓冲区,将RSI处理器中的指标值复制到我们为此目的指定的数组中。我们仅复制RSI指标的当前读数。而不需要之前的读数。

//+------------------------------------------------------------------+
//| Update our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Call the CopyBuffer method to get updated indicator values
   CopyBuffer(rsi_handler,0,0,1,rsi);
  }
//--- End of Update Scope

再次说明检查信号的方法——该方法仅用于查找我们之前定义的交易信号——即当RSI读数低于50时执行买入操作;相反,高于50则执行卖出操作。

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Buy signals when the RSI is below 50
   if(rsi[0] < 50)
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Sell signals when the RSI is above 50
   else
      if(rsi[0] > 50)
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }

  }
//--- End of CheckSignal Scope

最后,我们将统一开头定义的系统常量。

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef RSI_PRICE        
#undef RSI_PERIOD       
#undef RSI_TIME_FRAME   
#undef HOLDING_PERIOD   
//+------------------------------------------------------------------+

开始进行回测时,我们首先需要选定训练周期。本次测试从2022年1月1日运行至2025年5月1日。请注意,本次测试将在日线图时间框架下进行。

图1:我们为基准测量所选定的回测天数 

在模拟测试中,如果我们将延迟设置为“随机延迟”,将获得更稳健的测试结果。这样可以模拟真实市场中的延迟、滑点以及其他在实盘交易中引发紧张情绪的因素。

图2:我们在回测中使用的测试设置 

下图3所示的资金曲线,是RSI交易策略类需要重现的目标。

图3:我们硬编码版本交易策略所产生的资金曲线 

我们可以查看基准策略性能的详细统计数据。我们希望,在相同时间段内进行测试时,策略类能够获得相同的统计数据,以证明其可靠性。


图4:我们交易策略性能的详细统计数据 

接下来,我们需要实施相同的测试,以确定我们的RSI策略类是否有效。因此,之前定义的大部分全局元素,如系统常量和全局变量,将基本保持不变。我们将对应用程序进行的主要修改包括:在EA的初始化阶段初始化我们的策略类。 

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Time\Time.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Strategies\RSIMidPoint.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade               Trade;
TradeInfo            *TradeInformation;
Time                 *TradeTime;
RSIMidPoint          *RSIMid;

//--- System Types
int    position_timer;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),RSI_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),RSI_TIME_FRAME);
   RSIMid           = new RSIMidPoint(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);

//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope
此外,我们必须更新OnDeinit函数,以确保正确删除新创建的对象。
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the dynamic objects
   delete TradeTime;
   delete TradeInformation;
   delete RSIMid;
  }
//--- End of Deinit Scope
最后,我们需要确保正确调用CheckSignal方法。
//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Long positions when RSI is below 50
   if(RSIMid.BuySignal())
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Otherwise short positions when the RSI is above 50
   else
      if(RSIMid.SellSignal())
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }
  }
//--- End of CheckSignal Scope

现在,我们将使用与之前基准测试相同的周期,对我们构建的RSI策略类进行测试。

图5:为我们RSI交易策略的类版本选择测试日期 

这两项测试均按照图2中指定的“随机延迟”条件进行,且在回测设置下产生了相似的资金曲线。

图6:我们交易策略的类版本所获得的资金曲线与硬编码版本的基准曲线相匹配 

此外,两项测试在详细分析中得出的统计数据也完全相同。这就表明我们已成功无误地实现了该类。因此,我们现在将开始合并MA和RSI策略类。

图7:对我们交易策略类版本性能的详细统计分析结果与基准相符 

既然已经验证基于类的交易策略版本与硬编码版本能够产生完全相同的结果,那么接下来我们可以将移动平均线交叉策略与基于RSI的策略进行合并,从而创建一个新的组合策略。首先,我们必须选定一些固定参数。控制参数空间的增长至关重要,因为参数空间过大会使有意义的优化几乎不可能实现。 

//+------------------------------------------------------------------+
//|                                                   MSA Test 1.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
//--- Fix any parameters that can afford to remain fixed
#define MA_SHIFT         0
#define MA_TYPE          MODE_EMA
#define RSI_PRICE        PRICE_CLOSE

此外,由于现在要同时运行两种策略,我们必须在MQL5中定义这些策略如何交互。为了方便管理,我们可以使用枚举来定义可能存在于多种相关状态中的逻辑。

因此,我们创建了一个名为“策略模式”的枚举器,它有三种可能的设置。第一种设置用0表示,即投票策略。根据该策略,只有当MA策略和RSI策略对交易方向达成一致时,才会执行交易。换句话说,只有当两种策略都发出做多信号时,才会开立买入头寸。

第一种替代模式将做多头寸的开立责任分配给RSI策略,而由MA策略处理卖出头寸。最后一种模式则相反,将买入头寸的交易责任分配给MA策略,将卖出头寸的交易责任分配给RSI策略。

//+------------------------------------------------------------------+
//| User defined enumerator                                          |
//+------------------------------------------------------------------+

enum STRATEGY_MODE
  {
   MODE_ONE   = 0, //Voting Policy
   MODE_TWO   = 1, //RSI Buy & MA Sell
   MODE_THREE = 2  //MA Sell & RSI Buy
  };
通过以这种方式构建策略,我们将输入参数的总数减少到了5个,从而便于管理。接下来,我们加载之前讨论过的依赖项,并初始化我们熟悉的变量。 

//+------------------------------------------------------------------+
//| User Inputs                                                      |
//+------------------------------------------------------------------+
input   group          "Moving Average Strategy Parameters"
input   int             MA_PERIOD                       =        10;//Moving Average Period


input   group          "RSI Strategy Parameters"
input   int             RSI_PERIOD                      =         15;//RSI Period

input   group          "Global Strategy Parameters"
input   ENUM_TIMEFRAMES STRATEGY_TIME_FRAME             = PERIOD_D1;//Strategy Timeframe
input   int             HOLDING_PERIOD                  =         5;//Position Maturity Period
input   STRATEGY_MODE   USER_MODE                       =         0;//Operation Mode For Our Strategy

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Time\Time.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Strategies\OpenCloseMACrossover.mqh>
#include <VolatilityDoctor\Strategies\RSIMidPoint.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade               Trade;
Time                 *TradeTime;
TradeInfo            *TradeInformation;
RSIMidPoint          *RSIMid;
OpenCloseMACrossover *MACross;

//--- System Types
int                  position_timer;

在初始化函数中,我们加载了整个开发过程中使用的相同类。当EA终止时,我们确保正确删除所有动态创建的对象。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),STRATEGY_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),STRATEGY_TIME_FRAME);
   MACross          = new OpenCloseMACrossover(Symbol(),STRATEGY_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE);
   RSIMid           = new RSIMidPoint(Symbol(),STRATEGY_TIME_FRAME,RSI_PERIOD,RSI_PRICE);
//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the dynamic objects
   delete TradeTime;
   delete TradeInformation;
   delete MACross;
   delete RSIMid;
  }
//--- End of Deinit Scope

在OnTick函数中,我们应用了之前介绍过的相同逻辑。由于该逻辑已详细阐述过,本部分讨论将省略OnTick函数的具体内容。更新函数已稍作修改,现在它会分别调用两个独立的更新方法——每个策略对应一个。最后,CheckSignal方法经历了最为重大的变更。现在,必须首先确定当前的策略模式,然后才能启动交易。

//+------------------------------------------------------------------+
//| Update our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Update the strategy
   RSIMid.Update();
   MACross.Update();
  }
//--- End of Update Scope

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {

//--- Both Stratetgies Should Cast The Same Vote
   if(USER_MODE == 0)
     {
      //--- Long positions when the close moving average is above the open
      if(MACross.BuySignal() && RSIMid.BuySignal())
        {
         Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
         return;
        }

      //--- Otherwise short
      else
         if(MACross.SellSignal()  && RSIMid.SellSignal())
           {
            Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
            return;
           }
     }

//--- RSI Opens All Long Positions & The Moving Average Opens Short Positions
   else
      if(USER_MODE == 1)
        {

         if(RSIMid.BuySignal())
           {
            Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
            return;
           }

         //--- Otherwise short
         else
            if(MACross.SellSignal())
              {
               Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
               return;
              }
        }

      //--- RSI Opens All Short Positions & The Moving Average Opens Long Positions
      else
         if(USER_MODE == 2)
           {

            if(MACross.BuySignal())
              {
               Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
               return;
              }

            //--- Otherwise short
            else
               if(RSIMid.SellSignal())
                 {
                  Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
                  return;
                 }
           }

  }
//--- End of CheckSignal Scope

最后,当全部逻辑执行完毕后,我们取消定义所有系统常量,从而干净利落地结束程序。

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_SHIFT
#undef RSI_PRICE
#undef MA_TYPE
//+------------------------------------------------------------------+

现在,让我们开始为应用程序寻找合适的参数设置。选择我们刚刚构建的应用程序,然后将“Forward”字段设置为“1/2”。这样一来,我们的遗传优化器将使用现有的一半历史数据进行参数学习,而另一半数据则对优化器隐藏,用来验证优化器认为可行的参数设置。 

图8:设置我们的前向测试

如同前两次测试一样,在设置“随机延迟”后,请注意,这次我们还需要选择一个优化器。如果您拥有更强的计算能力,不妨尝试提供的其他优化器,例如”慢速完整"设置。

图9:在优化字段中选择“快速基于遗传算法”设置

点击屏幕底部的“输入”选项卡,调出控制待测试策略参数的面板。勾选每个参数旁边的复选框,然后为待搜索的值设定起始值和终止值。需要注意的是,步长参数同样至关重要。如果将步长设置为1,结果确实会非常详细,但所需的总步数可能会因此变得极其庞大。一般来说,对于范围较大的搜索,建议采用2、4、5甚至10等间隔。

图10:选择我们应对参数进行搜索的区间设置

当您启动测试时,前向测试的结果将以散点图的形式弹出,图中的每个点代表使用一组独特策略参数执行的一次完整测试。

图11:我们可以将策略测试器生成的结果以二维散点图的形式呈现,其中纵轴表示利润

我们的策略在遗传算法用于选择这些参数的训练样本之外仍然保持盈利。如果我们决定将该算法用于真实资金,这是一个预示未来可能保持稳定的良好指标。

图12:我们的交易策略生成的资金曲线在样本外呈现正趋势

我们新系统的盈利能力远超之前的版本。我们可以清楚地看到,“投票策略”组合表现最为稳定。 

图13:在为我们的应用程序增加一种策略后,其性能得到了提升

这些是我们在首次讨论中从前向测试中获得的最优结果。我们新系统的表现优于之前仅使用一种策略的版本。

图14:我们的应用程序在仅使用一种策略时获得的最优结果



结论

本文展示了在MQL5中结合人类创造力应用面向对象编程(OOP)原则的诸多益处。展示了将不同策略整合为一个连贯系统的多种方法。面向对象编程是进行此类创造性工作的强大且多功能的工具,它能够实现结构化、受控且可预测的开发过程。

基于本文介绍的基础概念,读者们现在已经能够完善和优化自己的私人交易策略。借助MetaTrader 5内置的策略测试器,读者们可以开始测试并改进自己的策略实现。

本文还强调了减少策略中输入参数数量的重要性。参数过多的策略将越来越难以有效地优化。虽然可以将部分计算负担转嫁给MQL5云网络服务,但是保持设计简洁往往能带来更好的结果和更稳健的策略。

通过应用本文讨论的原则,读者现在应该已经掌握构建自己的组合交易系统的实用见解。期待您继续参与未来的讨论,届时我们将为交易策略引入第三层架构。下一步将帮助我们为未来的统计模型构建坚实的基础。

文件名 文件描述 
MSA Test 2 Baseline.mq5 我们RSI交易策略的硬编码测试版本。 
MSA Test 2 Class.mq5 我们RSI策略类的测试版本。
MSA Test 2.ex5 我们集成交易策略的编译版本。 
MSA Test 2.mq5 我们开发的集成MA与RSI应用程序的源代码。
RSIMidPoint.mqh 用于我们RSI中点策略的MQL5类。 

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18471

附加的文件 |
MSA_Test_2.ex5 (47.2 KB)
MSA_Test_2.mq5 (7.64 KB)
RSIMidPoint.mqh (3.51 KB)
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
从新手到专家:使用 MQL5 制作动画新闻标题(六)—— 新闻交易的挂单策略 从新手到专家:使用 MQL5 制作动画新闻标题(六)—— 新闻交易的挂单策略
在本文中,我们将重点转移到整合新闻驱动的订单执行逻辑 —— 使 EA 能够采取行动,而不仅仅是提供信息。加入我们,一起探索如何在 MQL5 中实现自动交易执行,并将 News Headline EA 扩展为一个完全响应式的交易系统。由于 EA 交易支持多种功能,因此为算法开发人员提供了显著优势。到目前为止,我们一直专注于构建新闻和日历事件展示工具,其中包含集成的 AI 洞察通道和技术指标洞察。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
在 MQL5 中实现其他语言的实用模块(第 02 部分):构建受 Python 启发的 REQUESTS 库 在 MQL5 中实现其他语言的实用模块(第 02 部分):构建受 Python 启发的 REQUESTS 库
在本文中,我们实现了一个类似于 Python 中 requests 模块的功能,以便更轻松地使用 MQL5 在 MetaTrader 5 中发送和接收 Web 请求。