
MQL5 酷宝书 — 宏观经济事件数据库
概述
本文将重点探讨如何针对描述宏观经济日历事件的数据进行分组和管理。
在现代世界中,信息流动无孔不入。 故此,在分析事件时必须处理大量数据。 尽管最大程度延申,然本文涵盖的问题与内容无关,但在形式上,似乎数据的正确组织和结构对把这些数据转化为信息这一事实有很大贡献。
我们将通过 SQLite 来解决这些任务。 开发人员自 MQL5 构建 2265(2019 年 6 月 2019 日发布)中添加了直接处理 SQLite 的支持。 在此之前,我必须使用各种连接器,例如,如文章 SQL 和 MQL5:使用 SQLite 数据库 中所述内容。
1. 文档和附加素材
我们来浏览一下文档,即有关数据库处理的部分。 开发人员提供了 26 个原生函数:
- DatabaseOpen();
- DatabaseClose();
- DatabaseImport();
- DatabaseExport();
- DatabasePrint();
- DatabaseTableExists();
- DatabaseExecute();
- DatabasePrepare();
- DatabaseReset();
- DatabaseBind();
- DatabaseBindArray();
- DatabaseRead();
- DatabaseReadBind();
- DatabaseFinalize();
- DatabaseTransactionBegin();
- DatabaseTransactionCommit();
- DatabaseTransactionRollback();
- DatabaseColumnsCount();
- DatabaseColumnName();
- DatabaseColumnType();
- DatabaseColumnSize();
- DatabaseColumnText();
- DatabaseColumnInteger();
- DatabaseColumnLong();
- DatabaseColumnDouble();
- DatabaseColumnBlob().
最近还添加了统计和数学函数模块。 文章 SQLite:MQL5 中 SQL 数据库的原生处理 可以作为研究这些函数的起点。
2. CDatabase 类
为了方便处理数据库,我们创建了 CDatabase 类。 首先,描述类组成。 然后通过示例检查其操作。
CDatabase 类的数据成员包括:
- m_name - 数据库文件名(带扩展名);
- m_handle - 数据库句柄;
- m_flags - 标志的组合;
- m_table_names – 数据表名称;
- m_curr_table_name – 当前数据表名称;
- m_sql_request_ha – 最后一条SQL 查询句柄;
- m_sql_request – 最后一条SQL 查询;
至于方法,我会将它们分为几组:
- 包含处理数据库的原生函数的方法(API MQL5 函数);
- 处理数据表的方法;
- 处理请求的方法;
- 操控视图的方法;
- 获取数据成员值的方法(get 方法)。
SQLite 具有多种请求形式,这些形式既有简单、亦有复杂。 我的意图不是在 CDatabase 类中为每个这样的形式创建自定义方法。如果类没有特定的请求方法,则可以使用通用 CDatabase::Select() 方法形成请求。
现在,我们来看一下运用 CDatabase 类功能的示例。
2.1 创建一个数据库
我们利用 1_create_calendar_db.mq5 脚本创建我们的第一个日历数据库。该脚本只有几行代码。
//--- include #include "..\CDatabase.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { CDatabase db_obj; string file_name="Databases\\test_calendar_db.sqlite"; uint flags=DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE; if(!db_obj.Open(file_name, flags)) ::PrintFormat("Failed to create a calendar database \"%s\"!", file_name); db_obj.Close(); } //+------------------------------------------------------------------+
运行脚本后,我们将看到数据库文件 test_calendar_db.sqlite 已显现在 %MQL5\Files\Databases 当中(图例 1)。
图例 1. test_calendar_db.sqlite 数据库文件
如果我们在代码编辑器中打开这个文件,我们将看到数据库是空的(图例 2)。
图例 2. test_calendar_db 数据库
2.2 创建数据表
我们尝试填充数据库。 为此,创建 COUNTRIES 数据表,我们将在其中输入其日历事件的国家/地区列表,随后我们的查询就可据其进行处理。 2_create_countries_table.mq5 脚本将完成这份工作。
//--- include #include "..\CDatabase.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- open a database CDatabase db_obj; string file_name="Databases\\test_calendar_db.sqlite"; uint flags=DATABASE_OPEN_READWRITE; if(!db_obj.Open(file_name, flags)) { ::PrintFormat("Failed to open a calendar database \"%s\"!", file_name); db_obj.Close(); return; } //--- create a table string table_name="COUNTRIES"; string params[]= { "COUNTRY_ID UNSIGNED BIG INT PRIMARY KEY NOT NULL,", // 1) country ID "NAME TEXT," // 2) country name "CODE TEXT," // 3) country code "CONTINENT TEXT," // 4) country continent "CURRENCY TEXT," // 5) currency code "CURRENCY_SYMBOL TEXT," // 6) currency symbol "URL_NAME TEXT" // 7) country URL }; if(!db_obj.CreateTable(table_name, params)) { ::PrintFormat("Failed to create a table \"%s\"!", table_name); db_obj.Close(); return; } db_obj.Close(); } //+------------------------------------------------------------------+
运行脚本后,我们可以发现 COUNTRIES 数据表已经出现在数据库当中(图例 3)。
图例 3. 空的 COUNTRIES 数据表
2.3 在数据表内填充
我们用数据填充一个新的数据表。 为此,需使用 CiCalendarInfo 类功能。 有关该类的更多详细信息,请参阅文章 MQL5 酷宝书 – 财经日历。 该任务由 3_fill_in_countries_table.mq5 脚本执行。
//--- include #include "..\CalendarInfo.mqh" #include "..\CDatabase.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- open a database CDatabase db_obj; string file_name="Databases\\test_calendar_db.sqlite"; uint flags=DATABASE_OPEN_READWRITE; if(!db_obj.Open(file_name, flags)) { db_obj.Close(); return; } //--- open a table string table_name="COUNTRIES"; if(db_obj.SelectTable(table_name)) if(db_obj.EmptyTable()) { db_obj.FinalizeSqlRequest(); string col_names[]= { "COUNTRY_ID", "NAME", "CODE", "CONTINENT", "CURRENCY", "CURRENCY_SYMBOL", "URL_NAME" }; //--- fill in the table CiCalendarInfo calendar_info; if(calendar_info.Init()) { MqlCalendarCountry calendar_countries[]; if(calendar_info.GetCountries(calendar_countries)) { if(db_obj.TransactionBegin()) for(int c_idx=0; c_idx<::ArraySize(calendar_countries); c_idx++) { MqlCalendarCountry curr_country=calendar_countries[c_idx]; string col_vals[]; ::ArrayResize(col_vals, 7); col_vals[0]=::StringFormat("%I64u", curr_country.id); col_vals[1]=::StringFormat("'%s'", curr_country.name); col_vals[2]=::StringFormat("'%s'", curr_country.code); col_vals[3]="NULL"; SCountryByContinent curr_country_continent_data; if(curr_country_continent_data.Init(curr_country.code)) col_vals[3]=::StringFormat("'%s'", curr_country_continent_data.ContinentDescription()); col_vals[4]=::StringFormat("'%s'", curr_country.currency); col_vals[5]=::StringFormat("'%s'", curr_country.currency_symbol); col_vals[6]=::StringFormat("'%s'", curr_country.url_name); if(!db_obj.InsertSingleRow(col_names, col_vals)) { db_obj.TransactionRollback(); db_obj.Close(); return; } db_obj.FinalizeSqlRequest(); } if(!db_obj.TransactionCommit()) ::PrintFormat("Failed to complete transaction execution, error %d", ::GetLastError()); } //--- print if(db_obj.PrintTable()<0) ::PrintFormat("Failed to print the table \"%s\", error %d", table_name, ::GetLastError()); } } db_obj.Close(); } //+------------------------------------------------------------------+
在日志中打印出 COUNTRIES 数据表的数据。
3_fill_in_countries_table (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY CURRENCY_SYMBOL URL_NAME 3_fill_in_countries_table (EURUSD,H1) --+----------------------------------------------------------------------------------------- 3_fill_in_countries_table (EURUSD,H1) 1| 554 New Zealand NZ Australia/Oceania NZD $ new-zealand 3_fill_in_countries_table (EURUSD,H1) 2| 999 European Union EU Europe EUR € european-union 3_fill_in_countries_table (EURUSD,H1) 3| 392 Japan JP Asia JPY ¥ japan 3_fill_in_countries_table (EURUSD,H1) 4| 124 Canada CA North America CAD $ canada 3_fill_in_countries_table (EURUSD,H1) 5| 36 Australia AU Australia/Oceania AUD $ australia 3_fill_in_countries_table (EURUSD,H1) 6| 156 China CN Asia CNY ¥ china 3_fill_in_countries_table (EURUSD,H1) 7| 380 Italy IT Europe EUR € italy 3_fill_in_countries_table (EURUSD,H1) 8| 702 Singapore SG Asia SGD R$ singapore 3_fill_in_countries_table (EURUSD,H1) 9| 276 Germany DE Europe EUR € germany 3_fill_in_countries_table (EURUSD,H1) 10| 250 France FR Europe EUR € france 3_fill_in_countries_table (EURUSD,H1) 11| 76 Brazil BR South America BRL R$ brazil 3_fill_in_countries_table (EURUSD,H1) 12| 484 Mexico MX North America MXN Mex$ mexico 3_fill_in_countries_table (EURUSD,H1) 13| 710 South Africa ZA Africa ZAR R south-africa 3_fill_in_countries_table (EURUSD,H1) 14| 344 Hong Kong HK Asia HKD HK$ hong-kong 3_fill_in_countries_table (EURUSD,H1) 15| 356 India IN Asia INR ₹ india 3_fill_in_countries_table (EURUSD,H1) 16| 578 Norway NO Europe NOK Kr norway 3_fill_in_countries_table (EURUSD,H1) 17| 840 United States US North America USD $ united-states 3_fill_in_countries_table (EURUSD,H1) 18| 826 United Kingdom GB Europe GBP £ united-kingdom 3_fill_in_countries_table (EURUSD,H1) 19| 756 Switzerland CH Europe CHF ₣ switzerland 3_fill_in_countries_table (EURUSD,H1) 20| 410 South Korea KR Asia KRW ₩ south-korea 3_fill_in_countries_table (EURUSD,H1) 21| 724 Spain ES Europe EUR € spain 3_fill_in_countries_table (EURUSD,H1) 22| 752 Sweden SE Europe SEK Kr sweden 3_fill_in_countries_table (EURUSD,H1) 23| 0 Worldwide WW World ALL worldwide
在 MetaEditor 中,数据表如下所示(图例 4)。
图例 4. 已填充的 COUNTRIES 数据表
2.4 选择一些表列
我们要处理的是 COUNTRIES 数据表的数据。 假设我们要选择以下列:
- "COUNTRY_ID";
- "COUNTRY_NAME";
- "COUNTRY_CODE";
- "COUNTRY_CONTINENT";
- "CURRENCY".
使用 4_select_some_columns.mq5 脚本按以下方式创建 SQL 查询:
//--- include #include "..\CDatabase.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- open a database CDatabase db_obj; string file_name="Databases\\test_calendar_db.sqlite"; uint flags=DATABASE_OPEN_READONLY; if(!db_obj.Open(file_name, flags)) { db_obj.Close(); return; } //--- check a table string table_name="COUNTRIES"; if(db_obj.SelectTable(table_name)) { string col_names_to_select[]= { "COUNTRY_ID", "NAME", "CODE", "CONTINENT", "CURRENCY" }; if(!db_obj.SelectFrom(col_names_to_select)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest(); } db_obj.Close(); } //+------------------------------------------------------------------+
打印出查询时,我们得到:
4_select_some_columns (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY 4_select_some_columns (EURUSD,H1) --+---------------------------------------------------------- 4_select_some_columns (EURUSD,H1) 1| 554 New Zealand NZ Australia/Oceania NZD 4_select_some_columns (EURUSD,H1) 2| 999 European Union EU Europe EUR 4_select_some_columns (EURUSD,H1) 3| 392 Japan JP Asia JPY 4_select_some_columns (EURUSD,H1) 4| 124 Canada CA North America CAD 4_select_some_columns (EURUSD,H1) 5| 36 Australia AU Australia/Oceania AUD 4_select_some_columns (EURUSD,H1) 6| 156 China CN Asia CNY 4_select_some_columns (EURUSD,H1) 7| 380 Italy IT Europe EUR 4_select_some_columns (EURUSD,H1) 8| 702 Singapore SG Asia SGD 4_select_some_columns (EURUSD,H1) 9| 276 Germany DE Europe EUR 4_select_some_columns (EURUSD,H1) 10| 250 France FR Europe EUR 4_select_some_columns (EURUSD,H1) 11| 76 Brazil BR South America BRL 4_select_some_columns (EURUSD,H1) 12| 484 Mexico MX North America MXN 4_select_some_columns (EURUSD,H1) 13| 710 South Africa ZA Africa ZAR 4_select_some_columns (EURUSD,H1) 14| 344 Hong Kong HK Asia HKD 4_select_some_columns (EURUSD,H1) 15| 356 India IN Asia INR 4_select_some_columns (EURUSD,H1) 16| 578 Norway NO Europe NOK 4_select_some_columns (EURUSD,H1) 17| 840 United States US North America USD 4_select_some_columns (EURUSD,H1) 18| 826 United Kingdom GB Europe GBP 4_select_some_columns (EURUSD,H1) 19| 756 Switzerland CH Europe CHF 4_select_some_columns (EURUSD,H1) 20| 410 South Korea KR Asia KRW 4_select_some_columns (EURUSD,H1) 21| 724 Spain ES Europe EUR 4_select_some_columns (EURUSD,H1) 22| 752 Sweden SE Europe SEK 4_select_some_columns (EURUSD,H1) 23| 0 Worldwide WW World ALL
显然,所进行的选取结果没有任何排序。
2.5 选取一些排序的表列
我们选取数据表的 “COUNTRY_ID” 列,并进行数据排序。 该请求已在如下的 5_select_some_sorted_columns.mq5 脚本里实现。
//--- include #include "..\CDatabase.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- open a database CDatabase db_obj; string file_name="Databases\\test_calendar_db.sqlite"; uint flags=DATABASE_OPEN_READONLY; if(!db_obj.Open(file_name, flags)) { db_obj.Close(); return; } //--- check a table string table_name="COUNTRIES"; if(db_obj.SelectTable(table_name)) { string col_names_to_select[]= { "COUNTRY_ID", "NAME", "CODE", "CONTINENT", "CURRENCY" }; string ord_names[1]; ord_names[0]=col_names_to_select[0]; if(!db_obj.SelectFromOrderedBy(col_names_to_select, ord_names)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest(); } db_obj.Close(); } //+------------------------------------------------------------------+
查询执行的结果将显示在日志中:
5_select_some_sorted_columns (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY 5_select_some_sorted_columns (EURUSD,H1) --+---------------------------------------------------------- 5_select_some_sorted_columns (EURUSD,H1) 1| 0 Worldwide WW World ALL 5_select_some_sorted_columns (EURUSD,H1) 2| 36 Australia AU Australia/Oceania AUD 5_select_some_sorted_columns (EURUSD,H1) 3| 76 Brazil BR South America BRL 5_select_some_sorted_columns (EURUSD,H1) 4| 124 Canada CA North America CAD 5_select_some_sorted_columns (EURUSD,H1) 5| 156 China CN Asia CNY 5_select_some_sorted_columns (EURUSD,H1) 6| 250 France FR Europe EUR 5_select_some_sorted_columns (EURUSD,H1) 7| 276 Germany DE Europe EUR 5_select_some_sorted_columns (EURUSD,H1) 8| 344 Hong Kong HK Asia HKD 5_select_some_sorted_columns (EURUSD,H1) 9| 356 India IN Asia INR 5_select_some_sorted_columns (EURUSD,H1) 10| 380 Italy IT Europe EUR 5_select_some_sorted_columns (EURUSD,H1) 11| 392 Japan JP Asia JPY 5_select_some_sorted_columns (EURUSD,H1) 12| 410 South Korea KR Asia KRW 5_select_some_sorted_columns (EURUSD,H1) 13| 484 Mexico MX North America MXN 5_select_some_sorted_columns (EURUSD,H1) 14| 554 New Zealand NZ Australia/Oceania NZD 5_select_some_sorted_columns (EURUSD,H1) 15| 578 Norway NO Europe NOK 5_select_some_sorted_columns (EURUSD,H1) 16| 702 Singapore SG Asia SGD 5_select_some_sorted_columns (EURUSD,H1) 17| 710 South Africa ZA Africa ZAR 5_select_some_sorted_columns (EURUSD,H1) 18| 724 Spain ES Europe EUR 5_select_some_sorted_columns (EURUSD,H1) 19| 752 Sweden SE Europe SEK 5_select_some_sorted_columns (EURUSD,H1) 20| 756 Switzerland CH Europe CHF 5_select_some_sorted_columns (EURUSD,H1) 21| 826 United Kingdom GB Europe GBP 5_select_some_sorted_columns (EURUSD,H1) 22| 840 United States US North America USD 5_select_some_sorted_columns (EURUSD,H1) 23| 999 European Union EU Europe EUR
脚本工作正常 — “COUNTRY_ID” 列从 0 开始,到 999 结束。
2.6 选取指定表列的分组结果
现在,我们使用 6_select_some_grouped_columns.mq5 脚本按大陆获取分组的国家/地区名称。 该任务是获取每个大陆行中包含的国家/地区数量。 从 “NAME” 列中选取国家/地区。 运行脚本后,日志中将显示以下行:
6_select_some_grouped_columns (EURUSD,H1) #| CONTINENT COUNT(NAME) 6_select_some_grouped_columns (EURUSD,H1) -+------------------------------ 6_select_some_grouped_columns (EURUSD,H1) 1| Africa 1 6_select_some_grouped_columns (EURUSD,H1) 2| Asia 6 6_select_some_grouped_columns (EURUSD,H1) 3| Australia/Oceania 2 6_select_some_grouped_columns (EURUSD,H1) 4| Europe 9 6_select_some_grouped_columns (EURUSD,H1) 5| North America 3 6_select_some_grouped_columns (EURUSD,H1) 6| South America 1 6_select_some_grouped_columns (EURUSD,H1) 7| World 1
“Europe” 包括最多的国家 — 9个,而 “Africa” 和 “South America” 各只有 1 个。 此外,还有 “World”。
2.7 选取指定表列的唯一值
现在使用 7_select_distinct_columns.mq5 脚本收集 CURRENCY 列中的唯一值。 有些国家/地区使用相同的货币。 若要清除重复项,则运行脚本。 我们可以看到以下结果:
7_select_distinct_columns (EURUSD,H1) 1| NZD 7_select_distinct_columns (EURUSD,H1) 2| EUR 7_select_distinct_columns (EURUSD,H1) 3| JPY 7_select_distinct_columns (EURUSD,H1) 4| CAD 7_select_distinct_columns (EURUSD,H1) 5| AUD 7_select_distinct_columns (EURUSD,H1) 6| CNY 7_select_distinct_columns (EURUSD,H1) 7| SGD 7_select_distinct_columns (EURUSD,H1) 8| BRL 7_select_distinct_columns (EURUSD,H1) 9| MXN 7_select_distinct_columns (EURUSD,H1) 10| ZAR 7_select_distinct_columns (EURUSD,H1) 11| HKD 7_select_distinct_columns (EURUSD,H1) 12| INR 7_select_distinct_columns (EURUSD,H1) 13| NOK 7_select_distinct_columns (EURUSD,H1) 14| USD 7_select_distinct_columns (EURUSD,H1) 15| GBP 7_select_distinct_columns (EURUSD,H1) 16| CHF 7_select_distinct_columns (EURUSD,H1) 17| KRW 7_select_distinct_columns (EURUSD,H1) 18| SEK 7_select_distinct_columns (EURUSD,H1) 19| ALL
因此,日历包含总共 18 种货币的事件,和一组适用于所有货币的事件。
很容易看出,选取分组结果和选取唯一值的方法具有相似之处。 我们用一个例子来演示这一点。
该 8_compare_ grouped_and_distinct_columns.mq5 脚本在日志中显示以下结果:
8_compare_ grouped_and_distinct_columns (EURUSD,H1) 8_compare_ grouped_and_distinct_columns (EURUSD,H1) Method CDatabase::SelectFromGroupBy() 8_compare_ grouped_and_distinct_columns (EURUSD,H1) #| CONTINENT 8_compare_ grouped_and_distinct_columns (EURUSD,H1) -+------------------ 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 1| Africa 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 2| Asia 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 3| Australia/Oceania 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 4| Europe 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 5| North America 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 6| South America 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 7| World 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 8_compare_ grouped_and_distinct_columns (EURUSD,H1) Method CDatabase::SelectDistinctFrom() 8_compare_ grouped_and_distinct_columns (EURUSD,H1) #| CONTINENT 8_compare_ grouped_and_distinct_columns (EURUSD,H1) -+------------------ 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 1| Australia/Oceania 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 2| Europe 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 3| Asia 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 4| North America 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 5| South America 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 6| Africa 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 7| World
这些方法返回了相同的内容结果,因为我们将 “CONTINENT” 列定义为第一种方法的分组列(字段)。 有趣的是,第一种方法也为我们排序了选取值。
2.8 选择指定表列的排序唯一值
CURRENCY 列的数值经由 7_select_distinct_columns.mq5 脚本按未排序的方式显示。 我们选取并进行排序(9_select_sorted_distinct_columns.mq5 脚本)。 列 “COUNTRY_ID” 作为排序准则。 日志当中的操作结果,我们得到:
9_select_sorted_distinct_columns (EURUSD,H1) #| CURRENCY 9_select_sorted_distinct_columns (EURUSD,H1) --+--------- 9_select_sorted_distinct_columns (EURUSD,H1) 1| ALL 9_select_sorted_distinct_columns (EURUSD,H1) 2| AUD 9_select_sorted_distinct_columns (EURUSD,H1) 3| BRL 9_select_sorted_distinct_columns (EURUSD,H1) 4| CAD 9_select_sorted_distinct_columns (EURUSD,H1) 5| CNY 9_select_sorted_distinct_columns (EURUSD,H1) 6| EUR 9_select_sorted_distinct_columns (EURUSD,H1) 7| HKD 9_select_sorted_distinct_columns (EURUSD,H1) 8| INR 9_select_sorted_distinct_columns (EURUSD,H1) 9| JPY 9_select_sorted_distinct_columns (EURUSD,H1) 10| KRW 9_select_sorted_distinct_columns (EURUSD,H1) 11| MXN 9_select_sorted_distinct_columns (EURUSD,H1) 12| NZD 9_select_sorted_distinct_columns (EURUSD,H1) 13| NOK 9_select_sorted_distinct_columns (EURUSD,H1) 14| SGD 9_select_sorted_distinct_columns (EURUSD,H1) 15| ZAR 9_select_sorted_distinct_columns (EURUSD,H1) 16| SEK 9_select_sorted_distinct_columns (EURUSD,H1) 17| CHF 9_select_sorted_distinct_columns (EURUSD,H1) 18| GBP 9_select_sorted_distinct_columns (EURUSD,H1) 19| USD
现在所有货币均已排序。 默认情况下,排序按升序执行。
2.9 按条件选取部分表列
之前,我们已经创建了一个 SQL 查询来选取表列。 现在我们来这样做,如此在满足某些条件时可以获取列数据。 假设我们要选择 ID 等于或大于 392,且等于或小于 840 的国家/地区。 此任务可由 10_select_some_columns_where.mq5 脚本来解决。
运行脚本后,我们将在日志中看到以下内容:
10_select_some_columns_where (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY 10_select_some_columns_where (EURUSD,H1) --+---------------------------------------------------------- 10_select_some_columns_where (EURUSD,H1) 1| 392 Japan JP Asia JPY 10_select_some_columns_where (EURUSD,H1) 2| 410 South Korea KR Asia KRW 10_select_some_columns_where (EURUSD,H1) 3| 484 Mexico MX North America MXN 10_select_some_columns_where (EURUSD,H1) 4| 554 New Zealand NZ Australia/Oceania NZD 10_select_some_columns_where (EURUSD,H1) 5| 578 Norway NO Europe NOK 10_select_some_columns_where (EURUSD,H1) 6| 702 Singapore SG Asia SGD 10_select_some_columns_where (EURUSD,H1) 7| 710 South Africa ZA Africa ZAR 10_select_some_columns_where (EURUSD,H1) 8| 724 Spain ES Europe EUR 10_select_some_columns_where (EURUSD,H1) 9| 752 Sweden SE Europe SEK 10_select_some_columns_where (EURUSD,H1) 10| 756 Switzerland CH Europe CHF 10_select_some_columns_where (EURUSD,H1) 11| 826 United Kingdom GB Europe GBP 10_select_some_columns_where (EURUSD,H1) 12| 840 United States US North America USD
换言之,取样以国家/地区代码 392 开始,以 840 结束。
2.10 按条件选取一些排序的表列
我们继续让前面的问题更加复杂。 我们为取样添加一个排序准则 — 即属于大陆的国家。 当前任务已在 11_select_some_sorted_columns_where.mq5 脚本中得到解决。 运行它后,我们将在日志中看到以下行:
11_select_some_sorted_columns_where (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY 11_select_some_sorted_columns_where (EURUSD,H1) --+---------------------------------------------------------- 11_select_some_sorted_columns_where (EURUSD,H1) 1| 710 South Africa ZA Africa ZAR 11_select_some_sorted_columns_where (EURUSD,H1) 2| 392 Japan JP Asia JPY 11_select_some_sorted_columns_where (EURUSD,H1) 3| 410 South Korea KR Asia KRW 11_select_some_sorted_columns_where (EURUSD,H1) 4| 702 Singapore SG Asia SGD 11_select_some_sorted_columns_where (EURUSD,H1) 5| 554 New Zealand NZ Australia/Oceania NZD 11_select_some_sorted_columns_where (EURUSD,H1) 6| 578 Norway NO Europe NOK 11_select_some_sorted_columns_where (EURUSD,H1) 7| 724 Spain ES Europe EUR 11_select_some_sorted_columns_where (EURUSD,H1) 8| 752 Sweden SE Europe SEK 11_select_some_sorted_columns_where (EURUSD,H1) 9| 756 Switzerland CH Europe CHF 11_select_some_sorted_columns_where (EURUSD,H1) 10| 826 United Kingdom GB Europe GBP 11_select_some_sorted_columns_where (EURUSD,H1) 11| 484 Mexico MX North America MXN 11_select_some_sorted_columns_where (EURUSD,H1) 12| 840 United States US North America USD
结果就是,“South Africa” 在取样中排名第一,因为 “Africa” 大陆在大陆列表中排列第一。
2.11 更新符合条件的部分表列
假设我们面临着更新所选列中行数据的任务。 甚而,我们需要在满足先决条件后执行此操作。
我们以亚洲国家/地区为例,并在 “CURRENCY” 和 “CURRENCY_SYMBOL” 列中重置它们的值。 该任务由 12_update_some_columns.mq5 脚本执行。
其执行的结果,我们得到下数据表:
12_update_some_columns (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY CURRENCY_SYMBOL URL_NAME 12_update_some_columns (EURUSD,H1) --+----------------------------------------------------------------------------------------- 12_update_some_columns (EURUSD,H1) 1| 554 New Zealand NZ Australia/Oceania NZD $ new-zealand 12_update_some_columns (EURUSD,H1) 2| 999 European Union EU Europe EUR € european-union 12_update_some_columns (EURUSD,H1) 3| 392 Japan JP Asia None None japan 12_update_some_columns (EURUSD,H1) 4| 124 Canada CA North America CAD $ canada 12_update_some_columns (EURUSD,H1) 5| 36 Australia AU Australia/Oceania AUD $ australia 12_update_some_columns (EURUSD,H1) 6| 156 China CN Asia None None china 12_update_some_columns (EURUSD,H1) 7| 380 Italy IT Europe EUR € italy 12_update_some_columns (EURUSD,H1) 8| 702 Singapore SG Asia None None singapore 12_update_some_columns (EURUSD,H1) 9| 276 Germany DE Europe EUR € germany 12_update_some_columns (EURUSD,H1) 10| 250 France FR Europe EUR € france 12_update_some_columns (EURUSD,H1) 11| 76 Brazil BR South America BRL R$ brazil 12_update_some_columns (EURUSD,H1) 12| 484 Mexico MX North America MXN Mex$ mexico 12_update_some_columns (EURUSD,H1) 13| 710 South Africa ZA Africa ZAR R south-africa 12_update_some_columns (EURUSD,H1) 14| 344 Hong Kong HK Asia None None hong-kong 12_update_some_columns (EURUSD,H1) 15| 356 India IN Asia None None india 12_update_some_columns (EURUSD,H1) 16| 578 Norway NO Europe NOK Kr norway 12_update_some_columns (EURUSD,H1) 17| 840 United States US North America USD $ united-states 12_update_some_columns (EURUSD,H1) 18| 826 United Kingdom GB Europe GBP £ united-kingdom 12_update_some_columns (EURUSD,H1) 19| 756 Switzerland CH Europe CHF ₣ switzerland 12_update_some_columns (EURUSD,H1) 20| 410 South Korea KR Asia None None south-korea 12_update_some_columns (EURUSD,H1) 21| 724 Spain ES Europe EUR € spain 12_update_some_columns (EURUSD,H1) 22| 752 Sweden SE Europe SEK Kr sweden 12_update_some_columns (EURUSD,H1) 23| 0 Worldwide WW World ALL worldwide
2.12 替换和添加一些表行
我们继续操控数据表。 现在,我们尝试替换所选数据表中的某些行。
假设我们需要将 CURRENCY_SYMBOL 列中 “Mexico” 的当前符号 “Mex$” 替换为 “Peso mexicano”。 我们将此任务委托给 13_replace_some_rows.mq5 脚本。
在当前版本的 “COUNTRIES” 数据表中,Mexico 具有以下项目:
COUNTRY_ID | NAME | CODE | CONTINENT | CURRENCY | CURRENCY_SYMBOL | URL_NAME |
---|---|---|---|---|---|---|
484 | Mexico | MX | North America | MXN | Mex$ |
|
为了替换数据表中的这一行,我们需要为所选行设置一个唯一值。 否则,SQLite 将无法理解我们想要替换的内容。
假设此数值将是国家/地区的名称(NAME 列)。 那么,替换函数将在代码中表示如下:
//--- the replaced row for "COUNTRY_NAME=Mexico" string col_names[]= { "NAME", "CURRENCY_SYMBOL" }; string col_vals[2]; col_vals[0]=::StringFormat("'%s'", "Mexico"); col_vals[1]=::StringFormat("'%s'", "Peso mexicano"); if(!db_obj.Replace(col_names, col_vals)) { db_obj.Close(); return; }
而当执行脚本时,我们得到以下错误:
11_replace_some_rows (EURUSD,H1) database error, NOT NULL constraint failed: COUNTRIES.COUNTRY_ID 11_replace_some_rows (EURUSD,H1) CDatabase::Replace: failed with code 5619
显然,这是违反了 NOT NULL 约束。 问题是,最初,在创建数据表时,指定 COUNTRY_ID 列不能包含 null。 因此,有必要为此数据列加入一个数值。 为了不得到半空的行,我们为所有列添加值。
//--- the replaced row for "COUNTRY_NAME=Mexico" string col_names[]= { "COUNTRY_ID", "NAME", "CODE", "CONTINENT", "CURRENCY", "CURRENCY_SYMBOL", "URL_NAME" }; string col_vals[7]; col_vals[0]=::StringFormat("%I64u", 484); col_vals[1]=::StringFormat("'%s'", "Mexico"); col_vals[2]=::StringFormat("'%s'", "MX"); col_vals[3]=::StringFormat("'%s'", "North America"); col_vals[4]=::StringFormat("'%s'", "MXN"); col_vals[5]=::StringFormat("'%s'", "Peso mexicano"); col_vals[6]=::StringFormat("'%s'", "mexico"); if(!db_obj.Replace(col_names, col_vals)) { db_obj.Close(); return; }
现在脚本的工作恰到好处。 日志中将显示以下项目:
13_replace_some_rows (EURUSD,H1) 'Mexico' row before replacement 13_replace_some_rows (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY CURRENCY_SYMBOL URL_NAME 13_replace_some_rows (EURUSD,H1) -+----------------------------------------------------------------------- 13_replace_some_rows (EURUSD,H1) 1| 484 Mexico MX North America MXN Mex$ mexico 13_replace_some_rows (EURUSD,H1) 13_replace_some_rows (EURUSD,H1) 'Mexico' row after replacement 13_replace_some_rows (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY CURRENCY_SYMBOL URL_NAME 13_replace_some_rows (EURUSD,H1) -+----------------------------------------------------------------------- 13_replace_some_rows (EURUSD,H1) 1| 484 Mexico MX North America MXN Peso mexicano mexico
值得注意的是,如果没有包含有关 Mexico 数据的行,则只需添加即可。 换言之,替换操作也是添加表行的操作。
2.13 删除部分表行
现在,我们看看如何减少表行数,而不是增加表行数。 为此,创建了 14_delete_some_rows.mq5 脚本,该脚本将根据请求从所选数据表中删除与 Asia 相关的数据行。
运行脚本后,显示最终的数据表:
14_delete_some_rows (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY CURRENCY_SYMBOL URL_NAME 14_delete_some_rows (EURUSD,H1) --+----------------------------------------------------------------------------------------- 14_delete_some_rows (EURUSD,H1) 1| 554 New Zealand NZ Australia/Oceania NZD $ new-zealand 14_delete_some_rows (EURUSD,H1) 2| 999 European Union EU Europe EUR € european-union 14_delete_some_rows (EURUSD,H1) 3| 124 Canada CA North America CAD $ canada 14_delete_some_rows (EURUSD,H1) 4| 36 Australia AU Australia/Oceania AUD $ australia 14_delete_some_rows (EURUSD,H1) 5| 380 Italy IT Europe EUR € italy 14_delete_some_rows (EURUSD,H1) 6| 276 Germany DE Europe EUR € germany 14_delete_some_rows (EURUSD,H1) 7| 250 France FR Europe EUR € france 14_delete_some_rows (EURUSD,H1) 8| 76 Brazil BR South America BRL R$ brazil 14_delete_some_rows (EURUSD,H1) 9| 710 South Africa ZA Africa ZAR R south-africa 14_delete_some_rows (EURUSD,H1) 10| 578 Norway NO Europe NOK Kr norway 14_delete_some_rows (EURUSD,H1) 11| 840 United States US North America USD $ united-states 14_delete_some_rows (EURUSD,H1) 12| 826 United Kingdom GB Europe GBP £ united-kingdom 14_delete_some_rows (EURUSD,H1) 13| 756 Switzerland CH Europe CHF ₣ switzerland 14_delete_some_rows (EURUSD,H1) 14| 724 Spain ES Europe EUR € spain 14_delete_some_rows (EURUSD,H1) 15| 752 Sweden SE Europe SEK Kr sweden 14_delete_some_rows (EURUSD,H1) 16| 0 Worldwide WW World ALL worldwide 14_delete_some_rows (EURUSD,H1) 17| 484 Mexico MX North America MXN Peso mexicano mexico
已无与 Asia 相关的数据行。
2.14 向数据表中添加列
向数据表添加新数据列也是一项很常见的任务。
假设我们需要扩展 “COUNTRIES” 数据表,并添加一个包含日历中宏观经济事件数量的数据列。
该任务将由 15_add_new_column.mq5 脚本执行。
执行脚本后,检查数据表(图例 5)。 现在,它已拥有新的数据列 EVENTS_NUM。
图例 5. 在 COUNTRIES 数据表中的新 EVENTS_NUM 列
2.15 重命名数据表中的数据列
数据列重命名由 CDatabase::RenameColumn(const string _curr_name, const string _new_name) 完成。 参数设置为当前列名和新列名。 由 16_rename_column.mq5 脚本替换 EVENTS_NUM 列名至 EVENTS_NUMBER。
图例 6. 在 COUNTRIES 数据表中改名后的 EVENTS_NUMBER 列
现在数据表如下所示(图例 6)。
2.16 合并数据行
假设我们需要在单个数据表中合并取样结果。 这可以通过 CDatabase::Union() 方法达成。 该任务由 17_union_some_columns.mq5 脚本执行。
假设我们有两个数据表 — EUROPEAN_COUNTRIES 和 NORTH_AMERICAN_COUNTRIES。 第一个数据表包含欧洲国家,而第二个数据表包含北美国家。 我们首先创建数据表来合并它们的数据行。 每个数据表都将是从 “COUNTRIES” 数据表中筛选出的结果。 如以下代码中所示:
//--- create 2 tables string table1_name, table2_name, sql_request; table1_name="EUROPEAN_COUNTRIES"; table2_name="NORTH_AMERICAN_COUNTRIES"; sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY " "as currency FROM COUNTRIES " "WHERE CONTINENT='North America'"; if(!db_obj.CreateTableAs(table2_name, sql_request, true)) { db_obj.Close(); return; } db_obj.FinalizeSqlRequest(); sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY " "as currency FROM COUNTRIES " "WHERE CONTINENT='Europe'"; if(!db_obj.CreateTableAs(table1_name, sql_request, true)) { db_obj.Close(); return; } db_obj.FinalizeSqlRequest();
运行脚本时,我们在日志中得到以下项目:
16_union_some_columns (EURUSD,H1) #| id name currency 16_union_some_columns (EURUSD,H1) --+---------------------------- 16_union_some_columns (EURUSD,H1) 1| 124 Canada CAD 16_union_some_columns (EURUSD,H1) 2| 250 France EUR 16_union_some_columns (EURUSD,H1) 3| 276 Germany EUR 16_union_some_columns (EURUSD,H1) 4| 380 Italy EUR 16_union_some_columns (EURUSD,H1) 5| 484 Mexico MXN 16_union_some_columns (EURUSD,H1) 6| 578 Norway NOK 16_union_some_columns (EURUSD,H1) 7| 724 Spain EUR 16_union_some_columns (EURUSD,H1) 8| 752 Sweden SEK 16_union_some_columns (EURUSD,H1) 9| 756 Switzerland CHF 16_union_some_columns (EURUSD,H1) 10| 826 United Kingdom GBP 16_union_some_columns (EURUSD,H1) 11| 840 United States USD 16_union_some_columns (EURUSD,H1) 12| 999 European Union EUR
生成的取样已包括了欧洲和北美国家。
2.17 取样差异
假设我们有两个取样。 我们需要在第一个取样中找到第二个取样中不存在的项目。 这可以通过 CDatabase::Except() 方法达成。
我们以 COUNTRIES 和 EUROPEAN_COUNTRIES 数据表为例。 我们看看如果将 EXCEPT 运算符应用于第一个数据表,则有哪些国家/地区保留。
该解决方案由 18_except_some_columns.mq5 脚本提供。
由于脚本执行,以下行将显示在日志当中:
18_except_some_columns (EURUSD,H1) #| COUNTRY_ID NAME CURRENCY 18_except_some_columns (EURUSD,H1) -+---------------------------------- 18_except_some_columns (EURUSD,H1) 1| 0 Worldwide ALL 18_except_some_columns (EURUSD,H1) 2| 36 Australia AUD 18_except_some_columns (EURUSD,H1) 3| 76 Brazil BRL 18_except_some_columns (EURUSD,H1) 4| 124 Canada CAD 18_except_some_columns (EURUSD,H1) 5| 484 Mexico MXN 18_except_some_columns (EURUSD,H1) 6| 554 New Zealand NZD 18_except_some_columns (EURUSD,H1) 7| 710 South Africa ZAR 18_except_some_columns (EURUSD,H1) 8| 840 United States USD
如我们所见,该取样不包含欧洲国家。 亚洲的也不存在,因为它们早些时候就被删除了。
2.18 取样交集
现在,我们找出这些取样具有哪些共同特征。 换言之,任务是查找共同的取样行。
首先,我们更新 COUNTRIES 数据表,并将其恢复为原始形式,其中包括亚洲国家/地区。
创建两个包含 “id”、“name” 和 “currency” 列的临时数据表。 第一个将包括 COUNTRY_ID 列中数值不超过 578 的国家,第二个则包括同一列中数值至少为 392 的国家。
//--- create temporary tables string table1_name, table2_name, sql_request; table1_name="Table1"; table2_name="Table2"; sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY " "as currency FROM COUNTRIES " "WHERE COUNTRY_ID<=578"; if(!db_obj.CreateTableAs(table1_name, sql_request, true, true)) { db_obj.Close(); return; } db_obj.FinalizeSqlRequest(); //--- print the temporary table string temp_col_names[]= {"*"}; if(db_obj.SelectTable(table1_name, true)) if(db_obj.SelectFrom(temp_col_names)) { ::Print(" \nTable #1: "); db_obj.PrintSqlRequest(); db_obj.FinalizeSqlRequest(); } sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY " "as currency FROM COUNTRIES " "WHERE COUNTRY_ID>=392"; if(!db_obj.CreateTableAs(table2_name, sql_request, true, true)) { db_obj.Close(); return; } db_obj.FinalizeSqlRequest(); //--- print the temporary table if(db_obj.SelectTable(table2_name, true)) if(db_obj.SelectFrom(temp_col_names)) { ::Print(" \nTable #2: "); db_obj.PrintSqlRequest(); db_obj.FinalizeSqlRequest(); }
我们在 19_intersect_some_columns.mq5 脚本中调用 CDatabase::Intersect() 方法的功能。 结果就是,我们在日志中得到以下行:
19_intersect_some_columns (EURUSD,H1) #| id name currency 19_intersect_some_columns (EURUSD,H1) -+------------------------- 19_intersect_some_columns (EURUSD,H1) 1| 392 Japan JPY 19_intersect_some_columns (EURUSD,H1) 2| 410 South Korea KRW 19_intersect_some_columns (EURUSD,H1) 3| 484 Mexico MXN 19_intersect_some_columns (EURUSD,H1) 4| 554 New Zealand NZD 19_intersect_some_columns (EURUSD,H1) 5| 578 Norway NOK
脚本已正常工作。 我们得到了一个国家的列表,其中国家码的最小 id 值为 392,而最大值为 578。
2.19 创建视图
视图是一种虚拟数据表。 它的便利之处在于,您可以显示从任何其它数据表中选取的数据。
我们将调用 bool CDatabase::CreateView() 和 bool CDatabase::CreateViewWhere() 方法创建视图。第一个创建某种无条件视图,而第二个根据指定条件创建视图。
我们研究以下示例。 我们有 COUNTRIES 数据表。 假设我们需要按新的虚拟数据表的 “NAME”、“CONTINENT” 和 “CURRENCY” 列选取所有国家/地区。
我们利用 20_create_view.mq5 脚本来解决这个问题。 结果就是 “All_countries” 视图(图例 7)。
图例 7. “All_countries” 视图
我们把示例复杂化,现在仅选择欧洲的国家/地区。 21_create_view_where.mq5 脚本将做此事。 而结果就是,我们得到一个仅包含欧洲国家的虚拟数据表(图例 8)。
图例 8. “European” 视图
视图不是完备的数据表,因为我们无法在其中添加、删除或更新数据行。 但它们可方便地聚合复杂查询的结果,并可选择单个数据列,同时更改名称时,也不会影响数据表本身之间的关系。
2.20 删除视图
我们可以调用 CDatabase::DropView() 方法删除之前创建的视图。
该方法类似于其对应项,其为删除数据表的 DropTable()。 在之前的示例中,它是在创建视图之前调用的视图删除方法。
现在是时候说几句关于 IF EXISTS 结构的话了。 如果我们尝试删除含有此结构的不存在的视图,该方法返回 “true”,否则返回 “false”。
我们看看 22_drop_view.mq5 脚本是如何操作的。
//--- drop a view string table_name="COUNTRIES"; if(db_obj.SelectTable(table_name)) for(int idx=0; idx<2; idx++) { string view_name=::StringFormat("European%d", idx+1); bool if_exists=idx; if(db_obj.DropView(view_name, if_exists)) ::PrintFormat("A view \"%s\" has been successfully dropped!", view_name); db_obj.FinalizeSqlRequest(); }
它首先尝试删除不存在的 “European_countries1” 视图,且未调用 IF EXISTS。 结果我们收到错误 5601:
22_drop_view (EURUSD,H1) database error, no such view: European1 22_drop_view (EURUSD,H1) CDatabase::Select: failed with code 5601 22_drop_view (EURUSD,H1) A view "European2" has been successfully dropped!
之后,脚本尝试利用 IF EXISTS 删除同样不存在的 “European_countries2” 视图。 第二个视图删除成功,即使没有任何实际删除。
2.21 重命名数据表
假设我们面临着重命名数据表本身的任务。 为此,我们转向 CDatabase::RenameTable() 方法。 重命名由 23_rename_table.mq5 脚本完成。
图例 9. 重命名 COUNTRIES1 数据表
结果就是,当前数据表被重命名 COUNTRIES1 (图例 9)。
3. 宏观经济事件数据库
在本章节中,我提议开始创建涵盖日历中宏观经济事件的关系数据库。
因此,我们首先创建一个数据表结构,它将构成未来的数据库。 在文章 MQL5 酷宝书 – 财经日历 里已经研究过日历结构。 因此,在我们的例子中,创建数据库表,并为它们设置关系非常容易。
3.1 数据表和关系连接
数据库将包含三个源表:
- COUNTRIES;
- EVENTS;
- EVENT_VALUES.
数据表之间的关系连接如图例10 所示
图例 10. Calendar_DB 数据库中表之间的连接结构
COUNTRIES 数据表将成为 EVENTS 数据表的父表。 反之,后者成为前者的子表。
COUNTRIES 数据表的主键是 “COUNTRY_ID” 列(字段)。 在图像中,它前面带有 “+” 号。 对于 EVENTS 数据表,主键是 EVENT_ID 数据列,而 COUNTRY_ID 数据列是外键。 在结构中,它前面有 “#” 符号。
EVENTS 数据表成为 EVENT_VALUES 数据表的父表,而第二个数据表成为第一个数据表的子表。
在 EVENT_VALUES 数据表中,主键是 VALUE_ID 列(字段),而外键是 EVENT_ID。
正是为了实现上述数据表之间的关系,需要这些键。 反过来,这些关系有助于数据库中数据的完整性。
三个数据表之间的关系采用一对多形式 (1..*)。 我相信,破译它们并不难。 第一个国家和事件之间的连接可以表示如下:一个国家有许多宏观经济事件,而一个事件只对应一个国家。 第二个事件和事件值之间的连接可以说明如下:一个事件具有多个值,而任何值只对应一个事件。
我们继续讨论代码。 该 sCreateAndFillCalendarDB.mq5 脚本具有以下阶段:
- 创建日历数据库;
- 创建数据库表;
- 填充数据表。
例如,我们看看如何创建 EVENTS 数据表。 创建此数据表的最终查询如下所示:
CREATE TABLE IF NOT EXISTS EVENTS ( EVENT_ID [UNSIGNED BIG INT] PRIMARY KEY NOT NULL, TYPE TEXT, SECTOR TEXT, FREQUENCY TEXT, TIME_MODE TEXT, COUNTRY_ID [UNSIGNED BIG INT] NOT NULL, UNIT TEXT, IMPORTANCE TEXT, MULTIPLIER TEXT, DIGITS [UNSIGNED INT], SOURCE TEXT, CODE TEXT, NAME TEXT, FOREIGN KEY ( COUNTRY_ID ) REFERENCES COUNTRIES (COUNTRY_ID) ON UPDATE CASCADE ON DELETE CASCADE )
创建外键的代码特别重要。 FOREIGN KEY (COUNTRY_ID) 这一行意即数据表的外键是 COUNTRY_ID 字段。 而 REFERENCES COUNTRIES(COUNTRY_ID) 构造需引用 COUNTRIES 父表。
ON UPDATE CASCADE 和 ON DELETE CASCADE 表达式意即如果从父表中删除或修改相关数据行,则子表中的数据行也将被删除或修改。
至于填充数据表,下面是一个代码模块,COUNTRIES 数据表即在此处填充。
//--- Table 1 MqlCalendarCountry calendar_countries[]; table_name="COUNTRIES"; if(db_obj.SelectTable(table_name)) if(db_obj.EmptyTable()) { db_obj.FinalizeSqlRequest(); string col_names[]= { "COUNTRY_ID", // 1 "NAME", // 2 "CODE", // 3 "CONTINENT", // 4 "CURRENCY", // 5 "CURRENCY_SYMBOL",// 6 "URL_NAME" // 7 }; CiCalendarInfo calendar_info; if(calendar_info.Init()) { if(calendar_info.GetCountries(calendar_countries)) { if(db_obj.TransactionBegin()) for(int c_idx=0; c_idx<::ArraySize(calendar_countries); c_idx++) { MqlCalendarCountry curr_country=calendar_countries[c_idx]; string col_vals[]; ::ArrayResize(col_vals, 7); col_vals[0]=::StringFormat("%I64u", curr_country.id); col_vals[1]=::StringFormat("'%s'", curr_country.name); col_vals[2]=::StringFormat("'%s'", curr_country.code); col_vals[3]="NULL"; SCountryByContinent curr_country_continent_data; if(curr_country_continent_data.Init(curr_country.code)) col_vals[3]=::StringFormat("'%s'", curr_country_continent_data.ContinentDescription()); col_vals[4]=::StringFormat("'%s'", curr_country.currency); col_vals[5]=::StringFormat("'%s'", curr_country.currency_symbol); col_vals[6]=::StringFormat("'%s'", curr_country.url_name); if(!db_obj.InsertSingleRow(col_names, col_vals)) { db_obj.TransactionRollback(); db_obj.Close(); return; } db_obj.FinalizeSqlRequest(); } if(!db_obj.TransactionCommit()) ::PrintFormat("Failed to complete transaction execution, error %d", ::GetLastError()); } //--- print if(db_obj.PrintTable()<0) ::PrintFormat("Failed to print the table \"%s\", error %d", table_name, ::GetLastError()); } }
首先,为了进一步处理该数据表,我们需要使用 CDatabase::SelectTable() 方法选取它。 在此处,我们可以类比 ::PositionSelect() 原生函数如何选取交易持仓,以便进一步的处理。
然后 CDatabase::EmptyTable() 方法初步清除该数据表。
接下来,循环遍历 COUNTRIES 并按数据列填充数据表
- "COUNTRY_ID",
- "COUNTRY_NAME",
- "COUNTRY_CODE",
- "CONTINENT",
- "CURRENCY",
- "CURRENCY_SYMBOL",
- "URL_NAME".
最后一行通过 CDatabase::InsertSingleRow() 方法插入到数据表之中。 填充数据表涉及事务机制。 可在 “通过将事务包装到 DatabaseTransactionBegin()/DatabaseTransactionCommit()” 章节中了解更多信息。
填写三个数据表的结果如下:COUNTRIES 数据表包含 23 个项目,EVENTS 数据表包含 1500 个项目,而 EVENT_VALUES 数据表包含 158696 个项目(图例 11)。
图例 11. 填充 EVENT_VALUES 数据表
现在我们有了数据,我们就可开始形成查询了。
3.2 数据库查询
大体来说,所有数据库查询都可以分为两组:
1) 请求从数据库接收数据;
2) 询请更改数据库中数据。
首先,我们处理从日历数据库获取信息的示例。
3.2.1 按国家/地区获取事件取样数量
我们首先询问数据库当中每个国家/地区有多少的宏观经济事件。 创建以下查询并引用 “EVENTS” 数据表:
SELECT COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num FROM EVENTS GROUP BY COUNTRY_ID
在 MQL5 代码中,此类请求的实现方式如下:
//--- 1) group events number by country id string table_name="EVENTS"; if(db_obj.SelectTable(table_name)) { string col_names_to_select[]= { "COUNTRY_ID AS id", "COUNT(EVENT_ID) AS events_num" }; string gr_names[]= { "COUNTRY_ID" }; if(!db_obj.SelectFromGroupBy(col_names_to_select, gr_names)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
在终端日志中显示按 COUNTRY_ID 和 COUNT(EVENT_ID) 源数据列选取出的以下结果:
sRequest1 (EURUSD,H1) #| id events_num sRequest1 (EURUSD,H1) --+--------------- sRequest1 (EURUSD,H1) 1| 0 7 sRequest1 (EURUSD,H1) 2| 36 85 sRequest1 (EURUSD,H1) 3| 76 55 sRequest1 (EURUSD,H1) 4| 124 74 sRequest1 (EURUSD,H1) 5| 156 40 sRequest1 (EURUSD,H1) 6| 250 43 sRequest1 (EURUSD,H1) 7| 276 62 sRequest1 (EURUSD,H1) 8| 344 26 sRequest1 (EURUSD,H1) 9| 356 57 sRequest1 (EURUSD,H1) 10| 380 52 sRequest1 (EURUSD,H1) 11| 392 124 sRequest1 (EURUSD,H1) 12| 410 36 sRequest1 (EURUSD,H1) 13| 484 47 sRequest1 (EURUSD,H1) 14| 554 82 sRequest1 (EURUSD,H1) 15| 578 47 sRequest1 (EURUSD,H1) 16| 702 27 sRequest1 (EURUSD,H1) 17| 710 54 sRequest1 (EURUSD,H1) 18| 724 39 sRequest1 (EURUSD,H1) 19| 752 59 sRequest1 (EURUSD,H1) 20| 756 40 sRequest1 (EURUSD,H1) 21| 826 115 sRequest1 (EURUSD,H1) 22| 840 247 sRequest1 (EURUSD,H1) 23| 999 82
所选内容看起来不是很好阅读,因为 “id” 数据列是国家/地区 ID,而不是国家/地区名称。 而国家/地区名称可在 “COUNTRIES” 数据表中找到。
若要获取国家/地区名称和国家/地区事件的数量,我们需要创建一个复合查询(查询中内含查询)。
此类复合查询的第一个版本如下所示:
SELECT c.NAME AS country, ev.events_num AS events_number FROM COUNTRIES c JOIN ( SELECT COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num FROM EVENTS GROUP BY COUNTRY_ID ) AS ev ON c.COUNTRY_ID = ev.id
此选项采用我们在开始时创建的查询。 但现在它已成为另一条查询的一部分,从而将其形式更改为子查询的形式。
请求的第二个版本可以以 CTE 的形式实现:
WITH ev_cnt AS ( SELECT COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num FROM EVENTS GROUP BY COUNTRY_ID ) SELECT c.NAME AS country, ev.events_num AS events_number FROM COUNTRIES c INNER JOIN ev_cnt AS ev ON c.COUNTRY_ID = ev.id
在 MQL5 代码中,复合查询实现如下:
//--- 2) group events number by country name using a subquery ::Print("\nGroup events number by country name using a subquery:\n"); string subquery=db_obj.SqlRequest(); string new_sql_request=::StringFormat("SELECT c.NAME AS country," "ev.events_num AS events_number FROM COUNTRIES c " "JOIN(%s) AS ev " "ON c.COUNTRY_ID=ev.id", subquery); if(!db_obj.Select(new_sql_request)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
数据表表达式(CTE) 的实现方式如下:
//--- 3) group events number by country name using CTE ::Print("\nGroup events number by country name using CTE:\n"); new_sql_request=::StringFormat("WITH ev_cnt AS (%s)" "SELECT c.NAME AS country," "ev.events_num AS events_number FROM COUNTRIES c " "INNER JOIN ev_cnt AS ev ON c.COUNTRY_ID=ev.id", subquery); if(!db_obj.Select(new_sql_request)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
这两个选项都会在日志中打印以下查询结果:
sRequest1 (EURUSD,H1) #| country events_number sRequest1 (EURUSD,H1) --+----------------------------- sRequest1 (EURUSD,H1) 1| Worldwide 7 sRequest1 (EURUSD,H1) 2| Australia 85 sRequest1 (EURUSD,H1) 3| Brazil 55 sRequest1 (EURUSD,H1) 4| Canada 74 sRequest1 (EURUSD,H1) 5| China 40 sRequest1 (EURUSD,H1) 6| France 43 sRequest1 (EURUSD,H1) 7| Germany 62 sRequest1 (EURUSD,H1) 8| Hong Kong 26 sRequest1 (EURUSD,H1) 9| India 57 sRequest1 (EURUSD,H1) 10| Italy 52 sRequest1 (EURUSD,H1) 11| Japan 124 sRequest1 (EURUSD,H1) 12| South Korea 36 sRequest1 (EURUSD,H1) 13| Mexico 47 sRequest1 (EURUSD,H1) 14| New Zealand 82 sRequest1 (EURUSD,H1) 15| Norway 47 sRequest1 (EURUSD,H1) 16| Singapore 27 sRequest1 (EURUSD,H1) 17| South Africa 54 sRequest1 (EURUSD,H1) 18| Spain 39 sRequest1 (EURUSD,H1) 19| Sweden 59 sRequest1 (EURUSD,H1) 20| Switzerland 40 sRequest1 (EURUSD,H1) 21| United Kingdom 115 sRequest1 (EURUSD,H1) 22| United States 247 sRequest1 (EURUSD,H1) 23| European Union 82
不难看出,calendar 当中对于美国事件给予了最大的关注; events - 其中有 247 个事件。
我们把任务变得稍微复杂一点,并在取样中添加一个数据列,在其中计算特定国家/地区发生了多少重要事件。 重要性在 “IMPORTANCE” 数据列中定义。 我们仅选取那些具有 “High” 值的事件。
首先,我们使用 EVENTS 数据表。 在此,我们需要创建两个取样。 第一个取样是按国家/地区划分的事件数量。 此任务已在上面完成。 第二个取样是按国家/地区划分的重要事件数量。 最后,我们需要合并两个取样。
查询的 SQL 代码如下所示:
SELECT evn.COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num, imp.high AS imp_events_num FROM EVENTS evn JOIN ( SELECT COUNTRY_ID AS id, COUNT(IMPORTANCE) AS high FROM EVENTS WHERE IMPORTANCE = 'High' GROUP BY COUNTRY_ID ) AS imp ON evn.COUNTRY_ID = imp.id GROUP BY COUNTRY_ID
至于 MQL5 实现,代码如下所示:
//--- 5) important events - ids, events number and important events number ::Print("\nGroup events number and important events number by country id"); subquery=db_obj.SqlRequest(); string new_sql_request4=::StringFormat("SELECT ev.COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num," "imp.high AS imp_events_num " "FROM EVENTS ev JOIN (%s) AS imp " "ON ev.COUNTRY_ID=imp.id GROUP BY COUNTRY_ID", subquery); if(!db_obj.Select(new_sql_request4)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
结果就是,我们得到以下日志项目:
sRequest1 (EURUSD,H1) Group events number and important events number by country id: sRequest1 (EURUSD,H1) sRequest1 (EURUSD,H1) #| id events_num imp_events_num sRequest1 (EURUSD,H1) --+------------------------------ sRequest1 (EURUSD,H1) 1| 0 7 2 sRequest1 (EURUSD,H1) 2| 36 85 5 sRequest1 (EURUSD,H1) 3| 76 55 2 sRequest1 (EURUSD,H1) 4| 124 74 10 sRequest1 (EURUSD,H1) 5| 156 40 5 sRequest1 (EURUSD,H1) 6| 250 43 1 sRequest1 (EURUSD,H1) 7| 276 62 3 sRequest1 (EURUSD,H1) 8| 344 26 1 sRequest1 (EURUSD,H1) 9| 356 57 2 sRequest1 (EURUSD,H1) 10| 392 124 7 sRequest1 (EURUSD,H1) 11| 410 36 2 sRequest1 (EURUSD,H1) 12| 484 47 2 sRequest1 (EURUSD,H1) 13| 554 82 8 sRequest1 (EURUSD,H1) 14| 578 47 2 sRequest1 (EURUSD,H1) 15| 702 27 1 sRequest1 (EURUSD,H1) 16| 710 54 2 sRequest1 (EURUSD,H1) 17| 752 59 1 sRequest1 (EURUSD,H1) 18| 756 40 4 sRequest1 (EURUSD,H1) 19| 826 115 13 sRequest1 (EURUSD,H1) 20| 840 247 20 sRequest1 (EURUSD,H1) 21| 999 82 11
在最终选择中,仍然只是将 “id” 数据列替换为 “country”。
我们再次创建一个复合查询。 事实上,我们受益于早前编写的部分。 最后,按 “imp_events_number” 数据列的数值降序对取样进行排序。 复合查询如下所示:
SELECT c.NAME AS country, ev.events_num AS events_number, ev.imp_events_num AS imp_events_number FROM COUNTRIES c JOIN ( SELECT ev.COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num, imp.high AS imp_events_num FROM EVENTS ev JOIN ( SELECT COUNTRY_ID AS id, COUNT(IMPORTANCE) AS high FROM EVENTS WHERE IMPORTANCE = 'High' GROUP BY COUNTRY_ID ) AS imp ON ev.COUNTRY_ID = imp.id GROUP BY COUNTRY_ID ) AS ev ON c.COUNTRY_ID = ev.id ORDER BY imp_events_number DESC
在 MQL5 代码中,请求实现如下:
//--- 6) important events - countries, events number and important events number ::Print("\nGroup events number and important events number by country:\n"); subquery=db_obj.SqlRequest(); string new_sql_request5=::StringFormat("SELECT c.NAME AS country," "ev.events_num AS events_number," "ev.imp_events_num AS imp_events_number " "FROM COUNTRIES c " "JOIN(%s) AS ev " "ON c.COUNTRY_ID=ev.id " "ORDER BY imp_events_number DESC", subquery); if(!db_obj.Select(new_sql_request5)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
现在我们在日志中得到所需的取样:
sRequest1 (EURUSD,H1) Group events number and important events number by country: sRequest1 (EURUSD,H1) sRequest1 (EURUSD,H1) #| country events_number imp_events_number sRequest1 (EURUSD,H1) --+----------------------------------------------- sRequest1 (EURUSD,H1) 1| United States 247 20 sRequest1 (EURUSD,H1) 2| United Kingdom 115 13 sRequest1 (EURUSD,H1) 3| European Union 82 11 sRequest1 (EURUSD,H1) 4| Canada 74 10 sRequest1 (EURUSD,H1) 5| New Zealand 82 8 sRequest1 (EURUSD,H1) 6| Japan 124 7 sRequest1 (EURUSD,H1) 7| Australia 85 5 sRequest1 (EURUSD,H1) 8| China 40 5 sRequest1 (EURUSD,H1) 9| Switzerland 40 4 sRequest1 (EURUSD,H1) 10| Germany 62 3 sRequest1 (EURUSD,H1) 11| Worldwide 7 2 sRequest1 (EURUSD,H1) 12| Brazil 55 2 sRequest1 (EURUSD,H1) 13| India 57 2 sRequest1 (EURUSD,H1) 14| South Korea 36 2 sRequest1 (EURUSD,H1) 15| Mexico 47 2 sRequest1 (EURUSD,H1) 16| Norway 47 2 sRequest1 (EURUSD,H1) 17| South Africa 54 2 sRequest1 (EURUSD,H1) 18| France 43 1 sRequest1 (EURUSD,H1) 19| Hong Kong 26 1 sRequest1 (EURUSD,H1) 20| Singapore 27 1 sRequest1 (EURUSD,H1) 21| Sweden 59 1
从取样中可以看出,美国有最多的重要事件 — 20。 英国排名第二 — 13。 第三名是欧盟 — 11。 日本排名第六 — 7。
我们使用查询来查找那些根本没有重要事件的国家/地区。 为此,我们需要找到两个取样之间的差异。 第一个取样将包括从 COUNTRIES 数据表中获取的所有国家/地区,而第二个取样当中 — 包含上一个复合查询中的国家/地区的数据列。
在这种情况下,SQL 代码将如下所示:
SELECT NAME FROM COUNTRIES EXCEPT SELECT country FROM ( SELECT c.NAME AS country, ev.events_num AS events_number, ev.imp_events_num AS imp_events_number FROM COUNTRIES c JOIN ( SELECT ev.COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num, imp.high AS imp_events_num FROM EVENTS ev JOIN ( SELECT COUNTRY_ID AS id, COUNT(IMPORTANCE) AS high FROM EVENTS WHERE IMPORTANCE = 'High' GROUP BY COUNTRY_ID ) AS imp ON ev.COUNTRY_ID = imp.id GROUP BY COUNTRY_ID ) AS ev ON c.COUNTRY_ID = ev.id )
MQL5 代码看起来更简单,因为事实上我们受益于前一个查询,使之成为我们新的子查询。
//--- 7) countries having no important events ::Print("\nCountries having no important events:\n"); string last_request=db_obj.SqlRequest(); string new_sql_request6=::StringFormat("SELECT NAME FROM COUNTRIES " "EXCEPT SELECT country FROM (%s)", last_request); if(!db_obj.Select(new_sql_request6)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
代码执行完成后,我们将在日志中收到以下条目:
sRequest1 (EURUSD,H1) Countries having no important events: sRequest1 (EURUSD,H1) sRequest1 (EURUSD,H1) #| NAME sRequest1 (EURUSD,H1) -+------ sRequest1 (EURUSD,H1) 1| Italy sRequest1 (EURUSD,H1) 2| Spain
由此,在所有国家中,只有意大利和西班牙没有重要事件。 MQL5 中有关国家/地区事件的请求在 sRequest1.mq5 脚本中执行。
3.2.2 按国家/地区得到的 GDP 值取样
在此示例中,我们将按各个国家/地区进行数据库查询,以便选取各地的 GDP 值。 作为 GDP 的值,我们将采用参数 “Gross domestic product (GDP) q/q”(第三季度)。
将会有若干取样,故查询将是复合的。
首先,我们找出哪些经济体有季度 GDP 指标。
SQL 代码如下所示:
SELECT COUNTRY_ID, EVENT_ID FROM EVENTS WHERE (NAME LIKE 'GDP q/q' AND SECTOR = 'Gross Domestic Product')
MQL5 实现如下所示(sRequest2.mq5 脚本):
//--- 1) countries by id where the indicator '%GDP q/q%' exists string col_names[]= {"COUNTRY_ID", "EVENT_ID"}; string where_condition="(NAME LIKE 'GDP q/q' AND SECTOR='Gross Domestic Product')"; if(!db_obj.SelectFromWhere(col_names, where_condition)) { db_obj.Close(); return; } ::Print("\nCountries by id where the indicator 'GDP q/q' exists:\n"); //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
以下是执行查询后日志中的打印输出:
sRequest2 (EURUSD,H1) Countries by id where the indicator 'GDP q/q' exists: sRequest2 (EURUSD,H1) sRequest2 (EURUSD,H1) #| COUNTRY_ID EVENT_ID sRequest2 (EURUSD,H1) --+--------------------- sRequest2 (EURUSD,H1) 1| 554 554010024 sRequest2 (EURUSD,H1) 2| 999 999030016 sRequest2 (EURUSD,H1) 3| 392 392010001 sRequest2 (EURUSD,H1) 4| 124 124010022 sRequest2 (EURUSD,H1) 5| 36 36010019 sRequest2 (EURUSD,H1) 6| 156 156010004 sRequest2 (EURUSD,H1) 7| 380 380010020 sRequest2 (EURUSD,H1) 8| 702 702010004 sRequest2 (EURUSD,H1) 9| 276 276010008 sRequest2 (EURUSD,H1) 10| 250 250010005 sRequest2 (EURUSD,H1) 11| 76 76010010 sRequest2 (EURUSD,H1) 12| 484 484020016 sRequest2 (EURUSD,H1) 13| 710 710060009 sRequest2 (EURUSD,H1) 14| 344 344020002 sRequest2 (EURUSD,H1) 15| 578 578020012 sRequest2 (EURUSD,H1) 16| 840 840010007 sRequest2 (EURUSD,H1) 17| 826 826010037 sRequest2 (EURUSD,H1) 18| 756 756040001 sRequest2 (EURUSD,H1) 19| 410 410010011 sRequest2 (EURUSD,H1) 20| 724 724010005 sRequest2 (EURUSD,H1) 21| 752 752010019
正如我们所见,21 个国家存在所需的指标。 该指标在印度不用作全球指标(“Worldwide”)。
现在我们需要获取第 3 季度指标值的取样,并将其与事件 ID 的第一个选择相关联。
SQL 查询如下所示:
SELECT evs.COUNTRY_ID AS country_id, evals.EVENT_ID AS event_id, evals.VALUE_ID AS value_id, evals.PERIOD AS period, evals.TIME AS time, evals.ACTUAL AS actual FROM EVENT_VALUES evals JOIN ( SELECT COUNTRY_ID, EVENT_ID FROM EVENTS WHERE (NAME LIKE 'GDP q/q' AND SECTOR = 'Gross Domestic Product') ) AS evs ON evals.event_id = evs.EVENT_ID WHERE (period = '2022.07.01 00:00' )
至于 MQL5 代码,复合查询实现如下:
//--- 2) 'GDP y/y' event and last values string subquery=db_obj.SqlRequest(); string new_sql_request1=::StringFormat("SELECT evs.COUNTRY_ID AS country_id," "evals.EVENT_ID AS event_id," "evals.VALUE_ID AS value_id," "evals.PERIOD AS period," "evals.TIME AS time," "evals.ACTUAL AS actual " "FROM EVENT_VALUES evals " "JOIN(%s) AS evs ON evals.event_id = evs.event_id " " WHERE (period = \'2022.07.01 00:00\')", subquery); if(!db_obj.Select(new_sql_request1)) { db_obj.Close(); return; } ::Print("\n'GDP y/y' event and last values:\n"); //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
执行后,日志中将显示以下行:
sRequest2 (EURUSD,H1) 'GDP q/q' event and last values: sRequest2 (EURUSD,H1) sRequest2 (EURUSD,H1) #| country_id event_id value_id period time actual sRequest2 (EURUSD,H1) --+----------------------------------------------------------------------- sRequest2 (EURUSD,H1) 1| 554 554010024 168293 2022.07.01 00:00 2022.12.14 23:45 2.0 sRequest2 (EURUSD,H1) 2| 999 999030016 158836 2022.07.01 00:00 2022.10.31 12:00 0.2 sRequest2 (EURUSD,H1) 3| 999 999030016 158837 2022.07.01 00:00 2022.11.15 12:00 0.2 sRequest2 (EURUSD,H1) 4| 999 999030016 158838 2022.07.01 00:00 2022.12.07 12:00 0.3 sRequest2 (EURUSD,H1) 5| 392 392010001 165181 2022.07.01 00:00 2022.11.15 01:50 -0.3 sRequest2 (EURUSD,H1) 6| 392 392010001 165182 2022.07.01 00:00 2022.12.08 01:50 -0.2 sRequest2 (EURUSD,H1) 7| 124 124010022 161963 2022.07.01 00:00 2022.11.29 15:30 0.7 sRequest2 (EURUSD,H1) 8| 36 36010019 173679 2022.07.01 00:00 2022.12.07 02:30 0.6 sRequest2 (EURUSD,H1) 9| 156 156010004 172459 2022.07.01 00:00 2022.10.24 04:00 3.9 sRequest2 (EURUSD,H1) 10| 380 380010020 162296 2022.07.01 00:00 2022.10.31 11:00 0.5 sRequest2 (EURUSD,H1) 11| 380 380010020 162297 2022.07.01 00:00 2022.11.30 11:00 0.5 sRequest2 (EURUSD,H1) 12| 702 702010004 167581 2022.07.01 00:00 2022.10.14 02:00 1.5 sRequest2 (EURUSD,H1) 13| 702 702010004 174527 2022.07.01 00:00 2022.11.23 02:00 1.1 sRequest2 (EURUSD,H1) 14| 276 276010008 172410 2022.07.01 00:00 2022.10.28 10:00 0.3 sRequest2 (EURUSD,H1) 15| 276 276010008 157759 2022.07.01 00:00 2022.11.25 09:00 0.4 sRequest2 (EURUSD,H1) 16| 250 250010005 169062 2022.07.01 00:00 2022.10.28 07:30 0.2 sRequest2 (EURUSD,H1) 17| 250 250010005 169389 2022.07.01 00:00 2022.11.30 09:45 0.2 sRequest2 (EURUSD,H1) 18| 76 76010010 173825 2022.07.01 00:00 2022.12.01 14:00 0.4 sRequest2 (EURUSD,H1) 19| 484 484020016 166108 2022.07.01 00:00 2022.10.31 14:00 1.0 sRequest2 (EURUSD,H1) 20| 484 484020016 166109 2022.07.01 00:00 2022.11.25 14:00 0.9 sRequest2 (EURUSD,H1) 21| 710 710060009 175234 2022.07.01 00:00 2022.12.06 11:30 1.6 sRequest2 (EURUSD,H1) 22| 344 344020002 155337 2022.07.01 00:00 2022.10.31 10:30 -2.6 sRequest2 (EURUSD,H1) 23| 344 344020002 155338 2022.07.01 00:00 2022.11.11 10:30 -2.6 sRequest2 (EURUSD,H1) 24| 578 578020012 172320 2022.07.01 00:00 2022.11.18 09:00 1.5 sRequest2 (EURUSD,H1) 25| 840 840010007 163417 2022.07.01 00:00 2022.10.27 14:30 2.6 sRequest2 (EURUSD,H1) 26| 840 840010007 163418 2022.07.01 00:00 2022.11.30 15:30 2.9 sRequest2 (EURUSD,H1) 27| 840 840010007 163419 2022.07.01 00:00 2022.12.22 15:30 3.2 sRequest2 (EURUSD,H1) 28| 826 826010037 157174 2022.07.01 00:00 2022.11.11 09:00 -0.2 sRequest2 (EURUSD,H1) 29| 826 826010037 157175 2022.07.01 00:00 2022.12.22 09:00 -0.3 sRequest2 (EURUSD,H1) 30| 756 756040001 159276 2022.07.01 00:00 2022.11.29 10:00 0.2 sRequest2 (EURUSD,H1) 31| 410 410010011 161626 2022.07.01 00:00 2022.10.27 01:00 0.3 sRequest2 (EURUSD,H1) 32| 410 410010011 161627 2022.07.01 00:00 2022.12.01 01:00 0.3 sRequest2 (EURUSD,H1) 33| 724 724010005 159814 2022.07.01 00:00 2022.10.28 09:00 0.2 sRequest2 (EURUSD,H1) 34| 724 724010005 159815 2022.07.01 00:00 2022.12.23 10:00 0.1 sRequest2 (EURUSD,H1) 35| 752 752010019 170359 2022.07.01 00:00 2022.10.28 08:00 0.7 sRequest2 (EURUSD,H1) 36| 752 752010019 171381 2022.07.01 00:00 2022.11.29 09:00 0.6
很容易看出,取样中有多个值,对应某些具有相同 event_id 的事件。 例如,项目 2-4 引用 EU 参数。 GDP 已经需依据若干读数估算,因此有几个参数值。 结果就是,最终取样包含 36 个项目,这显然超过了计算参数的国家/地区数量。
如果我们仅需要获取给定事件的最新值来制作取样,那么我们需要在查询中添加对结果进行分组和排序的功能。 然后我们就得到以下复合 SQL 查询:
SELECT evs.COUNTRY_ID AS country_id, evals.EVENT_ID AS event_id, evals.VALUE_ID AS value_id, evals.PERIOD AS period, evals.TIME AS time, evals.ACTUAL AS actual FROM EVENT_VALUES evals JOIN ( SELECT COUNTRY_ID, EVENT_ID FROM EVENTS WHERE (NAME LIKE 'GDP q/q' AND SECTOR = 'Gross Domestic Product') ) AS evs ON evals.event_id = evs.EVENT_ID WHERE (period = '2022.07.01 00:00' ) GROUP BY evals.event_id HAVING MAX(value_id)
项目将按 “event_id” 数据列(字段)分组。 如果有多个项目,则按 “value_id” 数据列(字段)的最大值排序。 故此,在这种情况下,将仅选出了欧盟的三个项目之一:
country_id | event_id | value_id | period | time | actual |
---|---|---|---|---|---|
999 | 999030016 | 158838 | 2022.07.01 00:00 | 2022.12.07 12:00 | 0.3 |
结果就是,日志中将出现以下项目:
sRequest2 (EURUSD,H1) 'GDP q/q' event and grouped last values: sRequest2 (EURUSD,H1) sRequest2 (EURUSD,H1) #| country_id event_id value_id period time actual sRequest2 (EURUSD,H1) --+----------------------------------------------------------------------- sRequest2 (EURUSD,H1) 1| 36 36010019 173679 2022.07.01 00:00 2022.12.07 02:30 0.6 sRequest2 (EURUSD,H1) 2| 76 76010010 173825 2022.07.01 00:00 2022.12.01 14:00 0.4 sRequest2 (EURUSD,H1) 3| 124 124010022 161963 2022.07.01 00:00 2022.11.29 15:30 0.7 sRequest2 (EURUSD,H1) 4| 156 156010004 172459 2022.07.01 00:00 2022.10.24 04:00 3.9 sRequest2 (EURUSD,H1) 5| 250 250010005 169389 2022.07.01 00:00 2022.11.30 09:45 0.2 sRequest2 (EURUSD,H1) 6| 276 276010008 172410 2022.07.01 00:00 2022.10.28 10:00 0.3 sRequest2 (EURUSD,H1) 7| 344 344020002 155338 2022.07.01 00:00 2022.11.11 10:30 -2.6 sRequest2 (EURUSD,H1) 8| 380 380010020 162297 2022.07.01 00:00 2022.11.30 11:00 0.5 sRequest2 (EURUSD,H1) 9| 392 392010001 165182 2022.07.01 00:00 2022.12.08 01:50 -0.2 sRequest2 (EURUSD,H1) 10| 410 410010011 161627 2022.07.01 00:00 2022.12.01 01:00 0.3 sRequest2 (EURUSD,H1) 11| 484 484020016 166109 2022.07.01 00:00 2022.11.25 14:00 0.9 sRequest2 (EURUSD,H1) 12| 554 554010024 168293 2022.07.01 00:00 2022.12.14 23:45 2.0 sRequest2 (EURUSD,H1) 13| 578 578020012 172320 2022.07.01 00:00 2022.11.18 09:00 1.5 sRequest2 (EURUSD,H1) 14| 702 702010004 174527 2022.07.01 00:00 2022.11.23 02:00 1.1 sRequest2 (EURUSD,H1) 15| 710 710060009 175234 2022.07.01 00:00 2022.12.06 11:30 1.6 sRequest2 (EURUSD,H1) 16| 724 724010005 159815 2022.07.01 00:00 2022.12.23 10:00 0.1 sRequest2 (EURUSD,H1) 17| 752 752010019 171381 2022.07.01 00:00 2022.11.29 09:00 0.6 sRequest2 (EURUSD,H1) 18| 756 756040001 159276 2022.07.01 00:00 2022.11.29 10:00 0.2 sRequest2 (EURUSD,H1) 19| 826 826010037 157175 2022.07.01 00:00 2022.12.22 09:00 -0.3 sRequest2 (EURUSD,H1) 20| 840 840010007 163419 2022.07.01 00:00 2022.12.22 15:30 3.2 sRequest2 (EURUSD,H1) 21| 999 999030016 158838 2022.07.01 00:00 2022.12.07 12:00 0.3
现在取样中有 21 个项目。 最后,我们需要将国家/地区代码替换为其名称。 我们将之前的 SLQ 查询更改为以下查询:
SELECT c.NAME AS country, ev_evals.event_id AS event_id, ev_evals.value_id AS value_id, ev_evals.period AS period, ev_evals.TIME AS time, ev_evals.ACTUAL AS actual FROM COUNTRIES c JOIN ( SELECT evs.COUNTRY_ID AS country_id, evals.EVENT_ID AS event_id, evals.VALUE_ID AS value_id, evals.PERIOD AS period, evals.TIME AS time, evals.ACTUAL AS actual FROM EVENT_VALUES evals JOIN ( SELECT COUNTRY_ID, EVENT_ID FROM EVENTS WHERE (NAME LIKE 'GDP q/q' AND SECTOR = 'Gross Domestic Product') ) AS evs ON evals.event_id = evs.EVENT_ID WHERE (period = '2022.07.01 00:00') GROUP BY evals.event_id HAVING MAX(value_id) ) AS ev_evals ON c.COUNTRY_ID = ev_evals.country_id
依此方式,以 MQL5 实现以下复合查询:
//--- 4) 'GDP q/q' event and grouped last values with country names subquery=db_obj.SqlRequest(); string new_sql_request3=::StringFormat("SELECT c.NAME AS country," "ev_evals.event_id AS event_id," "ev_evals.value_id AS value_id," "ev_evals.period AS period," "ev_evals.TIME AS time," "ev_evals.ACTUAL AS actual " "FROM COUNTRIES c JOIN (%s) " "AS ev_evals ON c.COUNTRY_ID = ev_evals.country_id", subquery); if(!db_obj.Select(new_sql_request3)) { db_obj.Close(); return; } ::Print("\n'GDP q/q' event and grouped last values with country names:\n"); //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
在流水账中打印所需的取样:
sRequest2 (EURUSD,H1) 'GDP q/q' event and grouped last values with country names: sRequest2 (EURUSD,H1) sRequest2 (EURUSD,H1) #| country event_id value_id period time actual sRequest2 (EURUSD,H1) --+--------------------------------------------------------------------------- sRequest2 (EURUSD,H1) 1| Australia 36010019 173679 2022.07.01 00:00 2022.12.07 02:30 0.6 sRequest2 (EURUSD,H1) 2| Brazil 76010010 173825 2022.07.01 00:00 2022.12.01 14:00 0.4 sRequest2 (EURUSD,H1) 3| Canada 124010022 161963 2022.07.01 00:00 2022.11.29 15:30 0.7 sRequest2 (EURUSD,H1) 4| China 156010004 172459 2022.07.01 00:00 2022.10.24 04:00 3.9 sRequest2 (EURUSD,H1) 5| France 250010005 169389 2022.07.01 00:00 2022.11.30 09:45 0.2 sRequest2 (EURUSD,H1) 6| Germany 276010008 172410 2022.07.01 00:00 2022.10.28 10:00 0.3 sRequest2 (EURUSD,H1) 7| Hong Kong 344020002 155338 2022.07.01 00:00 2022.11.11 10:30 -2.6 sRequest2 (EURUSD,H1) 8| Italy 380010020 162297 2022.07.01 00:00 2022.11.30 11:00 0.5 sRequest2 (EURUSD,H1) 9| Japan 392010001 165182 2022.07.01 00:00 2022.12.08 01:50 -0.2 sRequest2 (EURUSD,H1) 10| South Korea 410010011 161627 2022.07.01 00:00 2022.12.01 01:00 0.3 sRequest2 (EURUSD,H1) 11| Mexico 484020016 166109 2022.07.01 00:00 2022.11.25 14:00 0.9 sRequest2 (EURUSD,H1) 12| New Zealand 554010024 168293 2022.07.01 00:00 2022.12.14 23:45 2.0 sRequest2 (EURUSD,H1) 13| Norway 578020012 172320 2022.07.01 00:00 2022.11.18 09:00 1.5 sRequest2 (EURUSD,H1) 14| Singapore 702010004 174527 2022.07.01 00:00 2022.11.23 02:00 1.1 sRequest2 (EURUSD,H1) 15| South Africa 710060009 175234 2022.07.01 00:00 2022.12.06 11:30 1.6 sRequest2 (EURUSD,H1) 16| Spain 724010005 159815 2022.07.01 00:00 2022.12.23 10:00 0.1 sRequest2 (EURUSD,H1) 17| Sweden 752010019 171381 2022.07.01 00:00 2022.11.29 09:00 0.6 sRequest2 (EURUSD,H1) 18| Switzerland 756040001 159276 2022.07.01 00:00 2022.11.29 10:00 0.2 sRequest2 (EURUSD,H1) 19| United Kingdom 826010037 157175 2022.07.01 00:00 2022.12.22 09:00 -0.3 sRequest2 (EURUSD,H1) 20| United States 840010007 163419 2022.07.01 00:00 2022.12.22 15:30 3.2 sRequest2 (EURUSD,H1) 21| European Union 999030016 158838 2022.07.01 00:00 2022.12.07 12:00 0.3
尽管问题已通过若干种方式解决,但将一个查询包含在另一个查询中的可能性令它变得容易得多。
结束语
我希望这篇文章能引起那些参考宏观经济数据来创建策略的交易者和开发人员的兴趣。 我也敢谏言,很可能没有什么宏观经济指标可以让您制定一个优秀的策略。 然而,它们也许可作为原始神经网络数据的补充。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/11977
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.


丹尼斯
多好的文章啊。我现在还在仔细阅读。每个脚本对我来说都很有效。我得到了和你一样的输出结果,但有一个例外;
第 2.18 节,您显示了输出结果。这让我有些困惑,因为在之前的第 2.13 节中,您删除了亚洲区的县。
我很感谢您花了这么多时间。