English Русский Español Deutsch 日本語 Português
preview
MQL5 简介(第 3 部分):掌握 MQL5 的核心元素

MQL5 简介(第 3 部分):掌握 MQL5 的核心元素

MetaTrader 5EA交易 | 2 九月 2024, 09:25
239 0
Israel Pelumi Abioye
Israel Pelumi Abioye

概述

欢迎回来,交易员朋友们和有理想的算法爱好者们!当我们进入 MQL5 旅程的第三章时,我们站在理论和实践的十字路口,准备揭开数组、自定义函数、预处理器和事件处理背后的秘密。我们的使命是让每一位读者,无论其编程背景如何,都能深刻理解这些基本的 MQL5 元素。

这一旅程的独特之处在于我们对清晰度的承诺。我们将解释每一行代码,确保您不仅能掌握概念,还能亲眼目睹它们在实际应用中的生动体现。我们的目标是创建一个包容性的空间,让所有人都能使用 MQL5 语言。在我们学习基础知识的过程中,我们不仅欢迎而且鼓励您提出问题。这只是一个开始,为未来引人入胜的编码冒险奠定了基础。让我们一起学习、构建和探索 MQL5 编程的世界。

因此,请集中注意力,加入我们的沉浸式探索。我们将共同把理论知识转化为实践专业知识,确保理解的拥抱延伸到每个人。舞台已经搭好,代码也已准备就绪,让我们一起进入精通 MQL5 的世界吧!

在本文中,我们将讨论以下主题:

  • 数组
  • 自定义函数
  • 预处理器
  • 事件处理

在我们开始第 3 部分深入 MQL5 编码的实践之旅之前,我有一个令人兴奋的消息要宣布。为了增强您的学习体验,我将提供一个视频摘要,总结我们第二部分探索的关键概念和亮点。这种可视化复述旨在强化您的理解,使复杂的主题更加易懂。



1.  数组

什么是数组?

数组是编程的基本概念,提供了组织和存储数据的有效方法。它允许你在一个变量名下存储和组织多个值。您可以使用数组来保存相同类型的元素集合,而不是为每项数据创建单独的变量。这些元素可以使用索引进行访问和操作。

类比

数组就像一个神奇的盒子,您可以把一堆相似的东西放在一起。想象一下,您有一排盒子,每个盒子都有自己的编号。这些数字有助于您查找和整理物品。假设有一排盒子,编号分别为 0、1、2、3,以此类推(计数总是从 0 开始)。每个盒子里都可以放一些特别的东西,比如您最喜欢的玩具或糖果。当您想玩某个特定的玩具时,说出盒子的编号,就可以了!您能够找到您想要的东西。 因此,编程中的数组就像这一排编号的盒子。它可让您将许多类似的物品放在同一个地方,并使用它们的特定编号轻松查找。这是一种很酷的整理方式!

1.1.如何声明数组

在 MQL5 中,通过指定数据类型和名称来声明数组,然后再指定数组维数(如果有的话)。

下面是基本语法:

data_type array_name[array_size];

  • data_type:数组将保存的数据类型(如 int、double、string 等)
  • array_name:数组的名称。
  • array_size:数组的大小或长度,指定数组可容纳的元素数量。
示例 :
void OnStart()
  {

   int closePrices[5]; // An integer array with a size of 5, means the array has 5 integer elements
   double prices[10];  // A double-precision floating-point array with a size of 10

  }

1.2. 为数组赋值

在 MQL5 中,您可以在数组声明时或声明后单独为其赋值。让我们来分析一下这两种方法:

1.2.1. 声明后赋值

在此方法中,您首先声明数组,然后分别为每个元素赋值。它允许您在程序执行期间或根据某些条件动态分配值。 

示例:

void OnStart()
  {

   int closePrices[5];
   closePrices[0] = 10;
   closePrices[1] = 20;
   closePrices[2] = 30;
   closePrices[3] = 40;
   closePrices[4] = 50;

  }

1.2.2.在声明时赋值

在这个方法中,你需要声明数组,并在大括号 {} 中提供数值。数组的大小由您提供的数值数量决定。在此示例中,数组 "closePrices" 的大小为 5,初始化值为 10、20、30、40 和 50。  这两种方法都是有效的,您可以选择适合自己编码风格或特定程序要求的方法。

示例:

void OnStart()
  {

   int closePrices[5] = {10, 20, 30, 40, 50};

  }

1.3. 如何访问数组元素

在大多数编程语言(包括 MQL5)中,数组索引从 0 开始。因此,默认情况下,当您声明一个数组并访问其元素时,计数从 0 开始。

图 1.访问数组元素

示例:

void OnStart()
  {
  
// Declare an integer array with a size of 5
   int closePrices[5];

// Assign values to array elements
   closePrices[0] = 10;
   closePrices[1] = 20;
   closePrices[2] = 30;
   closePrices[3] = 40;
   closePrices[4] = 50;
   
   // Access and print array elements
   Print("Element at index 0: ", closePrices[0]); // Output: 10
   Print("Element at index 1: ", closePrices[1]); // Output: 20
   Print("Element at index 2: ", closePrices[2]); // Output: 30
   Print("Element at index 3: ", closePrices[3]); // Output: 40
   Print("Element at index 4: ", closePrices[4]); // Output: 50

  }

解释:

数组声明:

int closePrices[5];

这一行声明了一个名为 "closePrices" 的整数数组,其大小固定为 5 个元素。在 MQL5 中,数组可以存储相同数据类型的元素,索引从 0 开始,因此该数组的索引为 0 至 4。

为数组元素赋值:

   closePrices[0] = 10;
   closePrices[1] = 20;
   closePrices[2] = 30;
   closePrices[3] = 40;
   closePrices[4] = 50;

在这里,使用索引为数组的每个元素赋值。现在,数组 "closePrices" 中的数值分别为 10、20、30、40 和 50。

访问和打印数组元素:

   Print("Element at index 0: ", closePrices[0]); // Output: 10
   Print("Element at index 1: ", closePrices[1]); // Output: 20
   Print("Element at index 2: ", closePrices[2]); // Output: 30
   Print("Element at index 3: ", closePrices[3]); // Output: 40
   Print("Element at index 4: ", closePrices[4]); // Output: 50
"Print" 语句演示了如何访问和显示存储在数组特定索引处的值。访问索引 0 至 4,并将相应的值打印到控制台。

在对 MQL5 数组的入门探索中,我们使用有组织的数据容器这一隐喻揭示了基本原理。通过代码示例,我们演示了数组元素的声明、初始化和访问。  值得注意的是,MQL5 配备了一系列强大的数组功能。稍后我们将详细探讨这些问题,但现在有必要先熟悉一下基本的数组概念。


2.自定义函数

什么是通用函数?

自定义函数也称为用户定义函数,是您创建的特定代码段,用于在编程语言中执行特定的任务或任务集。在用于算法交易的 MQL5 中,自定义函数由程序员编写,用于封装一系列行为、计算或操作。 通过这些函数,您可以将代码模块化,使其更具可读性、可重用性并更易于维护。您可以创建一个封装了逻辑的自定义函数,然后在需要执行特定任务时调用该函数,而不是在程序中的多个地方重复相同的代码。

在上一篇文章中,我们探讨了 Alert、Comment 和 Print 等常用函数,在此基础上,我们现在深入探讨在 MQL5 中制作函数的领域。正如技艺高超的工匠会改进他们的工具一样,创建自定义函数可以让您根据交易策略的要求来精确定制代码。

类比

想象一下,您正在厨房里准备一道美味佳肴(您的代码)。在编程的烹饪世界里,函数就像菜谱,提供了制作特定菜肴的逐步指令。现在,让我们来探讨一下自定义函数与通用函数的类比。 把通用函数想象成烹饪书中广为人知的食谱。这些食谱都是标准的常用食谱,比如煮一杯咖啡或煮一个鸡蛋。在编程方面,"Alert"、"Comment" 和 "Print" 等函数类似于这些众所周知的食谱。它们有特定的用途,而且随时可以立即使用。

另一方面,自定义函数就像您独一无二的家庭秘方。这些菜肴都是您根据自己的口味和喜好而量身打造的。同样,编码中的自定义函数是程序员为执行特定任务而精心设计的个性化指令集。它们包含了一系列实现特定结果的步骤,就像你制作完美巧克力蛋糕的秘方一样。主要区别在于定制程度。通用函数就像即食食品一样方便、快捷、适用范围广。它们可以高效地完成常见任务,就像可以加热的预制晚餐一样。另一方面,自定义函数提供了精确性和个性化。它们就像从头开始烹饪,让您可以调整配料、口味和技术,以满足您对编码菜肴的独特要求。

2.1.如何创建函数

在 MQL5 中创建函数包括指定函数名称、定义参数(如果有)、指定返回类型(如果返回值)以及提供构成函数主体的代码块。

步骤 1:确定函数的目的

确定函数将执行什么任务或计算。这将帮助您确定函数的名称、输入参数和返回类型(如有)。在本例中,让我们创建一个两个数字相乘的函数。

步骤 2:声明函数

使用选定的名称、输入参数和返回类型(如适用)声明函数。声明应在全局范围内进行,而不是在任何特定函数或事件处理函数中进行。

