Kết nối các chỉ báo tùy chỉnh dưới dạng tài nguyên
Để hoạt động, các chương trình MQL có thể yêu cầu một hoặc nhiều chỉ báo tùy chỉnh. Tất cả những chỉ báo này có thể được bao gồm dưới dạng tài nguyên trong tệp thực thi ex5, giúp việc phân phối và cài đặt trở nên dễ dàng.
Chỉ thị #resource
với mô tả của chỉ báo được nhúng có định dạng như sau:
#resource "path_indicator_name.ex5"
Các quy tắc để thiết lập và tìm kiếm tệp được chỉ định giống như đối với tất cả tài nguyên nói chung.
Chúng ta đã sử dụng tính năng này trong ví dụ Expert Advisor lớn, trong phiên bản cuối cùng của UnityMartingale.mq5
.
#resource "\\Indicators\\MQL5Book\\p6\\UnityPercentEvent.ex5"
Trong Expert Advisor đó, thay vì tên chỉ báo, tài nguyên này đã được truyền vào hàm iCustom
: ::Indicators\\MQL5Book\\p6\\UnityPercentEvent.ex5
.
Trường hợp một chỉ báo tùy chỉnh trong hàm OnInit
tạo ra một hoặc nhiều bản sao của chính nó cần được xem xét riêng (nếu giải pháp kỹ thuật này có vẻ kỳ lạ, chúng ta sẽ đưa ra một ví dụ thực tế sau các ví dụ giới thiệu).
Như chúng ta biết, để sử dụng một tài nguyên từ một chương trình MQL, nó phải được chỉ định dưới dạng sau: path_file_name.ex5::resource_name. Ví dụ, nếu chỉ báo EmbeddedIndicator.ex5
được bao gồm dưới dạng tài nguyên trong một chỉ báo khác MainIndicator.mq5
(chính xác hơn, trong hình ảnh nhị phân của nó MainIndicator.ex5
), thì tên được chỉ định khi gọi chính nó qua iCustom
không thể ngắn gọn, không có đường dẫn, và đường dẫn phải bao gồm vị trí của chỉ báo "cha" bên trong thư mục MQL5. Nếu không, hệ thống sẽ không thể tìm thấy chỉ báo được nhúng.
Thật vậy, trong trường hợp bình thường, một chỉ báo có thể gọi chính nó bằng cách sử dụng, ví dụ, toán tử iCustom(_Symbol, _Period, myself,...)
, trong đó myself
là một chuỗi bằng với MQLInfoString(MQL_PROGRAM_NAME)
hoặc tên đã được gán trước đó cho thuộc tính INDICATOR_SHORTNAME trong mã. Nhưng khi chỉ báo nằm bên trong một chương trình MQL khác dưới dạng tài nguyên, tên không còn tham chiếu đến tệp tương ứng vì tệp đóng vai trò là nguyên mẫu cho tài nguyên vẫn còn trên máy tính nơi thực hiện biên dịch, và trên máy tính của người dùng chỉ có tệp MainIndicator.ex5
. Điều này sẽ yêu cầu một số phân tích về môi trường chương trình khi khởi động chương trình.
Hãy xem điều này trong thực tế.
Để bắt đầu, hãy tạo một chỉ báo NonEmbeddedIndicator.mq5
. Điều quan trọng cần lưu ý là nó nằm trong thư mục MQL5/Indicators/MQL5Book/p7/SubFolder/
, tức là trong một SubFolder
tương đối so với thư mục p7
được phân bổ cho tất cả các chỉ báo của Phần này của cuốn sách. Điều này được thực hiện có chủ ý để mô phỏng tình huống tệp đã biên dịch không có mặt trên máy tính của người dùng. Bây giờ chúng ta sẽ thấy nó hoạt động như thế nào (hoặc đúng hơn, thể hiện vấn đề).
Chỉ báo có một tham số đầu vào duy nhất Reference
. Mục đích của nó là đếm số lượng bản sao của chính nó: khi được tạo lần đầu, tham số bằng 0, và chỉ báo sẽ tạo bản sao của chính nó với giá trị tham số là 1. Bản sao thứ hai, sau khi "nhìn thấy" giá trị 1, sẽ không tạo thêm bản sao nào nữa (nếu không, chúng ta sẽ nhanh chóng cạn kiệt tài nguyên mà không có điều kiện dừng sao chép).
input int Reference = 0;
Biến handle
được dành sẵn cho tay cầm của chỉ báo bản sao.
int handle = 0;
Trong trình xử lý OnInit
, để rõ ràng, chúng ta đầu tiên hiển thị tên và đường dẫn của chương trình MQL.
int OnInit()
{
const string name = MQLInfoString(MQL_PROGRAM_NAME);
const string path = MQLInfoString(MQL_PROGRAM_PATH);
Print(Reference);
Print("Name: " + name);
Print("Full path: " + path);
...
2
3
4
5
6
7
8
Tiếp theo là mã phù hợp để tự khởi chạy một chỉ báo riêng biệt (tồn tại dưới dạng tệp quen thuộc NonEmbeddedIndicator.ex5
).
if(Reference == 0)
{
handle = iCustom(_Symbol, _Period, name, 1);
if(handle == INVALID_HANDLE)
{
return INIT_FAILED;
}
}
Print("Success");
return INIT_SUCCEEDED;
}
2
3
4
5
6
7
8
9
10
11
Chúng ta có thể đặt thành công một chỉ báo như vậy lên biểu đồ và nhận được các mục nhập sau trong nhật ký (bạn sẽ có đường dẫn hệ thống tệp của riêng mình):
0
Name: NonEmbeddedIndicator
Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\NonEmbeddedIndicator.ex5
Success
1
Name: NonEmbeddedIndicator
Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\NonEmbeddedIndicator.ex5
Success
2
3
4
5
6
7
8
Bản sao đã khởi động thành công chỉ bằng cách sử dụng tên "NonEmbeddedIndicator".
Hãy tạm để chỉ báo này sang một bên và tạo một chỉ báo thứ hai, FaultyIndicator.mq5
, trong đó chúng ta sẽ bao gồm chỉ báo đầu tiên dưới dạng tài nguyên (chú ý đến việc chỉ định subfolder
trong đường dẫn tương đối của tài nguyên; điều này là cần thiết vì chỉ báo FaultyIndicator.mq5
nằm ở thư mục cao hơn một cấp: MQL5/Indicators/MQL5Book/p7/
).
// FaultyIndicator.mq5
#resource "SubFolder\\NonEmbeddedIndicator.ex5"
int handle;
int OnInit()
{
handle = iCustom(_Symbol, _Period, "::SubFolder\\NonEmbeddedIndicator.ex5");
if(handle == INVALID_HANDLE)
{
return INIT_FAILED;
}
return INIT_SUCCEEDED;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Nếu bạn cố chạy tệp đã biên dịch FaultyIndicator.ex5
, sẽ xảy ra lỗi:
0
Name: NonEmbeddedIndicator
Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\FaultyIndicator.ex5 »
» ::SubFolder\NonEmbeddedIndicator.ex5
cannot load custom indicator 'NonEmbeddedIndicator' [4802]
2
3
4
5
Khi một bản sao của chỉ báo nhúng được khởi chạy, nó được tìm kiếm trong thư mục của chỉ báo chính, nơi tài nguyên được mô tả. Nhưng không có tệp NonEmbeddedIndicator.ex5
vì tài nguyên cần thiết nằm bên trong FaultyIndicator.ex5
.
Để giải quyết vấn đề, chúng ta sửa đổi NonEmbeddedIndicator.mq5
. Trước hết, hãy đặt cho nó một cái tên phù hợp hơn, EmbeddedIndicator.mq5
. Trong mã nguồn, chúng ta cần thêm một hàm trợ giúp GetMQL5Path
, hàm này có thể tách phần tương đối bên trong thư mục MQL5 ra khỏi đường dẫn chung của chương trình MQL đã khởi chạy (phần này cũng sẽ chứa tên của tài nguyên nếu chỉ báo được khởi chạy từ một tài nguyên).
// EmbeddedIndicator.mq5
string GetMQL5Path()
{
static const string MQL5 = "\\MQL5\\";
static const int length = StringLen(MQL5) - 1;
static const string path = MQLInfoString(MQL_PROGRAM_PATH);
const int start = StringFind(path, MQL5);
if(start != -1)
{
return StringSubstr(path, start + length);
}
return path;
}
2
3
4
5
6
7
8
9
10
11
12
13
Dựa trên hàm mới, chúng ta sẽ thay đổi lệnh gọi iCustom
trong trình xử lý OnInit
.
int OnInit()
{
...
const string location = GetMQL5Path();
Print("Location in MQL5:" + location);
if(Reference == 0)
{
handle = iCustom(_Symbol, _Period, location, 1);
if(handle == INVALID_HANDLE)
{
return INIT_FAILED;
}
}
return INIT_SUCCEEDED;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Hãy đảm bảo rằng chỉnh sửa này không làm hỏng việc khởi chạy chỉ báo. Việc phủ lên biểu đồ dẫn đến các dòng dự kiến xuất hiện trong nhật ký:
0
Name: EmbeddedIndicator
Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5
Location in MQL5:\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5
Success
1
Name: EmbeddedIndicator
Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5
Location in MQL5:\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5
Success
2
3
4
5
6
7
8
9
10
Ở đây chúng ta đã thêm đầu ra gỡ lỗi của đường dẫn tương đối mà hàm GetMQL5Path
nhận được. Dòng này giờ được sử dụng trong iCustom
, và nó hoạt động ở chế độ này: một bản sao đã được tạo.
Bây giờ hãy nhúng chỉ báo này dưới dạng tài nguyên vào một chỉ báo khác trong thư mục MQL5Book/p7
với tên MainIndicator.mq5
. MainIndicator.mq5
hoàn toàn giống với FaultyIndicator.mq5
ngoại trừ tài nguyên được kết nối.
// MainIndicator.mq5
#resource "SubFolder\\EmbeddedIndicator.ex5"
...
int OnInit()
{
handle = iCustom(_Symbol, _Period, "::SubFolder\\EmbeddedIndicator.ex5");
...
}
2
3
4
5
6
7
8
Hãy biên dịch và chạy nó. Các mục nhập xuất hiện trong nhật ký với một đường dẫn tương đối mới bao gồm tài nguyên nhúng.
0
Name: EmbeddedIndicator
Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\MainIndicator.ex5 »
» ::SubFolder\EmbeddedIndicator.ex5
Location in MQL5:\Indicators\MQL5Book\p7\MainIndicator.ex5::SubFolder\EmbeddedIndicator.ex5
Success
1
Name: EmbeddedIndicator
Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\MainIndicator.ex5 »
» ::SubFolder\EmbeddedIndicator.ex5
Location in MQL5:\Indicators\MQL5Book\p7\MainIndicator.ex5::SubFolder\EmbeddedIndicator.ex5
Success
2
3
4
5
6
7
8
9
10
11
12
Như chúng ta thấy, lần này chỉ báo nhúng đã tạo thành công một bản sao của chính nó, vì nó đã sử dụng một tên đủ tiêu chuẩn với đường dẫn tương đối và tên tài nguyên "\Indicators\MQL5Book\p7\MainIndicator.ex5::SubFolder\EmbeddedIndicator.ex5".
Trong quá trình thực hiện nhiều thí nghiệm với việc khởi chạy chỉ báo này, xin lưu ý rằng các bản sao nhúng không được dỡ bỏ ngay lập tức khỏi biểu đồ sau khi chỉ báo chính bị xóa. Do đó, việc khởi động lại chỉ nên được thực hiện sau khi chúng ta đã đợi quá trình dỡ bỏ diễn ra: nếu không, các bản sao vẫn đang chạy sẽ được sử dụng lại, và các dòng khởi tạo ở trên sẽ không xuất hiện trong nhật ký. Để kiểm soát việc dỡ bỏ, một bản in giá trị của
Reference
đã được thêm vào trình xử lýOnDeinit
.
Chúng ta đã hứa sẽ cho thấy rằng việc tạo một bản sao của chỉ báo không phải là điều gì bất thường. Như một minh chứng ứng dụng của kỹ thuật này, chúng ta sử dụng chỉ báo DeltaPrice.mq5
để tính toán sự khác biệt trong các mức tăng giá của một bậc nhất định. Bậc 0 có nghĩa là không có sự phân biệt (chỉ để kiểm tra chuỗi thời gian gốc), 1 có nghĩa là phân biệt đơn, 2 có nghĩa là phân biệt kép, v.v.
Bậc được chỉ định trong tham số đầu vào Differencing
.
input int Differencing = 1;
Chuỗi khác biệt sẽ được hiển thị trong một bộ đệm duy nhất trong cửa sổ phụ.
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_width1 2
#property indicator_style1 STYLE_SOLID
double Buffer[];
2
3
4
5
6
7
8
9
10
Trong trình xử lý OnInit
, chúng ta thiết lập bộ đệm và tạo cùng một chỉ báo, truyền giá trị giảm đi 1 trong tham số đầu vào.
#include <MQL5Book/AppliedTo.mqh> // APPLIED_TO_STR macro
int handle = 0;
int OnInit()
{
const string label = "DeltaPrice (" + (string)Differencing + "/"
+ APPLIED_TO_STR() + ")";
IndicatorSetString(INDICATOR_SHORTNAME, label);
PlotIndexSetString(0, PLOT_LABEL, label);
SetIndexBuffer(0, Buffer);
if(Differencing > 1)
{
handle = iCustom(_Symbol, _Period, GetMQL5Path(), Differencing - 1);
if(handle == INVALID_HANDLE)
{
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
Để tránh các vấn đề tiềm ẩn khi nhúng chỉ báo dưới dạng tài nguyên, chúng ta sử dụng hàm đã được chứng minh GetMQL5Path
.
Trong hàm OnCalculate
, chúng ta thực hiện thao tác trừ các giá trị lân cận của chuỗi thời gian. Khi Differencing
bằng 1, các toán hạng là các phần tử của mảng price
. Với giá trị lớn hơn của Differencing
, chúng ta đọc bộ đệm của bản sao chỉ báo được tạo cho bậc trước đó.
int OnCalculate(const int rates_total,
const int prev_calculated,
const int begin,
const double &price[])
{
for(int i = fmax(prev_calculated - 1, 1); i < rates_total; ++i)
{
if(Differencing > 1)
{
static double value[2];
CopyBuffer(handle, 0, rates_total - i - 1, 2, value);
Buffer[i] = value[1] - value[0];
}
else if(Differencing == 1)
{
Buffer[i] = price[i] - price[i - 1];
}
else
{
Buffer[i] = price[i];
}
}
return rates_total;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Loại giá ban đầu được phân biệt được thiết lập trong hộp thoại cài đặt chỉ báo trong danh sách thả xuống Apply to
. Theo mặc định, đây là giá Close
.
Đây là cách mà một số bản sao của chỉ báo trông như thế nào trên biểu đồ với các bậc phân biệt khác nhau.
Sự khác biệt trong giá Close của các bậc khác nhau