English Русский Español Deutsch 日本語 Português
preview
如何构建和优化基于波动率的交易系统(Chaikin volatility-CHV)

如何构建和优化基于波动率的交易系统(Chaikin volatility-CHV)

MetaTrader 5交易 | 6 十一月 2024, 09:58
834 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

概述

在交易领域,波动率因素非常重要,这是众所周知的。有许多工具可以用来衡量这一因素,然后根据我们的衡量结果,如果我们将波动率作为交易系统的一部分来考虑,就可以做出更好的交易决策。

在本文中,我们将探讨这些衡量波动率的技术指标之一——蔡金波动率(Chaikin Volatility,简称CHV)。在阅读本文的过程中,您将了解蔡金波动率的重要性、定义、如何计算,以及我们如何利用它来获得更好的交易结果。我们将基于简单的交易策略(CHV穿越和CHV与MA穿越)来使用这一指标。

我们的方法是理解如何将蔡金波动率指标作为一种波动率工具,以获得在波动率测量方面可靠的交易结果,并将其作为我们交易系统的一部分。因此,我们将学习如何基于波动率策略创建交易系统,并给出一个例子,说明如何通过添加简单策略或结合其他工具来优化我们的交易系统,以获得比未优化前更好的结果。我们还将学习如何创建自己的CHV指标,将其作为我们交易系统的一部分。

众所周知,任何交易系统在使用于实际交易之前,都需要在不同的环境中进行测试,以确保其可用性和适用性,因为没有一种策略适合所有交易者。因此,我们将基于上述两种交易策略对我们的交易系统进行一些简单的测试,并鼓励您使用与本文不同的方向自行测试,找出如何改进您的系统并获得比我更好的结果,甚至发现这个工具根本不适合或不适用于您的交易系统。

我们将在以下主题中涵盖所有这些内容:

如果您正为学习交易或一般编程而编写代码,那么了解这些信息对您亲自实践和编写代码至关重要。因此,我鼓励您将所学内容编写成代码,因为这有助于提升您的编程技能和知识。

免责声明:所有信息“仅按原样”提供,且仅用于教学目的,并非为交易目的或建议而准备。该信息不保证任何类型的结果。如果您选择在任何交易账户上使用这些材料,必须自行承担风险,并且您将是唯一的责任人。


蔡金波动率

在这一部分,我们将详细介绍蔡金波动率指标。蔡金波动率由马克·蔡金(Marc Chaikin)创建,他创造了许多以其名字命名的技术指标。蔡金波动率指标用于衡量金融市场的波动性,并有助于预测潜在的市场反转机会。它可用于确定一段时间内高低价格之间的价格范围,以衡量潜在的市场波动或向任一方向的变动。在计算蔡金波动率时,需要注意的是,它并不会像其他指标那样考虑价格缺口。非常重要的一点是,波动率的增加可能意味着高风险或高回报,反之亦然。

蔡金波动率指标可以记录高值或低值,高值的上升意味着价格变动非常快,而低值则意味着价格保持稳定,且基础资产的波动性不大。我认为非常重要的一点是,波动率不仅可以在趋势市场中记录,也可以在非趋势市场中记录,因为我们衡量的是波动率,而非价格的趋势或方向。当使用其他技术工具对生成的信号进行确认时,会获得更好的结果。正如我们所提到的,我们将尝试在做决策时结合使用移动平均线技术指标来确定市场方向,并尽可能实行趋势交易。

正如我们之前提到的,我们可以使用蔡金波动率指标来预测市场反转,因为有时当指标记录到相对较高的值时,这可以用来预测市场反转以及潜在的顶部或底部。现在,我们需要了解蔡金波动率指标的计算方法,以加深对指标背后主要概念的理解。

H-L (i) = HIGH (i) - LOW (i)

H-L (i - 10) = HIGH (i - 10) - LOW (i - 10)

CHV = (EMA (H-L (i), 10) - EMA (H-L (i - 10), 10)) / EMA (H-L (i - 10), 10) * 100

