下载MetaTrader 5

以峰谷指标和 ATR 指标为例说明作为类来实施指标

25 十二月 2013, 06:00
Aleksandr Chugunov
0
1 773

我们需要它来做什么?

MetaQuotes Software Corp. 在全新第 5 版的 MetaTrade 客户端中修订了处理自定义指标的概念。现在,它们以快得多的速度执行;每个指标只有一个具有唯一输入参数的例子,因此不管在哪怕一个交易品种的十个图表上使用其副本,它都仅计算一次。

但是一种算法保持不变。在与服务器的连接中断时,或在重大的历史数据同步中断时,prev_calculated 值(对于 MetaTrader 4 而言为 IndicatorCounted())被设置为零,从而导致完全针对整个历史数据重新计算指标(开发人员可能故意为之,以保证指标值在任何情形下都正确无误)。有几个因素能够影响指标的计算速度:

  • 大周期:rates_total;
  • 消耗大量资源的复杂计算;
  • 使用几个交易品种和周期;
  • 个人计算机的性能差;

适合您的情形的因素越多,您面临的针对整个历史数据重新计算指标的问题就越有可能出现。此外,用于传输信息的通道较差也会导致情况变糟。

当然,您可以使用额外的输入参数限制指标计算的深度,但是使用 iCustom 指标有一个细微差别。任意图表或任意自定义指标使用的最大柱数是在全局范围内针对整个客户端设置的。内存是针对每个自定义指标缓冲区分配的,并且仅受 TERMINAL_MAXBARS 的限制。

然而,有一个重大的添加 - 如果您在指标的算法中限制所计算的柱的最大数量(例如使用一个输入参数或直接在代码中进行限制),则会在每个新的柱出现时动态分配内存(逐渐增加到指定的 TERMINAL_MAXBARS 限制,或者更多 - 此算法完全取决于开发人员,他们可以在下一版本中进行更改)。


避免针对整个历史数据重新计算指标的方法

目前,我发现解决此问题的方法如下:
  1. 要求 MetaQuotes 在平台级别修订此问题
  2. 为实施 prev_calculated 的对等创建一个单独的类

假定您能够在指标内建立 prev_calculated 的算法,则还有其他情形,但是似乎与 MetaTrader 4 明显不同的 MetaTrader 5 在将 prev_calculated 设为零时“清除”所有指标缓存(即它强行将所有指标数组设为零;您无法控制它,因为此行为是在平台级别实施的)。

让我们单独分析每种情形。

  • 第一种情形只取决于开发人员。也许他们会在本文发表之后有所考虑。并且,实施一种完全成熟的机制可能严重影响自定义指标的计算代码块的性能(然而,此机制可作为一种选项来实施),现在他们将保持一切不变。
  • 第二种情形。创建一个特殊的类,负责实施 prev_calculated 的对等。我们可以在一个自定义指标中使用它(仅为了获得 prev_calculated 值),也可以在一个 EA 交易(或脚本)将会使用的数据提供程序中使用它,连同一个计算必要的自定义指标的单独开发的类。


解决该问题的第二种情形的优点和缺点

优点:
  • 针对动态数组一次性分配固定的所需内存大小,该数组元素具有环形读取组织方式;
  • 在依据需要使用单独的进行计算时对指标进行同步和计算(不使用信号、标志、事件等);
  • 在使用单独调用进行指标计算时,以扩展形式返回重新计算的结果(例如:没有改变、仅最后一条射线改变、添加了新的射线等)。.
缺点:
  • 必须存储自己的用于计算指标值的价格历史副本;
  • 必须使用比较数据的逻辑运算与客户端历史数据进行手动同步。


为实施 prev_calculated 的对等创建 CCustPrevCalculated 类

类的实施本身不包含要说明的任何主题事项。算法除了考虑向两侧扩展历史数据以外,还考虑是否能够从左侧“裁切”数据。此算法还能够处理在计算得出的数据中插入历史数据(对于 MetaTrader 4,这是真的;而在 MetaTrader 5 中,我还没有遇到过)。文件 CustPrevCalculated.mqh 提供了该类的源代码。

让我告诉您关键的地方。


创建对数组元素的环形存取

为了创建此类,我们将使用一种非传统的方法 - 针对数组的一次性分配的内存,对数组元素进行环形存取,避免过多的数组复制程序。让我们通过 5 个元素的例子来进行说明:


