Lấy dữ liệu chuỗi thời gian từ một chỉ báo: CopyBuffer
Một chương trình MQL có thể đọc dữ liệu từ các bộ đệm công khai của chỉ báo thông qua handle của nó. Hãy nhớ rằng trong các chỉ báo tùy chỉnh, những bộ đệm này là các mảng được chỉ định trong mã nguồn bằng các lệnh gọi hàm SetIndexBuffer
.
API MQL5 cung cấp hàm CopyBuffer
để đọc các bộ đệm; hàm này có 3 dạng.
int CopyBuffer(int handle, int buffer, int offset, int count, double &array[])
int CopyBuffer(int handle, int buffer, datetime start, int count, double &array[])
int CopyBuffer(int handle, int buffer, datetime start, datetime stop, double &array[])
Tham số handle
chỉ định handle nhận được từ lệnh gọi iCustom
hoặc các hàm khác (để biết thêm chi tiết, vui lòng xem các phần về IndicatorCreate
và các chỉ báo tích hợp sẵn). Tham số buffer
đặt chỉ số của bộ đệm chỉ báo mà từ đó yêu cầu dữ liệu. Việc đánh số bắt đầu từ 0.
Các phần tử nhận được của chuỗi thời gian được yêu cầu sẽ được đưa vào array
được đặt bằng tham chiếu.
Ba biến thể của hàm khác nhau ở cách chúng chỉ định phạm vi dấu thời gian (start/stop
) hoặc số lượng (offset
) và số lượng (count
) thanh mà dữ liệu được lấy. Các nguyên tắc cơ bản khi làm việc với các tham số này hoàn toàn nhất quán với những gì chúng ta đã nghiên cứu trong Tổng quan về các hàm Copy để lấy mảng báo giá. Cụ thể, các phần tử của dữ liệu được sao chép trong offset
và count
được đếm từ hiện tại về quá khứ, nghĩa là vị trí bắt đầu bằng 0 có nghĩa là thanh hiện tại. Các phần tử trong array
nhận được được sắp xếp vật lý từ quá khứ đến hiện tại (tuy nhiên, cách đánh địa chỉ này có thể được đảo ngược ở cấp độ logic bằng cách sử dụng ArraySetAsSeries
).
CopyBuffer
là một hàm tương tự của các hàm đọc chuỗi thời gian tích hợp như CopyOpen
, CopyClose
và các hàm khác. Sự khác biệt chính là chuỗi thời gian với báo giá được tạo ra bởi chính terminal, trong khi chuỗi thời gian trong các bộ đệm chỉ báo được tính toán bởi các chỉ báo tùy chỉnh hoặc chỉ báo tích hợp sẵn. Ngoài ra, trong trường hợp của các chỉ báo, chúng ta đặt một cặp ký hiệu và khung thời gian cụ thể để xác định và nhận diện chuỗi thời gian trước, trong hàm tạo handle như iCustom
, và trong CopyBuffer
, thông tin này được truyền gián tiếp qua handle
.
Khi sao chép một lượng dữ liệu không xác định vào mảng đích, nên sử dụng mảng động. Trong trường hợp này, hàm CopyBuffer
sẽ phân bổ kích thước của mảng nhận theo kích thước của dữ liệu được sao chép. Nếu cần sao chép lặp lại một lượng dữ liệu đã biết, thì tốt hơn là thực hiện điều này trong một bộ đệm được cấp phát tĩnh (cục bộ với từ khóa static
hoặc kích thước cố định trong ngữ cảnh toàn cầu) để tránh phân bổ bộ nhớ lặp lại.
Nếu mảng nhận là một bộ đệm chỉ báo (một mảng đã được đăng ký trước đó trong hệ thống bằng hàm SetIndexBuffer
), thì cách đánh chỉ số trong chuỗi thời gian và bộ đệm nhận là giống nhau (với điều kiện yêu cầu cùng cặp ký hiệu/khung thời gian). Trong trường hợp này, dễ dàng thực hiện việc điền một phần vào bộ đệm nhận (đặc biệt, điều này được sử dụng để cập nhật các thanh cuối cùng, xem ví dụ dưới đây). Nếu ký hiệu hoặc khung thời gian của chuỗi thời gian được yêu cầu không khớp với ký hiệu và/hoặc khung thời gian của biểu đồ hiện tại, hàm sẽ không trả về số phần tử vượt quá số lượng thanh tối thiểu trong hai nguồn: nguồn và đích.
Nếu một mảng thông thường (không phải bộ đệm) được truyền làm đối số array
, thì hàm sẽ điền nó bắt đầu từ các phần tử đầu tiên, toàn bộ (trong trường hợp động) hoặc một phần (trong trường hợp tĩnh, với kích thước dư thừa). Do đó, nếu cần sao chép một phần giá trị chỉ báo vào một vị trí tùy ý trong một mảng khác, thì cần sử dụng một mảng trung gian, trong đó sao chép số lượng phần tử cần thiết, và từ đó chúng được chuyển đến đích cuối cùng.
Hàm trả về số lượng phần tử được sao chép hoặc -1 trong trường hợp có lỗi, bao gồm cả việc dữ liệu sẵn sàng tạm thời không có.
Vì các chỉ báo thường trực tiếp hoặc gián tiếp phụ thuộc vào chuỗi thời gian giá, việc tính toán của chúng bắt đầu không sớm hơn khi các báo giá được đồng bộ hóa. Về vấn đề này, cần tính đến các đặc điểm kỹ thuật của việc tổ chức và lưu trữ chuỗi thời gian trong terminal và chuẩn bị sẵn sàng rằng dữ liệu được yêu cầu sẽ không xuất hiện ngay lập tức. Đặc biệt, chúng ta có thể nhận được 0 hoặc một số lượng ít hơn số lượng yêu cầu. Tất cả các trường hợp như vậy nên được xử lý tùy theo tình huống, chẳng hạn như chờ đợi quá trình xây dựng hoặc báo cáo vấn đề cho người dùng.
Nếu chuỗi thời gian được yêu cầu chưa được xây dựng, hoặc chúng cần được tải xuống từ máy chủ, thì hàm hoạt động khác nhau tùy thuộc vào loại chương trình MQL mà từ đó nó được gọi.
Khi yêu cầu dữ liệu chưa sẵn sàng từ chỉ báo, hàm sẽ ngay lập tức trả về -1, nhưng quá trình tải và xây dựng chuỗi thời gian sẽ được khởi tạo.
Khi yêu cầu dữ liệu từ một Expert Advisor hoặc script, quá trình tải xuống từ máy chủ sẽ được khởi tạo và/hoặc việc xây dựng chuỗi thời gian cần thiết sẽ bắt đầu nếu dữ liệu có thể được xây dựng từ lịch sử cục bộ. Hàm sẽ trả về lượng dữ liệu sẵn sàng trong thời gian chờ (45 giây) được phân bổ cho việc thực thi đồng bộ của hàm (mã gọi đang đợi hàm hoàn thành).
Lưu ý rằng hàm CopyBuffer
có thể đọc dữ liệu từ các bộ đệm bất kể chế độ hoạt động của chúng, INDICATOR_DATA, INDICATOR_COLOR_INDEX, INDICATOR_CALCULATIONS, trong khi hai chế độ cuối cùng được ẩn khỏi người dùng.
Cũng cần lưu ý rằng độ dịch chuyển của chuỗi thời gian có thể được đặt trong chỉ báo được gọi bằng thuộc tính PLOT_SHIFT, và nó ảnh hưởng đến độ lệch của dữ liệu được đọc bằng CopyBuffer
. Ví dụ, nếu các đường chỉ báo được dịch chuyển vào tương lai N thanh, thì trong các tham số của CopyBuffer
(dạng đầu tiên), phải đặt offset
bằng (-N), tức là với dấu trừ, vì thanh hiện tại của chuỗi thời gian có chỉ số 0, và các chỉ số của các thanh tương lai với độ dịch chuyển giảm dần từng thanh một. Đặc biệt, tình huống như vậy xảy ra với chỉ báo Gator
, vì biểu đồ zero của nó được dịch chuyển về phía trước bởi giá trị của tham số TeethShift
, và biểu đồ đầu tiên được dịch chuyển bởi giá trị của tham số LipsShift
. Việc điều chỉnh nên được thực hiện dựa trên giá trị lớn nhất trong số chúng. Chúng ta sẽ xem một ví dụ trong phần Đọc dữ liệu từ biểu đồ có độ dịch chuyển.
MQL5 không cung cấp các công cụ lập trình để tìm thuộc tính PLOT_SHIFT của một chỉ báo bên thứ ba. Do đó, nếu cần, bạn sẽ phải yêu cầu thông tin này từ người dùng thông qua một biến đầu vào.
Chúng ta sẽ làm việc với CopyBuffer
từ mã Expert Advisor trong chương về Expert Advisors, nhưng hiện tại chúng ta sẽ giới hạn ở các chỉ báo.
Hãy tiếp tục phát triển ví dụ với chỉ báo phụ trợ IndWPR
. Lần này trong phiên bản UseWPR3.mq5
, chúng ta sẽ cung cấp một bộ đệm chỉ báo và điền dữ liệu vào nó từ IndWPR
bằng cách sử dụng CopyBuffer
. Để làm điều này, chúng ta sẽ áp dụng các chỉ thị với số lượng bộ đệm và cài đặt hiển thị.
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrBlue
#property indicator_width1 1
#property indicator_label1 "WPR"
2
3
4
5
6
7
8
Trong ngữ cảnh toàn cầu, chúng ta mô tả tham số đầu vào với chu kỳ WPR, một mảng cho bộ đệm, và một biến với descriptor.
input int WPRPeriod = 14;
double WPRBuffer[];
int handle;
2
3
4
5
Trình xử lý OnInit
hầu như không thay đổi: chỉ có lệnh gọi SetIndexBuffer
được thêm vào.
int OnInit()
{
SetIndexBuffer(0, WPRBuffer);
handle = iCustom(_Symbol, _Period, "IndWPR", WPRPeriod);
return handle == INVALID_HANDLE ? INIT_FAILED : INIT_SUCCEEDED;
}
2
3
4
5
6
Trong OnCalculate
, chúng ta sẽ sao chép dữ liệu mà không biến đổi.
int OnCalculate(const int rates_total,
const int prev_calculated,
const int begin,
const double &data[])
{
// chờ đợi tính toán sẵn sàng cho tất cả các thanh
if(BarsCalculated(handle) != rates_total)
{
return prev_calculated;
}
// sao chép toàn bộ chuỗi thời gian của chỉ báo phụ hoặc trên các thanh mới vào bộ đệm của chúng ta
const int n = CopyBuffer(handle, 0, 0, rates_total - prev_calculated + 1, WPRBuffer);
// nếu không có lỗi, dữ liệu của chúng ta đã sẵn sàng cho tất cả các thanh rates_total
return n > -1 ? rates_total : 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Bằng cách biên dịch và chạy UseWPR3
, chúng ta thực sự sẽ nhận được một bản sao của WPR gốc, ngoại trừ việc điều chỉnh mức, độ chính xác của số và tiêu đề. Điều này đủ để kiểm tra cơ chế, nhưng thường thì các chỉ báo mới dựa trên một hoặc nhiều chỉ báo phụ trợ sẽ cung cấp một ý tưởng và biến đổi dữ liệu riêng của chúng. Do đó, chúng ta sẽ phát triển một chỉ báo khác tạo ra tín hiệu giao dịch mua và bán (từ góc độ giao dịch, chúng không nên được coi là mô hình, vì đây chỉ là một bài tập lập trình). Ý tưởng của chỉ báo được thể hiện trong hình ảnh dưới đây.
Chú thích hình ảnh: Chỉ báo IndWPR, IndTripleEMA, IndFractals
Chúng ta sử dụng việc WPR thoát ra khỏi vùng quá mua và quá bán như một khuyến nghị, tương ứng, để bán và mua. Để các tín hiệu không phản ứng với những biến động ngẫu nhiên, chúng ta áp dụng đường trung bình động ba lần vào WPR và sẽ kiểm tra xem giá trị của nó có vượt qua ranh giới của vùng trên và dưới hay không.
Làm bộ lọc cho các tín hiệu này, chúng ta sẽ kiểm tra fractal cuối cùng trước thời điểm này là gì: một fractal đỉnh có nghĩa là giá đảo chiều xuống và xác nhận lệnh bán, còn một fractal đáy có nghĩa là đảo chiều lên và do đó hỗ trợ lệnh mua. Các fractal xuất hiện với độ trễ bằng số thanh tương ứng với bậc của fractal.
Chỉ báo mới có sẵn trong tệp UseWPRFractals.mq5
.
Chúng ta cần ba bộ đệm: hai bộ đệm tín hiệu và một bộ đệm nữa cho bộ lọc. Chúng ta có thể phát hành bộ đệm cuối cùng ở chế độ INDICATOR_CALCULATIONS. Thay vào đó, hãy làm nó ở chế độ INDICATOR_DATA tiêu chuẩn, nhưng với kiểu DRAW_NONE — như vậy nó sẽ không cản trở trên biểu đồ, nhưng giá trị của nó sẽ hiển thị trong Cửa sổ Dữ liệu.
Các tín hiệu sẽ được hiển thị trên biểu đồ chính (mặc định tại giá Close
), vì vậy chúng ta sử dụng chỉ thị indicator_chart_window
. Chúng ta vẫn có thể gọi các chỉ báo loại WPR được vẽ trong một cửa sổ riêng, vì tất cả các chỉ báo phụ có thể được tính toán mà không cần hiển thị. Nếu cần, chúng ta có thể vẽ chúng, nhưng chúng ta sẽ nói về điều này trong chương về biểu đồ (xem ChartIndicatorAdd
).
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots 3
// cài đặt vẽ bộ đệm
#property indicator_type1 DRAW_ARROW
#property indicator_color1 clrRed
#property indicator_width1 1
#property indicator_label1 "Sell"
#property indicator_type2 DRAW_ARROW
#property indicator_color2 clrBlue
#property indicator_width2 1
#property indicator_label2 "Buy"
#property indicator_type3 DRAW_NONE
#property indicator_color3 clrGreen
#property indicator_width3 1
#property indicator_label3 "Filter"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Trong các biến đầu vào, chúng ta sẽ cung cấp khả năng chỉ định chu kỳ WPR, chu kỳ trung bình (làm mịn), và bậc fractal. Đây là các tham số của các chỉ báo phụ. Ngoài ra, chúng ta đưa vào biến offset
với số thanh mà trên đó các tín hiệu sẽ được phân tích. Giá trị 0 (mặc định) có nghĩa là thanh hiện tại và phân tích ở chế độ tick (lưu ý: tín hiệu trên thanh cuối cùng có thể được vẽ lại; một số nhà giao dịch không thích điều này). Nếu chúng ta đặt offset
bằng 1, chúng ta sẽ phân tích các thanh đã hình thành, và các tín hiệu như vậy không thay đổi.
input int PeriodWPR = 11;
input int PeriodEMA = 5;
input int FractalOrder = 1;
input int Offset = 0;
input double Threshold = 0.2;
2
3
4
5
Biến Threshold
xác định kích thước của vùng quá mua và quá bán dưới dạng phân số của ±1.0 (theo mỗi hướng). Ví dụ, nếu bạn tuân theo cài đặt WPR cổ điển với mức -20 và -80 trên thang từ 0 đến -100, thì Threshold
nên bằng 0.4.
Các mảng sau được cung cấp cho các bộ đệm chỉ báo.
double UpBuffer[]; // tín hiệu trên nghĩa là quá mua, tức là bán
double DownBuffer[]; // tín hiệu dưới nghĩa là quá bán, tức là mua
double filter[]; // hướng bộ lọc fractal +1 (lên/mua), -1 (xuống/bán)
2
3
Các handle của chỉ báo sẽ được lưu trong các biến toàn cầu.
int handleWPR, handleEMA3, handleFractals;
Chúng ta sẽ thực hiện tất cả các cài đặt, như thường lệ, trong OnInit
. Vì hàm CopyBuffer
sử dụng cách đánh chỉ số từ hiện tại về quá khứ, để đồng nhất việc đọc dữ liệu, chúng ta đặt cờ "series" (ArraySetAsSeries
) cho tất cả các mảng.
int OnInit()
{
// liên kết các bộ đệm
SetIndexBuffer(0, UpBuffer);
SetIndexBuffer(1, DownBuffer);
SetIndexBuffer(2, Filter, INDICATOR_DATA); // phiên bản: INDICATOR_CALCULATIONS
ArraySetAsSeries(UpBuffer, true);
ArraySetAsSeries(DownBuffer, true);
ArraySetAsSeries(Filter, true);
// tín hiệu mũi tên
PlotIndexSetInteger(0, PLOT_ARROW, 234);
PlotIndexSetInteger(1, PLOT_ARROW, 233);
// các chỉ báo phụ
handleWPR = iCustom(_Symbol, _Period, "IndWPR", PeriodWPR);
handleEMA3 = iCustom(_Symbol, _Period, "IndTripleEMA", PeriodEMA, 0, handleWPR);
handleFractals = iCustom(_Symbol, _Period, "IndFractals", FractalOrder);
if(handleWPR == INVALID_HANDLE
|| handleEMA3 == INVALID_HANDLE
|| handleFractals == 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
23
24
25
26
27
Trong các lệnh gọi iCustom
, cần chú ý đến cách handleEMA3
được tạo. Vì trung bình này sẽ được tính toán dựa trên WPR, chúng ta truyền handleWPR
(được lấy từ lệnh gọi iCustom
trước đó) làm tham số cuối cùng, sau các tham số thực tế của chỉ báo IndTripleEMA
. Khi làm điều này, chúng ta phải chỉ định danh sách đầy đủ các tham số đầu vào của IndTripleEMA
(các tham số trong đó là int InpPeriodEMA
và BEGIN_POLICY InpHandleBegin
; chúng ta đã sử dụng tham số thứ hai để nghiên cứu việc bỏ qua các thanh ban đầu và không cần nó bây giờ, nhưng chúng ta phải truyền nó, vì vậy chúng ta chỉ đặt nó là 0). Nếu chúng ta bỏ qua tham số thứ hai trong lệnh gọi vì không liên quan trong ngữ cảnh ứng dụng hiện tại, thì handle handleWPR
được truyền sẽ được diễn giải trong chỉ báo được gọi là InpHandleBegin
. Kết quả là, IndTripleEMA
sẽ được áp dụng cho giá Close
thông thường.
Khi chúng ta không cần truyền một handle bổ sung, cú pháp của lệnh gọi iCustom
cho phép bỏ qua một số lượng tùy ý các tham số cuối cùng, trong khi chúng sẽ nhận giá trị mặc định từ mã nguồn.
Trong trình xử lý OnCalculate
, chúng ta đợi các chỉ báo WPR và fractal sẵn sàng, sau đó tính toán tín hiệu cho toàn bộ lịch sử hoặc thanh cuối cùng bằng hàm phụ trợ MarkSignals
.
int OnCalculate(const int rates_total,
const int prev_calculated,
const int begin,
const double &data[])
{
if(BarsCalculated(handleEMA3) != rates_total
|| BarsCalculated(handleFractals) != rates_total)
{
return prev_calculated;
}
ArraySetAsSeries(data, true);
if(prev_calculated == 0) // lần chạy đầu tiên
{
ArrayInitialize(UpBuffer, EMPTY_VALUE);
ArrayInitialize(DownBuffer, EMPTY_VALUE);
ArrayInitialize(Filter, 0);
// tìm kiếm tín hiệu trong suốt lịch sử
for(int i = rates_total - FractalOrder - 1; i >= 0; --i)
{
MarkSignals(i, Offset, data);
}
}
else // trực tuyến
{
for(int i = 0; i < rates_total - prev_calculated; ++i)
{
UpBuffer[i] = EMPTY_VALUE;
DownBuffer[i] = EMPTY_VALUE;
Filter[i] = 0;
}
// tìm kiếm tín hiệu trên thanh mới hoặc mỗi tick (nếu Offset == 0)
if(rates_total != prev_calculated
|| Offset == 0)
{
MarkSignals(0, Offset, data);
}
}
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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Chúng ta chủ yếu quan tâm đến việc làm việc với hàm CopyBuffer
ẩn trong MarkSignals
. Giá trị của WPR đã được làm mịn sẽ được đọc vào mảng wpr[2]
, và các fractal sẽ được đọc vào peaks[1]
và hollows[1]
.
int MarkSignals(const int bar, const int offset, const double &data[])
{
double wpr[2];
double peaks[1], hollows[1];
...
2
3
4
5
Sau đó, chúng ta điền vào các mảng cục bộ bằng ba lệnh gọi CopyBuffer
. Lưu ý rằng chúng ta không cần đọc trực tiếp từ IndWPR
, vì nó được sử dụng trong các tính toán của IndTripleEMA
. Chúng ta đọc dữ liệu vào mảng wpr
thông qua handle handleEMA3
. Cũng quan trọng là trong chỉ báo fractal có 2 bộ đệm, do đó hàm CopyBuffer
được gọi hai lần với các chỉ số khác nhau 0 và 1 cho các mảng peaks
và hollows
, tương ứng. Các mảng fractal được đọc với độ thụt bằng FractalOrder
, vì một fractal chỉ có thể hình thành trên một thanh có một số lượng thanh nhất định ở bên trái và bên phải.
if(CopyBuffer(handleEMA3, 0, bar + offset, 2, wpr) == 2
&& CopyBuffer(handleFractals, 0, bar + offset + FractalOrder, 1, peaks) == 1
&& CopyBuffer(handleFractals, 1, bar + offset + FractalOrder, 1, hollows) == 1)
{
...
2
3
4
5
Tiếp theo, chúng ta lấy từ thanh trước của bộ đệm Filter
hướng trước đó của bộ lọc (tại đầu lịch sử là 0, nhưng khi xuất hiện fractal lên hoặc xuống, chúng ta ghi +1 hoặc -1 vào đó, điều này có thể thấy trong mã nguồn ngay dưới đây) và thay đổi nó tương ứng khi phát hiện bất kỳ fractal mới nào.
int filterdirection = (int)Filter[bar + 1];
// fractal cuối cùng đặt chuyển động đảo chiều
if(peaks[0] != EMPTY_VALUE)
{
filterdirection = -1; // bán
}
if(hollows[0] != EMPTY_VALUE)
{
filterdirection = +1; // mua
}
Filter[bar] = filterdirection; // ghi nhớ hướng hiện tại
2
3
4
5
6
7
8
9
10
11
12
13
Cuối cùng, chúng ta phân tích sự chuyển đổi của WPR đã được làm mịn từ vùng trên hoặc dưới sang vùng giữa, tính đến độ rộng của các vùng được chỉ định trong Threshold
.
// dịch 2 giá trị WPR vào khoảng [-1,+1]
const double old = (wpr[0] + 50) / 50; // +1.0 -1.0
const double last = (wpr[1] + 50) / 50; // +1.0 -1.0
// bật ngược từ trên xuống
if(filterdirection == -1
&& old >= 1.0 - Threshold && last <= 1.0 - Threshold)
{
UpBuffer[bar] = data[bar];
return -1; // bán
}
// bật ngược từ dưới lên
if(filterdirection == +1
&& old <= -1.0 + Threshold && last >= -1.0 + Threshold)
{
DownBuffer[bar] = data[bar];
return +1; // mua
}
}
return 0; // không có tín hiệu
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Dưới đây là ảnh chụp màn hình của chỉ báo kết quả trên biểu đồ.
Chú thích hình ảnh: Chỉ báo tín hiệu UseWPRFractals dựa trên WPR, EMA3 và fractals