其中:

  • HIGH (i) - 当前K线图的最高价格。
  • LOW (i) - 当前K线图的最低价格。
  • HIGH (i - 10) - K线图从当前1到10位置的最高价格。
  • LOW (i - 10) - K线图从当前1到10位置的最低价格。
  • H-L (i) - 当前K线图的最高和最低价格之间的差额。
  • H-L (i - 10) - 前面十个时间单位最高和最低价格之间的差额。
  • EMA - 指数移动平均线。


自定义蔡金波动率指标

在这部分,我们将学习如何使用MQL5编写一个自定义的蔡金波动率(CHV)指标,这将非常实用,因为我们可以根据自己的目标对指标进行自定义。以下是编写自定义蔡金波动率(CHV)指标的步骤:

使用预处理指令#include,确保在程序和计算中能够访问移动平均线相关文件。

#include <MovingAverages.mqh>

使用预处理指令 #property,指定与以下指标值相同的其他参数:

  • 描述: 为MQL5程序设置简短文本。
  • indicator_separate_window:在单独的窗口中设置指标的位置。
  • indicator_buffers:设置指标计算的缓存区数量。
  • indicator_plots:在指标中设置图形系列的数量。
  • indicator_type1: 指定绘制的图形类型,即ENUM_DRAW_type的值N代表图形系列的数量;数量可以从1开始。
  • indicator_color1:为了指定显示线N的颜色,N代表图形系列的数量;数量可以从1开始。
  • indicator_width1:为了指定指标的线条粗细,N代表图形系列的数量;数量可以从1开始。
#property description "Chaikin Volatility"
#property indicator_separate_window
#property indicator_buffers 3

#property indicator_plots   1
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  MediumBlue
#property indicator_width1  3

使用enum关键字为移动平均平滑模式定义一组数据:

enum smoothMode
  {
   SMA=0,// Simple MA
   EMA=1 // Exponential MA
  };

使用input关键字输入指标设置:

input int          smoothPeriodInp=10;  // Smoothing Period
input int          chvPeriodInp=10;     // Chaikin Volatility Period
input smoothMode   InpSmoothType=EMA;   // Smoothing Mode

声明三个chv、hl和shl缓存区数组:

double             chvBuffer[];
double             hlBuffer[];
double             shlBuffer[];

为平滑期和CHV期声明两个全局变量:

int                smoothPeriod,chvPeriod;

在OnInit()部分,我们会检查并指定输入变量。

移动平均线名称:声明maName后,程序会检查输入是否为SMA,是的情况下名称记录为SMA(简单移动平均线),或者输入是否为EMA,是的情况下名称记录为EMA(指数移动平均)。

   string maName;
   if(InpSmoothType==SMA)
      maName="SMA";
   else
      maName="EMA";

平滑周期:程序将检查平滑周期,如果小于或等于零,则将该值赋值为默认值10,并打印一条消息。如果与上述不同,即值大于10,则按输入值赋值。

   if(smoothPeriodInp<=0)
     {
      smoothPeriod=10;
      printf("Incorrect value for Smoothing Period input = %d. Default value = %d.",smoothPeriodInp,smoothPeriod);
     }
   else smoothPeriod=smoothPeriodInp;

CHV周期:程序将检查CHV周期,如果小于或等于零,则将该值赋值为默认值10,并打印一条消息。如果与上述不同,即值大于10,则按输入值赋值。

   if(chvPeriodInp<=0)
     {
      chvPeriod=10;
      printf("Incorrect value for Chaikin Volatility Period input = %d. Default value = %d.",chvPeriodInp,chvPeriod);
     }
   else chvPeriod=chvPeriodInp;

使用SetIndexBuffer关键字定义声明缓存区,并返回一个布尔值。其参数如下:

  • 索引:指标缓存区编号,从0开始,小于#property中indicator_buffers标识符的值。
  • buffer[]:声明来自指定自定义指标中的数组。
  • data_type:指定存储的内容类型。chvBuffer的默认值是INDICATOR_DATA,而我们将指定INDICATOR_CALCULATIONS用于中间计算,而非绘图。
   SetIndexBuffer(0,chvBuffer);
   SetIndexBuffer(1,hlBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(2,shlBuffer,INDICATOR_CALCULATIONS);

指定图形设置:

   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,smoothPeriod+chvPeriod-1);
   PlotIndexSetString(0,PLOT_LABEL,"CHV("+string(smoothPeriod)+","+maName+")");
   IndicatorSetString(INDICATOR_SHORTNAME,"Chaikin Volatility("+string(smoothPeriod)+","+maName+")");
   IndicatorSetInteger(INDICATOR_DIGITS,1);

