面向初学者的 MQL4 语言。自定义指标(第 1 部分)

Antoniuk Oleg | 16 三月, 2016

简介

这是“面向初学者的 MQL4 语言”系列的第四篇文章。今天我们将学习编写自定义指标。我们将熟悉指标特征的分类,了解这些特征如何影响指标,学习新函数和优化方法,最后就是编写我们自己的指标。此外,本文末尾处提供了有关编程风格的建议。如果这是你阅读的第一篇“面向初学者”文章,那你最好抽空读读之前的几篇文章。此外,确保你已经正确理解了之前学过的材料,因为本文不介绍基础知识。



指标的类型

现在我将向你展示目前存在的指标种类。当然你已经看过不少指标了,不过现在我要你注意的是指标的特征参数,所以我们要对特征 和参数做一个小小的分类。它会帮助你编写自定义指标。那么,我们来看第一个简单的指标:

它是移动平均线 (MA),一个广泛使用的技术指标。注意以下重要情况:

  • 此指标在图表窗口中绘制
  • 此指标仅显示一个值
  • 此指标值的范围是无限的,且取决于当前价格
  • 线条采用特定的颜色宽度样式绘制(实线)

现在我们来看另一个指标:

它是威廉指标 (%R)。注意以下重要情况:

  • 此指标在单独子窗口中绘制
  • 与上一种情况类似,此指标仅显示一个值
  • 指标值范围有严格的限制
  • 绘制的线条采用另一种样式颜色宽度

因此,存在以下指标属性:

  • 此指标在图表窗口单独子窗口中绘制。现在我们试着理解为何在图表上绘制移动平均线,而在单独窗口中绘制威廉指标 (%R) 。区别就在于显示值的范围。注意,第二个指标显示的值范围为 0 -100。现在想象一下将这些值显示在图表窗口中。会发生什么情况??你看不到这条线,因为价格范围大大收窄。在我们的案例中,这个范围是 0.68050.7495。但这还不是全部。实际上,价格是正数,而我们的值是负数。如果指标的值超出了活动图表的价格范围,那么指标就会在单独子窗口中绘制。如果指标值的范围与活动图表的价格范围大致相同(例如不同种类的移动平均线),那么指标就会在图表窗口中绘制。以后便可根据这个简单的逻辑设置此指标的参数。下面是一张图片:
  • 在单独子窗口中绘制的指标可能有严格限制的范围。这意味着终端会设置一个固定的比例来显示指标值;即便存在超出这个范围的值,你也看不到它们。如果你禁用了此参数,终端会自动更改这个比例,以便包括相关指标的所有值。看下图:

  • 指标会使用不同的颜色样式宽度来显示其值。对于这种情况,你在终端中设置指标绘制时应该就已经司空见惯了。这里有一个限制:如果使用的线宽大于 1,那么你只能使用一种线型 - 实线。

还有个指标:

如你所见,这个交易量指标是采用直方图的形式绘制的。因此,有多种显示指标值的方式。下面是另一个类型的示例:

指标分形是采用特殊符号绘制的。现在看看以下指标:

这个指标叫鳄鱼指标。注意,这个指标同时绘制三个值(平衡线)。它是如何运作的?实际上,任何指标(有些例外情况,后文中再讨论)在显示值时都要使用数据缓冲区

数据缓冲区其实基本上就是个简单的数组。其特殊性在于,这个数组的一部分是由终端进行管理的。终端会更换这个数组,每当收到一个新柱,就会进行一次轮换。这样做的目的是让每个数组元素都对应一个特定的柱。一个指标中显示的数据缓冲区的最大数量是 8。现在这种方式看上去可能有些奇怪,但很快你就会明白除此以外别无他途。只需记住,鳄鱼指标中的每条线都有一个对应的单独数据缓冲区。每个数据缓冲区都有其自己的参数,终端就是根据这些参数绘制缓冲区的。我们的示例中有三个缓冲区,相关说明如下:

  1. 第一个缓冲区用宽度 3 的绿色实线绘制。
  2. 第二个缓冲区用宽度 1 的红色虚线绘制。
  3. 第三个缓冲区用宽度 2 的蓝色实线绘制。

