Theo dõi sự thay đổi sự kiện theo quốc gia hoặc tiền tệ
Như đã đề cập trong phần về khái niệm cơ bản của lịch, nền tảng ghi nhận tất cả các thay đổi sự kiện bằng một số phương tiện nội bộ. Mỗi trạng thái được đặc trưng bởi một định danh thay đổi (change_id
). Trong số các hàm MQL5, có hai hàm cho phép bạn tìm định danh này (tại một thời điểm bất kỳ) và sau đó yêu cầu các mục lịch đã thay đổi sau đó. Một trong những hàm này là CalendarValueLast
, sẽ được thảo luận trong phần này. Hàm thứ hai, CalendarValueLastByEvent
, sẽ được thảo luận trong phần tiếp theo.
int CalendarValueLast(ulong &change_id, MqlCalendarValue &values[], const string country = NULL, const string currency = NULL)
Hàm CalendarValueLast
được thiết kế cho hai mục đích: lấy định danh thay đổi lịch cuối cùng đã biết change_id
và điền mảng values
với các bản ghi đã sửa đổi kể từ lần sửa đổi trước đó được cung cấp bởi ID được truyền trong cùng change_id
. Nói cách khác, tham số change_id
hoạt động như cả đầu vào và đầu ra. Đó là lý do tại sao nó là một tham chiếu và yêu cầu một biến phải được chỉ định.
Nếu chúng ta nhập change_id
bằng 0 vào hàm, thì hàm sẽ điền biến với định danh hiện tại nhưng sẽ không điền mảng.
Tùy chọn, sử dụng các tham số country
và currency
, bạn có thể thiết lập lọc các bản ghi theo quốc gia và tiền tệ.
Hàm trả về số lượng mục lịch được sao chép. Vì mảng không được điền trong chế độ hoạt động đầu tiên (change_id = 0
), việc trả về 0 không phải là lỗi. Chúng ta cũng có thể nhận được 0 nếu lịch chưa được sửa đổi kể từ thay đổi được chỉ định. Do đó, để kiểm tra lỗi, bạn nên phân tích _LastError
.
Vì vậy, cách sử dụng thông thường của hàm là lặp qua lịch để tìm các thay đổi.
ulong change = 0;
MqlCalendarValue values[];
while(!IsStopped())
{
// truyền định danh cuối cùng mà chúng ta biết và nhận một cái mới nếu nó xuất hiện
if(CalendarValueLast(change, values))
{
// phân tích các bản ghi đã thêm và thay đổi
ArrayPrint(values);
...
}
Sleep(1000);
}
2
3
4
5
6
7
8
9
10
11
12
13
Việc này có thể được thực hiện trong một vòng lặp, trên bộ đếm thời gian, hoặc trên các sự kiện khác.
Các định danh liên tục tăng, nhưng chúng có thể không theo thứ tự, tức là nhảy qua vài giá trị.
Cần lưu ý rằng mỗi mục lịch luôn chỉ có sẵn ở trạng thái cuối cùng: lịch sử thay đổi không được cung cấp trong MQL5. Thông thường, điều này không phải là vấn đề, vì vòng đời của mỗi tin tức là tiêu chuẩn: thêm vào cơ sở dữ liệu trước một khoảng thời gian đủ dài và bổ sung dữ liệu liên quan tại thời điểm sự kiện. Tuy nhiên, trong thực tế, có thể xảy ra nhiều sai lệch khác nhau: chỉnh sửa dự báo, chuyển thời gian, hoặc sửa đổi giá trị. Không thể tìm ra chính xác thời gian nào và điều gì đã thay đổi trong bản ghi thông qua API MQL5 từ lịch sử lịch. Do đó, những hệ thống giao dịch đưa ra quyết định dựa trên tình hình tức thời sẽ yêu cầu lưu trữ độc lập lịch sử thay đổi và tích hợp nó vào một Expert Advisor để chạy trong bộ kiểm tra.
Sử dụng hàm CalendarValueLast
, chúng ta có thể tạo một dịch vụ hữu ích, CalendarChangeSaver.mq5
, sẽ kiểm tra lịch để tìm các thay đổi theo khoảng thời gian được chỉ định và, nếu có, lưu các định danh thay đổi vào tệp cùng với thời gian máy chủ hiện tại. Điều này sẽ cho phép sử dụng thêm thông tin tệp để kiểm tra Expert Advisor thực tế hơn trên lịch sử lịch. Tất nhiên, điều này sẽ yêu cầu tổ chức xuất/nhập toàn bộ cơ sở dữ liệu lịch, mà chúng ta sẽ xử lý theo thời gian.
Hãy cung cấp các biến đầu vào để chỉ định tên tệp và khoảng thời gian giữa các lần thăm dò (tính bằng mili giây).
input string Filename = "calendar.chn";
input int PeriodMsc = 1000;
2
Tại phần đầu của trình xử lý OnStart
, chúng ta mở tệp nhị phân để ghi, hoặc chính xác hơn là để thêm vào (nếu nó đã tồn tại). Định dạng của tệp hiện có không được kiểm tra ở đây và do đó bạn nên thêm bảo vệ khi nhúng vào ứng dụng thực tế.
void OnStart()
{
ulong change = 0, last = 0;
int count = 0;
int handle = FileOpen(Filename,
FILE_WRITE | FILE_READ | FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_BIN);
if(handle == INVALID_HANDLE)
{
PrintFormat("Can't open file '%s' for writing", Filename);
return;
}
const ulong p = FileSize(handle);
if(p > 0)
{
PrintFormat("Resuming file %lld bytes", p);
FileSeek(handle, 0, SEEK_END);
}
Print("Requesting start ID...");
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Ở đây chúng ta nên làm một chút lạc đề.
Mỗi lần lịch thay đổi, ít nhất một cặp số nguyên 8 byte phải được ghi vào tệp: thời gian hiện tại (datetime
) và ID tin tức (ulong
), nhưng có thể có nhiều hơn một bản ghi thay đổi cùng một lúc. Do đó, ngoài ngày tháng, số lượng bản ghi thay đổi được đóng gói vào số đầu tiên. Điều này tính đến việc các ngày tháng vừa với 0x7FFFFFFFF và do đó 3 byte trên cùng bị bỏ trống. Chính trong hai byte quan trọng nhất (ở vị trí lệch trái 48 bit) mà số lượng định danh mà dịch vụ sẽ ghi sau dấu thời gian tương ứng được đặt. Macro PACK_DATETIME_COUNTER
tạo ra một ngày "mở rộng", và hai macro khác, DATETIME
và COUNTER
, chúng ta sẽ cần sau này khi kho lưu trữ thay đổi được đọc (bởi một chương trình khác).
#define PACK_DATETIME_COUNTER(D,C) (D | (((ulong)(C)) << 48))
#define DATETIME(A) ((datetime)((A) & 0x7FFFFFFFF))
#define COUNTER(A) ((ushort)((A) >> 48))
2
3
Bây giờ hãy quay lại mã dịch vụ chính. Trong một vòng lặp được kích hoạt mỗi PeriodMsc
mili giây, chúng ta yêu cầu các thay đổi bằng CalendarValueLast
. Nếu có thay đổi, chúng ta ghi thời gian máy chủ hiện tại và mảng các định danh nhận được vào tệp.
while(!IsStopped())
{
if(!TerminalInfoInteger(TERMINAL_CONNECTED))
{
Print("Waiting for connection...");
Sleep(PeriodMsc);
continue;
}
MqlCalendarValue values[];
const int n = CalendarValueLast(change, values);
if(n > 0)
{
string records = "[" + Description(values[0]);
for(int i = 1; i < n; ++i)
{
records += "," + Description(values[i]);
}
records += "]";
Print("New change ID: ", change, " ",
TimeToString(TimeTradeServer(), TIME_DATE | TIME_SECONDS), "\n", records);
FileWriteLong(handle, PACK_DATETIME_COUNTER(TimeTradeServer(), n));
for(int i = 0; i < n; ++i)
{
FileWriteLong(handle, values[i].id);
}
FileFlush(handle);
++count;
}
else if(_LastError == 0)
{
if(!last && change)
{
Print("Start change ID obtained: ", change);
}
}
last = change;
Sleep(PeriodMsc);
}
PrintFormat("%d records added", count);
FileClose(handle);
}
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
Để trình bày thông tin về mỗi sự kiện tin tức một cách thuận tiện, chúng ta đã viết một hàm trợ giúp Description
.
string Description(const MqlCalendarValue &value)
{
MqlCalendarEvent event;
MqlCalendarCountry country;
CalendarEventById(value.event_id, event);
CalendarCountryById(event.country_id, country);
return StringFormat("%lld (%s/%s @ %s)",
value.id, country.code, event.name, TimeToString(value.time));
}
2
3
4
5
6
7
8
9
Do đó, nhật ký sẽ hiển thị không chỉ định danh mà còn mã quốc gia, tiêu đề và thời gian dự kiến của tin tức.
Giả định rằng dịch vụ nên hoạt động trong một thời gian khá dài để thu thập thông tin cho một khoảng thời gian đủ để kiểm tra (ngày, tuần, tháng). Thật không may, giống như với sổ lệnh, nền tảng không cung cấp sẵn lịch sử của sổ lệnh hoặc chỉnh sửa lịch, vì vậy việc thu thập chúng hoàn toàn được giao cho nhà phát triển các chương trình MQL.
Hãy xem dịch vụ hoạt động. Trong đoạn nhật ký tiếp theo (cho khoảng thời gian từ 15:30 đến 16:00 ngày 28/06/2022), một số sự kiện tin tức liên quan đến tương lai xa (chúng chứa các giá trị của trường prev_value
, cũng là trường actual_value
của sự kiện hiện tại cùng tên). Tuy nhiên, điều quan trọng hơn là: thời gian thực tế của một bản tin có thể khác biệt đáng kể, đôi khi vài phút, so với thời gian dự kiến.
Requesting start ID...
Start change ID obtained: 86358784
New change ID: 86359040 2022.06.28 15:30:42
[155955 (US/Wholesale Inventories m/m @ 2022.06.28 15:30)]
New change ID: 86359296 2022.06.28 15:30:45
[155956 (US/Wholesale Inventories m/m @ 2022.07.08 17:00)]
New change ID: 86359552 2022.06.28 15:30:48
[156117 (US/Goods Trade Balance @ 2022.06.28 15:30)]
New change ID: 86359808 2022.06.28 15:30:51
[156118 (US/Goods Trade Balance @ 2022.07.27 15:30)]
New change ID: 86360064 2022.06.28 15:30:54
[156231 (US/Retail Inventories m/m @ 2022.06.28 15:30)]
New change ID: 86360320 2022.06.28 15:30:57
[156232 (US/Retail Inventories m/m @ 2022.07.15 17:00)]
New change ID: 86360576 2022.06.28 15:31:00
[156255 (US/Retail Inventories excl. Autos m/m @ 2022.06.28 15:30)]
New change ID: 86360832 2022.06.28 15:31:03
[156256 (US/Retail Inventories excl. Autos m/m @ 2022.07.15 17:00)]
New change ID: 86361088 2022.06.28 15:31:07
[155956 (US/Wholesale Inventories m/m @ 2022.07.08 17:00)]
New change ID: 86361344 2022.06.28 15:31:10
[156118 (US/Goods Trade Balance @ 2022.07.27 15:30)]
New change ID: 86361600 2022.06.28 15:31:13
[156232 (US/Retail Inventories m/m @ 2022.07.15 17:00)]
New change ID: 86362368 2022.06.28 15:36:47
[158534 (US/Challenger Job Cuts y/y @ 2022.07.07 14:30)]
New change ID: 86362624 2022.06.28 15:51:23
...
New change ID: 86364160 2022.06.28 16:01:39
[154531 (US/HPI m/m @ 2022.06.28 16:00)]
New change ID: 86364416 2022.06.28 16:01:42
[154532 (US/HPI m/m @ 2022.07.26 16:00)]
New change ID: 86364672 2022.06.28 16:01:46
[154543 (US/HPI y/y @ 2022.06.28 16:00)]
New change ID: 86364928 2022.06.28 16:01:49
[154544 (US/HPI y/y @ 2022.07.26 16:00)]
New change ID: 86365184 2022.06.28 16:01:54
[154561 (US/HPI @ 2022.06.28 16:00)]
New change ID: 86365440 2022.06.28 16:01:58
[154571 (US/HPI @ 2022.07.26 16:00)]
New change ID: 86365696 2022.06.28 16:02:01
[154532 (US/HPI m/m @ 2022.07.26 16:00)]
New change ID: 86365952 2022.06.28 16:02:05
[154544 (US/HPI y/y @ 2022.07.26 16:00)]
New change ID: 86366208 2022.06.28 16:02:09
[154571 (US/HPI @ 2022.07.26 16:00)]
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
Tất nhiên, điều này không quan trọng đối với tất cả các lớp chiến lược giao dịch, mà chỉ đối với những chiến lược giao dịch nhanh trên thị trường. Đối với chúng, kho lưu trữ chỉnh sửa lịch được tạo ra có thể cung cấp việc kiểm tra Expert Advisor tin tức chính xác hơn. Chúng ta sẽ thảo luận cách bạn có thể "kết nối" lịch với bộ kiểm tra trong tương lai, nhưng hiện tại, chúng ta sẽ cho thấy cách đọc tệp đã nhận được.
Chúng ta sẽ sử dụng script CalendarChangeReader.mq5
để thể hiện chức năng được thảo luận. Trong thực tế, mã nguồn được cung cấp nên được đặt trong Expert Advisor.
Các biến đầu vào cho phép bạn đặt tên tệp cần đọc và ngày bắt đầu quét. Nếu dịch vụ tiếp tục hoạt động (ghi tệp), bạn cần sao chép tệp dưới một tên khác hoặc vào một thư mục khác (trong script ví dụ, tệp được đổi tên). Nếu tham số Start
để trống, việc đọc các thay đổi tin tức sẽ bắt đầu từ đầu ngày hiện tại.
input string Filename = "calendar2.chn";
input datetime Start;
2
Cấu trúc ChangeState
được mô tả để lưu trữ thông tin về các chỉnh sửa riêng lẻ.
struct ChangeState
{
datetime dt;
ulong ids[];
ChangeState(): dt(LONG_MAX) {}
ChangeState(const datetime at, ulong &_ids[])
{
dt = at;
ArraySwap(ids, _ids);
}
void operator=(const ChangeState &other)
{
dt = other.dt;
ArrayCopy(ids, other.ids);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Nó được sử dụng trong lớp ChangeFileReader
, thực hiện phần lớn công việc đọc tệp và cung cấp cho người gọi các thay đổi phù hợp với một thời điểm cụ thể.
Tay cầm tệp được truyền dưới dạng tham số cho hàm tạo, cũng như thời gian bắt đầu kiểm tra. Việc đọc tệp và điền cấu trúc ChangeState
cho một chỉnh sửa lịch được thực hiện trong phương thức readState
.
class ChangeFileReader
{
const int handle;
ChangeState current;
const ChangeState zero;
public:
ChangeFileReader(const int h, const datetime start = 0): handle(h)
{
if(readState())
{
if(start)
{
ulong dummy[];
check(start, dummy, true); // tìm chỉnh sửa đầu tiên sau start
}
}
}
bool readState()
{
if(FileIsEnding(handle)) return false;
ResetLastError();
const ulong v = FileReadLong(handle);
current.dt = DATETIME(v);
ArrayFree(current.ids);
const int n = COUNTER(v);
for(int i = 0; i < n; ++i)
{
PUSH(current.ids, FileReadLong(handle));
}
return _LastError == 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
Phương thức check
đọc tệp cho đến khi chỉnh sửa tiếp theo xuất hiện trong tương lai. Trong trường hợp này, tất cả các chỉnh sửa trước đó (theo dấu thời gian) kể từ lần gọi phương thức trước được đặt trong mảng đầu ra records
.
bool check(datetime now, ulong &records[], const bool fastforward = false)
{
if(current.dt > now) return false;
ArrayFree(records);
if(!fastforward)
{
ArrayCopy(records, current.ids);
current = zero;
}
while(readState() && current.dt <= now)
{
if(!fastforward) ArrayInsert(records, current.ids, ArraySize(records));
}
return true;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Đây là cách lớp được sử dụng trong OnStart
.
void OnStart()
{
const long day = 60 * 60 * 24;
datetime now = Start ? Start : (datetime)(TimeCurrent() / day * day);
int handle = FileOpen(Filename,
FILE_READ | FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_BIN);
if(handle == INVALID_HANDLE)
{
PrintFormat("Can't open file '%s' for reading", Filename);
return;
}
ChangeFileReader reader(handle, now);
// đọc từng bước, thời gian now được tăng giả tạo trong bản demo này
while(!FileIsEnding(handle))
{
// trong ứng dụng thực tế, một cuộc gọi tới reader.check có thể được thực hiện trên mỗi tick
ulong records[];
if(reader.check(now, records))
{
Print(now); // xuất thời gian
ArrayPrint(records); // mảng ID của tin tức đã thay đổi
}
now += 60; // thêm 1 phút mỗi lần, có thể là mỗi giây
}
FileClose(handle);
}
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
Dưới đây là kết quả của script cho cùng các thay đổi lịch đã được dịch vụ lưu trong bối cảnh đoạn nhật ký trước đó.
2022.06.28 15:31:00
155955 155956 156117 156118 156231 156232 156255
2022.06.28 15:32:00
156256 155956 156118 156232
2022.06.28 15:37:00
158534
...
2022.06.28 16:02:00
154531 154532 154543 154544 154561 154571
2022.06.28 16:03:00
154532 154544 154571
2
3
4
5
6
7
8
9
10
11
Các định danh giống nhau được tái tạo trong thời gian ảo với cùng độ trễ như trực tuyến, mặc dù ở đây bạn có thể thấy việc làm tròn đến 1 phút, điều này xảy ra vì chúng ta đã đặt một bước giả tạo có kích thước này trong vòng lặp. Về lý thuyết, vì lý do hiệu quả, chúng ta có thể hoãn kiểm tra cho đến thời gian được lưu trong cấu trúc ChangeState current
. Mã nguồn đính kèm định nghĩa phương thức getState
để lấy thời gian này.