对数组元素的环形存取


 
最初,我们处理从 0 开始编号的数组。但是如果我们需要添加下一个值并保持数组大小不变,我们应怎么办(添加新的柱)?有两种方式:
  • 将内存单元格 2-5 相应复制到单元格 1-4;因此我们有一个空的内存单元格 5;
  • 更改数组索引而不更改存储在其中的信息(环绕寻址)。

要实施第二种情形,我们需要一个变量 ,让我们将其称为 DataStartInd;它将存储数组零索引的位置。为了便于以后的计算,其编号将对应于数组的通常索引(即从零开始)。在变量 BarsLimit 中,我们将存储数组元素的数量。因此,将使用以下简单公式计算虚拟索引 'I' 的数组元素的实际地址:

  • (DataStartInd+I) % BarsLimit – 针对通常编号
  • (DataStartInd+DataBarsCount-1-I) % BarsLimit – 针对时间系列等寻址
变量 DataBarsCount 存储实际使用的内存单元格的数量(例如,我们可以只使用 5 个单元格中的 3 个)。


历史数据同步的算法

对于我自己而言,我已经选择并实施了历史副本(本地历史)与客户端中的历史进行同步的三种算法模式:
  • CPCHSM_NotSynch – 对于已经形成的柱,不执行本地历史的同步(风险和责任由您承担)。实际上,此模式可任意用于较小的价格偏差对计算的精确度没有严重影响的指标(MA、ADX 等)。然而,举例而言,此模式对峰谷指标是致命的,其中一个峰值超过另一个峰值很多。
  • CPCHSM_Normal – 每次新的柱出现时,都通过以下说明的算法同步本地历史数据。
  • CPCHSM_Paranoid – 在每次调用以下说明的数据同步函数时同步本地历史数据。

同步机制本身取决于程序员设置的另一个参数 - HSMinute(存储为 HistorySynchSecond)。我们假定 Dealer Center(交易中心)只能纠正最后 HSMinute 分钟的历史。如果在该时间范围内的同步期间没有找到差异,则将历史数据视为是相同的并停止比较。如果找到差异,则检查并纠正整个历史数据。

此外,该算法允许在初始化时从指定的结构 MqlRates 仅检查价格/点差/量。例如,要绘制峰谷指标,我们只需要最高价和最低价。


CCustPrevCalculated 类的实际使用

要对 CCustPrevCalculated 类进行初始化,我们需要调用函数 InitData(),该函数在成功时返回 'true':
CCustPrevCalculated CustPrevCalculated;
CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
要同步历史数据,我们需要调用函数 PrepareData():
CPCPrepareDataResultCode resData;
resData = CustPrevCalculated.PrepareData();

函数 PrepareData() 能够返回的值的类型:

enum CPCPrepareDataResultCode
  {
   CPCPDRC_NoData,                     // Returned when there is no data for calculation (not prepared by the server)
   CPCPDRC_FullInitialization,         // Full initialization of the array has been performed
   CPCPDRC_Synch,                      // Synchronization with adding new bars has been performed
   CPCPDRC_SynchOnlyLastBar,           // Synchronization of only the last bar has been performed (possible cutting of the history)
   CPCPDRC_NoRecountNotRequired        // Recalculation has not been performed, since the data was not changed
  };


用于数据存取的 CCustPrevCalculated 类的函数

注:为了加快计算,不包括对数组溢出的检查。为了更加精确,如果索引不正确,则会返回错误的值。

名称
用途
 uint GetDataBarsCount()
 返回可用柱的数量
 uint GetDataBarsCalculated()
 返回未改变的柱的数量
 uint GetDataStartInd()
 返回用于环绕存取的索引(对于自定义指标)
 bool GetDataBarsCuttingLeft()
 返回从左侧裁切柱的结果
 double GetDataOpen(int shift, bool AsSeries)
 为偏移柱返回 'Open'(开盘价)
 double GetDataHigh(int shift, bool AsSeries)
 为偏移柱返回 'High'(最高价)
 double GetDataLow(int shift, bool AsSeries)
 为偏移柱返回 "Low"(最低价)
 double GetDataClose(int shift, bool AsSeries)
 为偏移柱返回 'Close'(收盘价)
 datetime GetDataTime(int shift, bool AsSeries)
 为偏移柱返回 'Time'(时间)
 long GetDataTick_volume(int shift, bool AsSeries)
 为偏移柱返回 'Tick_volume'(交易量)
 long GetDataReal_volume(int shift, bool AsSeries)
 为偏移柱返回 'Real_volume'(实际交易量)
 int GetDataSpread(int shift, bool AsSeries)
 为偏移柱返回 'Spread'(点差)


