English Русский Español Deutsch 日本語 Português
preview
构建和测试肯特纳通道交易系统

构建和测试肯特纳通道交易系统

MetaTrader 5交易 | 16 九月 2024, 10:48
592 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

概述

了解金融市场波动的概念非常重要,在市场交易时,应该让它对您有利。本文的主要目的是帮助您节省时间,使用肯特纳通道技术指标为您提供许多基于波动性概念的交易系统的测试结果,以了解它如何作为您的完整系统的一部分来衡量波动性或在特定条件下就这一重要概念采取行动。

最重要的一点是,您可能会发现,您需要根据自己的喜好优化系统,以获得更好的结果,因此,您需要根据自己的交易目标开展工作并测试多个不同方面,以找到系统的最佳最终设置。

本文将介绍我们如何创建自定义的肯特纳通道指标和交易系统来交易简单的交易策略,然后在不同的金融资产上对其进行测试和比较,根据我们的测试和优化找出哪一个能带来更好的结果,我们将尽可能提供最佳设置。在这篇文章中我们将涵盖以下主题:

在前面的主题之后,我们将能够根据肯特纳通道指标的主要概念使用该指标,在详细了解该指标之后,我们将能够根据简单的策略创建交易系统。之后,我们就可以对它们进行测试,根据测试结果看看哪种策略比其他策略更好。我希望你发现这篇文章有助于你在学习新东西后改进交易并获得更好的结果,或者对任何能提高交易结果的相关想法有更多的见解。
免责声明:所有信息 "按原样 "提供,仅用于教育目的,不用于交易目的或提供建议。本信息不保证任何结果。如果您选择在您的任何交易账户上使用这些材料,您将自行承担风险,并承担唯一责任。


波动性定义

在本部分中,我们将了解什么是波动率,以及它在交易和金融市场中的重要性。波动性是一个统计概念,可用来衡量金融资产收益的分散性,当波动性增加时,风险也随之增加。我们可以说,波动率这一概念也是指市场双向的大幅波动,而这种波动运动是通过围绕平均价格的摆动来衡量的。

导致市场波动的原因有很多,比如以下几点:

  • 市场情绪:市场波动会受到交易者情绪的影响。
  • 收益:收益报告可以根据其结果影响市场波动。
  • 经济事件和指标:重要的经济事件和指标也会导致市场波动,如通货膨胀、国内生产总值和就业新闻。
  • 流动性:市场的流动性和波动性之间存在关系,当流动性较低时,波动性就会增加。

当谈到衡量波动性时,我们可以看到在这种情况下可以使用许多方法,例如 beta 系数和标准差。还有很多技术指标可以用来衡量市场的波动性,比如布林线、真实平均范围、波动率指数(VIX)和肯特纳通道,这也是我们本文的主题。测量波动率对评估金融资产的波动非常有帮助。

波动率也有多种类型,如隐含波动率和历史波动率。


肯特纳通道定义

在本部分中,我们将详细了解肯特纳通道指标、如何使用该指标以及如何计算该指标,以了解如何对我们有利地使用它。肯特纳通道指标是一种波动指标,包含价格上下两个区间和中间的移动平均线。

切斯特.肯特纳(Chester Keltner)于 20 世纪 60 年代在其《如何在商品中赚钱》一书中首次提出了肯特纳通道指标。它在计算中使用简单移动平均线和高/低范围,但后来演变成今天常用的形式,在计算中使用平均真实范围 (ATR)。移动平均线的典型设置是 20 个周期数,上下限的计算是 EMA 上下限 ATR 的两倍,这些设置可根据用户偏好和交易目标进行调整。

肯特纳通道是指当价格到达上区间时看涨,当价格到达下区间时看跌,因为它可以用来识别趋势方向。当价格在区间之间波动而没有明显的上升或下降趋势时,区间也可用作支撑位和阻力位。总之,肯特纳通道是一种技术指标,它利用指数移动平均线和平均真实范围来衡量金融资产的波动性。 

以下是计算肯特纳通道指标的方法:

  • 计算指数移动平均线,它将成为指标的中间线。
  • 计算平均真实范围,用于计算区间。
  • 计算上限值等于 EMA,再加上 2 与 ATR 相乘的结果。
  • 计算下限等于 EMA,然后减去 2 与 ATR 相乘的结果。

那么,

肯特纳通道中线 = 指数移动平均线 (EMA)

肯特纳通道上轨 = 指数移动平均线 (EMA) + 2 * 真实平均范围 (ATR)

肯特纳通道下轨 = 指数移动平均线 (EMA) - 2 * 真实平均范围 (ATR)


