English Русский Español Deutsch 日本語
preview
MQL5 简介(第 12 部分):构建自定义指标的初学者指南

MQL5 简介(第 12 部分):构建自定义指标的初学者指南

MetaTrader 5交易 |
365 2
Israel Pelumi Abioye
Israel Pelumi Abioye

概述

欢迎回到我们的 MQL5 系列!到目前为止,我们已经介绍了很多,包括处理内置指标、创建 EA 交易系统、探索基本的 MQL5 概念,以及将我们的知识应用于实际项目。是时候学习如何从头开始创建自定义指标了。我们将更深入地了解指标的内部运作方式,从而完全控制其运作和设计,而不是依赖于内置功能。您是否想过 MQL5 的两个内置指标,移动平均线或 MACD 是如何创建的?如果没有iRSI、iMA这样的函数,还能构建指标吗? 

采用基于项目的方法,我们将把流程分为两个主要部分。首先,在不使用 iMA 函数的情况下,我们将从头开始构建移动平均线指标。接下来,我们将更进一步,将移动平均线从传统的线形转变为烛形样式的指标。此外,这种实用的方法将为开发专门适合您需求的交易工具开辟新的途径。

线形样式的移动平均线

图 1. MA 线形样式

烛形样式的移动平均线

图 2. MA 烛形样式

在本文中,您将学习:

  • 如何在 MQL5 中从头创建自定义指标。
  • 以线形样式和烛形样式绘制指标之间的区别。
  • 使用指标缓冲区来存储和显示计算值。
  • 正确设置指标属性。
  • 在 MQL5 中创建自定义移动平均指标。
  • 使用 SetIndexBuffer() 映射指标缓冲区。
  • 使用 OnCalculate() 函数处理价格数据。
  • 如何根据价格变动确定烛形颜色。

即便你对 MQL5 尚不熟悉,也能轻松跟随学习而不感困惑,因为本文旨在为初学者提供友好指导。每一行代码都将得到详尽的解析,将复杂概念拆解为可操作的步骤。通过本教程的学习,您将全面掌握 MQL5 中自定义指标的工作原理,因为我将确保内容简洁实用。


1.自定义指标

1.1.什么是自定义指标?

自定义指标是默认情况下 MQL5 中没有的指标。与 MetaTrader 5 附带的内置指标(如移动平均线(MA)、相对强弱指数(RSI)或 MACD)不同,用户可以创建自己的指标来执行特定的计算、提供信号或以他们认为合适的方式呈现市场数据。

将内置指标纳入交易策略很简单,因为可以使用 iMA()、iRSI() 和 iMACD() 等函数直接访问它们。但在定制方面,它们受到限制。另一方面,自定义指标提供了对计算、逻辑和图表显示的完全控制,使您能够创建适合其特定交易要求的工具。希望使用内置指标不支持的方法进行市场分析的交易者会发现这些指标特别有用。您可以通过利用 MQL5 编程设计独特的指标,更全面地了解价格走势、趋势和可能的交易机会。

类比

假设在您的生活空间中布置一个书架。内置指标可与预先组装书架的商店相媲美。这些书架有预定的尺寸、形式和特征,并有标准设计。对于大多数人来说,它们易于使用、方便且有效。然而,如果你的空间形状奇怪,或者你需要特殊的书架来存放稀有的书籍,那么预装的书架就无法精确地满足你的需求。

另一方面,自定义指标就像您自己制作的书架。可以选择精确的尺寸、书架数量和材料来创建一个适合您空间的书架,并按照您选择的方式排列您的书籍。同样,在交易中,自定义指标为您提供了内置指标无法提供的精度和灵活性,使您能够设计出适合您独特策略的解决方案。

1.2.EA 交易和指标之间的差异

尽管指标和 EA 交易以类似的方式评估市场数据,但它们的功能却截然不同。因为 EA 是为了自动化交易而设计的,除了分析市场外,它们还根据预设规则进行交易。它们能够管理风险、修改止损、确定止盈水平以及开仓和平仓订单。每次有新的价格更新时,都会调用 EA 中最重要的函数之一 OnTick()。借助此函数,EA 可以实时关注价格变动,并在下订单之前检查交易标准是否满足。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

//

  }

相比之下,指标没有执行交易的能力;相反,它们只专注于市场分析。指标使用 OnCalculate() 方法,该方法分析历史和当前数据以产生交易指示,而不是 OnTick()。计算指标值、更新图表元素以及呈现趋势线或颜色编码烛形等视觉信号均由 OnCalculate() 处理。

指标仅仅提供有见地的信息;交易者需要做出最终决定,而 EA 则不同,EA 是用来采取行动的。尽管它们都检查市场数据,但指标和 EA 服务于不同的目的。虽然指标可以帮助交易者手动解读市场并做出明智的交易决策,但 EA 会根据交易机会采取行动。

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//
  }

类比

将指标和 EA 交易视为两种不同的交易工具。EA 可以分析市场、做出选择并自主执行交易,就像自动驾驶汽车一样。在没有人为干预的情况下,它应用交易技术,持续监控市场波动,控制风险。

另一方面,指标的功能更像 GPS,它提供方向但没有任何作用。它分析市场数据,识别模式,并帮助交易者理解价格变化。例如,移动平均线可以指示市场是上涨还是下跌,但不能启动或终止交易。指标仅提供信息;最终的选择取决于您,但 EA 会为您进行交易。


2.设置项目

现在我们知道了什么是自定义指标以及它们与 EA 交易和内置指标有何不同,让我们看看如何在 MQL5 中制作和修改我们的指标。我们将在本节中详细探讨该过程,以确保您从一开始就很好地了解如何创建指标。在本教程中,我们将不使用任何预先存在的指标例程(例如 MA),从头开始构建两个独特的指标。

2.1.MA 线

第一个指标将是一个简单的移动平均线指标,它计算并显示三条不同的移动平均线:

  • 周期数 200 应用于高价。
  • 周期数 100 应用于收盘价。
  • 周期数 50 应用于开盘价。