在OnCalculate部分,我们将声明三个整数变量,如下所示:

   int    i,pos,posCHV;

定义posCHV变量与2-chvPeriod和smoothPeriod的结果相同:

   posCHV=chvPeriod+smoothPeriod-2;

为了检查rateTotal是否小于posCHV的值,我们需要一个为0的返回值:

   if(rates_total<posCHV)
      return(0);

在检查prev_calculated是否小于1后定义pos值,如果prev_calculated小于1,pos值将为零,反之,将prev_compulated是-1的结果赋值给pos。

   if(prev_calculated<1)
      pos=0;
   else pos=prev_calculated-1;

定义hlBuffer[i]

   for(i=pos;i<rates_total && !IsStopped();i++)
     {
      hlBuffer[i]=High[i]-Low[i];
     }

定义平滑dhl(shl)缓存区:

   if(pos<smoothPeriod-1)
     {
      pos=smoothPeriod-1;
      for(i=0;i<pos;i++)
        {
         shlBuffer[i]=0.0;
        }
     }

通过使用SimpleMAOnBuffer和ExponentialMAOnBuffer函数来定义简单移动平均线(MA)和指数移动平均线(EMA):

   if(InpSmoothType==SMA)
     {
      SimpleMAOnBuffer(rates_total,prev_calculated,0,smoothPeriod,hlBuffer,shlBuffer);
     }
   else
      ExponentialMAOnBuffer(rates_total,prev_calculated,0,smoothPeriod,hlBuffer,shlBuffer);

在检查pos是否小于posCHV之后,更新pos的值,

   if(pos<posCHV)
     {
      pos=posCHV;
     }

定义CHV缓存区:

   for(i=pos;i<rates_total && !IsStopped();i++)
     {
      if(shlBuffer[i-chvPeriod]!=0.0)
         chvBuffer[i]=100.0*(shlBuffer[i]-shlBuffer[i-chvPeriod])/shlBuffer[i-chvPeriod];
      else
         chvBuffer[i]=0.0;
     }

返回rates_total:

return(rates_total);

因此,以下是一个块中的完整代码:

//+------------------------------------------------------------------+
//|                                           Chaikin Volatility.mq5 |
//+------------------------------------------------------------------+
#include <MovingAverages.mqh>
#property description "Chaikin Volatility"
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   1
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  MediumBlue
#property indicator_width1  3
enum smoothMode
  {
   SMA=0,// Simple MA
   EMA=1 // Exponential MA
  };
input int          smoothPeriodInp=10;  // Smoothing Period
input int          chvPeriodInp=10;     // Chaikin Volatility Period
input smoothMode InpSmoothType=EMA;   // Smoothing Mode
double             chvBuffer[];
double             hlBuffer[];
double             shlBuffer[];
int                smoothPeriod,chvPeriod;
void OnInit()
  {
   string maName;
   if(InpSmoothType==SMA)
      maName="SMA";
   else
      maName="EMA";
   if(smoothPeriodInp<=0)
     {
      smoothPeriod=10;
      printf("Incorrect value for Smoothing Period input = %d. Default value = %d.",smoothPeriodInp,smoothPeriod);
     }
   else
      smoothPeriod=smoothPeriodInp;
   if(chvPeriodInp<=0)
     {
      chvPeriod=10;
      printf("Incorrect value for Chaikin Volatility Period input = %d. Default value = %d.",chvPeriodInp,chvPeriod);
     }
   else
      chvPeriod=chvPeriodInp;
   SetIndexBuffer(0,chvBuffer);
   SetIndexBuffer(1,hlBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(2,shlBuffer,INDICATOR_CALCULATIONS);
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,smoothPeriod+chvPeriod-1);
   PlotIndexSetString(0,PLOT_LABEL,"CHV("+string(smoothPeriod)+","+maName+")");
   IndicatorSetString(INDICATOR_SHORTNAME,"Chaikin Volatility("+string(smoothPeriod)+","+maName+")");
   IndicatorSetInteger(INDICATOR_DIGITS,1);
  }
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 &TickVolume[],
                const long &Volume[],
                const int &Spread[])
  {
   int    i,pos,posCHV;
   posCHV=chvPeriod+smoothPeriod-2;
   if(rates_total<posCHV)
      return(0);
   if(prev_calculated<1)
      pos=0;
   else
      pos=prev_calculated-1;
   for(i=pos;i<rates_total && !IsStopped();i++)
     {
      hlBuffer[i]=High[i]-Low[i];
     }
   if(pos<smoothPeriod-1)
     {
      pos=smoothPeriod-1;
      for(i=0;i<pos;i++)
        {
         shlBuffer[i]=0.0;
        }
     }
   if(InpSmoothType==SMA)
     {
      SimpleMAOnBuffer(rates_total,prev_calculated,0,smoothPeriod,hlBuffer,shlBuffer);
     }
   else
      ExponentialMAOnBuffer(rates_total,prev_calculated,0,smoothPeriod,hlBuffer,shlBuffer);
   if(pos<posCHV)
     {
      pos=posCHV;
     }
   for(i=pos;i<rates_total && !IsStopped();i++)
     {
      if(shlBuffer[i-chvPeriod]!=0.0)
         chvBuffer[i]=100.0*(shlBuffer[i]-shlBuffer[i-chvPeriod])/shlBuffer[i-chvPeriod];
      else
         chvBuffer[i]=0.0;
     }
   return(rates_total);
  }