无需为每个指标绘制一个缓冲区。它可用于中间计算。这就是缓冲区数量可能大于你所见数量的原因。但数据缓冲区最重要的属性是每个缓冲区元素都应对应图表上的一个特定柱。只要记住这点就好。很快你就会看到它在代码中的应用。

现在让我们总结一下,为我们这段小插曲画上句点。任何指标都使用以下参数:

  • 一个或多个数据缓冲区(尽管并非必要),用于显示其值或用于中间计算。每个循序轮换的缓冲区都有其自己的参数,其中定义了该缓冲区的绘制方式以及是否要绘制。例如,以直方图、符号还是线条的形式绘制值;采用哪种颜色和线型;
  • 指标应绘制在何处(在图表窗口中还是在子窗口中)。
  • 如果在子窗口中绘制指标,是否应限制范围,或是否应自动调节比例。

确保你清楚地理解所有这些参数。现在我们将使用向导创建一个自定义指标。



创建自定义指标

启动 MetaEditor,选择文件 -> 新建

然后会显示一个 Expert Advisor 向导 窗口,选择自定义指标,单击下一步

填写字段名称作者链接。到这里一切都还平淡无奇,但现在你可以添加参数了。这是什么?

参数是可由用户设置的常用变量。重要的是,这些变量可用于指标代码中。参数的应用显而易见 - 让用户能够设置指标操作的某些方面。可以是任何想要设置的方面。例如,要使用的时间范围、操作模式、供计算平均值的柱数等。

举个例子,让我们试着添加一个参数,此参数将显示计算指标值所处理的柱数。它可以用在什么地方?试想一下,由于所做的计算太多,你的指标让处理器严重过载。而你经常更改图表的时间范围并仅查看最新的 100 - 200 个柱。那么你无需进行其他浪费时间的计算。这种情况下,此参数就能帮到你。当然,我们的指标中没有会浪费计算机资源的复杂东西。这只是一个使用指标参数的形式。

要添加参数,单击添加 (1)。之后可更改变量名称 (2)。在我们的示例中,我们用它来替代 barsToProcess。你也可更改初始值 (3),即默认值。将其更改为 100。此外你也可更改变量类型,但我们的示例中无需更改任何东西,因为 int 类型就很契合我们的意图。执行所有必要的更改后,单击下一步

基本上这就搞定了。现在要说明的是应如何绘制指标:在单独窗口还是图表窗口中绘制。也可限制范围。选中单独窗口中的指标。以下是空字段指数(数据缓冲区)。此处可添加必要的数据缓冲区数(最多 8 个)。此外,以后可随时添加或删除缓冲区,更改代码。单击添加添加缓冲区。现在可更改绘制缓冲区的方式:线条直方图截面箭头。我们不更改任何东西,所以我们的类型是线条。设置颜色并单击确定

这样,你的第一个指标就完成了!好吧,它并没有绘制任何东西,但它是代码!带源代码的文件位于带指标的文件夹内:MetaTrader4\experts\indicators



让我们来逐行进行分析:

现在我们来看看 Meta Editor 创建的内容:

//+------------------------------------------------------------------+
//|                                             myFirstIndicator.mq4 |
//|                                                     Antonuk Oleg |
//|                                                   banderass@i.ua |
//+------------------------------------------------------------------+

和往常一样,开头是一行注释,内含你早先写入的信息。下一段:

#property copyright "Antonuk Oleg"

还记得第二篇文章中的预处理器指令 #define 吗?我们用它来声明常量。这里是另一个指令,用于表明指标的特定属性。本例中它用于表明作者身份。请注意,它开头使用了特殊符号 #,后跟关键字 property(没有空格)。之后是我们要设置的具体属性,本例中是版权 copyright,最后是此属性的。本例中此值是包括你的姓名的行。可使用指令 #property 设置指标的很多特定方面。现在就让我们来看一下。默认情况下,所有这些属性都将进行设置。来看下一段代码:

#property link      "banderass@i.ua"

这个指令表达的是作者的联系方式。你可能会问这个信息(作者姓名和联系信息)在哪,因为任何地方都看不到这个信息。其实它包括在可执行文件里。如果你将此可执行文件以常见文本的格式打开查看,你就会看到这个信息:

下一段:

#property indicator_separate_window

这个指令是说指标必须在单独子窗口中绘制。如你所见,这个指令和上一个不同,它不含额外的参数。

#property indicator_buffers 1

这个指令表明指标将使用多少个数据缓冲区。你可能已经注意到,指令在某些方面与常用函数很相像:它们也接受一些参数,并执行一些操作作为响应。但这里有个重要的差别:指令在一开始就执行了(在编译之前)。

#property indicator_color1 DarkOrchid

这个指令指示第一个缓冲区的默认颜色。注意,缓冲区的编号是从 1 开始的,不是从 0。试着记住这一点,以免后来产生混淆。这个颜色是使用众多预定义名称的其中一个来指定的。你可以在帮助中查看所有可用颜色的关键字:MQL4 参考 -> 标准常数 -> 网页颜色。类似的,你也可以指示其他缓冲区的颜色,只要更改缓冲区编号就行了。

extern int       barsToProcess=100;

这是我们的指标参数。我们已经在向导中设置了这个参数。注意,它和常用变量的唯一区别就是关键字 extern 在变量类型的前面。这是指标启动时,这个参数表现在用户眼前的样子:

下一段:

double ExtMapBuffer1[];

这是个常用数组。但没有指示维数,也没有执行初始化。这个数组之后将被设置为一个数据缓冲区。

现在我们来声明和描述函数。与常见脚本不同,每个指标有三个函数,而不是 1 个:

  • init() - 这个函数仅在我们启动指标时由终端调用一次。其用途是让指标做好操作准备,设置数据缓冲区,检查参数(用户编写的内容)以及其他准备活动。此函数不是必要函数。如果没有在此函数中执行代码,你可以删除它。
  • deinit() - 此函数在你从图表中删除指标时被调用,也仅调用一次。你应做好指标终止操作的准备。例如,关闭打开的文件,删除文件中的图形对象(别担心,你会知道怎么做的)。此函数也不是必要函数。
  • start() - 和在脚本中时不同,这个函数在指标中时,价格每变动一次,它就被调用一次。也即是说,当当前货币对的新报价显示到指标连接的图表时,就将调用此函数。此外,此函数是在指标启动时调用的,即在调用函数 init() 之后。

我们来看看各个函数发生的情况:

int init()
{
   SetIndexStyle(0,DRAW_LINE);
   SetIndexBuffer(0,ExtMapBuffer1);
 
   return(0);
}

这里我们看到调用两个重要函数以设置数据缓冲区:

SetIndexStyle(0,DRAW_LINE);

此函数设置如何绘制数据缓冲区。第一个参数指示要应用更改的缓冲区。请注意,这个函数(及类似函数)中,缓冲区的编号是从 0 开始的,而不像指令中从 1 开始。这很重要,务必小心。第二个参数指示如何绘制选定缓冲区。本例中我们使用的是常数 DRAW_LINE,它表示缓冲区将被绘制成一根线条。当然还有其他常数,我们之后再讨论。

SetIndexBuffer(0,ExtMapBuffer1);

此函数将一个数组“绑定”到一个缓冲区编号。也就是说,此函数表示带指定编号的缓冲区将使用指定数组存储数据。所以,更改此数组的元素时,也会更改缓冲区的值。实际上数组就是个数据缓冲区。第一个参数是要绑定的数组的名称

return(0);

函数执行结束,返回 0 - 初始化成功。

int deinit()
{
//----
   
//----
   return(0);
}

取消初始化函数默认为空。

int start()
{
   int counted_bars=IndicatorCounted();
//----
   
//----
   return(0);
}