要构建自定义指标,首先必须了解其功能背后的逻辑。在编写任何代码之前,我们必须定义指标将如何处理市场数据并显示相关信息。这一步确保我们的方法结构清晰、易于实施。撰写伪代码是理解其逻辑之外必不可少的步骤。制作伪代码有助于将实现划分为更易于管理、更小的部分。它通过清楚地概述必须做什么来降低错误的可能性并提高编码过程的效率。

本节将首先研究利用各种价格来源确定和显示三个移动平均线所需的逻辑。在编写实际代码之前,我们将首先起草一个伪代码,作为我们实现的指南。这种结构化的方法将帮助我们构建一个定义良好的自定义指标,同时加强 MQL5 中的关键编程概念。

伪代码:

// 设置指标

1.设置显示在图表窗口上的指标。

2.定义 3 个缓冲区来存储移动平均线(MA200、MA100、MA50)的数据。

3.定义 3 个绘图来在图表上显示移动平均线。

// 设置绘图属性

4.对于 MA200:

  • 标签:“MA 200”
  • 类型:线
  • 风格:点划线
  • 宽度:1
  • 颜色:蓝色

5.对于 MA100:

  • 标签:“MA 100”
  • 类型:线
  • 风格:虚线
  • 宽度:1
  • 颜色:棕色

6.对于 MA50:

  • 标签:“MA 50”
  • 类型:线
  • 风格:点线
  • 宽度:1
  • 颜色:紫色

// 定义缓冲区

7.创建缓冲区以存储以下计算值:

  • MA200
  • MA100
  • MA50

// 定义输入参数

8.允许用户设置每个移动平均线的周期数:

  • MA200:默认周期数 = 200
  • MA100:默认周期数= 100
  • MA50:默认周期数 = 50

// 初始化函数

9.当指标初始化时:

  • 将每个缓冲区分配给其对应的绘图。
  • 设置每个移动平均线要跳过的初始柱数:
  • MA200:跳过前 200 个柱
  • MA100:跳过前 100 个柱
  • MA50:跳过前 50 个柱

// 计算函数

10.对于每根新烛形或图表更新时:

将前 200、100 和 50 个柱形的最高价、收盘价和开盘价分别加起来,除以相应的时间,并将结果存储在适当的缓冲区中,以确定 MA200、MA100 和 MA50。

2.2.烛形 MA

接下来,我们将创建一个不同的移动平均指标,以烛形样式表示价格趋势。该指标将使用周期为 5 的移动平均线,并使用烛形而不是线条直观地表示其值。通过以这种方式显示移动平均数据,我们可以突出短期趋势,更有效地过滤市场噪音。这将为价格走势提供独特的视角,同时保持移动平均线计算的原则。

每个项目都将得到彻底的解释,确保每一行代码都足够简单,让初学者能够理解。到本部分结束时,您将拥有从头开始构建和修改指标的实践经验,为您的进一步项目提供有用的能力。

伪代码:

// 设置指标

1.设置指标在单独的窗口中显示。

2.创建 5 个数组(缓冲区)来存储:

  • 开盘价(OpenBuffer)
  • 高价(HighBuffer)
  • 低价(LowBuffer)
  • 收盘价(CloseBuffer)
  • 烛形颜色(ColorBuffer:0 = 绿色,1 = 红色)

3.定义 1 个绘图来显示蜡烛。

// 设置绘图属性

4.对于绘图:

  • 标签:"Candles"
  • 类型:彩色蜡烛(DRAW_COLOR_CANDLES)
  • 颜色:绿色表示看涨,红色表示看跌
  • 风格:实心的
  • 宽度:1

// 定义输入参数

5.允许用户设置平滑周期数(默认值 = 5)。

// 初始化函数

6.当指标初始化时:

  • 将每个缓冲区分配给其对应的数据或颜色索引。
  • 设置要跳过的初始柱数(等于平滑周期数)。

// 计算函数

7.对于每根新烛形或图表更新时:

  • 检查是否有足够的柱形可供计算(至少是“period”个柱形)。
  • 如果没有,则打印错误消息并停止。

8.对于从“period-1”柱形到最新柱形的每个柱形:

计算平滑的开盘价、最高价、最低价和收盘价:

  • 将之前“period”个柱形的开盘价、最高价、最低价和收盘价相加。
  • 将每个总数除以“period”即可得到平均值。
  • 将结果存储在 OpenBuffer、HighBuffer、LowBuffer 和 CloseBuffer 中。

设置烛形颜色:

  • 如果平滑收盘价 >= 平滑开盘价,则将颜色设置为绿色 (0)。
  • 否则,将颜色设置为红色(1)。


3.创建和定制自定义指标

我们现在可以创建和修改自定义指标,因为我们已经编写了伪代码。有了明确的策略后,我们将开始逐步将逻辑付诸实践,确保指标按预期运行。通过正确组织代码,我们可以有效地可视化市场走势,并修改指标以满足我们的需求。

3.1.以线格式构建移动平均线指标

创建指标的第一步是描绘出你希望它看起来和工作的样子。在创建任何代码之前,问自己几个关键问题来帮助你理解设计和实现是至关重要的:

  • 指标应该显示在主图表窗口还是单独的窗口中?
  • 指标需要多少个缓冲区来存储和显示数据?
  • 应该使用什么类型的图表 —— 线图、直方图、蜡烛图还是其他?
  • 为了获得更好的可视化效果,应该为不同的元素分配什么颜色?
  • 该指标是否会使用多个数据源,例如最高价、最低价、开盘价或收盘价?

通过回答这些问题,您可以为指标创建一个清晰的蓝图,使开发过程更加顺畅和结构化。

示例:

//INDICATOR IN CHART WINDOW
#property indicator_chart_window

//SET INDICATOR BUFFER TO STORE DATA
#property indicator_buffers 3