肯特纳通道策略

根据我们对如何使用肯特纳通道指标的理解,将区间作为支撑或阻力,或在接近上区间时看涨,在接近下区间时看跌,我们将使用两种简单的策略:

  • 区间反弹:当反弹超过区间下轨时,我们会发出买入指令;当反弹低于区间上轨时,我们会发出卖出指令。
  • 区间突破:突破上区间时,我们将下达买单;跌破下区间时,我们将下达卖单。

策略一:区间反弹

根据我们对肯特纳通道指标的理解,我们可以根据收盘价的位置和指标的区间来检测买入和卖出信号。当最后收盘价的前一个收盘价低于下限值,同时上一个收盘价高于下限值时,我们就会发出买入指令。当最后收盘价的前一个收盘价高于上限值,同时最后收盘价低于上限值时,卖出指令将被打开。

很简单,

最后收盘价的前一个收盘价 < 下限值,以及最后收盘价 > 下限值 ==> 买入信号

最后收盘价的前一个收盘价 > 上限值,并且最后收盘价 < 上限值 ==> 卖出信号

策略二:区间突破
根据该策略,我们将在突破发生时下单,当最后一个收盘价的前一个价格低于最后一个上限区间的前一个价格,同时最后一个收盘价收于最后一个上限区间之上时,我们将下买单;反之亦然,当最后一个收盘价的前一个价格高于最后一个下限区间的前一个价格,同时最后一个收盘价收于最后一个下限区间之下时,我们将下卖单。

很简单,

最后收盘价的前一个收盘价 < 上限值,并且最后收盘价 > 上限值 ==> 买入信号

最后收盘价的前一个收盘价 > 下限值,并且最后收盘价 < 下限值 ==> 卖出信号


肯特纳通道交易系统

在这一部分,我们将创建一个交易系统,根据前面提到的策略自动下单。首先,我们将创建自定义指标来计算肯特纳通道指标,以便在以后创建交易系统时使用:

使用 #property 预处理器指令确定指标的位置,我们将使用 indicator_chart_window 在图表上显示指标

#property indicator_chart_window

通过使用 indicator_buffers 的标识符值来确定缓冲区数量,我们将设置为 3,而为了确定绘图的数量,我们也将设置为 3。

#property indicator_buffers 3
#property indicator_plots 3

为上轨、中线和下轨设置指标的类型、样式、宽度、颜色和标签属性

#property indicator_type1 DRAW_LINE
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
#property indicator_color1 clrRed
#property indicator_label1 "Keltner Upper Band"


#property indicator_type2 DRAW_LINE
#property indicator_style2 STYLE_SOLID
#property indicator_width2 1
#property indicator_color2 clrBlue
#property indicator_label2 "Keltner Middle Line"


#property indicator_type3 DRAW_LINE
#property indicator_style3 STYLE_SOLID
#property indicator_width3 2
#property indicator_color3 clrGreen
#property indicator_label3 "Keltner Lower Band"

为指标设置移动平均周期、通道乘数、在区间计算器中使用 ATR、移动平均方法或模式以及价格类型等方面的输入参数。

input int      maPeriod            = 10;           // Moving Average Period
input double   multiInp             = 2.0;          // Channel multiplier
input bool     isAtr                 = false;        // ATR
input ENUM_MA_METHOD     maMode       = MODE_EMA;     // Moving Average Mode
input ENUM_APPLIED_PRICE priceType = PRICE_TYPICAL;// Price Type

声明三个数组(upper、middle、lower)、两个移动平均线和 ATR 的句柄以及 minBars 全局变量

double upper[], middle[], lower[];
int maHandle, atrHandle;
static int minBars = maPeriod + 1;

在 OnInit() 函数中,我们将使用 SetIndexBuffer 函数将指标缓冲区与数组连接起来,该函数的参数为:

  • index:确定缓冲区的索引,upper 为 0,middle 为 1,lower 为 2。
  • buffer[]: 用于确定数组,它将是 upper、middle 和 lower。
  • data_type:确定我们需要在指标中存储的内容,它可以是 ENUM_INDEXBUFFER_TYPE 中的一种,默认情况下是 INDICATOR_DATA,我们将使用它。
   SetIndexBuffer(0,upper,     INDICATOR_DATA);
   SetIndexBuffer(1,middle,  INDICATOR_DATA);
   SetIndexBuffer(2,lower,  INDICATOR_DATA);

