English Русский Español Deutsch 日本語 Português
preview
市场模拟(第 19 部分):SQL 入门(二)

市场模拟(第 19 部分):SQL 入门(二)

MetaTrader 5测试者 |
19 0
Daniel Jose
Daniel Jose

概述

大家好,欢迎阅读本系列关于如何构建复制/建模系统的又一篇文章。

在上一篇文章“市场模拟(第18部分):SQL 入门(一)”中,我们开始探索了可以在 SQL 中使用的首批命令。目标是创建一个初始数据库,用于获取信息和执行未来的查询。我们展示了在 MetaEditor 中,你可以使用与任何 SQL 程序或脚本中相同的功能。这意味着我们也可以使用相同的代码,只不过是把它放到用 MQL5 创建的可执行文件内部。因此,它可以直接在 MetaTrader 5 中运行。

然而,SQL 中有一条命令,如果我们试图通过 MetaEditor 执行它,就不会生效。当我们尝试运行它时,它会返回错误。但我想明确一点:所有 SQL 命令均可用于基于 MQL5 的解决方案中。这是因为 MetaTrader 5 使用的是 SQLite。但是,并非所有这些命令都可以直接从 MetaEditor 执行。为了避免误解,让我们来看看我们正在讨论的是哪个命令。


MetaEditor 永远不会执行的 SQL 命令

现在是时候进行实验了。我们才刚刚开始学习,所以带来的连带影响会很小。然而,我想提醒你,在未事先了解其作用的情况下,切勿在重要数据库上执行任何 SQL 命令。如果你打算尝试新事物,请尽可能安全地进行,以免日后惹上麻烦。正如俗语所说,信仰可以移山,但它无法恢复你的数据。

现在,让我们来看看这个“神奇”的命令。在下面的动画中,你可以看到它是关于什么的。

请注意,在 SQL 中使用 DROP 命令时,我们可以永久删除某些内容。如果我们不慎使用此命令,可能会遇到严重问题。请注意,SQL 不会要求你确认 — 它会直接删除我们指定的任何内容。

然而,在使用 MetaEditor 时,数据库文件本身不会被删除。但是,如果我们发送在动画中引发错误的相同命令来执行 SQL,那么我们的数据库文件确实会被删除。

起初,我们可能会误以为 MetaEditor 拒绝执行 DROP 命令。但正如你在动画中所见,事实并非如此,因为之前创建的表是可以删除的。

你可能还会认为,如果我们尝试在 MetaEditor 中使用 CREATE DATABASE 命令,它也不会像 USE 命令那样起作用 — 事实的确如此。然而,这两个命令的实现方式使得我们无需手动输入。

也就是说,MetaEditor 不会从磁盘中删除数据库文件。因此,关键区别在于:出于安全原因,MetaEditor 不允许执行 DROP DATABASE 命令,该命令在 SQL 中意味着删除数据库文件本身。

因此,我们刚刚已经证明(你可以验证这一点)存在一条 MetaEditor 无法执行的 SQL 命令。除了这条命令之外,其他所有命令都能正常运行。这有助于解释 DROP 命令,它只不过是一个用于永久删除某些内容的命令 — 无论是列、表,甚至是整个数据库。使用此命令时务必格外小心,因为它在执行前不会征求确认。

确实有一些方法可以防止未经授权的意外删除,但这需要对 SQL 的使用方法进行更深入的讲解,而这并非本文的目的。我们的目标是为您打下必要的基础,以便您理解我们接下来要做什么 — 即在 MQL5 创建的可执行文件中使用 SQL。


将数据插入数据库

接下来我们要介绍的命令负责将信息插入数据库。我不建议手动编辑数据库文件。正确的做法始终是使用 SQL 命令来实现这一目的。无论数据库是小还是只包含几列和几条记录,你都不应该手动编辑它,而应该始终使用 SQL 命令。

用于将数据插入数据库的命令相当简单,但你仍需谨慎。事实上,在使用它时你需要多加注意,因为它基于“键-值”原理工作。任何熟悉 Python 编程的人都会很快理解这个概念。

