Tổng quan về các hàm xử lý sự kiện
Việc chuyển giao quyền điều khiển cho các chương trình MQL, tức là việc thực thi chúng, diễn ra bằng cách gọi các hàm đặc biệt bởi terminal hoặc các tác nhân thử nghiệm, mà nhà phát triển MQL định nghĩa trong mã ứng dụng của họ để xử lý các sự kiện được xác định trước. Các hàm như vậy phải có một nguyên mẫu được chỉ định, bao gồm tên, danh sách tham số (số lượng, loại và thứ tự), và loại giá trị trả về.
Tên của mỗi hàm tương ứng với ý nghĩa của sự kiện, với việc thêm tiền tố On
. Ví dụ, OnStart
là hàm chính để "khởi động" scripts và services; nó được terminal gọi vào thời điểm script được đặt trên biểu đồ hoặc phiên bản service được khởi chạy.
Trong phạm vi cuốn sách này, chúng ta sẽ gọi một sự kiện và hàm xử lý tương ứng của nó bằng cùng một tên.
Bảng sau liệt kê tất cả các loại sự kiện và các chương trình hỗ trợ chúng:
- chỉ báo,
- Expert Advisor,
- script,
- service.
Mô tả chi tiết về các sự kiện được đưa ra trong các phần của các loại chương trình tương ứng. Nhiều yếu tố có thể gây ra các sự kiện khởi tạo và hủy khởi tạo: đặt chương trình lên biểu đồ, thay đổi cài đặt của nó, thay đổi ký hiệu/khung thời gian của biểu đồ (hoặc mẫu, hoặc hồ sơ), thay đổi tài khoản, và các yếu tố khác (xem chương Tính năng khởi động và dừng các chương trình thuộc nhiều loại khác nhau).
Sự kiện/Hàm xử lý | ![]() | ![]() | ![]() | ![]() | Mô tả |
---|---|---|---|---|---|
OnStart | - | - | ● | ● | Khởi động/Thực thi |
OnInit | + | + | - | - | Khởi tạo sau khi tải (xem chi tiết trong phần Tính năng khởi động và dừng các chương trình thuộc nhiều loại khác nhau) |
OnDeinit | + | + | - | - | Hủy khởi tạo trước khi dừng và dỡ bỏ |
OnTick | - | + | - | - | Nhận giá mới (tick) |
OnCalculate | ● | - | - | - | Yêu cầu tính toán lại chỉ báo do nhận giá mới hoặc đồng bộ hóa giá cũ |
OnTimer | + | + | - | - | Kích hoạt bộ đếm thời gian với tần suất được chỉ định |
OnTrade | - | + | - | - | Hoàn thành một hoạt động giao dịch trên máy chủ |
OnTradeTransaction | - | + | - | - | Thay đổi trạng thái tài khoản giao dịch (lệnh, giao dịch, vị thế) |
OnBookEvent | + | + | - | - | Thay đổi trong sổ lệnh |
OnChartEvent | + | + | - | - | Hành động của người dùng hoặc chương trình MQL trên biểu đồ |
OnTester | - | + | - | - | Kết thúc một lần chạy thử nghiệm |
OnTesterInit | - | + | - | - | Khởi tạo trước khi tối ưu hóa |
OnTesterDeinit | - | + | - | - | Hủy khởi tạo sau khi tối ưu hóa |
OnTesterPass | - | + | - | - | Nhận dữ liệu tối ưu hóa từ tác nhân thử nghiệm |
Các hàm xử lý bắt buộc được đánh dấu bằng ký hiệu '●', và các hàm xử lý tùy chọn được đánh dấu bằng '+'.
Mặc dù các hàm xử lý chủ yếu được thiết kế để được gọi bởi môi trường thực thi, bạn cũng có thể gọi chúng từ mã nguồn của mình. Ví dụ, nếu một Expert Advisor cần thực hiện một số tính toán dựa trên báo giá có sẵn ngay sau khi khởi động, ngay cả khi không có tick (ví dụ, vào cuối tuần), bạn có thể gọi OnTick
trước khi rời khỏi OnInit
. Ngoài ra, sẽ hợp lý khi tách tính toán thành một hàm riêng và gọi nó từ cả OnInit
và OnTick
. Tuy nhiên, mong muốn là thực hiện công việc của hàm khởi tạo nhanh chóng, và nếu tính toán kéo dài, nó nên được thực hiện trên một bộ đếm thời gian.
Tất cả các chương trình MQL (trừ thư viện) phải có ít nhất một hàm xử lý sự kiện. Nếu không, trình biên dịch sẽ tạo ra lỗi "không tìm thấy hàm xử lý sự kiện".
Sự hiện diện của một số hàm xử lý xác định loại chương trình trong trường hợp không có chỉ thị #property
đặt loại khác. Ví dụ, việc có hàm xử lý OnCalculate
dẫn đến việc tạo ra chỉ báo (ngay cả khi nó nằm trong thư mục khác, ví dụ, scripts hoặc Expert Advisors). Sự hiện diện của hàm xử lý OnStart
(nếu không có OnCalculate
) có nghĩa là tạo ra một script. Đồng thời, nếu chỉ báo, ngoài OnCalculate
, gặp phải OnStart
, chúng ta nhận được cảnh báo của trình biên dịch "Hàm OnStart được định nghĩa trong chương trình không phải script".
Cuốn sách bao gồm hai tệp: AllInOne.mq5
và AllInOne.mqh
. Tệp tiêu đề mô tả các mẫu gần như trống của tất cả các hàm xử lý sự kiện chính. Chúng không chứa gì ngoài việc xuất tên của hàm xử lý ra nhật ký. Chúng ta sẽ xem xét cú pháp và đặc thù của việc sử dụng từng hàm xử lý trong các phần về các loại chương trình MQL cụ thể. Ý nghĩa của tệp này là cung cấp một sân chơi để thử nghiệm với việc biên dịch các loại chương trình khác nhau, tùy thuộc vào sự hiện diện của một số hàm xử lý và chỉ thị thuộc tính (#property
).
Một số kết hợp có thể dẫn đến lỗi hoặc cảnh báo.
Nếu quá trình biên dịch thành công, thì loại chương trình kết quả sẽ tự động được ghi vào nhật ký sau khi nó được tải bằng dòng sau:
const string type = PRTF(EnumToString((ENUM_PROGRAM_TYPE)MQLInfoInteger(MQL_PROGRAM_TYPE)));
Chúng ta đã nghiên cứu enum ENUM_PROGRAM_TYPE và hàm MQLInfoInteger
trong phần Loại chương trình và giấy phép.
Tệp AllInOne.mq5
, bao gồm AllInOne.mqh
, ban đầu nằm trong thư mục MQL5Book/Scripts/p5/
, nhưng nó có thể được sao chép vào bất kỳ thư mục nào khác, bao gồm các nhánh lân cận của Navigator
(ví dụ, đến thư mục của Expert Advisors hoặc chỉ báo). Bên trong tệp, trong các chú thích, các tùy chọn được để lại để kết nối một số cấu hình lắp ráp chương trình nhất định. Theo mặc định, nếu bạn không chỉnh sửa tệp, bạn sẽ nhận được một Expert Advisor.
//+------------------------------------------------------------------+
//| Uncomment the following line to get the service |
//| NB: also activate #define _OnStart OnStart |
//+------------------------------------------------------------------+
//#property service
//+------------------------------------------------------------------+
//| Uncomment the following line to get a library |
//+------------------------------------------------------------------+
//#property library
//+------------------------------------------------------------------+
//| Uncomment the following line to get a script or |
//| service (#property service must be enabled) |
//+------------------------------------------------------------------+
//#define _OnStart OnStart
//+------------------------------------------------------------------+
//| Uncomment one of the following two lines for the indicator |
//+------------------------------------------------------------------+
//#define _OnCalculate1 OnCalculate
//#define _OnCalculate2 OnCalculate
#include <MQL5Book/AllInOne.mqh>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Nếu chúng ta gắn chương trình vào biểu đồ, chúng ta sẽ nhận được một mục trong nhật ký:
EnumToString((ENUM_PROGRAM_TYPE)MQLInfoInteger(MQL_PROGRAM_TYPE))=PROGRAM_EXPERT / ok
OnInit
OnChartEvent
OnTick
OnTick
OnTick
...
2
3
4
5
6
7
Ngoài ra, rất có thể, một luồng bản ghi sẽ được tạo ra từ hàm xử lý OnTick
nếu thị trường đang mở.
Nếu bạn sao chép tệp mq5 dưới một tên khác và, ví dụ, bỏ chú thích chỉ thị #property service
, trình biên dịch sẽ tạo ra service nhưng sẽ trả về một vài cảnh báo.
no OnStart function defined in the script
OnInit function is useless for scripts
OnDeinit function is useless for scripts
2
3
Cảnh báo đầu tiên, về việc không có hàm OnStart
, thực sự quan trọng, vì khi một phiên bản service được tạo, không có hàm nào sẽ được gọi trong đó, mà chỉ các biến toàn cục sẽ được khởi tạo. Tuy nhiên, nhờ điều này, nhật ký (tab Experts
trong terminal) vẫn sẽ in ra loại PROGRAM_SERVICE. Nhưng theo quy tắc, trong services, cũng như trong scripts,假定假定 có sự hiện diện của hàm OnStart
.
Hai cảnh báo còn lại xuất hiện vì tệp tiêu đề của chúng ta chứa các hàm xử lý cho mọi trường hợp, và trình biên dịch nhắc nhở chúng ta rằng OnInit
và OnDeinit
không có ý nghĩa (sẽ không được terminal gọi và thậm chí không được bao gồm trong hình ảnh nhị phân của chương trình). Tất nhiên, trong các chương trình thực tế không nên có những cảnh báo như vậy, tức là tất cả các hàm xử lý nên được sử dụng, và mọi thứ thừa thãi nên được loại bỏ khỏi mã nguồn, hoặc bằng cách vật lý hoặc logic, sử dụng các chỉ thị tiền xử lý cho việc biên dịch có điều kiện.
Nếu bạn tạo một bản sao khác của AllInOne.mq5 và kích hoạt không chỉ chỉ thị #property service
mà còn macro #define _OnStart OnStart
, bạn sẽ nhận được một service hoạt động đầy đủ sau khi biên dịch. Khi khởi chạy, nó sẽ không chỉ hiển thị tên loại của nó mà còn tên của hàm xử lý được kích hoạt OnStart
.
Macro là cần thiết để có thể bật/tắt hàm xử lý chuẩn OnStart
nếu họ muốn. Trong văn bản AllInOne.mqh
, hàm này được mô tả như sau:
void _OnStart() // "extra" underline makes the function customized
{
Print(__FUNCTION__);
}
2
3
4
Tên bắt đầu bằng dấu gạch dưới khiến nó không phải là một hàm xử lý chuẩn, mà chỉ là một hàm do người dùng định nghĩa với nguyên mẫu tương tự. Khi chúng ta bao gồm một macro, trong quá trình biên dịch, trình biên dịch thay thế _OnStart
bằng OnStart
, và kết quả đã là một hàm xử lý chuẩn. Nếu chúng ta đặt tên rõ ràng cho hàm OnStart
, thì theo thứ tự ưu tiên của các đặc điểm xác định loại chương trình MQL (xem phần Tính năng của các chương trình MQL thuộc nhiều loại khác nhau), nó sẽ không cho phép bạn nhận được mẫu Expert Advisor (vì OnStart
xác định chương trình là script hoặc service).
Việc biên dịch tùy chỉnh tương tự với các macro _OnCalculate1
hoặc _OnCalculate2
cần thiết để tùy chọn "ẩn" hàm xử lý với tên chuẩn OnCalculate
: nếu không, nếu nó có mặt, chúng ta sẽ luôn nhận được một chỉ báo.
Nếu trong bản sao tiếp theo của chương trình, bạn kích hoạt macro #define _OnCalculate1 OnCalculate
, bạn sẽ nhận được một ví dụ chỉ báo (mặc dù nó trống và không làm gì). Như chúng ta sẽ thấy sau này, có hai dạng khác nhau của hàm xử lý OnCalculate
cho các chỉ báo, liên quan đến việc chúng được trình bày dưới các tên được đánh số (_OnCalculate1
và _OnCalculate2
). Nếu bạn chạy chỉ báo trên biểu đồ, bạn có thể thấy trong nhật ký các tên của sự kiện OnCalculate
(khi có tick đến) và OnChartEvent
(ví dụ, khi nhấp chuột).
Khi biên dịch chỉ báo, trình biên dịch sẽ tạo ra hai cảnh báo:
no indicator window property is defined, indicator_chart_window is applied
no indicator plot defined for indicator
2
Điều này là do các chỉ báo, với tư cách là công cụ trực quan hóa dữ liệu, yêu cầu một số cài đặt cụ thể trong mã của chúng mà ở đây không có. Ở giai đoạn làm quen sơ bộ với các loại chương trình khác nhau này, điều này không quan trọng. Nhưng tiếp theo, chúng ta sẽ học cách mô tả các thuộc tính và mảng của chúng trong các chỉ báo, xác định cái gì và cách thức nên được trực quan hóa trên biểu đồ. Sau đó, các cảnh báo này sẽ biến mất.
Hàng đợi sự kiện
Khi một sự kiện mới xảy ra, nó phải được gửi đến tất cả các chương trình MQL đang chạy trên biểu đồ tương ứng. Do mô hình thực thi đơn luồng của các chương trình MQL (xem phần Luồng), có thể xảy ra trường hợp sự kiện tiếp theo đến khi sự kiện trước đó vẫn đang được xử lý. Trong những trường hợp như vậy, terminal duy trì một hàng đợi sự kiện cho mỗi chương trình MQL tương tác. Tất cả các sự kiện trong đó được xử lý lần lượt theo thứ tự nhận được.
Hàng đợi sự kiện có kích thước giới hạn. Do đó, một chương trình được viết không hợp lý có thể gây tràn hàng đợi của nó do các hành động chậm chạp. Khi tràn, các sự kiện mới bị loại bỏ mà không được xếp vào hàng đợi.
Việc không xử lý sự kiện đủ nhanh có thể ảnh hưởng tiêu cực đến trải nghiệm người dùng hoặc chất lượng dữ liệu (hãy tưởng tượng bạn ghi lại các thay đổi Sâu thị trường và bỏ qua một vài tin nhắn). Để giải quyết vấn đề này, bạn có thể tìm kiếm các thuật toán hiệu quả hơn hoặc sử dụng hoạt động song song của nhiều chương trình MQL liên kết với nhau (ví dụ, giao các tính toán cho một chỉ báo, và chỉ đọc dữ liệu sẵn sàng trong một Expert Advisor).
Cần lưu ý rằng terminal không đặt tất cả các sự kiện vào hàng đợi mà hoạt động có chọn lọc. Một số loại sự kiện được xử lý theo nguyên tắc "không quá một sự kiện loại này trong hàng đợi". Ví dụ, nếu đã có sự kiện OnTick
trong hàng đợi, hoặc nó đang được xử lý, thì sự kiện OnTick
mới sẽ không được xếp vào hàng đợi. Nếu đã có sự kiện OnTimer
hoặc sự kiện thay đổi biểu đồ trong hàng đợi, thì các sự kiện mới của các loại này cũng bị loại bỏ (bỏ qua). Điều này liên quan đến một phiên bản cụ thể của chương trình. Các chương trình khác, ít "bận rộn" hơn, sẽ nhận được tin nhắn này.
Chúng ta không cung cấp danh sách đầy đủ các loại sự kiện như vậy vì tối ưu hóa này bằng cách bỏ qua các sự kiện "chồng lấp" có thể được thay đổi bởi các nhà phát triển terminal.
Cách tiếp cận tổ chức công việc của các chương trình để phản hồi các sự kiện đến được gọi là điều khiển bởi sự kiện. Nó cũng có thể được gọi là không đồng bộ vì việc xếp hàng đợi của một sự kiện trong hàng đợi chương trình và việc trích xuất (cùng với xử lý) diễn ra vào các thời điểm khác nhau (lý tưởng nhất, cách nhau một khoảng thời gian cực nhỏ, nhưng lý tưởng không phải lúc nào cũng đạt được). Tuy nhiên, trong số bốn loại chương trình MQL, chỉ có chỉ báo và Expert Advisors hoàn toàn tuân theo cách tiếp cận này. Scripts và services thực tế chỉ có hàm chính, khi được gọi, phải nhanh chóng thực hiện hành động cần thiết và hoàn thành hoặc bắt đầu một vòng lặp vô tận để duy trì một số hoạt động (ví dụ, đọc dữ liệu từ mạng) cho đến khi người dùng dừng lại. Chúng ta đã thấy các ví dụ về những vòng lặp như vậy:
while(!IsStopped())
{
useful code
...
Sleep(...);
}
2
3
4
5
6
Trong các vòng lặp như vậy, điều quan trọng là không quên sử dụng Sleep
với một khoảng thời gian nào đó để chia sẻ tài nguyên CPU với các chương trình khác. Giá trị của khoảng thời gian được chọn dựa trên cường độ ước tính của hoạt động đang được triển khai.
Cách tiếp cận này có thể được gọi là chu kỳ hoặc đồng bộ, hoặc thậm chí là thời gian thực, vì bạn có thể chọn khoảng thời gian ngủ để cung cấp tần suất xử lý dữ liệu không đổi, ví dụ:
int rhythm = 100; // 100 ms, 10 lần mỗi giây
while(!IsStopped())
{
const int start = (int)GetTickCount();
useful code
...
Sleep(rhythm - ((int)GetTickCount() - start));
}
2
3
4
5
6
7
8
Tất nhiên, "mã hữu ích" phải phù hợp với khung thời gian được phân bổ.
Ngược lại, với cách tiếp cận sự kiện, không thể biết trước khi nào đoạn mã (hàm xử lý) sẽ hoạt động lần tiếp theo. Ví dụ, trong một thị trường nhanh, trong thời gian có tin tức, các tick có thể đến theo lô, và vào ban đêm chúng có thể vắng mặt trong nhiều giây. Trong trường hợp giới hạn, sau tick cuối cùng vào tối thứ Sáu, thay đổi giá tiếp theo cho một số công cụ tài chính có thể chỉ được phát vào sáng thứ Hai, và do đó các sự kiện OnTick
sẽ vắng mặt trong hai ngày. Nói cách khác, trong các sự kiện (và thời điểm kích hoạt các hàm xử lý sự kiện) không có tính đều đặn, không có lịch trình rõ ràng.
Nhưng nếu cần, bạn có thể kết hợp cả hai cách tiếp cận. Đặc biệt, sự kiện bộ đếm thời gian (OnTimer) cung cấp tính đều đặn, và nhà phát triển có thể định kỳ tạo ra sự kiện tùy chỉnh cho biểu đồ bên trong một vòng lặp (ví dụ, nhấp nháy một nhãn cảnh báo).