English Русский Español Deutsch 日本語 Português
preview
MQL5 中的高级变量和数据类型

MQL5 中的高级变量和数据类型

MetaTrader 5交易 | 31 十月 2024, 09:35
669 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

概述

MQL5 是 MetaTrader 5 的编程语言,MetaTrader 5 被认为是最受欢迎的交易平台,它拥有非常丰富的工具和概念,可用于创建从简单到复杂的任何交易系统。作为开发人员,我们的目标是了解如何使用这些工具和概念来实现我们的开发目标。

我们已经在 "了解为什么以及如何设计算法交易系统"一文中提到了 MQL5 中简单变量和数据类型的基础知识,并了解了更多关于如何使用 MQL5 来编写交易软件代码、变量定义、变量类型以及如何使用变量的信息。我们还学习了简单的数据类型,如整数、浮点数、双精度型、字符串和布尔型,我们还提到了输入变量,以及如何利用它们让用户在软件中设置自己的偏好。

在另一篇文章 "学习如何在 MQL5 中处理日期和时间" 中,我们详细了解了 Datetime 数据类型,因为我们知道如何使用这种数据类型非常重要,没有软件不使用这种数据类型,尤其是在交易软件中。

在本文中,我们将描述并深入了解 MQL5 中的变量和数据类型,以及它们如何在创建或构建 MQL5 交易软件时发挥作用。我们将学习更多关于变量和数据类型的高级概念,并将通过以下主题进行讲解:

  • 常量:它们是其值不变的标识符。
  • 数组:它们是具有多个值的任何类型的变量。 
  • 枚举:它们是具有整数值的常量整数列表。
  • 结构:它们是一组具有不同类型的相关变量。
  • 类型转换:这是将一种类型的值转换为另一种类型的过程。
  • 局部变量:是函数内部声明的局部变量。
  • 全局变量:它们是函数之外的全局声明变量。
  • 静态变量:它们是声明的局部变量,其值保留在内存中。
  • 预定义变量:编程语言原本就预定义的变量。

我们将尝试详细解释每个主题,以了解我们如何使用它,并尽可能多地查看示例,以加深我们的理解,并能够有效地使用所有提到的概念。编程是一门实践非常重要的科学,因此尝试应用所学知识来理解如何将每个概念作为整体代码的一部分来创建有效的交易系统非常重要。


常量

在本部分中,我们将深入探讨编程和 MQL5 中常量的概念,以了解为什么我们需要使用这种类型的变量。常量变量也称为只读或命名常量,它不能更改,或者在第一次初始化后我们不能为其分配新值,如果代码试图这样做,它将产生错误。大多数编程语言都支持常量的概念,MQL5 也支持这个概念,我们有两种方法在软件中定义常量:

  • 全局常量
  • const 指示符

全局常量:

我们可以在软件的开头使用 #define 预处理器指令来定义这个全局常量,然后指定标识符,再分配常量值。常量值可以是任何类型的数据,如字符串类型,以下是我们需要在软件中编码的内容:

#define Identifier_Name constant_value

正如我们在前面的代码中看到的,我们有 #define 预处理器指令,它指定我们有一个常量声明,我们可以看到下面的示例来了解更多关于这个方法的信息:

在全局范围内,我们首先使用 #define 预处理器指令声明我们有一个常量,标识符是 PLATFORM_NAME,标识符的常量值是字符串数据类型 "MetaTrader 5"。

#define PLATFORM_NAME "MetaTrader 5"

在所需的函数中,例如在 OnStart() 中,我们将打印标识符

void OnStart()
  {
   Print("The trading platform name is: ", PLATFORM_NAME);
  }

在编译和执行软件时,我们可以在专家选项卡中找到结果信息,如下截图所示

常量示例

如前所述,MetaTrader 5 的常量值不可更改。

const 指示符:

根据此方法,我们可以在变量及其数据类型之前使用 const 指示符来声明常量。此指示符声明这将是一个常量,不能更改,以下是我们如何做到这一点

const int varName = assignedVal

下面是一个用这种方法声明常量的简单例子

const int val = 1;

如果我们需要打印该变量的值

Print("constant val is: ", val);

我们可以发现结果如下:

常量示例2

如前所述,如果我们尝试更新变量 (val),我们会发现会产生以下错误,因为它是一个常量,无法更新。

constant_error

为了说明普通变量和常量的区别,我们可以用下面的例子来说明,这里有两个值,一个是常量,另一个是普通变量。

   const int val = 1;
   int val2 = 2;
   
   Print("constant val is: ", val);
   Print("val 2 is: ", val2);