这是因为将数据插入数据库的 SQL 命令与在 Python 中创建字典非常相似。换句话说,你使用一个键,并为其分配一个值。然而,在 SQL 中,情况略有不同。然而,其核心思想仍然不变。让我们来看看它到底是如何工作的。

现在,我们不会在 MetaEditor 中显示相同的命令。我认为,我们需要执行的命令是相同的,这一点已经很明确了,所以没有必要重复同样的事情。为了简化对该命令的解释,我们将使用一个非常简单且易于理解的小型 SQL 代码示例。

请查看以下代码:

01. CREATE DATABASE IF NOT EXISTS MT5_Tutor_DB;
02. USE MT5_Tutor_DB;
03. 
04. CREATE TABLE IF NOT EXISTS tb_Quotes
05. (
06.     of_day DATE,
07.     symbol CHAR(6),
08.     price DECIMAL(5, 2)
09. );
10. 
11. INSERT INTO tb_Quotes (of_day, symbol , price)
12.                VALUES ('2023-07-06', 'BOVA11' , 105.61);
13. 
14. SELECT * FROM tb_Quotes;

SQL 脚本

目前,不必担心第 14 行。在这个阶段,它只是用来让我们更容易地查看结果。这段代码既可以在 MetaEditor 中使用,也可以直接在SQL服务器上使用。然而,如果你打算在 MetaEditor 中使用它,请记住,如前文所述,第 01 行和第 02 行的执行方式必须不同。如有疑问,请参阅这些文章,了解如何在 MetaEditor 中继续操作并运行相同的代码。这里对我们来说重要的是第 4 行和第 11 行,分别用于创建表和插入数据。

虽然这段代码可以运行,但在创建数据库时不应该使用它。原因在于,它允许数据被无限期地复制。如果您对数据库有所了解,就会明白应避免出现重复值。数据库的设计就是为了防止这种情况的发生。然而,即便有这样的目标,如果 SQL 脚本编写得不够专业,也很可能出现重复值。上面的代码就体现了这种较为业余的写法。不过,由于我们的目标是解释如何向数据库中添加值,因此我们可以容忍重复项,甚至一些插入错误,比如字段值为 NULL。

要理解发生了什么,请注意,在第 04 行,我们仅在数据库中不存在该表时才创建它。该表将包含以下列。例如,在第 06 行,我们定义一个内容表示日期的列。

在第 07 行,我们定义了一个宽度为 6 个字符的列。这样我们就可以存储交易品种的名称了。这种命名规则遵循 B3(巴西证券交易所)使用的标准。

在第 08 行,我们定义了一个 DECIMAL(5,2) 字段,总共 5 位数字,其中 2 位是小数位。这样我们就可以存储 0 到 999.99 的价格,这对于在 B3 上交易的交易品种来说绰绰有余。请注意,我们没有定义任何键或约束。正是由于缺乏这些限制,才允许重复值的存在。

现在,我们来看看第 11 行发生了什么。请注意,将数据插入到表中的命令以 “INSERT INTO” 开头。紧接着,我们指定要将值插入到哪个表中 — 在我们的例子中,是刚刚创建的 tb_Quotes 表。然后我们声明将接收这些值的列的名称。

现在请注意:我们可以按任何顺序列出列,但必须使用与表中定义完全相同的列名。如果我们使用不同的名称,该命令将导致错误。在定义了列顺序之后,我们添加 “VALUES” 一词,并开始指定要插入的值。此时,值的顺序必须与前面列名声明的顺序完全一致。换言之,如果我们在交易品种列之前声明价格列,那么在值列表中,我们必须在交易品种之前提供价格。

这可能看起来有点令人困惑,但如果你使用下面的代码,插入值时你会得到与上面所示脚本相同的结果。

INSERT INTO tb_Quotes (of_day, price, symbol)
               VALUES ('2023-07-06', 105.61, 'BOVA11');