现在是最重要的函数 - 主要代码就在这里。注意:变量 counted_bars 已预先声明,它由函数 IndicatorCounted() 进行初始化。此变量通常用于优化和加快指标操作,具体情况将在后文中进行分析。现在让我们在指标窗口中绘制点东西。

完成指标

我们来决定要显示的内容。指标将向我们显示什么内容?很简单的东西。首先我们来绘制随机数。为什么不呢?绘制随机数可以保证 50% 的盈利信号。

让我们在函数 init() 中写入一段用于初始化随机数发生器的代码:

int init()
{
 
   SetIndexStyle(0,DRAW_LINE);
   SetIndexBuffer(0,ExtMapBuffer1);
 
   // initialization of the generator of random numbers
   MathSrand(TimeLocal());
 
   return(0);
}

初始化就绪,接下来是函数 start()

int start()
{
   int counted_bars=IndicatorCounted();
 
   for(int i=0;i<Bars;i++)
   {
      ExtMapBuffer1[i]=MathRand()%1001;
   }
   
   return(0);
}

编译 - F7。启动终端,找到导航器面板,选择自定义指标部分,然后双击我们指标的名称:

此指标将被连接到活动图表:

看,一切顺利。现在让我们看看代码要做什么工作:

for(int i=0;i<Bars;i++)

我们使用循环 for遍历数据缓冲区的所有元素。由于每个特定柱对应缓冲区的各个元素,我们使用了循环,循环从零柱(最后一个可用柱)开始到第一个可用柱结束,每次循环都比变量 Bars 少一个柱(因为我们从 0 开始对柱计数)。

{ ExtMapBuffer1[i]=MathRand()%1001; }

每次迭代,计数器增加 1,在我们从最后一个可用柱移至第一个可用柱的同时,向各个缓冲区元素(与特定柱相对应)分配一个 0 到 1000 以内的随机数。如果你觉得难以理解特定缓冲区元素如何对应特定柱,试着用以下方式更改循环,然后在终端中查看结果:

for(int i=0;i<Bars;i++) { ExtMapBuffer1[i]=i; }

现在指标将显示各个柱的编号,我们来看一下:

我们看到,柱编号从最后一个柱开始到第一个柱结束一直在增大(从 0 到 Bars(条柱数))。希望现在你理解了缓冲区元素与图表上的条柱的对应关系。

现在我们回到“随机”指标的代码这里。如果你用这个指标用了至少几分钟时间,你会看到指标每次跳动都会绘制完全不同的图表。也就是说,每次跳动都会重新计算上一次计算过的内容。这就给我们造成了不便,因为我们甚至连上一次跳动时发生的情况都看不到。不过没关系,因为没人会去使用这种指标 - 我们只是学习编写它而已。还有一点。想象一下,你的指标要执行大量复杂计算,而仅仅计算一个柱就需要大量的处理器资源。这种情况下,如果出现新的价格,你的指标将计算每个可用柱的值,即便之前就已经算过。清楚了吗?不是仅计算一次,是一次次反复计算。消除此类不合理的资源浪费问题的过程,我们称之为优化

如何解决这个问题?通常我们用以下方式来解决。首先针对所有可用烛台计算指标,然后仅在收到报价时,才仅针对最后一个烛台重新计算该值。这个方法比较合理 - 没有不必要的操作。现在我们来优化函数 start(),让它执行以下操作:

int start()
{
   int counted_bars=IndicatorCounted(),
       limit;
 
   if(counted_bars>0)
      counted_bars--;
   
   limit=Bars-counted_bars;
  
   for(int i=0;i<limit;i++)
   {
      ExtMapBuffer1[i]=MathRand()%1001;
   }
   
   return(0);
}

让我们来逐行进行分析:

int counted_bars=IndicatorCounted(),

我们声明变量 counted_bars,此变量将存储指标计算的柱数。实际上,函数 IndicatorCounted() 返回的是上一次调用函数 start() 之后未发生变化的柱数。所以,如果这是第一次调用 start(),IndicatorBars() 将返回 0,因为所有柱对我们来说都是新的。如果不是第一次调用,只有最后一个柱发生了更改,那么 IndicatorBars() 将返回一个等于 Bars-1 的数。