执行该软件后,结果将与以下截图相同

diffExam

现在让我们看看,如果我们尝试用另一个值(即 4)更新 val2,代码如下

val2 = 4;

如果再次打印这两个值,结果将与下面的示例相同:

diffExam2

我们可以看到,val2 的值更新为 4,而不是 2。

数组

在本部分中,我们将了解任何编程语言都有的一个基本概念,即数组。数组是一个变量,我们可以在其中存储许多各种数据类型的值。我们可以把数组看作一个带有索引和相应值的列表,当我们需要访问一个特定值时,可以通过索引来实现。

数组索引从零开始,因此最大索引与数组大小减少一的值的结果相同。如果有一个数组有五个值,那么我们可以发现它的索引是(0、1、2、3、4),我们可以看到最大值是 4,这就是 (5-1) 的结果。

如果我们需要访问特定索引的值,我们会用方括号[]中给出的索引来引用它,这就是我们前面提到的通过索引访问的意思。

我们还有一个静态数组和一个动态数组,两者在数组大小方面的区别在于,静态数组是一个大小固定的数组,不能调整大小,但动态数组没有固定大小,如果我们需要调整数组大小,可以使用动态数组,而不是使用静态数组。

静态数组声明

如果我们需要声明一个新的静态数组,可以用下面的示例来实现

int newArray[5];

newArray[0] = 1;
newArray[1] = 2;
newArray[2] = 3;
newArray[3] = 4;
newArray[4] = 5;

Print("newArray - Index 0 - Value: ", newArray[0]);
Print("newArray - Index 1 - Value: ", newArray[1]);
Print("newArray - Index 2 - Value: ", newArray[2]);
Print("newArray - Index 3 - Value: ", newArray[3]);
Print("newArray - Index 4 - Value: ", newArray[4]);

运行该软件后,我们可以看到与下面截图相同的结果

静态数组声明

为了提高效率,我们可以使用下面的简写代码来声明数组,在括号 {} 中为数组赋值,并用逗号 (,) 分隔,从而得到相同的结果。

   int newArray[5] = {1, 2, 3, 4, 5};

   Print("newArray - Index 0 - Value: ", newArray[0]);
   Print("newArray - Index 1 - Value: ", newArray[1]);
   Print("newArray - Index 2 - Value: ", newArray[2]);
   Print("newArray - Index 3 - Value: ", newArray[3]);
   Print("newArray - Index 4 - Value: ", newArray[4]);

 如果我们执行前面的代码,就会得到与我们提到的打印信息相同的结果。

动态数组声明

正如我们之前提到的,动态数组是没有固定大小的数组,可以调整大小。我们需要知道的是,当我们通过 MQL5 编写代码来存储价格和指标值等数据时,我们会使用这种类型的数组,因为这些数据是动态的,所以这种类型的数组非常适合。

如果我们需要声明一个新的动态数组,我们就可以按以下所示代码来做

   double myDynArray[];
   ArrayResize(myDynArray,3);

   myDynArray[0] = 1.5;
   myDynArray[1] = 2.5;
   myDynArray[2] = 3.5;
   
   Print("Dynamic Array 0: ",myDynArray[0]);
   Print("Dynamic Array 1: ",myDynArray[1]);
   Print("Dynamic Array 2: ",myDynArray[2]);

正如我们在前面的代码中所看到的,我们用空的方括号声明了数组,然后使用 ArrayResize 函数调整了数组的大小,执行软件时我们可以看到结果,如下方截图所示

动态数组声明

从前面的截图中我们可以看到,我们为每个索引和相应的值打印了三条信息。现在我们已经理解了一维数组,而我们也可以使用多维数组,如二维、三维或四维数组,但我们最常用的是二维或三维数组。

多维数组

多维数组可视为嵌套数组或数组中的数组。举例来说,我们有两个数组,每个数组有 2 个元素,我们可以通过下面的代码了解如何编写这种类型的数组:

声明包含两个元素的二维数组

double newMultiArray[2][2];

为索引为 0 的第一个数组赋值

newMultiArray[0][0] = 1.5;
newMultiArray[0][1] = 2.5;

 为索引为 1 的第二个数组赋值

newMultiArray[1][0] = 3.5;
newMultiArray[1][1] = 4.5;