SQL 脚本

请注意,我更改了列的声明顺序。因此,我也必须调整这些值的顺序。这是您应该关注的主要方面。现在您可能会好奇:为什么日期要以这种特定格式来指定?原因是 MySQL 期望的格式是这样的 — 即先年,再月,最后日。有些数据库允许使用其他格式,然后在存储数据时将这些格式转换为此格式。例如 SQL Server 就允许使用日-月-年格式。总之,在数据库中,格式始终为年-月-日。

如果你运行上述脚本,结果将会相应地显示出来。

现在请注意:如果您再次执行第 11 行代码,数据库中将会出现相同信息的重复。结果将反映出这种重复:

您可能会认为这不是问题 — 您可以直接删除重复的行。是的,删除它很容易,但首先你需要明白,这个例子很简单。现在想象一个真实的数据库:它可能包含海量记录。数据库中存在重复条目会使数据库完全无法使用。这也使得修改或更新特定记录变得困难。这就是为什么创建数据库需要仔细研究和规划的原因。

尽管上述脚本在概念上是正确的(因为它能运行),但并不理想,因为它允许数据重复。此外,它不允许修改或更新记录。为了更好地理解这一点,让我们用另一种方式将数据插入数据库。我们不会一次性声明所有列和值,而是会逐步进行,这在许多场景中其实很常见。因此,插入与前一个脚本中相同值的代码将如下所示:

01. CREATE DATABASE IF NOT EXISTS MT5_Tutor_DB;
02. USE MT5_Tutor_DB;
03. 
04. CREATE TABLE IF NOT EXISTS tb_Quotes
05. (
06.     of_day DATE,
07.     symbol CHAR(6),
08.     price DECIMAL(5, 2)
09. );
10. 
11. INSERT INTO tb_Quotes (of_day) VALUES ('2023-07-06');
12. INSERT INTO tb_Quotes (symbol) VALUES ('BOVA11');
13. INSERT INTO tb_Quotes (price) VALUES (105.61);
14.                
15. SELECT * FROM tb_Quotes;

SQL 脚本

请注意,这里我们实际上是在使用“键-值”概念,这与 Python 中创建字典的方式类似。是的,我们可以用 SQL 来实现这一点,数据库会将值存储在指定的表中。然而,请注意执行此脚本的结果 — 就在下方可见。

“但是发生了什么?为什么我们得到了这个结果而不是之前的结果?”原因有二。首先,我们只是简单地将数据添加到数据库中。第二,数据库的结构允许存在重复值。如果我们尝试修改任何记录,但数据库是按照这些脚本中所示的方式创建的,我们将无法做到这一点。我们需要建立一种能够唯一定位特定记录的机制,以便对其进行操作并更改其值。这样,我们就可以使用“键-值”格式逐个插入数据了。但我们怎么能做到这一点呢?

第一步是将其中一个列转换为唯一键。这是起点。这样做可以防止数据库中出现重复记录。但一个重要的问题出现了:最佳的解决方法是什么?

有些人通常会为此专门创建一个单独的列,这也不失为一个好主意。然而,解决方案的类型取决于每个具体案例。就我们的情况而言,无需创建额外的列作为唯一键。只需选择一个合适的现有列即可完成此操作。那么,让我们来思考一下。

在 “price” 这一列中,数值有时可能不同,有时可能相同,因此将其设为唯一值可能并非明智之举。但是,包含交易品种名称的列将在每个新记录中重复出现。日期列可能是一个候选列。

但为什么是“可能”而不是“确定”呢?原因在于我们计划向数据库中插入新记录的频率。这为什么重要呢?因为如果我们要以非常短的时间间隔插入记录,我们可能需要专门为此创建一个单独的列。但如果每天只插入一次记录,那么日期就足够了。

因此,如果我们想要存储某个交易品种的每日价格,我们可以按如下方式修改前面所示的第一个脚本:

01. CREATE DATABASE IF NOT EXISTS MT5_Tutor_DB;
02. USE MT5_Tutor_DB;
03. 
04. CREATE TABLE IF NOT EXISTS tb_Quotes
05. (
06.     of_day DATE PRIMARY KEY,
07.     symbol CHAR(6),
08.     price DECIMAL(5, 2)
09. );
10. 
11. INSERT INTO tb_Quotes (of_day, symbol , price)
12.                VALUES ('2023-07-06', 'BOVA11' , 105.61);
13. 
14. SELECT * FROM tb_Quotes;

SQL 脚本

请注意,第一个脚本中所有更改的内容都恰好出现在第 06 行,也就是我们添加 PRIMARY KEY 关键字的地方。此时,我们表明 of_day 列的值将成为该表的主键。我们可以将多个列作为主键。但是,主键中不能有重复值。如果我们尝试这样做,SQL会将其视为错误,并且该命令将不会被执行。

因此,如果我们再次尝试使用数据库中已存在的相同日期执行 INSERT INTO 命令,我们将无法成功。我们需要指定一个不同的日期,以便 SQL 能够接受我们尝试插入的记录。这一非常简单的措施确保了数据库的一致性,并防止了重复记录的产生。如前所述,一项简单的措施解决了诸多问题。

执行上述脚本的结果如下图所示:

请注意,它与其他图片有所不同。但是,查询结果却是一样的。当然,您会认为现在我们可以以“键-值”格式,或者更准确地说,以“列-值”格式插入数据了。是的,我们可以做到。所以您的第一个想法是创建一个脚本,如下所示:

01. CREATE DATABASE IF NOT EXISTS MT5_Tutor_DB;
02. USE MT5_Tutor_DB;
03. 
04. CREATE TABLE IF NOT EXISTS tb_Quotes
05. (
06.     of_day DATE PRIMARY KEY,
07.     symbol CHAR(6),
08.     price DECIMAL(5, 2)
09. );
10. 
11. INSERT INTO tb_Quotes (of_day) VALUES ('2023-07-07');
12. INSERT INTO tb_Quotes (symbol) VALUES ('BOVA11');
13. INSERT INTO tb_Quotes (price) VALUES (105.61);
14. 
15. SELECT * FROM tb_Quotes;

SQL 脚本

请记住,此脚本在获取 of_day 的值后立即执行。因此,为了防止 SQL 阻止记录的创建,我们更改了日期。实际上,我们正在运用逻辑,并证明我们理解主键的工作原理。然而,在执行此脚本时,我们遇到了一个错误。况且,鉴于此事关乎专业层面的兴趣,我们尝试探查数据库中究竟发生了什么,最终获得了这样一幅图片:

现在您可能会问:既然记录已经创建,为什么还会出错呢?原因是在第 12 行,我们正试图插入信息。这样一来,SQL 将尝试创建一个新记录。但请等一等。如果我没有指定要创建新的主键,为什么 SQL 要创建新记录呢?我认为只要我们使用主键,就会使用相同的记录。事实上,许多人一开始会犯这个错误,是因为他们不了解这个概念的精髓。

但让我们先澄清一下情况。当我们使用 INSERT INTO 命令时,我们正在数据库中创建一条新记录。但是,要修改此记录(这正是本例中需要的),我们必须使用不同的命令。现在,让我们把这些事情分开,在另一个主题中再仔细研究。


修改和更新记录

为了让 SQL 知道要修改或更新哪条记录,我们需要一个唯一键,即主键。由于我们将 of_day 值定义为主键,因此我们首先需要做的就是尝试将其添加到数据库中。如果记录已存在,SQL 将不允许创建新记录。

在数据库中定义并注册主键后,我们可以修改或更新其他列的值。注意:请勿尝试更改主键的值,以免出现问题。我们需要先创建键,然后再更新记录。以下代码使用键-值概念(或更准确地说,列-值)执行更新操作以创建新记录:

01. CREATE DATABASE IF NOT EXISTS MT5_Tutor_DB;
02. USE MT5_Tutor_DB;
03. 
04. CREATE TABLE IF NOT EXISTS tb_Quotes
05. (
06.     of_day DATE PRIMARY KEY,
07.     symbol CHAR(6),
08.     price DECIMAL(5, 2)
09. );
10. 
11. INSERT INTO tb_Quotes (of_day) VALUES ('2023-07-07');
12. UPDATE tb_Quotes SET symbol = 'BOVA11' WHERE of_day = '2023-07-07';
13. UPDATE tb_Quotes SET price = 105.61 WHERE of_day = '2023-07-07';
14. 
15. SELECT * FROM tb_Quotes;

SQL 脚本

执行此脚本后的结果如下所示:

请注意,在执行上述脚本之前,我们已考虑到记录已存在的事实。所以,我们就这样做。然后,在第 11 行,我们创建一个键来表示我们将向数据库添加一条新记录。创建此键后,我们使用 UPDATE 命令,如下所示:

  1. 在命令名称之后,我们立即指定表名。
  2. 在表名之后,我们使用 SET 命令;它表示我们将要修改或更新某个列。
  3. 接下来,我们有了列名及其对应的值,我们将把这些信息输入到表格中。
  4. 为了让 SQL 找到需要更新的记录,我们使用 WHERE 命令,指定将使用哪个键。

这里还有几个问题,但我们不会把事情复杂化,让我们尽量把事情简化。目标是为您提供在 MetaTrader 5 中使用 SQL 的基础知识,而不是进行完整的 SQL 课程。然而,我建议我们对今天的材料进行更深入的探讨,因为在这方面我们可以做更多的事情来加快 SQL 的速度,并避免因低效查询而使其过载。这里重点是教学讲解,而不是执行效率。

好了,现在我们知道如何插入、修改和更新记录了。我们只需要了解如何删除记录即可。但是,我们再次强调,要将这些材料分开,以避免混淆。


删除记录

从数据库中删除记录是一件非常简单的事情。有几种方法可以批量删除一整组记录。但是,我再说一遍,我们将探讨最简单的方法,这样你至少能从数据库中删除一条记录。

为此,我们假设如下情况:您添加了 2023 年 7 月 7 日的记录,但随后意识到当天没有交易。因此,您需要从数据库中删除这条记录,因为无效的记录可能会破坏整个数据库。实现此目的的一种方法是使用以下脚本:

1. USE MT5_Tutor_DB;
2. 
3. DELETE FROM tb_Quotes WHERE of_day = '2023-07-07';
4. 
5. SELECT * FROM tb_Quotes;

SQL 脚本

请注意,我们首先在第 01 行选择数据库。在第 03 行,我们执行一条命令,从数据库中删除一条记录。这是怎么做到的?以最简单的方式。也就是说,我们首先要更准确地指定命令名称:DELETE FROM; 然后是记录所在的表名,在本例中为 tb_Quotes。接下来,我们指出哪些内容将被删除。为此,我们使用 WHERE 命令,后面跟上告诉 SQL 要查找哪种匹配项的信息。在我们的场景中,我们使用要从数据库中删除的日期的主键。

为了完成脚本,我们在第 05 行要求 SQL 显示该表。你注意到删除一条记录有多简单了吗?但所示方法只是众多可能方法中的一种。


最后的想法

本文力求将内容尽可能简化。我知道做同样的事情有更好的方法。如果您已经具备 SQL 程序方面的经验和知识,您就会非常清楚,这里介绍的所有内容才是 SQL 的真正基础。对于那些不了解的人来说,“大米和豆子”这一说法是一种地方性表达,用来表示我们只需要最基本的东西,但同时也要有一些最起码的多样性,这样我们才能感到满足。

但回到我们的话题,我想让你稍微做些准备,以便你能利用这些文章在 SQL 中做些事情。我不希望你浪费时间编写无意义的代码,创建不同的子程序来实现 SQL 中已经包含的功能。