limit;

这是另一个变量,它将用作一个限制器,即帮助循环提前完成,忽略已计算的烛台。

if(counted_bars>0) counted_bars--;

前面已经说过,如果 IndicatorCounted() 返回 0,则表示函数 start() 还是初次调用,所有柱对我们来说都是“新的”(未针对这些柱计算指标)。但如果不是第一次调用 start(),则将返回等于 Bars-1 的值。所以,这个条件与这种情况相关联。之后我们将变量 counted_bars 减小 1。既然只有最后一个柱可被更改,那我们为什么还要这么做?事实上,在某些情况下,上一个柱的最后一次价格变动会保持未被处理的状态,因为最后一次价格变动时,处理的是倒数第二次价格变动。自定义指标未被调用,也未被计算。所以我们要将变量 counted_bars 减 1,以消除这种情况。

limit=Bars-counted_bars;

这里我们会将需要重新计算的最后一批柱的数量分配给变量 limit(限制器)。既然变量 counted_bars 存储着已计算烛台的数量,我们只需找到 Bars(可用柱总数)和 counted_bars 之间的差值,便可定义必须计算的烛台数

for(int i=0;i<limit;i++)
{
   ExtMapBuffer1[i]=MathRand()%1001;
}

循环自身基本没有变化。我们仅更改实施条件。现在,循环将在计算器 i 小于 limit 时执行。

优化到此结束。如果查看一下指标的更新版本,你会发现每当收到一个新的价格变动时,只有最后一个柱的值会发生变化。试着经常执行这种优化,即便你的指标并没有执行复杂困难的计算。这是个高级优化方法。

还记得我们在向导中添加的指标参数 barsToProcess 吗?现在就是用到它的时候了。我们只需要在循环前添加几行:

int start()
{
   int counted_bars=IndicatorCounted(),
       limit;
 
   if(counted_bars>0)
      counted_bars--;
   
   limit=Bars-counted_bars;
   
   if(limit>barsToProcess)
      limit=barsToProcess;
  
   for(int i=0;i<limit;i++)
   {
      ExtMapBuffer1[i]=MathRand()%1001;
   }
   
   return(0);
}

看到了吧,一切都是那么地简单。我们检查一下,看 limit 是否大于 barsToProcess。如果是,通过分配减小限制器。这样一来,如果我们设置 barsToProcess=100,你就会看到以下图片:

如你所见,仅计算我们设置的柱数。

我们的指标基本上准备就绪了。但我们还没获得进入市场的明确信号。所以我们需要进一步加大确定性。为此我们将使用水平线

水平线是指标用特定样式、颜色和宽度绘制的水平线条。这里要注意的是,一个柱上的水平线最大数量为 8。此外你可以使用指令函数设置水平线。如果要默认设置水平线,最好使用指令。要使水平线在指标操作期间发生动态变化,就用函数。那么我们来设置两个水平线:第一个在点 800 上,第二个在点 200 上。为此,我们要在指标代码开头添加几个指令:

//+------------------------------------------------------------------+
//|                                             myFirstIndicator.mq4 |
//|                                                     Antonuk Oleg |
//|                                                   banderass@i.ua |
//+------------------------------------------------------------------+
#property copyright "Antonuk Oleg"
#property link      "banderass@i.ua"
 
#property indicator_level1 800.0
#property indicator_level2 200.0
#property indicator_levelcolor LimeGreen
#property indicator_levelwidth 2
#property indicator_levelstyle 0
 
#property indicator_separate_window

我们来分析这些新指令:

#property indicator_level1 800.0

这个指令表示水平线 1 应放在点 800.0 处。注意,缓冲区的编号从 1 开始,就像用于设置缓冲区的指令那样。要设置另一个水平线,只需更改指令末尾的水平线编号:

#property indicator_level2 200.0

设置水平线的外部形式时,有一个重要的限制条件。你不能单独设置各个水平线。所有设置都会完全应用到所有水平线。如果需要单独设置各个水平线,应使用对象(完全不用水平线),具体会在下一篇文章中详述。