//SET NUMBER FOR INDICATOR PLOTS
#property indicator_plots   3

解释:

图表窗口中的指标
#property indicator_chart_window

该指令指示 MetaTrader 5 立即在显示价格走势的主图表上显示自定义指标,而不是在单独的窗口中显示。要在不同的窗口中绘制指标(例如 RSI 或 MACD),我们将使用:

#property indicator_separate_window

该指令确保指标显示在主图表下方的单独窗口中,而不是直接绘制在价格图表上。

设置指标缓冲区来存储数据

#property indicator_buffers 3

将绘制在图表上的计算值存储在称为指标缓冲区的数组中。每个缓冲区代表指标的一个不同元素。在本例中,我们将存储三组不同的值,因为我们构建了三个指标缓冲区。由于我们正在构建具有三个不同周期数(50、100 和 200)的移动平均指标,因此需要三个缓冲区来独立保存每个移动平均线的值。

设置指标绘图的数量

#property indicator_plots   3

这指定了指标将显示多少个绘图。图表上独特的图形表示称为绘图。由于我们要绘制三条移动平均线,因此需要三个绘图:50、100 和 200 周期移动平均线各一个。

可以为每个图表自定义颜色、样式和格式(例如线条、直方图或蜡烛图)。这里,三个绘图中的每一个都将显示为一条代表不同移动平均线的线。

