在MQL5中构建自优化智能交易系统(EA)(第五部分):自适应交易规则

0
580

技术指标已成为大多数算法交易应用程序中不可或缺的工具,因为它们提供了明确的交易信号,有助于快速开发应用程序。遗憾的是,交易者常常会面临与使用任何技术指标的标准化规则相悖的不利市场行情。 

例如,相对强弱指标(RSI)用于衡量给定市场中价格水平变化的强度。人们普遍认为,当RSI读数低于30时,交易者应设置买入指令;当RSI读数高于70时,应设置卖出指令,这是最优方式。 

在下图1中,我们在黄金的美元日线价格图上应用了20周期的RSI指标。读者可以注意到,在长达3个月的时间里,都没有观察到预期的读数。从图表上绘制的趋势线可以看出,从2019年9月到2019年12月,黄金价格处于强劲的下跌趋势中。

然而,如果读者更仔细地观察,就会发现在这个下跌趋势期间,我们的RSI指标并没有产生预期的做空信号。

截图1

图1:市场行情有可能无法使技术指标产生预期读数,从而导致意料之外的行为

一些读者可能会认为:“可以尝试相应地调整指标周期,这样就能得到标准化的结果。”然而,调整周期并不总是有效的解决方案。例如,我们社区中那些对多品种分析感兴趣的成员,可能已经有特定的周期,能最好地揭示出他们所寻找的跨市场模式,因此需要保持周期固定。

此外,那些一次只交易单个市场的读者必须牢记,增大RSI周期会使问题变得更糟。另外,减小RSI周期会引入更多的市场噪声,降低交易信号的整体质量。

简而言之,随意处理这个问题可能会导致交易应用程序出现意外行为,造成因未抓住机会而错失利润,或因未完全满足退出条件而导致市场风险敞口高于预期。


方法论概述

显然,我们需要以一种让应用程序能够根据现有历史数据调整其交易规则的方式来设计程序。我们将在本文后续部分详细探讨我们提出的解决方案。目前,我们提出的解决方案可以概括为:要判断什么是强劲走势,首先必须了解特定市场中平均走势是什么样的。这样,读者就可以将应用程序设置为仅接受高于平均水平的入场信号,或者更具体地说,选择高于平均水平2倍或3倍的走势。 

通过将交易信号设置为相对于平均走势幅度来决定,我们将有效避免长时间收不到交易信号的情况,也希望不必再对RSI计算的周期进行无休止的优化。 


MQL5入门指南

我们的探索从使用MetaTrader 5策略测试器建立基准性能水平开始。我们的策略将作为框架,读者可将其替换为自己的私人交易策略。我们将用MQL5构建一个交易策略,该策略在关键支撑位或阻力位突破时进行交易。我们的RSI指标将为我们确认该价位是否已成功突破。我们将在应用程序的所有版本中固定止损和止盈的规模,以确保盈利能力的变化直接源于我们做出更明智的决策。 

对于什么是有效的支撑位或阻力位,有着各种不同的定义。但就我们的讨论而言,我们只需知道,支撑位是引发价格反弹的低价位水平。相反,阻力位是图表上引发强劲看跌趋势的关注价位水平。在每个交易时段开始时,这些价位水平能维持多久并不确定。 

当这些价位水平中的任何一个被成功突破时,通常随后会出现价格的大幅波动。当观察到的突破行情发生时,我们的RSI指标将作为我们决定是否进行交易的指引。我们寻找的是在价格跌破关键支撑位时做多的机会,并且只在阻力位出现相反情况时做空。

我们的支撑位和阻力位,通过将当前价格水平与5天前的相应水平进行比较来确定。 

截图2

图2:在白银兑美元(XAGUSD)上可视化支撑位和阻力位

我们讨论中的一些参数将保持不变。因此,为避免不必要地重复相同信息,我们将在此处讨论这些固定参数。我们的讨论将聚焦于在美元计价的白银(XAGUSD)日线图上,针对支撑位和阻力位的突破行情进行交易。我们将使用2017年1月1日至2025年1月28日期间的M15历史数据来测试策略。

截图3

图3:我们将用于回测的时间段

此外,在回测过程中我们将启用“随机延迟”设置,因为这更贴近真实交易体验。同时,我们已选择“基于真实点差的每tick”模式,使用从经纪商获取的实际市场数据进行测试。

截图4