#property indicator_levelcolor LimeGreen

此指令设置用于绘制所有水平线的颜色

#property indicator_levelwidth 2

此指令设置绘制所有水平线时所用的线宽。宽度可设范围为 1 5。别忘了,如果宽度大于 1,水平线将用实线绘制。如果需要另一种水平线绘制样式,仅使用宽度 1。

#property indicator_levelstyle STYLE_SOLID

此指令设置绘制线条的样式。有以下预设常量:

  • STYLE_SOLID - 实线
  • STYLE_DASH - 虚线
  • STYLE_DOT - 点线
  • STYLE_DASHDOT - 点划线
  • STYLE_DASHDOTDOT - 双点式点划线

我们的“随机”指标开发完毕了。现在让我们用更恰当的名称保存源文件 - randomIndicator.mq4。再次重新编辑源文件。这个指标在下一部分中也会用到。最终版本如下:

函数 iCustom

现在我们详细说明一个非常有用的函数 - iCustom。它的作用是获取任何自定义指标的值。记住,对于内置指标,我们要使用可以与上一篇文章中所述技术指标配合使用的函数(例如:iADX()、iMACD 等)。对于所有其他指标(自定义指标),使用函数 iCustom。此函数是通用函数,可以应用于任何满足以下要求的自定义指标:

  • 指标采用可执行文件 (*.ex4) 格式编译。
  • 指标在文件夹 MetaTrader 4\experts\indicators 中

函数原型具有以下格式:

double iCustom( string symbol, int timeframe, string name, ..., int mode, int shift);

参数:

  • symbol(交易品种) - 定义应采用哪种金融证券(货币对)计算自定义指标值。如果你需要当前(活动)的证券(图表),则使用 NULL(或 0)。
  • timeframe(时间范围) - 定义指标将用于哪个时间范围(周期)。对当前时期使用 0 或以下常量之一(PERIOD_M1、PERIOD_M5、PERIOD_M15、PERIOD_M30、PERIOD_H1、PERIOD_H4、PERIOD_D1、PERIOD_W1、PERIOD_MN1)。
  • name(名称) - 自定义指标的可执行文件名称。只需指定名称:切勿输入扩展名 (.ex4) 或文件路径 (experts/indicators/)。例如,如果自定义指标的可执行文件名称为“RandomIndicator.ex4”,你应该输入“RandomIndicator”。这里的寄存器无关紧要。意思就是输入“RANDOMindicator”就可以了。
  • ...- 这里你应该指定自定义指标参数的所有值。例如,我们的指标 RandomIndicator 中仅有一个参数 - barsToProcess。即在我们的示例中,我们在这里输入 100(或任何其他合适的值)。如果参数超过一个,将以它们在自定义指标中的声明顺序指定它们,并用逗号隔开。现在我们将尝试根据此函数编写一个指标,这样你会理解地更深。
  • mode - 自定义指标的操作模式。实际上它是你要获取的数据缓冲区的编号值。编号从 0 开始(与指令中不同)。如果自定义指标仅有一个数据缓冲区,此参数应等于 0。
  • shift - 定义自定义指标要应用到的柱。

使用示例:

ExtMapBuffer[0]=iCustom(NULL,PERIOD_H1,"Momentum",14,0,0);
 
// assign to the first element of the array ExtMapBuffer the value of the custom 
// indicator Momentum on the last available bar. We use here the active 
// security on hour chart. The name of the executable file: Momentum. 
// This indicator has only one parameter - period. In our case the period 
// is equal to 14. This indicator has only one data buffer, so we use zero, 
// in order to get access to its values.
double signalLast=iCustom("EURUSD",PERIOD_D1,"MACD",12,26,9,1,0);
 
// declare a new variable signalLast and assign to it the value of the custom 
// indicator индикатора MACD on the last available bar. We use the pair EURUSD on 
// a daily chart. The name of the executable file: MACD. This indicator has 3 parameters: 
// period for quick average, period for slow average and period for a signal line. 
// This indicator also has 2 data buffers. The first one is with values of the main line. The second one 
// with values of a signal line. In our case we take the value of the signal line.