我这样做是为了激励你始终追求充分且更广泛的知识,这样你就不会固守自己的舒适区,浪费时间去做那些如果你使用合适的工具本可以更简单、高效、安全地完成的事情。

经常,当许多人看到程序员或程序员团队使用某种工具时,他们会因误解而皱起眉头,并表示这与他们的个人兴趣不符。但我想告诉你们,亲爱的正在努力成为专业程序员的读者们,你们不要把面子或个人执念看得太重。首先开始使用现有工具。你会变得更加高效,对自己和自己的工作更有信心,最重要的是,你会更有价值。毕竟,当其他人绞尽脑汁寻找解决方案时,你凭借更广泛的知识,会立即找到合适的工具,并轻松地创造出客户所需的解决方案。本文的主旨如下: 

技术世界发展得非常快。等我们理解了某件事,它已经过时了。

想想看吧,下篇文章再见。关于 SQL,还有一些要点需要解释,之后我们才能开始使用 SQL 和 MQL5 的组合为我们的回放/模拟系统创建一些东西。那么,回头见。

文件 描述
Experts\Expert Advisor.mq5
演示 Chart Trade 与 EA 之间的交互(交互需要 Mouse Study)。
Indicators\Chart Trade.mq5 创建一个窗口,用于配置要发送的订单(需要 Mouse Study 才能进行交互)
Indicators\Market Replay.mq5 创建用于与回放/模拟服务交互的控件(交互需要 Mouse Study)。
Indicators\Mouse Study.mq5 提供图形控件与用户之间的交互(回放系统和真实市场操作都需要)。
Services\Market Replay.mq5 创建并维护市场回放/模拟服务(整个系统的主文件)。
VS C++ Code Server.cpp 创建并维护一个用 C++ 开发的套接字服务器(迷你聊天版本)。
Python Code Server.py 实现 MetaTrader 5 和 Excel 之间的 Python 套接字通信。
Indicators\Mini Chat.mq5 通过指标实现迷你聊天功能(需要服务器支持)
Experts\Mini Chat.mq5 使用 EA(需服务器支持)启用迷你聊天功能。
Scripts\SQLite.mq5 演示如何在 MQL5 中使用 SQL 脚本。
Files\Script 01.sql 演示如何创建带有外键的简单表。
Files\Script 02.sql 演示如何向数据表中添加值。

本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12927

附加的文件 |
Anexo.zip (571.71 KB)
机器学习中的高斯过程:MQL5中的回归模型 机器学习中的高斯过程:MQL5中的回归模型
本文将介绍高斯过程(GP)这一概率机器学习模型的基础,并基于合成数据演示其在回归问题中的应用。
不使用期权的期权交易(第一部分):基础理论与基于标的资产的模拟实现 不使用期权的期权交易(第一部分):基础理论与基于标的资产的模拟实现
本文介绍了一种通过标的资产实现期权模拟的方案,并使用MQL5编程语言完成代码实现。以莫斯科交易所(MOEX)FORTS期货市场以及Bybit加密货币交易所为例,对比了该方案与真实场内期权的优缺点。
基于生物地理学的优化算法(BBO) 基于生物地理学的优化算法(BBO)
基于生物地理学的优化算法(BBO)是一种精巧的全局优化算法,灵感源自群岛中物种在各个岛屿间迁徙的自然规律。该算法的核心思路简洁且高效:优质解会主动共享自身特征,劣质解则主动吸纳新特征,信息天然从最优解向较差解传递。其独有的自适应变异算子能够很好地平衡算法的全局探索与局部利用能力。在各类优化任务中,BBO 算法均表现出较高的运算效率。
从基础到中级:结构(七) 从基础到中级:结构(七)
在今天的文章中,我们将展示如何着手解决与构建不同元素以及创造更简单、更具吸引力的解决方案相关的问题。尽管内容以学习为导向,因此并不构成生产代码,但深入理解这里将涵盖的概念和知识至关重要。这样,今后我们就能理解我们将展示的代码。