使用 ArraySetAsSeries 函数为数组设置 AS_Series 标志,其参数为:

  • array[]:通过引用来确定数组,我们将使用 upper、middle 和 lower 数组。
  • flag:用于确定数组索引的方向,成功时返回 true,失败时返回 false。
   ArraySetAsSeries(upper, true);
   ArraySetAsSeries(middle, true);
   ArraySetAsSeries(lower, true);

使用 IndicatorSetString 函数设置指标名称,其参数为:

  • prop_id:确定标识符,可以是 ENUM_CUSTOMIND_PROPERTY_STRING 中的一个,我们将使用 INDICATOR_SHORTNAME 设置指标名称。
  • prop_value:确定所需的指标名称。
IndicatorSetString(INDICATOR_SHORTNAME,"Custom Keltner Channel " + IntegerToString(maPeriod));

使用 IndicatorSetInteger 函数设置指标值的位数,其参数与 IndicatorSetString 函数相同,只是 prop_value 的数据类型将改为字符串。

IndicatorSetInteger(INDICATOR_DIGITS,_Digits);

使用 iMA 函数定义移动平均线句柄,其参数为:

  • symbol:指定交易品种名称,我们将使用 NULL 适用于当前交易品种。
  • period:指定时段,我们将使用 0 来应用于当前时段。
  • ma_period:指定移动平均周期数,我们将使用用户输入的 maPeriod。
  • ma_shift:根据需要指定水平移动。
  • ma_method:指定移动平均方法,我们将使用用户输入的 maMode。
  • applied_price:指定价格类型,我们将使用用户输入的 priceType。
maHandle = iMA(NULL, 0, maPeriod, 0, maMode, priceType);

根据 ATR 输入的 true 或者 false 定义 ATR,以决定是否用于区间计算

   if(isAtr)
     {
      atrHandle = iATR(NULL, 0, maPeriod);
      if(atrHandle == INVALID_HANDLE)
        {
         Print("Handle Error");
         return(INIT_FAILED);
        }
     }
   else
      atrHandle = INVALID_HANDLE;

声明 indValue 函数,以指定指标缓冲区、移动平均值、middle、upper 和 lower 值

void indValue(const double& h[], const double& l[], int shift)
  {
   double ma[1];
   if(CopyBuffer(maHandle, 0, shift, 1, ma) <= 0)
      return;
   middle[shift] = ma[0];
   double average = AVG(h, l, shift);
   upper[shift]    = middle[shift] + average * multiInp;
   lower[shift] = middle[shift] - average * multiInp;
  }

声明 AVG 函数,根据计算出的乘数计算区间边界位置

double AVG(const double& High[],const double& Low[], int shift)
  {
   double sum = 0.0;
   if(atrHandle == INVALID_HANDLE)
     {
      for(int i = shift; i < shift + maPeriod; i++)
         sum += High[i] - Low[i];
     }
   else
     {
      double t[];
      ArrayResize(t, maPeriod);
      ArrayInitialize(t, 0);
      if(CopyBuffer(atrHandle, 0, shift, maPeriod, t) <= 0)
         return sum;
      for(int i = 0; i < maPeriod; i++)
         sum += t[i];
     }
   return sum / maPeriod;
  }

在 OnCalculate 函数中,我们将按照以下代码计算指标值

   if(rates_total <= minBars)
      return 0;
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low,  true);
   int limit = rates_total - prev_calculated;
   if(limit == 0)             
     {
     }
   else
      if(limit == 1)      
        {
         indValue(high, low, 1);
         return(rates_total);
        }
      else
         if(limit > 1)       
           {
            ArrayInitialize(middle, EMPTY_VALUE);
            ArrayInitialize(upper,    EMPTY_VALUE);
            ArrayInitialize(lower, EMPTY_VALUE);
            limit = rates_total - minBars;
            for(int i = limit; i >= 1 && !IsStopped(); i--)
               indValue(high, low, i);
            return(rates_total);
           }
   indValue(high, low, 0);
   return(rates_total);

因此,我们可以在自定义肯特纳通道指标的一个代码块中找到完整代码,如下所示:

