English Русский Deutsch 日本語
preview
重构经典策略(第十三部分):最小化均线交叉的滞后性

重构经典策略(第十三部分):最小化均线交叉的滞后性

MetaTrader 5示例 |
37 3
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

在我们之前的讨论中,我们已经从不同角度探讨了如何在使用均线交叉策略时最大化我们的效率。简单来说,我们之前曾对超过200种不同的交易品种进行过一项测试,并观察到,我们的计算机似乎更擅长学习预测未来的均线值,而不是正确预测未来的价格水平。我为这篇文章提供了一个快速链接,请点击此处。我们扩展了这一思路,并对两条均线进行了建模,其唯一目标就是比普通市场参与者更早地预测到交叉点的出现,并据此相应地调整我们的交易。我也附上了第二篇文章的链接,方便您随时查阅,请点击此处。 

我们今天的讨论,将再次对原始策略进行扩展,以最小化交易策略中固有的滞后性。传统的交叉策略要求两条移动平均线之间存在一个时间周期上的差距。然而,我们将打破这种常规思路,在两条移动平均线上使用相同的周期。

此时,一些读者可能会质疑:这还能算作移动平均线交叉策略吗?因为如果两条移动平均线的周期相同,它们又怎么可能产生交叉呢?答案出奇地简单:我们分别将一条移动平均线应用于开盘价,另一条应用于收盘价。
当应用于开盘价的移动平均线位于收盘价的移动平均线之上时,意味着价格水平的收盘价低于其开盘价。这等同于我们知道价格正在下跌。反之,当收盘价的移动平均线位于开盘价之上时,则意味着价格正在上涨。
在两条移动平均线上使用相同的周期确实非同寻常,但我今天在我们的实践中选择它,是为了向读者展示一种可能的方法,用以平息所有关于移动平均线交叉策略滞后性的批评。

在我们深入讨论的技术环节之前,需要说明的是,本文可被视为一类通用交易策略的范例,这种方法可以轻松地扩展到许多其他技术指标上。例如,相对强弱指数也可以独立地应用于开盘价、收盘价、最高价或最低价。我们会观察到,当市场处于下跌趋势时,跟踪开盘价的RSI往往会升至跟踪收盘价的RSI之上。


我们的回测概述

为了让我们能充分领会今天讨论的意义,首先将建立一个由传统交叉策略创建的基准策略。然后,我们将把这种传统策略的表现,与我们使用重新构想后的策略所能达成的效果进行比较。

我今天就以EURUSD货币对为例进行讨论。EURUSD是全球交易最活跃的货币对。它的波动性显著高于大多数货币对,因此通常不是简单交叉策略的理想选择。正如我们之前已经讨论过的,我们将重点关注日线图。将用大约4年的历史数据来回测我们的策略,时间范围从2020年1月1日到2024年12月24日,回测周期在下文的图1中高亮显示。

图1:在MetaTrader 5终端上,使用月线图查看我们为期4年的EURUSD回测周期

尽管传统的交叉策略直观易懂,并且其背后有相当合理的基本原理作为支撑,但这些策略通常需要无尽的优化才能保证有效使用。此外,用于慢速和快速移动指标的“正确”周期并非一目了然,而且可能发生剧烈变化。 

概括来说,原始策略是基于两条移动平均线产生的交叉,这两条线都跟踪同一证券的收盘价,但周期不同。当周期较短的移动平均线位于上方时,我们将此解读为价格水平一直处于上升趋势,并可能继续上涨的信号。反之,当周期较长的移动平均线位于上方时,我们将其解读为看跌信号。下文的图2为您提供了一个图示说明。

图2:传统移动平均线交叉策略的一个实例,黄线是快速移动平均线,白线是慢速移动平均线

在上面的图2中,我们随机选择了一个展示经典策略局限性的时间段。在图表中垂直线的左侧,你会观察到价格行为在大约两个月内一直处于横盘整理状态。这种迟滞的价格行为会产生很快被反转的交易信号,而且很可能是无利可图的。然而,在经历了这段惨淡的表现之后,我们终于在垂直线的右侧看到了价格水平突破并形成了真正的趋势。传统的交叉策略在趋势市场条件下效果最佳。然而,本文提出的策略能优雅地处理这些问题。



建立基准

我们的交易应用可以概念化为四个主要组件,它们将协同工作,以帮助我们进行交易:

