Sự kiện chính của chỉ báo: OnCalculate
Hàm OnCalculate
là điểm vào chính của mã chỉ báo MQL5. Nó được gọi mỗi khi sự kiện OnCalculate xảy ra, được tạo ra khi dữ liệu giá thay đổi. Ví dụ, điều này có thể xảy ra khi một tick mới cho một biểu tượng đến hoặc khi giá cũ thay đổi (điền vào khoảng trống trong lịch sử hoặc tải dữ liệu bị thiếu từ máy chủ).
Có hai biến thể của hàm, khác nhau về nguồn dữ liệu cho việc tính toán:
- Full — cung cấp một tập hợp các chuỗi thời gian giá tiêu chuẩn trong các tham số (giá OHLC, khối lượng, chênh lệch)
- Reduced — cho một chuỗi thời gian tùy ý (không nhất thiết là tiêu chuẩn)
Một chỉ báo chỉ nên sử dụng một trong hai tùy chọn, trong khi không thể kết hợp chúng trong cùng một chỉ báo.
Trong trường hợp sử dụng dạng rút gọn của OnCalculate
, khi đặt chỉ báo lên biểu đồ, một tab bổ sung sẽ xuất hiện trong hộp thoại thuộc tính của nó. Nó cung cấp danh sách thả xuống Apply to
, trong đó bạn nên chọn chuỗi thời gian ban đầu mà chỉ báo sẽ được tính toán dựa trên đó. Theo mặc định, nếu không có chuỗi thời gian nào được chọn, phép tính sẽ dựa trên giá trị giá Close
.
Chọn chuỗi thời gian ban đầu cho chỉ báo với dạng ngắn OnCalculate
Danh sách luôn cung cấp các loại giá tiêu chuẩn, nhưng nếu có các chỉ báo khác trên biểu đồ, cài đặt này cho phép bạn chọn một trong số chúng làm nguồn dữ liệu cho chỉ báo khác, từ đó xây dựng một chuỗi xử lý từ các chỉ báo. Chúng ta sẽ thử xây dựng một chỉ báo từ một chỉ báo khác trong phần Bỏ qua vẽ trên các thanh ban đầu. Khi sử dụng dạng đầy đủ, tùy chọn này không khả dụng.
Không được phép áp dụng chỉ báo cho các chỉ báo tích hợp sẵn sau: Fractals, Gator, Ichimoku và Parabolic SAR.
Dạng ngắn của OnCalculate
có nguyên mẫu sau.
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &data[])
Mảng data
chứa dữ liệu ban đầu cho phép tính toán. Đây có thể là một trong những chuỗi thời gian giá hoặc bộ đệm được tính toán của một chỉ báo khác. Tham số rates_total
chỉ định kích thước của mảng data
. Các lệnh gọi ArraySize(data)
hoặc iBars(NULL, 0)
nên cho cùng giá trị với rates_total
.
Tham số prev_calculated
được thiết kế để tính toán lại chỉ báo một cách hiệu quả trên một số lượng nhỏ các thanh mới (thường là trên một thanh cuối cùng), thay vì tính toán toàn bộ trên tất cả các thanh. Giá trị prev_calculated
bằng kết quả của hàm OnCalculate
được trả về cho runtime từ lần gọi hàm trước đó. Ví dụ, nếu khi nhận được tick tiếp theo, chỉ báo đã tính toán công thức cho tất cả các thanh, nó nên trả về giá trị rates_total
A từ OnCalculate
(ở đây chỉ số A nghĩa là thời điểm ban đầu). Sau đó, vào tick tiếp theo, khi nhận được sự kiện OnCalculate
, terminal sẽ đặt prev_calculated
thành giá trị trước đó rates_total
A. Tuy nhiên, số lượng thanh trong thời gian này có thể đã thay đổi, và giá trị mới rates_total
sẽ tăng; hãy gọi nó là rates_total
B. Do đó, chỉ các thanh từ prev_calculated
(hay còn gọi là rates_total
A) đến rates_total
B sẽ được tính toán.
Tuy nhiên, tình huống phổ biến nhất là khi các tick mới nằm trong thanh số không hiện tại, tức là rates_total
không thay đổi, và do đó trong hầu hết các lệnh gọi OnCalculate
, chúng ta có đẳng thức prev_calculated == rates_total
. Liệu có cần tính toán lại gì trong trường hợp này không? Điều đó phụ thuộc vào bản chất của phép tính. Ví dụ, nếu chỉ báo được tính toán dựa trên giá mở thanh, vốn không thay đổi, thì không có lý do gì để tính toán lại. Tuy nhiên, nếu chỉ báo sử dụng giá đóng (thực tế là giá của tick cuối cùng đã biết) hoặc bất kỳ giá tổng hợp nào khác phụ thuộc vào Close
, thì thanh cuối cùng luôn nên được tính toán lại.
Lần đầu tiên hàm OnCalculate
được gọi, giá trị của prev_calculated
bằng 0.
Nếu kể từ lần gọi cuối cùng của hàm OnCalculate
, dữ liệu giá đã thay đổi (ví dụ, lịch sử sâu hơn đã được tải lên hoặc các khoảng trống đã được điền), thì giá trị của tham số prev_calculated
cũng sẽ được terminal đặt thành 0. Do đó, chỉ báo sẽ nhận được tín hiệu để tính toán lại hoàn toàn trên toàn bộ lịch sử có sẵn.
Nếu hàm OnCalculate
trả về giá trị null, chỉ báo sẽ không được vẽ, và tên cùng giá trị của các bộ đệm của nó trong Data window
sẽ bị ẩn.
Lưu ý rằng việc trả về số lượng thanh đầy đủ
rates_total
là cách tiêu chuẩn duy nhất để thông báo cho terminal và các chương trình MQL khác sẽ sử dụng chỉ báo rằng dữ liệu của nó đã sẵn sàng. Ngay cả khi một chỉ báo được thiết kế để tính toán và hiển thị chỉ một lượng dữ liệu giới hạn, nó vẫn nên trả vềrates_total
.
Hướng lập chỉ mục của mảng data
có thể được chọn bằng cách gọi ArraySetAsSeries (mặc định là false
, điều này có thể được xác minh bằng cách gọi ArrayGetAsSeries
). Đồng thời, nếu chúng ta áp dụng hàm ArrayIsSeries cho mảng, nó sẽ trả về true
. Điều này có nghĩa là mảng này là mảng nội bộ, được quản lý bởi terminal. Chỉ báo không thể thay đổi nó theo bất kỳ cách nào, mà chỉ có thể đọc nó, đặc biệt vì có một sửa đổi const
trong mô tả tham số.
Tham số begin
báo cáo số lượng giá trị ban đầu của mảng data
cần được loại trừ khỏi phép tính. Tham số này được hệ thống đặt khi chỉ báo của chúng ta được người dùng cấu hình sao cho nó nhận data
từ một chỉ báo khác (xem hình ảnh trên). Ví dụ, nếu chỉ báo nguồn dữ liệu được chọn tính toán trung bình động kỳ N
, thì N - 1
thanh đầu tiên, theo định nghĩa, không chứa dữ liệu nguồn, vì không thể lấy trung bình trên N
thanh ở đó. Nếu nhà phát triển đã đặt một thuộc tính đặc biệt trong chỉ báo nguồn này, nó sẽ được truyền chính xác cho chúng ta trong tham số begin
. Chúng ta sẽ sớm kiểm tra khía cạnh này trong thực tế (xem phần Bỏ qua vẽ trên các thanh ban đầu).
Hãy thử tạo một chỉ báo trống với dạng rút gọn của OnCalculate
. Nó chưa thể làm gì nhưng sẽ phục vụ như một bước chuẩn bị cho các thí nghiệm tiếp theo. Tệp gốc IndStub.mq5
có thể được tìm thấy trong thư mục MQL5/Indicators/MQL5Book/p5/
. Để đảm bảo chỉ báo hoạt động, hãy thêm vào OnCalculate
khả năng xuất giá trị prev_calculated
và rates_total
vào nhật ký và đếm số lần gọi hàm.
int OnCalculate(const int rates_total,
const int prev_calculated,
const int begin,
const double &data[])
{
static int count = 0;
++count;
// so sánh số lượng thanh ở lần gọi trước và lần hiện tại
if(prev_calculated != rates_total)
{
// chỉ báo hiệu khi có sự khác biệt
PrintFormat("calculated=%d rates=%d; %d ticks",
prev_calculated, rates_total, count);
}
return rates_total; // trả về số lượng thanh đã xử lý
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Điều kiện về sự không bằng nhau của prev_calculated
và rates_total
đảm bảo rằng thông báo chỉ xuất hiện lần đầu tiên khi chỉ báo được đặt lên biểu đồ, và cũng khi các thanh mới xuất hiện. Tất cả các tick đến trong quá trình hình thành thanh hiện tại sẽ không thay đổi số lượng thanh, và do đó prev_calculated
và rates_total
sẽ bằng nhau. Tuy nhiên, chúng ta sẽ đếm tổng số tick trong biến count.
Các tham số còn lại vẫn chưa được sử dụng, nhưng chúng ta sẽ dần dần tận dụng tất cả các khả năng.
Mã nguồn này biên dịch thành công, nhưng tạo ra hai cảnh báo.
no indicator window property is defined, indicator_chart_window is applied
no indicator plot defined for indicator
2
Chúng cho biết sự vắng mặt của một số chỉ thị #property
, mặc dù không bắt buộc, nhưng đặt các thuộc tính cơ bản của chỉ báo. Cụ thể, cảnh báo đầu tiên cho biết rằng chưa chọn phương thức liên kết cho chỉ báo: cửa sổ chính hay cửa sổ phụ, và do đó cửa sổ biểu đồ chính sẽ được sử dụng theo mặc định. Cảnh báo thứ hai liên quan đến việc chúng ta chưa đặt số lượng biểu đồ để hiển thị. Như đã đề cập, một số chỉ báo được thiết kế có chủ đích không có bộ đệm vì chúng được thiết kế để thực hiện các hành động khác, nhưng trong trường hợp của chúng ta, đây là lời nhắc nhở để thêm phần hình ảnh sau này.
Chúng ta sẽ xử lý việc loại bỏ các cảnh báo trong vài đoạn tiếp theo, nhưng bây giờ, chúng ta sẽ khởi chạy chỉ báo trên biểu đồ EURUSD,M1. Chúng ta sử dụng khung thời gian M1 vì cách này chúng ta có thể nhanh chóng thấy sự hình thành của các thanh mới và sự xuất hiện của các thông báo trong nhật ký.
calculated=0 rates=10002; 1 ticks
calculated=10002 rates=10003; 30 ticks
calculated=10003 rates=10004; 90 ticks
calculated=10004 rates=10005; 167 ticks
calculated=10005 rates=10006; 240 ticks
2
3
4
5
Do đó, chúng ta thấy rằng trình xử lý OnCalculate
được gọi như kỳ vọng, và bạn có thể thực hiện các phép tính trong đó, cả trên mỗi tick và trên các thanh. Chỉ báo có thể được xóa khỏi biểu đồ bằng cách gọi hộp thoại Indicator List
từ menu ngữ cảnh của biểu đồ: chọn chỉ báo mong muốn và nhấn Delete
.
Bây giờ hãy quay lại với một nguyên mẫu khác của hàm OnCalculate
. Chúng ta đã thử nghiệm phiên bản rút gọn trong thực tế, nhưng chúng ta cũng có thể thực hiện một bản trống tương tự cho dạng đầy đủ.
Dạng đầy đủ được thiết kế để tính toán dựa trên các chuỗi thời gian giá tiêu chuẩn và có nguyên mẫu sau.
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[])
Các tham số rates_total
và prev_calculated
có ý nghĩa tương tự như trong dạng đơn giản của OnCalculate
: rates_total
đặt kích thước của các chuỗi thời gian được truyền (tất cả các mảng có cùng độ dài vì đây là tổng số thanh trên biểu đồ), và prev_calculated
chứa số lượng thanh được xử lý trong lần gọi trước (tức là giá trị mà hàm OnCalculate
đã trả về trước đó cho terminal bằng câu lệnh return).
Các mảng open
, high
, low
, và close
chứa các giá liên quan cho các thanh của biểu đồ hiện tại: chuỗi thời gian của biểu tượng và khung thời gian đang hoạt động. Mảng time
chứa thời gian mở cho mỗi thanh, và tick_volume
và volume
chứa khối lượng giao dịch (tick và sàn) cho mỗi thanh.
Trong chương trước, chúng ta đã nghiên cứu Timeseries với các loại giá và khối lượng tiêu chuẩn được terminal cung cấp cho các chương trình MQL thông qua một tập hợp các hàm. Vì vậy, để thuận tiện cho việc tính toán chỉ báo, các chuỗi thời gian này được truyền trực tiếp tới trình xử lý OnCalculate
dưới dạng tham chiếu tới mảng. Điều này loại bỏ nhu cầu gọi các hàm này và sao chép (nhân đôi) báo giá vào các mảng nội bộ. Tất nhiên, kỹ thuật này chỉ phù hợp với những chỉ báo được tính toán trên một kết hợp của biểu tượng và khung thời gian đang hoạt động khớp với biểu đồ hiện tại. Tuy nhiên, MQL5 cho phép tạo các chỉ báo đa tiền tệ, đa khung thời gian, cũng như các chỉ báo cho các biểu tượng và khung thời gian khác ngoài biểu đồ hiện tại. Trong tất cả các trường hợp như vậy, không thể thực hiện mà không có các hàm truy cập chuỗi thời gian. Một chút sau chúng ta sẽ thấy điều này được thực hiện như thế nào.
Nếu chúng ta kiểm tra tất cả các mảng được truyền xem chúng có thuộc về terminal bằng ArrayIsSeries hay không, hàm này sẽ trả về true
. Tất cả các mảng đều chỉ đọc. Sửa đổi const
trong mô tả tham số cũng nhấn mạnh điều này.
Chọn giữa dạng đầy đủ và dạng rút gọn dựa trên dữ liệu mà thuật toán tính toán cần. Ví dụ, để làm mịn một mảng bằng thuật toán trung bình động, chỉ cần một mảng đầu vào, và do đó chỉ báo có thể được xây dựng cho bất kỳ loại giá nào mà người dùng chọn. Tuy nhiên, các chỉ báo nổi tiếng ParabolicSAR
hoặc ZigZag
yêu cầu giá High
và Low
và do đó phải sử dụng phiên bản đầy đủ của OnCalculate
. Trong các phần tiếp theo, chúng ta sẽ thấy các ví dụ về chỉ báo cho cả phiên bản đơn giản của OnCalculate
và phiên bản đầy đủ.