Tạo sự kiện tùy chỉnh
Ngoài các sự kiện tiêu chuẩn, thiết bị đầu cuối hỗ trợ khả năng tạo sự kiện tùy chỉnh bằng lập trình, bản chất và nội dung của chúng được xác định bởi chương trình MQL. Các sự kiện như vậy được thêm vào hàng đợi chung của các sự kiện biểu đồ và có thể được xử lý trong hàm OnChartEvent
bởi tất cả các chương trình quan tâm.
Một phạm vi đặc biệt gồm 65536 mã định danh số nguyên được dành riêng cho các sự kiện tùy chỉnh: từ CHARTEVENT_CUSTOM
đến CHARTEVENT_CUSTOM_LAST
bao gồm cả hai. Nói cách khác, sự kiện tùy chỉnh phải có ID là CHARTEVENT_CUSTOM + n
, trong đó n
nằm trong khoảng từ 0 đến 65535. CHARTEVENT_CUSTOM_LAST
chính xác bằng CHARTEVENT_CUSTOM + 65535
.
Các sự kiện tùy chỉnh được gửi đến biểu đồ bằng hàm EventChartCustom
.
bool EventChartCustom(long chartId, ushort customEventId, long lparam, double dparam, string sparam)
chartId
là mã định danh của biểu đồ nhận sự kiện, trong khi 0 biểu thị biểu đồ hiện tại; customEventId
là ID sự kiện (do nhà phát triển chương trình MQL chọn). Mã định danh này tự động được cộng vào giá trị CHARTEVENT_CUSTOM
và chuyển đổi sang kiểu số nguyên. Giá trị này sẽ được truyền đến trình xử lý OnChartEvent
dưới dạng đối số đầu tiên. Các tham số khác của EventChartCustom
tương ứng với các tham số sự kiện tiêu chuẩn trong OnChartEvent
với các kiểu long
, double
và string
, và có thể chứa thông tin bất kỳ.
Hàm trả về true
nếu sự kiện người dùng được xếp hàng thành công hoặc false
nếu xảy ra lỗi (mã lỗi sẽ có sẵn trong _LastError
).
Khi chúng ta tiến gần đến phần phức tạp và quan trọng nhất của cuốn sách dành trực tiếp cho việc tự động hóa giao dịch, chúng ta sẽ bắt đầu giải quyết các vấn đề ứng dụng hữu ích trong việc phát triển robot giao dịch. Bây giờ, trong bối cảnh thể hiện khả năng của các sự kiện tùy chỉnh, hãy chuyển sang phân tích đa tiền tệ (hoặc tổng quát hơn, đa biểu tượng) của môi trường giao dịch.
Trước đó, trong chương về chỉ báo, chúng ta đã xem xét chỉ báo đa tiền tệ nhưng không chú ý đến một điểm quan trọng: mặc dù các chỉ báo xử lý báo giá của các biểu tượng khác nhau, việc tính toán thực tế được khởi chạy trong trình xử lý OnCalculate
, chỉ được kích hoạt bởi sự xuất hiện của một tick mới của biểu tượng làm việc duy nhất của biểu đồ. Hóa ra các tick của các công cụ khác về cơ bản bị bỏ qua. Ví dụ, nếu chỉ báo hoạt động trên biểu tượng A, khi tick của nó đến, chúng ta chỉ lấy các tick cuối cùng đã biết của các biểu tượng khác (B, C, D), nhưng rất có thể các tick khác đã kịp trôi qua trên mỗi biểu tượng.
Nếu bạn đặt chỉ báo đa tiền tệ trên công cụ thanh khoản nhất (nơi tick được nhận thường xuyên nhất), điều này không quá nghiêm trọng. Tuy nhiên, các công cụ khác nhau có thể nhanh hơn vào các thời điểm khác nhau trong ngày, và nếu một thuật toán phân tích hoặc giao dịch yêu cầu phản hồi nhanh nhất có thể với báo giá mới của tất cả các công cụ trong danh mục, chúng ta đối mặt với thực tế rằng giải pháp hiện tại không phù hợp với chúng ta.
Thật không may, sự kiện tiêu chuẩn của việc đến tick mới trong MQL5 chỉ hoạt động cho một biểu tượng, đó là biểu tượng làm việc của biểu đồ hiện tại. Trong các chỉ báo, trình xử lý OnCalculate được gọi vào những thời điểm như vậy, và trong Expert Advisors, trình xử lý OnTick được gọi.
Do đó, cần phải phát minh ra một cơ chế để chương trình MQL có thể nhận thông báo về tick trên tất cả các công cụ quan tâm. Đây là nơi các sự kiện tùy chỉnh sẽ giúp chúng ta. Tất nhiên, điều này không cần thiết cho các chương trình chỉ phân tích một công cụ.
Chúng ta sẽ phát triển một ví dụ về chỉ báo EventTickSpy.mq5
, khi được chạy trên một biểu tượng cụ thể X, sẽ có thể gửi thông báo tick từ hàm OnCalculate
của nó bằng cách sử dụng EventChartCustom
. Kết quả là, trong trình xử lý OnChartEvent
, được chuẩn bị đặc biệt để nhận các thông báo này, có thể thu thập thông báo từ các phiên bản khác nhau của chỉ báo từ các biểu tượng khác nhau.
Ví dụ này được cung cấp để minh họa. Sau này, khi nghiên cứu giao dịch tự động đa tiền tệ, chúng ta sẽ điều chỉnh kỹ thuật này để sử dụng thuận tiện hơn trong Expert Advisors.
Trước hết, hãy nghĩ về một số sự kiện tùy chỉnh cho chỉ báo. Vì chúng ta sẽ gửi thông báo tick cho nhiều biểu tượng khác nhau từ một danh sách nhất định, chúng ta có thể chọn các chiến thuật khác nhau ở đây. Ví dụ, bạn có thể chọn một mã định danh sự kiện và truyền số của biểu tượng trong danh sách và/hoặc tên của biểu tượng trong các tham số lparam
và sparam
, tương ứng. Hoặc bạn có thể lấy một hằng số (lớn hơn hoặc bằng CHARTEVENT_CUSTOM
) và nhận số sự kiện bằng cách cộng số biểu tượng vào hằng số này (sau đó chúng ta có tất cả các tham số tự do, đặc biệt là lparam
và dparam
, và chúng có thể được sử dụng để truyền giá Ask
, Bid
hoặc thứ gì khác).
Chúng ta sẽ tập trung vào tùy chọn khi có một mã sự kiện. Hãy khai báo nó trong macro TICKSPY
. Đây sẽ là giá trị mặc định, người dùng có thể thay đổi để tránh xung đột (dù không chắc chắn) với các chương trình khác nếu cần.
#define TICKSPY 0xFEED // 65261
Giá trị này được chọn cố ý để cách xa khá xa so với CHARTEVENT_CUSTOM
đầu tiên được phép.
Trong lần khởi chạy ban đầu (tương tác) của chỉ báo, người dùng phải chỉ định danh sách các công cụ mà chỉ báo nên theo dõi tick. Để phục vụ mục đích này, chúng ta sẽ mô tả biến chuỗi đầu vào SymbolList
với danh sách các biểu tượng phân tách bằng dấu phẩy.
Mã định danh của sự kiện người dùng được đặt trong tham số message
.
Cuối cùng, chúng ta cần mã định danh của biểu đồ nhận để truyền sự kiện. Chúng ta sẽ cung cấp tham số Chart
cho mục đích này. Người dùng không nên chỉnh sửa nó: trong phiên bản đầu tiên của chỉ báo được khởi chạy thủ công, biểu đồ được biết ngầm định bằng cách gắn nó vào biểu đồ. Trong các bản sao khác của chỉ báo mà phiên bản đầu tiên của chúng ta sẽ chạy bằng lập trình, tham số này sẽ được thuật toán điền bằng cách gọi hàm ChartID
(xem bên dưới).
input string SymbolList = "EURUSD,GBPUSD,XAUUSD,USDJPY"; // Danh sách biểu tượng phân tách bằng dấu phẩy (ví dụ)
input ushort message = TICKSPY; // Tin nhắn tùy chỉnh
input long chart = 0; // Biểu đồ nhận (không chỉnh sửa)
2
3
Trong tham số SymbolList
, ví dụ, một danh sách với bốn công cụ phổ biến được chỉ định. Chỉnh sửa nó theo nhu cầu để phù hợp với Market Watch
của bạn.
Trong trình xử lý OnInit
, chúng ta chuyển đổi danh sách thành mảng Symbols
của các biểu tượng, sau đó trong một vòng lặp, chúng ta chạy cùng chỉ báo cho tất cả các biểu tượng từ mảng, ngoại trừ biểu tượng hiện tại (thường có sự trùng khớp như vậy, vì biểu tượng hiện tại đã được xử lý bởi bản sao ban đầu của chỉ báo này).
string Symbols[];
void OnInit()
{
PrintFormat("Starting for chart %lld, msg=0x%X [%s]", Chart, Message, SymbolList);
if(Chart == 0)
{
if(StringLen(SymbolList) > 0)
{
const int n = StringSplit(SymbolList, ',', Symbols);
for(int i = 0; i < n; ++i)
{
if(Symbols[i] != _Symbol)
{
ResetLastError();
// chạy cùng chỉ báo trên một biểu tượng khác với các cài đặt khác,
// đặc biệt, chúng ta truyền ChartID của mình để nhận lại thông báo
iCustom(Symbols[i], PERIOD_CURRENT, MQLInfoString(MQL_PROGRAM_NAME),
"", Message, ChartID());
if(_LastError != 0)
{
PrintFormat("The symbol '%s' seems incorrect", Symbols[i]);
}
}
}
}
else
{
Print("SymbolList is empty: tracking current symbol only!");
Print("To monitor other symbols, fill in SymbolList, i.e.",
" 'EURUSD,GBPUSD,XAUUSD,USDJPY'");
}
}
}
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
Ở đầu OnInit
, thông tin về phiên bản chỉ báo được khởi chạy được hiển thị trong nhật ký để rõ ràng điều gì đang xảy ra.
Nếu chúng ta chọn tùy chọn với các mã sự kiện riêng cho mỗi ký tự, chúng ta sẽ phải gọi iCustom
như sau (cộng i
vào message
):
iCustom(Symbols[i], PERIOD_CURRENT, MQLInfoString(MQL_PROGRAM_NAME), "",
Message + i, ChartID());
2
Lưu ý rằng giá trị khác không của tham số Chart
ngụ ý rằng bản sao này được khởi chạy bằng lập trình và nó nên theo dõi một biểu tượng duy nhất, tức là biểu tượng làm việc của biểu đồ. Do đó, chúng ta không cần truyền danh sách biểu tượng khi chạy các bản sao phụ.
Trong hàm OnCalculate
, được gọi khi nhận được tick mới, chúng ta gửi sự kiện tùy chỉnh Message
đến biểu đồ Chart
bằng cách gọi EventChartCustom
. Trong trường hợp này, tham số lparam
không được sử dụng (bằng 0). Trong tham số dparam
, chúng ta truyền giá hiện tại (cuối cùng) price[0]
(đây là Bid
hoặc Last
, tùy thuộc vào loại giá mà biểu đồ dựa trên: nó cũng là giá của tick cuối cùng được biểu đồ xử lý), và chúng ta truyền tên biểu tượng trong tham số sparam
.
int OnCalculate(const int rates_total, const int prev_calculated,
const int, const double &price[])
{
if(prev_calculated)
{
ArraySetAsSeries(price, true);
if(Chart > 0)
{
// gửi thông báo tick đến biểu đồ cha
EventChartCustom(Chart, Message, 0, price[0], _Symbol);
}
else
{
OnSymbolTick(_Symbol, price[0]);
}
}
return rates_total;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Trong phiên bản gốc của chỉ báo, nơi tham số Chart
là 0, chúng ta gọi trực tiếp một hàm đặc biệt, một loại trình xử lý tick đa tài sản OnSymbolTick
. Trong trường hợp này, không cần gọi EventChartCustom
: mặc dù thông điệp như vậy vẫn sẽ đến biểu đồ và bản sao này của chỉ báo, việc truyền mất vài mili giây và làm tải hàng đợi một cách vô ích.
Mục đích duy nhất của OnSymbolTick
trong bản demo này là in tên biểu tượng và giá mới trong nhật ký.
void OnSymbolTick(const string &symbol, const double price)
{
Print(symbol, " ", DoubleToString(price,
(int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)));
}
2
3
4
5
Tất nhiên, cùng hàm này được gọi từ trình xử lý OnChartEvent
trong bản sao nhận (nguồn) của chỉ báo, với điều kiện thông điệp của chúng ta đã được nhận. Hãy nhớ rằng thiết bị đầu cuối chỉ gọi OnChartEvent
trong bản sao tương tác của chỉ báo (được áp dụng cho biểu đồ) và không xuất hiện trong các bản sao mà chúng ta đã tạo "vô hình" bằng iCustom
.
void OnChartEvent(const int id,
const long &lparam, const double &dparam, const string &sparam)
{
if(id >= CHARTEVENT_CUSTOM + Message)
{
OnSymbolTick(sparam, dparam);
// HOẶC (nếu sử dụng phạm vi sự kiện tùy chỉnh):
// OnSymbolTick(Symbols[id - CHARTEVENT_CUSTOM - Message], dparam);
}
}
2
3
4
5
6
7
8
9
10
Chúng ta có thể tránh gửi cả giá hoặc tên của biểu tượng trong sự kiện của mình vì danh sách chung của các biểu tượng đã được biết trong chỉ báo ban đầu (đã khởi tạo quá trình), và do đó chúng ta có thể bằng cách nào đó báo cho nó số của biểu tượng từ danh sách. Điều này có thể được thực hiện trong tham số lparam
hoặc, như đã đề cập ở trên, bằng cách cộng một số vào hằng số cơ sở của sự kiện người dùng. Sau đó, chỉ báo gốc, trong khi nhận sự kiện, có thể lấy một biểu tượng theo chỉ số từ mảng và nhận tất cả thông tin về tick cuối cùng bằng SymbolInfoTick, bao gồm các loại giá khác nhau.
Hãy chạy chỉ báo trên biểu đồ EURUSD với cài đặt mặc định, bao gồm danh sách thử nghiệm "EURUSD,GBPUSD,XAUUSD,USDJPY". Đây là nhật ký:
16:45:48.745 (EURUSD,H1) Starting for chart 0, msg=0xFEED [EURUSD,GBPUSD,XAUUSD,USDJPY]
16:45:48.761 (GBPUSD,H1) Starting for chart 132358585987782873, msg=0xFEED []
16:45:48.761 (USDJPY,H1) Starting for chart 132358585987782873, msg=0xFEED []
16:45:48.761 (XAUUSD,H1) Starting for chart 132358585987782873, msg=0xFEED []
16:45:48.777 (EURUSD,H1) XAUUSD 1791.00
16:45:49.120 (EURUSD,H1) EURUSD 1.13068 *
16:45:49.135 (EURUSD,H1) USDJPY 115.797
16:45:49.167 (EURUSD,H1) XAUUSD 1790.95
16:45:49.167 (EURUSD,H1) USDJPY 115.796
16:45:49.229 (EURUSD,H1) USDJPY 115.797
16:45:49.229 (EURUSD,H1) XAUUSD 1790.74
16:45:49.369 (EURUSD,H1) XAUUSD 1790.77
16:45:49.572 (EURUSD,H1) GBPUSD 1.35332
16:45:49.572 (EURUSD,H1) XAUUSD 1790.80
16:45:49.791 (EURUSD,H1) XAUUSD 1790.80
16:45:49.791 (EURUSD,H1) USDJPY 115.796
16:45:49.931 (EURUSD,H1) EURUSD 1.13069 *
16:45:49.931 (EURUSD,H1) XAUUSD 1790.86
16:45:49.931 (EURUSD,H1) USDJPY 115.795
16:45:50.056 (EURUSD,H1) USDJPY 115.793
16:45:50.181 (EURUSD,H1) XAUUSD 1790.88
16:45:50.321 (EURUSD,H1) XAUUSD 1790.90
16:45:50.399 (EURUSD,H1) EURUSD 1.13066 *
16:45:50.727 (EURUSD,H1) EURUSD 1.13067 *
16:45:50.773 (EURUSD,H1) GBPUSD 1.35334
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Lưu ý rằng trong cột với (biểu tượng, khung thời gian) là nguồn của bản ghi, chúng ta đầu tiên thấy các phiên bản chỉ báo khởi động trên bốn biểu tượng được yêu cầu.
Sau khi khởi chạy, tick đầu tiên là XAUUSD, không phải EURUSD. Các tick biểu tượng tiếp theo đến với cường độ gần bằng nhau, xen kẽ. Các tick EURUSD được đánh dấu bằng dấu sao, để bạn có thể hình dung有多少 tick khác sẽ bị bỏ lỡ nếu không có thông báo.
Dấu thời gian đã được lưu trong cột bên trái để tham khảo.
Những nơi mà hai giá của hai sự kiện liên tiếp từ cùng một biểu tượng trùng nhau thường cho thấy giá Ask
đã thay đổi (chúng ta chỉ không hiển thị nó ở đây).
Một chút sau, sau khi nghiên cứu API giao dịch MQL5, chúng ta sẽ áp dụng nguyên tắc tương tự để phản hồi với các tick đa tiền tệ trong Expert Advisors.