图4:今日回测中的第二批固定设置

排除了这些干扰因素后,我们现在可以集中精力使用MQL5构建基础应用了。首先,我们将定义一些关键的系统常量,这些常量在后续讨论中将保持不变。这些系统常量用于定义控制交易应用行为的参数,例如止损和止盈的幅度、使用的时间框架,以及我们每笔交易的仓位规模等,这里仅列举了部分功能。

//+------------------------------------------------------------------+ //|                                  Self Adapting Trading Rules.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                                                 | //+------------------------------------------------------------------+ #define RSI_PERIOD 10             //The period for our RSI indicator #define RSI_PRICE  PRICE_CLOSE   //The price level our RSI should be applied to #define ATR_SIZE   1.5             //How wide should our Stop loss be? #define ATR_PERIOD 14             //The period of calculation for our ATR indicator #define TF_1       PERIOD_D1     //The primary time frame for our trading application #define TF_2       PERIOD_M15     //The secondary time frame for our trading application #define VOL        0.1           //Our trading volume   

现在,我们将导入交易库以帮助管理交易头寸。

//+------------------------------------------------------------------+ //| Libraries we need                                                | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade Trade; 

接下来,我们需要定义一些重要的全局变量。

//+------------------------------------------------------------------+ //| Global variables                                                 | //+------------------------------------------------------------------+ int rsi_handler,atr_handler; double rsi[],atr[]; double support,resistance; 

我们的MetaTrader 5应用程序主要由交易终端中发生的事件来驱动。我们已构建了自定义方法,这些方法会在每个终端事件被触发时被依次调用。

//+------------------------------------------------------------------+ //| Expert initialization function                                   | //+------------------------------------------------------------------+ int OnInit()   { //---    setup(); //---    return(INIT_SUCCEEDED);   } //+------------------------------------------------------------------+ //| Expert deinitialization function                                 | //+------------------------------------------------------------------+ void OnDeinit(const int reason)   { //---    release();   } //+------------------------------------------------------------------+ //| Expert tick function                                             | //+------------------------------------------------------------------+ void OnTick()   { //---    update();   } //+------------------------------------------------------------------+

当首次加载指标时,我们将设置技术指标,然后将支撑位和阻力位设定为前一周所观察到的最高价和最低价水平。

//+------------------------------------------------------------------+ //| User defined methods                                             | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Initialize our system variables                                  | //+------------------------------------------------------------------+ void setup(void)   { //Load our technical indicators    atr_handler = iATR(Symbol(),TF_1,ATR_PERIOD);    rsi_handler = iRSI(Symbol(),TF_2,RSI_PERIOD,RSI_PRICE);    resistance  = iHigh(Symbol(),TF_1,5);    support     = iLow(Symbol(),TF_1,5);   }

如果交易应用程序不再使用,我们需要释放那些不再需要的技术指标。

//+------------------------------------------------------------------+ //| Let go of resources we are no longer consuming                   | //+------------------------------------------------------------------+ void release(void)   { //Free up resources we are not using    IndicatorRelease(atr_handler);    IndicatorRelease(rsi_handler);   } 

我们的更新函数分为两部分。前半部分负责处理每日需执行一次的常规任务,而后半部分则负责在更短的时间框架内频繁执行的任务。这样确保了我们不会因市场的突发或异常波动而措手不及。 

//+------------------------------------------------------------------+ //| Update our system variables and look for trading setups          | //+------------------------------------------------------------------+ void update(void)   { //Update our system variables //Some duties must be performed periodically on the higher time frame      {       static datetime time_stamp;       datetime current_time = iTime(Symbol(),TF_1,0);       //Update the time       if(time_stamp != current_time)         {          time_stamp = current_time;          //Update indicator readings          CopyBuffer(rsi_handler,0,0,1,rsi);          CopyBuffer(atr_handler,0,0,1,atr);          //Update our support and resistance levels          support    = iLow(Symbol(),TF_1,5);          resistance = iHigh(Symbol(),TF_1,5);          ObjectDelete(0,"Support");          ObjectDelete(0,"Resistance");          ObjectCreate(0,"Suppoprt",OBJ_HLINE,0,0,support);          ObjectCreate(0,"Resistance",OBJ_HLINE,0,0,resistance);         }      } //While other duties need more attention, and must be handled on lower time frames.      {       static datetime time_stamp;       datetime current_time = iTime(Symbol(),TF_2,0);       double bid,ask;                            //Update the time       if(time_stamp != current_time)         {          time_stamp = current_time;          bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);          ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);                   //Check if we have broken either extreme          if(PositionsTotal() == 0)             {                //We are looking for opportunities to sell                if(iLow(Symbol(),TF_2,0) > resistance)                   {                      if(rsi[0] > 70) Trade.Sell(VOL,Symbol(),bid,(ask + (ATR_SIZE * atr[0])),(ask - (ATR_SIZE * atr[0])));                   }                              //We are looking for opportunities to buy               if(iHigh(Symbol(),TF_2,0) < support)                   {                      if(rsi[0] < 30) Trade.Buy(VOL,Symbol(),ask,(bid - (ATR_SIZE * atr[0])),(bid + (ATR_SIZE * atr[0])));                   }             }          }               }      }