//+------------------------------------------------------------------+
//|                                       Custom_Keltner_Channel.mq5 |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots 3
#property indicator_type1 DRAW_LINE
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
#property indicator_color1 clrRed
#property indicator_label1 "Keltner Upper Band"
#property indicator_type2 DRAW_LINE
#property indicator_style2 STYLE_SOLID
#property indicator_width2 1
#property indicator_color2 clrBlue
#property indicator_label2 "Keltner Middle Line"
#property indicator_type3 DRAW_LINE
#property indicator_style3 STYLE_SOLID
#property indicator_width3 2
#property indicator_color3 clrGreen
#property indicator_label3 "Keltner Lower Band"
input int      maPeriod            = 10;           // Moving Average Period
input double   multiInp             = 2.0;          // Channel multiplier
input bool     isAtr                 = false;        // ATR
input ENUM_MA_METHOD     maMode       = MODE_EMA;     // Moving Average Mode
input ENUM_APPLIED_PRICE priceType = PRICE_TYPICAL;// Price Type
double upper[], middle[], lower[];
int maHandle, atrHandle;
static int minBars = maPeriod + 1;
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0,upper, INDICATOR_DATA);
   SetIndexBuffer(1,middle, INDICATOR_DATA);
   SetIndexBuffer(2,lower, INDICATOR_DATA);
   ArraySetAsSeries(upper, true);
   ArraySetAsSeries(middle, true);
   ArraySetAsSeries(lower, true);
   IndicatorSetString(INDICATOR_SHORTNAME,"Custom Keltner Channel " + IntegerToString(maPeriod));
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
   maHandle = iMA(NULL, 0, maPeriod, 0, maMode, priceType);
   if(isAtr)
     {
      atrHandle = iATR(NULL, 0, maPeriod);
      if(atrHandle == INVALID_HANDLE)
        {
         Print("Handle Error");
         return(INIT_FAILED);
        }
     }
   else
      atrHandle = INVALID_HANDLE;
   return INIT_SUCCEEDED;
  }
void indValue(const double& h[], const double& l[], int shift)
  {
   double ma[1];
   if(CopyBuffer(maHandle, 0, shift, 1, ma) <= 0)
      return;
   middle[shift] = ma[0];
   double average = AVG(h, l, shift);
   upper[shift]    = middle[shift] + average * multiInp;
   lower[shift] = middle[shift] - average * multiInp;
  }
double AVG(const double& High[],const double& Low[], int shift)
  {
   double sum = 0.0;
   if(atrHandle == INVALID_HANDLE)
     {
      for(int i = shift; i < shift + maPeriod; i++)
         sum += High[i] - Low[i];
     }
   else
     {
      double t[];
      ArrayResize(t, maPeriod);
      ArrayInitialize(t, 0);
      if(CopyBuffer(atrHandle, 0, shift, maPeriod, t) <= 0)
         return sum;
      for(int i = 0; i < maPeriod; i++)
         sum += t[i];
     }
   return sum / maPeriod;
  }
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   if(rates_total <= minBars)
      return 0;
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low,  true);
   int limit = rates_total - prev_calculated;
   if(limit == 0)             
     {
     }
   else
      if(limit == 1)      
        {
         indValue(high, low, 1);
         return(rates_total);
        }
      else
         if(limit > 1)       
           {
            ArrayInitialize(middle, EMPTY_VALUE);
            ArrayInitialize(upper,    EMPTY_VALUE);
            ArrayInitialize(lower, EMPTY_VALUE);
            limit = rates_total - minBars;
            for(int i = limit; i >= 1 && !IsStopped(); i--)
               indValue(high, low, i);
            return(rates_total);
           }
   indValue(high, low, 0);
   return(rates_total);
  }
//+------------------------------------------------------------------+

编译完这段代码后,我们可以在指标文件夹中找到它。将其插入图表后,我们会发现它如下所示:

KCH_insert

正如我们在上一张图表中看到的,价格周围有一个代表肯特纳通道指标的通道。现在,我们需要根据上述策略使用该指标创建我们的交易系统,我们可以按照以下步骤进行。

现在,我们需要为每种提到的策略创建交易系统,以便根据它们的概念自动下达买入和卖出订单。


策略一:区间反弹交易系统:

第一种策略与我们提到的相同,即通过创建 EA 自动下单,考虑根据区间附近的反弹情况来开仓交易。下面是我们如何编写这个交易系统的代码:

我们将使用预处理器指令 include 来包含交易文件

#include <trade/trade.mqh>

声明移动平均线周期数、通道乘数、ATR(是否用于波段计算)、移动平均线模式、价格类型、手数、止损点数和止盈点数的输入参数

input int      maPeriod            = 10;           // Moving Average Period
input double   multiInp             = 2.0;          // Channel multiplier
input bool     isAtr                 = false;        // ATR
input ENUM_MA_METHOD     maMode       = MODE_EMA;     // Moving Average Mode
input ENUM_APPLIED_PRICE priceType = PRICE_TYPICAL;// Price Type
input double      lotSize=1;
input double slPips = 150;
input double tpPips = 300;

为 Keltner、barTotal 和交易对象声明全局变量

int keltner;
int barsTotal;
CTrade trade;

