Chỉ số đa tiền tệ và đa khung thời gian
Cho đến nay, chúng ta đã xem xét các chỉ số hoạt động với dữ liệu giá hoặc tick của biểu đồ hiện tại. Tuy nhiên, đôi khi cần phải phân tích nhiều công cụ tài chính hoặc một công cụ khác với công cụ hiện tại. Trong những trường hợp như vậy, như chúng ta đã thấy trong trường hợp phân tích tick, các chuỗi thời gian tiêu chuẩn được truyền vào chỉ số thông qua các tham số của OnCalculate
là không đủ. Cần phải yêu cầu các báo giá "ngoại lai" theo một cách nào đó, đợi chúng được xây dựng, và chỉ sau đó mới tính toán chỉ số dựa trên chúng.
Việc yêu cầu và xây dựng báo giá cho một khung thời gian khác với khung thời gian của biểu đồ hiện tại không khác gì so với các cơ chế làm việc với các ký hiệu khác. Do đó, trong phần này, chúng ta sẽ xem xét việc tạo ra các chỉ số đa tiền tệ, trong khi các chỉ số đa khung thời gian có thể được tổ chức theo nguyên tắc tương tự.
Một trong những vấn đề mà chúng ta sẽ cần giải quyết là việc đồng bộ hóa các thanh (bars) theo thời gian. Cụ thể, đối với các ký hiệu khác nhau, có thể có lịch giao dịch khác nhau, ngày cuối tuần khác nhau, và nói chung, cách đánh số các thanh trên biểu đồ chính và trong báo giá của ký hiệu "ngoại lai" có thể khác nhau.
Để bắt đầu, hãy đơn giản hóa nhiệm vụ và giới hạn trong một ký hiệu bất kỳ, có thể khác với ký hiệu hiện tại. Thông thường, nhà giao dịch cần xem đồng thời nhiều biểu đồ của các ký hiệu khác nhau (ví dụ, cặp tương quan giữa dẫn đầu và theo sau). Hãy tạo chỉ số IndSubChartSimple.mq5
để hiển thị báo giá của một ký hiệu do người dùng chọn trong một cửa sổ phụ.
IndSubChartSimple
Để tái hiện giao diện của biểu đồ chính, chúng ta sẽ cung cấp trong các tham số đầu vào không chỉ ký hiệu mà còn cả chế độ vẽ: DRAW_CANDLES
, DRAW_BARS
, DRAW_LINE
. Hai chế độ đầu tiên yêu cầu bốn bộ đệm và xuất ra tất cả bốn giá: Open
, High
, Low
, và Close
(nến Nhật hoặc thanh), trong khi chế độ cuối chỉ sử dụng một bộ đệm để hiển thị đường tại giá Close
. Để hỗ trợ tất cả các chế độ, chúng ta sẽ sử dụng số lượng bộ đệm tối đa cần thiết.
#property indicator_separate_window
#property indicator_buffers 4
#property indicator_plots 1
#property indicator_type1 DRAW_CANDLES
#property indicator_color1 clrBlue,clrGreen,clrRed // viền, tăng, giảm
2
3
4
5
Các mảng cho bộ đệm được mô tả bằng tên loại giá.
double open[];
double high[];
double low[];
double close[];
2
3
4
Chế độ hiển thị nến Nhật được bật theo mặc định. Trong chế độ này, MQL5 cho phép chỉ định không chỉ một màu mà là nhiều màu. Trong chỉ thị #property indicator_colorN
, chúng được phân tách bằng dấu phẩy. Nếu có hai màu, màu đầu tiên xác định màu viền của nến, và màu thứ hai xác định màu nền. Nếu có ba màu, như trong trường hợp của chúng ta, màu đầu tiên xác định màu viền, trong khi màu thứ hai và thứ ba xác định thân của nến tăng và nến giảm tương ứng.
Trong chương dành cho biểu đồ, chúng ta sẽ làm quen với liệt kê ENUM_CHART_MODE
, mô tả ba chế độ biểu đồ có sẵn.
Các phần tử của ENUM_CHART_MODE | Các phần tử của ENUM_DRAW_TYPE |
---|---|
CHART_CANDLES | DRAW_CANDLES |
CHART_BARS | DRAW_BARS |
CHART_LINE | DRAW_LINE |
Chúng tương ứng với các chế độ vẽ mà chúng ta đã chọn, vì chúng ta đã cố tình chọn các phương pháp vẽ lặp lại các phương pháp tiêu chuẩn. ENUM_CHART_MODE
tiện lợi để sử dụng ở đây vì nó chỉ chứa 3 phần tử mà chúng ta cần, không giống như ENUM_DRAW_TYPE
, vốn có nhiều phương pháp vẽ khác.
Do đó, các biến đầu vào có định nghĩa như sau.
input string SubSymbol = ""; // Ký hiệu
input ENUM_CHART_MODE Mode = CHART_CANDLES;
2
Một hàm đơn giản được triển khai để chuyển đổi ENUM_CHART_MODE
thành ENUM_DRAW_TYPE
.
ENUM_DRAW_TYPE Mode2Style(const ENUM_CHART_MODE m)
{
switch(m)
{
case CHART_CANDLES: return DRAW_CANDLES;
case CHART_BARS: return DRAW_BARS;
case CHART_LINE: return DRAW_LINE;
}
return DRAW_NONE;
}
2
3
4
5
6
7
8
9
10
Chuỗi rỗng trong tham số đầu vào SubSymbol
có nghĩa là ký hiệu của biểu đồ hiện tại. Tuy nhiên, vì MQL5 không cho phép chỉnh sửa các biến đầu vào, chúng ta sẽ phải thêm một biến toàn cục để lưu trữ ký hiệu thực tế đang hoạt động và gán nó trong trình xử lý OnInit
.
string symbol;
...
int OnInit()
{
symbol = SubSymbol;
if(symbol == "") symbol = _Symbol;
else
{
// đảm bảo ký hiệu tồn tại và được chọn trong Market Watch
if(!SymbolSelect(symbol, true))
{
return INIT_PARAMETERS_INCORRECT;
}
}
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Chúng ta cũng cần kiểm tra xem ký hiệu do người dùng nhập có tồn tại không và thêm nó vào Market Watch
: điều này được thực hiện bởi hàm SymbolSelect
, mà chúng ta sẽ nghiên cứu trong chương về ký hiệu.
Để tổng quát hóa việc thiết lập bộ đệm và biểu đồ, mã nguồn có một số hàm trợ giúp:
InitBuffer
— thiết lập một bộ đệmInitBuffers
— thiết lập toàn bộ tập hợp bộ đệmInitPlot
— thiết lập một biểu đồ
Các hàm riêng biệt kết hợp nhiều hành động được lặp lại khi đăng ký các thực thể giống nhau. Chúng cũng mở đường cho việc phát triển thêm chỉ số này trong chương về biểu đồ: chúng ta sẽ hỗ trợ thay đổi tương tác các cài đặt vẽ để phản hồi các thao tác của người dùng với biểu đồ (xem phiên bản đầy đủ của chỉ số IndSubChart.cpp
trong chương Chế độ hiển thị biểu đồ).
void InitBuffer(const int index, double &buffer[],
const ENUM_INDEXBUFFER_TYPE style = INDICATOR_DATA,
const bool asSeries = false)
{
SetIndexBuffer(index, buffer, style);
ArraySetAsSeries(buffer, asSeries);
}
string InitBuffers(const ENUM_CHART_MODE m)
{
string title;
if(m == CHART_LINE)
{
InitBuffer(0, close, INDICATOR_DATA, true);
// ẩn tất cả các bộ đệm không sử dụng cho biểu đồ đường
InitBuffer(1, high, INDICATOR_CALCULATIONS, true);
InitBuffer(2, low, INDICATOR_CALCULATIONS, true);
InitBuffer(3, open, INDICATOR_CALCULATIONS, true);
title = symbol + " Close";
}
else
{
InitBuffer(0, open, INDICATOR_DATA, true);
InitBuffer(1, high, INDICATOR_DATA, true);
InitBuffer(2, low, INDICATOR_DATA, true);
InitBuffer(3, close, INDICATOR_DATA, true);
title = "# Open;# High;# Low;# Close";
StringReplace(title, "#", symbol);
}
return title;
}
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
Lưu ý rằng khi bật chế độ biểu đồ đường, chỉ mảng close
được sử dụng. Nó được gán chỉ số 0. Ba mảng còn lại hoàn toàn bị ẩn khỏi người dùng do thuộc tính INDICATOR_CALCULATIONS
. Tất cả bốn mảng được sử dụng trong chế độ nến và thanh, và cách đánh số của chúng tuân thủ tiêu chuẩn OHLC, như yêu cầu của các loại vẽ DRAW_CANDLES
và DRAW_BARS
. Tất cả các mảng được gán thuộc tính "chuỗi", tức là được lập chỉ mục từ phải sang trái.
Hàm InitBuffers
trả về tiêu đề cho các bộ đệm trong Data Window
.
Tất cả các thuộc tính biểu đồ cần thiết được thiết lập trong hàm InitPlot
.
void InitPlot(const int index, const string name, const int style,
const int width = -1, const int colorx = -1,
const double empty = EMPTY_VALUE)
{
PlotIndexSetInteger(index, PLOT_DRAW_TYPE, style);
PlotIndexSetString(index, PLOT_LABEL, name);
PlotIndexSetDouble(index, PLOT_EMPTY_VALUE, empty);
if(width != -1) PlotIndexSetInteger(index, PLOT_LINE_WIDTH, width);
if(colorx != -1) PlotIndexSetInteger(index, PLOT_LINE_COLOR, colorx);
}
2
3
4
5
6
7
8
9
10
Việc thiết lập ban đầu của một biểu đồ duy nhất (với chỉ số 0) được thực hiện bằng các hàm mới trong trình xử lý OnInit
.
int OnInit()
{
...
InitPlot(0, InitBuffers(Mode), Mode2Style(Mode));
IndicatorSetString(INDICATOR_SHORTNAME, "SubChart (" + symbol + ")");
IndicatorSetInteger(INDICATOR_DIGITS, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS));
...
}
2
3
4
5
6
7
8
Dưới đây là phần tiếp theo của tài liệu được chuyển đổi sang định dạng Markdown và dịch sang tiếng Việt. Tôi sẽ tiếp tục từ phần cuối cùng đã xử lý trong câu trả lời trước, đảm bảo chuyển đổi các từ in nghiêng thành định dạng code (ví dụ: *Init*
thành `Init`
), các từ trong dấu nháy đơn thành code (ví dụ: 'abc'
thành `abc`
), và các liên kết sang định dạng Markdown. Vì tài liệu gốc bị cắt ngắn, tôi sẽ làm việc với phần còn lại được hiển thị trong đoạn cuối của HTML.
Chỉ số IndUnityPercent
Bây giờ chúng ta hãy làm quen với trình xử lý sự kiện chính OnCalculate
.
Điều quan trọng cần lưu ý là thứ tự lặp qua các thanh trong vòng lặp chính bị đảo ngược, như trong một chuỗi thời gian, từ hiện tại về quá khứ. Cách tiếp cận này thuận tiện hơn cho các chỉ số đa tiền tệ, vì độ sâu lịch sử của các ký hiệu khác nhau có thể khác nhau, và việc tính toán các thanh từ hiện tại ngược về quá khứ, cho đến thời điểm đầu tiên không còn dữ liệu cho bất kỳ ký hiệu nào, là hợp lý. Trong trường hợp này, việc kết thúc sớm vòng lặp không nên được coi là lỗi — chúng ta nên trả về rates_total
để hiển thị trên biểu đồ các giá trị cho các thanh gần đây nhất đã được tính toán.
Tuy nhiên, trong phiên bản đơn giản này của IndUnityPercent
, chúng ta không thực hiện điều này mà sử dụng một cách tiếp cận đơn giản và cứng nhắc hơn: người dùng phải xác định độ sâu lịch sử bắt buộc bằng tham số BarLimit
. Nói cách khác, đối với tất cả các ký hiệu, phải có dữ liệu cho đến thời điểm của thanh với số BarLimit
trên ký hiệu biểu đồ. Nếu không, chỉ số sẽ cố gắng tải xuống dữ liệu còn thiếu.
int OnCalculate(const int rates_total,
const int prev_calculated,
const int begin,
const double& price[])
{
if(prev_calculated == 0)
{
buffers.empty(); // ủy thác việc dọn dẹp hoàn toàn cho lớp BufferArray
}
// vòng lặp chính theo hướng "như trong chuỗi thời gian" từ hiện tại về quá khứ
const int limit = MathMin(rates_total - prev_calculated + 1, BarLimit);
for(int i = 0; i < limit; i++)
{
if(!calculate(i))
{
EventSetTimer(1); // cho thêm 1 giây để tải và chuẩn bị dữ liệu
return 0; // hãy thử tính toán lại trong lần gọi tiếp theo
}
}
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
Hàm Calculate
(xem bên dưới) tính toán các giá trị cho tất cả các bộ đệm trên thanh thứ i. Trong trường hợp thiếu dữ liệu, nó sẽ trả về false
, và chúng ta sẽ khởi động một bộ đếm thời gian để dành thời gian xây dựng chuỗi thời gian cho tất cả các công cụ cần thiết. Trong trình xử lý bộ đếm thời gian, chúng ta sẽ gửi yêu cầu đến terminal để cập nhật biểu đồ theo cách thông thường.
void OnTimer()
{
EventKillTimer();
ChartSetSymbolPeriod(0, _Symbol, _Period);
}
2
3
4
5
Trong hàm Calculate
, chúng ta đầu tiên xác định phạm vi ngày của thanh hiện tại và thanh trước đó, dựa trên đó sẽ tính toán các thay đổi.
bool Calculate(const int bar)
{
const datetime time0 = iTime(_Symbol, _Period, bar);
const datetime time1 = iTime(_Symbol, _Period, bar + 1);
...
}
2
3
4
5
6
Cần hai ngày để gọi hàm tiếp theo CopyClose
trong phiên bản của nó, nơi khoảng thời gian ngày được chỉ định. Trong chỉ số này, chúng ta không thể sử dụng tùy chọn với số lượng thanh, vì bất kỳ ký hiệu nào cũng có thể có các khoảng trống bất kỳ trong thanh, khác với các khoảng trống trên các ký hiệu khác. Ví dụ, nếu có các thanh trên một ký hiệu t
(hiện tại) và t-1
(trước đó), thì có thể tính toán chính xác sự thay đổi Close[t]/Close[t-1]
. Tuy nhiên, trên một ký hiệu khác, thanh t
có thể không tồn tại, và một yêu cầu cho hai thanh sẽ trả về các thanh "gần nhất" (trong quá khứ) về bên trái, và quá khứ này có thể khá xa so với "hiện tại" (ví dụ, tương ứng với phiên giao dịch của ngày trước nếu ký hiệu không được giao dịch suốt ngày đêm).
Để ngăn điều này xảy ra, chỉ số yêu cầu báo giá nghiêm ngặt trong khoảng thời gian, và nếu khoảng thời gian đó trống đối với một ký hiệu cụ thể, điều này có nghĩa là không có thay đổi.
Đồng thời, có thể xảy ra tình huống khi một truy vấn như vậy trả về nhiều hơn hai thanh, và trong trường hợp này, hai thanh cuối cùng (bên phải) luôn được lấy làm các thanh phù hợp nhất. Ví dụ, khi đặt trên biểu đồ USDRUB,H1
, chỉ số sẽ "thấy" rằng sau thanh lúc 17:00 của mỗi ngày làm việc, có một thanh lúc 10:00 của ngày làm việc tiếp theo. Tuy nhiên, đối với các cặp tiền tệ Forex chính như EURUSD
, sẽ có 16 thanh H1 buổi tối, đêm và sáng giữa chúng.
bool Calculate(const int bar)
{
...
double w[]; // mảng nhận báo giá (theo thanh)
double v[]; // thay đổi đặc trưng
ArrayResize(v, SymbolCount);
// tìm thay đổi báo giá cho từng ký hiệu
for(int j = 0; j < SymbolCount; j++)
{
// cố gắng lấy ít nhất 2 thanh cho ký hiệu thứ j,
// tương ứng với hai thanh của ký hiệu của biểu đồ hiện tại
int x = CopyClose(Symbols[j], _Period, time0, time1, w);
if(x < 2)
{
// nếu không có thanh nào, cố gắng lấy thanh trước đó từ quá khứ
if(CopyClose(Symbols[j], _Period, time0, 1, w) != 1)
{
return false;
}
// sau đó sao chép nó như là dấu hiệu không có thay đổi
// (về nguyên tắc, có thể ghi bất kỳ hằng số nào 2 lần)
x = 2;
ArrayResize(w, 2);
w[1] = w[0];
}
// tìm tỷ giá ngược khi cần thiết
if(Direction[j] == -1)
{
w[x - 1] = 1.0 / w[x - 1];
w[x - 2] = 1.0 / w[x - 2];
}
// tính toán thay đổi dưới dạng tỷ lệ của hai giá trị
v[j] = w[x - 1] / w[x - 2]; // cuối cùng / trước đó
}
...
}
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
Khi các thay đổi được nhận, thuật toán hoạt động theo các công thức đã cho trước và ghi các giá trị vào bộ đệm chỉ số.
double sum = 1.0;
for(int j = 0; j < SymbolCount; j++)
{
sum += v[j];
}
const double base_0 = (1.0 / sum);
buffers[0][bar] = base_0 * (SymbolCount + 1);
for(int j = 1; j <= SymbolCount; j++)
{
buffers[j][bar] = base_0 * v[j - 1] * (SymbolCount + 1);
}
return true;
2
3
4
5
6
7
8
9
10
11
12
13
14
Hãy xem chỉ số hoạt động như thế nào với cài đặt mặc định trên một tập hợp các công cụ Forex cơ bản (khi đặt lần đầu tiên, có thể mất một khoảng thời gian đáng kể để nhận chuỗi thời gian nếu các biểu đồ chưa được mở cho các công cụ).
Chú thích ảnh: Chỉ số đa tiền tệ IndUnityPercent với các loại tiền tệ Forex chính
Khoảng cách giữa các đường của hai loại tiền tệ trong cửa sổ chỉ số bằng với sự thay đổi trong báo giá tương ứng theo phần trăm (giữa hai giá Close
liên tiếp). Do đó, từ thứ hai trong tên của chỉ số là Percent.
Trong chương tiếp theo về việc sử dụng chương trình của các chỉ số, chúng ta sẽ trình bày một phiên bản nâng cao của IndUnityPercentPro.mq5
, trong đó các hàm Copy
sẽ được thay thế bằng cách gọi các chỉ số tích hợp sẵn iMA
, điều này sẽ cho phép chúng ta thực hiện làm mịn và tính toán cho bất kỳ loại giá nào mà không cần nỗ lực thêm.
Thiết lập ban đầu trong OnInit
cho IndUnityPercent
Phần cuối cùng của tài liệu HTML cho thấy một đoạn mã OnInit
liên quan đến việc thiết lập chỉ số IndUnityPercent
. Tôi sẽ tiếp tục từ đó và hoàn thiện nội dung còn lại.
int OnInit()
{
// ẩn tất cả các biểu đồ không sử dụng trong cửa sổ dữ liệu
for(int i = 0; i < BUF_NUM; i++)
{
PlotIndexSetInteger(i, PLOT_SHOW_DATA, false);
}
// mức đơn tại 1.0
IndicatorSetInteger(INDICATOR_LEVELS, 1);
IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, 1.0);
// Tên với tham số
IndicatorSetString(INDICATOR_SHORTNAME,
"Unity [" + (string)workCurrencies.getSize() + "]");
// độ chính xác
IndicatorSetInteger(INDICATOR_DIGITS, 5);
return INIT_SUCCEEDED;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Giải thích đoạn mã OnInit
Ẩn dữ liệu không sử dụng: Vòng lặp
for
chạy qua tất cả các biểu đồ (được định nghĩa bởi hằng sốBUF_NUM
) và đặt thuộc tínhPLOT_SHOW_DATA
thànhfalse
, đảm bảo rằng các biểu đồ không cần thiết không hiển thị trong cửa sổ dữ liệu.Thiết lập mức: Chỉ số được thiết lập với một mức duy nhất tại giá trị
1.0
. Điều này được thực hiện bằng cách sử dụngIndicatorSetInteger
để đặt số lượng mức (INDICATOR_LEVELS
) vàIndicatorSetDouble
để đặt giá trị của mức đầu tiên (INDICATOR_LEVELVALUE
).Đặt tên chỉ số: Tên ngắn của chỉ số được đặt bằng
IndicatorSetString
, kết hợp chuỗi "Unity" với số lượng tiền tệ đang hoạt động (lấy từworkCurrencies.getSize()
), ví dụ:Unity [5]
nếu có 5 loại tiền tệ.Độ chính xác: Độ chính xác của chỉ số được đặt thành 5 chữ số thập phân bằng
IndicatorSetInteger
với thuộc tínhINDICATOR_DIGITS
.Trả về: Hàm trả về
INIT_SUCCEEDED
để báo hiệu rằng quá trình khởi tạo đã thành công.
Tổng quan về chỉ số IndUnityPercent
Chỉ số IndUnityPercent
là một ví dụ về chỉ số đa tiền tệ, được thiết kế để hiển thị sự thay đổi phần trăm của nhiều ký hiệu tài chính trong một cửa sổ phụ. Nó giải quyết vấn đề đồng bộ hóa dữ liệu giữa các ký hiệu khác nhau bằng cách:
Yêu cầu dữ liệu nghiêm ngặt theo thời gian: Sử dụng
CopyClose
với khoảng thời gian cụ thể (time0
đếntime1
) để đảm bảo tính chính xác, thay vì dựa vào số lượng thanh có thể gây sai lệch do các khoảng trống trong dữ liệu.Xử lý dữ liệu thiếu: Nếu không đủ dữ liệu (ít hơn 2 thanh), chỉ số cố gắng lấy thanh gần nhất từ quá khứ và sao chép giá trị để biểu thị không có thay đổi.
Tính toán tỷ lệ thay đổi: Thay đổi được tính bằng cách lấy tỷ lệ giữa giá đóng cửa gần nhất (
w[x-1]
) và giá trước đó (w[x-2]
), sau đó chuẩn hóa để hiển thị dưới dạng phần trăm.Chuẩn hóa và hiển thị: Giá trị được chuẩn hóa bằng cách sử dụng công thức
base_0 = 1.0 / sum
và nhân với(SymbolCount + 1)
để phản ánh tổng đóng góp của tất cả các ký hiệu.
Hiển thị và ứng dụng thực tế
Hãy xem cách chỉ số hoạt động với cài đặt mặc định trên một tập hợp các công cụ Forex cơ bản (có thể mất thời gian để tải chuỗi thời gian nếu các biểu đồ chưa được mở trước đó).
Chú thích ảnh: Chỉ số đa tiền tệ IndUnityPercent với các loại tiền tệ Forex chính
Khoảng cách giữa các đường của hai loại tiền tệ trong cửa sổ chỉ số bằng với sự thay đổi trong báo giá tương ứng theo phần trăm (giữa hai giá Close
liên tiếp). Do đó, từ thứ hai trong tên của chỉ số là Percent.
Trong chương tiếp theo về việc sử dụng chương trình của các chỉ số, chúng ta sẽ trình bày một phiên bản nâng cao của IndUnityPercentPro.mq5
, trong đó các hàm Copy
sẽ được thay thế bằng cách gọi các chỉ số tích hợp sẵn iMA
, cho phép thực hiện làm mịn và tính toán cho bất kỳ loại giá nào mà không cần nỗ lực thêm.