特性 说明
系统常量 帮助我们隔离因应用交易逻辑的改变而带来的改进。
全局变量 负责跟踪指标值、当前市场价格以及我们可能需要的更多信息。
事件处理程序 在适当的时间执行各种任务,以实现我们有效交易移动平均线交叉的目标。
自定义函数 我们系统中的每个自定义函数都有一个委派给它的特定任务,所有这些函数共同帮助我们实现目标。

我们所应用的基准版本在实现上将是极其简单的。首要任务是建立系统常量,这些常量在我们将要进行的两次测试中都将保持固定。系统常量对于在不同交易策略之间进行公平比较非常重要,它可以防止我们在测试中无意间更改那些不应更改的设置,例如,我们的止损大小在两次测试中都应该是恒定的,以确保公平比较。

//+------------------------------------------------------------------+
//|                                               Channel And MA.mq5 |
//|                                        Gamuchirai Zororo Ndawana |
//|                          https://www.mql5.com/en/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define TF_1           PERIOD_D1 //--- Our main time frame
#define TF_2           PERIOD_H1 //--- Our lower time frame
#define ATR_PERIOD     14        //--- The period for our ATR
#define ATR_MULTIPLE   3         //--- How wide should our stops be?
#define VOL            0.01      //--- Trading volume

我们还将定义一些重要的全局变量,用于获取指标值和当前市场价格。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    trade;
int    ma_f_handler,ma_s_handler,atr_handler;
double ma_f[],ma_s[],atr[];
double bid,ask;
double o,h,l,c;
double original_sl;

使用交易类库来管理我们的头寸。 

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

在 MQL5 中,智能交易系统是由事件处理程序构建而成的。在 MetaTrader 5 终端中,会发生许多不同类型的事件。这些事件可以由用户的操作触发,也可以在有新报价时触发。每个事件都配有一个事件处理程序,每当该事件被触发时,相应的处理程序就会被调用。因此,我将应用程序设计成这样:每个事件处理程序都有其自己指定的函数,该函数会依次被调用来执行均线交叉策略所必需的任务。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Release our indicators
   release();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Update system variables
   update();
  }
//+------------------------------------------------------------------+

setup 函数:在我们的系统启动时被调用。OnInit 处理程序:当交易应用程序首次被应用到图表上时,OnInit 处理程序会被调用。它会将命令链传递给我们自定义的 setup 函数,由该函数为我们加载技术指标。

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Setup our system                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler  = iATR(Symbol(),TF_1,ATR_PERIOD);

   ma_f_handler = iMA(Symbol(),TF_1,10,0,MODE_EMA,PRICE_CLOSE);
   ma_s_handler = iMA(Symbol(),TF_1,60,0,MODE_EMA,PRICE_CLOSE);
  }

release 函数:当我们不再使用交易应用程序时,OnDeinit 事件处理程序会被调用,它会进而调用我们的 release 函数,以释放之前被技术指标占用的系统资源。

//+------------------------------------------------------------------+
//| Release variables we do not need                                 |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(atr_handler);
   IndicatorRelease(ma_f_handler);
   IndicatorRelease(ma_s_handler);
  }

update 函数:每当有新的市场价格报价时,OnTick 处理程序就会被调用,它会依次调用 update 函数来存储当前可用的最新市场信息。在此之后,如果我们没有持仓,我们将寻找交易机会。否则,我们将管理我们已开立的仓位。

/+------------------------------------------------------------------+
//| Update system variables                                          |
//+------------------------------------------------------------------+
void update(void)
  {
//--- Update the system
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),TF_2,0);
   if(current_time != time_stamp)
     {
      time_stamp = current_time;

      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(ma_s_handler,0,0,1,ma_s);
      CopyBuffer(ma_f_handler,0,0,1,ma_f);

      o  = iOpen(Symbol(),TF_1,0);
      h  = iHigh(Symbol(),TF_1,0);
      l  = iLow(Symbol(),TF_1,0);
      c  = iClose(Symbol(),TF_1,0);
      bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
      ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

      if(PositionsTotal() == 0)
         find_position();
      if(PositionsTotal() > 0)
         manage_position();
     }
  }

我们的持仓入场规则非常直接,并且已在上面详细解释过。当快速移动平均线位于慢速移动平均线上方时,我们开立多头仓位;反之,则开立空头仓位。