编译此代码后,如果我们将其附加到图表中,能够找到与下图相同的指标:

CHVInda

从之前的图表中我们可以看出,图表下方窗口中的指标以直方图的形式展示,其值在0水平线上下波动。


蔡金波动率交易策略

在这一部分,我们将了解如何利用蔡金波动率指标(Chaikin Volatility indicator)为我们的交易服务,并使其成为我们交易系统中考虑波动性的一个组成部分。我们将仅使用CHV指标,并根据其值来做出交易决策。之后,我们将对这些决策进行测试,查看其是否能够带来盈利。

我们还将采用另一种策略,并结合另一个指标来根据移动平均线的方向过滤交易决策,以此作为策略的优化手段。我们将测试这种结合策略是否盈利,或者是否比仅使用CHV表现得更好。

以下是这些策略:

CHV穿越:

这个策略能够生成买卖信号并自动下单。当CHV的值高于0时,该EA将进行买入操作。当CHV的值低于0时,该EA将进行卖出操作。

简单地说,

CHV value > 0 => 买入

CHV value < 0 => 卖出

CHV和MA穿越:

该策略将基于CHV值与0水平线间的穿越情况,并结合移动平均线的方向来生成信号并进行买入或卖出操作。如果CHV值高于0,且移动平均线位于收盘价之下,那么将进行买入操作。另一方面,如果CHV值低于0,且移动平均线位于收盘价之上,那么将进行卖出操作。

简单地说,

CHV > 0 且收盘价 > MA => 买入

CHV < 0 且收盘价 < MA => 卖出


蔡金波动率交易系统

在这一部分,我们将基于前面提到的策略,使用MQL5创建一个交易系统,并测试每一个策略,了解如何优化这些策略并获得更好的结果。首先,我们将创建一个简单的EA,作为我们两种交易策略的基础。这个EA将在图表上以注释的形式显示CHV的值。以下是实现这一目标的步骤:

基于指标声明交易系统的输入参数:

enum SmoothMethod
  {
   SMA=0,// Simple MA
   EMA=1 // Exponential MA
  };
input int          InpSmoothPeriod=10;  // Smoothing period
input int          InpCHVPeriod=10;     // Chaikin Volatility period
input SmoothMethod InpSmoothType=EMA;   // Smoothing method

为CHV定义一个整数变量:

int chv;

在EA的OnInit()部分,我们将定义chv变量,并将其设置为iCustom函数的返回值,以获取CHV指标的句柄。iCustom函数的参数如下:

  • symbo:指定交易品种名称,我们将(_Symbol)应用于当前交易品种。
  • period:指定时间周期,我们将PERIOD_CURRENT应用于当前时间周期。
  • name:指定指标的名称。
  • 输入列表规范(smoothPeriodInp、chvPeriodInp和smoothTypeInp)。
