Tự động điều chỉnh: ParameterGetRange
và ParameterSetRange
Trong phần trước, chúng ta đã học cách truyền một tiêu chí tối ưu hóa cho tester. Tuy nhiên, chúng ta đã bỏ qua một điểm quan trọng. Nếu bạn xem xét nhật ký tối ưu hóa của chúng ta, bạn sẽ thấy rất nhiều thông báo lỗi ở đó, như những thông báo dưới đây.
...
Best result 90.61004580175876 produced at generation 25. Next generation 26
genetic pass (26, 388) tested with error "incorrect input parameters" in 0:00:00.021
genetic pass (26, 436) tested with error "incorrect input parameters" in 0:00:00.007
genetic pass (26, 439) tested with error "incorrect input parameters" in 0:00:00.007
genetic pass (26, 363) tested with error "incorrect input parameters" in 0:00:00.008
genetic pass (26, 365) tested with error "incorrect input parameters" in 0:00:00.008
...
2
3
4
5
6
7
8
Nói cách khác, cứ sau vài lần kiểm tra, có điều gì đó không ổn với các tham số đầu vào, và lần kiểm tra đó không được thực hiện. Trình xử lý OnInit
chứa đoạn kiểm tra sau:
if(`FastOsMA` >= `SlowOsMA`) return `INIT_PARAMETERS_INCORRECT`;
Về phía chúng ta, việc áp đặt hạn chế rằng chu kỳ của MA chậm phải lớn hơn chu kỳ của MA nhanh là khá hợp lý. Tuy nhiên, tester không biết những điều này về thuật toán của chúng ta, do đó nó cố gắng thử qua nhiều tổ hợp chu kỳ khác nhau, bao gồm cả những tổ hợp không chính xác. Đây có thể là một tình huống phổ biến trong tối ưu hóa, tuy nhiên, nó lại mang đến một hậu quả tiêu cực.
Vì chúng ta áp dụng tối ưu hóa di truyền, nên trong mỗi thế hệ sẽ có một số mẫu bị loại bỏ không tham gia vào các đột biến tiếp theo. Trình tối ưu hóa MetaTrader 5 không bù đắp cho những tổn thất này, tức là nó không tạo ra sự thay thế cho chúng. Sau đó, kích thước dân số nhỏ hơn có thể ảnh hưởng tiêu cực đến chất lượng. Do đó, cần phải tìm ra một cách để đảm bảo rằng các cài đặt đầu vào chỉ được liệt kê trong các tổ hợp chính xác. Và ở đây, hai hàm API MQL5 đến để hỗ trợ chúng ta: ParameterGetRange
và ParameterSetRange
.
Cả hai hàm này đều có hai nguyên mẫu quá tải khác nhau về kiểu tham số: long
và double
. Đây là cách mô tả hai biến thể của hàm ParameterGetRange
.
bool ParameterGetRange(const string name, bool &enable, long &value, long &start, long &step, long &stop)
bool ParameterGetRange(const string name, bool &enable, double &value, double &start, double &step, double &stop)
Đối với biến đầu vào được chỉ định bởi tên, hàm nhận thông tin về giá trị hiện tại của nó (value
), phạm vi giá trị (start
, stop
), và bước thay đổi (step
) trong quá trình tối ưu hóa. Ngoài ra, một thuộc tính được ghi vào biến enable
để xác định liệu tối ưu hóa có được bật cho biến đầu vào có tên name
hay không.
Hàm trả về dấu hiệu thành công (true
) hoặc lỗi (false
).
Hàm chỉ có thể được gọi từ ba trình xử lý đặc biệt liên quan đến tối ưu hóa: OnTesterInit
, OnTesterPass
, và OnTesterDeinit
. Chúng ta sẽ nói về chúng trong phần tiếp theo. Như bạn có thể đoán từ tên, OnTesterInit
được gọi trước khi bắt đầu tối ưu hóa, OnTesterDeinit
— sau khi hoàn thành tối ưu hóa, và OnTesterPass
— sau mỗi lần chạy trong quá trình tối ưu hóa. Hiện tại, chúng ta chỉ quan tâm đến OnTesterInit
. Cũng giống như hai hàm còn lại, nó không có tham số và có thể được khai báo với kiểu void
, tức là không trả về gì.
Hai phiên bản của hàm ParameterSetRange
có nguyên mẫu tương tự và thực hiện hành động ngược lại: chúng thiết lập các thuộc tính tối ưu hóa của tham số đầu vào của Expert Advisor.
bool ParameterSetRange(const string name, bool enable, long value, long start, long step, long stop)
bool ParameterSetRange(const string name, bool enable, double value, double start, double step, double stop)
Hàm thiết lập các quy tắc sửa đổi của biến input
với tên name
khi tối ưu hóa: giá trị, bước thay đổi, giá trị bắt đầu và kết thúc.
Hàm này chỉ có thể được gọi từ trình xử lý OnTesterInit
khi bắt đầu tối ưu hóa trong tester chiến lược.
Do đó, sử dụng các hàm ParameterGetRange
và ParameterSetRange
, bạn có thể phân tích và thiết lập các giá trị phạm vi và bước mới, cũng như loại bỏ hoàn toàn, hoặc ngược lại, bao gồm một số tham số nhất định khỏi tối ưu hóa, bất kể cài đặt trong tester chiến lược. Điều này cho phép bạn tạo các kịch bản của riêng mình để quản lý không gian tham số đầu vào trong quá trình tối ưu hóa.
Hàm cho phép sử dụng trong tối ưu hóa ngay cả những biến được khai báo với bộ修饰符 sinput
(chúng không thể được người dùng đưa vào tối ưu hóa).
Chú ý! Sau khi gọi
ParameterSetRange
với sự thay đổi trong cài đặt của một biến đầu vào cụ thể, các lần gọi tiếp theo củaParameterGetRange
sẽ không "thấy" những thay đổi này và vẫn sẽ trả về các cài đặt ban đầu. Điều này khiến không thể sử dụng các hàm cùng nhau trong các sản phẩm phần mềm phức tạp, nơi cài đặt có thể được xử lý bởi các lớp và thư viện khác nhau từ các nhà phát triển độc lập.
Hãy cải thiện Expert Advisor BandOsMA
bằng cách sử dụng các hàm mới. Phiên bản cập nhật được đặt tên là BandOsMApro.mq5
("pro" có thể được giải mã một cách có điều kiện là "tối ưu hóa phạm vi tham số").
Vậy, chúng ta có trình xử lý OnTesterInit
, trong đó chúng ta đọc cài đặt cho các tham số FastOsMA
và SlowOsMA
, và kiểm tra xem chúng có được đưa vào tối ưu hóa hay không. Nếu có, bạn cần tắt chúng và đề xuất một giải pháp thay thế.
void `OnTesterInit`()
{
bool `enabled1`, `enabled2`;
long `value1`, `start1`, `step1`, `stop1`;
long `value2`, `start2`, `step2`, `stop2`;
if(`ParameterGetRange`("FastOsMA", `enabled1`, `value1`, `start1`, `step1`, `stop1`)
&& `ParameterGetRange`("SlowOsMA", `enabled2`, `value2`, `start2`, `step2`, `stop2`))
{
if(`enabled1` && `enabled2`)
{
if(!`ParameterSetRange`("FastOsMA", false, `value1`, `start1`, `step1`, `stop1`)
|| !`ParameterSetRange`("SlowOsMA", false, `value2`, `start2`, `step2`, `stop2`))
{
Print("Can't disable optimization by FastOsMA and SlowOsMA: ",
`E2S`(_LastError));
return;
}
...
}
}
else
{
Print("Can't adjust optimization by FastOsMA and SlowOsMA: ", `E2S`(_LastError));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Thật không may, do việc bổ sung OnTesterInit
, trình biên dịch cũng yêu cầu bạn thêm OnTesterDeinit
, mặc dù chúng ta không cần hàm này. Nhưng chúng ta buộc phải đồng ý và thêm một trình xử lý rỗng.
void `OnTesterDeinit`()
{
}
2
3
Sự hiện diện của các hàm OnTesterInit/OnTesterDeinit
trong mã sẽ dẫn đến việc khi tối ưu hóa bắt đầu, một biểu đồ bổ sung sẽ mở ra trong terminal với một bản sao của Expert Advisor của chúng ta chạy trên đó. Nó hoạt động ở một chế độ đặc biệt cho phép nhận dữ liệu bổ sung (cái gọi là frames
) từ các bản sao được kiểm tra trên các agent, nhưng chúng ta sẽ khám phá khả năng này sau. Hiện tại, điều quan trọng đối với chúng ta là lưu ý rằng tất cả các thao tác với tệp, nhật ký, biểu đồ và đối tượng đều hoạt động trong bản sao phụ trợ này của Expert Advisor trực tiếp trong terminal, như bình thường (và không phải trên agent). Đặc biệt, tất cả các thông báo lỗi và các lệnh gọi Print
sẽ được hiển thị trong nhật ký trên tab Experts
của terminal.
Chúng ta có thông tin về phạm vi thay đổi và bước của các tham số này, chúng ta có thể tính toán lại tất cả các tổ hợp chính xác theo nghĩa đen. Nhiệm vụ này được giao cho một hàm riêng biệt Iterate
vì một thao tác tương tự sẽ phải được sao chép bởi các bản sao của Expert Advisor trên các agent, trong trình xử lý OnInit
.
Trong hàm Iterate
, chúng ta có hai vòng lặp lồng nhau qua các chu kỳ của MA nhanh và chậm, trong đó chúng ta đếm số lượng tổ hợp hợp lệ, tức là khi chu kỳ i
nhỏ hơn j
. Chúng ta cần tham số tùy chọn find
khi gọi Iterate
từ OnInit
để trả về cặp theo số thứ tự của tổ hợp i
và j
. Vì cần trả về 2 số, chúng ta đã khai báo cấu trúc PairOfPeriods
cho chúng.
struct `PairOfPeriods`
{
int `fast`;
int `slow`;
};
`PairOfPeriods` `Iterate`(const long `start1`, const long `stop1`, const long `step1`,
const long `start2`, const long `stop2`, const long `step2`,
const long `find` = -1)
{
int `count` = 0;
for(int `i` = (int)`start1`; `i` <= (int)`stop1`; `i` += (int)`step1`)
{
for(int `j` = (int)`start2`; `j` <= (int)`stop2`; `j` += (int)`step2`)
{
if(`i` < `j`)
{
if(`count` == `find`)
{
`PairOfPeriods` `p` = {`i`, `j`};
return `p`;
}
++`count`;
}
}
}
`PairOfPeriods` `p` = {`count`, 0};
return `p`;
}
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
Khi gọi Iterate
từ OnTesterInit
, chúng ta không sử dụng tham số find
và tiếp tục đếm cho đến cuối, rồi trả về số lượng kết quả trong trường đầu tiên của cấu trúc. Đây sẽ là phạm vi giá trị của một tham số bóng mới, mà chúng ta phải bật tối ưu hóa cho nó. Hãy gọi nó là FastSlowCombo4Optimization
và thêm vào nhóm mới của các tham số đầu vào phụ trợ. Sẽ còn nhiều thứ được thêm vào đây sớm thôi.
input group "A U X I L I A R Y"
sinput int `FastSlowCombo4Optimization` = 0; // (reserved for optimization)
...
2
3
Hãy quay lại OnTesterInit
và tổ chức một tối ưu hóa MQL5 theo tham số FastSlowCombo4Optimization
trong phạm vi mong muốn bằng cách sử dụng ParameterSetRange
.
void `OnTesterInit`()
{
...
`PairOfPeriods` `p` = `Iterate`(`start1`, `stop1`, `step1`, `start2`, `stop2`, `step2`);
const int `count` = `p`.`fast`;
`ParameterSetRange`("FastSlowCombo4Optimization", true, 0, 0, 1, `count`);
`PrintFormat`("Parameter FastSlowCombo4Optimization is enabled with maximum: %d",
`count`);
...
}
2
3
4
5
6
7
8
9
10
Vui lòng lưu ý rằng số lần lặp kết quả cho tham số mới sẽ được hiển thị trong nhật ký terminal.
Khi kiểm tra trên agent, sử dụng số trong FastSlowCombo4Optimization
để lấy một cặp chu kỳ bằng cách gọi lại Iterate
, lần này với tham số find
đã được điền. Nhưng vấn đề là để thực hiện thao tác này, cần phải biết phạm vi ban đầu và bước thay đổi của tham số FastOsMA
và SlowOsMA
. Thông tin này chỉ có trong terminal. Vì vậy, chúng ta cần một cách nào đó để chuyển nó sang agent.
Bây giờ chúng ta sẽ áp dụng giải pháp duy nhất mà chúng ta biết cho đến nay: chúng ta sẽ thêm 3 tham số tối ưu hóa bóng nữa và đặt một số giá trị cho chúng. Trong tương lai, chúng ta sẽ làm quen với công nghệ chuyển tệp sang các agent (xem Chỉ thị tiền xử lý cho tester). Sau đó, chúng ta sẽ có thể ghi toàn bộ mảng chỉ số được tính toán bởi hàm Iterate
vào tệp và gửi nó đến các agent. Điều này sẽ tránh được ba tham số tối ưu hóa bóng thừa.
Vậy, hãy thêm ba tham số đầu vào:
sinput ulong `FastShadow4Optimization` = 0; // (reserved for optimization)
sinput ulong `SlowShadow4Optimization` = 0; // (reserved for optimization)
sinput ulong `StepsShadow4Optimization` = 0; // (reserved for optimization)
2
3
Chúng ta sử dụng kiểu ulong
để tiết kiệm hơn: để đóng gói 2 số int
vào mỗi giá trị. Đây là cách chúng được điền trong OnTesterInit
.
void `OnTesterInit`()
{
...
const ulong `fast` = `start1` | (`stop1` << 16);
const ulong `slow` = `start2` | (`stop2` << 16);
const ulong `step` = `step1` | (`step2` << 16);
`ParameterSetRange`("FastShadow4Optimization", false, `fast`, `fast`, 1, `fast`);
`ParameterSetRange`("SlowShadow4Optimization", false, `slow`, `slow`, 1, `slow`);
`ParameterSetRange`("StepsShadow4Optimization", false, `step`, `step`, 1, `step`);
...
}
2
3
4
5
6
7
8
9
10
11
Cả 3 tham số đều không thể tối ưu hóa (false
trong đối số thứ hai).
Điều này kết thúc các thao tác của chúng ta với hàm OnTesterInit
. Hãy chuyển sang phía nhận: trình xử lý OnInit
.
int `OnInit`()
{
// giữ lại kiểm tra cho các bài kiểm tra đơn lẻ
if(`FastOsMA` >= `SlowOsMA`) return `INIT_PARAMETERS_INCORRECT`;
// khi tối ưu hóa, chúng ta yêu cầu sự hiện diện của các tham số bóng
if(`MQLInfoInteger`(`MQL_OPTIMIZATION`) && `StepsShadow4Optimization` == 0)
{
return `INIT_PARAMETERS_INCORRECT`;
}
`PairOfPeriods` `p` = {`FastOsMA`, `SlowOsMA`}; // mặc định chúng ta làm việc với các tham số thông thường
if(`FastShadow4Optimization` && `SlowShadow4Optimization` && `StepsShadow4Optimization`)
{
// nếu các tham số bóng đầy đủ, giải mã chúng thành chu kỳ
int `FastStart` = (int)(`FastShadow4Optimization` & 0xFFFF);
int `FastStop` = (int)((`FastShadow4Optimization` >> 16) & 0xFFFF);
int `SlowStart` = (int)(`SlowShadow4Optimization` & 0xFFFF);
int `SlowStop` = (int)((`SlowShadow4Optimization` >> 16) & 0xFFFF);
int `FastStep` = (int)(`StepsShadow4Optimization` & 0xFFFF);
int `SlowStep` = (int)((`StepsShadow4Optimization` >> 16) & 0xFFFF);
`p` = `Iterate`(`FastStart`, `FastStop`, `FastStep`,
`SlowStart`, `SlowStop`, `SlowStep`, `FastSlowCombo4Optimization`);
`PrintFormat`("MA periods are restored from shadow: FastOsMA=%d SlowOsMA=%d",
`p`.`fast`, `p`.`slow`);
}
`strategy` = new `SimpleStrategy`(
new `BandOsMaSignal`(`p`.`fast`, `p`.`slow`, `SignalOsMA`, `PriceOsMA`,
`BandsMA`, `BandsShift`, `BandsDeviation`,
`PeriodMA`, `ShiftMA`, `MethodMA`),
`Magic`, `StopLoss`, `Lots`);
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
25
26
27
28
29
30
31
32
33
34
35
Sử dụng hàm MQLInfoInteger
, chúng ta có thể xác định tất cả các chế độ của Expert Advisor, bao gồm cả những chế độ liên quan đến tester và tối ưu hóa. Khi chỉ định một trong các phần tử của liệt kê ENUM_MQL_INFO_INTEGER làm tham số, chúng ta sẽ nhận được dấu hiệu logic làm kết quả (true/false
):
MQL_TESTER
— chương trình hoạt động trong testerMQL_VISUAL_MODE
— tester đang chạy ở chế độ trực quanMQL_OPTIMIZATION
— lần kiểm tra được thực hiện trong quá trình tối ưu hóa (không riêng lẻ)MQL_FORWARD
— lần kiểm tra được thực hiện trên giai đoạn forward sau khi tối ưu hóa (nếu được chỉ định bởi cài đặt tối ưu hóa)MQL_FRAME_MODE
— Expert Advisor đang chạy ở chế độ dịch vụ đặc biệt trên biểu đồ terminal (và không phải trên agent) để kiểm soát tối ưu hóa (xem thêm trong phần tiếp theo)
Chế độ Tester của các chương trình MQL
Mọi thứ đã sẵn sàng để bắt đầu tối ưu hóa. Ngay khi nó bắt đầu, với các cài đặt đã đề cập Presets/MQL5Book/BandOsMA.set
, chúng ta sẽ thấy một thông báo trong nhật ký Experts
trong terminal:
Parameter FastSlowCombo4Optimization is enabled with maximum: 698
Lần này không nên có lỗi trong nhật ký tối ưu hóa và tất cả các thế hệ được tạo ra mà không bị lỗi.
...
Best result 91.02452934181422 produced at generation 39. Next generation 42
Best result 91.56338892567393 produced at generation 42. Next generation 43
Best result 91.71026391877101 produced at generation 43. Next generation 44
Best result 91.71026391877101 produced at generation 43. Next generation 45
Best result 92.48460871443507 produced at generation 45. Next generation 46
...
2
3
4
5
6
7
Điều này có thể được xác định ngay cả bởi thời gian tối ưu hóa tổng thể tăng lên: trước đây, một số lần chạy bị từ chối ở giai đoạn đầu, và bây giờ tất cả đều được xử lý đầy đủ.
Nhưng giải pháp của chúng ta có một nhược điểm. Bây giờ các cài đặt hoạt động của Expert Advisor không chỉ bao gồm một cặp chu kỳ trong các tham số FastOsMA
và SlowOsMA
, mà còn bao gồm số thứ tự của tổ hợp của chúng trong tất cả các tổ hợp có thể (FastSlowCombo4Optimization
). Điều duy nhất chúng ta có thể làm là xuất các chu kỳ đã giải mã trong hàm OnInit
, như đã được trình bày ở trên.
Do đó, sau khi tìm thấy các cài đặt tốt với sự trợ giúp của tối ưu hóa, người dùng, như thường lệ, sẽ thực hiện một lần chạy đơn lẻ để tinh chỉnh hành vi của hệ thống giao dịch. Ở đầu nhật ký kiểm tra, một dòng chữ dạng sau sẽ xuất hiện:
MA periods are restored from shadow: FastOsMA=27 SlowOsMA=175
Sau đó, bạn có thể nhập các chu kỳ đã chỉ định vào các tham số cùng tên và đặt lại tất cả các tham số bóng.