Lọc sự kiện theo nhiều điều kiện
Như chúng ta đã biết từ các phần trước của chương này, API MQL5 cho phép bạn yêu cầu các sự kiện lịch dựa trên một số điều kiện:
- theo quốc gia (
CalendarValueHistory
,CalendarValueLast
) - theo tần suất (
CalendarValueHistory
,CalendarValueLast
) - theo ID loại sự kiện (
CalendarValueHistoryByEvent
,CalendarValueLastByEvent
) - theo khoảng thời gian (
CalendarValueHistory
,CalendarValueHistoryByEvent
) - theo các thay đổi kể từ lần thăm dò lịch trước đó (
CalendarValueLast
,CalendarValueLastByEvent
) - theo ID của tin tức cụ thể (
CalendarValueById
)
Điều này có thể được tóm tắt trong bảng sau về các hàm (trong tất cả các hàm CalendarValue
, chỉ có CalendarValueById
để lấy một giá trị cụ thể là không có ở đây).
Điều kiện | Khoảng thời gian | Thay đổi cuối cùng |
---|---|---|
Quốc gia | CalendarValueHistory | CalendarValueLast |
Tiền tệ | CalendarValueHistory | CalendarValueLast |
Sự kiện | CalendarValueHistoryByEvent | CalendarValueLastByEvent |
Bộ công cụ như vậy bao phủ các kịch bản phân tích lịch phổ biến chính, nhưng không phải tất cả. Do đó, trong thực tế, thường cần triển khai các cơ chế lọc tùy chỉnh trong MQL5, bao gồm, đặc biệt, các yêu cầu sự kiện theo:
- nhiều quốc gia
- nhiều tiền tệ
- nhiều loại sự kiện
- giá trị của các thuộc tính bất kỳ của sự kiện (mức độ quan trọng, lĩnh vực kinh tế, kỳ báo cáo, loại, sự hiện diện của dự báo, tác động ước tính đến tỷ giá, chuỗi con trong tên sự kiện, v.v.)
Để giải quyết những vấn đề này, chúng ta đã tạo ra lớp CalendarFilter
(CalendarFilter.mqh
).
Do đặc thù của các hàm API tích hợp, một số thuộc tính tin tức được ưu tiên cao hơn những thuộc tính còn lại. Điều này bao gồm quốc gia, tiền tệ và khoảng ngày. Chúng có thể được chỉ định trong hàm tạo của lớp, và sau đó thuộc tính tương ứng không thể thay đổi động trong các điều kiện lọc.
Điều này là do lớp lọc sau này sẽ được mở rộng với khả năng lưu trữ tin tức để cho phép đọc từ bộ kiểm tra, và các điều kiện ban đầu của hàm tạo thực sự xác định ngữ cảnh lưu trữ mà trong đó việc lọc tiếp theo là khả thi. Ví dụ, nếu chúng ta chỉ định mã quốc gia "EU" khi tạo một đối tượng, thì rõ ràng không có ý nghĩa gì khi yêu cầu tin tức về Hoa Kỳ hoặc Brazil thông qua nó. Tương tự với khoảng ngày: việc chỉ định nó trong hàm tạo sẽ khiến không thể nhận tin tức ngoài khoảng đó.
Chúng ta cũng có thể tạo một đối tượng mà không có điều kiện ban đầu (vì tất cả các tham số của hàm tạo là tùy chọn), và sau đó nó sẽ có thể lưu trữ và lọc tin tức trên toàn bộ cơ sở dữ liệu lịch (tính đến thời điểm lưu).
Ngoài ra, vì các quốc gia và tiền tệ hiện nay hầu như được hiển thị một cách duy nhất (ngoại trừ Liên minh Châu Âu và EUR), chúng được truyền vào hàm tạo thông qua một tham số duy nhất context
: nếu bạn chỉ định một chuỗi có độ dài 2 ký tự, mã quốc gia (hoặc sự kết hợp của các quốc gia) được ngụ ý, và nếu độ dài là 3 ký tự, mã tiền tệ được ngụ ý. Đối với các mã "EU" và "EUR", khu vực đồng euro là một tập hợp con của "EU" (trong các quốc gia có hiệp ước chính thức). Trong các trường hợp đặc biệt, nơi các quốc gia EU không thuộc khu vực đồng euro được quan tâm, chúng cũng có thể được mô tả bằng ngữ cảnh "EU". Nếu cần, các điều kiện hẹp hơn cho tin tức về tiền tệ của các quốc gia này (BGN, HUF, DKK, ISK, PLN, RON, HRK, CZK, SEK) có thể được thêm vào bộ lọc một cách động bằng các phương thức mà chúng ta sẽ trình bày sau. Tuy nhiên, do tính chất ngoại lai, không có đảm bảo rằng những tin tức như vậy sẽ xuất hiện trong lịch.
Hãy bắt đầu nghiên cứu lớp này.
class CalendarFilter
{
protected:
// điều kiện ban đầu (tùy chọn) được đặt trong hàm tạo, bất biến
string context; // quốc gia và tiền tệ
datetime from, to; // khoảng ngày
bool fixedDates; // nếu 'from'/'to' được truyền vào hàm tạo, chúng không thể thay đổi
// bộ chọn chuyên dụng (quốc gia/tiền tệ/định danh loại sự kiện)
string country[], currency[];
ulong ids[];
MqlCalendarValue values[]; // kết quả đã lọc
virtual void init()
{
fixedDates = from != 0 || to != 0;
if(StringLen(context) == 3)
{
PUSH(currency, context);
}
else
{
// ngay cả khi context là NULL, chúng ta sử dụng nó để thăm dò toàn bộ cơ sở lịch
PUSH(country, context);
}
}
...
public:
CalendarFilter(const string _context = NULL,
const datetime _from = 0, const datetime _to = 0):
context(_context), from(_from), to(_to)
{
init();
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Hai mảng được phân bổ cho các quốc gia và tiền tệ: country
và currency
. Nếu chúng không được điền từ context
trong quá trình tạo đối tượng, thì chương trình MQL sẽ có thể thêm điều kiện cho nhiều quốc gia hoặc tiền tệ để thực hiện truy vấn tin tức kết hợp trên chúng.
Để lưu trữ các điều kiện trên tất cả các thuộc tính tin tức khác, mảng selectors
được mô tả trong đối tượng CalendarFilter
, với chiều thứ hai bằng 3. Có thể nói rằng đây là một dạng bảng mà mỗi hàng có 3 cột.
long selectors[][3]; // [0] - thuộc tính, [1] - giá trị, [2] - điều kiện
Tại chỉ số 0, các định danh thuộc tính tin tức sẽ được đặt. Vì các thuộc tính được trải rộng trên ba bảng cơ sở (MqlCalendarCountry
, MqlCalendarEvent
, MqlCalendarValue
), chúng được mô tả bằng các phần tử của liệt kê tổng quát ENUM_CALENDAR_PROPERTY
(CalendarDefines.mqh
).
enum ENUM_CALENDAR_PROPERTY
{ // +/- nghĩa là hỗ trợ lọc trường
CALENDAR_PROPERTY_COUNTRY_ID, // -ulong
CALENDAR_PROPERTY_COUNTRY_NAME, // -string
CALENDAR_PROPERTY_COUNTRY_CODE, // +string (2 ký tự)
CALENDAR_PROPERTY_COUNTRY_CURRENCY, // +string (3 ký tự)
CALENDAR_PROPERTY_COUNTRY_GLYPH, // -string (1 ký tự)
CALENDAR_PROPERTY_COUNTRY_URL, // -string
CALENDAR_PROPERTY_EVENT_ID, // +ulong (ID loại sự kiện)
CALENDAR_PROPERTY_EVENT_TYPE, // +ENUM_CALENDAR_EVENT_TYPE
CALENDAR_PROPERTY_EVENT_SECTOR, // +ENUM_CALENDAR_EVENT_SECTOR
CALENDAR_PROPERTY_EVENT_FREQUENCY, // +ENUM_CALENDAR_EVENT_FREQUENCY
CALENDAR_PROPERTY_EVENT_TIMEMODE, // +ENUM_CALENDAR_EVENT_TIMEMODE
CALENDAR_PROPERTY_EVENT_UNIT, // +ENUM_CALENDAR_EVENT_UNIT
CALENDAR_PROPERTY_EVENT_IMPORTANCE, // +ENUM_CALENDAR_EVENT_IMPORTANCE
CALENDAR_PROPERTY_EVENT_MULTIPLIER, // +ENUM_CALENDAR_EVENT_MULTIPLIER
CALENDAR_PROPERTY_EVENT_DIGITS, // -uint
CALENDAR_PROPERTY_EVENT_SOURCE, // +string ("http[s]://")
CALENDAR_PROPERTY_EVENT_CODE, // -string
CALENDAR_PROPERTY_EVENT_NAME, // +string (4+ ký tự hoặc ký tự đại diện '*')
CALENDAR_PROPERTY_RECORD_ID, // -ulong
CALENDAR_PROPERTY_RECORD_TIME, // +datetime
CALENDAR_PROPERTY_RECORD_PERIOD, // +datetime (như long)
CALENDAR_PROPERTY_RECORD_REVISION, // +int
CALENDAR_PROPERTY_RECORD_ACTUAL, // +long
CALENDAR_PROPERTY_RECORD_PREVIOUS, // +long
CALENDAR_PROPERTY_RECORD_REVISED, // +long
CALENDAR_PROPERTY_RECORD_FORECAST, // +long
CALENDAR_PROPERTY_RECORD_IMPACT, // +ENUM_CALENDAR_EVENT_IMPACT
CALENDAR_PROPERTY_RECORD_PREVISED, // +không chuẩn (previous hoặc revised nếu có)
CALENDAR_PROPERTY_CHANGE_ID, // -ulong (dành sẵn)
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Chỉ số 1 sẽ lưu trữ các giá trị để so sánh với chúng trong các điều kiện chọn bản ghi tin tức. Ví dụ, nếu bạn muốn đặt bộ lọc theo lĩnh vực kinh tế, thì chúng ta ghi CALENDAR_PROPERTY_EVENT_SECTOR
vào selectors[i][0]
và một trong các giá trị của liệt kê chuẩn ENUM_CALENDAR_EVENT_SECTOR
vào selectors[i][1]
.
Cuối cùng, cột cuối cùng (dưới chỉ số thứ 2) được dành sẵn cho thao tác so sánh giá trị bộ chọn với giá trị thuộc tính trong tin tức: tất cả các thao tác được hỗ trợ được tóm tắt trong liệt kê IS
.
enum IS
{
EQUAL,
NOT_EQUAL,
GREATER,
LESS,
OR_EQUAL,
...
};
2
3
4
5
6
7
8
9
Chúng ta đã thấy cách tiếp cận tương tự trong TradeFilter.mqh
. Do đó, chúng ta sẽ có thể sắp xếp các điều kiện không chỉ cho sự bằng nhau của giá trị mà còn cho sự không bằng nhau hoặc quan hệ lớn hơn/nhỏ hơn. Ví dụ, dễ dàng tưởng tượng một bộ lọc trên trường CALENDAR_PROPERTY_EVENT_IMPORTANCE
, mà phải GREATER
hơn CALENDAR_IMPORTANCE_LOW
(đây là một phần tử của liệt kê chuẩn ENUM_CALENDAR_EVENT_IMPORTANCE
), có nghĩa là chọn tin tức có tầm quan trọng trung bình và cao.
Liệt kê tiếp theo được định nghĩa đặc biệt cho lịch là ENUM_CALENDAR_SCOPE
. Vì việc lọc lịch thường liên quan đến khoảng thời gian, những khoảng được yêu cầu nhiều nhất được liệt kê ở đây.
#define DAY_LONG (60 * 60 * 24)
#define WEEK_LONG (DAY_LONG * 7)
#define MONTH_LONG (DAY_LONG * 30)
#define QUARTER_LONG (MONTH_LONG * 3)
#define YEAR_LONG (MONTH_LONG * 12)
enum ENUM_CALENDAR_SCOPE
{
SCOPE_DAY = DAY_LONG, // Ngày
SCOPE_WEEK = WEEK_LONG, // Tuần
SCOPE_MONTH = MONTH_LONG, // Tháng
SCOPE_QUARTER = QUARTER_LONG, // Quý
SCOPE_YEAR = YEAR_LONG, // Năm
};
2
3
4
5
6
7
8
9
10
11
12
13
14
Tất cả các liệt kê được đặt trong một tệp tiêu đề riêng CalendarDefines.mqh
.
Nhưng hãy quay lại lớp CalendarFilter
. Kiểu của mảng selectors
là long
, phù hợp để lưu trữ giá trị của hầu hết các loại liên quan: liệt kê, ngày và giờ, định danh, số nguyên, và thậm chí cả giá trị chỉ số kinh tế vì chúng được lưu trong lịch dưới dạng số long
(tính bằng phần triệu của giá trị thực). Tuy nhiên, phải làm gì với các thuộc tính chuỗi?
Vấn đề này được giải quyết bằng cách sử dụng mảng chuỗi stringCache
, nơi tất cả các chuỗi được đề cập trong điều kiện lọc sẽ được thêm vào.
class CalendarFilter
{
protected:
...
string stringCache[]; // bộ nhớ đệm của tất cả các hàng trong 'selectors'
...
2
3
4
5
6
Sau đó, thay vì giá trị chuỗi trong selectors[i][1]
, chúng ta có thể dễ dàng lưu chỉ số của một phần tử trong mảng stringCache
.
Để điền mảng selectors
với các điều kiện lọc, có một số phương thức let
được cung cấp, đặc biệt, cho các liệt kê:
class CalendarFilter
{
...
public:
// tất cả các trường kiểu liệt kê được xử lý ở đây
template<typename E>
CalendarFilter *let(const E e, const IS c = EQUAL)
{
const int n = EXPAND(selectors);
selectors[n][0] = resolve(e); // theo kiểu E, trả về phần tử ENUM_CALENDAR_PROPERTY
selectors[n][1] = e;
selectors[n][2] = c;
return &this;
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Cho các giá trị thực tế của chỉ số:
// các trường sau được xử lý ở đây:
// CALENDAR_PROPERTY_RECORD_ACTUAL, CALENDAR_PROPERTY_RECORD_PREVIOUS,
// CALENDAR_PROPERTY_RECORD_REVISED, CALENDAR_PROPERTY_RECORD_FORECAST,
// và CALENDAR_PROPERTY_RECORD_PERIOD (dưới dạng long)
CalendarFilter *let(const long value, const ENUM_CALENDAR_PROPERTY property, const IS c = EQUAL)
{
const int n = EXPAND(selectors);
selectors[n][0] = property;
selectors[n][1] = value;
selectors[n][2] = c;
return &this;
}
...
2
3
4
5
6
7
8
9
10
11
12
13
Và cho chuỗi:
// điều kiện cho tất cả các thuộc tính chuỗi có thể được tìm thấy ở đây (được rút gọn)
CalendarFilter *let(const string find, const IS c = EQUAL)
{
const int wildcard = (StringFind(find, "*") + 1) * 10;
switch(StringLen(find) + wildcard)
{
case 2:
// nếu ngữ cảnh ban đầu khác với quốc gia, chúng ta có thể bổ sung nó bằng quốc gia,
// nếu không bộ lọc bị bỏ qua
if(StringLen(context) != 2)
{
if(ArraySize(country) == 1 && StringLen(country[0]) == 0)
{
country[0] = find; // thu hẹp "tất cả các quốc gia" xuống còn một (có thể thêm nữa)
}
else
{
PUSH(country, find);
}
}
break;
case 3:
// chúng ta có thể đặt bộ lọc cho một tiền tệ chỉ khi nó không có trong ngữ cảnh ban đầu
if(StringLen(context) != 3)
{
PUSH(currency, find);
}
break;
default:
{
const int n = EXPAND(selectors);
PUSH(stringCache, find);
if(StringFind(find, "http://") == 0 || StringFind(find, "https://") == 0)
{
selectors[n][0] = CALENDAR_PROPERTY_EVENT_SOURCE;
}
else
{
selectors[n][0] = CALENDAR_PROPERTY_EVENT_NAME;
}
selectors[n][1] = ArraySize(stringCache) - 1;
selectors[n][2] = c;
break;
}
}
return &this;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Trong phương thức nạp chồng cho chuỗi, lưu ý rằng các chuỗi dài 2 hoặc 3 ký tự (nếu chúng không có dấu sao mẫu *
, vốn là thay thế cho một chuỗi ký tự bất kỳ) sẽ rơi vào các mảng quốc gia và ký hiệu tương ứng, và tất cả các chuỗi khác được xem như các đoạn của tên hoặc nguồn tin tức, và cả hai trường này đều liên quan đến stringCache
và selectors
.
Theo một cách đặc biệt, lớp này cũng hỗ trợ lọc theo loại (định danh) của sự kiện.
protected:
ulong ids[]; // các loại sự kiện đã lọc
...
public:
CalendarFilter *let(const ulong event)
{
PUSH(ids, event);
return &this;
}
...
2
3
4
5
6
7
8
9
10
Do đó, số lượng bộ lọc ưu tiên (được xử lý ngoài mảng selectors
) không chỉ bao gồm các quốc gia, tiền tệ và khoảng ngày, mà còn cả các định danh loại sự kiện. Quyết định cấu trúc này xuất phát từ việc các tham số này có thể được truyền vào một số hàm API lịch làm đầu vào. Chúng ta nhận được tất cả các thuộc tính tin tức khác dưới dạng giá trị trường đầu ra trong các mảng cấu trúc (MqlCalendarValue
, MqlCalendarEvent
, MqlCalendarCountry
). Chính qua chúng mà chúng ta sẽ thực hiện việc lọc bổ sung, theo các quy tắc trong mảng selectors
.
Tất cả các phương thức let
trả về một con trỏ đến đối tượng, cho phép chuỗi các lệnh gọi của chúng. Ví dụ, như sau:
CalendarFilter f;
f.let(CALENDAR_IMPORTANCE_LOW, GREATER) // tin tức quan trọng và tầm trung
.let(CALENDAR_TIMEMODE_DATETIME) // chỉ các sự kiện có thời gian chính xác
.let("DE").let("FR") // một vài quốc gia, hoặc, để lựa chọn...
.let("USD").let("GBP") // ...một vài tiền tệ (nhưng cả hai điều kiện sẽ không hoạt động cùng lúc)
.let(TimeCurrent() - MONTH_LONG, TimeCurrent() + WEEK_LONG) // khoảng ngày "xung quanh" thời điểm hiện tại
.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_FORECAST, NOT_EQUAL) // có dự báo
.let("farm"); // tìm kiếm toàn văn bản theo tiêu đề tin tức
2
3
4
5
6
7
8
Các điều kiện quốc gia và tiền tệ về lý thuyết có thể được kết hợp. Tuy nhiên, xin lưu ý rằng nhiều giá trị chỉ có thể được thiết lập cho quốc gia hoặc tiền tệ, nhưng không phải cả hai. Một trong hai khía cạnh này của ngữ cảnh (hoặc cả hai) trong triển khai hiện tại chỉ hỗ trợ một hoặc không có giá trị nào (tức là không có bộ lọc trên đó). Ví dụ, nếu tiền tệ EUR được chọn, có thể thu hẹp ngữ cảnh tìm kiếm tin tức chỉ ở Đức và Pháp (mã quốc gia DE
và FR
). Kết quả là, tin tức ECB và Eurostat sẽ bị loại bỏ, cũng như cụ thể là tin tức từ Ý và Tây Ban Nha. Tuy nhiên, việc chỉ định EUR trong trường hợp này là dư thừa vì không có tiền tệ nào khác ở Đức và Pháp.
Vì lớp này sử dụng các hàm tích hợp trong đó các tham số country
và currency
được áp dụng cho tin tức bằng phép toán logic AND, hãy kiểm tra tính nhất quán của các điều kiện lọc.
Sau khi mã gọi thiết lập các điều kiện lọc, cần phải chọn tin tức dựa trên chúng. Đó là điều mà phương thức công khai select
thực hiện (được đưa ra với các đơn giản hóa).
public:
bool select(MqlCalendarValue &result[])
{
int count = 0;
ArrayFree(result);
if(ArraySize(ids)) // định danh của các loại sự kiện
{
for(int i = 0; i < ArraySize(ids); ++i)
{
MqlCalendarValue temp[];
if(PRTF(CalendarValueHistoryByEvent(ids[i], temp, from, to)))
{
ArrayCopy(result, temp, ArraySize(result));
++count;
}
}
}
else
{
// nhiều quốc gia hoặc tiền tệ, chọn cái nào nhiều hơn làm cơ sở,
// chỉ phần tử đầu tiên từ mảng nhỏ hơn được sử dụng
if(ArraySize(country) > ArraySize(currency))
{
const string c = ArraySize(currency) > 0 ? currency[0] : NULL;
for(int i = 0; i < ArraySize(country); ++i)
{
MqlCalendarValue temp[];
if(PRTF(CalendarValueHistory(temp, from, to, country[i], c)))
{
ArrayCopy(result, temp, ArraySize(result));
++count;
}
}
}
else
{
const string c = ArraySize(country) > 0 ? country[0] : NULL;
for(int i = 0; i < ArraySize(currency); ++i)
{
MqlCalendarValue temp[];
if(PRTF(CalendarValueHistory(temp, from, to, c, currency[i])))
{
ArrayCopy(result, temp, ArraySize(result));
++count;
}
}
}
}
if(ArraySize(result) > 0)
{
filter(result);
}
if(count > 1 && ArraySize(result) > 1)
{
SORT_STRUCT(MqlCalendarValue, result, time);
}
return ArraySize(result) > 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
Tùy thuộc vào việc mảng thuộc tính ưu tiên nào được điền, phương thức sẽ gọi các hàm API khác nhau để thăm dò lịch:
- Nếu mảng
ids
được điền,CalendarValueHistoryByEvent
được gọi trong một vòng lặp cho tất cả các định danh. - Nếu mảng
country
được điền và nó lớn hơn mảng tiền tệ, gọiCalendarValueHistory
và lặp qua các quốc gia. - Nếu mảng
currency
được điền và nó lớn hơn hoặc bằng kích thước của mảng quốc gia, gọiCalendarValueHistory
và lặp qua các tiền tệ.
Mỗi lệnh gọi hàm điền một mảng tạm thời của cấu trúc MqlCalendarValue temp[]
, được tích lũy tuần tự trong mảng tham số result
. Sau khi ghi tất cả tin tức liên quan vào đó theo các điều kiện chính (ngày, quốc gia, tiền tệ, định danh), nếu có, một phương thức phụ trợ filter
sẽ hoạt động, lọc mảng dựa trên các điều kiện trong selectors
. Vào cuối phương thức select
, các mục tin tức được sắp xếp theo thứ tự thời gian, có thể bị phá vỡ khi kết hợp kết quả của nhiều truy vấn hàm "lịch". Việc sắp xếp được triển khai bằng macro SORT_STRUCT
, đã được thảo luận trong phần So sánh, sắp xếp và tìm kiếm trong mảng.
Đối với mỗi phần tử của mảng tin tức, phương thức filter
gọi phương thức làm việc match
, trả về một chỉ báo boolean về việc tin tức có khớp với các điều kiện lọc hay không. Nếu không, phần tử sẽ bị xóa khỏi mảng.
protected:
void filter(MqlCalendarValue &result[])
{
for(int i = ArraySize(result) - 1; i >= 0; --i)
{
if(!match(result[i]))
{
ArrayRemove(result, i, 1);
}
}
}
...
2
3
4
5
6
7
8
9
10
11
12
Cuối cùng, phương thức match
phân tích mảng selectors
của chúng ta và so sánh nó với các trường của cấu trúc MqlCalendarValue
được truyền vào. Đây là mã được cung cấp ở dạng rút gọn.
bool match(const MqlCalendarValue &v)
{
MqlCalendarEvent event;
if(!CalendarEventById(v.event_id, event)) return false;
// lặp qua tất cả các điều kiện lọc, trừ quốc gia, tiền tệ, ngày, ID,
// đã được sử dụng trước đó khi gọi các hàm Calendar
for(int j = 0; j < ArrayRange(selectors, 0); ++j)
{
long field = 0;
string text = NULL;
// lấy giá trị trường từ tin tức hoặc mô tả của nó
switch((int)selectors[j][0])
{
case CALENDAR_PROPERTY_EVENT_TYPE:
field = event.type;
break;
case CALENDAR_PROPERTY_EVENT_SECTOR:
field = event.sector;
break;
case CALENDAR_PROPERTY_EVENT_TIMEMODE:
field = event.time_mode;
break;
case CALENDAR_PROPERTY_EVENT_IMPORTANCE:
field = event.importance;
break;
case CALENDAR_PROPERTY_EVENT_SOURCE:
text = event.source_url;
break;
case CALENDAR_PROPERTY_EVENT_NAME:
text = event.name;
break;
case CALENDAR_PROPERTY_RECORD_IMPACT:
field = v.impact_type;
break;
case CALENDAR_PROPERTY_RECORD_ACTUAL:
field = v.actual_value;
break;
case CALENDAR_PROPERTY_RECORD_PREVIOUS:
field = v.prev_value;
break;
case CALENDAR_PROPERTY_RECORD_REVISED:
field = v.revised_prev_value;
break;
case CALENDAR_PROPERTY_RECORD_PREVISED: // previous hoặc revised (nếu có)
field = v.revised_prev_value != LONG_MIN ? v.revised_prev_value : v.prev_value;
break;
case CALENDAR_PROPERTY_RECORD_FORECAST:
field = v.forecast_value;
break;
...
}
// so sánh giá trị với điều kiện lọc
if(text == NULL) // các trường số
{
switch((IS)selectors[j][2])
{
case EQUAL:
if(!equal(field, selectors[j][1])) return false;
break;
case NOT_EQUAL:
if(equal(field, selectors[j][1])) return false;
break;
case GREATER:
if(!greater(field, selectors[j][1])) return false;
break;
case LESS:
if(greater(field, selectors[j][1])) return false;
break;
}
}
else // các trường chuỗi
{
const string find = stringCache[(int)selectors[j][1]];
switch((IS)selectors[j][2])
{
case EQUAL:
if(!equal(text, find)) return false;
break;
case NOT_EQUAL:
if(equal(text, find)) return false;
break;
case GREATER:
if(!greater(text, find)) return false;
break;
case LESS:
if(greater(text, find)) return false;
break;
}
}
}
return true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
Các phương thức equal
và greater
gần như sao chép hoàn toàn những phương thức được sử dụng trong các phát triển trước đây của chúng ta với các lớp lọc.
Với điều này, vấn đề lọc nói chung đã được giải quyết, tức là chương trình MQL có thể sử dụng đối tượng CalendarFilter
theo cách sau:
CalendarFilter f;
f.let()... // một loạt lệnh gọi phương thức let để thiết lập điều kiện lọc
MqlCalendarValue records[];
if(f.select(records))
{
ArrayPrint(records);
}
2
3
4
5
6
7
Thực tế, phương thức select
có thể làm điều gì đó quan trọng khác mà chúng ta để lại cho việc nghiên cứu tự chọn độc lập.
Thứ nhất, trong danh sách tin tức kết quả, mong muốn có cách nào đó để chèn một dấu phân cách (delimiter
) giữa quá khứ và tương lai, để mắt có thể nhận ra. Về lý thuyết, tính năng này cực kỳ quan trọng đối với lịch, nhưng vì lý do nào đó, nó không có sẵn trong giao diện người dùng MetaTrader 5 và trên trang web mql5.com. Triển khai của chúng ta có khả năng chèn một cấu trúc trống giữa quá khứ và tương lai, mà chúng ta nên hiển thị trực quan (điều này chúng ta sẽ xử lý dưới đây).
Thứ hai, kích thước của mảng kết quả có thể khá lớn (đặc biệt ở giai đoạn đầu chọn cài đặt), và do đó phương thức select
còn cung cấp khả năng giới hạn kích thước của mảng (limit
). Điều này được thực hiện bằng cách xóa các phần tử xa nhất so với thời điểm hiện tại.
Vì vậy, nguyên mẫu đầy đủ của phương thức trông như sau:
bool select(MqlCalendarValue &result[],
const bool delimiter = false, const int limit = -1);
2
Mặc định, không có dấu phân cách nào được chèn và mảng không bị cắt bớt.
Cách đây vài đoạn, chúng ta đã đề cập đến một nhiệm vụ phụ bổ sung của việc lọc, đó là hiển thị trực quan mảng kết quả. Lớp CalendarFilter
có một phương thức đặc biệt format
, phương thức này chuyển đổi mảng cấu trúc được truyền vào MqlCalendarValue &data[]
thành mảng các chuỗi mà con người có thể đọc được string &result[]
. Mã của phương thức này có thể được tìm thấy trong tệp đính kèm CalendarFilter.mqh
.
bool format(const MqlCalendarValue &data[],
const ENUM_CALENDAR_PROPERTY &props[], string &result[],
const bool padding = false, const bool header = false);
2
3
Các trường của MqlCalendarValue
mà chúng ta muốn hiển thị được chỉ định trong mảng props
. Hãy nhớ rằng liệt kê ENUM_CALENDAR_PROPERTY
chứa các trường từ cả ba cấu trúc lịch phụ thuộc, để một chương trình MQL có thể tự động hiển thị không chỉ các chỉ số kinh tế từ một bản ghi sự kiện cụ thể mà còn cả tên, đặc điểm, mã quốc gia hoặc mã tiền tệ của nó. Tất cả điều này được thực hiện bởi phương thức format
.
Mỗi hàng trong mảng đầu ra result
chứa một biểu diễn văn bản của giá trị của một trong các trường (số, mô tả, phần tử liệt kê). Kích thước của mảng result
bằng tích số của số lượng cấu trúc ở đầu vào (trong data
) và số lượng trường được hiển thị (trong props
). Tham số tùy chọn header
cho phép bạn thêm một hàng với tên của các trường (cột) vào đầu mảng đầu ra. Tham số padding
kiểm soát việc tạo thêm khoảng trắng trong văn bản để thuận tiện hiển thị bảng trong phông chữ đơn cách (ví dụ, trong một tạp chí).
Lớp CalendarFilter
có một phương thức công khai quan trọng khác: update
.
bool update(MqlCalendarValue &result[]);
Cấu trúc của nó gần như hoàn toàn lặp lại select
. Tuy nhiên, thay vì gọi các hàm CalendarValueHistoryByEvent
và CalendarValueHistory
, phương thức này gọi CalendarValueLastByEvent
và CalendarValueLast
. Mục đích của phương thức này rõ ràng: nó truy vấn lịch để tìm các thay đổi gần đây phù hợp với điều kiện lọc. Nhưng để hoạt động, nó yêu cầu một ID của các thay đổi. Một trường như vậy thực sự được định nghĩa trong lớp: lần đầu tiên nó được điền bên trong phương thức select
.
class CalendarFilter
{
protected:
...
ulong change;
...
public:
bool select(MqlCalendarValue &result[],
const bool delimiter = false, const int limit = -1)
{
...
change = 0;
MqlCalendarValue dummy[];
CalendarValueLast(change, dummy);
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Một số sắc thái của lớp CalendarFilter
vẫn còn "ẩn sau hậu trường", nhưng chúng ta sẽ giải quyết một số trong những phần sau.
Hãy kiểm tra bộ lọc trong thực tế: đầu tiên trong một kịch bản đơn giản CalendarFilterPrint.mq5
và sau đó trong một chỉ báo thực tế hơn CalendarMonitor.mq5
.
Trong các tham số đầu vào của kịch bản, bạn có thể thiết lập ngữ cảnh (mã quốc gia hoặc tiền tệ), phạm vi thời gian, và chuỗi cho việc tìm kiếm toàn văn bản theo tên sự kiện, cũng như giới hạn kích thước của bảng tin tức kết quả.
input string Context; // Ngữ cảnh (quốc gia - 2 ký tự, tiền tệ - 3 ký tự, trống - không lọc)
input ENUM_CALENDAR_SCOPE Scope = SCOPE_MONTH;
input string Text = "farm";
input int Limit = -1;
2
3
4
Dựa trên các tham số, một đối tượng bộ lọc toàn cục được tạo.
CalendarFilter f(Context, TimeCurrent() - Scope, TimeCurrent() + Scope);
Sau đó, trong OnStart
, chúng ta cấu hình một vài điều kiện không đổi bổ sung (độ quan trọng trung bình và cao của sự kiện) và sự hiện diện của dự báo (trường không bằng LONG_MIN
), cũng như truyền một chuỗi tìm kiếm vào đối tượng.
void OnStart()
{
f.let(CALENDAR_IMPORTANCE_LOW, GREATER)
.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_FORECAST, NOT_EQUAL)
.let(Text); // hỗ trợ thay thế '*'
// Lưu ý: các chuỗi có độ dài 2 hoặc 3 ký tự không có '*' sẽ được xem là mã quốc gia hoặc mã tiền tệ, tương ứng
2
3
4
5
6
Tiếp theo, phương thức select
được gọi và mảng kết quả của cấu trúc MqlCalendarValue
được định dạng thành một bảng với 9 cột bằng phương thức format
.
MqlCalendarValue records[];
// áp dụng các điều kiện lọc và lấy kết quả
if(f.select(records, true, Limit))
{
static const ENUM_CALENDAR_PROPERTY props[] =
{
CALENDAR_PROPERTY_RECORD_TIME,
CALENDAR_PROPERTY_COUNTRY_CURRENCY,
CALENDAR_PROPERTY_EVENT_NAME,
CALENDAR_PROPERTY_EVENT_IMPORTANCE,
CALENDAR_PROPERTY_RECORD_ACTUAL,
CALENDAR_PROPERTY_RECORD_FORECAST,
CALENDAR_PROPERTY_RECORD_PREVISED,
CALENDAR_PROPERTY_RECORD_IMPACT,
CALENDAR_PROPERTY_EVENT_SECTOR,
};
static const int p = ArraySize(props);
// xuất kết quả đã định dạng
string result[];
if(f.format(records, props, result, true, true))
{
for(int i = 0; i < ArraySize(result) / p; ++i)
{
Print(SubArrayCombine(result, " | ", i * p, p));
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Các ô của bảng được nối thành các hàng và xuất ra nhật ký.
Với cài đặt mặc định (tức là cho tất cả các quốc gia và tiền tệ, với phần "farm" trong tên của các sự kiện có độ quan trọng trung bình và cao), bạn có thể nhận được một lịch trình như thế này.
Selecting calendar records...
country[i]= / ok
calendarValueHistory(temp,from,to,country[i],c)=2372 / ok
Filtering 2372 records
Got 9 records
TIME | CUR⁞ | NAME | IMPORTAN⁞ | ACTU⁞ | FORE⁞ | PREV⁞ | IMPACT | SECT⁞
2022.06.02 15:15 | USD | ADP Nonfarm Employment Change | HIGH | +128 | -225 | +202 | POSITIVE | JOBS
2022.06.02 15:30 | USD | Nonfarm Productivity q/q | MODERATE | -7.3 | -7.5 | -7.5 | POSITIVE | JOBS
2022.06.03 15:30 | USD | Nonfarm Payrolls | HIGH | +390 | -19 | +436 | POSITIVE | JOBS
2022.06.03 15:30 | USD | Private Nonfarm Payrolls | MODERATE | +333 | +8 | +405 | POSITIVE | JOBS
2022.06.09 08:30 | EUR | Nonfarm Payrolls q/q | MODERATE | +0.3 | +0.3 | +0.3 | NA | JOBS
– | – | – | – | – | – | – | – | –
2022.07.07 15:15 | USD | ADP Nonfarm Employment Change | HIGH | +nan | -263 | +128 | NA | JOBS
2022.07.08 15:30 | USD | Nonfarm Payrolls | HIGH | +nan | -229 | +390 | NA | JOBS
2022.07.08 15:30 | USD | Private Nonfarm Payrolls | MODERATE | +nan | +51 | +333 | NA | JOBS
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Bây giờ hãy xem xét chỉ báo CalendarMonitor.mq5
. Mục đích của nó là hiển thị lựa chọn sự kiện hiện tại trên biểu đồ cho người dùng theo các bộ lọc được chỉ định. Để hiển thị bảng, chúng ta sẽ sử dụng lớp bảng điểm đã quen thuộc (Tableau.mqh
, xem phần Tính toán ký quỹ cho một lệnh tương lai). Chỉ báo không có bộ đệm và biểu đồ.
Các tham số đầu vào cho phép bạn đặt phạm vi của cửa sổ thời gian (scope
), cũng như ngữ cảnh toàn cục cho đối tượng CalendarFilter
, có thể là mã tiền tệ hoặc mã quốc gia trong Context
(mặc định là trống, tức là không giới hạn) hoặc sử dụng cờ boolean UseChartCurrencies
. Nó được bật mặc định và được khuyến nghị sử dụng để tự động nhận tin tức của những tiền tệ tạo nên công cụ làm việc của biểu đồ.
input string Context; // Ngữ cảnh (quốc gia - 2 ký tự, tiền tệ - 3 ký tự, trống - tất cả)
input ENUM_CALENDAR_SCOPE Scope = SCOPE_WEEK;
input bool UseChartCurrencies = true;
2
3
Các bộ lọc bổ sung có thể được áp dụng cho loại sự kiện, lĩnh vực và mức độ nghiêm trọng.
input ENUM_CALENDAR_EVENT_TYPE_EXT Type = TYPE_ANY;
input ENUM_CALENDAR_EVENT_SECTOR_EXT Sector = SECTOR_ANY;
input ENUM_CALENDAR_EVENT_IMPORTANCE_EXT Importance = IMPORTANCE_MODERATE; // Độ quan trọng (ít nhất)
2
3
Độ quan trọng đặt giới hạn dưới của lựa chọn, không phải khớp chính xác. Do đó, giá trị mặc định IMPORTANCE_MODERATE
sẽ bao gồm không chỉ các sự kiện có độ quan trọng trung bình mà còn cả độ quan trọng cao.
Một độc giả tinh ý sẽ nhận thấy rằng các liệt kê chưa biết được sử dụng ở đây: ENUM_CALENDAR_EVENT_TYPE_EXT
, ENUM_CALENDAR_EVENT_SECTOR_EXT
, ENUM_CALENDAR_EVENT_IMPORTANCE_EXT
. Chúng nằm trong tệp đã đề cập CalendarDefines.mqh
, và chúng gần như tương ứng một-một với các liệt kê tích hợp tương tự. Sự khác biệt duy nhất là chúng đã thêm một phần tử có nghĩa là giá trị "bất kỳ". Chúng ta cần mô tả các liệt kê như vậy để đơn giản hóa việc nhập điều kiện: bây giờ bộ lọc cho mỗi trường được cấu hình bằng danh sách thả xuống nơi bạn có thể chọn một trong các giá trị hoặc tắt bộ lọc. Nếu không có phần tử liệt kê bổ sung này, chúng ta sẽ phải thêm một cờ logic "bật/tắt" vào giao diện cho mỗi trường.
Ngoài ra, các tham số đầu vào cho phép truy vấn các sự kiện theo sự hiện diện của các chỉ số thực tế, dự báo và trước đó trong chúng, cũng như tìm kiếm một chuỗi văn bản (Text
).
input string Text;
input ENUM_CALENDAR_HAS_VALUE HasActual = HAS_ANY;
input ENUM_CALENDAR_HAS_VALUE HasForecast = HAS_ANY;
input ENUM_CALENDAR_HAS_VALUE HasPrevious = HAS_ANY;
input ENUM_CALENDAR_HAS_VALUE HasRevised = HAS_ANY;
input int Limit = 30;
2
3
4
5
6
Các đối tượng CalendarFilter
và tableau
được mô tả ở cấp độ toàn cục.
CalendarFilter f(Context);
AutoPtr<Tableau> t;
2
Lưu ý rằng bộ lọc được tạo một lần, trong khi bảng được biểu diễn bởi một bộ chọn tự động và sẽ được tạo lại động tùy thuộc vào kích thước của dữ liệu nhận được.
Cài đặt bộ lọc được thực hiện trong OnInit
thông qua các lệnh gọi liên tiếp của phương thức let
theo các tham số đầu vào.
int OnInit()
{
if(!f.isLoaded()) return INIT_FAILED;
if(UseChartCurrencies)
{
const string base = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_BASE);
const string profit = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT);
f.let(base);
if(base != profit)
{
f.let(profit);
}
}
if(Type != TYPE_ANY)
{
f.let((ENUM_CALENDAR_EVENT_TYPE)Type);
}
if(Sector != SECTOR_ANY)
{
f.let((ENUM_CALENDAR_EVENT_SECTOR)Sector);
}
if(Importance != IMPORTANCE_ANY)
{
f.let((ENUM_CALENDAR_EVENT_IMPORTANCE)(Importance - 1), GREATER);
}
if(StringLen(Text))
{
f.let(Text);
}
if(HasActual != HAS_ANY)
{
f.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_ACTUAL,
HasActual == HAS_SET ? NOT_EQUAL : EQUAL);
}
...
EventSetTimer(1);
return INIT_SUCCEEDED;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Cuối cùng, một bộ đếm thời gian giây được khởi động. Tất cả công việc được thực hiện trong OnTimer
.
void OnTimer()
{
static const ENUM_CALENDAR_PROPERTY props[] = // cột bảng
{
CALENDAR_PROPERTY_RECORD_TIME,
CALENDAR_PROPERTY_COUNTRY_CURRENCY,
CALENDAR_PROPERTY_EVENT_NAME,
CALENDAR_PROPERTY_EVENT_IMPORTANCE,
CALENDAR_PROPERTY_RECORD_ACTUAL,
CALENDAR_PROPERTY_RECORD_FORECAST,
CALENDAR_PROPERTY_RECORD_PREVISED,
CALENDAR_PROPERTY_RECORD_IMPACT,
CALENDAR_PROPERTY_EVENT_SECTOR,
};
static const int p = ArraySize(props);
MqlCalendarValue records[];
f.let(TimeCurrent() - Scope, TimeCurrent() + Scope); // dịch chuyển cửa sổ thời gian mỗi lần
const ulong trackID = f.getChangeID();
if(trackID) // nếu trạng thái đã được xóa, kiểm tra thay đổi
{
if(f.update(records)) // yêu cầu thay đổi theo bộ lọc
{
// nếu có thay đổi, thông báo cho người dùng
string result[];
f.format(records, props, result);
for(int i = 0; i < ArraySize(result) / p; ++i)
{
Alert(SubArrayCombine(result, " | ", i * p, p));
}
// "rơi qua" tiếp để cập nhật bảng
}
else if(trackID == f.getChangeID())
{
return; // lịch không có thay đổi
}
}
// yêu cầu tập hợp tin tức đầy đủ theo bộ lọc
f.select(records, true, Limit);
// hiển thị bảng tin tức trên biểu đồ
string result[];
f.format(records, props, result, true, true);
if(t[] == NULL || t[].getRows() != ArraySize(records) + 1)
{
t = new Tableau("CALT", ArraySize(records) + 1, p,
TBL_CELL_HEIGHT_AUTO, TBL_CELL_WIDTH_AUTO,
Corner, Margins, FontSize, FontName, FontName + " Bold",
TBL_FLAG_ROW_0_HEADER,
BackgroundColor, BackgroundTransparency);
}
const string hints[] = {};
t[].fill(result, hints);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
Nếu chúng ta chạy chỉ báo trên biểu đồ EURUSD với cài đặt mặc định, chúng ta có thể nhận được hình ảnh sau.
Tập hợp tin tức đã lọc và định dạng trên biểu đồ