//+------------------------------------------------------------------+
//| Find a position                                                  |
//+------------------------------------------------------------------+
void find_position(void)
  {

   if((ma_s[0] > ma_f[0]))
     {
      Trade.Sell(VOL,Symbol(),bid,0,0,"");
      trade = -1;
     }

   if((ma_s[0] < ma_f[0]))
     {
      Trade.Buy(VOL,Symbol(),ask,0,0,"");
      trade = 1;
     }
  }

最后,我们将使用平均真实波幅指标来动态调整我们的止损。我们会在入场价格的上方和下方,分别加上一个固定倍数的ATR读数,以此来设定止盈和止损水平。此外,我们还会加入过去90天(一个商业周期)的平均ATR读数,这样做的目的是为了将市场近期的波动性水平纳入考量。最后,将使用三元运算符来调整止盈和止损水平。规则是:只有当新的持仓比旧的持仓更有利可图时,才应该更新止损/止盈位。三元运算符让我们能够以一种紧凑、简洁的方式来表达这一逻辑。此外,三元运算符还为我们提供了灵活性,可以轻松地独立调整止盈和止损水平。

//+------------------------------------------------------------------+
//| Manage our positions                                             |
//+------------------------------------------------------------------+
void manage_position(void)
  {
//--- Select the position
   if(PositionSelect(Symbol()))
     {
      //--- Get ready to update the SL/TP
      double initial_sl  = PositionGetDouble(POSITION_SL);
      double initial_tp  = PositionGetDouble(POSITION_TP);
      //--- Calculate the average ATR move
      vector atr_mean;
      atr_mean.CopyIndicatorBuffer(atr_handler,0,0,90);
      double buy_sl      = (ask - ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double sell_sl     = (bid + ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double buy_tp      = (ask + ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double sell_tp     = (bid - ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double new_sl      = ((trade == 1) && (initial_sl <  buy_sl)) ? (buy_sl) : ((trade == -1) && (initial_sl > sell_sl)) ? (sell_sl) : (initial_sl);
      double new_tp      = ((trade == 1) && (initial_tp <  buy_tp)) ? (buy_tp) : ((trade == -1) && (initial_tp > sell_tp)) ? (sell_tp) : (initial_tp);

      if(initial_sl == 0 && initial_tp == 0)
        {
         if(trade == 1)
           {
            original_sl = buy_sl;
            Trade.PositionModify(Symbol(),buy_sl,buy_tp);
           }

         if(trade == -1)
           {
            original_sl = sell_sl;
            Trade.PositionModify(Symbol(),sell_sl,sell_tp);
           }

        }
      //--- Update the position
      else
         if((initial_sl * initial_tp) != 0)
           {
            Trade.PositionModify(Symbol(),new_sl,new_tp);
           }
     }
  }
//+------------------------------------------------------------------+

当把这一切集成到一起后,我们的代码如下所示。

//+------------------------------------------------------------------+
//|                                               Channel And MA.mq5 |
//|                                        Gamuchirai Zororo Ndawana |
//|                          https://www.mql5.com/en/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| This version off the application is mean reverting               |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define TF_1           PERIOD_D1 //--- Our main time frame
#define TF_2           PERIOD_H1 //--- Our lower time frame
#define ATR_PERIOD     14        //--- The period for our ATR
#define ATR_MULTIPLE   3         //--- How wide should our stops be?
#define VOL            0.01         //--- Trading volume

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    trade;
int    ma_f_handler,ma_s_handler,atr_handler;
double ma_f[],ma_s[],atr[];
double bid,ask;
double o,h,l,c;
double original_sl;

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Release our indicators
   release();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Update system variables
   update();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Setup our system                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler  = iATR(Symbol(),TF_1,ATR_PERIOD);

   ma_f_handler = iMA(Symbol(),TF_1,10,0,MODE_EMA,PRICE_CLOSE);
   ma_s_handler = iMA(Symbol(),TF_1,60,0,MODE_EMA,PRICE_CLOSE);
  }

//+------------------------------------------------------------------+
//| Release variables we do not need                                 |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(atr_handler);
   IndicatorRelease(ma_f_handler);
   IndicatorRelease(ma_s_handler);
  }

//+------------------------------------------------------------------+
//| Update system variables                                          |
//+------------------------------------------------------------------+
void update(void)
  {
//--- Update the system
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),TF_2,0);
   if(current_time != time_stamp)
     {
      time_stamp = current_time;

      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(ma_s_handler,0,0,1,ma_s);
      CopyBuffer(ma_f_handler,0,0,1,ma_f);

      o  = iOpen(Symbol(),TF_1,0);
      h  = iHigh(Symbol(),TF_1,0);
      l  = iLow(Symbol(),TF_1,0);
      c  = iClose(Symbol(),TF_1,0);
      bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
      ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

      if(PositionsTotal() == 0)
         find_position();
      if(PositionsTotal() > 0)
         manage_position();
     }
  }