信号指示器

现在我们将编写另一个简单指标。那么,想象一下以下场景。你已经编写了一个相当复杂的,带有很多数据缓冲区的指标。其中不少数据缓冲区显示在单独窗口中,其他的用于中间计算。你了解买入和卖出的确切信号。但问题是很难跟踪这些信号。你需要一直盯着监控器,试着找到水平线上方或下方的十字线。因此你决定再编写一个指标来帮你做这个工作,并仅向你显示入场信号。例如,这些信号可以是表明你要建仓的方向的箭头。至于想要显示信号指标的正确位置,那是不可能的!我们的情况比较简单,但仍然和第一种情况类似。

我们将基于前一个指标 RandomIndicator 编写一个信号指标。首先我们需要定义入场条件 - 这里我们需要用到我们的水平线。那么条件将如下:

  • 如果一条线移至上水平线 (800.0) 的上方,那就买入
  • 如果一条线移至下水平线 (200.0) 的下方,那就卖出

现在是时候编写一个新的指标了。使用 Expert Advisor 向导创建一个新的自定义指标。像上一个例子中那样,添加一个额外的参数:

最后一步(绘制自定义指标程序的属性)应该是这样的:

首先添加两个将用于通过箭头的形式绘制买入和卖出信号的数据缓冲区。将数据缓冲区的类型更改为箭头。更改颜色和符号代码。以下是所有可用符号代码:

我们不需要在单独窗口中绘制指标,因为我们要在图表窗口中绘制信号。

我们使用两个数据缓冲区,因为我们无法仅使用一个缓冲区绘制不同的箭头(符号)。每个以符号形式显示的数据缓冲区都仅可用一个符号绘制。现在让我们集中注意力,认真分析一下指标初始化代码:

int init()
{
//---- indicators
   SetIndexStyle(0,DRAW_ARROW);
   SetIndexArrow(0,236);
   SetIndexBuffer(0,ExtMapBuffer1);
   SetIndexEmptyValue(0,0.0);
   SetIndexStyle(1,DRAW_ARROW);
   SetIndexArrow(1,238);
   SetIndexBuffer(1,ExtMapBuffer2);
   SetIndexEmptyValue(1,0.0);
//----
   return(0);
}

注意,现在针对数据缓冲区绘制类型使用了另一个常数 - DRAW_ARROW

SetIndexStyle(0,DRAW_ARROW);

我们还看到两个用于设置符号绘制的新函数。SetIndexArrow 用于设置什么符号将用来代表缓冲区。第一个参数是缓冲区编号,第二个是代表指标的符号代码

SetIndexArrow(0,236);

SetIndexEmptyValue 用于指示“空”值。这表示我们要指定一个值,出现这个值时,无需绘制任何东西。这个函数在我们的示例中很好用,因为不会在每个柱上都生成信号。它的工作方式如下:当我们不需要在当前柱上绘制数组时,你可以向对应的数据缓冲区元素分配一个“空”值,在我们的例子中,这个值是 0。此函数的第一个参数是数据缓冲区的编号。第二个是“空”值

SetIndexEmptyValue(0,0.0);

剩余的初始化代码设置缓冲区的方式类似于我们早先分析过的“随机”指标。现在我们来完成函数 start() 中的代码。

int start()
{
   int counted_bars=IndicatorCounted(),
       limit;
 
   if(counted_bars>0)
      counted_bars--;
   
   limit=Bars-counted_bars;
   
   if(limit>barsToProcess)
      limit=barsToProcess;
  
   for(int i=0;i<limit;i++)
   {
      double randomValue=iCustom(NULL,0,"RandomIndicator",barsToProcess,0,i);
      
      if(randomValue>800.0)
         ExtMapBuffer1[i]=High[i]+5*Point;
      else
         ExtMapBuffer1[i]=0.0;
         
      if(randomValue<200.0)
         ExtMapBuffer2[i]=Low[i]-5*Point;         
      else
         ExtMapBuffer2[i]=0.0;         
   }
   
   return(0);
}

