MQL5的自定义指标
介绍 我们终于有机会试试新交易终端- metaTrader 5了。毫无疑问,它是值得关注的,与它的前身相比,它有许多新的特性。这个平台的重要优势有: 编程语言上的根本修改,允许用面向对象的方法编程。还允许丰富的结构化编程。 代码执行速度比metaTrader 4快多了。 显示必要信息的能力有了本质的增加。 新终端和编程语言的许多新特性和能力无法一一列出,一些新鲜的东西值得我们用专门的文章分别讨论。暂时还没有面向对象的编程代码,对于开发者而言,还有很多重要的课题。在这篇文章里,我们研究一下指标,他们的结构、画法、类型和编程细节等与MQL4的对比。这篇文章并不复杂,而且,这里研究的东西可以用后面的附件直接在终端里验证。 1、一般的结构 与MQL4相比,指标的一般结构没有改变。 象以前一样,有三个函数:初始化函数、数据处理过程、卸载函数 象以前一样,许多指标参数可以用属性(#property)定义。他们有许多是专门为指标设计的,属性和输入参数象以前一样,在全局背景下定义。 例如,让我们看一下彩色RSI指标的执行。下面的代码是删节版,完整版在Color_RSI.mq5里。 //+------------------------------------------------------------------+ //|Color_RSI.mq5 | //+------------------------------------------------------------------+ // information properties #property copyright "TheXpert" #property link"theforexpert@gmail.com" #property version "1.00" // Indicator description -- the total length should not exceed 511 characters with CR symbols #property description "Indicator programming demo in MQL5" #property description "RSI indicator custom painting" 上面指定的属性显示在指标信息面板上(属性标签“Common”)。看起来象下面这样: // the indicator properties#property indicator_separate_window // indicator will be displayed in the separate subwindow //#property indicator_minimum 0 //#property indicator_maximum 100#property indicator_buffers 2 // buffers used#property indicator_plots 1 // buffers displayed#property indicator_color1 DarkSalmon, DeepSkyBlue // we use 2 colors#property indicator_type1 DRAW_COLOR_LINE // and special color style[/pre]These properties are the indicator properties. The other properties description can be found in the help. //---- buffersdouble Values[]; // buffer for valuesdouble ValuesPainting[]; // buffer for color indexesdouble BufferForCopy[]; // additional buffer for copying // indicator input parameter sinput string _1 = "RSI Parameters"; input intRSIPeriod= 5; input ENUM_APPLIED_PRICE AppliedPrice = PRICE_CLOSE; input string _2 = "Color settings"; input colorDown = DarkSalmon; input colorUp = DeepSkyBlue; // variable to store indicator handleint RSIHandle; 这里是指标输入参数和全局变量(不要和客户终端全局变量搞混)。指标输入参数用标识符“input”指定。 现在为指标设定枚举是可能的了,有时对于避免选错参数很有用。例如:可以用下拉框把AppliedPrice参数有效值列出。 因此,所有的枚举,包括用户定义的,将在同一下拉框中列出。例如下面的参数: //...enum DayOfWeek{ Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}; input DayOfWeek Day; //... 将会象下面这样显示: int onInit(){ // set the indicator buffers // set Values as a buffer for displaying SetIndexBuffer(0, Values, INDICATOR_DATA); // set ValuesPainting as buffer for colors SetIndexBuffer(1, ValuesPainting, INDICATOR_COLOR_INDEX); // Set beginning of Values buffer drawing PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, RSIPeriod); // Set indicator name IndicatorSetString(INDICATOR_SHORTNAME, "Color RSI(" + string(RSIPeriod) + ")"); // Set empty value for Values buffer PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); // Set colors for the buffer PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, Down); PlotIndexSetInteger(0, PLOT_LINE_COLOR, 1, Up); RSIHandle = iRSI(NULL, 0, RSIPeriod, AppliedPrice); ArrayResize(BufferForCopy, 3); // Set the indexation order for buffers (as series) ArraySetAsSeries(Values, true); ArraySetAsSeries(ValuesPainting, true); ArraySetAsSeries(BufferForCopy, true); return(0); } OnInit是指标初始化函数。在这里我们配置指标缓存数和他们的属性,并且定义指标变量(不能在属性段定义或必须动态设定的)。还有原始数据的初始化,包括指标句柄的分配。 // Function for data calculation // note that the function parameters can be renamed 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& tick_volume[], const long& volume[], const int& spread[]) { int toCount = (int)MathMin(rates_total, rates_total - prev_calculated + 1); if (prev_calculated < 0 || prev_calculated > rates_total) { toCount = rates_total; } int copied = CopyBuffer(RSIHandle, 0, 0, toCount, Values); if (copied == -1) { Print("Error while data copying. Error №", GetLastError()); return 0; } // Coloring. Yes, now it became so simple for (int i = toCount - 2; i >= 0; --i) { if (Values[i + 1] != EMPTY_VALUE && Values > Values[i + 1]) ValuesPainting = 1; else ValuesPainting = 0; } return rates_total; } OnCalculate是数据计算函数。这个函数有两种形式。这里是它的标准形式: // the function is not obligatory in a code OnDeinit是指标反初始化函数。通常为了释放资源是必须的,如文件句柄。一些其他情况下不是必须的。 2、指标的两种形式 第一种是标准形式,象我们在MQL4中用过的一样,只有一点修改。OnCalculate函数替代了Start函数。标准形式象下面这样: int onCalculate(const int rates_total, // Arrays size const int prev_calculated, // Bars processed on the previous callconst datetime& time[], // Data for the current chart and timeframe... const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int& spread[]) { return rates_total; } 为了减少准备数据拷贝的代码量,图表数据作为数组直接传入指标参数。此外,有效K线数作为函数的第一个参数,自上次调用或首次调用以来处理过的K线数作为第二个参数。 在第一次指标调用时这个参数为0值。这个参数是IndicatorCounted()函数的替代,这对于许多开发者来说是不方便的。 第二种形式是MQL4里i<…>OnArray类函数的替代和扩展。终端示例里有一个这种类型的指标——Custom Moving Average.这类指标是为用户选择的数据处理过程准备的,包括自定义指标。这类指标数据处理函数看起来象这样: int onCalculate (const int rates_total,// the size of the price[] array const int prev_calculated,// bars calculated in the previous call const int begin,// where notional data start from const double& price[] // data array for calculation { return rates_total;} 最后一个参数是用户选择的待处理数据。如果你想使用一个有很多数据缓存的指标,第一个指标缓存将用做数据处理。 第一个指标的数据意味着这个指标将被应用到首先附加在选定图表上的指标。 先前指标的数据意味着这个指标将被应用到最后附加在选定图表上的指标。 这些指标可以用来组合所有堆栈。例如,用Custom Moving Average指标可以得到三重平滑均线,利用第一条指标数据进行处理,然后第二个指标平滑第一个,然后第三个指标平滑第二个: 有很多执行这种形式的标准指标。因此,当你看到applied_price_or_handle这样的指标提示: { // ... RSIHandle = iRSI(NULL, 0, RSIPeriod, AppliedPrice); SmoothHandle = iMA(NULL, 0, SmoothPeriod, 0, MODE_EMA, RSIHandle); // ...} 这种形式有另一种新应用——写通用指标的能力。这样指标的一个例子是附件中的Direction_Brush.mq5。 结果显示在上图中。这个例子中,趋势颜色被分离到一个独立的实体,在另一个指标中执行。 当然,他们的应用是有限制的,因为只能用在0缓存指标中。尽管如此,我认为这种指标是有用的。 另一方面,当你写一个自定义指标时,你应该充分考虑它,因为在0缓存指标中主要信息处理允许避免在一个指标中执行多个功能。 粗略浏览一下,应用范围并不像看起来那样狭窄: 指标参数表着色(顶、方向、水平、片段等),包括风格形象; 不同条件下的不同信号; 收集并显示统计数据——例如,数据分布; 通用指标的结构可以用一个缓存计算——例如,平均线、ZIGZAG。 上面提到的特征并不全面。我想许多另人印象深刻的设计以后会被发现。 3、存取数据 在MQL5中数据存取原则已经改变。重点变化在数组,做为一个结果,计算速度取得了引人注目的增长,现在,不必为每个值创建一个数组并调用iCustom函数了。取而代之的是,调用一个函数就可以得到必要的数据了,然后直接使用已拷贝到指定数组的想要的数据。 数据拷贝使用系统函数CopyBuffer执行。在帮助文件中可以找到这个函数的描述。 指标和静态数组能拷贝的最大数据量由数组的大小决定。动态数组的大小在拷贝数据量超过它的大小时能够改变。除此以外,还有许多存取历史数据的函数: Function Description CopyBuffer Gets data of a specified buffer of a certain indicator in the necessary quantity. CopyRates Gets history data of MqlRates structure of a specified symbol-period in specified quantity into the rates_array array. CopyTime The function gets to time_array history data of bar opening time for the specified symbol-period pair in the specified quantity. CopyOpen The function gets into open_array the history data of bar open prices for the selected symbol-period pair in the specified quantity. CopyHigh The function gets into high_array the history data of highest bar prices for the selected symbol-period pair in the specified quantity. CopyLow The function gets into low_array the history data of minimal bar prices for the selected symbol-period pair in the specified quantity. CopyClose The function gets into close_array the history data of bar close prices for the selected symbol-period pair in the specified quantity. CopyTickVolume The function gets into volume_array the history data of tick volumes for the selected symbol-period pair in the specified quantity. CopyRealVolume The function gets into volume_array the history data of trade volumes for the selected symbol-period pair in the specified quantity. CopySpread The function gets into spread_array the history data of spread values for the selected symbol-period pair in the specified quantity. 祥细描述在帮助文件里有。 这个数据只在指标的一种形式里传递,其他的有他们自己的形式。 基于历史数据数组没有必要双重存在的事实,建议使用一个动态的无指标的缓存存储数据。 4、指标缓存 指标缓存数没有限制。 现在你没有必要思考怎样容纳正确的信息、怎样高效执行中间计算、如何创建指标组了。但是我们不能忘记缓存的存储是需要内存的。因此,如果你指定了一个历史纵深达1000000柱的终端并在一分钟图表上附加上一组“厚重的”指标组,那么,当终端吃掉上G的内存时你不要惊讶。 缓存的本质经历了一些改变。使用的缓存数量在属性段指定。#property indicator_buffers 2 // buffers used 这个值相当于缓存的数量。 显示的缓存数量在属性段设定:#property indicator_plots 1 // buffers displeyed 这里是些细节。许多绘制样式只需要一个INDICATOR_DATA缓存来绘制。然而,有些样式需要几个指标缓存来绘制。 象下面的绘制样式: DRAW_HISTOGRAM2 –需要2个指标缓存(HistogramSample.mq5)
DRAW_FILLING --需要2个指标缓存(CrossMa.mq5) INDICATOR_DATA –在图表上显示数据的缓存,这些缓存为制图和iCustom而准备。他们需要事先声明。在过于随意的命令下,代码编译会成功,但附加到图表时会失败; INDICATOR_COLOR_INDEX –存储颜色缓存。对于INDICATOR_DATA类型的彩色缓存索引的存储是必需的有一个特殊的颜色类型。这样的缓存(我们称为颜色缓存)应该在使用它的主缓存后声明; INDICATOR_CALCULATIONS –这种类型缓存是为存储辅助计算结果而准备的。他们不在图表上显示。 { // ... SetIndexBuffer(0, V2, INDICATOR_DATA); SetIndexBuffer(1, V2C,INDICATOR_COLOR_INDEX); SetIndexBuffer(2, V4, INDICATOR_DATA); SetIndexBuffer(3, V4C,INDICATOR_COLOR_INDEX); SetIndexBuffer(4, V1, INDICATOR_CALCULATIONS); SetIndexBuffer(5, V3, INDICATOR_CALCULATIONS); // ... return 0; } 客户指标还有一些特征: 图表上显示的指标的是可读的。指标索引必须与声明的一致; 颜色存储指标是可读的,但不都是,例如,上面指标代码中,V2C指标可读并可获得必要数据,但V4不能; 象MQL4一样,缓存不能做为中间计算,如果你想存取外部数据缓存,要声明为INDICATOR_DATA。 让我们探讨一下颜色缓存的细节: 在MQL4中,为每一个颜色建立一个缓存是必须的,但现在用颜色样式,你可以为一个缓存指定63种颜色。在某些情况下,可以优化使用的缓存数量,并节省内存。它也打开了书写指标的新能力,特别在显示风格方面。 另外,这种改革,在某些时候,与MQL4比,极大的简化了几种颜色的应用逻辑,最明显的例子——用颜色区分趋势。在MQL4里,这种简单的例子的实施需要3个缓存,并且编程复杂。 #property indicator_color1 DarkSalmon, DeepSkyBlue // we use 2 colors #property indicator_type1 DRAW_COLOR_LINE // and special color drawing style//---- buffersdouble Values[]; // buffer for valuesdouble ValuesPainting[]; // buffer for color indexesint onInit(){ // Registering buffers // Values as drawing buffer SetIndexBuffer(0, Values, INDICATOR_DATA); // ValuesPainting as color storage buffer SetIndexBuffer(1, ValuesPainting, INDICATOR_COLOR_INDEX); // … return(0);} // function for data calculationint onCalculate(){ int toCount = (int)MathMin(rates_total, rates_total - prev_calculated + 1); int copied = CopyBuffer(RSIHandle, 0, 0, rates_total, Values); if (copied == -1) {Print("Error while data copying. Error №", GetLastError());return 1; } // Colouring. Yes, now it became so simple for (int i = toCount - 2; i >= 0; --i) {if (Values > Values[i + 1]) ValuesPainting = 1; else ValuesPainting = 0; } return rates_total;} 更多的代码,我们得到下面的图: 用颜色类型绘图时,你应该注意细节。 对于颜色缓存,当使用动态颜色定义表时,最大的颜色数限制由indicator_colorN属性决定。例如: #property indicator_color1 DarkSalmon, DeepSkyBlue // we use 2 colors 然而,你可以象下面这样做: #roperty indicator_color1Red, Red, Red, Red, Red, Red, Red, Red, //… 你可以写成: #define C Red#property indicator_color1C, C, C, C, C, C, C, C, C, C, C, C, C, C, //… 一个缓存的最大颜色数是63。当颜色数超过最大值时,指标不能显示。这是一个显示风格的例子,使 缓存颜色表包涵最大颜色数是2,即使你动态设置了较大的颜色数。 你用颜色类型绘制指标时需注意一些细节。 总的来说,绘图机会引人注目的增长了,非常了不起。 5、数组 在提到数组索引之前,有必要说明一下数据排序类型——AsSeries属性。它不能由一些数组类型定义。 因此,需要的颜色数要写在一行——属性定义行。然后他们能动态改变。我发现的最短的颜色——“红色”。 这个标识不能由多维和静态数组设定。对于传递到OnCalculate函数的数组类型可以设置一个这样的标识。使用CopyBuffer拷贝的数据不依靠AsSeries属性。,但是,对于不同的缓存,执行方式是不同的。 对于指标缓存,必须拷贝整个有效历史纵深数据。必须记住这一点。 对于动态和静态数组,数据拷贝方式是从现在拷贝到过去。 CopyBuffer函数根据需要改变动态缓存(除指标的)的大小。 不建议使用静态数组拷贝数据。 总而言之,我建议你经常检查,怎样拷贝数据、怎样放置他们。最简单安全的方法是: 为所有使用存储历史数据的缓存设置AsSeries属性; 对于不同的缓存要注意他们的结构; 经常检查出错代码LastError的值。 另外,我强烈建议学习帮助文件,学习关于CopyBuffer函数和所有关于AsSeries属性的函数的内容。 6、IndicatorCounted函数 现在关于IndicatorCounted函数的辩论逐渐消失了,因为这个值被我们直接定义为上次函数调用的返回值。 通常总是返回当前函数调用所包涵的K线数rates_total值。 int onCalculate(const int rates_total,// array size const int prev_calculated,// bars processed after last call//... ) { return rates_total;} 然而,如果自从上次调用函数价格数据改变了(例如,历史数据被调用或历史数据空白被填充),那么输入参数prev_calculated会被终端置为0值。 还有,如果OnCalculate函数返回0值,那么指标数据不会在终端的数据窗口中显示。因此,如果你在调用历史数据期间或通讯失败后看到和执行指标,需返回1而不是0。 另一个有用的特征是可以决定在指标中计算多少K线。这在EA中计算大量数据很有用。这个函数是BarsCalculated,它的细节在帮助文件中可以找到。 这个函数还有一个有用的应用。如果指标没有被调用,调用过程可能要花一些时间——在建立指标句柄和使用它计算之间的时间。 初始化和预先计算需要时间。它由计算速度和指标细节决定。在这期间,CopyBuffer函数调用产生一个错误码4806——“需要的数据没有发现”。 BarsCalculated函数能用于决定指标数据拷贝是否有效。int WPRHandle = iWPR(NULL, 0, WPRPeriod); int copied = CopyBuffer(WPRHandle, 0, 0, bars, Values); int err = GetLastError(); if (-1 == copied) { if (4806 == err){ for (int i = 0; i < 1000; ++i) { if (BarsCalculated(WPRHandle) > 0) break; } copied = CopyBuffer(WPRHandle, 0, 0, bars, Values); err = GetLastError(); } } if (-1 == copied) { Print("Error when trying to get WPR values, last error is ", err, " bars ", bars); return 0; } //... 综上所述,我想说在这篇文章里,只讨论了一些细节。但我希望基本方面在这里已经展现。如果这篇文章总能成为你找到细节信息的指引,那就太好了。如果你发现了文章中的任何错误,或发现了其他重要的东西,请通知我,我将试着改正并尽可能提高这篇文章。我正计划列出最近的改变,除此以外,我希望在注释里出现一些有用的信息。这是我们仔细阅读它的原因。 附录 Color.mqh –包涵文件,把这个文件拷贝到MQL5/Include文件夹。这个文件是Toned_WPR指标需要的。 Color.mq5 --库文件,把这个文件拷贝到MQL5/Libraries文件夹。这个文件是Toned_WPR指标需要的 上面的文件都是指标。 ACKNOWLEDGMENTS 感谢 另外,我要感谢Mr. Victor Rustamov (granit77)阅读手稿,有益的讨论和建议。 |
打赏
最新创建圈子
- 新闻EA运行效果图圈 2019-05-05
圈主:admin 帖子:1