Chỉ thị tiền xử lý cho tester
Trong phần về Thuộc tính chung của chương trình, chúng ta đã làm quen lần đầu với các chỉ thị #property
trong các chương trình MQL. Sau đó, chúng ta đã gặp các chỉ thị dành cho script, dịch vụ, và chỉ báo. Ngoài ra, còn có một nhóm chỉ thị dành cho tester. Chúng ta đã đề cập đến một số trong số đó. Ví dụ, chỉ thị tester_everytick_calculate ảnh hưởng đến việc tính toán các chỉ báo.
Bảng dưới đây liệt kê tất cả các chỉ thị tester kèm theo giải thích.
Chỉ thị | Mô tả |
---|---|
tester_indicator "string" | Tên của chỉ báo tùy chỉnh theo định dạng "indicator_name.ex5" |
tester_file "string" | Tên tệp theo định dạng "file_name.extension" chứa dữ liệu ban đầu cần thiết cho bài kiểm tra chương trình |
tester_library "string" | Tên thư viện với phần mở rộng như "library.ex5" hoặc "library.dll" |
tester_set "string" | Tên tệp theo định dạng "file_name.set" chứa cài đặt cho giá trị và phạm vi tối ưu hóa của các tham số đầu vào chương trình |
tester_no_cache | Tắt việc đọc bộ nhớ cache hiện có của các lần tối ưu hóa trước đó (tệp opt) |
tester_everytick_calculate | Tắt chế độ tiết kiệm tài nguyên để tính toán chỉ báo trong tester |
Hai chỉ thị cuối không có đối số. Tất cả các chỉ thị khác yêu cầu một chuỗi được đặt trong dấu ngoặc kép với tên của một tệp thuộc một loại nào đó. Điều này cũng ngụ ý rằng các chỉ thị có thể được lặp lại với các tệp khác nhau, tức là bạn có thể bao gồm nhiều tệp cài đặt hoặc nhiều chỉ báo.
Chỉ thị tester_indicator
cần thiết để kết nối với quá trình kiểm tra những chỉ báo không được đề cập trong mã nguồn của chương trình đang thử nghiệm dưới dạng chuỗi hằng (literals). Thông thường, chỉ báo cần thiết có thể được trình biên dịch tự động xác định từ các lệnh gọi iCustom
nếu tên của nó được chỉ định rõ ràng trong tham số tương ứng, ví dụ, iCustom(symbol, period, "indicator_name",...)
. Tuy nhiên, điều này không phải lúc nào cũng đúng.
Giả sử chúng ta đang viết một Expert Advisor đa năng có thể sử dụng các chỉ báo đường trung bình động khác nhau, không chỉ các chỉ báo tích hợp sẵn tiêu chuẩn. Khi đó, chúng ta có thể tạo một biến đầu vào để người dùng chỉ định tên của chỉ báo. Sau đó, lệnh gọi iCustom
sẽ trở thành iCustom(symbol, period, CustomIndicatorName,...)
, trong đó CustomIndicatorName
là một biến đầu vào của Expert Advisor, nội dung của nó không được biết tại thời điểm biên dịch. Hơn nữa, trong trường hợp này, nhà phát triển có khả năng sẽ áp dụng IndicatorCreate
thay vì iCustom
, vì số lượng và loại tham số của chỉ báo cũng phải được cấu hình. Trong những trường hợp như vậy, để gỡ lỗi chương trình hoặc thể hiện nó với một chỉ báo cụ thể, chúng ta nên cung cấp tên cho tester bằng chỉ thị tester_indicator
.
Việc cần báo cáo tên chỉ báo trong mã nguồn hạn chế đáng kể khả năng kiểm tra các chương trình đa năng như vậy, vốn có thể kết nối các chỉ báo khác nhau trực tuyến.
Nếu không có chỉ thị tester_indicator
, terminal sẽ không thể gửi một chỉ báo đến agent mà không được khai báo rõ ràng trong mã nguồn, dẫn đến việc chương trình phụ thuộc sẽ mất một phần hoặc toàn bộ chức năng của nó.
Chỉ thị tester_file
cho phép bạn chỉ định một tệp sẽ được chuyển đến các agent và đặt trong sandbox trước khi kiểm tra. Nội dung và loại tệp không bị quy định. Ví dụ, đó có thể là trọng số của một mạng nơ-ron đã được huấn luyện trước, dữ liệu Độ sâu Thị trường đã thu thập trước (vì dữ liệu như vậy không thể được tester tái tạo), v.v.
Lưu ý rằng tệp từ chỉ thị
tester_file
chỉ được đọc nếu nó đã tồn tại tại thời điểm biên dịch. Nếu mã nguồn được biên dịch khi không có tệp tương ứng, thì việc tệp xuất hiện sau đó sẽ không còn hữu ích: chương trình đã biên dịch sẽ được gửi đến agent mà không có tệp phụ trợ. Do đó, ví dụ, nếu tệp được chỉ định trongtester_file
được tạo trongOnTesterInit
, bạn nên đảm bảo rằng tệp với tên đã cho đã tồn tại tại thời điểm biên dịch, ngay cả khi nó trống. Chúng ta sẽ trình bày điều này dưới đây.
Hãy lưu ý rằng trình biên dịch không tạo cảnh báo nếu tệp được chỉ định trong chỉ thị tester_file
không tồn tại.
Các tệp được kết nối phải nằm trong sandbox của terminal tại MQL5/Files/
.
Chỉ thị tester_library
thông báo cho tester về nhu cầu chuyển thư viện, là một chương trình phụ trợ chỉ có thể hoạt động trong bối cảnh của một chương trình MQL khác, đến các agent. Chúng ta sẽ nói chi tiết về thư viện trong một phần riêng.
Các thư viện cần thiết cho việc kiểm tra được xác định tự động bởi các chỉ thị #import
trong mã nguồn. Tuy nhiên, nếu bất kỳ thư viện nào được sử dụng bởi một chỉ báo bên ngoài, thì thuộc tính này phải được bật. Thư viện có thể có phần mở rộng dll
hoặc ex5
.
Chỉ thị tester_set
hoạt động với các tệp set
chứa cài đặt của chương trình MQL. Tệp được chỉ định trong chỉ thị sẽ trở nên khả dụng từ menu ngữ cảnh của tester và cho phép người dùng áp dụng nhanh các cài đặt.
Nếu tên được chỉ định mà không có đường dẫn, tệp set
phải nằm trong cùng thư mục với Expert Advisor. Điều này hơi bất ngờ, vì thư mục mặc định cho các tệp set
là Presets
, và đây là nơi chúng được lưu bởi các lệnh từ giao diện terminal. Để kết nối tệp set
từ thư mục đã cho, bạn phải chỉ định rõ ràng trong chỉ thị và thêm dấu gạch chéo trước nó, điều này biểu thị đường dẫn tuyệt đối bên trong thư mục MQL5.
#property tester_set "/Presets/xyz.set"
Khi không có dấu gạch chéo đầu tiên, đường dẫn là tương đối so với nơi đặt văn bản nguồn.
Ngay sau khi thêm tệp và biên dịch lại chương trình, bạn cần chọn lại Expert Advisor trong tester; nếu không, tệp sẽ không được nhận diện!
Nếu bạn chỉ định tên Expert Advisor và số phiên bản dưới dạng <expert_name>_<number>.set
trong tên của tệp set
, thì nó sẽ tự động được thêm vào menu tải phiên bản tham số dưới số phiên bản <number>
. Ví dụ, tên MACD Sample_4.set
có nghĩa là đó là tệp set
cho Expert Advisor MACD Sample.mq5
với số phiên bản 4.
Những ai quan tâm có thể nghiên cứu định dạng của các tệp set
: để làm điều này, hãy lưu thủ công cài đặt kiểm tra/tối ưu hóa trong strategy tester và sau đó mở tệp được tạo theo cách này trong trình chỉnh sửa văn bản.
Bây giờ hãy xem xét chỉ thị tester_no_cache
. Khi thực hiện tối ưu hóa, strategy tester lưu tất cả kết quả của các lần chạy đã thực hiện vào bộ nhớ cache tối ưu hóa (tệp với phần mở rộng opt
), trong đó kết quả kiểm tra được lưu trữ cho mỗi tập hợp tham số đầu vào. Điều này cho phép, khi tối ưu hóa lại trên cùng các tham số, lấy kết quả sẵn có mà không cần tính toán lại và mất thời gian.
Tuy nhiên, đối với một số tác vụ, chẳng hạn như tính toán toán học, có thể cần thực hiện tính toán bất kể sự hiện diện của kết quả sẵn có trong bộ nhớ cache tối ưu hóa. Trong trường hợp này, trong mã nguồn, bạn phải bao gồm thuộc tính tester_no_cache
. Đồng thời, kết quả kiểm tra vẫn sẽ được lưu trong bộ nhớ cache để bạn có thể xem tất cả dữ liệu về các lần chạy đã hoàn thành trong strategy tester.
Chỉ thị tester_everytick_calculate
được thiết kế để kích hoạt chế độ tính toán chỉ báo trên mỗi tick trong tester.
Theo mặc định, các chỉ báo chỉ được tính toán trong tester khi chúng được truy cập để lấy dữ liệu, tức là khi giá trị của bộ đệm chỉ báo được yêu cầu. Điều này mang lại sự tăng tốc đáng kể trong việc kiểm tra và tối ưu hóa nếu bạn không cần lấy giá trị chỉ báo tại mỗi tick.
Tuy nhiên, một số chương trình có thể yêu cầu các chỉ báo được tính toán lại trên mỗi tick. Chính trong những trường hợp như vậy, thuộc tính tester_everytick_calculate
hữu ích.
Các chỉ báo trong strategy tester cũng bị buộc tính toán trên mỗi tick trong các trường hợp sau:
- Khi kiểm tra ở chế độ trực quan
- Nếu có các hàm
EventChartCustom
,OnChartEvent
, hoặcOnTimer
trong chỉ báo
Thuộc tính này chỉ áp dụng cho các hoạt động trong strategy tester. Trong terminal, các chỉ báo luôn được tính toán trên mỗi tick đến.
Chỉ thị này thực sự đã được sử dụng trong Expert Advisor FrameTransfer.mq5:
#property tester_set "FrameTransfer.set"
Chúng ta chỉ không tập trung vào nó. Tệp FrameTransfer.set
nằm cạnh mã nguồn. Trong cùng Expert Advisor đó, chúng ta cũng cần một chỉ thị khác từ bảng trên:
#property tester_no_cache
Ngoài ra, hãy xem xét một ví dụ về chỉ thị tester_file
. Trước đó trong phần về tự động điều chỉnh tham số Expert Advisor khi tối ưu hóa, chúng ta đã giới thiệu BandOsMApro.mq5
, trong đó cần đưa vào một số tham số bóng để truyền phạm vi tối ưu hóa đến mã nguồn của chúng ta chạy trên các agent.
Chỉ thị tester_file
sẽ cho phép chúng ta loại bỏ những tham số thừa này. Hãy đặt tên phiên bản mới là BandOsMAprofile.mq5
.
Vì chúng ta hiện đã quen với chỉ thị tester_set
, hãy thêm vào phiên bản mới tệp đã đề cập trước đó /Presets/MQL5Book/BandOsMA.set
.
#property tester_set "/Presets/MQL5Book/BandOsMA.set"
Thông tin về phạm vi và bước thay đổi chu kỳ của FastOsMA
và SlowOsMA
sẽ được lưu vào tệp BandOsMAprofile.csv
thay vì ba tham số đầu vào bổ sung FastShadow4Optimization
, SlowShadow4Optimization
, StepsShadow4Optimization
.
#define SETTINGS_FILE "BandOsMAprofile.csv"
#property tester_file SETTINGS_FILE
const string SettingsFile = SETTINGS_FILE;
2
3
4
Tham số bóng FastSlowCombo4Optimization
vẫn cần thiết cho việc liệt kê đầy đủ các kết hợp chu kỳ được phép.
input group "A U X I L I A R Y"
sinput int FastSlowCombo4Optimization = 0; // (reserved for optimization)
2
Hãy nhớ rằng chúng ta tìm phạm vi của nó để tối ưu hóa trong hàm Iterate
. Lần đầu tiên chúng ta gọi nó trong OnTesterInit
với việc liệt kê đầy đủ các kết hợp của chu kỳ nhanh và chậm.
Về cơ bản, chúng ta có thể lưu trữ tất cả các kết hợp hợp lệ trong mảng cấu trúc PairOfPeriods
và ghi nó vào một tệp nhị phân để truyền đến các agent. Sau đó, trên các agent, Expert Advisor của chúng ta có thể đọc mảng sẵn sàng từ tệp và bằng chỉ số FastSlowCombo4Optimization
trích xuất cặp tương ứng của FastOsMA
và SlowOsMA
từ mảng.
Thay vào đó, chúng ta sẽ tập trung vào thay đổi tối thiểu trong logic hoạt động của chương trình: chúng ta sẽ tiếp tục khôi phục một cặp chu kỳ do lần gọi thứ hai Iterate
trong trình xử lý OnInit
. Lần này, chúng ta sẽ lấy phạm vi và bước liệt kê giá trị chu kỳ không từ các tham số bóng, mà từ tệp CSV.
Dưới đây là các thay đổi đối với OnTesterInit
.
int OnTesterInit()
{
...
// kiểm tra xem tệp đã tồn tại trước khi biên dịch chưa
// - nếu không, tester sẽ không thể gửi nó đến các agent
const bool preExisted = FileIsExist(SettingsFile);
// ghi cài đặt vào một tệp để chuyển đến các chương trình sao chép trên agent
int handle = FileOpen(SettingsFile, FILE_WRITE | FILE_CSV | FILE_ANSI, ",");
FileWrite(handle, "FastOsMA", start1, step1, stop1);
FileWrite(handle, "SlowOsMA", start2, step2, stop2);
FileClose(handle);
if(!preExisted)
{
PrintFormat("Required file %s is missing. It has been just created."
" Please restart again.",
SettingsFile);
ChartClose();
return INIT_FAILED;
}
...
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
Lưu ý rằng chúng ta đã làm cho trình xử lý OnTesterInit
có kiểu trả về int
, điều này cho phép hủy tối ưu hóa nếu tệp không tồn tại. Tuy nhiên, trong mọi trường hợp, dữ liệu thực tế được ghi vào tệp, vì vậy nếu nó chưa tồn tại, giờ đây nó đã được tạo, và lần khởi động tối ưu hóa tiếp theo chắc chắn sẽ thành công.
Nếu bạn muốn bỏ qua bước này, bạn có thể tạo trước một tệp trống MQL5/Files/BandOsMAprofile.csv
.
Trình xử lý OnInit
đã được thay đổi như sau.
int OnInit()
{
if(FastOsMA >= SlowOsMA) return INIT_PARAMETERS_INCORRECT;
PairOfPeriods p = {FastOsMA, SlowOsMA}; // tham số ban đầu mặc định
int handle = FileOpen(SettingsFile, FILE_READ | FILE_TXT | FILE_ANSI);
// trong quá trình tối ưu hóa, cần một tệp với các tham số bóng
if(MQLInfoInteger(MQL_OPTIMIZATION) && handle == INVALID_HANDLE)
{
return INIT_PARAMETERS_INCORRECT;
}
if(handle != INVALID_HANDLE)
{
if(FastSlowCombo4Optimization != -1)
{
// nếu có bản sao bóng, đọc giá trị chu kỳ từ đó
const string line1 = FileReadString(handle);
string settings[];
if(StringSplit(line1, ',', settings) == 4)
{
int FastStart = (int)StringToInteger(settings[1]);
int FastStep = (int)StringToInteger(settings[2]);
int FastStop = (int)StringToInteger(settings[3]);
const string line2 = FileReadString(handle);
if(StringSplit(line2, ',', settings) == 4)
{
int SlowStart = (int)StringToInteger(settings[1]);
int SlowStep = (int)StringToInteger(settings[2]);
int SlowStop = (int)StringToInteger(settings[3]);
p = Iterate(FastStart, FastStop, FastStep,
SlowStart, SlowStop, SlowStep, FastSlowCombo4Optimization);
PrintFormat("MA periods are restored from shadow: FastOsMA=%d SlowOsMA=%d",
p.fast, p.slow);
}
}
}
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
Khi chạy các bài kiểm tra đơn lẻ sau khi tối ưu hóa, chúng ta sẽ thấy các giá trị chu kỳ đã giải mã FastOsMA
và SlowOsMA
trong nhật ký dựa trên giá trị tối ưu hóa FastSlowCombo4Optimization
. Trong tương lai, chúng ta có thể thay thế các giá trị này vào các tham số chu kỳ và xóa tệp csv. Chúng ta cũng đã quy định rằng tệp sẽ không được tính đến nếu FastSlowCombo4Optimization
được đặt thành -1.