最后,我们必须将应用程序开头定义的系统常量取消定义。

//+------------------------------------------------------------------+ //| Undefine the system constants                                    | //+------------------------------------------------------------------+ #undef RSI_PERIOD #undef RSI_PRICE #undef ATR_PERIOD #undef ATR_SIZE #undef TF_1 #undef TF_2 #undef VOL //+------------------------------------------------------------------+

总体来说,我们当前的代码库如下:

//+------------------------------------------------------------------+ //|                                  Self Adapting Trading Rules.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                                                 | //+------------------------------------------------------------------+ #define RSI_PERIOD 10            //The period for our RSI indicator #define RSI_PRICE  PRICE_CLOSE   //The price level our RSI should be applied to #define ATR_SIZE   1.5             //How wide should our Stop loss be? #define ATR_PERIOD 14            //The period of calculation for our ATR indicator #define TF_1       PERIOD_D1     //The primary time frame for our trading application #define TF_2       PERIOD_M15    //The secondary time frame for our trading application #define VOL        0.1           //Our trading volume    //+------------------------------------------------------------------+ //| Libraries we need                                                | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade Trade; //+------------------------------------------------------------------+ //| Global variables                                                 | //+------------------------------------------------------------------+ int rsi_handler,atr_handler; double rsi[],atr[]; double support,resistance; //+------------------------------------------------------------------+ //| Expert initialization function                                   | //+------------------------------------------------------------------+ int OnInit()   { //---    setup(); //---    return(INIT_SUCCEEDED);   } //+------------------------------------------------------------------+ //| Expert deinitialization function                                 | //+------------------------------------------------------------------+ void OnDeinit(const int reason)   { //---    release();   } //+------------------------------------------------------------------+ //| Expert tick function                                             | //+------------------------------------------------------------------+ void OnTick()   { //---    update();   } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| User defined methods                                             | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Initialize our system variables                                  | //+------------------------------------------------------------------+ void setup(void)   { //Load our technical indicators    atr_handler = iATR(Symbol(),TF_1,ATR_PERIOD);    rsi_handler = iRSI(Symbol(),TF_2,RSI_PERIOD,RSI_PRICE);    resistance  = iHigh(Symbol(),TF_1,5);    support     = iLow(Symbol(),TF_1,5);   } //+------------------------------------------------------------------+ //| Let go of resources we are no longer consuming                   | //+------------------------------------------------------------------+ void release(void)   { //Free up resources we are not using    IndicatorRelease(atr_handler);    IndicatorRelease(rsi_handler);   } //+------------------------------------------------------------------+ //| Update our system variables and look for trading setups          | //+------------------------------------------------------------------+ void update(void)   { //Update our system variables //Some duties must be performed periodically on the higher time frame      {       static datetime time_stamp;       datetime current_time = iTime(Symbol(),TF_1,0);       //Update the time       if(time_stamp != current_time)         {          time_stamp = current_time;          //Update indicator readings          CopyBuffer(rsi_handler,0,0,1,rsi);          CopyBuffer(atr_handler,0,0,1,atr);          //Update our support and resistance levels          support    = iLow(Symbol(),TF_1,5);          resistance = iHigh(Symbol(),TF_1,5);          ObjectDelete(0,"Support");          ObjectDelete(0,"Resistance");          ObjectCreate(0,"Suppoprt",OBJ_HLINE,0,0,support);          ObjectCreate(0,"Resistance",OBJ_HLINE,0,0,resistance);         }      } //While other duties need more attention, and must be handled on lower time frames.      {       static datetime time_stamp;       datetime current_time = iTime(Symbol(),TF_2,0);       double bid,ask;       //Update the time       if(time_stamp != current_time)         {          time_stamp = current_time;          bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);          ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);          //Check if we have broken either extreme          if(PositionsTotal() == 0)            {             //We are looking for opportunities to sell             if(iLow(Symbol(),TF_2,0) > resistance)               {                if(rsi[0] > 70)                   Trade.Sell(VOL,Symbol(),bid,(ask + (ATR_SIZE * atr[0])),(ask - (ATR_SIZE * atr[0])));               }             //We are looking for opportunities to buy             if(iHigh(Symbol(),TF_2,0) < support)               {                if(rsi[0] < 30)                   Trade.Buy(VOL,Symbol(),ask,(bid - (ATR_SIZE * atr[0])),(bid + (ATR_SIZE * atr[0])));               }            }         }      }   } //+------------------------------------------------------------------+ //| Undefine the system constants                                    | //+------------------------------------------------------------------+ #undef RSI_PERIOD #undef RSI_PRICE #undef ATR_PERIOD #undef ATR_SIZE #undef TF_1 #undef TF_2 #undef VOL //+------------------------------------------------------------------+