//+------------------------------------------------------------------+
//| Find a position                                                  |
//+------------------------------------------------------------------+
void find_position(void)
  {

   if((ma_s[0] > ma_f[0]))
     {
      Trade.Sell(VOL,Symbol(),bid,0,0,"");
      trade = -1;
     }

   if((ma_s[0] < ma_f[0]))
     {
      Trade.Buy(VOL,Symbol(),ask,0,0,"");
      trade = 1;
     }
  }

//+------------------------------------------------------------------+
//| Manage our positions                                             |
//+------------------------------------------------------------------+
void manage_position(void)
  {
//--- Select the position
   if(PositionSelect(Symbol()))
     {
      //--- Get ready to update the SL/TP
      double initial_sl  = PositionGetDouble(POSITION_SL);
      double initial_tp  = PositionGetDouble(POSITION_TP);
      //--- Calculate the average ATR move
      vector atr_mean;
      atr_mean.CopyIndicatorBuffer(atr_handler,0,0,90);
      double buy_sl      = (ask - ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double sell_sl     = (bid + ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double buy_tp      = (ask + ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double sell_tp     = (bid - ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double new_sl      = ((trade == 1) && (initial_sl <  buy_sl)) ? (buy_sl) : ((trade == -1) && (initial_sl > sell_sl)) ? (sell_sl) : (initial_sl);
      double new_tp      = ((trade == 1) && (initial_tp <  buy_tp)) ? (buy_tp) : ((trade == -1) && (initial_tp > sell_tp)) ? (sell_tp) : (initial_tp);

      if(initial_sl == 0 && initial_tp == 0)
        {
         if(trade == 1)
           {
            original_sl = buy_sl;
            Trade.PositionModify(Symbol(),buy_sl,buy_tp);
           }

         if(trade == -1)
           {
            original_sl = sell_sl;
            Trade.PositionModify(Symbol(),sell_sl,sell_tp);
           }

        }
      //--- Update the position
      else
         if((initial_sl * initial_tp) != 0)
           {
            Trade.PositionModify(Symbol(),new_sl,new_tp);
           }
     }
  }
//+------------------------------------------------------------------+

由于我们当前的策略并未使用人工智能或任何曲线拟合技术,因此可以进行一次简单的回测,而无需担心对我们现有的数据产生过度拟合。此外,我们没有任何需要调整的输入参数。因此,将“Forward”(前向测试)设置为“No”(否),因为我们目前不需要使用MetaTrader 5终端的前向测试功能。

图 3:为我们的回测选择日期

此外,在你能模拟出的最严苛的环境下测试你的交易应用程序,是一种良好的做法。因此,我们为今天的回测选择了“Random delay”(随机延迟)模式,并将我们的建模方式设置为使用“Real ticks”(真实 ticks)。当使用真实 ticks 时,下载历史数据所需的时间可能会比使用其他模式(如“仅开盘价”)更长。然而,其结果可能会更接近你在真实 ticks 上的实际表现。

我们的第二批设置

图 4:我们 EURUSD 交叉策略回测的第二批设置

当我们分析使用这个简单交叉策略所得到的结果时,我们可以很快地看到,如果我们以其原始形式遵循该策略,将会遇到的潜在问题。请注意,从回测开始到2022年7月,我们的策略一直在努力,仅仅是为了达到盈亏平衡。这是一个接近我们回测总时长一半,即2年的回撤期。这是不可取的,也不是我们可以信任其在不加监督的情况下交易我们资金的策略所应具备的特征。

图 5:分析遵循均线交叉策略所产生的盈亏曲线

我们策略的原始形式几乎不盈利,并且其进行的所有交易中,亏损率接近61%。这让我们对该策略产生了负面的预期,而我们的悲观情绪也因夏普比率非常接近于0这一事实而进一步得到了验证。但请注意,我们只需对所采用的交易逻辑做一些简单的调整,就能多么显著地改善我们的策略。 

图 6:使用传统均线交叉策略的详细绩效摘要



改进原始策略

在下方的图7中,我为大家提供了我们新提出的交叉策略的可视化图示。蓝色和绿色线分别是5周期的移动平均线,分别追踪收盘价(蓝色)和开盘价(绿色)。请注意,当蓝色移动平均线位于绿色移动平均线上方时,价格水平正在上涨。此外,请注意我们的交叉信号对价格水平变化的响应有多么灵敏。传统的交叉策略需要一段不定的时间来反映市场趋势的任何变化。然而,当我们使用新策略来追踪价格水平时,我们可以快速观察到趋势的变化,甚至是盘整期——在此期间,我们的两条移动平均线会反复交叉,但并未形成真正的趋势。

图 7:在EURUSD日线图上可视化我们的新交叉策略

到目前为止,我们的系统已经能够实现盈利交易,但我们还可以做得更好。我们将要对系统做的唯一改变,就是改变触发我们持仓的条件:

更改 说明
交易
规则
传统规则是:当快速移动平均线位于慢速移动平均线上方时买入。而现在,我们将改为:当开盘价移动平均线位于收盘价移动平均线上方时买入。

为了实现我们期望的改进,必须对当前交易策略的原始形式做一些更改:

更改 说明
附加的系统变量 我们将需要一个负责固定开盘价和收盘价移动平均线周期的新系统变量。
新的全局变量 将创建新的全局变量,以跟踪我们正在关注的新信息。
修改自定义函数 我们目前构建的一些自定义函数,需要根据我们正在遵循的新系统设计进行扩展。

在大多数情况下,我们系统的所有其他部分都将保留。我们希望将因改变对均线交叉的看法而带来的改进隔离开来。因此,为了实现我们的目标,我们将首先创建一个新的系统常量,以固定移动平均线的周期。

//--- Omitted code that has not changed
#define MA_PERIOD      2         //--- The period for our moving average following the close

然后,我们需要为正在跟踪的新信息定义新的全局变量。现在将为4个报价价格(开盘价、最高价、最低价和收盘价)分别创建移动平均线处理程序。

//--- Omitted code that have not changed
int    ma_h_handler,ma_l_handler,ma_c_handler,ma_o_handler;
double ma_h[],ma_l[],ma_c[],ma_o[];

当我们的交易应用程序启动时,除了已经熟悉的指标外,我们还需要加载一些额外的指标。

//+------------------------------------------------------------------+
//| Setup our system                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler  = iATR(Symbol(),TF_1,ATR_PERIOD);
   ma_h_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_HIGH);
   ma_l_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_LOW);
   ma_c_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_CLOSE);
   ma_o_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_OPEN);
  }