总结

  • 指标将显示在主图表上(#property indicator_chart_window)。
  • 它将使用指标缓冲区(#property indicator_buffers 3)存储三组值。
  • 它将绘制三个不同的移动平均线(#property indicator_plots 3)。

此设置可确保指标正确计算并在价格图表上显示三个移动平均线。

图 3. 绘图和窗口图表

定义指标的基本特征后,接下来就是设置绘图属性。为了指定指标在图表上的显示方式,在 MQL5 中构建自定义指标时必须提供绘图属性。绘图的类型、线条样式、粗细、颜色和标签均由这些参数决定。理解这些参数将有助于您为各种用例创建和修改指标,因为本文重点介绍如何使用基于项目的方法设计自定义指标。

示例:

//INDICATOR IN CHART WINDOW
#property indicator_chart_window

//SET INDICATOR BUFFER TO STORE DATA
#property indicator_buffers 3

//SET NUMBER FOR INDICATOR PLOTS
#property indicator_plots   3

//SETTING PLOTS PROPERTIES
//PROPERTIES OF THE FIRST MA (MA200)
#property indicator_label1  "MA 200"    //GIVE PLOT ONE A NAME
#property indicator_type1   DRAW_LINE  //TYPE OF PLOT THE FIRST MA 
#property indicator_style1  STYLE_DASHDOTDOT  //STYLE OF SPOT THE FIRST MA
#property indicator_width1  1         //LINE THICKNESS THE FIRST MA
#property indicator_color1  clrBlue    //LINE COLOR THE FIRST MA

//PROPERTIES OF THE SECOND MA (MA100)
#property indicator_label2  "MA 100"   //GIVE PLOT TWO A NAME
#property indicator_type2   DRAW_LINE  //TYPE OF PLOT THE SECOND MA
#property indicator_style2  STYLE_DASH  //STYLE OF SPOT THE SECOND MA
#property indicator_width2  1          //LINE THICKNESS THE SECOND MA
#property indicator_color2  clrBrown    //LINE COLOR THE SECOND MA

//PROPERTIES OF THE THIRD MA (MA50)
#property indicator_label3  "MA 50"    //GIVE PLOT TWO A NAME
#property indicator_type3   DRAW_LINE  //TYPE OF PLOT THE THIRD MA
#property indicator_style3  STYLE_DOT  //STYLE OF SPOT THE THIRD MA
#property indicator_width3  1          //LINE THICKNESS THE THIRD MA
#property indicator_color3  clrPurple    //LINE COLOR THE THIRD MA

解释:

第一条移动平均线(MA 200)

#property indicator_label1  "MA 200"    //GIVE PLOT ONE A NAME
#property indicator_type1   DRAW_LINE  //TYPE OF PLOT THE FIRST MA 
#property indicator_style1  STYLE_DASHDOTDOT  //STYLE OF SPOT THE FIRST MA
#property indicator_width1  1         //LINE THICKNESS THE FIRST MA
#property indicator_color1  clrBlue    //LINE COLOR THE FIRST MA

第一条移动平均线(MA200)将显示为粗细为 1 的蓝色虚线。

第二条移动平均线(MA 100)

#property indicator_label2  "MA 100"   //GIVE PLOT TWO A NAME
#property indicator_type2   DRAW_LINE  //TYPE OF PLOT THE SECOND MA
#property indicator_style2  STYLE_DASH  //STYLE OF SPOT THE SECOND MA
#property indicator_width2  1          //LINE THICKNESS THE SECOND MA
#property indicator_color2  clrBrown    //LINE COLOR THE SECOND MA

第二条移动平均线(MA100)将显示为粗细为 1 的棕色虚线。

第三条移动平均线(MA 50)

#property indicator_label3  "MA 50"    //GIVE PLOT TWO A NAME
#property indicator_type3   DRAW_LINE  //TYPE OF PLOT THE THIRD MA
#property indicator_style3  STYLE_DOT  //STYLE OF SPOT THE THIRD MA
#property indicator_width3  1          //LINE THICKNESS THE THIRD MA
#property indicator_color3  clrPurple    //LINE COLOR THE THIRD MA

第三条移动平均线(MA50)将显示为粗细为 1 的紫色虚线。

理解 MQL5 中绘图属性之间的关系

在确定指标在图表上的显示方式时,每个绘图(线、直方图、箭头等)都需要一组特征来控制其外观。MQL5 中使用编号后缀(例如 indicator_label1、indicator_type1、indicator_style1 等)来分配属性。

通过这种编号,可以保证某个绘图的所有属性都属于一起。让我们来探索一下:

  • indicator_label1 为第一个绘图分配一个名称,使其更容易识别。
  • indicator_type1 指定如何绘制第一个图(例如,绘制为一条线)。
  • indicator_style1 确定第一个绘图的线条样式(实线、虚线、点线等)。
  • indicator_width1 控制第一个绘图的线的粗细。
  • indicator_color1 设置第一个绘图的颜色。

阐明关系的示例

将这些属性视为每个移动平均线的匹配集:

属性 它控制什么
第一 MA(200)
第二 MA(100) 第三 MA(50)
indicator_labelX
MA 的名称
“MA 200”
“MA 100”
“MA 50”
indicator_typeX
绘图类型
DRAW_LINE
DRAW_LINE
DRAW_LINE
indicator_styleX
线形的风格
STYLE_DASHDOTDOT
STYLE_DASH
STYLE_DOT
indicator_widthX 线形的宽度 1 1 1
indicator_colorX
颜色
clrBlue
clrBrown
clrPurple

这种方法保证了每个属性都与适当的移动平均线相匹配,并使我们能够以有组织的方式指定许多绘图。因此,如果 indicator_style2 发生变化,则只有第二条 MA 会受到影响。如果 indicator_color3 发生变化,则只有第三条 MA 的颜色会发生变化。通过保持这些特征的一致性和组织性,我们可以管理图表上每个移动平均线的外观,而不会造成混乱。

除了我们在移动平均线指标中采用的多种绘图类型、线条样式和颜色之外,MQL5 还为指标绘图提供了一系列其他自定义可能性。开发人员可以使用这些参数设计各种指示类型,从简单的线条到更复杂的可视化,如区带、箭头和直方图。我们采用基于项目的方法,专注于一个指标,同时提出可用于其他定制指标的基本想法,因为一篇文章中讨论的变量太多。不过,知道还有其他选择是有帮助的。

通常,可以使用本文中使用的相同逻辑来分析不同的绘图风格。但并非所有类型的绘图都是如此。例如,DRAW_LINE 每个绘图只需要一个缓冲区,而 DRAW_CANDLES 则需要四个缓冲区来绘制单个烛形。您可以查阅 MQL5 文档以获得可用的多种绘图类型及其特定需求的全面描述。

在配置绘图属性之后,声明双精度型变量来保存指标缓冲区。因为它们存储了将在图表上显示的计算值,所以这些缓冲区至关重要。声明变量后,必须使用 SetIndexBuffer() 函数将变量链接到指标缓冲区。此阶段保证这些缓冲区中保存的数据可以准确显示,并适当地分配给相应的绘图。

示例:

//INDICATOR IN CHART WINDOW
#property indicator_chart_window

//SET INDICATOR BUFFER TO STORE DATA
#property indicator_buffers 3

//SET NUMBER FOR INDICATOR PLOTS
#property indicator_plots   3

//SETTING PLOTS PROPERTIES
//PROPERTIES OF THE FIRST MA (MA200)
#property indicator_label1  "MA 200"   //GIVE PLOT ONE A NAME
#property indicator_type1   DRAW_LINE  //TYPE OF PLOT THE FIRST MA 
#property indicator_style1  STYLE_DASHDOTDOT  //STYLE OF SPOT THE FIRST MA
#property indicator_width1  1         //LINE THICKNESS THE FIRST MA
#property indicator_color1  clrBlue    //LINE COLOR THE FIRST MA

//PROPERTIES OF THE SECOND MA (MA100)
#property indicator_label2  "MA 100"   //GIVE PLOT TWO A NAME
#property indicator_type2   DRAW_LINE  //TYPE OF PLOT THE SECOND MA
#property indicator_style2  STYLE_DASH  //STYLE OF SPOT THE SECOND MA
#property indicator_width2  1          //LINE THICKNESS THE SECOND MA
#property indicator_color2  clrBrown    //LINE COLOR THE SECOND MA

//PROPERTIES OF THE THIRD MA (MA50)
#property indicator_label3  "MA 50"    //GIVE PLOT TWO A NAME
#property indicator_type3   DRAW_LINE  //TYPE OF PLOT THE THIRD MA
#property indicator_style3  STYLE_DOT  //STYLE OF SPOT THE THIRD MA
#property indicator_width3  1          //LINE THICKNESS THE THIRD MA
#property indicator_color3  clrPurple    //LINE COLOR THE THIRD MA

//SET MA BUFFER TO STORE DATA
double buffer_mA200[];  //BUFFER FOR THE FIRST MA
double buffer_mA100[];  //BUFFER FOR THE SECOND MA
double buffer_mA50[];   //BUFFER FOR THE THIRD MA

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

//SETTING BUFFER
   SetIndexBuffer(0, buffer_mA200, INDICATOR_DATA);  //INDEX 0 FOR MA200
   SetIndexBuffer(1, buffer_mA100, INDICATOR_DATA);  //INDEX 1 FOR MA100
   SetIndexBuffer(2, buffer_mA50, INDICATOR_DATA);   //INDEX 1 FOR MA50

//---
   return(INIT_SUCCEEDED);
  }

解释:

//SET MA BUFFER TO STORE DATA
double buffer_mA200[];  //BUFFER FOR THE FIRST MA
double buffer_mA100[];  //BUFFER FOR THE SECOND MA
double buffer_mA50[];   //BUFFER FOR THE THIRD MA

这些数组(buffer_mA200、buffer_mA100、buffer_mA50)用作每个移动平均线(MA)计算值的存储。

每个缓冲区对应一个特定的 MA:

  • buffer_mA200 → 存储 MA 200 的值
  • buffer_mA100 → 存储 MA 100 的值
  • buffer_mA50 → 存储 MA 50 的值

当指标显示时,这些缓冲区中保存的值将绘制在图表上。 为了在 MQL5 中存储指标的计算值,指标缓冲区是必需的。如果没有缓冲区,就不可能保存和显示结果。

//SETTING BUFFER
SetIndexBuffer(0, buffer_mA200, INDICATOR_DATA);  //INDEX 0 FOR MA200
SetIndexBuffer(1, buffer_mA100, INDICATOR_DATA);  //INDEX 1 FOR MA100
SetIndexBuffer(2, buffer_mA50, INDICATOR_DATA);   //INDEX 2 FOR MA50

SetIndexBuffer() 用于将每个缓冲区链接到指标的绘图系统。

它需要三个参数:

  • 缓冲区索引 → 确定缓冲区的使用顺序(从 0 开始)。
  • 缓冲区变量 → 将存储该索引的值的缓冲区。
  • 缓冲区类型 → INDICATOR_DATA 指定缓冲区保存指标值。

每条线的分析:

  • SetIndexBuffer(0, buffer_mA200, INDICATOR_DATA); → 将 buffer_mA200 分配给索引 0(第一条 MA)。
  • SetIndexBuffer(1, buffer_mA100, INDICATOR_DATA); → 将 buffer_mA100 分配给索引 1(第二条 MA)。
  • SetIndexBuffer(2, buffer_mA50, INDICATOR_DATA); → 将 buffer_mA50 分配给索引 2(第三条 MA)。

这保证了 MQL5 指标系统正确识别和绘制每个缓冲区。如果没有这一步,指标将无法正常工作。

对指标背后的数学或推理有透彻的理解对于其创建至关重要。只有当一个指标使用健全的数学或逻辑基础准确评估价格变化时,它才能被认为是有用的。在将指标应用于 MQL5 之前,您必须了解指标的功能以及有效计算指标所需的输入参数。

例如,我们以移动平均线为例:

通过减少价格波动,移动平均线使得在不受瞬态变化影响的情况下更容易发现市场模式。

要计算移动平均线,我们需要考虑:

周期数

用于计算移动平均线的历史烛形的数量取决于时间周期。例如,最新的 200 根烛形由 200 周期 MA 平均。当周期较短时,MA 对近期走势的响应更为灵敏,而周期较长时,MA 产生的线更为平滑,对价格变化的响应也更为缓慢。

价格类型

可以使用不同的价格类型来计算 MA,例如:

  • 收盘价 → 使用每根烛形的收盘价。
  • 开盘价 → 使用每根烛形的开盘价。
  • 高价 → 使用每根烛形的最高价格。
  • 低价 → 使用每根烛形的最低价格。

在这个项目中,我们将使用三种不同的移动平均线:

  • MA 200(应用于高价)
  • MA 100(应用于收盘价)
  • MA 50(应用于开盘价)

理解这些核心概念可确保我们能够正确计算和可视化移动平均线,而无需依赖 iMA() 等内置 MQL5 函数。

示例:

//INDICATOR IN CHART WINDOW
#property indicator_chart_window

//SET INDICATOR BUFFER TO STORE DATA
#property indicator_buffers 3

//SET NUMBER FOR INDICATOR PLOTS
#property indicator_plots   3

//SETTING PLOTS PROPERTIES
//PROPERTIES OF THE FIRST MA (MA200)
#property indicator_label1  "MA 200"   //GIVE PLOT ONE A NAME
#property indicator_type1   DRAW_LINE  //TYPE OF PLOT THE FIRST MA 
#property indicator_style1  STYLE_DASHDOTDOT  //STYLE OF SPOT THE FIRST MA
#property indicator_width1  1         //LINE THICKNESS THE FIRST MA
#property indicator_color1  clrBlue    //LINE COLOR THE FIRST MA

//PROPERTIES OF THE SECOND MA (MA100)
#property indicator_label2  "MA 100"   //GIVE PLOT TWO A NAME
#property indicator_type2   DRAW_LINE  //TYPE OF PLOT THE SECOND MA
#property indicator_style2  STYLE_DASH  //STYLE OF SPOT THE SECOND MA
#property indicator_width2  1          //LINE THICKNESS THE SECOND MA
#property indicator_color2  clrBrown    //LINE COLOR THE SECOND MA

//PROPERTIES OF THE THIRD MA (MA50)
#property indicator_label3  "MA 50"    //GIVE PLOT TWO A NAME
#property indicator_type3   DRAW_LINE  //TYPE OF PLOT THE THIRD MA
#property indicator_style3  STYLE_DOT  //STYLE OF SPOT THE THIRD MA
#property indicator_width3  1          //LINE THICKNESS THE THIRD MA
#property indicator_color3  clrPurple    //LINE COLOR THE THIRD MA

//SET MA BUFFER TO STORE DATA
double buffer_mA200[];  //BUFFER FOR THE FIRST MA
double buffer_mA100[];  //BUFFER FOR THE SECOND MA
double buffer_mA50[];   //BUFFER FOR THE THIRD MA

//SET MA PERIOD
input int period_ma200 = 200;  //PERIOD FOR THE FIRST MA
input int period_ma100 = 100;  //PERIOD FOR THE SECOND MA
input int period_ma50  = 50;   //PERIOD FOR THE THIRD MA

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

//SETTING BUFFER
   SetIndexBuffer(0, buffer_mA200, INDICATOR_DATA);  //INDEX 0 FOR MA200
   SetIndexBuffer(1, buffer_mA100, INDICATOR_DATA);  //INDEX 1 FOR MA100
   SetIndexBuffer(2, buffer_mA50, INDICATOR_DATA);   //INDEX 1 FOR MA50

//SETTING BARS TO START PLOTTING
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, period_ma200);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, period_ma100);
   PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, period_ma50);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {

//CALCULATE THE MOVING AVERAGE FOR THE THE First MA (MA200)
   for(int i = period_ma200 - 1; i < rates_total; i++)
     {

      double sum = 0.0;
      for(int j = 0; j < period_ma200; j++)
        {
         sum += high[i - j];
        }

      buffer_mA200[i] = sum / period_ma200;

     }

//CALCULATE THE MOVING AVERAGE FOR THE THE SECOND MA (MA100)
   for(int i = period_ma100 - 1; i < rates_total; i++)
     {

      double sum = 0.0;
      for(int j = 0; j < period_ma100; j++)
        {
         sum += close[i - j];
        }
      buffer_mA100[i] = sum / period_ma100;

     }

//CALCULATE THE MOVING AVERAGE FOR THE THE THIRD MA (MA50)
   for(int i = period_ma50 - 1; i < rates_total; i++)
     {
      double sum = 0.0;
      for(int j = 0; j < period_ma50; j++)
        {
         sum += open[i - j];
        }
      buffer_mA50[i] = sum / period_ma50;
     }

//--- return value of prev_calculated for next call
   return(rates_total);
  }