进一步优化 CCustPrevCalculated 类的例子

  • 通过切换到(根据某种目的所确定)几个数组来拒绝 MqlRates,并(降低内存要求,但是增大在数组复制的调用次数方面的载入)。
  • 将每个存取函数分为两个独立的函数,以便明确的使用某类数组索引(拒绝 «bool AsSeries» 参数)。优点在于仅在逻辑条件 «if (AsSeries)» 中。


依据 CCustPrevCalculated 类的数据创建用于计算自定义峰谷指标的 CCustZigZagPPC

此算法依据自定义指标专业峰谷指标。类的源代码位于 ZigZags.mqh 文件中;此外,库 OutsideBar.mqh 用于处理额外的柱。

为了说明我们的指标的一个柱,让我们创建一个单独的结构:

struct ZZBar
  {
   double UP, DN;                      // Buffers of the ZigZag indicator
   OrderFormationBarHighLow OB;       // Buffer for caching of an external bar
  };

也让我们确定类的计算返回的结果:

enum CPCZZResultCode
  {
   CPCZZRC_NotInitialized,             // Class is no initialized
   CPCZZRC_NoData,                     // Faield to receive data (including the external bar)
   CPCZZRC_NotChanged,                 // No changes of ZZ rays
   CPCZZRC_Changed                     // ZZ rays changed
  };

要对 CCustZigZagPPC 类进行初始化,我们需要调用一次 Init() 函数;该函数在成功时返回 'true':

CCustZigZagPPC ZZ1;
ZZ1.Init(CustPrevCalculated, _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);

要计算指标,我们需要依据先前计算出来的 CCustPrevCalculated 类的数据开始更新数据:

CPCPrepareDataResultCode resZZ1;
resZZ1 = ZZ1.PrepareData(resData);

然后调用程序 Calculate():

if ( (resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired) )
   ZZ1.Calculate();

在文件 ScriptSample_CustZigZagPPC.mq5 中提供了一起使用一个 CCustPrevCalculated 类和几个 CCustZigZagPPC 类的完整例子。


CCustZigZagPPC 类的数据存取函数

名称
用途
 uint GetBarsCount()
 返回可用柱的数量
 uint GetBarsCalculated()  返回计算出来的柱的数量
 double GetUP(uint shift, bool AsSeries)
 返回柱的峰谷指标的峰值
 double GetDN(uint shift, bool AsSeries)
 返回柱的峰谷指标的谷值
 OrderFormationBarHighLow GetOB(uint shift, bool AsSeries)  返回柱的 'Outside'(外部)值


图形和程序检查

为了进行图形检查,让我们将原来的指标附加到一个图表,并且在其上附加专门编写的具有相同输入参数的测试指标 Indicator_CustZigZag.mq5(但是您应选择其他颜色以查看两个指标);以下是运行结果:

红色 - 原来的指标,蓝色 - 我们自己的指标,使用最后 100 根柱计算。

我们可以在 EA 交易中以同样的方式比较它们;这样会有区别吗?在测试 EA 交易 Expert_CustZigZagPPC_test.mq5 中,每一次价格变动都会比较从 iCustom("AlexSTAL_ZigZagProf") 获得的结果以及从 CCustZigZagPPC 类获得的结果。计算信息显示在日志中(在第一根柱上可能没有计算,因此缺乏算法需要的历史数据):

(EURUSD,M1)                1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 1.35971; // it is normal
(EURUSD,M1) Tick processed: 1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 
(EURUSD,M1) Divergence on the bar: 7 

让我们更加详细地考虑此 EA 交易。确定工作需要的全局变量

#include <ZigZags.mqh>

CCustPrevCalculated CustPrevCalculated;
CCustZigZagPPC ZZ1;
int HandleZZ;

对变量进行初始化:

int OnInit()
  {
   // Creating new class and initializing it
   CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
   
   // Initializing the class ZZ
   ZZ1.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);
   
   // Receiving handle for the custom indicator
   HandleZZ = iCustom(_Symbol, _Period, "AlexSTAL_ZigZagProf", 12, 10, 0 , true);
   Print("ZZ_handle = ", HandleZZ, "  error = ", GetLastError());

   return(0);
  }