打印两个数组的值及其元素或值

   Print("Array1 - Index 0 - Value: ", newMultiArray[0][0]);
   Print("Array1 - Index 1 - Value: ", newMultiArray[0][1]);
   Print("Array2 - Index 0 - Value: ", newMultiArray[1][0]);
   Print("Array2 - Index 1 - Value: ", newMultiArray[1][1]);

运行该软件后,我们可以看到与下面截图相同的输出结果

multiDymArrays

如上图所示,数组 1 有两个值为 1.5 和 2.5,数组 2 也有两个值为 3.5 和 4.5。

这里还需要说明的是,当我们声明多维数组时,只能将第一个维度留空,与下面的示例相同

double newMultiArray[][2];

然后,我们可以使用一个与下面相同的变量来传递它

int array1 = 2;
ArrayResize(newMultiArray, array1);

编译并运行该软件后,我们会发现与之前的结果相同。


枚举

在这一部分,我们将探讨枚举。我们可以将枚举视为数据集或常量或项目列表,可用于描述相关概念,可分为内置枚举和自定义枚举。内置枚举在 MQL5 中是预定义的,我们可以在程序中调用和使用它们,而自定义枚举是根据我们的需要自定义的枚举。

我们可以在 MQL5 的文档或参考资料中找到内置枚举,例如 ENUM_DAY_OF_WEEK。但如果我们看一下自定义枚举,就会发现我们可以设置所需的内容,以便以后在创建的软件中使用它们。

下面介绍如何在软件中使用这些枚举。首先,我们使用 enum 关键字定义一个枚举、枚举类型的名称,以及用逗号分隔的相关数据类型值的列表。

enum name of enumerable type 
  { 
   value 1,
   value 2,
   value 3
  };

让我们来看一个例子,创建名为 workingDays 的枚举,workingDays 将是一个类型,我稍后将为其声明变量

   enum workingDays
     {
      Monday,
      Tuesday,
      Wednesday,
      Thursday,
      Friday,
     };

现在,我们需要声明一个新的相关变量,类型为 workingDays,在列表中定义的变量中指定今天的值,并打印这个变量

     workingDays toDay;
     toDay = Wednesday;
     
     Print(toDay);

如下图所示,我们可以从从 0(星期一)到 4(星期五)的列表中找到星期三(2,Wednesday) 作为工作日的 信息。

枚举

我们还可以确定另一个起始数,而不是 (0),方法是将我们需要的起始数赋值,例如,指定 Monday = 1。


结构

我们可能会发现,我们需要为相关变量声明许多不同的数据类型,在这种情况下,结构可以有效地完成这项工作,因为它可以用来完成与本部分这样的工作。该结构的成员可以是任何数据类型,这与枚举不同,因为枚举的成员只能是同一类型。它们与枚举相同,MQL5 中有预定义结构,例如 MqlTick,我们也可以创建自己的结构。

比方说,我们需要为 tradeInfo 创建一个结构,我们可以看到成员包括交易品种、价格、止损、止盈和交易时间,它们的数据类型不同,因为交易品种是字符串,而价格、止损、止盈是双精度型数值。因此,在这种情况下,我们需要使用结构来完成以下这样的任务:

我们将使用 struct 关键字来声明我们自己的结构。

   struct tradeInfo
     {
      string         symbol;
      double         price;
      double         stopLoss;
      double         takeProfit;
     };

然后,我们可以声明一个 tradeInfo 类型的新交易对象,并通过在对象名称后使用(.)进行访问,为该结构的成员赋值,如下所示

   tradeInfo trade;

   trade.symbol = "EURUSD";
   trade.price = 1.07550;
   trade.stopLoss = 1.07500;
   trade.takeProfit = 1.07700;

我们可以像下面这样将成员及其值打印出来,以查看我们的工作,如下所示

   Print("Symbol Trade Info: ", trade.symbol);
   Print("Price Trade Info: ", trade.price);
   Print("SL Trade Info: ", trade.stopLoss);
   Print("TP Trade Info: ", trade.takeProfit);

我们可以通过下面的截图找到打印信息的输出结果

结构

从前面的截图中我们可以看到,我们分配的值与我们需要的值是一样的,因此我们可以在相同的上下文中对它们进行调整,以实现我们的目标。


类型转换

在本节中,我们将了解类型转换的概念及其含义。当我们需要将一个变量的值从一种数据类型转换为另一种数据类型时,我们可以将这一过程称为类型转换,但这一过程可能会产生意想不到的结果。

怎么会这样?当我们从一个变量向另一个变量转换或复制带有数字的值时,在类型间进行这一过程时,我们可能会面临数据丢失的问题。

