将指标代码转移至 Expert Advisor 代码。指标结构
简介
为了更好地理解作者的意图,推荐阅读以下材料:- MetaQuotes Software Corp. 创建自定义指标的特点 (Features of Custom Indicators Creation)。https://www.mql5.com/zh/articles/1497
- Nikolay Kositsin.在一些指标中多次重新计算空柱 (Multiple Null Bar Re-Count in Some Indicators)。https://www.mql5.com/zh/articles/1411
在讨论本文标题中指示的主题之前,我们最好还是先来看看以下问题:“如果在大多数情况下,使用自定义指标运行的 EA 看起来比其他类似程序更简单易用,自定义指标自身的代码中包含了执行操作的所有必要项目,那么为什么我们还要将指标代码转移到 EA 代码中?特别是我们还考虑到一个事实,即如果代码编写正确,两种情况下的结果将完全相同!”
我认为,在两种情况下这种转移是必要的:
- 如果在 EA 计算中,根本没有用到在零柱上计算出的值,我们自然会想要忽略在零柱和第一根柱上的不必要的重新计算。这可将此类 EA 优化所需的时间缩短三倍,对于非常复杂并要占用大量资源的代码来说是相当有用的!
- Expert Advisor 用作商业用途时可最大程度防止代码被反编译。
在第二种情况下,显而易见,代码转移是相当合理的。在第一种情况下,大多数时候重新编写自定义指标的代码是很轻松的,无需进行不必要的计算!此类指标自然仅适用于 Expert Advisor,不适用于交易!那么,让我们从此类问题解决方案着手,开始讨论吧。
指标优化示例
首先,请注意一个自定义指标的以下代码片段:
int start() { int limit; int counted_bars = IndicatorCounted(); //---- the last calculated bar will be recalculated if(counted_bars > 0) counted_bars--; limit = Bars - counted_bars - 1; //---- the main cycle for(int i = limit; i >= 0; i--) { //---- ExtBlueBuffer[i] = iMA(NULL, 0, JawsPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); ExtRedBuffer[i] = iMA(NULL, 0, TeethPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); ExtLimeBuffer[i] = iMA(NULL, 0, LipsPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); } //---- return(0); }
在这种情况下,下面这一行对我们很重要:
if(counted_bars > 0) counted_bars--;
确认变量 'counted_bars' 值减小 1 的意义在于:如果某自定义指标不包括此行,则当零柱更改时,该自定义指标可能会将错误的值从其缓冲区发送到 EA 中。EA 中的指标曲线看起来会相当“扭曲”。
但这里要注意的是,一些平滑算法对最开始进行平滑处理的基准点十分敏感。即,为了获得相同的值,指标的周期与 EA 中指标代码的周期中,最早的柱(所有柱都从其开始重新计算)的数量应是一致的。
这里有个示例,说明了为实现指标代码在 EA 中的更快操作而采取的这种优化方式。在主要指标周期中,将 0 更改为 1,之后指标停止重新计算其在零柱上的值。
// instead of for(int i = limit; i >= 0; i--) // write for(int i = limit; i >= 1; i--)
结果,源代码将如下所示:
int start() { int limit; int counted_bars = IndicatorCounted(); //---- the last bar will be recalculated if(counted_bars > 0) counted_bars--; limit = Bars - counted_bars - 1; //---- the main cycle //now the cycle ends in 1 for(int i = limit; i >= 1; i--) { //---- ExtBlueBuffer[i] = iMA(NULL, 0, JawsPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); ExtRedBuffer[i] = iMA(NULL, 0, TeethPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); ExtLimeBuffer[i] = iMA(NULL, 0, LipsPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); } //---- return(0); }
如果您真的要在真金白银的实际交易中使用 EA,应仔细检查 Expert Advisor 中的所有细节以及 EA 使用的指标。此外,这些工作都应亲力亲为!我认为花几天时间彻底理解指标结构及其代码的优化方式,比起用三个月时间耐心地使用一个从草草编写的指标中获取值的 EA,会更加轻松也更为明智。
因此,必须清楚的是,将指标代码转移到 EA 代码中需要确切的理由。如果指标编写正确,即使未转移,EA 的操作也不会慢太多。使用自定义指标先编写一个 EA 代码,然后在此表格中进行检查,这样会比较容易。如果 EA 确实表现出完美的结果,可进一步优化代码,将自定义指标的调用逐个转变为指标代码片段。
必须注意,更改 EA 代码之后,不应再修改测试的损失值和利润值!
现有指标数量庞大,其中每个指标都有其独特的代码。这就是很难创建一个适用于所有指标的通用代码转移方式的原因。而还有一种情况会加剧这一问题,即同一个自定义指标可能在 EA 代码中多次出现。如果一个指标代码还算简单,可以将其写入自定义函数,并且在这种情况下将自定义指标转换为函数是相当简单的。但更常见的情况是 EA 代码繁杂庞大,几乎无法检测到其中的错误。我们所有的努力都徒劳无益。
指标结构的总体方案
//+------------------------------------------------------------------+ //| IndicatorPlan.mq4 | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ #property copyright "Copyright © 2007, MetaQuotes Software Corp." #property link "https://www.metaquotes.net/" //---- drawing the indicator in the main window #property indicator_chart_window //---- number of indicator buffers #property indicator_buffers 1 //---- indicator color #property indicator_color1 Gold //---- INPUT PARAMETERS OF THE INDICATOR extern int period0 = 15; extern int period1 = 15; extern int period2 = 15; //---- indicator buffers double Ind_Buffer0[]; double Ind_Buffer1[]; double Ind_Buffer2[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int init() { //---- defining the graph execution style SetIndexStyle(0, DRAW_LINE); //---- 3 indicator buffers are used for calculation IndicatorBuffers(3); SetIndexBuffer(0, Ind_Buffer0); SetIndexBuffer(1, Ind_Buffer1); SetIndexBuffer(2, Ind_Buffer2); //---- end of initialization return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int start() { //---- Checking whether the bars number is enough for further calculation if(Bars < period0 + period1 + period2) return(0); //----+ Insertion of variables with a floating point double Resalt0, Resalt1, Resalt2; //----+ Insertion of integer variables and getting calculated bars int limit, MaxBar,bar, counted_bars = IndicatorCounted(); //---- checking for possible errors if(counted_bars < 0) return(-1); //---- the last calculated bar must be recalculated if(counted_bars > 0) counted_bars--; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated limit = Bars - counted_bars - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated MaxBar = Bars - 1 - (period0 + period1 + period2); //---- initialization of zero if(limit > MaxBar) { limit = MaxBar; for(bar = Bars - 1; bar >= MaxBar; bar--) { Ind_Buffer0[bar] = 0.0; Ind_Buffer1[bar] = 0.0; Ind_Buffer2[bar] = 0.0; } } //----+ THE FIRST CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt1 calculation based on the external // variable period1 Ind_Buffer1[bar] = Resalt1; } //----+ THE SECOND CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt2 calculation // based on the values of the buffer Ind_Buffer1[] // and external variable period2 Ind_Buffer2[bar] = Resalt2; } //----+ THE MAIN CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt0 calculation // based on the values of the buffer Ind_Buffer2[] // and external variable0 Ind_Buffer0[bar] = Resalt0; } return(0); } //+------------------------------------------------------------------+
当然,实际的指标在计算中可能会有不同数量的反射指标值、不同数量的指标缓冲区和不同数量的指标缓冲区值计算周期,但这并不会改变给定方案的意义。其他情况下也都是完全类似的。
现在让我们从方案元素中排除此上下文中对我们没有意义且在 Expert Advisor 中不必要的元素。
//+------------------------------------------------------------------+ //| IndicatorPlan1.mq4 | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ //---- INPUT PARAMETERS OF THE INDICATOR extern int period0 = 15; extern int period1 = 15; extern int period2 = 15; //---- indicator buffers double Ind_Buffer0[]; double Ind_Buffer1[]; double Ind_Buffer2[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int init() { //---- end of initialization return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int start() { //---- Checking whether the bars number is enough for further calculation if(Bars < period0 + period1 + period2) return(0); //----+ Insertion of variables with a floating point double Resalt0, Resalt1, Resalt2; //----+ Insertion of integer variables and getting calculated bars int limit, MaxBar, bar, counted_bars = IndicatorCounted(); //---- checking for possible errors if(counted_bars < 0) return(-1); //---- the last calculated bar must be recalculated if(counted_bars > 0) counted_bars--; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated limit = Bars - counted_bars - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated MaxBar = Bars - 1 - (period0 + period1 + period2); //---- initialization of zero if(limit > MaxBar) { limit = MaxBar; for(bar = Bars - 1; bar >= MaxBar; bar--) { Ind_Buffer0[bar] = 0.0; Ind_Buffer1[bar] = 0.0; Ind_Buffer2[bar] = 0.0; } } //----+ THE FIRST CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt1 calculation // based on the external variable period1 Ind_Buffer1[bar]= Resalt1; } //----+ THE SECOND CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt2 calculation // based on the values of the buffer Ind_Buffer1[] // and external variable period2 Ind_Buffer2[bar] = Resalt2; } //----+ THE MAIN CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt0 // based on the values of the buffer Ind_Buffer2[] // and external variable period0 Ind_Buffer0[bar] = Resalt0; } return(0); } //+------------------------------------------------------------------+
只要不出现以下几个令人遗憾的小失误,此代码可以轻松放到 Expert Advisor 代码中:
- 首先请别忘了,函数 IndicatorCounted() 在 Expert Advisor 中不起作用!
- 我们也无法在初始化块中将自定义数组转变为指标数组!
因此,为了充分保护指标代码,我们首先要开发一个类似于函数 IndicatorCounted() 的函数,并通过某种方式在 Expert Advisor 中模拟指标缓冲区的类似项。遗憾的是,用标准函数在 EA 中直接模拟指标缓冲区不可行。目前为止,还没有适用于 Expert Advisor 的函数 SetIndexBuffer() 和 IndicatorBuffers() 的类似项!所以必须通过其他方法解决这个问题。此外,MQL4 中有足够的相关选项。
在 Expert Advisor 中模拟指标缓冲区
首先我们来详细地看一下指标缓冲区中执行的流程。
- 当一个指标连接到图表,且终端正常工作时,分配给指标缓冲区变量的值在周期之间不会丢失。
- 如果图表上的零柱(最后一个柱)有更改,该指标缓冲区的所有元素都会被转移。
- 如果新增了一个柱,指标中的变量限值将从 1 变为 2,且所有缓冲区元素移一位,即零柱变成第一个柱,第一个变为第二个,以此类推。
- 指标缓冲区的维数会自然而然地发生变化。如果下一价格变动时柱未发生变化,所有缓冲区元素都原地不动。
现在开始模拟指标缓冲区。为实现这一目的,我们将使用以下 MQL4 标准函数:ArraySize()、ArrayResize() 和 ArraySetAsSeries()。指标缓冲区模拟的代码相当简单,其工作原理如下:当零柱发生变化时,将还原缓冲区中元素定义的正向顺序,并使用函数 ArrayResize() 从新柱将新单元添加到缓冲区,之后以反向顺序安排缓冲区中元素定义,且空白单元显示在模拟指标缓冲区中的第一批单元内。
//---- INDICATOR BUFFERS EMULATION int NewSize = iBars(symbol, timeframe); //---- Checking the change of the zero bar if(ArraySize(Ind_Buffer0) < NewSize) { //---- Set the direct indexing direction in the array ArraySetAsSeries(Ind_Buffer0, false); ArraySetAsSeries(Ind_Buffer1, false); ArraySetAsSeries(Ind_Buffer2, false); //---- Change the size of the emulated indicator buffers ArrayResize(Ind_Buffer0, NewSize); ArrayResize(Ind_Buffer1, NewSize); ArrayResize(Ind_Buffer2, NewSize); //---- Set the reverse indexing direction in the array ArraySetAsSeries(Ind_Buffer0, true); ArraySetAsSeries(Ind_Buffer1, true); ArraySetAsSeries(Ind_Buffer2, true); } //----
顺便提一下,当八个指标缓冲区不足以执行中间计算时,这种指标缓冲区模拟方法也适用于指标。随附文件 SMI.mq4 和 SMI_New.mq4 有相关示例。
替代函数 IndicatorCounted()
//+------------------------------------------------------------------+ //| NewIndicatorPlan1.mqh | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ //---- INPUT INDICATOR PARAMETERS extern int period0 = 15; extern int period1 = 15; extern int period2 = 15; //---- indicator buffers double Ind_Buffer0[]; double Ind_Buffer1[]; double Ind_Buffer2[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int init() { //---- initialization end return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int start() { //---- Checking whether the bars number is enough for further calculation if(Bars < period0 + period1 + period2) return(0); //---- INDICATOR BUFFERS EMULATION if(ArraySize(Ind_Buffer0) < Bars) { ArraySetAsSeries(Ind_Buffer0, false); ArraySetAsSeries(Ind_Buffer1, false); ArraySetAsSeries(Ind_Buffer2, false); //---- ArrayResize(Ind_Buffer0, Bars); ArrayResize(Ind_Buffer1, Bars); ArrayResize(Ind_Buffer2, Bars); //---- ArraySetAsSeries(Ind_Buffer0, true); ArraySetAsSeries(Ind_Buffer1, true); ArraySetAsSeries(Ind_Buffer2, true); } //----+ INSERTION OF A STATIC INTEGER MEMORY VARIABLE static int IndCounted; //----+ Insertion of variables with a floating point double Resalt0, Resalt1, Resalt2; //----+ Insertion of integer variables and getting calculated bars int limit, MaxBar, bar, counted_bars = IndCounted; //---- checking for possible errors if(counted_bars < 0) return(-1); //---- the last calculated bar must be recalculated if(counted_bars > 0) counted_bars--; //----+ REMEMBERING THE AMOUNT OF ALL BARS OF THE CHART IndCounted = Bars - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated limit = Bars - counted_bars - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated MaxBar = Bars - 1 - (period0 + period1 + period2); //---- initialization of zero if(limit > MaxBar) { limit = MaxBar; for(bar = Bars - 1; bar >= 0; bar--) { Ind_Buffer0[bar] = 0.0; Ind_Buffer1[bar] = 0.0; Ind_Buffer2[bar] = 0.0; } } //----+ THE FIRST CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt1 calculation // based on the external variable period1 Ind_Buffer1[bar] = Resalt1; } //----+ THE SECOND CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt2 calculation // based on the values of the buffer Ind_Buffer1[] and external variable period2 Ind_Buffer2[bar] = Resalt2; } //----+ THE MAIN CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt0 calculation // based on the values of the buffer Ind_Buffer2[] and external variable period0 Ind_Buffer0[bar] = Resalt0; } return(0); } //+------------------------------------------------------------------+
指标代码的进一步转换及其结构的最终方案
当然,假如某个 Expert Advisor 中需要在当前图表上使用此指标,且仅需使用一次,我们可以仅将指标代码分批转移到 EA 代码中!如果这个指标要使用两次,我们只需在第二个用例中更改所有指标变量的名称,然后再次添加此代码。但这样一来,Expert Advisor 将变得更加复杂。
处理其他时间范围内的数据也很简单。用类型的时间序列替代 Bars 类型的预定义变量
iBars(string symbol, int timeframe);
NULL - for string symbol;, 0 (in timeseries) - for int timeframe;, Close[bar] - for
iClose(string symbol, int timeframe, bar);
等等。
现在我们来分析指标行:
//---- checking for possible errors if(counted_bars < 0) return(-1); //---- the last calculated bar must be recalculated if(counted_bars > 0) counted_bars--;
对于我们的指标结构类型中提供的函数 IndicatorCounted() 的替代项,指标 counted_bars 绝不会小于 0。因此,Expert Advisor 中的这一行
//---- checking for possible errors if(counted_bars < 0) return(-1);
可以省略。而下面两行:
//---- the last calculated bar must be recalculated if(counted_bars > 0) counted_bars--;
是相同的。应该把这几行删去,它们只会对第一根柱进行不必要的重新计算,拖慢 EA 工作进度,并且在 EA 操作中,上述检查是毫无用处的。之后,要转移到 EA 中的最终代码如下所示:
//+------------------------------------------------------------------+ //| NewIndicatorPlan2.mqh | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ //---- INPUT INDICATOR PARAMETERS extern int period0 = 15; extern int period1 = 15; extern int period2 = 15; //---- indicator buffers double Ind_Buffer0[]; double Ind_Buffer1[]; double Ind_Buffer2[]; //---- DECLARING VARIABLES FOR CHOOSING A CHART string symbol; int timeframe; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int init() { //---- CHOOSING A CHART FRO INDICATOR CALCULATION symbol = Symbol();//INITIALIZATION OF THE VARIABLE symbol; timeframe =240;//INITIALIZATION OF THE VARIABLE timeframe; //---- end of initialization return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int start() { // GETTING THE AMOUNT OF ALL BARS OF THE CHART int IBARS = iBars(symbol, timeframe); //---- Checking whether the bars number is enough for further calculation if(IBARS < period0 + period1 + period2) return(0); // INDICATOR BUFFERS EMULATION if(ArraySize(Ind_Buffer0) < IBARS) { ArraySetAsSeries(Ind_Buffer0, false); ArraySetAsSeries(Ind_Buffer1, false); ArraySetAsSeries(Ind_Buffer2, false); //---- ArrayResize(Ind_Buffer0, IBARS); ArrayResize(Ind_Buffer1, IBARS); ArrayResize(Ind_Buffer2, IBARS); //---- ArraySetAsSeries(Ind_Buffer0, true); ArraySetAsSeries(Ind_Buffer1, true); ArraySetAsSeries(Ind_Buffer2, true); } // INSERTION OF A STATIC INTEGER MEMORY VARIABLE static int IndCounted; //----+ Insertion of variables with a floating point double Resalt0, Resalt1, Resalt2; //----+ Insertion of integer variables and GETTING ALREADY CALCULATED BARS int limit, MaxBar, bar, counted_bars = IndCounted; //----+ REMEMBERING THE AMOUNT OF ALL BARS OF THE CHART IndCounted = 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--) { Ind_Buffer0[bar] = 0.0; Ind_Buffer1[bar] = 0.0; Ind_Buffer2[bar] = 0.0; } } //----+ THE FIRST CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt1 calculation based on the external // variable period1 Ind_Buffer1[bar] = Resalt1; } //----+ THE SECOND CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt2 calculation // based on the values of the buffer Ind_Buffer1[] and the external variable period2 Ind_Buffer2[bar] = Resalt2; } //----+ THE MAIN CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt0 calculation // based on the values of the buffer Ind_Buffer2[] and the external variable period0 Ind_Buffer0[bar] = Resalt0; } return(0); } //+------------------------------------------------------------------+
这里我们应该考虑到某个时刻。多次重新计算零柱时,在计算周期开始时的第一根柱上会看到指标,记住一些用于将代码返回其初始状态的变量的值(文章)。在 Expert Advisor 中删除最后两行后,按上述方式,针对零柱(而不再是第一根柱)记住相关值。在计算周期开始时,此类指标通常包含以下代码片段:
// Saving the variables values if(bar == 1) if(((limit == 1) && (time == Time[2])) || (limit > 1)) { time = Time[2]; // These variables are not interesting for us PRICE = price; TREND = trend; RESALT = Resalt; } //+------------------------------------------------------------------+
应修改此代码片段,以保存零柱而非第一根柱上的变量值。所有“1”都应替换为“0”,所有“2”替换为“1”。如果此代码不是为在当前图表中使用而准备的,则还应更改时间序列数组的参考,即将
time = Time[1];
更改为
time = iTime(symbol, timeframe, 1);
结果,更改后的代码片段如下所示:
// Saving variables values if(bar == 0) if(((limit == 0) && (time == iTime(symbol, timeframe, 1))) || (limit > 0)) { time = iTime(symbol, timeframe, 1); PRICE = price; TREND = trend; RESALT = Resalt; } //+------------------------------------------------------------------+
指标代码也可包含本人自己开发的平滑函数,如 XXXSeries()。要在 EA 代码中使用带此类函数的代码片段,这些函数应替换为其 EA 类似函数。
总结
毫无疑问,目前给出的这种将指标代码转移到 EA 代码的算法还相当粗糙,不是最佳的算法,但文本的目的并不是细致地描述此流程的所有细节。本文的主要目的是一窥指标的概貌,并以最简化的形式分析代码转移的总体思路,而不是将重点放在次要的细节上。我认为我们已达成了这个目标!在下一篇文章中,我们将分析 Expert Advisor 的一般结构和指标函数的结构方案
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/1456