解释:

//SET MA PERIOD
input int period_ma200 = 200;  //PERIOD FOR THE FIRST MA
input int period_ma100 = 100;  //PERIOD FOR THE SECOND MA
input int period_ma50  = 50;   //PERIOD FOR THE THIRD MA

  • 这些输入变量定义了每个移动平均线 (MA) 的周期数。
  • 该周期数决定了 MA 将使用多少根过去的烛形进行计算。
  • period_ma200 = 200 表示 MA200 将使用最后 200 根烛形来计算平均价格。
  • period_ma100 = 100 表示 MA100 将使用最后 100 根烛形来计算平均值。
  • period_ma50 = 50 表示 MA50 将使用最后 50 根烛形来计算平均值。
  • 由于这些是输入变量,交易者可以在指标设置中修改它们,而无需更改代码。

//SETTING BARS TO START PLOTTING
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, period_ma200);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, period_ma100);
   PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, period_ma50);

为什么这是必要的?

  • 由于周期数为 200 的移动平均线需要前 200 个烛形来计算其值,因此它无法绘制前 200 个烛形之前的任何内容。
  • PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, period_ma200); 告诉 MetaTrader 仅在 200 个烛形可用后才开始绘制 MA200。
  • PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, period_ma100); 对 MA100 执行相同操作(100 根烛形后开始)。
  • PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, period_ma50); 确保 MA50 在 50 根烛形后开始。