以下是未丢失数据或丢失部分数据的示例:

我们将一个整数值复制到一个长整数值,这种情况不会产生这种数据丢失,因为转换是从较小的变量转换到较大的变量,当我们将一个整数复制到一个 double 时,也是在复制整数,小数点后的值为零,所以不会丢失数据。如果我们将一个 double 值复制到一个整数变量中(情况正好相反),小数点后的值将丢失或被截断。

让我们通过代码举例说明,以便更容易理解。首先,我们将看到没有任何问题的情况:

   int price = 3 - 1;
   double newPrice = price + 1;
   
   Print ("The price is ", newPrice);

我们可以看到,变量(price)的类型是 int,然后我们将其转换为 double 变量并打印出来。假定不会出现问题或丢失数据,因为我们是从较小的类型转换到较大的类型。打印信息的结果如下

typeCasting1

但当我们反其道而行之时,就会看到与下面相同的不同之处

   double price = 3.50 - 1;
   int newPrice = price + 1;
   
   Print ("The price is ", newPrice);

我们可以看到,价格变量的类型是 double,值是 3.50。因此,我们将丢失小数点后的数据,也就是 (.50) 的值,结果将是 (3),这与以下打印信息相同

typeCasting2

这里有一点非常重要,如果我们将一个较大类型的值转换成一个较小类型的变量,编译器会发出警告,这样我们就可以根据我们的目标或丢失数据的敏感性来决定是否忽略这条信息。下面是这一警告的示例:

typeCasting2

我们也可以选择忽略该警告(如果它没有危害),或者通过在 double 变量前添加 (int) 来舍入,以显示编译器发出的警告。


局部变量

在本部分中,我们将了解一个新概念,即局部变量。假设我们的软件中有全局作用域和局部作用域。全局作用域是指我们可以访问软件中的任何地方,我们将在下一个主题中从变量的角度来了解全局的概念,但局部变量只能在声明的同一作用域中访问。

为了进一步说明,如果我们有一个函数,而在该函数内部,我们有仅在函数级别声明的变量,这些变量就被视为局部变量。因此,在函数运行时,它们可以被访问,而在退出函数后,它们将无法被访问。

让我们举个例子来更清楚地说明这一点:

void myFunc()
{
   int var = 5;
   Print(var);
}

正如我们在前面的代码中所看到的,我们有一个名为 myFunc 的函数,这个函数可以在任何地方被调用。当我们调用这个函数时,我们可以找到一个名为 var 的局部变量,这个变量只能在函数内部运行,但从函数退出后就无法访问了。

因此,当我们调用这个函数时,可以发现输出结果是 5,这与下面的结果相同

localVar

如果我们试图从函数外部访问局部变量 var,编译器将产生与下面这样的错误(未声明标识符):

localVarError

如果函数内部有嵌套层或作用域,也会出现同样的情况,每个声明的变量只能在其作用域内访问。

我们也来举个这种情况的例子:

void myFunc()
  {
   int var = 5;
   if(var == 5)
     {
      int newVar = 6 ;
      Print(newVar);
     }

  }

正如我们在上一个示例中看到的,我们嵌套了 if 来比较 var 值和 5,如果为真,我们将声明 newVar,值为 6 并打印出来。由于条件为真,因此输出结果为 6,具体如下

localVar2

这个 newVar 是 if 操作符作用域内的一个新的局部变量,不能像其他局部变量一样在它的作用域之外被访问。重要的是要知道,任何与局部变量同名的变量声明都将覆盖前一个变量声明,并在更高的作用域中起作用。

因此,概念非常简单,任何局部变量都可以在函数的级别或作用域内访问。但是,如果我们需要访问软件中任何地方的变量,该怎么办呢?在这里,全局变量发挥了作用。


全局变量

在上一个关于局部变量的主题中,我们提到了全局变量,全局变量是在全局范围内声明的变量,这意味着我们可以在软件的任何地方访问它们,这与局部变量不同。因此,当我们需要声明全局变量时,我们会注意在软件的全局或全局范围内,在任何函数之外声明它们,这样我们就可以在软件的任何地方使用或调用它们。当我们需要声明一个会被多个函数中多次使用的变量时,就可以使用这类变量。

全局变量定义在软件的开头或顶部,位于输入变量之后。与局部变量不同,如果我们试图在块内声明全局变量,它将产生“变量声明隐藏全局声明”的编译错误,如下所示:

globalVar

