Bỏ qua vẽ trên các thanh ban đầu
Trong nhiều trường hợp, theo điều kiện của thuật toán, việc tính toán giá trị chỉ báo không thể bắt đầu từ thanh đầu tiên (thanh ngoài cùng bên trái có sẵn), vì cần đảm bảo số lượng thanh trước đó tối thiểu được chỉ định trong lịch sử. Ví dụ, nhiều loại làm mịn ngụ ý rằng giá trị hiện tại được tính toán bằng cách sử dụng một mảng giá cho N thanh trước đó.
Trong những trường hợp như vậy, có thể không tính được giá trị chỉ báo trên các thanh đầu tiên, hoặc những giá trị này không nhằm hiển thị trên biểu đồ mà chỉ mang tính chất phụ trợ để tính toán các giá trị tiếp theo.
Để tắt hiển thị chỉ báo trên N-1 thanh đầu tiên của lịch sử, hãy đặt thuộc tính PLOT_DRAW_BEGIN
thành N cho chỉ số biểu đồ đồ họa tương ứng: PlotIndexSetInteger(index, PLOT_DRAW_BEGIN, N)
. Theo mặc định, thuộc tính này là 0, nghĩa là dữ liệu được hiển thị từ ngay đầu tiên.
Chúng ta cũng có thể tắt hiển thị đường trên các thanh cần thiết bằng cách đặt chúng thành giá trị trống (EMPTY_VALUE
theo mặc định). Tuy nhiên, lệnh gọi hàm PlotIndexSetInteger
với thuộc tính PLOT_DRAW_BEGIN
thực hiện một điều khác. Qua đó, chúng ta thông báo cho các chương trình bên ngoài số lượng giá trị đầu tiên không quan trọng trong bộ đệm chỉ báo của chúng ta. Đặc biệt, các chỉ báo khác có thể được xây dựng dựa trên chuỗi thời gian của chỉ báo của chúng ta sẽ nhận được giá trị của thuộc tính PLOT_DRAW_BEGIN
trong tham số begin
của trình xử lý OnCalculate
. Do đó, chúng sẽ có cơ hội bỏ qua các thanh.
Trong ví dụ về chỉ báo IndColorWPR.mq5
, hãy thêm một cài đặt tương tự vào hàm OnInit
.
input int WPRPeriod = 14; // Period
void OnInit()
{
...
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, WPRPeriod - 1);
...
}
2
3
4
5
6
7
8
Bây giờ, trong hàm OnCalculate
, có thể loại bỏ việc xóa cưỡng chế các thanh đầu tiên, vì chúng sẽ luôn bị ẩn.
if(prev_calculated == 0)
{
ArrayFill(WPRBuffer, 0, WPRPeriod - 1, EMPTY_VALUE);
}
2
3
4
Nhưng điều này sẽ hoạt động chính xác chỉ khi người dùng đã chọn thủ công chỉ báo của chúng ta làm nguồn chuỗi thời gian cho một chỉ báo khác. Nếu một lập trình viên quyết định sử dụng chỉ báo của chúng ta trong các phát triển của họ, thì có một cơ chế khác để lấy dữ liệu (chúng ta sẽ nói về điều này trong chương tiếp theo), và nó sẽ không cho phép tìm ra thuộc tính PLOT_DRAW_BEGIN
. Do đó, tốt hơn là sử dụng khởi tạo bộ đệm rõ ràng.
Để chứng minh cách sử dụng thuộc tính này trong một chỉ báo khác được tính toán bằng dữ liệu của chỉ báo của chúng ta, hãy chuẩn bị một chỉ báo khác. Đây sẽ là thuật toán Trung bình Di động Lũy thừa Ba nổi tiếng được đóng gói trong chỉ báo IndTripleEMA.mq5
. Khi nó sẵn sàng, sẽ dễ dàng áp dụng nó cho cả chuỗi thời gian giá và các chỉ báo tùy ý, chẳng hạn như chỉ báo IndColorWPR.mq5
trước đó.
Ngoài ra, chúng ta sẽ làm quen với khả năng kỹ thuật mô tả các bộ đệm phụ trợ để tính toán (INDICATOR_CALCULATIONS
).
Công thức EMA ba gồm nhiều bước tính toán. Việc làm mịn theo lũy thừa đơn giản của chu kỳ P cho chuỗi thời gian ban đầu T được biểu thị như sau:
K = 2.0 / (P + 1)
A[i] = T[i] * K + A[i - 1] * (1 - K)
2
Trong đó, K là hệ số trọng số để tính đến các phần tử của chuỗi ban đầu, được tính toán sau chu kỳ P đã cho; (1 - K) là hệ số quán tính áp dụng cho các phần tử của chuỗi đã làm mịn A. Để lấy phần tử thứ i của chuỗi A, chúng ta cộng phần K của phần tử thứ i của chuỗi ban đầu T[i] và phần (1 - K) của phần tử trước đó A[i - 1].
Nếu chúng ta ký hiệu việc làm mịn theo các công thức đã nêu là toán tử E, thì EMA ba bao gồm, như tên gọi của nó, việc áp dụng E ba lần, sau đó ba hàng đã làm mịn kết quả được kết hợp theo một cách đặc biệt.
EMA1 = E(A, P), for all i
EMA2 = E(EMA1, P), for all i
EMA3 = E(EMA2, P), for all i
TEMA = 3 * EMA1 - 3 * EMA2 + EMA3, for all i
2
3
4
EMA ba cung cấp độ trễ nhỏ hơn so với chuỗi ban đầu so với EMA thông thường cùng chu kỳ. Tuy nhiên, nó có đặc điểm là phản ứng nhanh hơn, điều này có thể gây ra các bất thường trong đường kết quả và đưa ra tín hiệu sai.
Việc làm mịn EMA cho phép bạn có được một ước lượng thô về trung bình, bắt đầu từ phần tử thứ hai của chuỗi, và điều này không yêu cầu thay đổi thuật toán. Điều này phân biệt EMA với các phương pháp làm mịn khác đòi hỏi P phần tử trước đó hoặc một thuật toán đã sửa đổi cho các mẫu ban đầu nếu có ít hơn P phần tử. Một số nhà phát triển thích vô hiệu hóa P-1 phần tử đầu tiên của một hàng đã làm mịn ngay cả khi sử dụng EMA. Tuy nhiên, cần lưu ý rằng ảnh hưởng của các phần tử quá khứ của chuỗi trong công thức EMA không giới hạn ở P phần tử, và nó trở nên không đáng kể chỉ khi số lượng phần tử tiến tới vô cực (trong các thuật toán MA nổi tiếng khác, chính xác P phần tử trước đó có ảnh hưởng).
Vì mục đích của cuốn sách này, để nghiên cứu tác động của việc bỏ qua dữ liệu ban đầu, chúng ta sẽ không tắt việc hiển thị các giá trị EMA ban đầu.
Để tính toán ba cấp độ EMA, chúng ta cần các bộ đệm phụ trợ và một bộ đệm nữa cho chuỗi cuối cùng: nó sẽ được hiển thị dưới dạng biểu đồ đường.
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_color1 Orange
#property indicator_width1 1
#property indicator_label1 "EMA³"
double TemaBuffer[];
double Ema[];
double EmaOfEma[];
double EmaOfEmaOfEma[];
void OnInit()
{
...
SetIndexBuffer(0, TemaBuffer, INDICATOR_DATA);
SetIndexBuffer(1, Ema, INDICATOR_CALCULATIONS);
SetIndexBuffer(2, EmaOfEma, INDICATOR_CALCULATIONS);
SetIndexBuffer(3, EmaOfEmaOfEma, INDICATOR_CALCULATIONS);
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Biến đầu vào InpPeriodEMA
cho phép đặt chu kỳ làm mịn. Biến thứ hai, InpHandleBegin
, là một công tắc chế độ mà chúng ta có thể khám phá cách chỉ báo phản ứng với việc tính đến hoặc bỏ qua tham số begin
trong trình xử lý OnCalculate
. Các chế độ có sẵn được tóm tắt trong liệt kê BEGIN_POLICY
và có ý nghĩa như sau (theo thứ tự sắp xếp):
- Dịch chuyển nghiêm ngặt theo
begin
- Xác thực tùy chỉnh dữ liệu ban đầu, không tính đến
begin
- Không xử lý, tức là bỏ qua
begin
và tính toán trực tiếp cho tất cả dữ liệu
enum BEGIN_POLICY
{
STRICT, // strict
CUSTOM, // custom
NONE, // no
};
input int InpPeriodEMA = 14; // EMA period:
input BEGIN_POLICY InpHandleBegin = STRICT; // Handle 'begin' parameter:
2
3
4
5
6
7
8
9
Chế độ thứ hai CUSTOM
dựa trên việc so sánh sơ bộ mỗi phần tử nguồn với EMPTY_VALUE
và thay thế nó bằng một giá trị phù hợp với thuật toán. Điều này sẽ hoạt động chính xác chỉ với những chỉ báo trung thực khởi tạo phần đầu không sử dụng của bộ đệm mà không để lại rác. Chỉ báo IndColorWPR
của chúng ta điền bộ đệm như yêu cầu, và do đó bạn có thể mong đợi kết quả gần giống nhau với các chế độ STRICT
và CUSTOM
.
Hằng số K được chuẩn bị để tính toán EMA dựa trên InpPeriodEMA
.
const double K = 2.0 / (InpPeriodEMA + 1);
Chính hàm EMA
khá đơn giản (đoạn bảo vệ cho biến thể CUSTOM
với kiểm tra EMPTY_VALUE
được bỏ qua ở đây).
void EMA(const double &source[], double &result[], const int pos, const int begin = 0)
{
...
if(pos <= begin)
{
result[pos] = source[pos];
}
else
{
result[pos] = source[pos] * K + result[pos - 1] * (1 - K);
}
}
2
3
4
5
6
7
8
9
10
11
12
Và đây là tính toán đầy đủ của việc làm mịn ba lần trong OnCalculate
.
int OnCalculate(const int rates_total,
const int prev_calculated,
const int begin,
const double &price[])
{
const int _begin = InpHandleBegin == STRICT ? begin : 0;
// khởi đầu mới, hoặc cập nhật lịch sử
if(prev_calculated == 0)
{
Print("begin=", begin, " ", EnumToString(InpHandleBegin));
// chúng ta có thể thay đổi cài đặt biểu đồ động
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, _begin);
// chuẩn bị mảng
ArrayInitialize(Ema, EMPTY_VALUE);
ArrayInitialize(EmaOfEma, EMPTY_VALUE);
ArrayInitialize(EmaOfEmaOfEma, EMPTY_VALUE);
ArrayInitialize(TemaBuffer, EMPTY_VALUE);
Ema[_begin] = EmaOfEma[_begin] = EmaOfEmaOfEma[_begin] = price[_begin];
}
// vòng lặp chính, tính đến việc bắt đầu từ _begin
for(int i = fmax(prev_calculated - 1, _begin);
i < rates_total && !IsStopped(); i++)
{
EMA(price, Ema, i, _begin);
EMA(Ema, EmaOfEma, i, _begin);
EMA(EmaOfEma, EmaOfEmaOfEma, i, _begin);
if(InpHandleBegin == CUSTOM) // bảo vệ khỏi các phần tử trống ở đầu
{
if(Ema[i] == EMPTY_VALUE
|| EmaOfEma[i] == EMPTY_VALUE
|| EmaOfEmaOfEma[i] == EMPTY_VALUE)
continue;
}
TemaBuffer[i] = 3 * Ema[i] - 3 * EmaOfEma[i] + EmaOfEmaOfEma[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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Trong lần khởi động đầu tiên hoặc khi lịch sử được cập nhật, giá trị nhận được của tham số begin
cùng với chế độ xử lý do người dùng chọn được ghi vào nhật ký.
Sau khi biên dịch thành công, mọi thứ đã sẵn sàng cho các thử nghiệm.
Trước tiên, hãy chạy chỉ báo IndColorWPR
(theo mặc định, chu kỳ của nó là 14, nghĩa là, theo mã nguồn, đặt thuộc tính PLOT_DRAW_BEGIN
nhỏ hơn 1 vì chỉ số bắt đầu từ 0 và thanh thứ 13 sẽ là thanh đầu tiên xuất hiện giá trị). Sau đó kéo chỉ báo IndTripleEMA
vào cửa sổ phụ hiển thị WPR. Trong hộp thoại cài đặt thuộc tính mở ra, trên tab Options
, chọn Previous indicator data
trong danh sách thả xuống Apply to
. Để lại các giá trị mặc định trên tab Inputs
.
Hình ảnh sau cho thấy phần đầu của biểu đồ. Nhật ký sẽ có mục nhập sau: begin=13 STRICT
.
Chỉ báo EMA ba áp dụng cho WPR với phần đầu của dữ liệu
Lưu ý rằng đường trung bình bắt đầu ở một khoảng cách từ điểm bắt đầu, cũng như WPR.
Chú ý! Số lượng thanh có sẵn để tính toán chỉ báo
rates_total
(hoặciBars(_Symbol, _Period)
) có thể vượt quá số lượng thanh tối đa được phép trên biểu đồ từ cài đặt terminal nếu có lịch sử báo giá cục bộ dài hơn. Trong trường hợp này, các phần tử trống ở đầu đường WPR (hoặc bất kỳ chỉ báo nào khác bỏ qua các phần tử đầu tiên, như MA) sẽ trở nên vô hình — chúng sẽ bị ẩn sau biên trái của biểu đồ. Để tái hiện tình huống không có đường trên các thanh ban đầu, bạn sẽ cần tăng số lượng thanh trên biểu đồ hoặc đóng terminal và xóa lịch sử cục bộ cho một biểu tượng cụ thể.
Bây giờ hãy chuyển sang chế độ CUSTOM
trong cài đặt chỉ báo IndTripleEMA
(nhật ký sẽ hiển thị begin=0 CUSTOM
). Không nên có thay đổi nghiêm trọng trong các giá trị chỉ báo.
Cuối cùng, chúng ta kích hoạt chế độ NONE
. Nhật ký sẽ xuất ra: begin=0 NONE
.
Ở đây, tình huống trên biểu đồ sẽ trông kỳ lạ, vì đường thực sự sẽ biến mất. Trong Data Window
, bạn có thể thấy rằng các giá trị phần tử rất lớn.
Chỉ báo EMA ba áp dụng cho WPR mà không có điểm bắt đầu dữ liệu
Điều này là do các giá trị của EMPTY_VALUE
bằng với số thực tối đa DBL_MAX
. Do đó, không tính đến tham số begin
, các phép tính với những giá trị như vậy cũng tạo ra các số rất lớn. Tùy thuộc vào đặc thù của phép tính, sự tràn số có thể khiến chúng ta nhận được một giá trị đặc biệt NaN
(Không phải Số, xem Kiểm tra số thực có bình thường không). Một trong số đó, -nan(ind)
, được đánh dấu trong hình ảnh (Data Window
đã biết cách xuất một số loại NaN, ví dụ, "inf" và "-inf", nhưng điều này chưa áp dụng cho "-nan(ind)"). Như chúng ta biết, các giá trị NaN như vậy rất nguy hiểm, vì các phép tính liên quan đến chúng cũng sẽ tiếp tục cho ra NaN. Nếu không tạo ra NaN, thì khi di chuyển sang phải qua các thanh, "quá trình chuyển tiếp" trong việc tính toán các số lớn sẽ giảm dần (do hệ số giảm (1 - K) trong công thức EMA), và kết quả ổn định, trở nên hợp lý. Nếu bạn cuộn biểu đồ đến thời điểm hiện tại, bạn sẽ thấy một EMA ba bình thường.
Việc tính đến tham số begin
là một thực hành tốt, nhưng nó không đảm bảo rằng nhà cung cấp dữ liệu (nếu đó là chỉ báo của bên thứ ba) đã điền đúng thuộc tính này. Do đó, mong muốn cung cấp một số bảo vệ trong mã của bạn. Trong triển khai này của IndTripleEMA
, nó được thực hiện ở mức ban đầu.
Nếu chúng ta chạy chỉ báo IndTripleEMA
trên biểu đồ giá, bạn sẽ luôn nhận được begin = 0
, vì chuỗi thời gian giá được điền dữ liệu thực từ ngay đầu tiên, ngay cả trên các thanh cũ nhất.