在 OnInit() 函数中,我们将定义 barsTotal 变量,以便稍后使用 iBars 函数评估是否有新的柱形:

  • symbol:确定要在哪个交易品种上应用代码,我们将使用 _Symbol 应用于当前交易品种。
  • timeframe:确定时间段,我们将使用 PERIOD_CURRENT 适用于当前时间段。
barsTotal=iBars(_Symbol,PERIOD_CURRENT);

使用 iCustom 函数定义用作指标句柄的 Keltner 变量,其参数为:

  • symbol:确定交易品种名称,_Symbol 将用于当前交易品种。
  • period:使用 PERIOD_CURRENT 设置时间框架。
  • name:指定指标名称。
  • ...:指标输入参数列表。
keltner = iCustom(_Symbol,PERIOD_CURRENT,"Custom_Keltner_Channel",maPeriod,multiInp, isAtr, maMode, priceType);

在 OnDeinit 函数中,我们将在删除 EA 时打印一条信息

Print("EA is removed");

在 OnTick() 函数中,我们将声明并定义一个 bars 的整数型变量,将其与 barsTotal 进行比较,以检查是否出现了新的柱形

int bars=iBars(_Symbol,PERIOD_CURRENT);

如果 barsTotal 小于 bars,则将 barsTotal 与 bars 进行比较

if(barsTotal < bars)

如果 barsTotal 小于 bars 值,我们需要将 bars 值赋值给 barsTotal

barsTotal=bars;

然后声明 upper、middle 和 lower 的三个数组

double upper[], middle[], lower[];

使用 CopyBuffer 函数获取指标缓冲区的数据,其参数为:

  • indicator_handle:确定指标句柄,我们将使用 Keltner 表示上、中、下。
  • buffer_num:指定指标的缓冲区编号,上层为 0,中层为 1,下层为 2。
  • start_pos:指定开始计数的位置,我们将使用 0。
  • count:指定我们需要复制的数量,我们将使用 3。
  • buffer[]: 用于指定要复制的目标数组,我们将使用upper、middle 和 lower 数组。
      CopyBuffer(keltner,0,0,3,upper);
      CopyBuffer(keltner,1,0,3,middle);
      CopyBuffer(keltner,2,0,3,lower);

使用 ArraySetAsSeries 函数设置 AS_SERIES 标志

      ArraySetAsSeries(upper,true);
      ArraySetAsSeries(middle,true);
      ArraySetAsSeries(lower,true);

声明并定义指标值上一个值的 upper、middle 和 lower 值

      double prevUpperValue = NormalizeDouble(upper[2], 5);
      double prevMiddleValue = NormalizeDouble(middle[2], 5);
      double prevLowerValue = NormalizeDouble(lower[2], 5);

声明并定义指标值最后一个值的 upper、middle 和 lower 值

      double upperValue = NormalizeDouble(upper[1], 5);
      double middleValue = NormalizeDouble(middle[1], 5);
      double lowerValue = NormalizeDouble(lower[1], 5);

使用 iClose 函数及其参数声明和定义最后收盘价和最后收盘价的前一个收盘价:

  • symbol:指定交易品种名称。
  • timeframe:指定应用的时间段。
  • shift:通过指定索引来说明是否需要向后偏移。
      double lastClose=iClose(_Symbol,PERIOD_CURRENT,1);
      double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,2);

设置策略条件,如果最后收盘价的前一个值低于最后一个 lower 值的前一个值,且最后收盘价大于最后一个 lower 值,则下达买入订单

      if(prevLastClose<prevLowerValue && lastClose>lowerValue)
        {
         double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         double slVal = lowerValue - slPips*_Point;
         double tpVal = middleValue + tpPips*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }

设置策略条件,如果最后收盘价的前一个值大于最后 upper 值的前一个值,且最后收盘价低于最后 upper 值,则下达卖出指令

      if(prevLastClose>prevUpperValue && lastClose<upperValue)
        {
         double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         double slVal = upperValue + slPips*_Point;
         double tpVal = middleValue - tpPips*_Point;
         trade.Sell(lotSize,_Symbol,bid,upperValue,tpVal);
        }

以下是基于区间反弹策略创建交易系统的整段代码