同样地,我们负责移除EA的自定义函数也需要进行扩展,以适应我们引入到系统中的这些新指标。

//+------------------------------------------------------------------+
//| Release variables we do not need                                 |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(atr_handler);
   IndicatorRelease(ma_h_handler);
   IndicatorRelease(ma_l_handler);
   IndicatorRelease(ma_c_handler);
   IndicatorRelease(ma_o_handler);
  }

最后,用于开仓的交易条件也必须进行更新,以使其与我们新的交易逻辑保持一致。

//+------------------------------------------------------------------+
//| Find a position                                                  |
//+------------------------------------------------------------------+
void find_position(void)
  {

   if((ma_o[0] > ma_c[0]))
     {
      Trade.Sell(VOL,Symbol(),bid,0,0,"");
      trade = -1;
     }

   if((ma_o[0] < ma_c[0]))
     {
      Trade.Buy(VOL,Symbol(),ask,0,0,"");
      trade = 1;
     }
  }

现在,让我们来看看这对最终收益到底有何影响。首先,将设置新的交易应用程序,使其在我们第一次测试所使用的相同时间段内进行交易。

图 8:设置经过改进的新交易算法,使其在我们第一次测试所使用的相同时间段内进行交易。

此外,我们希望在完全相同的条件下进行两次回测,以确保测试没有偏差。否则,如果其中一个策略获得了不公平的优势,那么我们迄今为止测试的公正性就会受到质疑。

图 9:我们希望确保两次测试的条件完全相同,以便进行公平的比较。

我们已经可以看到,与最初获得的结果相比,这带来了巨大的改进。在最初的回测中,前两年我们都在努力实现盈亏平衡。然而,通过我们的新交易策略,突破了这一限制,并在全部四年中都实现了盈利。 

图 10:我们的新交易策略突破了那段亏损的交易期,而这段时期对于传统的交叉策略来说,是极具挑战性的。

