介绍
MQL与数据库交互的问题并不新鲜,但它们仍然是相关的。使用数据库可以大大增强元交易者的可塑性:存储和分析价格历史,将交易从一个交易平台复制到另一个交易平台,提供实时报价/交易,定期在服务器端进行深入的分析和计算,使用Web技术监控和远程控制账户。
总之,有许多应用程序试图从MQL和MySQL的组合中获益,一些解决方案已经出现在代码库中。
例如,“mysql packaging-link library for metatrader 4”就是这样一个项目,很多程序员开始自己开发,并可以在未来扩展。我认为这个解决方案的缺点之一是,分配了特殊的数组来从数据库中读取数据。
另一个项目,mysql log 1-ea for metatrader 4,更专业。它不使用打包访问标准链接库libmysql.dll。因此,它不能在metatrader4编译器600+上工作,因为char字符类型已被wchar_t替换,tmysql结构指针已被in t类型替换,导致项目内存泄漏(无法控制/释放内存分配)。
另一个有趣的项目是“eax-mysql-mysql-link-library-link-library for metatrader 5”。这是一个很好的实现。然而,作者列举了一些在使用中必须注意的缺点。
任何需要在其项目中使用数据库的人都有两个选择:要么开发自己的解决方案并了解其中的每一部分,要么使用/调整任何第三方解决方案来了解如何使用它们并检测它们是否会阻碍其项目。
当我开发一个相当复杂的自动化事务时,我必须面对必要性和两种选择。在搜索现有项目并研究了许多解决方案之后,我意识到我发现的所有实施解决方案都没有帮助我将自动化交易提升到“专业水平”。
此外,还有一些荒谬的方案,例如使用标准libmysql.dll执行dml/ddl操作(插入/更新/删除数据,在数据库中创建/丢弃对象),以及使用数据检索(select)的实现作为HTTP请求(使用inet.dll)与mysql服务器端Web服务器上的PHP脚本通信。SQL查询是用PHP脚本编写的。
换句话说,要运行项目,必须确保以下所有组件都已准备好、配置好并正在运行:mysql server、apache/iis web server、服务器端的php/asp脚本。大量的技术组合。当然,在某些情况下,这是可以接受的,但是当唯一的任务是从数据库中查询数据时,这些都是毫无意义的。此外,要支持这样一个繁琐的解决方案需要时间。
大多数方案都没有插入数据、创建对象等问题。问题在于数据查询,因为数据将返回到调用环境。
我认为这样使用数组是不切实际和不方便的。简单的原因是数据库查询可以在主程序的开发/调试/支持过程中更改,并且必须正确控制分配给组的内存。所以,这些可以而且必须避免。
下面讨论的mql<;->;mysql的接口基于Oracle PL/SQL、MS SQL T-SQL、ADODB和其他使用光标的产品中使用的典型模式。这个接口的开发目标是使它易于编程和维护,以及最少的组件。它被实现为一个dll包装器,链接标准链接库libmysql。以及作为.mqh文件的接口函数集。
1。mql<;->;MySQL接口
MetaTrader终端之间的交互(通过MQL程序)可以通过以下组件实现:
1。接口库mqlmysql.mqh。使用include语句将其添加到项目项目中,并且可以根据您的偏好进行修改。
它包含从mqlmysql导入函数的说明。动态库和调用它们并处理错误。
2。mqlmysql.dll动态库。这是用于访问标准库libmysql功能的包装器。动态链接库。
此外,mqlmysql.dll链接库处理操作结果,共享连接和光标以访问数据库。这意味着您可以同时创建和使用多个连接(来自一个或多个MQL程序),保留少量打开的游标并查询一个或多个数据库。互斥用于分离对共享资源的访问。
三。标准动态链接库libmysql.dll是本地访问驱动程序。您可以从任何mysql数据库发布位置c:/windows/sytem32或<;terminal>;/mql5/libraries(对于metatrader 4 at<;terminal>;/mql4/libraries)复制它。
实际上,它负责向数据库发送查询并接收搜索结果。
让我们详细介绍一些关键点,例如打开/关闭连接、执行DML/DDL查询和数据检索。
1.1。打开和关闭连接
mysqlconnect函数实现了打开与mysql数据库的连接:
类型 | 名字 | 参数 | 描述 |
int | MySQL连接 | 此函数已实现与数据库的连接,并返回连接标识符。数据库查询需要此ID。如果连接失败,返回值“-1”。有关错误详细信息,请检查变量mysqlErrorNumber和mysqlErrorDescription。通常,当MQL程序处理OnInit()事件时调用此函数。 | |
弦柱 | 这是MySQL服务器的域名或IP地址。 | ||
字符串用户 | 数据库用户名(例如,root) | ||
字符串ppassword | 数据库用户的密码 | ||
字符串pdatabase | 数据库名称 | ||
int端口 | 用于数据访问的TCP/IP端口(通常为3306) | ||
字符串pSub | Unix套接字(用于Unix基准系统) | ||
In Pclipse标志 | 特殊标志组合(通常为0) |
mysqldonnect接口函数已关闭连接:
类型 | 名字 | 参数 | 描述 |
无效 | MySQL断开连接 | 此函数关闭与MySQL数据库的连接。通常,当mql程序处理ondeinit()事件时调用此函数。 | |
int连接 | 连接标识符 |
需要注意的是,MySQL数据库可以在硬件故障、网络拥塞或超时(如果长时间没有向数据库发送查询)的情况下自行关闭连接。
开发人员通常使用ontick()事件将数据写入数据库。然而,当周末来临时,市场关闭,连接仍然“挂起”。在这种情况下,MySQL将关闭超时(默认为8小时)。
周一市场开市时,系统发现一个连接错误。因此,强烈建议定期检查连接和/或以略低于MySQL服务器设置中指定的超时值重新连接到服务器。
1.2。执行DML/DDL查询
DML操作用于数据操作(数据操作语言)。数据操作包括以下语句集:insert、update和delete。
DDL操作用于数据定义(数据定义语言)。其中包括创建(创建)数据库对象(表、视图、存储过程、触发器等)和更改(更改)和删除(删除)。
这些不是所有的dll/ddl语句。此外,DCL(数据控制语言)用于数据访问的分离,但我们不会深入探讨SQL的特性。所有这些命令都可以使用mysqlexecute接口函数执行:
类型 | 名字 | 参数 | 描述 |
布尔 | MySQL执行 | 此函数用于在成功建立数据库后(使用mysqlconnect函数)执行非select SQL语句。如果命令执行成功,函数将返回true,否则返回-false。有关错误的详细信息,请使用mysql错误号和mysql错误描述。 | |
int连接 | 连接标识符 | ||
字符串查询 | SQL查询 |
作为SQL查询,还可以使用use命令选择数据库。我想提醒您使用复合语句的查询。它是一组SQL命令,分隔字符“;”。
要启用复合语句模式,在打开数据库连接时应携带客户机多语句标志:
... int ClientFlag = CLIENT_MULTI_STATEMENTS; // Setting the multi-statements flag int DB; DB = MySqlConnect(Host, User, Password, Database, Port, Socket, ClientFlag); // Connection to the database if (DB == -1) { // Handling the connection error } ... // Preparing a SQL query to insert data (3 rows in one query) string SQL; SQL = "INSERT INTO EURUSD(Ask,Bid) VALUES (1.3601,1.3632);"; SQL = SQL + "INSERT INTO EURUSD(Ask,Bid) VALUES (1.3621,1.3643);"; SQL = SQL + "INSERT INTO EURUSD(Ask,Bid) VALUES (1.3605,1.3629);"; ... if (!MySqlExecute(DB,SQL)) { // Showing an error message } ...
在这个片段中,只要调用一次数据库,就可以将三个条目插入到eurusdus表中。存储在SQL变量中的每个查询都由“;”分隔。
该方法可以频繁地进行插入/更新/删除操作,将必要的命令集组合成一个“包”,以减少网络流量,提高数据库性能。
在MySQL中,插入语法非常适合处理异常。
例如,如果任务是移动价格历史记录,则应创建一个对应于其主键为日期类型的货币对的表,因为列的日期和时间是唯一的。此外,还应该检查数据库中是否存在任何特定的列数据(以提高数据迁移的稳定性)。MySQL不需要进行此检查,因为insert语句支持重复键。
简单地说,如果尝试插入数据,并且表中的记录已经具有相同的日期和时间,则可以忽略insert语句,或者可以用update替换此行(请参见https://dev.mysql.com/doc/refman/5.0/en/insert on duplicate.html)。
1.3。数据检索
SQL Select语句用于从数据库中检索数据。以下操作序列用于检索数据并返回检索的结果:
- 准备select语句。
- 打开光标。
- 获取查询返回的行数。
- 在循环中检索结果的每一行。
- 在循环中将数据分配给MQL变量。
- 关闭光标。
当然,这是一个共同的计划,所以不是每种情况都需要所有的操作。例如,如果您想确认表中有一行数据(根据任何条件),这就足以准备查询、打开光标、获取行数并关闭光标。实际上,强制部分是准备select语句,打开和关闭光标。
什么是光标?它是对逻辑内存区域的引用,实际上是结果值的集合。发送select查询时,数据库为结果分配内存,并创建一个行指针,用于在数据行之间移动。因此,可以按顺序访问由查询定义的队列的每一行。(select语句的order by子句)。
以下接口函数用于数据检索:
打开光标:
类型 | 名字 | 参数 | 描述 |
int | MySQL语言 | 此函数打开用于选择查询的光标,如果成功,则返回光标标识符。否则,函数返回“-1”。为了找出错误的原因,使用变量mysqlErrorNumber和mysqlErrorDescription。 | |
int连接 | 数据库连接标识符 | ||
字符串查询 | SQL查询(select语句) |
获取查询返回的行数:
类型 | 名字 | 参数 | 描述 |
int | MySQL光标行 | 此函数返回查询的搜索行数。 | |
int pCurRID | mysqlcursoropen返回的光标标识符 |
获取查询数据行:
类型 | 名字 | 参数 | 描述 |
布尔 | mysql cursorfetchrow | 从查询返回的数据集中获取一行数据。成功执行后,可以将数据分配给MQL变量。如果success函数返回true,否则返回false。 | |
int pCurRID | mysqlcursoropen返回的光标标识符 |
检索查询行后,将其分配给MQL变量:
类型 | 名字 | 参数 | 描述 |
int | mysqlGetFieldAsInt(mysqlGetFieldAsInt) | 函数以int数据类型返回数据表字段的值。 | |
int pCurRID | mysqlcursoropen返回的光标标识符 | ||
int域 | select中的字段编号(起始编号为0) | ||
双重的 | mysqlGetFieldAsDouble | 函数以双数据类型返回数据表字段的值。 | |
int pCurRID | mysqlcursoropen返回的光标标识符 | ||
int域 | select中的字段编号(起始编号为0) | ||
日期时间 | mysqlGetFieldas日期时间 | 函数返回具有日期时间数据类型的数据表字段的值。 | |
int pCurRID | mysqlcursoropen返回的光标标识符 | ||
int域 | select中的字段编号(起始编号为0) | ||
弦 | mysqlGetFieldAsString | 函数返回具有字符串数据类型的数据表字段的值。 | |
int pCurRID | mysqlcursoropen返回的光标标识符 | ||
int域 | select中的字段编号(起始编号为0) |
mysql返回的所有数据都是本地表示的(不仅仅是字符串)。
因此,使用这些函数,您可以将所选数据转换为所需的类型。唯一的缺点是,在选择列表中,使用列号(起始编号为0)而不是其名称。但是,在开发应用程序时,准备select语句和获取结果几乎都在一个页面上,因此当指定数据采集逻辑时,可以看到select查询。
因此,您总是知道选择列表中的字段数(这也适用于访问ADODB数据)。好的,这部分以后可以修改。它对解决方案的功能开发只有轻微的影响。
关闭光标:
类型 | 名字 | 参数 | 描述 |
无效 | MySQL光标丢失 | 此函数关闭指定的光标并释放内存。 | |
int pCurRID | mysqlcursoropen返回的光标标识符 |
关闭光标是一个关键操作。不要忘记关闭光标。
想象一下,打开一个光标,却忘了关闭它。假设每次一个即时引用到达并处理ontick()事件时,数据都是通过一个光标检索的,并且每次都打开一个新的光标,并且内存分配给它(客户机和服务器都是相同的)。在某个时刻,服务器将拒绝服务,因为它达到了打开光标的限制并导致缓冲区溢出。
当然,这有点夸张,当直接使用libmysql时,结果可能会出现。动态链接库。但是,mqlmysql.dll会为游标分配内存,并在超过允许的限制时拒绝打开新的游标。
在执行实际任务时,只需保持2-3个打开的光标即可。每个光标可以处理笛卡尔大小的数据;两个或三个光标(嵌套,例如,一个参数依赖于另一个光标)可以同时用于覆盖两个或三个维度。对于大多数任务来说,这是完全正常的。此外,为了实现复杂的数据检索,您可以始终使用这些对象表示数据库(视图),在服务器端创建它们,并像数据表一样从MQL代码发送查询。
1.4。附加信息
以下功能可作为附加功能:
1.4.1从.ini文件读取数据
类型 | 名字 | 参数 | 描述 |
弦 | 雷迪尼 | 返回ini文件中给定段落的键值。 | |
字符串pfilename | ini文件名 | ||
弦截 | 段落名 | ||
字符串键 | 密钥名称 |
直接将数据库连接信息(服务器IP地址、端口、用户名、密码等)存储在MQL代码(或EA、索引、脚本的参数)中是不合理的,因为服务器可能会传输,其地址会动态变化,等等。在这种情况下,您需要修改MQL代码。因此,所有这些数据最好存储在标准中。并只在MQL程序中写入其名称。然后,使用readini函数读取连接参数并使用它们。
例如,ini文件包含以下信息:
[MYSQL] Server = 127.0.0.1 User = root Password = Adm1n1str@t0r Database = mysql Port = 3306
要获取服务器IP地址,请执行以下语句:
string vServer = ReadIni("C://MetaTrader5//MQL5//Experts//MyConnection.ini", "MYSQL", "Server");
ini文件放在c:/metatrader5/mql5/experts中,称为myconnection.ini。您可以访问mysql段落的服务器键值。在一个ini文件中,可以保存项目中使用的多个服务器设置。
1.4.2问题区域跟踪
接口库中提供了跟踪模式,该模式可用于调试MQL程序中任意位置的SQL查询。
在问题区域中,指定了以下内容:
SQLTrace = true;
或
SQLTrace = false;
如果您开始在MQL程序中启用跟踪,但没有禁用它,那么将记录所有数据库调用。记录保存在终端的控制台中(使用print命令)。
2。常规
本节提供了一些用于连接和使用开发的链接库的例程。参考它们并评估软件解决方案的可用性。
例程mysql-003.mq5如下所示:连接到数据库(连接参数保存在中)。ini文件)、创建数据表、插入数据(也用于复合语句)和断开与数据库的连接。
//+------------------------------------------------------------------+ //| MySQL-003.mq5 | //| Copyright 2014, Eugene Lugovoy | //| https://www.mql5.com | //| Inserting data with multi-statement (DEMO) | //+------------------------------------------------------------------+ #property copyright "Copyright 2014, Eugene Lugovoy." #property link "https://www.mql5.com" #property version "1.00" #property strict #include <MQLMySQL.mqh> string INI; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { string Host, User, Password, Database, Socket; // database credentials int Port,ClientFlag; int DB; // database identifier Print (MySqlVersion()); INI = TerminalInfoString(TERMINAL_PATH)+"//MQL5//Scripts//MyConnection.ini"; // reading database credentials from INI file Host = ReadIni(INI, "MYSQL", "Host"); User = ReadIni(INI, "MYSQL", "User"); Password = ReadIni(INI, "MYSQL", "Password"); Database = ReadIni(INI, "MYSQL", "Database"); Port = (int)StringToInteger(ReadIni(INI, "MYSQL", "Port")); Socket = ReadIni(INI, "MYSQL", "Socket"); ClientFlag = CLIENT_MULTI_STATEMENTS; //(int)StringToInteger(ReadIni(INI, "MYSQL", "ClientFlag")); Print ("Host: ",Host, ", User: ", User, ", Database: ",Database); // open database connection Print ("Connecting..."); DB = MySqlConnect(Host, User, Password, Database, Port, Socket, ClientFlag); if (DB == -1) { Print ("Connection failed!Error: "+MySqlErrorDescription); } else { Print ("Connected!DBID#",DB);} string Query; Query = "DROP TABLE IF EXISTS `test_table`"; MySqlExecute(DB, Query); Query = "CREATE TABLE `test_table` (id int, code varchar(50), start_date datetime)"; if (MySqlExecute(DB, Query)) { Print ("Table `test_table` created."); // Inserting data 1 row Query = "INSERT INTO `test_table` (id, code, start_date) VALUES ("+(string)AccountInfoInteger(ACCOUNT_LOGIN)+",/'ACCOUNT/',/'"+TimeToString(TimeLocal(), TIME_DATE|TIME_SECONDS)+"/')"; if (MySqlExecute(DB, Query)) { Print ("Succeeded: ", Query); } else { Print ("Error: ", MySqlErrorDescription); Print ("Query: ", Query); } // multi-insert Query = "INSERT INTO `test_table` (id, code, start_date) VALUES (1,/'EURUSD/',/'2014.01.01 00:00:01/');"; Query = Query + "INSERT INTO `test_table` (id, code, start_date) VALUES (2,/'EURJPY/',/'2014.01.02 00:02:00/');"; Query = Query + "INSERT INTO `test_table` (id, code, start_date) VALUES (3,/'USDJPY/',/'2014.01.03 03:00:00/');"; if (MySqlExecute(DB, Query)) { Print ("Succeeded!3 rows has been inserted by one query."); } else { Print ("Error of multiple statements: ", MySqlErrorDescription); } } else { Print ("Table `test_table` cannot be created. Error: ", MySqlErrorDescription); } MySqlDisconnect(DB); Print ("Disconnected. Script done!"); }
例程mysql-004.mq5显示从“mysql-003.mq5”脚本创建的数据表中检索到的数据。nbsp;
//+------------------------------------------------------------------+ //| MySQL-004.mq5 | //| Copyright 2014, Eugene Lugovoy | //| https://www.mql5.com | //| Select data from table (DEMO) | //+------------------------------------------------------------------+ #property copyright "Copyright 2014, Eugene Lugovoy." #property link "https://www.mql5.com" #property version "1.00" #property strict #include <MQLMySQL.mqh> string INI; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { string Host, User, Password, Database, Socket; // database credentials int Port,ClientFlag; int DB; // database identifier Print (MySqlVersion()); INI = TerminalInfoString(TERMINAL_PATH)+"//MQL5//Scripts//MyConnection.ini"; // reading database credentials from INI file Host = ReadIni(INI, "MYSQL", "Host"); User = ReadIni(INI, "MYSQL", "User"); Password = ReadIni(INI, "MYSQL", "Password"); Database = ReadIni(INI, "MYSQL", "Database"); Port = (int)StringToInteger(ReadIni(INI, "MYSQL", "Port")); Socket = ReadIni(INI, "MYSQL", "Socket"); ClientFlag = (int)StringToInteger(ReadIni(INI, "MYSQL", "ClientFlag")); Print ("Host: ",Host, ", User: ", User, ", Database: ",Database); // open database connection Print ("Connecting..."); DB = MySqlConnect(Host, User, Password, Database, Port, Socket, ClientFlag); if (DB == -1) { Print ("Connection failed!Error: "+MySqlErrorDescription); return; } else { Print ("Connected!DBID#",DB);} // executing SELECT statement string Query; int i,Cursor,Rows; int vId; string vCode; datetime vStartTime; Query = "SELECT id, code, start_date FROM `test_table`"; Print ("SQL> ", Query); Cursor = MySqlCursorOpen(DB, Query); if (Cursor >= 0) { Rows = MySqlCursorRows(Cursor); Print (Rows, " row(s) selected."); for (i=0; i<Rows; i++) if (MySqlCursorFetchRow(Cursor)) { vId = MySqlGetFieldAsInt(Cursor, 0); // id vCode = MySqlGetFieldAsString(Cursor, 1); // code vStartTime = MySqlGetFieldAsDatetime(Cursor, 2); // start_time Print ("ROW[",i,"]: id = ", vId, ", code = ", vCode, ", start_time = ", TimeToString(vStartTime, TIME_DATE|TIME_SECONDS)); } MySqlCursorClose(Cursor); // NEVER FORGET TO CLOSE CURSOR !!! } else { Print ("Cursor opening failed. Error: ", MySqlErrorDescription); } MySqlDisconnect(DB); Print ("Disconnected. Script done!"); }
上述例程包括实际项目中使用的典型错误处理。
实际上,MQL程序中使用的每个查询都应该在任何MySQL客户机上进行调试(在phpmyadmin、db ninja、mysql控制台中)。我个人使用并推荐MySQL版本的QuestToad,一个专业的数据库开发软件。
结论
本文没有详细说明MQLMySQL LDL是如何在微软Visual Studio 2010(C/C++)中开发和实现的。该软件解决方案专为特殊应用而设计,在各种MQL软件开发领域(从创建复杂的交易系统到Web发布)有100多个成功实现。
- 下面附上了MQL4和MQL5的链接库版本。附件还包括一个源代码为mqlmysql的zip文件。动态链接库。
- 文件也包括在档案中;
- 如果使用此例程,请不要忘记在文件/scripts/myconnection中指定数据库连接参数。ini
本文由MetaQuotes Software Corp.翻译自俄语原文
,网址为https://www.mql5.com/ru/articles/932。
MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!
免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。