现在,让我们评估该算法在历史市场数据上的表现如何。启动您的MetaTrader 5交易终端,并选择我们刚刚共同开发的应用程序。请注意,本节文章开头已为您提供了测试日期和设置参数。

截图5

图5:在MetaTrader 5中启动回测

我们的智能交易系统(EA)没有输入参数,您只需加载应用程序并相应设置测试日期,然后启动MetaTrader 5策略测试器即可。下图6展示了按照为RSI构建的经典交易规则所生成的净值曲线。 

截图6

图6:遵循经典RSI交易策略所生成的净值曲线

图7为我们提供了基于RSI的交易应用程序在首次测试中的表现的详细总结。我们的总净利润为588.60美元,夏普比率为0.85。到目前为止,这样的结果令人鼓舞,并将作为我们在下一版交易应用程序中力求超越的目标。

截图7

图7:我们从回测中获得的详细结果总结



改进初始结果

现在,我们已经提出了针对指标非标准输出可能引发问题的解决方案。为了用直接从市场历史表现中选出的最优水平替代经典的70和30水平,我们必须首先观察该指标在给定市场上产生的输出范围。之后,我们将对标记指标所产生最大和最小输出的两条平行线进行二等分。这条二等分线将成为我们的新中点。请注意,RSI指标原本的中点是50水平。

一旦我们计算出新的中点,我们将记录每个RSI读数与指标中点之间的绝对差值,并求出这些差值的平均值,以估算该特定市场的平均波动幅度。随后,我们将指示交易应用程序仅在观察到RSI值的变化达到该市场平均波动幅度的两倍时,才进行交易操作。这个新参数使我们能够控制交易应用程序的敏感度,但如果选择的数值过大,比如100,那么应用程序将永远不会进行任何交易。

为了在应用程序中实现这些变更,我们必须进行以下修改:

调整建议 设计目的
创建新的系统常量 这些新的系统常量将固定应用程序初始版本中的大部分参数,同时引入一些我们需要的新参数。
修改我们的自定义方法 我们之前编写的自定义函数需要进行扩展,以提供所需的新功能。

首先,让我们创建所需的新系统常量。我们需要动态决定在执行计算时应该使用多少根K线。这将是新系统常量“BARS”的职责。它将返回一个接近可用K线总数一半的整数。此外,我们还决定只关注RSI变化幅度超过平均水平两倍的情况。因此,我们创建了一个名为“BIG SIZE”的新常量,用于记录希望观察到的市场波动强度。

最后,我们的支撑位和阻力位将通过比较当前价格与一周前(或5天前)的价格来确定。

//+---------------------------------------------------------------+ //| System constants                                              | //+---------------------------------------------------------------+ #define RSI_PERIOD     10                                          //The period for our RSI indicator #define RSI_PRICE      PRICE_CLOSE                                 //The price level our RSI should be applied to #define ATR_SIZE       1.5                                         //How wide should our Stop loss be? #define ATR_PERIOD     14                                          //The period of calculation for our ATR indicator #define TF_1           PERIOD_D1                                   //The primary time frame for our trading application #define TF_2           PERIOD_M15                                  //The secondary time frame for our trading application #define VOL            0.1                                         //Our trading volume    #define BARS           (int) MathFloor((iBars(Symbol(),TF_2) / 2)) //How many bars should we use for our calculation? #define BIG_SIZE       2                                           //How many times bigger than the average move should the observed change be? #define SUPPORT_PERIOD 5                                           //How far back into the past should we look to find our support and resistance levels?