在我们最初的回测中,系统总共执行了 41 笔交易,而在最新的回测中,总共执行了 42 笔交易。因此,新系统比旧的交易方式承担了更多的风险。因为如果让时间继续推移,它们之间的差距可能会继续扩大。然而,尽管我们的新系统似乎比旧系统执行了更多的交易,但旧系统的总利润是 59.57 美元,而现在我们的总利润已经翻了一倍多,达到了 125.36 美元。请回想一下,目前我们限制了交易系统,每次只能以最小手数执行一笔交易。此外,在我们的第一个系统中,总亏损额为 410.02 美元,而采用新策略后,总亏损额已降至 330.74 美元。 

在设计系统时,必须从权衡取舍的角度来思考。新系统为我们带来了更好的表现。然而,我们也应该注意到,平均盈利额从 29.35 美元下降到了 22.81 美元。这是因为,新系统会偶尔错过一些旧系统能够从中获利的交易。然而,考虑到我们所获得的性能提升,这种偶尔的遗憾或许是值得的。 

夏普比率(Sharpe ratio)从第一次测试的 0.18,上升到了当前测试的 0.5。这是一个好兆头,表明我们更好地利用了我们的资本。此外,亏损交易的比例从第一次测试的 60.98%,下降到了 52.38% 的新低。

图 11:对我们新交易策略绩效的详细回顾。



结论

我们社区的大多数成员往往是独立开发者,他们独自进行项目开发。对于处于这些处境的社区同仁,我相信像我们在这里为您提出的这类简单算法,可能是更实用的解决方案。它更易于维护、开发和扩展。作为一名独立开发者,管理一个复杂且庞大的代码库并非易事,即便对于经验丰富的开发者也是如此。而且,如果您是刚加入我们算法交易社区的新成员,那么这个策略可能会对您特别有帮助。如果您喜欢阅读这篇文章,请务必加入我们的下一次讨论,届时我们将尝试超越我们今天所取得的最佳成果。 

文件名 说明
Open & Close MA Cross 该文件包含我们对均线交叉策略进行重新构想后的新版本。
Traditional MA Cross 该文件包含均线交叉策略的经典实现。

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

附加的文件 |
最近评论 | 前往讨论 (3)
linfo2
linfo2 | 24 1月 2025 在 19:16
感谢您提供的代码和想法,我喜欢您的代码结构。您向我介绍了向量数据类型,我以前从未使用过。
  vector atr_mean;
      atr_mean.CopyIndicatorBuffer(atr_handler,0,0,90); atr_mean.Mean())
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 2 2月 2025 在 03:33
linfo2 #:
感谢您提供的代码和想法,我喜欢您的代码结构。你向我介绍了向量数据类型,我以前从未使用过。
谢谢你,Niel,我的目标是将简单性与技术严谨性相结合,很高兴知道我的努力得到了回报。

矢量类改变了游戏规则,它提供的功能比我们日常使用的还要多。

我非常期待学习如何使用矩阵类,因为它能让我们只需调用一个函数就能建立线性模型。
Celestine Nwakaeze
Celestine Nwakaeze | 13 9月 2025 在 10:39
非常感谢这种奇妙的代码结构方式。作为初学者,这篇文章让我受益匪浅。你的肘部需要更多油脂。上帝保佑你。谢谢。
大爆炸-大坍缩(BBBC)算法 大爆炸-大坍缩(BBBC)算法
本文介绍了大爆炸-大坍缩方法,该方法包含两个关键阶段:随机点的循环生成,以及将这些点压缩至最优解。该方法结合了探索与精炼过程,使我们能够逐步找到更优的解,并开拓新的优化可能性。
从基础到中级:数组(四) 从基础到中级:数组(四)
在本文中,我们将看看如何做一些与 C、C++ 和 Java 等语言中实现的非常相似的事情。我说的是在函数或过程中传递几乎无限数量的参数。虽然这似乎是一个相当高级的主题,但在我看来,任何理解了前面概念的人都可以很容易地实现这里展示的内容。只要它们真的被正确理解。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
价格行为分析工具包开发(第六部分):均值回归信号捕捉器 价格行为分析工具包开发(第六部分):均值回归信号捕捉器
有些概念乍一看似乎简单明了,但在实际操作中的实现却颇具挑战。在接下来的文章中,将带您了解我们创新性地自动化一款运用均值回归策略分析市场的智能交易系统(EA)的方法。与我们一同揭开这一激动人心的自动化过程的神秘面纱吧。