图 4. 绘图开始

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])

每当收到新的报价或更新历史数据时,都会自动调用每个自定义指标的核心函数 OnCalculate()。它对于指标计算至关重要,因为它处理价格数据并修改指标缓冲区。OnCalculate() 专为指标而设计,可直接访问历史价格数据,而无需 CopyOpen()、CopyClose() 或 ArraySetAsSeries() 等其他程序,这与 EA 交易中使用的 OnTick() 不同。

OnCalculate() 和 OnTick() 之间的区别

特征
OnCalculate()
OnTick()
用于
指标
EA 交易
调用时间 新的报价到达或图表更新
新的报价到来
访问价格数据
使用内置数组(open[]、close[] 等)
需要像 CopyClose() 这样的函数来获取数据
用途
计算并更新指标值
执行交易逻辑,下订单
了解 OnCalculate() 中的参数
参数
描述
rates_total
图表上可用烛形(柱)的总数。
prev_calculated
先前计算的柱形数量(有助于优化性能)。
time[]
每根烛形的时间戳(例如,2024.02.02 12:30)。
open[]
每根烛形的开盘价。
high[]
每根烛形的最高价格
low[]
每根烛形的最低价格。
close[]
每根烛形的收盘价。
tick_volume[]
烛形内的报价数(价格更新)。
volume[]
一根烛形内的总交易量。
spread[]
每根烛形的点差(买入价和卖出价之间的差额)。
// CALCULATE THE MOVING AVERAGE FOR THE FIRST MA (MA200)
for(int i = period_ma200 - 1; i < rates_total; i++)
  {
   double sum = 0.0;
   for(int j = 0; j < period_ma200; j++)
     {
      sum += high[i - j];  // SUM OF HIGH PRICES OVER THE PERIOD
     }
   buffer_mA200[i] = sum / period_ma200;  // CALCULATE THE AVERAGE
  }

使用遍历价格数据的 for 循环来计算第一个 MA(MA200)的移动平均线 (MA)。我们有足够的先前数据点来计算平均值,因为循环从 period_ma200 - 1 开始。它一直持续到 rates_total,即提供的价格数据点的总量。这种方法避免了我们尝试访问不存在的数据点(例如负索引值)时可能发生的错误。图表中的每个柱形都有其移动平均线,由循环系统地确定,然后适当地更新指标缓冲区。

为了保存给定时间段内最高价的累计总和(在本例中为 200),我们在循环内初始化一个变量 sum。嵌套的 for 循环遍历从 0 到 period_ma200 的最后 200 个柱形。Sum += high[i - j]; 在每次迭代中用于将某个柱的最高价加到 sum 变量中。通过从当前索引 i 向后推进,公式 i - j 保证我们将最后 200 个柱的最高价加在一起。此过程有效地将指定时间周期内的所有高价加起来。

一旦我们得到了 200 个周期内最高价的总和,我们就用该数量除以周期长度来得到平均值:buffer_mA200[i] = sum / period_ma200; 随后,每个柱形计算出的 MA 值都由这个最终值表示,然后将其保存在 buffer_mA200 的相应索引中。通过取最近 200 个高价的平均值,移动平均线可以平衡价格波动并使交易者能够发现长期模式。尽管其他移动平均线采用不同的价格类型(收盘价和开盘价)和周期数(100 和 50),但同样的推理也适用于它们。

图 5. 均线

3.2.构建烛形格式的移动平均线指标

在本节中,我们将开发一种不同类型的移动平均线指标,该指标使用烛形格式显示价格走势。该指标将采用 5 周期移动平均线,并使用烛形而不是线条以图形方式显示其值。通过这种方式呈现移动平均数据,我们可以更成功地过滤掉市场噪音并突出短期趋势。这将保留移动平均线计算的基本原理,同时对价格走势提供独特的观点。

