Sự kiện OnTester
Sự kiện OnTester
được tạo ra khi việc kiểm tra Expert Advisor trên dữ liệu lịch sử hoàn tất (bao gồm cả lần chạy tester riêng lẻ do người dùng khởi tạo và một trong nhiều lần chạy tự động được tester khởi động trong quá trình tối ưu hóa). Để xử lý sự kiện OnTester
, một chương trình MQL phải có hàm tương ứng trong mã nguồn của nó, nhưng điều này không bắt buộc. Ngay cả khi không có hàm OnTester
, các Expert Advisor vẫn có thể được tối ưu hóa thành công dựa trên các tiêu chí tiêu chuẩn.
Hàm này chỉ có thể được sử dụng trong Expert Advisor.
double OnTester()
Hàm này được thiết kế để tính toán một giá trị kiểu double
, được sử dụng làm tiêu chí tối ưu hóa tùy chỉnh (Custom max
). Việc lựa chọn tiêu chí rất quan trọng, chủ yếu để tối ưu hóa di truyền thành công, đồng thời cũng cho phép người dùng đánh giá và so sánh hiệu quả của các cài đặt khác nhau.
Trong tối ưu hóa di truyền, kết quả được sắp xếp trong một thế hệ theo thứ tự giảm dần của tiêu chí. Nghĩa là, các kết quả có giá trị cao nhất được coi là tốt nhất theo tiêu chí tối ưu hóa. Các giá trị tệ nhất trong cách sắp xếp này sau đó bị loại bỏ và không tham gia vào việc hình thành thế hệ tiếp theo.
Xin lưu ý rằng các giá trị do hàm
OnTester
trả về chỉ được tính đến khi tiêu chí tùy chỉnh được chọn trong cài đặt tester. Sự hiện diện của hàmOnTester
không tự động có nghĩa là nó được thuật toán di truyền sử dụng.API MQL5 không cung cấp phương tiện để lập trình tìm hiểu tiêu chí tối ưu hóa nào mà người dùng đã chọn trong cài đặt tester. Đôi khi việc biết điều này rất quan trọng để thực hiện các thuật toán phân tích của riêng bạn nhằm xử lý hậu kỳ kết quả tối ưu hóa.
Hàm này được nhân kernel gọi chỉ trong tester, ngay trước khi gọi hàm OnDeinit
.
Để tính giá trị trả về, chúng ta có thể sử dụng cả các thống kê tiêu chuẩn có sẵn thông qua hàm TesterStatistics
và các phép tính tùy ý của chúng.
Trong Expert Advisor BandOsMA.mq5
, chúng ta tạo trình xử lý OnTester
xem xét một số chỉ số: lợi nhuận, độ sinh lời, số lượng giao dịch và tỷ lệ Sharpe. Tiếp theo, chúng ta nhân tất cả các chỉ số sau khi lấy căn bậc hai của từng chỉ số. Tất nhiên, mỗi nhà phát triển có thể có sở thích và ý tưởng riêng để xây dựng các tiêu chí chất lượng tổng quát như vậy.
double sign(const double x)
{
return x > 0 ? +1 : (x < 0 ? -1 : 0);
}
double OnTester()
{
const double profit = TesterStatistics(STAT_PROFIT);
return sign(profit) * sqrt(fabs(profit))
* sqrt(TesterStatistics(STAT_PROFIT_FACTOR))
* sqrt(TesterStatistics(STAT_TRADES))
* sqrt(fabs(TesterStatistics(STAT_SHARPE_RATIO)));
}
2
3
4
5
6
7
8
9
10
11
12
13
Nhật ký kiểm tra đơn vị hiển thị một dòng với giá trị của hàm OnTester
.
Hãy khởi động tối ưu hóa di truyền của Expert Advisor cho năm 2021 trên EURUSD, H1 với việc lựa chọn các tham số chỉ báo và kích thước dừng lỗ (tệp MQL5/Presets/MQL5Book/BandOsMA.set
được cung cấp cùng với sách). Để kiểm tra chất lượng tối ưu hóa, chúng ta cũng sẽ bao gồm các bài kiểm tra forward từ đầu năm 2022 (5 tháng).
Đầu tiên, hãy tối ưu hóa theo tiêu chí của chúng ta.
Như bạn đã biết, MetaTrader 5 lưu tất cả các tiêu chí tiêu chuẩn trong kết quả tối ưu hóa ngoài tiêu chí hiện tại được sử dụng trong quá trình tối ưu hóa. Điều này cho phép, sau khi hoàn thành tối ưu hóa, phân tích kết quả từ các góc độ khác nhau bằng cách chọn các tiêu chí nhất định từ danh sách thả xuống ở góc trên bên phải của bảng điều khiển. Do đó, mặc dù chúng ta đã thực hiện tối ưu hóa theo tiêu chí của riêng mình, tiêu chí phức hợp tích hợp sẵn thú vị nhất cũng có sẵn cho chúng ta.
Chúng ta có thể xuất bảng tối ưu hóa sang tệp XML, trước tiên với tiêu chí của chúng ta được chọn, sau đó với tiêu chí phức hợp và đặt tên mới cho tệp (đáng tiếc là chỉ một tiêu chí được ghi vào tệp xuất; điều quan trọng là không thay đổi thứ tự sắp xếp giữa hai lần xuất). Điều này cho phép kết hợp hai bảng trong một chương trình bên ngoài và xây dựng biểu đồ mà trên đó hai tiêu chí được vẽ dọc theo các trục; mỗi điểm ở đó biểu thị sự kết hợp của các tiêu chí trong một lần chạy.
So sánh tiêu chí tối ưu hóa tùy chỉnh và phức hợp
Trong tiêu chí phức hợp, chúng ta quan sát thấy một cấu trúc đa cấp, vì nó được tính toán theo công thức có điều kiện: ở một số nơi nhánh này hoạt động, và ở nơi khác nhánh khác hoạt động. Tiêu chí tùy chỉnh của chúng ta luôn được tính toán bằng cùng một công thức. Chúng ta cũng ghi nhận sự hiện diện của các giá trị âm trong tiêu chí của mình (điều này là dự kiến) và phạm vi được khai báo từ 0-100 cho tiêu chí phức hợp.
Hãy kiểm tra tiêu chí của chúng ta tốt như thế nào bằng cách phân tích các giá trị của nó trong giai đoạn forward.
Giá trị của tiêu chí tùy chỉnh trên các giai đoạn tối ưu hóa và kiểm tra forward
Như dự kiến, chỉ một phần các chỉ số tối ưu hóa tốt vẫn duy trì trong giai đoạn forward. Nhưng chúng ta quan tâm nhiều hơn không phải đến tiêu chí, mà là lợi nhuận. Hãy xem xét sự phân bố của nó trong mối liên kết tối ưu hóa-forward.
Lợi nhuận trên các giai đoạn tối ưu hóa và kiểm tra forward
Bức tranh ở đây tương tự. Trong số 6850 lần chạy có lợi nhuận trong giai đoạn tối ưu hóa, 3123 lần vẫn có lợi trong giai đoạn forward (45%). Và trong số 1000 lần tốt nhất đầu tiên, chỉ 323 lần có lợi nhuận, điều này không đủ tốt. Do đó, Expert Advisor này sẽ cần rất nhiều công việc để xác định các cài đặt ổn định sinh lời. Nhưng có lẽ đó là vấn đề của tiêu chí tối ưu hóa?
Hãy lặp lại quá trình tối ưu hóa, lần này sử dụng tiêu chí phức hợp tích hợp sẵn.
Chú ý! MetaTrader 5 tạo bộ nhớ đệm tối ưu hóa trong quá trình tối ưu hóa: các tệp opt tại
Tester/cache
. Khi bắt đầu tối ưu hóa tiếp theo, nó tìm kiếm các bộ nhớ đệm phù hợp để tiếp tục tối ưu hóa. Nếu có tệp bộ nhớ đệm với cài đặt trước đó, quá trình không bắt đầu từ đầu mà tính đến các kết quả trước đó. Điều này cho phép xây dựng các tối ưu hóa di truyền theo chuỗi, giả định rằng bạn tìm thấy các kết quả tốt nhất (suy cho cùng, mỗi tối ưu hóa di truyền là một quá trình ngẫu nhiên).MetaTrader 5 không coi tiêu chí tối ưu hóa là yếu tố phân biệt trong cài đặt. Điều này có thể hữu ích trong một số trường hợp, dựa trên những điều đã đề cập ở trên, nhưng sẽ cản trở nhiệm vụ hiện tại của chúng ta. Để tiến hành một thí nghiệm thuần túy, chúng ta cần tối ưu hóa từ đầu. Do đó, ngay sau lần tối ưu hóa đầu tiên sử dụng tiêu chí của chúng ta, chúng ta không thể khởi động lần thứ hai sử dụng tiêu chí phức hợp.
Không có cách nào để tắt hành vi hiện tại từ giao diện terminal. Do đó, bạn nên xóa hoặc đổi tên (thay đổi phần mở rộng) tệp opt trước đó theo cách thủ công trong bất kỳ trình quản lý tệp nào. Một chút sau, chúng ta sẽ làm quen với chỉ thị tiền xử lý cho tester
tester_no_cache
, có thể được chỉ định trong mã nguồn của một Expert Advisor cụ thể, cho phép tắt việc đọc bộ nhớ đệm.
So sánh các giá trị của tiêu chí phức hợp trên các giai đoạn tối ưu hóa và giai đoạn forward có dạng như sau.
Tiêu chí phức hợp trên các giai đoạn tối ưu hóa và kiểm tra forward
Đây là độ ổn định của lợi nhuận trên các giai đoạn forward.
Lợi nhuận trên các giai đoạn tối ưu hóa và kiểm tra forward
Trong số 5952 kết quả tích cực trong lịch sử, chỉ 2655 (khoảng 45%) vẫn có lãi. Nhưng trong số 1000 lần đầu tiên, 581 lần thành công trên giai đoạn forward.
Vì vậy, chúng ta đã thấy rằng việc sử dụng OnTester
khá đơn giản về mặt kỹ thuật, nhưng tiêu chí của chúng ta hoạt động kém hơn so với tiêu chí tích hợp sẵn (trong điều kiện tương đương), mặc dù nó còn xa lý tưởng. Do đó, từ quan điểm tìm kiếm công thức của chính tiêu chí và việc lựa chọn các tham số hợp lý sau đó mà không nhìn vào tương lai, có nhiều câu hỏi hơn về nội dung của OnTester
so với câu trả lời.
Ở đây, lập trình chuyển dần sang nghiên cứu và hoạt động khoa học, vượt ra ngoài phạm vi của cuốn sách này. Nhưng chúng ta sẽ đưa ra một ví dụ về tiêu chí được tính toán dựa trên chỉ số của riêng chúng ta, không phải trên các chỉ số có sẵn: TesterStatistics
. Chúng ta sẽ nói về tiêu chí R2, còn được gọi là hệ số xác định (RSquared.mqh
).
Hãy tạo một hàm để tính R2 từ đường cong số dư. Được biết rằng khi giao dịch với lô cố định, một hệ thống giao dịch lý tưởng nên hiển thị số dư dưới dạng một đường thẳng. Chúng ta hiện đang sử dụng lô cố định, vì vậy điều này sẽ phù hợp với chúng ta. Đối với R2 trong trường hợp lô thay đổi, chúng ta sẽ xử lý sau.
Cuối cùng, R2 là một thước đo ngược của phương sai dữ liệu so với hồi quy tuyến tính được xây dựng trên chúng. Phạm vi giá trị R2 nằm từ âm vô cực đến +1 (mặc dù các giá trị âm lớn rất khó xảy ra trong trường hợp của chúng ta). Rõ ràng là đường thẳng tìm được đồng thời được đặc trưng bởi một độ dốc, do đó, để tổng quát hóa mã, chúng ta sẽ lưu cả R2 và tiếp tuyến của góc trong cấu trúc R2A như một kết quả trung gian.
struct R2A
{
double r2; // bình phương của hệ số tương quan
double angle; // tiếp tuyến của độ dốc
R2A(): r2(0), angle(0) { }
};
2
3
4
5
6
Việc tính toán các chỉ số được thực hiện trong hàm RSquared
nhận một mảng dữ liệu làm đầu vào và trả về một cấu trúc R2A.
R2A RSquared(const double &data[])
{
int size = ArraySize(data);
if(size <= 2) return R2A();
double x, y, div;
int k = 0;
double Sx = 0, Sy = 0, Sxy = 0, Sx2 = 0, Sy2 = 0;
for(int i = 0; i < size; ++i)
{
if(data[i] == EMPTY_VALUE
|| !MathIsValidNumber(data[i])) continue;
x = i + 1;
y = data[i];
Sx += x;
Sy += y;
Sxy += x * y;
Sx2 += x * x;
Sy2 += y * y;
++k;
}
size = k;
const double Sx22 = Sx * Sx / size;
const double Sy22 = Sy * Sy / size;
const double SxSy = Sx * Sy / size;
div = (Sx2 - Sx22) * (Sy2 - Sy22);
if(fabs(div) < DBL_EPSILON) return R2A();
R2A result;
result.r2 = (Sxy - SxSy) * (Sxy - SxSy) / div;
result.angle = (Sxy - SxSy) / (Sx2 - Sx22);
return result;
}
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
Để tối ưu hóa, chúng ta cần một giá trị tiêu chí duy nhất, và ở đây góc rất quan trọng vì một đường cong số dư giảm đều với độ dốc âm cũng có thể nhận được đánh giá R2 tốt. Do đó, chúng ta sẽ viết thêm một hàm sẽ "thêm dấu trừ" vào bất kỳ đánh giá R2 nào có góc âm. Chúng ta lấy giá trị R2 theo mô-đun vì nó có thể âm trong trường hợp dữ liệu rất tệ (phân tán) không phù hợp với mô hình tuyến tính của chúng ta. Như vậy, chúng ta phải ngăn chặn tình huống mà dấu trừ nhân dấu trừ cho ra dấu cộng.
double RSquaredTest(const double &data[])
{
const R2A result = RSquared(data);
const double weight = 1.0 - 1.0 / sqrt(ArraySize(data) + 1);
if(result.angle < 0) return -fabs(result.r2) * weight;
return result.r2 * weight;
}
2
3
4
5
6
7
Ngoài ra, tiêu chí của chúng ta tính đến kích thước của chuỗi, tương ứng với số lượng giao dịch. Do đó, việc tăng số lượng giao dịch sẽ làm tăng chỉ số.
Với công cụ này trong tay, chúng ta sẽ triển khai hàm tính toán đường cong số dư trong Expert Advisor và tìm R2 cho nó. Cuối cùng, chúng ta nhân giá trị với 100, do đó chuyển đổi thang đo sang phạm vi của tiêu chí phức hợp tích hợp sẵn.
#define STAT_PROPS 4
double GetR2onBalanceCurve()
{
HistorySelect(0, LONG_MAX);
const ENUM_DEAL_PROPERTY_DOUBLE props[STAT_PROPS] =
{
DEAL_PROFIT, DEAL_SWAP, DEAL_COMMISSION, DEAL_FEE
};
double expenses[][STAT_PROPS];
ulong tickets[]; // chỉ cần vì nguyên mẫu 'select', nhưng hữu ích cho gỡ lỗi
DealFilter filter;
filter.let(DEAL_TYPE, (1 << DEAL_TYPE_BUY) | (1 << DEAL_TYPE_SELL), IS::OR_BITWISE)
.let(DEAL_ENTRY,
(1 << DEAL_ENTRY_OUT) | (1 << DEAL_ENTRY_INOUT) | (1 << DEAL_ENTRY_OUT_BY),
IS::OR_BITWISE)
.select(props, tickets, expenses);
const int n = ArraySize(tickets);
double balance[];
ArrayResize(balance, n + 1);
balance[0] = TesterStatistics(STAT_INITIAL_DEPOSIT);
for(int i = 0; i < n; ++i)
{
double result = 0;
for(int j = 0; j < STAT_PROPS; ++j)
{
result += expenses[i][j];
}
balance[i + 1] = result + balance[i];
}
const double r2 = RSquaredTest(balance);
return r2 * 100;
}
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
Trong trình xử lý OnTester
, chúng ta sẽ sử dụng tiêu chí mới dưới chỉ thị biên dịch có điều kiện, vì vậy chúng ta cần bỏ ghi chú chỉ thị #define USE_R2_CRITERION
ở đầu mã nguồn.
double OnTester()
{
#ifdef USE_R2_CRITERION
return GetR2onBalanceCurve();
#else
const double profit = TesterStatistics(STAT_PROFIT);
return sign(profit) * sqrt(fabs(profit))
* sqrt(TesterStatistics(STAT_PROFIT_FACTOR))
* sqrt(TesterStatistics(STAT_TRADES))
* sqrt(fabs(TesterStatistics(STAT_SHARPE_RATIO)));
#endif
}
2
3
4
5
6
7
8
9
10
11
12
Hãy xóa kết quả tối ưu hóa trước đó (các tệp opt với bộ nhớ đệm) và khởi động một tối ưu hóa mới của Expert Advisor: theo tiêu chí R2.
Khi so sánh các giá trị của tiêu chí R2 với tiêu chí phức hợp, chúng ta có thể nói rằng "sự hội tụ" giữa chúng đã tăng lên.
So sánh tiêu chí tùy chỉnh R2 và tiêu chí phức hợp tích hợp sẵn
Các giá trị của tiêu chí R2 trong cửa sổ tối ưu hóa và trên giai đoạn forward cho các tập hợp tham số tương ứng trông như sau.
Tiêu chí R2 trên các giai đoạn tối ưu hóa và kiểm tra forward
Và đây là cách lợi nhuận trong quá khứ và tương lai được kết hợp.
Lợi nhuận trên các giai đoạn tối ưu hóa và kiểm tra forward cho R2
Thống kê như sau: trong số 5582 lần chạy sinh lời cuối cùng, 2638 (47%) vẫn sinh lời, và trong số 1000 lần sinh lời nhất, có 566 lần vẫn sinh lời, tương đương với tiêu chí phức hợp tích hợp sẵn.
Như đã đề cập ở trên, thống kê cung cấp nguyên liệu thô cho các giai đoạn tối ưu hóa thông minh tiếp theo, điều này không chỉ là một nhiệm vụ lập trình. Chúng ta sẽ tập trung vào các khía cạnh hoàn toàn lập trình khác của tối ưu hóa.