从“随机”指标直至循环的整个代码将重复执行。这段代码实际上是任何指标中的标准代码,一直在重复使用,最多进行一些细微的更改。现在我们来详细分析这个循环:

   for(int i=0;i<limit;i++)
   {
      double randomValue=iCustom(NULL,0,"RandomIndicator",barsToProcess,0,i);
      
      if(randomValue>800.0)
         ExtMapBuffer1[i]=High[i]+5*Point;
      else
         ExtMapBuffer1[i]=0.0;
         
      if(randomValue<200.0)
         ExtMapBuffer2[i]=Low[i]-5*Point;         
      else
         ExtMapBuffer2[i]=0.0;         
   }

首先我们声明变量 randomValue(随机值)并向其分配我们的“随机”指标在当前柱上的值。为此我们使用了函数 iCustom

// get the value of the "random" indicator on the i-th bar. Use the active chart on the current period. // The name of the executable file of indicator: RandomIndicator. Single parameter of "random" indicator // is number of bars for calculation. In our indicator there is also analogous variable, that is why // we use it. In "random" indicator only 1 data buffer, so we use 0, for getting // access to its values.

如果“随机”指标的值在上水平线 (800) 上方,就表示是买入信号:

if(randomValue>800.0)
   ExtMapBuffer1[i]=High[i]+5*Point;
// if there is signal to buy, assign to current element of data buffer the highest
// value of the current bar. Besides add 5 points, so that the arrow were a little higher 
// than the current price. The predetermined variable Point is used to get automatically
// a multiplier for presenting points. Otherwise we would have to write something like
// this: ExtMapBuffer1[i]=High[i]+0.0005; 

否则,如果没有买入信号:

else
   ExtMapBuffer1[i]=0.0;
 
// if no Buy signal, assign to the current element of data
// buffer "empty" value, which is equal to 0.0.
// Now no symbol will be shown on this bar.

如果“随机”指标的值在下水平线 (200) 下方,就表示是卖出信号:

if(randomValue<200.0)
   ExtMapBuffer2[i]=Low[i]-5*Point;
 
// if it is signal to sell, assign to the current element of data buffer the lowest
// value of the current bar. Besides diminish the value by 5 points, so that the arrow were 
// a little lower than the current price.

否则,如果没有卖出信号:

else
   ExtMapBuffer2[i]=0.0;
 
// if no Sell signal, assign to the current element of data
// buffer "empty" value. Now no symbol will be shown on this bar.

这就是循环。编译指标并在终端中启动它:



关于风格

我说的可不是领带与外套和衬衫的那种搭配风格,尽管那种风格总是那么符合潮流。如果你写代码不是为了自用自看,那么编程风格就非常重要。实际上,每个开发人员都有其自己的编程风格。每个人都用自己的方式设计循环,使用不同的缩进(或根本不使用缩进),声明变量等。你应该找到属于你自己的,今后一直会使用的编程风格。我想给你几个建议,帮你把代码写的易读易懂:

  • 不要在一行内编写很多操作(用分号隔开)(;)
  • 用英语编写变量名和函数名
  • 在变量名中,使用大写字母作为分隔符
  • 变量名和函数名中避免过多使用缩写和缩略词
  • 缩进一定的长度,使代码块均等
  • 在各个(循环或条件的)新主体中执行额外的缩进
  • 将同一类型的变量进行分组
  • 对较长较难的代码块进行适当的注释
  • 对你编写的函数(其分配、参数)进行适当的注释

总结

今天你学到了一些新东西。你编写了两个简单的指标。好吧,它们并没什么用处,但我又不是在教你怎么成功交易!你已经了解了指标的操作方式以及指标拥有的参数和属性。你还学到了如何设置缓冲区和使用它们。你认识了好几个新函数。函数 iCustom 非常重要,即使在 Expert Advisor 中也会进一步用到。如果遇到难题,再读一遍本文,试着加深理解。如果还有问题,请马上在论坛提出或写下对本文的评论。