开盘价、收盘价、最高价和最低价是构成蜡烛图的四个基本价格要素。蜡烛图格式需要对这四个值进行独立的移动平均计算,而线形图则只需要每个周期一个价格值(例如收盘价或开盘价)。因此,我们要求单个绘图至少有四个缓冲区,每个缓冲区对应一个代表开盘价、收盘价、最高价和最低价的移动平均值。该指标的结构保证了移动平均线数据的呈现方式与实际烛台模式非常相似,这有助于使用众所周知的视觉线索进行价格趋势分析。

示例:

#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1

//---- plot ColorCandles
#property indicator_label1  "Candles"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrGreen, clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- indicator buffers
double OpenBuffer[];
double HighBuffer[];
double LowBuffer[];
double CloseBuffer[];
double ColorBuffer[];

int period = 5;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0, OpenBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, HighBuffer, INDICATOR_DATA);
   SetIndexBuffer(2, LowBuffer, INDICATOR_DATA);
   SetIndexBuffer(3, CloseBuffer, INDICATOR_DATA);
   SetIndexBuffer(4, ColorBuffer, INDICATOR_COLOR_INDEX);

   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, period);

   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {

   if(rates_total < period)
     {
      Print("Error: Not enough bars to calculate.");
      return 0;
     }

   for(int i = period - 1; i < rates_total; i++)
     {

      if(i - period + 1 < 0)
         continue;

      double sumClose = 0.0;
      double sumOpen = 0.0;
      double sumHigh = 0.0;
      double sumLow = 0.0;

      for(int j = 0; j < period; j++)
        {
         int index = i - j;
         if(index < 0)
            continue; // Prevent out-of-bounds access

         sumClose += close[index];
         sumOpen += open[index];
         sumHigh += high[index];
         sumLow += low[index];
        }

      if(period > 0)
        {
         OpenBuffer[i] = sumOpen / period;
         HighBuffer[i] = sumHigh / period;
         LowBuffer[i] = sumLow / period;
         CloseBuffer[i] = sumClose / period;
        }
      else
        {
         Print("Error: Division by zero prevented.");
         return 0;
        }

      ColorBuffer[i] = (CloseBuffer[i] >= OpenBuffer[i]) ? 0 : 1;
     }

   return rates_total;
  }

解释:

#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1

//---- plot ColorCandles
#property indicator_label1  "Candles"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrGreen, clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- indicator buffers
double OpenBuffer[];
double HighBuffer[];
double LowBuffer[];
double CloseBuffer[];
double ColorBuffer[];

int period = 5;

此代码定义了以烛形格式显示移动平均线的自定义指标的属性和缓冲区。烛形通常需要四个缓冲区:每个分别用于开盘价、最高价、最低价和收盘价组成部分。然而,由于需要额外的缓冲区(ColorBuffer)来识别和存储每根烛形的颜色,因此该指标需要五个缓冲区。

指标属性

  • #property indicator_separate_window 确保指标显示在单独的窗口中而不是绘制在主图表上。
  • #property indicator_buffers 5 定义使用的缓冲区数量。尽管一根烛形只需要四个缓冲区(开盘价、最高价、最低价和收盘价),但仍需要第五个缓冲区来为每根烛形分配颜色。
  • #property indicator_plots 1 指定该指标只有一个绘图,将使用彩色蜡烛。

绘图和可视化设置

  • indicator_label1 "Candles" 命名绘图。
  • indicator_type1 DRAW_COLOR_CANDLES 将绘图类型设置为彩色烛形。与 DRAW_LINE 不同,此类型需要开盘价、最高价、最低价和收盘价,以及颜色索引。
  • indicator_color1 clrGreen, clrRed 将绿色分配给看涨烛形,将红色分配给看跌烛形。
  • indicator_style1 STYLE_SOLID 确保烛形边缘为实线。
  • indicator_width1 1 定义烛形轮廓的粗细。

为什么是 5 个缓冲区而不是 4 个?

典型的蜡烛图结构需要 4 个缓冲区:

  • OpenBuffer[]:存储移动平均开盘价。
  • HighBuffer[]:存储移动平均高价。
  • LowBuffer[]:存储移动平均低价。
  • CloseBuffer[]:存储移动平均收盘价。

然而,由于该指标使用彩色烛形,它还需要第五个缓冲区:

  • ColorBuffer[]:存储颜色索引(0 代表绿色,1 代表红色)。  通过提供额外的缓冲区,移动平均线以看跌(红色)和看涨(绿色)烛形表示,这有助于识别价格模式。
//--- indicator buffers mapping
SetIndexBuffer(0, OpenBuffer, INDICATOR_DATA);
SetIndexBuffer(1, HighBuffer, INDICATOR_DATA);
SetIndexBuffer(2, LowBuffer, INDICATOR_DATA);
SetIndexBuffer(3, CloseBuffer, INDICATOR_DATA);
SetIndexBuffer(4, ColorBuffer, INDICATOR_COLOR_INDEX);

PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, period);

为了以烛形格式保存确定的移动平均值,此部分代码映射了指标缓冲区。移动平均线的此烛形变体使用四个缓冲区(OpenBuffer、HighBuffer、LowBuffer 和 CloseBuffer)来存储相当于开盘价、最高价、最低价和收盘价的移动平均线,而传统的基于线的版本只需要一个缓冲区来保存用于绘制的计算值。

第五个缓冲区称为 ColorBuffer,也用于识别每根蜡烛的颜色,区分看跌(红色)和看涨(绿色)烛形。为了保证指标正确处理和显示数据,SetIndexBuffer() 函数将每个缓冲区与不同的绘图索引关联起来。为了防止图形不完整或具有欺骗性,PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, period); 确保指标仅在有足够可用的柱形后才开始绘制。

if(rates_total < period)
  {
   Print("Error: Not enough bars to calculate.");
   return 0;
  }