chv = iCustom(_Symbol,PERIOD_CURRENT,"Custom_CHV",smoothPeriodInp,chvPeriodInp, smoothTypeInp);

在OnDeinit()部分,我们会在删除EA时打印消息:

Print("EA is removed");

在OnTick()部分,声明chvInd数组:

double chvInd[];

使用CopyBuffer关键字获取CHV指标指定的缓存区数据。其参数如下:

  • indicator_handle:指定为chv的指标句柄 
  • buffer_num:指定指标缓存区编号为0
  • start_pos:指定起始位置为0
  • count:指定要复制的金额为3
  • buffer[]:指定要复制的目标数组chvInd
CopyBuffer(chv,0,0,3,chvInd);

使用ArraySetAsSeries关键字将AS_SERIES标识设置为所选数组,其参数为:

  • array[]:通过引用指定数组chvInd  
  • flag:设置为true,表示索引的反向顺序 
ArraySetAsSeries(chvInd,true);

声明并定义chvVal以返回指标的当前值:

double chvVal = NormalizeDouble(chvInd[0], 1);

在图表上注释当前值:

Comment("CHV value = ",chvVal);

因此,我们可以在一个模块中看到完整的代码,如下所示:

//+------------------------------------------------------------------+
//|                                                       chvVal.mq5 |
//+------------------------------------------------------------------+
enum SmoothMethod
  {
   SMA=0,// Simple MA
   EMA=1 // Exponential MA
  };
input int          smoothPeriodInp=10;  // Smoothing period
input int          chvPeriodInp=10;     // Chaikin Volatility period
input SmoothMethod smoothTypeInp=EMA;   // Smoothing method
int chv;
int OnInit()
  {
   chv = iCustom(_Symbol,PERIOD_CURRENT,"Custom_CHV",smoothPeriodInp,chvPeriodInp, smoothTypeInp);
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }
void OnTick()
  {
    double chvInd[];
    CopyBuffer(chv,0,0,3,chvInd);
    ArraySetAsSeries(chvInd,true);
    double chvVal = NormalizeDouble(chvInd[0], 1);
    Comment("CHV value = ",chvVal);
  }
//+------------------------------------------------------------------+

编译此代码后,我们可以找到当前值的位置,如下图所示:

chvVal

为了确保图表上打印值是正确的,我们可以将指标插入到同一个图表中,并比较打印值与指标显示值之间是否存在差异。因此,在继续构建我们的交易系统之前,使用下列图表验证一切是否正确:

chvValSame

正如我们所见,当前CHV的打印值与所附的指标值相同。现在,让我们为第一种策略(CHV Crossover)创建我们的交易系统。

CHV穿越:

根据CHV穿越策略的策略,以下是它的完整代码:

//+------------------------------------------------------------------+
//|                                                 chvCrossover.mq5 |
//+------------------------------------------------------------------+
#include <trade/trade.mqh>
enum smoothMode
  {
   SMA=0,// Simple MA
   EMA=1 // Exponential MA
  };
input int          smoothPeriodInp=10;  // Smoothing period
input int          chvPeriodInp=10;     // Chaikin Volatility period
input smoothMode   smoothTypeInp=EMA;   // Smoothing Mode
input double       lotSize=1;
input double slPips = 300;
input double tpPips = 600;

int chv;
int barsTotal;
CTrade trade;
int OnInit()
  {
   barsTotal=iBars(_Symbol,PERIOD_CURRENT);
   chv = iCustom(_Symbol,PERIOD_CURRENT,"Custom_CHV",smoothPeriodInp,chvPeriodInp, smoothTypeInp, lotSize, slPips, tpPips);
   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 chvInd[];
      CopyBuffer(chv,0,0,3,chvInd);
      ArraySetAsSeries(chvInd,true);
      double chvVal = NormalizeDouble(chvInd[0], 1);
      double chvValPrev = NormalizeDouble(chvInd[1], 1);
      if(chvVal>0)
        {
         double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         double slVal = ask - slPips*_Point;
         double tpVal = ask + tpPips*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }
      if(chvVal<0)
        {
         double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         double slVal = bid + slPips*_Point;
         double tpVal = bid - tpPips*_Point;
         trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
        }
     }
  }
//+------------------------------------------------------------------+