//+------------------------------------------------------------------+
//|                                       Keltner_Trading_System.mq5 |
//+------------------------------------------------------------------+
#include <trade/trade.mqh>
input int      maPeriod            = 10;           // Moving Average Period
input double   multiInp             = 2.0;          // Channel multiplier
input bool     isAtr                 = false;        // ATR
input ENUM_MA_METHOD     maMode       = MODE_EMA;     // Moving Average Mode
input ENUM_APPLIED_PRICE priceType = PRICE_TYPICAL;// Price Type
input double      lotSize=1;
input double slPips = 150;
input double tpPips = 300; 
int keltner;
int barsTotal;
CTrade trade;
int OnInit()
  {
   barsTotal=iBars(_Symbol,PERIOD_CURRENT);
   keltner = iCustom(_Symbol,PERIOD_CURRENT,"Custom_Keltner_Channel",maPeriod,multiInp, isAtr, maMode, priceType);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }
//+------------------------------------------------------------------+
void OnTick()
  {
   int bars=iBars(_Symbol,PERIOD_CURRENT);
   if(barsTotal < bars)
     {
      barsTotal=bars;
      double upper[], middle[], lower[];
      CopyBuffer(keltner,0,0,3,upper);
      CopyBuffer(keltner,1,0,3,middle);
      CopyBuffer(keltner,2,0,3,lower);
      ArraySetAsSeries(upper,true);
      ArraySetAsSeries(middle,true);
      ArraySetAsSeries(lower,true);
      double prevUpperValue = NormalizeDouble(upper[2], 5);
      double prevMiddleValue = NormalizeDouble(middle[2], 5);
      double prevLowerValue = NormalizeDouble(lower[2], 5);
      double upperValue = NormalizeDouble(upper[1], 5);
      double middleValue = NormalizeDouble(middle[1], 5);
      double lowerValue = NormalizeDouble(lower[1], 5);
      double lastClose=iClose(_Symbol,PERIOD_CURRENT,1);
      double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,2);
      if(prevLastClose<prevLowerValue && lastClose>lowerValue)
        {
         double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         double slVal = lowerValue - slPips*_Point;
         double tpVal = middleValue + tpPips*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }
      if(prevLastClose>prevUpperValue && lastClose<upperValue)
        {
         double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         double slVal = upperValue + slPips*_Point;
         double tpVal = middleValue - tpPips*_Point;
         trade.Sell(lotSize,_Symbol,bid,upperValue,tpVal);
        }
     }
  }
//+------------------------------------------------------------------+


策略二:区间突破交易系统:

第一种策略与我们提到的相同,即通过创建一个自动下单的 EA,在突破或跌破区间时考虑开仓交易。以下是本交易系统编码时的不同之处:

只有当 EA 首先发现上一个收盘价低于上一个上限区间的上一个收盘价,然后发现上一个收盘价高于上一个上限区间时,才会下达买入指令。

      if(prevLastClose<prevUpperValue && lastClose>upperValue)
        {
         double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         double slVal = upperValue - slPips*_Point;
         double tpVal = upperValue + tpPips*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }

只有当 EA 首先发现上一个收盘价高于上一个下限区间的上一个收盘价,然后发现上一个收盘价低于上一个下限区间的上一个收盘价时,才会发出卖出指令。

      if(prevLastClose>prevLowerValue && lastClose<lowerValue)
        {
         double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         double slVal = lowerValue + slPips*_Point;
         double tpVal = lowerValue - tpPips*_Point;
         trade.Sell(lotSize,_Symbol,bid,upperValue,tpVal);
        }

因此,以下是基于区间反弹策略创建交易系统的完整代码:

//+------------------------------------------------------------------+
//|                                      Keltner_Trading_System2.mq5 |
//+------------------------------------------------------------------+
#include <trade/trade.mqh>
input int      maPeriod            = 10;           // Moving Average Period
input double   multiInp             = 2.0;          // Channel multiplier
input bool     isAtr                 = false;        // ATR
input ENUM_MA_METHOD     maMode       = MODE_EMA;     // Moving Average Mode
input ENUM_APPLIED_PRICE priceType = PRICE_TYPICAL;// Price Type
input double      lotSize=1;
input double slPips = 150;
input double tpPips = 500; 
int keltner;
int barsTotal;
CTrade trade;
int OnInit()
  {
   barsTotal=iBars(_Symbol,PERIOD_CURRENT);
   keltner = iCustom(_Symbol,PERIOD_CURRENT,"Custom_Keltner_Channel",maPeriod,multiInp, isAtr, maMode, priceType);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }
//+------------------------------------------------------------------+
void OnTick()
  {
   int bars=iBars(_Symbol,PERIOD_CURRENT);
   if(barsTotal < bars)
     {
      barsTotal=bars;
      double upper[], middle[], lower[];
      CopyBuffer(keltner,0,0,3,upper);
      CopyBuffer(keltner,1,0,3,middle);
      CopyBuffer(keltner,2,0,3,lower);
      ArraySetAsSeries(upper,true);
      ArraySetAsSeries(middle,true);
      ArraySetAsSeries(lower,true);
      double prevUpperValue = NormalizeDouble(upper[2], 5);
      double prevMiddleValue = NormalizeDouble(middle[2], 5);
      double prevLowerValue = NormalizeDouble(lower[2], 5);
      double upperValue = NormalizeDouble(upper[1], 5);
      double middleValue = NormalizeDouble(middle[1], 5);
      double lowerValue = NormalizeDouble(lower[1], 5);
      double lastClose=iClose(_Symbol,PERIOD_CURRENT,1);
      double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,2);
      if(prevLastClose<prevUpperValue && lastClose>upperValue)
        {
         double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         double slVal = upperValue - slPips*_Point;
         double tpVal = upperValue + tpPips*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }
      if(prevLastClose>prevLowerValue && lastClose<lowerValue)
        {
         double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         double slVal = lowerValue + slPips*_Point;
         double tpVal = lowerValue - tpPips*_Point;
         trade.Sell(lotSize,_Symbol,bid,upperValue,tpVal);
        }
     }
  }
//+------------------------------------------------------------------+

现在,我们将在 2023 年 1 月 1 日至 2023 年 12 月 31 日期间,使用 1H 时间框架和默认输入设置,对黄金 (XAUUSD)、(EURUSD) 和 (GBPUSD) 的两个交易策略的 EA 进行测试、

我们将重点关注以下关键衡量指标,对两者进行比较:

  • 净利润:计算方法是从毛利润中减去毛损失。最高值就是最好的。
  • 相对余额回撤:这是账户在交易过程中的最大损失。最低的就是最好的。
  • 利润因子: 是毛利润与毛损失的比率。最高值就是最好的。
  • 预期回报: 是一笔交易的平均利润或亏损。最高值就是最好的。
  • 恢复系数:它衡量的是经过测试的策略在遭受损失后的恢复能力。最高的就是最好的。
  • 夏普比率:它通过比较收益率和无风险收益率来确定测试交易系统的风险和稳定性。夏普比率最高的就是最好的。


策略一:区间反弹测试:

XAUUSD 测试

我们可以发现 (XAUUSD) 的测试结果如下:

图形

回溯测试

回溯测试2

从前面的 XAUUSD 测试结果中我们可以看到,测试中的重要数据如下所示

  • 净利润:11918.60 美元。
  • 相对余额回撤:3.67%.
  • 利润因子:1.36.
  • 预期回报:75.91.
  • 恢复系数:2.85。
  • 夏普比率:4.06.

EURUSD 测试

在 EURUSD 上的测试结果如下:

图形

回溯测试

回溯测试2

从之前的 EURUSD 测试结果中我们可以看到,我们在测试中的重要数据如下所示

  • 净利润:2221.20 美元。
  • 相对余额回撤:2.86%.
  • 利润因子:1.11.
  • 预期回报:13.63.
  • 恢复系数:0.68.
  • 夏普比率:1.09.

GBPUSD 测试

我们可以发现(GBPUSD)的测试结果如下:

图形

回溯测试

回溯测试2

从之前的 GBPUSD 测试结果中我们可以看到,测试中的重要数据如下所示

  • 净利润:-1389.40 美元。
  • 相对余额回撤:4.56%.
  • 利润因子:0.94.
  • 预期回报:-8.91。
  • 恢复系数:-0.28。
  • 夏普比率:-0.78。


策略二:波段突破测试:

XAUUSD 测试

我们可以找到如下的(XAUUSD) 测试结果图表:

图形

回溯测试

回溯测试2

从前面的 XAUUSD 测试结果中我们可以看到,测试中的重要数据如下所示

  • 净利润: -11783 美元。
  • 相对余额回撤:12.89%.
  • 利润因子:0.56.
  • 预期回报:-96.58。
  • 恢复系数:-0.83。
  • 夏普比率:-5.00。

EURUSD 测试

我们可以找到如下的 (EURUSD) 测试结果图:

图形

回溯测试

回溯测试2

从之前的 EURUSD 测试结果中我们可以看到,我们在测试中的重要数据如下所示

  • 净利润:-1358.30 美元。
  • 相对余额回撤:6.53%.
  • 利润因子:0.94.
  • 预期回报:-8.54。
  • 恢复系数:-0.20。
  • 夏普比率:-0.59。

GBPUSD 测试

我们可以找到如下的 (GBPUSD) 测试结果图表:

图形

回溯测试

回溯测试2