for(int i = period - 1; i < rates_total; i++)
  {

   if(i - period + 1 < 0)
      continue;

   double sumClose = 0.0;
   double sumOpen = 0.0;
   double sumHigh = 0.0;
   double sumLow = 0.0;

   for(int j = 0; j < period; j++)
     {
      int index = i - j;
      if(index < 0)
         continue; // Prevent out-of-bounds access

      sumClose += close[index];
      sumOpen += open[index];
      sumHigh += high[index];
      sumLow += low[index];
     }

   if(period > 0)
     {
      OpenBuffer[i] = sumOpen / period;
      HighBuffer[i] = sumHigh / period;
      LowBuffer[i] = sumLow / period;
      CloseBuffer[i] = sumClose / period;
     }
   else
     {
      Print("Error: Division by zero prevented.");
      return 0;
     }

   ColorBuffer[i] = (CloseBuffer[i] >= OpenBuffer[i]) ? 0 : 1;
  }

return rates_total;

该代码段首先确定是否有足够的价格柱来继续计算。由于 if (rates_total < period) 条件,有足够的历史数据点(或“柱”)来根据用户定义的周期计算移动平均值。如果柱数不足,该函数将返回 0 并写入错误消息。这一步至关重要,因为如果没有足够的数据就无法进行有意义的移动平均计算,而尝试这样做可能会导致不准确或误导的统计数据。

在验证有足够的柱形后,for 循环将迭代价格数据,从 period - 1 索引开始,一直到 rates_total。每个柱都由循环处理,循环从指定时间开始并继续前进。在这个循环中,当前柱索引由变量 i 表示。该循环使用前一时期的柱形价格信息来确定每个柱形的移动平均值。如果索引 i - 周期 + 1 小于 0,则循环继续下一次迭代,从而确保仅当有足够数量的柱线可用时才开始计算。

循环内部有四个变量初始化为 0.0:sumClose、sumOpen、sumHigh 和 sumLow。指定时间段内相应价格数据的累计总和将存储在这些变量中。使用第二个 for 循环收集周期内每个柱形的价格信息,这次从 0 迭代到 period -1。通过从当前索引 i 中减去 j,内部循环检索每个先前的柱形并计算指定期间内的收盘价、开盘价、最高价和最低价的总和。循环通过使用 if(index < 0) 条件来防止访问数组中的越界条目,从而避免错误数据。

为了避免除以 0 的问题,代码在收集该期间的价格数据后确定该期间是否大于 0。如果该周期有效,代码将确定开盘价、最高价、最低价和收盘价的平均值,并将结果保存在 OpenBuffer、HighBuffer、LowBuffer 和 CloseBuffer 缓冲区中。烛形的计算值存储在这些缓冲区中。最后,通过更新 ColorBuffer 来设置烛形的颜色。当收盘价大于或等于开盘价时,烛形颜色为绿色(值 0),表示看涨趋势;如果收盘价小于或等于开盘价,则烛形颜色为红色(值 1),表示看跌趋势。

rates_total 的值向 MetaTrader 表明成功处理了多少个柱,该值在 OnCalculate 函数结束时返回。对于稍后调用 OnCalculate 方法,此值至关重要,因为它跟踪已处理的柱形数量并确保正确处理实时数据更新。

图 6. 烛形移动平均线

通过使用蜡烛图格式,我们还为更高级的指标(如 Heikin Ashi 图表)奠定了基础,我们将在未来的文章中探讨这些指标。


结论

总之,本文介绍了一些基本概念,例如创建自定义指标、使用移动平均线以及以线形和烛形样式等不同格式可视化数据。我们还探讨了如何使用缓冲区和设置图表来表现数据。展望未来,我们将在未来的文章中重点介绍更有趣的项目。初学者学习的最佳方式是通过基于项目的方法,将学习分解为可管理的步骤,而不是在这个阶段用不必要的细节压倒你。这种方法确保了逐步进步和对关键概念的更深入理解。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17096

附加的文件 |
最近评论 | 前往讨论 (2)
dhermanus
dhermanus | 30 5月 2025 在 07:12

感谢您的努力。Isreal.非常感谢。


你没有使用 prev_calculated。这意味着每运行一个 tick
,你的代码就会重新计算之前计算过的每一个条形图。

Israel Pelumi Abioye
Israel Pelumi Abioye | 30 5月 2025 在 09:27
dhermanus calculated。这意味着每运行一个 tick
,你的代码就会重新计算之前计算过的每一个条形图。
您好。
非常感谢您的评论,我会考虑的。

谢谢。
从新手到专家:支撑与阻力强度指标(SRSI) 从新手到专家:支撑与阻力强度指标(SRSI)
在本文中,我们将分享如何利用MQL5编程来精准定位市场关键价位——区分价格水平中的弱势与强势区域。我们将完整开发一个可用的支撑与阻力强度指标(SRSI)。
优化中自定义准则的新方法(第一部分):激活函数示例 优化中自定义准则的新方法(第一部分):激活函数示例
本系列文章首篇将探讨自定义准则的数学原理,重点聚焦神经网络中使用的非线性函数、MQL5实现代码,以及目标导向与校正偏移量的应用。
交易中的神经网络:配备注意力机制(MASAAT)的智代融汇(终章) 交易中的神经网络:配备注意力机制(MASAAT)的智代融汇(终章)
在上一篇文章中,我们讲述了多智代自适应框架 MASAAT,其用一组智代的融汇在不同数据尺度下对多模态时间序列进行交叉分析。今天我们将继续实现该框架方法的 MQL5 版本,并将这项工作带至逻辑完结。
接受者操作特征(ROC)曲线入门 接受者操作特征(ROC)曲线入门
ROC 曲线是用于评估分类器性能的图形工具。尽管 ROC 图形相对简单,但在实践中使用它们时,仍存在一些常见的误解和误区。本文旨在为那些希望理解分类器性能评估的交易者提供一份关于 ROC 图形的入门介绍。