该代码的差异如下:

包含交易功能的交易文件。

#include <trade/trade.mqh>

声明用户可编辑的lotSize、slPips和tpPips三个输入。

input double      lotSize=1;
input double slPips = 300;
input double tpPips = 600;

声明一个名为 barsTotal 的整数变量,用于表示每个新K线的代码。

int barsTotal;

声明交易对象。

CTrade trade;

在OnInit() 部分,我们将定义变量barsTotal。

barsTotal=iBars(_Symbol,PERIOD_CURRENT);

将lotSize、slPips和tpPips这三个输入添加到iCustom关键字中。

chv = iCustom(_Symbol,PERIOD_CURRENT,"Custom_CHV",smoothPeriodInp,chvPeriodInp, smoothTypeInp, lotSize, slPips, tpPips);

声明和定义整型bar变量。

int bars=iBars(_Symbol,PERIOD_CURRENT);

检查barsTotal是否小于bars,之后我们需要执行以下操作:

用bars的值更新barsTotal的值。

barsTotal=bars;

声明chvInd数组。

double chvInd[];

通过使用CopyBuffer关键字检索CHV指标的指定缓存区数据。

CopyBuffer(chv,0,0,3,chvInd);

使用ArraySetAsSeries关键字在所选数组上设置AS_SERIES标识,如下所示。

ArraySetAsSeries(chvInd,true);

声明和定义chvVal,用于返回指标当前值和先前值。

      double chvVal = NormalizeDouble(chvInd[0], 1);
      double chvValPrev = NormalizeDouble(chvInd[1], 1);

在设置买入条件时,我们需要EA在检查CHV值并发现其大于零时实行自动买入。

      if(chvVal>0)
        {
         double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         double slVal = ask - slPips*_Point;
         double tpVal = ask + tpPips*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }

设置卖出条件时,我们需要EA在检查CHV值并发现其小于零时实行自动卖出。

      if(chvVal<0)
        {
         double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         double slVal = bid + slPips*_Point;
         double tpVal = bid - tpPips*_Point;
         trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
        }

在编译并运行代码后,我们可以观察到进行买入操作可能与以下情况相同:

chvCrossover_buySignal

我们发现进行卖出操作也可以按照以下方式:

chvCrossover_sellSignal

正如我们所知,没有测试和获得盈利的结果,就谈不上好的策略。我们将重点关注以下关键指标:

  • 净利润: 它是通过从毛利润中减去毛亏损计算得出。该值越高越好。
  • 相对余额最大回撤: 它指账户在交易过程中的最大损失。. 该值越低越好。
  • 盈利系数:它指毛利润与毛亏损的比率。该值越高越好。
  • 预期收益:它指交易的平均利润或损失。该值越高越好。
  • 恢复系数:它用于衡量在经历投资损失后测试策略的恢复能力。该值越高越好。
  • 夏普比率:它通过比较回报与无风险回报来确定测试交易系统的风险和稳定性。夏普比率越高越好。

下表显示了测试结果。我们在一年内(2023年1月1日至2023年12月31日)对欧元/美元进行测试,时间周期为1小时。

chvCrossover_result

chvCrossover_result2

chvCrossover_result1

根据测试结果,我们可以找到以下测试编号的对应值:

  • 净利润:-35936.34 USD。
  • 相对余额最大回撤:48.12%。
  • 盈利系数:0.94。
  • 预期收益:-6.03。
  • 恢复系数:-0.62。
  • 夏普比率:-1.22。

根据前面的结果,我们可以看出这些结果并不理想,也没有带来盈利,因此我们需要对其进行优化,做出改进以获得更好的结果。T以下是通过策略来实现这一目标的步骤。

CHV和MA穿越:

根据这一策略,我们将使用CHV指标,并结合另一个技术指标——移动平均线,根据移动平均线的方向过滤交易。以下是实现这一策略的完整代码:

//+------------------------------------------------------------------+
//|                                             chv_MA_Crossover.mq5 |
//+------------------------------------------------------------------+
#include <trade/trade.mqh>
enum smoothMode
  {
   SMA=0,// Simple MA
   EMA=1 // Exponential MA
  };
