概述
MetaTrader 5的策略测试器是许多人用来评估专家顾问(EA)潜力的主要工具。虽然它的功能足够,但经验丰富的开发人员可以使用它来制作能够伪装出非凡性能的“特技”EA。我们都看到了那些净值曲线的屏幕截图,显示了EA卖家令人难以置信的表现。乍一看,这一切都令人印象深刻,但当该策略应用于现实世界时,往往会产生完全不同的净值曲线。我们怎样才能避免上当这些廉价把戏的后果呢?在本文中,我们将研究这样一个系统,并演示如何使用置换测试来消除误导性净值曲线的烟幕弹,从而更准确地了解策略性能。此外,在前一篇文章中,我们看到了一种置换分时数据的算法的实现。这一次,我们将描述一种置换价格柱的方法。
置换 OHLC 数据
由于涉及多个系列,置换价格柱并不难实现。与置换分时数据类似,在处理价格柱时,我们努力保持原始价格序列的总体趋势。同样重要的是,我们绝不允许柱的打开或关闭分别超过或低于上限或下限的界限。目标是获得一系列具有与原始数据完全相同的特征分布的柱形图。
除了趋势之外,随着系列从开盘到收盘的发展,我们必须保持价格变化的分散性。开盘和收盘之间的价格变化幅度在排列的柱形图中应与原始柱形图相同。在柱形之外,我们必须确保柱间价格变化的分布也是相同的。具体地说,一个柱的关闭和下一柱的开始之间的差异。
这一点非常重要,以免对正在测试的策略造成不利影响。该系列的一般特征应该相似,唯一的区别应该是第一柱和最后一个柱之间每个开盘价、最高价、最低价、收盘价(OHLC)的绝对值。实现这一点的代码与在 MetaTrader 5 的蒙特卡罗置换测试一文中介绍的CPermuteTicks类中使用的代码非常相似。价格柱置换代码将封装在PermuteRates.mqh中包含的CPermuteRates类中。
CPermuteRates 类
//+------------------------------------------------------------------+
//| struct to handle relative values of rate data to be worked on |
//+------------------------------------------------------------------+
struct CRelRates
{
double rel_open;
double rel_high;
double rel_low;
double rel_close;
};
//+------------------------------------------------------------------+
//| Class to enable permuation of a collection of ticks in an array |
//+------------------------------------------------------------------+
class CPermuteRates
{
private :
MqlRates m_rates[]; //original rates to be shuffled
CRelRates m_differenced[]; //log difference of rates
bool m_initialized; //flag to signal state of random number object
CUniFrand *m_random; //random number generator
public :
//constructor
CPermuteRates(void);
//desctructor
~CPermuteRates(void);
bool Initialize(MqlRates &in_rates[]);
bool Permute(MqlRates &out_rates[]);
};
PermuteRate.mqh从一个简单结构的定义开始,该结构将存储原始价格的差异记录。
rel_open 将保存当前柱开盘价和上一个柱收盘价之间的对数差
rel_high 表示当前柱形最高价和开盘价之间的对数差。
rel_low 是指当前柱最低价和开盘价之间的对数差
rel_close 又是当前柱收盘价和开盘价之间的对数差
自定义 CRelRates 结构表示从将被置换的MqlRates中提取的数据。MqlRates的其他结构成员将不会被更改。置换价格的最终结果将使这些结构成员从原始价格序列中复制。正如已经提到的,将改变的只是OHLC值。
//+------------------------------------------------------------------+
//| Permute the bars |
//+------------------------------------------------------------------+
bool CPermuteRates::Permute(MqlRates &out_rates[])
{
//---
if(!m_initialized)
{
Print("Initialization error");
ZeroMemory(out_rates);
return false;
}
//---
int i,j;
double temp=0.0;
//---
i=ArraySize(m_rates)-2;
//---
while(i > 1 && !IsStopped())
{
j = (int)(m_random.RandomDouble() * i) ;
if(j >= i)
j = i - 1 ;
--i ;
temp = m_differenced[i+1].rel_open ;
m_differenced[i+1].rel_open = m_differenced[j+1].rel_open ;
m_differenced[j+1].rel_open = temp ;
}
//---
i =ArraySize(m_rates)-2;
//---
while(i > 1 && !IsStopped())
{
j = (int)(m_random.RandomDouble() * i) ;
if(j >= i)
j = i - 1 ;
--i ;
temp = m_differenced[i].rel_high;
m_differenced[i].rel_high = m_differenced[j].rel_high ;
m_differenced[j].rel_high = temp ;
temp = m_differenced[i].rel_low ;
m_differenced[i].rel_low = m_differenced[j].rel_low ;
m_differenced[j].rel_low = temp ;
temp = m_differenced[i].rel_close ;
m_differenced[i].rel_close = m_differenced[j].rel_close ;
m_differenced[j].rel_close = temp ;
}
//---
if(ArrayCopy(out_rates,m_rates)!=int(m_rates.Size()))
{
ZeroMemory(out_rates);
Print("Copy error ", GetLastError());
return false;
}
//---
for(i=1 ; i<ArraySize(out_rates) && !IsStopped() ; i++)
{
out_rates[i].open = MathExp(((MathLog(out_rates[i-1].close)) + m_differenced[i-1].rel_open)) ;
out_rates[i].high = MathExp(((MathLog(out_rates[i].open)) + m_differenced[i-1].rel_high)) ;
out_rates[i].low = MathExp(((MathLog(out_rates[i].open)) + m_differenced[i-1].rel_low)) ;
out_rates[i].close = MathExp(((MathLog(out_rates[i].open)) + m_differenced[i-1].rel_close)) ;
}
//---
if(IsStopped())
return false;
//---
return true;
//---
}
置换是在Permute()方法中完成的。CRelRates结构将柱形图数据分为两种类型的描述符。rel_open 系列值表示从一个柱形到下一个柱形的变化,而rel_high、rel_low和rel_close表示柱形内的变化。为了置换柱形图,我们首先对 rel_open 的价格序列进行打乱,这些是柱形图之间的差异。从那里开始,再打乱内部柱的变化。新的OHLC系列是从打乱的柱间数据构建的,以获得由打乱的柱内变化构建的具有相应最高价、最低价和收盘价的新开盘价。