
将指标代码转移至 Expert Advisor 代码。Expert Advisor 和指标函数的总体结构方案
简介
在上一篇文章(将指标代码转移至 Expert Advisor 代码。指标结构)中,我们分析了指标的总体结构以及用于转移至 Expert Advisor 代码的代码,并介绍了有关指标代码的初步调整的主要想法。现在,让我们来尝试将获得的代码转换成自定义函数,因为这可能是在 Expert Advisor 中显示指标代码的一个最便捷的方式。自定义函数可显示为 mqh-file,其在 Expert Advisor 中使用指令 #include 的声明将占用很小的空间,而且调用此函数并不比调用自定义指标难很多。更重要的是,此类自定义函数相当普遍,将来可进一步用于任何 Expert Advisor。
在开始编写此类函数之前,让我们来分析一下,此函数将如何与 Expert Advisor 的另一部分相互作用,而不考虑此函数的内部结构。
带自定义指标调用的 EA 的结构
首先,让我们来研究一下接收来自自定义指标的数据的 EA 的概要结构。在此 EA 中,我们首先感兴趣仅在于接收来自自定义指标的数据的那部分。因此,我们不会讨论以下内容: EA 处理这些数据的方式、将这些数据转移至交易信号以及 EA 的执行部分的结构。让我们来分析此 EA 中作为自定义指标的指标,上一篇文章已对其进行讨论。下面是 Expert Advisor 结构的示例:
//+------------------------------------------------------------------+ //| ExpertIndPlan0.mqh | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ #property copyright "Copyright © 2007, MetaQuotes Software Corp." #property link "https://www.metaquotes.net/" //---- EA input parameters extern int period10 = 15; extern int period11 = 15; extern int period12 = 15; //---- EA input parameters extern int period20 = 15; extern int period21 = 15; extern int period22 = 15; //---- Declaring buffers for indicator values double Ind_Buffer1[6]; double Ind_Buffer2[6]; //+------------------------------------------------------------------+ //| Custom Expert initialization function | //+------------------------------------------------------------------+ int init() { // Here is the code of EA initialization //---- initialization end return(0); } //+------------------------------------------------------------------+ //| Custom Expert iteration function | //+------------------------------------------------------------------+ int start() { //---- Checking whether the bars number is enough for further calculation if(iBars(Symbol(), 0) < period10 + period11 + period12 + 10) return(0); if(iBars(Symbol(), 0) < period20 + period21 + period22 + 10) return(0); //---- getting indicator values for further calculation for(int bar = 5; bar >= 1; bar--) { Ind_Buffer1[bar] = iCustom("IndicatorPlan", Symbol(), 0, period10, period11, period12, 0, bar); Ind_Buffer2[bar] = iCustom("IndicatorPlan", Symbol(), 0, period20, period21, period22, 0, bar); } // Here is the EA code, forming trade signals // based on the values of indicator buffer cells //---- // here is the EA executive part code, // requesting for placing an order //---- return(0); } //+------------------------------------------------------------------+
在此方案中,每次价格变动时,我们都会从自定义指标 IndicatorPlan. mq4 的零缓冲区中获取两次调用中的计数值,并将它们放置在公用数组 Ind_Buffer1[] 和 Ind_Buffer2[] 中。考虑到我们将仅需要最后五个指标值(零值除外)进行进一步计算,因而制定了调用指标的方案。
带自定义指函数用的 EA 的结构
尽管我们正在开发一个适用于任何 Expert Advisor 的通用指标函数,但应将收到的值发送给指标缓冲区类似值,它们将保存图表的所有柱的指标值。当然,我们可以开发一个指标函数,调用此函数完全类似于调用自定义指标,但编写此类函数会花费过多时间,而且其代码也会相当冗长。
我们可以更轻松地做到这一点。此函数应接收自定义指标和缓冲区的参数作为输入参数,并应向同一缓冲区返回指标模式仿真,其中单元格中填有计算出的指标值。可通过在我们的函数中声明指标缓冲区对应的由参考链接的函数外部变量, 轻松执行此操作。在 MQL4 语言中,看起来像这样:double& InputBuffer。指标函数应声明作为逻辑函数,如果计算成功,返回“true”,如果由于图表中没有正确数量的柱而导致计算失败,则返回“false”。在进行上述解释之后,假定从上一篇文章中讨论的指标方案中构建的指标函数采用以下格式:
bool Get_IndSeries(int Number,string symbol, int timeframe, bool NullBarRecount, int period0, int period1, int period2, double& InputBuffer0[], double& InputBuffer1[], double& InputBuffer2[])
除指标缓冲区 InputBuffer0 以外,其他外部变量还将包含用于中间计算的缓冲区 InputBuffer1 和 InputBuffer2,这是很自然的,因为在此函数中包含这些缓冲区相当成问题。最好是在函数中模拟这些缓冲区计算的指标模式。这样不会造成任何问题。现在,让我们来详细介绍外部变量 NullBarRecount 的含义。事实上,大多数 EA 不需要在零柱上进行计算,当我们在编写通用指标函数的代码时,它自然会在零柱上重新计算指标值,这可能会大幅增加执行时间。如果没有必要,通过将函数 NullBarRecount 的外部参数 指定为“false”,我们可以阻止在零柱上进行函数计算。
现在,我们可以给出使用调用函数调用变量中的指标的 Expert Advisor 结构的方案:
//+------------------------------------------------------------------+ //| ExpertIndPlan1.mqh | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ #property copyright "Copyright © 2007, MetaQuotes Software Corp." #property link "https://www.metaquotes.net/" //---- EA input parameters extern int period10 = 15; extern int period11 = 15; extern int period12 = 15; //---- EA input parameters extern int period20 = 15; extern int period21 = 15; extern int period22 = 15; //---- Indicator buffers declaration double Ind_Buffer10[], Ind_Buffer11[], Ind_Buffer12[]; double Ind_Buffer20[], Ind_Buffer21[], Ind_Buffer22[]; //+------------------------------------------------------------------+ //| Get_IndSeries() function | //+------------------------------------------------------------------+ //---- Declaration of the function Get_IndSeries() bool Get_IndSeries(int Number,string symbol, int timeframe, bool NullBarRecount, int period0, int period1, int period2, double& InputBuffer0[], double& InputBuffer1[], double& InputBuffer2[]) { //---- // Here is the code of the function GetIdicator() //---- return(true); } //+------------------------------------------------------------------+ //| Custom Expert initialization function | //+------------------------------------------------------------------+ int init() { //---- // Here is the code of the EA initialization //---- initialization end return(0); } //+------------------------------------------------------------------+ //| Custom Expert iteration function | //+------------------------------------------------------------------+ int start() { //---- Checking whether the bars number is enough for further calculation if(iBars(Symbol(), 0) < period10 + period11 + period12 + 10) return(0); if(iBars(Symbol(), 0) < period20 + period21 + period22 + 10) return(0); //---- getting indicator values for further calculation if(!Get_IndSeries(0,Symbol(), 0, false, period10, period11, period12, Ind_Buffer10, Ind_Buffer11, Ind_Buffer12)) return(0); if(!Get_IndSeries(1, Symbol(), 0, false, period20, period21, period22, Ind_Buffer20, Ind_Buffer21,Ind_Buffer22)) return(0); //---- // Here is the EA code, forming trade signals // based on the values of indicator buffer cells //---- // here is the EA executive part code, // requesting for placing an order //---- return(0); } //+------------------------------------------------------------------+
将指标代码转换成自定义函数的总体方案
在完成上述初步工作之后,我们可以开始构建指标函数的内部结构的总体方案。让我们来将上一篇文章的最后一个指标方案作为基准。应该没有什么困难之处:
1.仅提取函数 int start() 的内容;
2.添加函数 Get_IndSeries() 的声明:
bool Get_IndSeries(string symbol, int timeframe, bool NullBarRecount, int period0, int period1, int period2, double& InputBuffer0, double& InputBuffer1, double& InputBuffer2)
3.根据函数 Get_IndSeries() 的外部变量的缓冲区名称 (InputBuffer) 更改代码 (Ind_Buffer) 中的指标缓冲区的名称 (Ind_Buffer);
4.添加变量 LastCountBar 的声明;
5.检查变量 NullBarRecount 是否为真:
if(!NullBarRecount) LastCountBar = 1;
7.在检查柱数是否足以进行进一步计算时,在代码最开头部分进行更改:将 return(0) 更改为 return(false);
8.在代码的结尾处,将 return(0) 更改为 return(true);
指标函数准备就绪:
//+------------------------------------------------------------------+ //| Get_IndSeries.mqh | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ bool Get_IndSeries(int Number, string symbol,int timeframe, bool NullBarRecount, int period0, int period1, int period2, double& InputBuffer0[], double& InputBuffer1[], double& InputBuffer2[]) { //---- getting the number of all bars of a chart int IBARS = iBars(symbol, timeframe); //---- Checking whether the bars number is enough for further calculation if(IBARS < period0 + period1 + period2) return(false); //---- EMULATION OF INDICATOR BUFFERS if(ArraySize(InputBuffer0) < IBARS) { ArraySetAsSeries(InputBuffer0, false); ArraySetAsSeries(InputBuffer1, false); ArraySetAsSeries(InputBuffer2, false); //---- ArrayResize(InputBuffer0, IBARS); ArrayResize(InputBuffer1, IBARS); ArrayResize(InputBuffer2, IBARS); //---- ArraySetAsSeries(InputBuffer0, true); ArraySetAsSeries(InputBuffer1, true); ArraySetAsSeries(InputBuffer2, true); } //----+ introducing static memory variables static int IndCounted[]; //----+ changing the size of static variables if(ArraySize(IndCounted) < Number + 1) ArrayResize(IndCounted, Number + 1); //----+ introducing an integer variable int LastCountBar; //----+ Checking if the recalculation of the zero bar is allowed if(!NullBarRecount) LastCountBar = 1; else LastCountBar = 0; //----+ Inserting a variable with a floating point double Resalt0, Resalt1, Resalt2; //----+ Inserting integer variables and getting already calculated bars int limit, MaxBar, bar, counted_bars = IndCounted[Number]; //----+ Remembering the number of all bars of a chart (we do not count the zero bar!) IndCounted[Number] = IBARS - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated limit = IBARS - counted_bars - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated MaxBar = IBARS - 1 - (period0 + period1 + period2); //---- initialization of zero if(limit > MaxBar) { limit = MaxBar; for(bar = IBARS - 1; bar >= 0; bar--) { InputBuffer0[bar] = 0.0; InputBuffer1[bar] = 0.0; InputBuffer2[bar] = 0.0; } } //----+ THE FIRST CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= LastCountBar; bar--) { // Here code of the variable Resalt1 calculation // based on the external variable period1 InputBuffer1[bar] = Resalt1; } //----+ THE SECOND CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= LastCountBar; bar--) { // Here code of the variable Resalt2 calculation // based on the values of the buffer Ind_Buffer1[] // and external variable period2 InputBuffer2[bar] = Resalt2; } //----+ THE MAIN CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= LastCountBar; bar--) { // Here code of the variable Resalt0 calculation // based on the values of the buffer Ind_Buffer2[] // and external variable period0 InputBuffer0[bar] = Resalt0; } return(true); } //+------------------------------------------------------------------+
我认为,如果读者使用 MQL4 相当不错,在阅读上述操作之后,则在根据给定方案编写指标函数方面,不会有任何问题。
编写自定义指标函数的示例
现在让我们来编写一个指标函数。我们使用一个最简单的指标:
//+------------------------------------------------------------------+ //| RAVI.mq4 | //| Copyright © 2005, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ #property copyright "Copyright © 2005, MetaQuotes Software Corp." #property link "https://www.metaquotes.net/" //---- drawing the indicator in a separate window #property indicator_separate_window //---- number of indicator buffers #property indicator_buffers 1 //---- indicator color #property indicator_color1 Red //---- INPUT PARAMETERS OF THE INDICATOR extern int Period1 = 7; extern int Period2 = 65; extern int MA_Metod = 0; extern int PRICE = 0; //---- indicator buffers double ExtBuffer[]; //+------------------------------------------------------------------+ //| RAVI initialization function | //+------------------------------------------------------------------+ int init() { //---- indicator drawing style SetIndexStyle(0, DRAW_LINE); //---- indicator buffers SetIndexBuffer(0,ExtBuffer); //---- indicator name and labels for subwindows IndicatorShortName("RAVI (" + Period1+ ", " + Period2 + ")"); SetIndexLabel(0, "RAVI"); //---- initialization end return(0); } //+------------------------------------------------------------------+ //| RAVI iteration function | //+------------------------------------------------------------------+ int start() { int MinBars = MathMax(Period1, Period2); //---- checking whether the bars number is enough for further calculation if(Bars < MinBars) return(0); //----+ Introducing variables with a floating point double MA1, MA2, result; //----+ Introducing integer variables and getting already calculated bars int MaxBar, bar, limit, counted_bars = IndicatorCounted(); //---- checking for possible errors if(counted_bars < 0) return(-1); //---- the last calculated bar should be recalculated if(counted_bars > 0) counted_bars--; //---- defining the number of the oldest bar, // starting from which all bars will be recalculated MaxBar = Bars - 1 - MinBars; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated limit = Bars - counted_bars - 1; //---- zero initialization if(limit > MaxBar) { for(int ii = Bars - 1; ii >= MaxBar; ii--) ExtBuffer[ii] = 0.0; limit = MaxBar; } //---- main cycle for(bar = 0; bar <= limit; bar++) { MA1 = iMA(NULL, 0, Period1, 0, MA_Metod, PRICE,bar); MA2 = iMA(NULL, 0, Period2, 0, MA_Metod, PRICE,bar); //---- result = ((MA1 - MA2) / MA2)*100; ExtBuffer[bar] = result; } //---- return(0); } //+------------------------------------------------------------------+
修复算法
1.去除指标代码中所有不必要的元素;
2.为单个缓冲区 ExtBuffer[] 编写指标缓冲区模拟的代码;
3.用变量 IndCounted 替代函数 IndicatorCounted();
4.使用图表柱的数量减去一初始化变量 IndCounted;
5.将预定义变量 Bars 更改为调用时间序列 iBars(符号,时间范围);
6.针对 counted_bars 删除不必要的检查:
//---- checking possible errors if(counted_bars < 0) return(-1); //---- the last calculated bar must be recalculated if(counted_bars > 0) counted_bars--;
7.仅保留函数 int start() 的内容;
8.添加函数 Get_RAVISeries() 的声明:
bool Get_RAVISeries(int Number, string symbol,int timeframe, bool NullBarRecount, int Period1, int Period2, int MA_Metod, int PRICE, double& InputBuffer[])
9.用函数 Get_RAVISeries() 的外部变量的缓冲区名称 (InputBuffer) 相应地替代代码中的指标缓冲区名称 (ExtBuffer);
10.添加变量 LastCountBar 的声明;
11.根据函数 Get_RAVISeries. mqh 的调用次数,将静态变量 IndCounted 转变成数组 IndCounted[Number] 并添加用于更改变量大小的代码:
//----+ changing the size of static variables if(ArraySize(IndCounted) < Number + 1) { ArrayResize(IndCounted, Number + 1); }
12.检查变量 NullBarRecount 是否为真:
if(!NullBarRecount) LastCountBar = 1;
13.在所有指标计算循环周期中,将零更改为 LastCountBar:
for(bar = limit; bar >= LastCountBar; bar--)
14.在检查柱数是否足够时,在代码的开头部分进行更改:将 return(0) 更改为 return(false);
15.在结尾处,用 return(true) 替代 return(0)。
在完成所有代码更改之后,我们将获得指标函数 Get_RAVISeries():
//+------------------------------------------------------------------+ //| Get_RAVISeries.mqh | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ bool Get_RAVISeries(int Number, string symbol,int timeframe, bool NullBarRecount, int Period1, int Period2, int MA_Metod, int PRICE, double& InputBuffer[]) { //---- getting the number of all bars of a chart int IBARS = iBars(symbol, timeframe); //---- Checking whether the bars number is enough for further calculation if(IBARS < MathMax(Period1, Period2)) return(false); //---- EMULATION OF INDICATOR BUFFERS if(ArraySize(InputBuffer) < IBARS) { ArraySetAsSeries(InputBuffer, false); //---- ArrayResize(InputBuffer, IBARS); //---- ArraySetAsSeries(InputBuffer, true); } //----+ inserting static variables of memory static int IndCounted[]; //----+ changing the size of static variables if(ArraySize(IndCounted) < Number + 1) { ArrayResize(IndCounted, Number + 1); } //----+ Introducing an integer variable int LastCountBar; //----+ Checking whether the recalculation of the zero bar is allowed if(!NullBarRecount) LastCountBar = 1; //----+ Introducing floating point variables double MA1,MA2,result; //----+ Introducing integer variables and getting alreadu calculated bars int MaxBar, bar, limit, counted_bars = IndCounted[Number]; //----+ Remembering the amount of all chart bars IndCounted[Number] = IBARS - 1; //---- determining the number of the oldest bar, // starting from which new bars will be recalculated limit = IBARS - counted_bars - 1; // Print(IBARS - counted_bars); //---- determining the number of the oldest bar, // starting from which all bars will be recalculated MaxBar = IBARS - 1 - MathMax(Period1, Period2); //---- zero initialization if(limit > MaxBar) { limit = MaxBar; for(bar = IBARS - 1; bar >= 0; bar--) { InputBuffer[bar] = 0.0; } } //----+ THE FIRST CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= LastCountBar; bar--) { MA1 = iMA(symbol, timeframe, Period1, 0, MA_Metod, PRICE, bar); MA2 = iMA(symbol, timeframe, Period2, 0, MA_Metod, PRICE, bar); //---- result = ((MA1 - MA2) / MA2)*100; InputBuffer[bar] = result; } //----+ return(true); } //+------------------------------------------------------------------+
当然,所有操作都很棒!难度还行,也不是太简单。但是出现了一个问题 - 此指标函数与自定义指标的计算是否相同?
测试自定义指标函数的计算准确性
我们需要检查函数计算结果是否等于自定义指标计算的结果。为此,最好的 Expert Advisor 并不进行交易,而是仅接收来自自定义指标 RAVI.mq4 和自定义函数 Get_RAVISeries() 的值,找出其中的差异,然后,将指标值、自定义函数值以及这两者之间的差发送给日志文件。我们要做的就是分析日志文件的内容,针对自定义函数 Get_RAVISeries() 和指标 RAVI.mq4 的算法的一致性做出最终结论。
//+------------------------------------------------------------------+ //| Get_RAVISeriesTest.mq4 | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ #property copyright "Copyright © 2007, MetaQuotes Software Corp." #property link "https://www.metaquotes.net/" //---- INPUT EA PARAMETERS extern bool NullBarRecount = true; //---- indicator buffers double RAVI_Buffer0[]; double RAVI_Buffer1[]; double RAVI_Buffer2[]; //+------------------------------------------------------------------+ //| Get_RAVISeries() function | //+------------------------------------------------------------------+ #include <Get_RAVISeries.mqh> //+------------------------------------------------------------------+ //| Custom Expert initialization function | //+------------------------------------------------------------------+ int init() { //---- initialization end return(0); } //+------------------------------------------------------------------+ //| Custom Expert iteration function | //+------------------------------------------------------------------+ int start() { //---- double Ind_Velue, Resalt; //---- if(!Get_RAVISeries(0, Symbol(), 0, NullBarRecount, 10, 20, 1, 0, RAVI_Buffer0)) return(0); if(!Get_RAVISeries(1, Symbol(), 240, NullBarRecount, 25, 66, 2, 1, RAVI_Buffer1)) return(0); if(!Get_RAVISeries(2, Symbol(), 1440, NullBarRecount, 30, 70, 3, 3, RAVI_Buffer2)) return(0); //---- getting indicator values for the test 0 Ind_Velue = iCustom(NULL, 0, "RAVI", 10, 20, 1, 0, 0, 2); Resalt = RAVI_Buffer0[2] - Ind_Velue; Print(" " + Ind_Velue + " " + RAVI_Buffer0[2] + " " + Resalt+""); //---- getting indicator values for the test 1 Ind_Velue = iCustom(NULL, 240, "RAVI", 25, 66, 2, 1, 0, 2); Resalt = RAVI_Buffer1[2] - Ind_Velue; Print(" " + Ind_Velue + " " + RAVI_Buffer1[2] + " " + Resalt+"" ); //---- getting indicator values for the test 2 Ind_Velue = iCustom(NULL, 1440, "RAVI", 30, 70, 3, 3, 0, 2); Resalt = RAVI_Buffer2[2] - Ind_Velue; Print(" " + Ind_Velue + " " + RAVI_Buffer2[2] + " " + Resalt + ""); //---- return(0); } //+------------------------------------------------------------------+
在策略测试程序中,启动 Expert Advisor Get_RAVISeriesTest。自然,在 MetaTrader 客户端中,编译的文件 RAVI.ex4 必须位于文件夹 \expert\indicators 中,文件 Get_RAVISeries.mqh 必须位于 \expert \include 文件夹中。在策略测试程序日志中以及日志文件中,我们看到两个包含指标值及其函数形式的模拟的列,第三列显示这些值之间的差。最后一列的所有值都等于零。这意味着,这两种情况下的值相同。我们可以得出结论:已成功完成编写指标自定义函数的任务。
总结
在下一篇关注本主题的文章中,我们将分析有关编写此类函数和基于此类函数实现简单的 Expert Advisor 的更难的示例。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/1457