input int          InpSmoothPeriod=10;  // Smoothing period
input int          InpCHVPeriod=10;     // Chaikin Volatility period
input smoothMode smoothTypeInp=EMA;   // Smoothing Mode
input int InpMAPeriod=10; //MA Period
input ENUM_MA_METHOD InpMAMode=MODE_EMA; // MA Mode
input double      lotSize=1;
input double slPips = 300;
input double tpPips = 600;
int chv;
int ma;
int barsTotal;
CTrade trade;
int OnInit()
  {
   barsTotal=iBars(_Symbol,PERIOD_CURRENT);
   chv = iCustom(_Symbol,PERIOD_CURRENT,"Custom_CHV",InpSmoothPeriod,InpCHVPeriod, smoothTypeInp, lotSize, slPips, tpPips);
   ma=iMA(_Symbol,PERIOD_CURRENT, InpMAPeriod, 0, InpMAMode, PRICE_CLOSE);
   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 chvInd[];
      double maInd[];
      CopyBuffer(chv,0,0,3,chvInd);
      ArraySetAsSeries(chvInd,true);
      CopyBuffer(ma,0,0,3,maInd);
      ArraySetAsSeries(maInd,true);
      double chvVal = NormalizeDouble(chvInd[0], 1);
      double chvValPrev = NormalizeDouble(chvInd[1], 1);
      double maVal = NormalizeDouble(maInd[0], 5);
      double maValPrev = NormalizeDouble(maInd[1], 5);
      double lastClose=iClose(_Symbol,PERIOD_CURRENT,1);
      double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,2);

      if(prevLastClose<maValPrev && lastClose>maVal && chvVal>0)
        {
         double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         double slVal = ask - slPips*_Point;
         double tpVal = ask + tpPips*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }
      if(prevLastClose>maValPrev && lastClose<maVal && chvVal<0)
        {
         double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         double slVal = bid + slPips*_Point;
         double tpVal = bid - tpPips*_Point;
         trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
        }
     }
  }
//+------------------------------------------------------------------+

该代码的差异如下:

为ma声明一个整型变量。

int ma;

在OnInit() 不分,我们将使用iMA关键字定义ma,以返回移动平均线的句柄。其参数如下:

  • symbol:指定交易品种名称,用_symbol表示当前交易品种。 
  • period:指定时间范围,用period_CURRENT表示当前时间范围。
  • ma_period:指定移动平均周期,用MAperiod的输入值表示。
  • ma_shift:指定是否需要水平移动平均线。
  • ma_method:指定移动平均线的平滑模式,它是MA模式的输入。 
  • applied_price:指定用于计算的价格类型,用收盘价表示。
ma=iMA(_Symbol,PERIOD_CURRENT, InpMAPeriod, 0, InpMAMode, PRICE_CLOSE);

检查是否有新K线之后,添加以下内容:

声明maInd[] 数组。

double maInd[];

使用CopyBuffer关键字获取移动平均线指标的指定缓存区数据,并使用ArraySetAsSeries关键字将AS_SERIES标识设置为所选数组。

      CopyBuffer(ma,0,0,3,maInd);
      ArraySetAsSeries(maInd,true);

定义下列值:

当前移动平均值、前一个移动平均值,最近一次收盘价,以及前一次收盘价。

      double maVal = NormalizeDouble(maInd[0], 5);
      double maValPrev = NormalizeDouble(maInd[1], 5);
      double lastClose=iClose(_Symbol,PERIOD_CURRENT,1);
      double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,2);

在设置买入条件时,如果上一个收盘价低于前一个移动平均值,并且最后一次收盘价大于当前移动平均值,且同时Chaikin当前值大于零,我们就需要EA执行买入操作。

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

在设置卖出条件时,如果前一次收盘价高于前一个移动平均值,并且最后一次收盘价小于当前移动平均值,且同时Chaikin当前值小于零,我们需要EA执行卖出操作。

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

在编译并运行代码后,我们可以观察到进行买入操作可能与以下情况相同:

chvMACrossover_buySignal

我们发现进行卖出操作也可以按照以下方式:

chvMACrossover_sellSignal

经过一年(2023年1月1日至2023年12月31日)对欧元/美元进行时间周期为1小时的测试,结果如下。

chvMACrossover_result

chvMACrossover_result2