从之前的 GBPUSD 测试结果中我们可以看到,测试中的重要数据如下所示

  • 净利润:-3930.60 美元。
  • 相对余额回撤:5.06%.
  • 利润因子:0.84.
  • 预期回报:-25.69。
  • 恢复系数:-0.75。
  • 夏普比率:-2.05。

根据我们在前面的策略中看到的对多个工具的测试结果,我们可以在一个表格中找到所有结果,方便比较,如下表所示:

所有结果2

根据上表,我们可以找到与所测试的策略和时间框架相同的最佳参数:
  • 净利润:  在 XAUUSD 资产上测试时,区间反弹策略显示了最佳较高数字(11918.60 美元)。
  • 相对余额回撤: 在对 EURUSD 资产进行测试时,区间反弹策略显示了最佳下限数字(2.86 %)。
  • 利润因子: 在对 XAUUSD 资产进行测试时,区间反弹策略显示了最佳较高数字(1.36)。
  • 预期回报: 在 XAUUSD 资产上测试时,区间反弹策略显示了较高的数字(75.91)。
  • 恢复因子: 在对 XAUUSD 资产进行测试时,区间反弹策略显示了较高的数字(2.85)。
  • 夏普比率: 在对 XAUUSD 资产进行测试时,区间反弹策略显示了较高的数字(4.06)。

我们可以看到,最佳策略是 XAUUSD 的区间反弹策略,如果您想看到更好的结果和设置,也可以根据自己的偏好优化策略并重新测试,以满足您的交易目标。


结论

作为交易者,拥有一个可靠的交易系统是非常重要的,如果这个交易系统包括并衡量波动性等每一个重要方面,就会增加其可靠性。

在本文中,我们试图提供基于肯特纳通道指标的简单策略的测试结果,让您了解在详细识别指标、如何根据其主要概念使用指标以及如何计算指标之后,如何纳入或使用基于这一重要概念的交易系统。

我们提到了以下策略:

  • 区间反弹:我们将在反弹超过下限时发出买入指令,在反弹低于上限时发出卖出指令。
  • 区间突破:当突破上限时,我们发出买入指令;当突破下限时,我们发出卖出指令。

然后,我们对它们进行了测试,并根据每种策略的结果,确定了许多金融工具(XAUUSD、EURUSD 和 GBPUSD)的重要数据,从而知道哪种策略更好。

重要的是要明白,这些提到的策略可能需要更多的优化和努力,才能为我们的交易系统带来更好的结果,因为正如我们前面提到的,这篇文章的主要目的是通过分享一些交易理念,打开我们的思路,建立或开发更好的交易系统。

我希望这篇文章对你的交易和编码学习之旅有所帮助,让你获得更好、更可靠、更有效的结果。如果你觉得这篇文章有用,并且需要阅读更多关于基于不同策略和不同技术指标(尤其是最流行的技术指标)构建交易系统的文章,你可以通过查看我的出版物页面阅读我以前的文章,在这方面可以找到许多关于最流行技术指标的文章,我希望这些文章对你有用。

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

使用优化算法即时配置 EA 参数 使用优化算法即时配置 EA 参数
文章讨论了使用优化算法即时查找最佳 EA 参数,以及交易操作和 EA 逻辑虚拟化的实际问题。这篇文章可作为在 EA 中实现优化算法的指导。
神经网络变得简单(第 71 部分):目标条件预测编码(GCPC) 神经网络变得简单(第 71 部分):目标条件预测编码(GCPC)
在之前的文章中,我们讨论了决策转换器方法,以及从其衍生的若干种算法。我们测验了不同的目标设定方法。在测验期间,我们依据各种设定目标的方式进行操作。然而,该模型早期研究时验算过的轨迹,始终处于我们的关注范围之外。在这篇文章中。我想向您介绍一种填补此空白的方法。
种群优化算法:细菌觅食优化 — 遗传算法(BFO-GA) 种群优化算法:细菌觅食优化 — 遗传算法(BFO-GA)
本文释义了一种解决优化问题的新方式,即把细菌觅食优化(BFO)算法和遗传算法(GA)中所用的技术结合到混合型 BFO-GA 算法当中。它用细菌群落来全局搜索最优解,并用遗传运算器来优调局部最优值。与原始的 BFO 不同,细菌现在可以突变,并继承基因。
在 MQL5 中实现广义赫斯特指数和方差比检验 在 MQL5 中实现广义赫斯特指数和方差比检验
在本文中,我们将研究如何利用广义赫斯特指数(Generalized Hurst Exponent)和方差比检验(Variance Ratio Test)来分析 MQL5 中价格序列的行为。