请注意:MQL5 中的事件处理函数是自动响应特定事件的函数,如市场价格变化 (OnTick)、图表交互 (OnChartEvent) 和定时间隔 (OnTimer)。它们根据 MetaTrader 平台中的实时或计划事件简化代码执行。  更多解释,敬请期待。

句法:

return_type FunctionName(parameter1_type parameter1, parameter2_type parameter2, ...) 
   {
    // Function body (code block)
    // Perform actions or calculations
    // Optionally, return a value using the 'return' keyword if the function has a return type
   }

步骤 3:定义函数体

在函数内部,添加执行所需任务或计算的代码。包括任何必要的变量和逻辑。

示例:

double MyFunction(int param1, double param2)
     {
      // Function body: Calculate the product of the two parameters
      double result = param1 * param2;

      // Return the result
      return result;
     }

解释:

"double MyFunction(int param1, double param2)":

  • 这一行定义了一个名为 "MyFunction" 的函数。 它需要两个参数:"param1" 的类型为 "int"(整数),"param2" 的类型为 "double"(浮点数)。函数预计将返回一个 "double" 类型的值。

{:

  • 开头的大括号"{"标志着函数主体的开始,也就是函数实际代码所在的位置。

“double result = param1 * param2;”:

  • 这一行计算 "param1" 和 "param2" 的乘积,并将结果存储在名为 result 的变量中。由于 "param1" 和 "param2" 都是数字类型,乘法运算 (*) 的结果是 double 型。

“return result;”:

  • 这一行退出函数("MyFunction"),并将存储在结果变量中的值返回给调用函数的代码。由于函数的返回类型是 "double",这意味着调用者希望收到一个 double 型数值。

"}":

  • 结尾的大括号"}"标志着函数主体的结束。

步骤 4:使用函数

从代码的其他部分调用该函数。您可以在事件处理程序、其他函数或任何合适的上下文中使用该函数。

示例:
void OnStart()
  {

// Calling the custom function
   double result = MyFunction(5, 3.5);

// Printing the result
   Print("The result is: ", result); // The output will be 17.5

  }

所提供的函数 "MyFunction" 可自动完成两个数字的乘法运算。调用该函数并提供所需的数值作为参数后,计算就会顺利进行,并返回计算结果。这种抽象简化了数字乘法的过程,提高了代码的可读性和可重用性。

我鼓励您积极参与内容,并随时提出可能出现的任何问题。您的理解是我的首要任务,我会在这里提供清晰的解释,或就任何相关主题提供进一步的说明。您的问题有助于我们获得更丰富的学习体验,因此请随时联系我们。我会尽我所能及时回复,并帮助您理解文章中讨论的概念。


3.预处理器

什么是预处理器?

在 MQL5 编程的动态世界中,预处理器是决定代码解释和执行方式的重要工具。预处理器是一种专门的软件组件,可在实际编译过程开始前对源代码进行操作。它的主要作用是处理影响结果程序的行为、结构和特征的指令和指示。

预处理器的工作原理是对原始源代码进行转换或预处理。它们会解释前面用哈希符号("#")标记的特定指令,并采取相应行动,在编译前更改代码的内容。 这些指令也称为预处理器指令,包含一系列功能,包括定义常量、合并外部文件、设置程序属性和启用条件编译。每条指令都有其独特的作用,可根据开发人员的要求塑造代码。

预处理器的独特之处在于它们能够在实际编译之前执行。代码操作的早期阶段允许开发人员建立将影响最终编译程序的某些条件、配置或包含。

图 2.MetaEditor 中的预处理器

请注意:在 MQL5 编程中,必须记住预处理器不像常规语句那样以分号(;)结束,而是在代码的全局空间中声明。这些独特的指令,如用于宏定义的 "#define" 或用于包含文件的 "#include",在塑造程序的行为和结构方面起着至关重要的作用。因此,在使用预处理器时,跳过分号,确保它们在全局聚光灯下有自己的空间!

类比

让我们想象一下,编写代码就像是告诉机器人该做什么。现在,在我们告诉机器人之前,我们有一个特殊的朋友叫做 "预处理器"。这位朋友帮助我们为机器人准备好一切,让它完全理解我们的指令。可以把预处理器想象成一个神奇的助手,它会在机器人执行指令之前先查看我们的指令。它的工作就是让事情变得更简单、更有条理。 有时,我们想使用一些特殊的词语来表达一些重要的含义,比如说 "MAGIC_NUMBER",而不是数字 10。预处理器可以帮助我们理解这些特殊词汇,并用真实数值代替它们。

示例:

#define MAGIC_NUMBER 10

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {

   int result = MAGIC_NUMBER + 5;
   Comment(result); // Output will be 15

  }

解释:

"#define MAGIC_NUMBER 10":

  • 这一行就像是在说:"嘿,预处理器,只要你在我的代码中看到 'MAGIC_NUMBER' 这个词,就把它替换成数字 10"。这就像创造一个代表特定数值的特殊词汇。

"void OnStart()":

  • 这部分是主要行动开始的地方。"OnStart" 是 MQL5 中的一个特殊函数,会在脚本开始运行时执行。

"int result = MAGIC_NUMBER + 5;":

  • 在这里,我们使用了特殊的单词 "MAGIC_NUMBER"。预处理器看到这一点后,将 "MAGIC_NUMBER" 替换为 10。因此,这一行等同于 "int result = 10 + 5;"

“Comment(result); // Output will be 15”:

  • "Comment" 函数就像是让机器人说点什么。在本例中,我们要求机器人说出 "result" 的值,即 15。因此,当您运行这个脚本时,它将在图表的注释中显示 "15"。

最酷的是,在我们告诉机器人该做什么之前,预处理器就已经开始工作了!它会查看我们的特殊字词并准备好一切,因此当机器人开始工作时,它已经知道该做什么了。因此,预处理器就像是我们的秘密朋友,它能确保我们的指令清晰明了、代码整齐划一,而且一切都设置得完美无缺,让机器人能够顺利地执行指令。这就像在编码世界里有了一位超级英雄朋友!


3.1.预处理器类别

3.1.1.宏替换(#define)

在编码领域,宏用于创建常量,常量就像不变的数值。通过宏,我们可以创建代表这些值的特殊词语。

示例:

#define MAGIC_NUMBER 10

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {

   int result = MAGIC_NUMBER + 5;

  }

在这里,我们不需要每次都使用数字 "10",而只需使用 "MAGIC_NUMBER" 即可,从而使我们的代码更加简洁易读。

3.1.1.1.  参数化宏(类函数宏)

想象一下,你有一个神奇的配方,有时你想改变其中的关键成分。参数化宏使您可以创建灵活的指令。

示例:

#define MULTIPLY(x, y) (x * y)

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {

   int result = MULTIPLY(3, 5); // Result is 15

  }

在这里,宏 "MULTIPLY" 将两个成分(数字)相乘。这就像在你的代码中加入了一个神奇的函数!

解释:

"#define MULTIPLY(x, y) (x * y)":

  • 这一行就像是在告诉计算机:"嘿,每当我说'MULTIPLY'时,我的意思是把两个数字(我们称它们为'x'和'y')相乘"。这就像用我们的魔法词创造了一台乘法器。

"int result = MULTIPLY(3, 5);":

  • 在这里,我们使用了一个神奇的词 "MULTIPLY"。计算机看到这句话,就知道它的意思是 "把数字 3 和 5 相乘"。因此,它将 MULTIPLY(3, 5) 替换为 (3 * 5)。

“// Result is 15”:

  • 3 和 5 相乘的结果是 15。因此,计算机计算后,"Result" 的值就变成了 15。

本质上,这段代码简化了乘法过程。我们不直接写 "3 * 5",而是使用 "MULTIPLY" 这个神奇的单词,使我们的代码更易读、更易懂。这就像在我们的代码中加入了一个迷你数学助手!

3.1.1.2.#undef 指令

"#undef" 指令就像是在说:"忘了我之前跟你说过的话吧,让我们重新开始"。在 MQL5 编程世界中,它允许我们取消定义或 "取消创建 "先前定义的宏。就像擦除魔法白板上的文字一样。

示例:

#define MAGIC_NUMBER 10

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {

   int result = MAGIC_NUMBER + 5;
#undef MAGIC_NUMBER // Undefining MAGIC_NUMBER


  }

解释:

"#define MAGIC_NUMBER 10":

  • 首先,我们定义一个名为 "MAGIC_NUMBER" 的宏,并将其值设为 10。这就好比说,"每当我使用'MAGIC_NUMBER'时,我指的就是数字 10"。

"int result = MAGIC_NUMBER + 5;":

  • 我们在计算中使用了 "MAGIC_NUMBER",结果变成了 15。

"#undef MAGIC_NUMBER":

  • 通过 "#undef MAGIC_NUMBER",我们可以说:"好吧,忘掉'MAGIC_NUMBER'之前的意思吧。"这就好比删除定义,使 "MAGIC_NUMBER" 变成未定义。
现在,如果您尝试在 "#undef "行之后使用 "MAGIC_NUMBER",就会出现错误,因为计算机不再知道 "MAGIC_NUMBER" 的含义。如果您想在代码的特定部分重新定义或停止使用宏,这将非常有用。这就好比告诉电脑:"我用了这个神奇的词一会儿,现在换别的吧"。

3.1.2.程序属性 (#property)

在编码领域,每个程序都有其独特的特征,就像故事中的人物一样。通过使用 "#property",我们可以赋予程序特殊的功能和品质。这就好比在说:"嘿,电脑,这是我的程序与众不同的地方。" 想象一下,如果你正在写一本书或一首歌,你可能会想告诉人们是谁创作了这本书或这首歌,以及创作的时间。"#property" 可以帮助我们为程序做同样的事情。这就像在我们的代码开头添加一个小注释,说:“这个程序是1.0版本,我是在2022年制作的。

示例:

#property copyright "crownsoyin"
#property version   "1.00"

运行代码后的结果

图 3.在 MT5 中运行代码后的结果

"#property" 就像一本书的封面页或一部电影的片头字幕。它定下了基调,提供了有关程序的信息,并帮助每个人,包括编码人员和以后可能阅读代码的其他人,理解发生了什么。

3.1.3 包含指令 (#include)

什么是包含文件?

想象一下,你有一本神奇的菜谱集,但有些菜谱保存在另一本书中。"#include" 就像是在说:"嘿,让我们把其他书中另外的食谱拿过来,合并成一个大的食谱集"。在编码方面,它可以帮助我们将外部文件与主代码合并,使所有文件都可以在一个地方访问。 把编码想象成建造一座有不同房间的房子,每个房间都有特定的功能,比如厨房或卧室。通过 "#include",我们可以在其他房屋(程序)中重复使用这些房间(功能和结构)。这就好比说,"我建造了这个梦幻般的厨房;现在,我可以在我所有的房子里使用它,而无需从头开始建造"。

示例:

#include "extraRecipes.mqh" // Include external file with extra recipes

解释:

"#include":

  • 将 #include" 视为一个魔法指令,它说:"从其他地方引入一些特别的东西,并将其添加到这里"。这就像一个秘密入口,可以为我们的食谱获取更多的配料。

'"extraRecipes.mqh"':

  • 双引号内的文本是我们要包含的外部文件的名称。在本例中,是 "extraRecipes.mqh"。该文件包含我们要在主程序中使用的附加配方或代码。

因此,当我们写下 #include "extraRecipes.mqh" 时,就相当于打开了一本秘密食谱,然后说:"让我们把这些额外的食谱添加到主要的烹饪说明中吧"。这有助于保持主代码的整洁和有序,同时还能访问 "extraRecipes.mqh" 中的额外魔法。这就像用更多的咒语和魔法来扩充我们的魔法食谱! 使用 "#include" 在代码中包含外部文件时,该外部文件中定义的任何函数或结构都可以访问,并可在主代码中使用。

示例:

外部文件:"extraRecipes.mqh"

// extraRecipes.mqh
int MultiplyByTwo(int number) // function from an external file  
{
    return number * 2;
}

主代码:

// Your main code
#include "extraRecipes.mqh" // Include external file with extra recipes
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {

   int result = MultiplyByTwo(5);  // Using the function from the included file
   Print("Result: ", result);      // Output: Result: 10

  }


在本例中,"MultiplyByTwo" 函数是在外部文件 "extraRecipes.mqh" 中定义的。通过在主代码中使用 #include "extraRecipes.mqh",您现在可以调用并使用 "MultiplyByTwo" 函数,就像直接在主代码中定义该函数一样。

了解代码包含和函数使用的复杂性,一开始可能会觉得有点复杂,这没关系!编程就像学习一门新语言,每一段旅程都始于第一步。如果你发现自己挠头或有点困惑,不要犹豫,问问题。您的好奇心会推动学习进程,而我将帮助您揭开所有谜团。所以,拥抱学习冒险,记住问题是解开更深层次理解的钥匙。如果您有任何疑问,欢迎随时提出,让我们一起踏上编码之旅!

3.1.4.编译条件(#ifdef、#ifndef、#else、#endif)

编译条件允许我们在编译过程中包含或排除部分代码。这就像一个特殊的指令,指导编译器根据某些条件包含哪些内容。

3.1.4.1."#ifdef" 指令

MQL5 中的 #ifdef 指令是一种预处理器指令,用于检查特定符号是否已定义。如果符号已定义,编译时将包含 #ifdef 后面的代码块;否则,将包含 #else 或 #endif 后面的代码块。

示例:

#define MAGIC_NUMBER 10
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {

#ifdef MAGIC_NUMBER
   Print(MAGIC_NUMBER);
#else
   Print(MAGIC_NUMBER);
#endif


  }

解释:

  • 在本例中,使用 "#define "定义了 "MAGIC_NUMBER"。
  • #ifdef MAGIC_NUMBER "检查是否定义了 "MAGIC_NUMBER"。
  • 由于它已被定义,因此 "#ifdef" 后面的代码块被包含在内,从而编译了第一条 "Print" 语句。
  • 如果没有定义 "MAGIC_NUMBER",则将包含 "#else" 后面的代码块,并编译第二条 "Print" 语句。

"#ifdef" 通常用于条件编译,允许开发人员根据预定义符号包含或排除特定代码段。它是创建可调整和可配置代码的重要工具,允许开发人员在编译过程中根据定义的符号或条件定制应用程序。

3.1.4.2."#ifndef" 指令

MQL5 中的 "#ifndef" 指令是一种预处理器指令,用于检查特定符号是否未定义。如果宏未定义,则在编译时包含 "#ifndef" 后面的代码块;否则,包含 "#else" 或 "#endif" 后面的代码块。

示例:

void OnStart()
  {

#ifndef MAGIC_NUMBER
   Print(MAGIC_NUMBER);
#else
   Print(MAGIC_NUMBER);
#endif


  }

解释:

  • #ifndef MAGIC_NUMBER" 会检查 "MAGIC_NUMBER" 是否未定义。
  • 如果未定义 "MAGIC_NUMBER",则会包含 "#ifndef" 后面的代码块,并打印一条信息,说明未定义 "MAGIC_NUMBER"。
  • 如果定义了 "MAGIC_NUMBER",则包括 "#else" 后面的代码块,并打印出 "MAGIC_NUMBER" 的值。

这段代码演示了根据是否定义了特定宏(本例中为 "MAGIC_NUMBER")来使用条件编译的方法。根据宏的存在与否,编译时会包含不同的代码块。

请注意:本例中未定义 "MAGIC_NUMBER"

3.1.4.3."#endif" 指令

MQL5 中的 "#endif" 指令标志着由 "#ifdef" 或 "#ifndef" 等指令启动的条件代码块的结束。它向预处理器发出信号,表明条件编译部分已经结束,后续代码应进行编译处理。它没有任何条件或参数,其目的是表示条件编译块的结束。

示例:

#define MAGIC_NUMBER 10
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {

#ifndef MAGIC_NUMBER
   Print(MAGIC_NUMBER);
#else
   Print(MAGIC_NUMBER);
#endif

  }

解释:

  • "#endif" 标志着条件块的结束,随后的代码将正常处理。

请注意:始终将 "#endif" 与开头条件指令(#ifdef 或 #ifndef)搭配使用,以保持正确的语法并避免编译错误。

在对 MQL5 预处理器的简要探讨中,我们介绍了用于创建常量的宏,用于定义特性的程序属性 (#property),用于模块化的包含文件,以及用于灵活代码的条件编译。虽然这只是一个简介,但还有更多精彩等着我们去发现。这些工具构成了高效 MQL5 编程的基石,提供了多功能性和适应性。在您继续制作强大交易算法的过程中,请不要犹豫,提出问题并深入探讨这些概念。拥抱好奇心,让您对预处理器的理解有机地增长。


4.事件处理

什么是事件?

程序设计中的事件是指在程序执行过程中发生的特定事情或情况。事件可由各种操作触发,如用户交互和系统状态变化。在 MQL5 的背景下,事件对于开发动态响应市场条件的算法交易脚本至关重要。

什么是事件处理过程?

事件处理过程是一段指定的代码,负责响应特定类型的事件。在 MQL5 中,事件处理过程是在特定事件发生时执行的函数。这些函数是预定义的,可作为各种事件的指定响应机制。每种类型的事件都有相应的事件处理函数。

类比

想象一下,你正在观看一场神奇的木偶剧,每当某些事情发生时,比如观众拍手或按下特殊按钮时,木偶就会移动并说话。现在,把木偶想象成计算机程序的不同部分,而魔法按钮则是一个事件。事件处理过程就像幕后的木偶主人,等待着那些特定时刻(事件)的发生。当事件发生时,事件处理过程就会立即行动起来,让程序做一些特别的事情,就像木偶大师在观众欢呼或按下按钮时让木偶跳舞或说话一样。因此,在编程的世界里,事件处理过程就像一个神奇的木偶大师,当某些事件发生时,它就会让事物栩栩如生!

4.1.事件处理函数类型

图 4.某些类型的事件处理函数


4.1.1.OnInit

在 MQL5 中,"OnInit" 是专家顾问(EA)中的一个特殊函数,用于在首次将 EA 加载到图表时对其进行初始化。

类比:

好吧,让我们想象一下,您有一个神奇的机器人朋友。在机器人开始做任何令人兴奋的事情(比如走来走去或发出怪声)之前,它需要做好准备。"准备"部分就像是机器人的 "OnInit" 时刻。 因此,当你第一次开启机器人时,它会进入一个特殊的房间(OnInit 函数),在那里进行自我准备。在这里,它可以设置自己喜欢的颜色,决定移动的速度,并确保一切都恰到好处。一切准备就绪后,机器人就可以出场了,开始耍酷,比如跳舞或讲笑话。

在计算机程序中,"OnInit" 函数的工作原理与此类似。这是一个特殊的房间,程序在这里做好准备,然后才开始执行任务。这就像是计划的开幕式,确保一切就绪,准备就绪!

示例:

// Declare global variables
double LotSize;
int TakeProfit;
int StopLoss;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
// OnInit function
int OnInit()
  {
// Set EA parameters
   LotSize = 0.1;
   TakeProfit = 50;
   StopLoss = 30;

// Display initialization message
   Print("EA is getting ready. Lot Size: ", LotSize, ", Take Profit: ", TakeProfit, ", Stop Loss: ", StopLoss);

   return(INIT_SUCCEEDED);
  }

解释:

  • 我们声明了一些 EA 可能会用到的全局变量。
  • 在 OnInit 函数中,我们将初始化这些变量并进行必要的设置。在本例中,我们设置了 "LotSize(手数)"、"TakeProfit(止盈)"和 "StopLoss(止损)"的值。
  • Print"(打印)语句就像是 EA 向控制台发送的一条信息,用来通知我们它的初始化情况。这就像机器人说:"我准备好了,这是我的设置"。

将此 EA 附加到图表时,"OnInit" 函数会运行一次,控制台会显示初始化信息。这可确保 EA 在开始交易或执行其他操作之前,其设置已准备就绪。

4.1.2.OnStart

在 MQL5 中,"OnStart" 函数是脚本和智能交易系统(EA)的重要组成部分。它的主要作用是在脚本激活或启动时只执行一次命令。它是脚本执行的初始入口点。 就脚本而言,"OnStart" 函数运行其定义的逻辑,其中可能包括下单交易、执行计算或其他操作。不过,与在 "OnStart" 函数中持续运行并重新评估条件的智能交易系统(EA)不同,脚本通常只执行一次逻辑,然后就会停止。

因此,如果您在 MQL5 脚本的 "OnStart" 函数中设置了与交易相关的操作,这些操作将在脚本激活时执行,但脚本不会持续监控市场或执行额外的交易。

示例:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
// This script prints a message to the console when activated

// Entry point when the script is started
void OnStart()
  {
// Print a message to the console
   Print("Script Activated! Hello, Traders!");
  }

解释:

  • "OnStart" 函数是脚本的入口点
  • "Print" 函数用于在 MetaTrader 终端的 "终端" 窗口的 "专家" 选项卡中显示信息。
  • 将此脚本附加到图表或在 MetaEditor 中运行时,您将在控制台中看到指定的信息。您可以根据脚本的要求修改打印语句并添加更复杂的逻辑。

当该脚本附加到图表时,"OnStart" 函数将运行一次。脚本中 "OnStart" 函数的作用通常是在脚本连接到图表时执行初始化任务或执行某些操作。脚本运行逻辑后,就完成了操作。

 

4.1.3.OnTick

在 MQL5 中,"OnTick" 函数是 EA 交易的重要组成部分。它的主要作用是包含核心交易逻辑和在市场的每个分时报价上应执行的操作。EA 使用 "OnTick" 监控价格变化,分析市场状况,并根据预定义的交易策略做出决策。与订单相关的操作,如开仓、修改或平仓,通常都放在 "OnTick" 函数中。

类比

把 "OnTick" 函数想象成你的交易朋友 TickTrader,它正在探索繁忙的市场。每一次分时报价都像是一次新的机会或改变。TickTrader 一直在寻找好的交易,就像您在热闹的市场中寻找最佳交易一样。

当市场平静时,TickTrader 可能会慢慢来,只是四处看看,而不急于买进或卖出。同样,如果 "OnTick" 函数发现每一个分时报价点都很平静,那么它可能会建议大家小心谨慎,比如在市场上随便看看。如果价格突然上涨,TickTrader 可能会看到一笔不错的交易并决定购买。同样,当 "OnTick" 函数注意到每一个分时报价点的价格变化很大时,它可能会建议抓住好机会,就像在繁忙的市场中进行大笔交易一样。

在这个生机勃勃的市场环境中,TickTrader 时刻准备着迎接下一个机会,并在分时报价中做出决策。同样,"OnTick" 函数也能实时工作,根据每个分时报价进行调整,并在动态市场中指导交易行为。

示例:
// Declare a variable to store the last tick's close price
double lastClose;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
// OnInit function
int OnInit()
  {

// Initialize the variable with the current close price
   lastClose = iClose(_Symbol, PERIOD_CURRENT, 0);

   return(INIT_SUCCEEDED);
  }

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

// Get the current close price
   double currentClose = iClose(_Symbol, PERIOD_CURRENT, 0);

// Check if the close price has changed
   if(currentClose != lastClose)
     {
      // Print a message when the close price changes
      Print("Close price changed! New close price: ", currentClose);

      // Update the last close price
      lastClose = currentClose;
     }


  }

解释:

变量声明:

// Declare a variable to store the last tick's close price
double lastClose;

我们声明了一个类型为 "double" 的变量 "lastClose",用于存储最后一个分时报价的收盘价。

初始化(OnInit 函数):

nt OnInit()
  {

// Initialize the variable with the current close price
   lastClose = iClose(_Symbol, PERIOD_CURRENT, 0);

   return(INIT_SUCCEEDED);
  }

在 "OnInit" 函数中,我们使用 "iClose" 函数将 "lastClose" 初始化为最近一根烛形的收盘价。参数"_Symbol"、"PERIOD_CURRENT" 和 "0" 分别表示当前交易品种、当前时间框架和最近的烛形

OnTick 函数:

void OnTick()
  {

// Get the current close price 
   double currentClose = iClose(_Symbol, PERIOD_CURRENT, 0);

// Check if the close price has changed
   if(currentClose != lastClose)
     {
      // Print a message when the close price changes
      Print("Close price changed! New close price: ", currentClose);

      // Update the last close price
      lastClose = currentClose;
     }


  }

  • 在 "OnTick" 函数中,我们使用 "iClose" 获取当前收盘价,并将其存储在 "currentClose" 变量中。
  • 然后,我们检查当前收盘价是否与上次记录的收盘价不同("currentClose != lastClose")。
  • 如果有变化,我们会打印一条信息,说明变化情况,并更新 "lastClose" 变量。

每当收盘价在每个分时上发生变化时,这段代码就会进行监控并打印一条信息。它展示了如何利用 "OnTick" 函数实时响应市场动态。


结论

在本文中,我们深入研究了 MQL5 的基本方面,探索了数组、自定义函数、预处理器和事件处理等概念。亲爱的读者们,我鼓励你们拥抱学习之旅,并随时提出任何可能出现的问题。您对这些基本要素的了解将为我们接下来讨论如何创建功能强大的交易机器人铺平道路。 请记住,知识源于好奇心,你的问题就是深入理解的种子。敬请期待下期节目,我们将一起踏上建立交易机器人的精彩旅程。现在,请花点时间熟悉这些核心概念。祝你编码愉快,愿你的交易努力和你对知识的追求一样成功!

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

时间序列分类问题中的因果推理 时间序列分类问题中的因果推理
在本文中,我们将研究使用机器学习的因果推理理论,以及 Python 中的自定义方法实现。因果推理和因果思维植根于哲学和心理学,在我们理解现实中起着重要作用。
神经网络变得简单(第 68 部分):离线优先引导政策优化 神经网络变得简单(第 68 部分):离线优先引导政策优化
自从第一篇专门讨论强化学习的文章以来,我们以某种方式触及了 2 个问题:探索环境和检定奖励函数。最近的文章曾专门讨论了离线学习中的探索问题。在本文中,我想向您介绍一种算法,其作者完全剔除了奖励函数。
神经网络变得简单(第 69 部分):基于密度的行为政策支持约束(SPOT) 神经网络变得简单(第 69 部分):基于密度的行为政策支持约束(SPOT)
在离线学习中,我们使用固定的数据集,这限制了环境多样性的覆盖范围。在学习过程中,我们的 Agent 能生成超出该数据集之外的动作。如果没有来自环境的反馈,我们如何判定针对该动作的估测是正确的?在训练数据集中维护 Agent 的政策成为确保训练可靠性的一个重要方面。这就是我们将在本文中讨论的内容。
多交易品种多周期指标中的 DRAW_ARROW 绘图类型 多交易品种多周期指标中的 DRAW_ARROW 绘图类型
本文将介绍如何绘制多交易品种多周期的箭头指标。我们还将改进类方法,以便正确显示箭头指标的数据,这些数据是根据与当前图表交易品种/周期不一致的交易品种/周期计算的。