chvMACrossover_result1

根据测试结果,我们可以找到以下测试编号的对应值:

  • 净利润:20817.39 USD。
  • 相对余额最大回撤:9.62%。
  • 盈利系数:1.15。
  • 预期收益:29.28。
  • 恢复系数:1.69。
  • 夏普比率:1.71。

根据之前的测试结果,在添加另一种技术工具——移动平均线后,我们可以找到与CHV和MA穿越测试策略相对应的最佳结果值,以及与以下相同的1小时时间周期:

  • 净利润: 相对较高值(20817.39 USD)。
  • 相对余额最大回撤: 相对较低值(9.62%)。
  • 盈利系数: 相对较高值 (1.15)。
  • 预期收益: 较高值 (29.28)。
  • 恢复系数: 较高值(1.69)。
  • 夏普比率: 较高值(1.71)。


结论

通过学习CHV这一技术指标,我们能够理解波动率概念在金融市场中的重要性,CHV指标对于解读或预测潜在的市场走势非常实用。我们能够了解CHV指标的工作原理、如何计算、以及如何在交易市场中利用它来为我们获得优势。

我们原本已经理解了两种简单的策略,并且学会如何通过结合另一种技术工具(如移动平均线)来优化我们的策略,以获得更好的结果。我们已经学会了如何使用MQL5语言创建自己的CHV指标,并能够根据自己的偏好对其进行调整。我们还学会了如何在基于上述策略的交易系统中使用这个自定义的CHV指标。

  • CHV穿越
  • CHV和MA穿越

我们还学习了如何基于前面提到的策略,通过EA来构建一个能够自动执行交易和设置仓位的交易系统。随后,我们利用策略测试器(Strategy Tester)对这些EA进行测试,并学会如何通过结合另一项技术工具(如移动平均线)作为优化手段,来获得更加深入的洞察和更优的交易结果。

我鼓励大家去尝试和测试其他可能的工具,以获得更深入的见解。因为无论是交易还是我们的生活中,改进都是一个永无止境的过程,它像一个无限循环的函数。我希望您能享受阅读本文的过程,并发现它对您的交易之路有所帮助。如果您想阅读更多关于如何构建交易系统以及理解其背后主要概念的文章,您可以查阅我之前在这些主题以及其他与MQL5编程相关的文章


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

附加的文件 |
Custom_CHV.mq5 (3.24 KB)
chvVal.mq5 (0.97 KB)
chvCrossover.mq5 (1.8 KB)
如何使用抛物线转向(Parabolic SAR)指标设置跟踪止损(Trailing Stop) 如何使用抛物线转向(Parabolic SAR)指标设置跟踪止损(Trailing Stop)
在创建交易策略时,我们需要测试多种多样的保护性止损。这时,一个随着价格变动而动态调整止损位的想法浮现在我的脑海中。抛物线转向(Parabolic SAR)指标无疑是最佳选择。很难想到有比这更简单且视觉上更清晰的指标了。
开发回放系统(第 44 部分):Chart Trader 项目(三) 开发回放系统(第 44 部分):Chart Trader 项目(三)
在上一篇文章中,我介绍了如何操作模板数据以便在 OBJ_CHART 中使用。在那篇文章中,我只是概述了这一主题,并没有深入探讨细节,因为在那个版本中,这项工作是以非常简单的方式完成的。这样做是为了更容易解释内容,因为尽管很多事情表面上很简单,但其中有些并不那么明显,如果不了解最简单、最基本的部分,就无法真正理解全局。
数据处理的分组方法:在MQL5中实现组合算法 数据处理的分组方法:在MQL5中实现组合算法
在本文中,我们将继续探索数据处理家族分组算法,在MQL5中实现组合算法(Combinatorial Algorithm)及其优化版本——组合选择算法(Combinatorial Selective Algorithm)。
构建K线图趋势约束模型(第一部分):针对EA和技术指标 构建K线图趋势约束模型(第一部分):针对EA和技术指标
本文面向初学者和专业的MQL5开发者。它提供了一段代码,用于定义并限制信号生成指标仅在较长的时间框架的趋势中运行。通过这种方式,交易者可以通过融入更广泛的市场视角来增强他们的策略,从而可能产生更稳健和可靠的交易信号。