在 EA 交易中处理价格变动:
void OnTick()
  {
   // Calculation of data
   CPCPrepareDataResultCode resData, resZZ1;
   resData = CustPrevCalculated.PrepareData();
   
   // Start recalculation for each indicator! PrepareData obligatory!
   resZZ1 = ZZ1.PrepareData(resData);
   
   // Расчет данных ZZ1
   if ( !((resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired)) )
      return;

   // Получим результаты расчета
   ZZ1.Calculate();

现在,我们具有用 CCustZigZagPPC 计算出来的 ZZ1.GetBarsCalculated() 根柱。让我们添加用于比较 iCustom("AlexSTAL_ZigZagProf") 和 CCustZigZagPPC 类的数据的代码:

   int tmpBars = (int)ZZ1.GetBarsCalculated();
   double zzUP[], zzDN[];
   CopyBuffer(HandleZZ, 0, 0, tmpBars, zzUP);
   CopyBuffer(HandleZZ, 1, 0, tmpBars, zzDN);
   
   // Perform comparison
   string tmpSt1 = "", tmpSt2 = "";
   for (int i = (tmpBars-1); i >= 0; i--)
     {
      double tmpUP = ZZ1.GetUP(i, false);
      double tmpDN = ZZ1.GetDN(i, false);
      if (tmpUP != zzUP[i])
         Print("Divergence on the bar: ", i);
      if (tmpDN != zzDN[i])
         Print("Divergence on the bar: ", i);
      if (tmpUP != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpUP, _Digits) + "; ";
      if (tmpDN != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpDN, _Digits) + "; ";

      if (zzUP[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzUP[i], _Digits) + "; ";
      if (zzDN[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzDN[i], _Digits) + "; ";
     }
  Print("Tick processed: ", tmpSt1);
  Print("                              ", tmpSt2);
  }

这是 CCustZigZagPPC 类在一个 EA 交易或脚本中的简单实践运用。直接存取函数 GetUP()、GetDN()、GetOB() 代替了 CopyBuffer()


将我们的指标移到一个单独的类(以 iATR 为例)

以文件 ZigZags.mqh 为基础,我创建了 MyIndicator.mqh 模板以便依据上述原则快速开发自定义指标。

一般计划:

1. 准备阶段。

  • 复制 MyIndicator.mqh 文件并取为另一个文件名(在我的例子中为 ATRsample.mqh),并在 MetaEditor 5 中打开新取名的文件。
  • 用您的指标的名称代替文本 "MyInd"(在我的例子中为 "ATR")。

2. 选择要从初始(原来的)指标取值到类的外部参数,然后声明它们并对其进行初始化。

在我的例子中,ATR 指标有一个外部参数:
input int InpAtrPeriod=14;  // ATR period
  • 将此参数添加到我们的类,并添加到对类进行初始化的函数:
class CCustATR
  {
protected:
   ...
   uchar iAtrPeriod;
   ...
public:
   ...
   bool Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod);
  • 更改 Init 函数的主体头部,然后用输入值对变量参数进行初始化:
bool CCustATR::Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod)
{
      ...
      BarsLimit = Limit;
      iAtrPeriod = AtrPeriod;
      ...

3. 确定初始指标中需要的缓存数量,然后在我们的类中声明它们。同时声明用于返回 INDICATOR_DATA 缓存的函数。

  • 将结构
struct ATRBar
  {
   double Val;                          // Indicator buffers
  };

更改成我们的结构:

struct ATRBar
  {
   double ATR;
   double TR;
  };
  • 确定零值:
CPCPrepareDataResultCode CCustATR::PrepareData(CPCPrepareDataResultCode resData)
{
   ...
   for (uint i = (DataBarsCalculated == 0)?0:(DataBarsCalculated+1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].ATR = EMPTY_VALUE;
      Buf[PInd(i, false)].TR = EMPTY_VALUE;
     }
   ...
  • 更改及添加用于返回 INDICATOR_DATA 缓存的值的函数:

(如果只有一个缓存,则可以跳过更改)将

class CCustATR
  {
   ...
   double GetVal(uint shift, bool AsSeries);                      // returns the Val value of the buffer for a bar
   ...

改为

class CCustATR
  {
   ...
   double GetATR(uint shift, bool AsSeries);                      // Возвращает значение буфера ATR для бара
   ...

并且更改对应函数的代码:

double CCustATR::GetATR(uint shift, bool AsSeries)
{
   if ( shift > (DataBarsCount-1) )
      return(EMPTY_VALUE);
   return(Buf[PInd(shift, AsSeries)].ATR);
}

注:代替几个返回缓存值的函数,您可以仅使用一个具有额外参数(缓存的编号或名称)的函数。


4. 将初始指标的 OnCalculate() 函数的逻辑复制到类的对应函数

  • 初步检查
CPCATRResultCode CCustATR::Calculate()
{
   ...
   // Check if there are enough bars for the calculation
   if (DataBarsCount <= iAtrPeriod)
      return(CPCATRRC_NoData);
   ...
  • 计算:在第一次价格变动时,以及下一次价格变动时用于计算的柱数:
   if ( DataBarsCalculated != 0 )
      BarsForRecalculation = DataBarsCount - ATRDataBarsCalculated - 1;
   else
     {
      Buf[PInd(0, false)].TR = 0.0;
      Buf[PInd(0, false)].ATR = 0.0;
      //--- filling out the array of True Range values for each period
      for (uint i = 1; i < DataBarsCount; i++)
         Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                                  MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      //--- first AtrPeriod values of the indicator are not calculated
      double firstValue = 0.0;
      for (uint i = 1; i <= iAtrPeriod; i++)
        {
         Buf[PInd(i, false)].ATR = 0;
         firstValue += Buf[PInd(i, false)].TR;
        }
      //--- calculating the first value of the indicator
      firstValue /= iAtrPeriod;
      Buf[PInd(iAtrPeriod, false)].ATR = firstValue;
      
      BarsForRecalculation = DataBarsCount - iAtrPeriod - 2;
     }
  • 每一次价格变动本身的计算:
   for (uint i = (DataBarsCount - BarsForRecalculation - 1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                               MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      Buf[PInd(i, false)].ATR = Buf[PInd(i-1, false)].ATR + (Buf[PInd(i, false)].TR-Buf[PInd(i-iAtrPeriod, false)].TR) / iAtrPeriod;
      ...

以上便是全部内容。我们的类已经创建完毕。对于图形检查,您可以创建一个测试指标(在我的例子中为 Indicator_ATRsample.mq5):



在校对本文时我形成了一个想法,如果您使用 CCustPrevCalculated 类以及唯一一个自定义指标,则您可以将这个类的创建、初始化和同步整合到自定义指标中(在我的例子中,它们为 CCustZigZagPPC 和 CCustATR)。当为此目的调用自定义指标的初始化函数时,您需要使用对象的零指针:

   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);

此时,一般结构

#include <CustPrevCalculated.mqh>
#include <ATRsample.mqh>
CCustPrevCalculated CustPrevCalculated;
CCustATR ATR;

int OnInit()
  {
   CustPrevCalculated.InitData(_Symbol, _Period, iBars, CPCHSM_Normal, 0, 30);
   ATR.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   CPCPrepareDataResultCode resData = CustPrevCalculated.PrepareData();
   CPCPrepareDataResultCode resATR = ATR.PrepareData(resData);
   if ( (resATR != CPCPDRC_NoData) && (resATR != CPCPDRC_NoRecountNotRequired) )
      ATR.Calculate();
  }

将简化为:

#include <ATRsample.mqh>
CCustATR ATR;

int OnInit()
  {
   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   ATR.Calculate();
  }
在文件 Indicator_ATRsample2.mq5 中提供了一个实例。

所介绍的技术在策略测试程序中进行测试时对性能的影响

为了进行检查,我创建了一个测试 EA 交易 (TestSpeed_IndPrevCalculated.mq5),该 EA 依据三种情形之一在每一次价格变动时接收零柱指标的值:

enum eTestVariant
  {
   BuiltIn,    // Built-in indicator iATR
   Custom,     // Custom indicator iCustom("ATR")
   IndClass    // Calculation in the class
  };

此 EA 交易将在 1 个代理上运行 10 次,并采用以下优化参数:

  • 交易品种:EURUSD
  • 周期:整个历史 [1993..2001]
  • 交易模式:Every tick(每一价格变动)
  • 外部参数:FalseParameter [0..9]

我测量了使用三种指标情形中的每一种时优化的时间。检查结果显示为一个线性直方图。

ATR 指标的三类实施的优化时间

    用于测量优化时间的 EA 交易的源代码:

    //+------------------------------------------------------------------+
    //|                                  TestSpeed_IndPrevCalculated.mq5 |
    //|                                         Copyright 2011, AlexSTAL |
    //|                                           http://www.alexstal.ru |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2011, AlexSTAL"
    #property link      "http://www.alexstal.ru"
    #property version   "1.00"
    //--- connect the include file with the CustATR class
    #include <ATRsample.mqh>
    //--- set the selection of the parameter as an enumeration
    enum eTestVariant
      {
       BuiltIn,    // Built-in indicator iATR
       Custom,     // Custom indicator iCustom("ATR")
       IndClass    // Calculation withing the class
      };
    //--- input variables
    input eTestVariant TestVariant;
    input int          FalseParameter = 0;
    //--- period of the ATR indicator
    const uchar        InpAtrPeriod = 14;
    //--- handle of the built-in or custom indicator
    int                Handle;
    //--- indicator based on the class 
    CCustATR           *ATR;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       //---
       switch(TestVariant)
         {
          case BuiltIn:
             Handle = iATR(_Symbol, _Period, InpAtrPeriod);
             break;
          case Custom:
             Handle = iCustom(_Symbol, _Period, "Examples\ATR", InpAtrPeriod);
             break;
          case IndClass:
             ATR = new CCustATR;
             ATR.Init(NULL, _Symbol, _Period, 100, CPCHSM_Normal, 0, 30, InpAtrPeriod);
             break;
         };
       //---
       return(0);
      }
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
       switch(TestVariant)
         {
          case IndClass:
             delete ATR;
             break;
         };
      }
    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
      {
       double tmpValue[1];
       switch(TestVariant)
         {
          case BuiltIn:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case Custom:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case IndClass:
             ATR.Calculate();
             tmpValue[0] = ATR.GetATR(0, true);
             break;
         };
      }
    //+------------------------------------------------------------------+
    

    如我们所见,与使用普通的自定义指标相比,在策略测试程序中进行测试时,此技术并没有显著降低性能。


    有关实际使用此技术的说明

    • 在策略测试程序中测试 EA 交易时,不能在自定义指标中将 prev_calculated 值设置为零,这是为什么在此模式中禁用历史数据同步的原因;
    • 指标的计算仅在类的最初的初始化时严格设定的最后 'n' 根柱上执行;
    • 计算表示对于某个交易品种和初始化后的类的周期的严格绑定。要针对其他交易品种或周期执行计算,需要创建类的新实例。


    总结

    在每一种情形中,程序员都应考虑任务的不同实施方法所具有的优点和缺点。本文所建议的实施也有其自己的优点和缺点。

    注:不犯错的人必将一事无成!如果您找到错误,请告诉我。

    本文译自 MetaQuotes Software Corp. 撰写的俄文原文
    原文地址: https://www.mql5.com/ru/articles/247

    基于成交历史的交易播放器 基于成交历史的交易播放器

    交易播放器。仅仅五个字,无需解释。一个带有按钮的小对话框出现在您的脑海中。按一个按钮 - 它开始播放,移动控制杆 - 播放速度改变。事实上,它非常类似。在本文中,我想展示我编写的以几乎与实时交易完全相同的方式播放交易历史的程序。本文使用指标和管理图表来介绍 OOP 的某些细节。

    不使用额外的缓冲区,为中间计算进行系列价格的平均化 不使用额外的缓冲区,为中间计算进行系列价格的平均化

    本文要讲述的是封装于最简单的单型类中的传统与非寻常平均线算法。它们旨在实现于几乎所有指标的开发过程中的普适用途。我希望建议的这些类,会成为那些自定义与技术指标“笨重”调用的一个很好的替代。

    基于预定义的风险和风险/回报比建立互动式半自动拖放“EA 交易” 基于预定义的风险和风险/回报比建立互动式半自动拖放“EA 交易”

    部分交易人员选择自动执行所有交易,而另外一些交易人员基于多个指标的输出混合使用自动和手动交易。作为后者中的一员,我需要一个互动式工具以直接从图表动态地评估风险和回报价格水平。本文将介绍通过预定义的资产净值风险和风险/回报比实施互动式半自动“EA 交易”的方法。“EA 交易”风险、风险/回报和手数参数可于运行时期间在 EA 面板上更改。

    基于 CChartObject 类设计和实施新 GUI 组件 基于 CChartObject 类设计和实施新 GUI 组件

    在我撰写了关于通过 GUI 界面实现半自动“EA 交易”的前作后,结果表明针对更复杂的指标和“EA 交易”,最好使用新的功能来改善界面。在熟悉 MQL5 标准库类后,我实施了一些新的组件。本文介绍新 MQL5 GUI 组件的设计和实施过程;这些组件可用于指标和“EA 交易”。本文中介绍的组件包括:CChartObjectSpinner、CChartObjectProgressBar 和 CChartObjectEditTable。