我们可以在软件的任何地方更新全局变量,这与下面的示例相同,它展示了我们如何声明和更新全局变量:

int stopLoss = 100;

void OnStart()
  {
   Print("1st Value: ", stopLoss);
   addToSL();
  }

void addToSL()
  {
   stopLoss = stopLoss + 50;
   Print("Updated Value: ", stopLoss);
  }

从前面的代码块可以看出,我们在软件顶部全局声明了 stopLoss 变量,打印了赋值,调用 addToSL() 函数在第一个赋值的基础上增加了 50,然后打印了更新后的值。编译并执行该软件后,我们可以发现打印的信息如下:

globalVar2

我们可以看到,变量的第一个初始值是 100,在 addToSL() 函数中添加 50 后,变量的更新值变成了 150。

静态变量

在本部分中,我们将了解另一种变量,即静态变量。静态变量是局部变量,但即使软件已退出其作用域,它们也会在内存中保留其值,并且可以在函数块或局部变量中声明。

我们可以通过在变量名之前使用关键字 static 并为其赋值来实现这一点,我们可以看到以下示例以获得更多说明

void staticFunc()
{
   static int statVar = 5;
   statVar ++;
   Print(statVar);
}

然后,我们将调用 staticFunc() 函数

staticFunc();

在这里,变量将在内存中保留其值 (5),每次调用函数时,输出将为 6,即 (5+1)。以下是输出结果的截图:

静态变量

我们可以在专家选项卡中看到数值为 6 的打印信息。


预定义变量

在编程领域,我们可能会发现自己需要反复编写许多行代码来做一些常用的事情。因此,在很多编程语言中,变量甚至函数都是预定义的,这意味着它们已经被编码,我们可以轻松地使用它们,而无需再次重写所有代码。在这里,预定义变量的作用就体现出来了,我们需要做的就是记住或知道做某事的关键字,然后使用它。

MQL5 有许多预定义变量,我们可以用下面示例中的方法访问它们的值:

  • _Symbol:指图表上的当前交易品种。
  • _Point:指当前交易品种的点值,当前交易品种的五位数为 0.00001,三位数为 0.001。
  • _Period:指交易品种的当前周期或时间框架。
  • _Digits:指当前交易品种小数点后的位数,如果是五位数交易品种,表示小数点后的位数是 5,三位数交易品种表示小数点后有三位数。
  • _LastError:指最后一次错误的值。
  • _RandomSeed:指伪随机整数生成器的当前状态。
  • _AppliedTo:可用于查找用于计算指标的数据类型。

这些预定义变量和其他预定义变量可在预定义变量部分的文档中找到。


结论

本文探讨了 MQL5 的复杂性,重点是数据类型、变量等基本编程概念,以及开发高级软件的其他关键要素。掌握 MQL5 需要了解基本和复杂的方面,而练习是精通的基础。本指南旨在帮助您在 MQL5 中学习交易平台开发。有关使用移动平均线、RSI 等流行技术指标编程或创建交易系统的更多见解,请查看我的出版物,了解详细的文章和策略开发技巧。


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

克服集成ONNX(Open Neural Network Exchange )的挑战 克服集成ONNX(Open Neural Network Exchange )的挑战
ONNX是集成不同平台间复杂AI代码的强大工具,尽管它非常出色,但要想充分发挥其作用,就必须解决一些伴随而来的挑战。在本文中,我们将讨论您可能会遇到的一些常见问题,以及如何处理这些问题。
头脑风暴优化算法(第二部分): 多模态 头脑风暴优化算法(第二部分): 多模态
在文章的第二部分,我们将继续讨论BSO算法的实际应用,对测试函数进行测试,并将BSO的效率与其他优化方法进行比较。
种群优化算法:社群进化(ESG) 种群优化算法:社群进化(ESG)
我们将研究构造多种群算法的原理。作为该算法类别的一个示例,我们将查看新的自定义算法 — 社群进化(ESG)。我们将分析该算法的基本概念、种群互动机制和优势,并检查其在优化问题中的表现。
构建一个用于实现带约束条件的自定义最大值的通用优化公式(GOF) 构建一个用于实现带约束条件的自定义最大值的通用优化公式(GOF)
在这篇文章中,我们将介绍一种在MetaTrader 5终端的设置选项卡中选择“自定义最大值”时,实现具有多个目标和约束的优化问题的方法。举例来说,优化问题可以是:最大化利润因子、净利润和恢复因子,同时满足以下条件:回撤小于10%,连续亏损次数少于5次,每周交易次数多于5次。