现在,我们将修改决定是否开仓的条件。请注意,我们不再将入场点设定在固定的指标水平上,而是设定在根据指标内存缓冲区计算得出的水平上。

//While other duties need more attention, and must be handled on lower time frames.      {       static datetime time_stamp;       datetime current_time = iTime(Symbol(),TF_2,0);       double bid,ask;       //Update the time       if(time_stamp != current_time)         {          time_stamp = current_time;          bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);          ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);          //Copy the rsi readings into a vector          vector rsi_vector   = vector::Zeros(BARS);          rsi_vector.CopyIndicatorBuffer(rsi_handler,0,1,BARS);          //Let's see how far the RSI tends to deviate from its centre          double rsi_midpoint = ((rsi_vector.Max() + rsi_vector.Min()) / 2);          vector rsi_growth   = MathAbs(rsi_vector - rsi_midpoint);          //Check if we have broken either extreme          if(PositionsTotal() == 0)            {             //We are looking for opportunities to sell             if(iLow(Symbol(),TF_2,0) > resistance)               {                if((rsi[0] - rsi_midpoint) > (rsi_growth.Mean() * BIG_SIZE))                   Trade.Sell(VOL,Symbol(),bid,(ask + (ATR_SIZE * atr[0])),(ask - (ATR_SIZE * atr[0])));               }             //We are looking for opportunities to buy             if(iHigh(Symbol(),TF_2,0) < support)               {                if((rsi[0] - rsi_midpoint) < (-(rsi_growth.Mean() * BIG_SIZE)))                   Trade.Buy(VOL,Symbol(),ask,(bid - (ATR_SIZE * atr[0])),(bid + (ATR_SIZE * atr[0])));               }            }         }      }

最后,我们需要取消定义已创建的新系统常数。

//+------------------------------------------------------------------+ //| Undefine the system constants                                    | //+------------------------------------------------------------------+ #undef RSI_PERIOD #undef RSI_PRICE #undef ATR_PERIOD #undef ATR_SIZE #undef TF_1 #undef TF_2 #undef VOL #undef BARS #undef BIG_SIZE #undef SUPPORT_PERIOD //+------------------------------------------------------------------+

现在,我们已准备好测试针对RSI指标的新动态交易规则。在启动策略测试器之前,请务必在测试器中选择正确版本的EA。请记住,日期设置将与我们在文章开头部分所述的设置保持一致。

截图8

图8:为测试选择正确的交易应用程序版本

我们新版本的RSI交易算法所生成的净值曲线,与最初获得的结果看似相似。然而,让我们仔细分析结果的详细总结,以便明确了解究竟产生了哪些差异。

截图9

图9:采用动态规则交易算法的版本所生成的净值曲线

在初始测试中,我们在136笔交易中总共获得了588.60美元的净利润。而我们的新策略在121笔交易中实现了703.20美元的利润。因此,我们的盈利能力提高了约19.5%,与此同时,进行的交易总次数却减少了约11%。显然,我们的新系统相较于定义通常如何使用该指标的经典规则,显示出了明显的竞争优势。 

截图10

图10:新交易策略性能的详细结果总结



结论

我们今日探讨的解决方案,为您提供了一种设计模式,即如何利用MetaTrader 5交易终端,对交易应用程序的敏感度进行精细控制。对于分析多个交易品种的交易者而言,这将为他们提供一种全新的视角,帮助他们以客观、周全的方式比较不同市场中价格水平变动的强度,从而避免因意外漏洞而轻易偏离正轨的风险。

此外,我们提出的算法,即谨慎地将经典的30和70水平替换为直接从特定市场应用中观察到的指标范围内选出的最优水平,可能会使我们在与那些等待标准化结果出现的普通市场参与者相比时,获得实质性的优势。 

附件文件 描述
自适应交易规则 采用静态固定规则的交易应用程序基准版本。
自适应交易规则V2 根据可用市场数据调整交易规则的交易应用程序优化版本。
下一篇文章 >>