Sử dụng các chỉ báo tích hợp sẵn
Là một ví dụ giới thiệu đơn giản về cách sử dụng chỉ báo tích hợp sẵn, hãy gọi hàm iStochastic
. Nguyên mẫu của hàm chỉ báo này như sau:
int iStochastic(const string symbol, ENUM_TIMEFRAMES timeframe,
int Kperiod, int Dperiod, int slowing,
ENUM_MA_METHOD method, ENUM_STO_PRICE price)
2
3
Như chúng ta thấy, ngoài các tham số tiêu chuẩn symbol
và time frame
, chỉ báo Stochastic có một số tham số cụ thể:
Kperiod
– số lượng thanh để tính đường %KDperiod
– chu kỳ làm mượt chính cho đường %Dslowing
– chu kỳ làm mượt thứ cấp (giảm tốc)method
– phương pháp trung bình (làm mượt)price
– phương pháp tính toán Stochastic
Hãy thử tạo chỉ báo riêng của chúng ta có tên UseStochastic.mq5
, chỉ báo này sẽ sao chép các giá trị của Stochastic vào các bộ đệm của nó. Vì Stochastic có hai bộ đệm, chúng ta cũng sẽ dự trữ hai bộ đệm: đây là các đường "chính" và "tín hiệu".
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots 2
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrBlue
#property indicator_width1 1
#property indicator_label1 "St`Main"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrChocolate
#property indicator_width2 1
#property indicator_label2 "St`Signal"
#property indicator_style2 STYLE_DOT
2
3
4
5
6
7
8
9
10
11
12
13
14
Trong các biến đầu vào, chúng ta cung cấp tất cả các tham số cần thiết.
input int KPeriod = 5;
input int DPeriod = 3;
input int Slowing = 3;
input ENUM_MA_METHOD Method = MODE_SMA;
input ENUM_STO_PRICE StochasticPrice = STO_LOWHIGH;
2
3
4
5
Tiếp theo, chúng ta mô tả các mảng cho bộ đệm chỉ báo và một biến toàn cục cho descriptor.
double MainBuffer[];
double SignalBuffer[];
int Handle;
2
3
4
Chúng ta sẽ khởi tạo trong OnInit
.
int OnInit()
{
IndicatorSetString(INDICATOR_SHORTNAME,
StringFormat("Stochastic(%d,%d,%d)", KPeriod, DPeriod, Slowing));
// binding of arrays as buffers
SetIndexBuffer(0, MainBuffer);
SetIndexBuffer(1, SignalBuffer);
// getting the descriptor Stochastic
Handle = iStochastic(_Symbol, _Period,
KPeriod, DPeriod, Slowing, Method, StochasticPrice);
return Handle == INVALID_HANDLE ? INIT_FAILED : INIT_SUCCEEDED;
}
2
3
4
5
6
7
8
9
10
11
12
Bây giờ, trong OnCalculate
, chúng ta cần đọc dữ liệu bằng hàm CopyBuffer
ngay khi handle đã sẵn sàng.
int OnCalculate(const int rates_total,
const int prev_calculated,
const int begin,
const double &data[])
{
// waiting for the calculation of the stochastic on all bars
if(BarsCalculated(Handle) != rates_total)
{
return prev_calculated;
}
// copy data to our two buffers
const int n = CopyBuffer(Handle, 0, 0, rates_total - prev_calculated + 1,
MainBuffer);
const int m = CopyBuffer(Handle, 1, 0, rates_total - prev_calculated + 1,
SignalBuffer);
return n > -1 && m > -1 ? rates_total : 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Lưu ý rằng chúng ta gọi CopyBuffer
hai lần: cho từng bộ đệm riêng biệt (0 và 1 trong tham số thứ hai). Việc cố gắng đọc một bộ đệm với chỉ số không tồn tại, ví dụ như 2, sẽ tạo ra lỗi và chúng ta sẽ không nhận được dữ liệu nào.
Chỉ báo của chúng ta không đặc biệt hữu ích, vì nó không thêm gì vào Stochastic gốc và không phân tích các giá trị của nó. Mặt khác, chúng ta có thể chắc chắn rằng các đường của chỉ báo tiêu chuẩn trong terminal và các đường được tạo trong MQL5 trùng khớp (các mức và cài đặt độ chính xác cũng có thể dễ dàng được thêm vào, như chúng ta đã làm với các chỉ báo hoàn toàn tùy chỉnh, nhưng sau đó sẽ khó phân biệt bản sao với bản gốc).
Stochastic tiêu chuẩn và tùy chỉnh dựa trên hàm iStochastic
Để chứng minh việc lưu trữ chỉ báo trong bộ nhớ cache của terminal, hãy thêm một vài dòng vào hàm OnInit
.
double array[];
Print("This is very first copy of iStochastic with such settings=",
!(CopyBuffer(Handle, 0, 0, 10, array) > 0));
2
3
Ở đây, chúng ta đã sử dụng một mẹo liên quan đến các tính năng đã biết: ngay sau khi chỉ báo được tạo, cần một khoảng thời gian để tính toán, và không thể đọc dữ liệu từ bộ đệm ngay sau khi nhận được handle. Điều này đúng trong trường hợp khởi động "lạnh", khi chỉ báo với các tham số được chỉ định chưa tồn tại trong bộ nhớ cache, trong bộ nhớ của terminal. Nếu đã có một bản tương tự sẵn sàng, thì chúng ta có thể truy cập bộ đệm ngay lập tức.
Sau khi biên dịch chỉ báo mới, bạn nên đặt hai bản sao của nó trên hai biểu đồ của cùng một ký hiệu và khung thời gian. Lần đầu tiên, một thông báo với cờ true
sẽ được hiển thị trong nhật ký (đây là bản sao đầu tiên), và lần thứ hai (và các lần tiếp theo, nếu có nhiều biểu đồ) sẽ là false
. Bạn cũng có thể thêm thủ công chỉ báo "Stochastic Oscillator" tiêu chuẩn vào biểu đồ trước (với cài đặt mặc định hoặc những cài đặt sẽ được áp dụng trong Use Stochastic
) và sau đó chạy Use Stochastic
: chúng ta cũng cần nhận được false
.
Bây giờ, hãy thử nghĩ ra một điều gì đó nguyên bản dựa trên một chỉ báo tiêu chuẩn. Chỉ báo sau đây UseM1MA.mq5
được thiết kế để tính giá trung bình mỗi thanh trên các khung thời gian M5 trở lên (chủ yếu là trong ngày). Nó tích lũy giá của các thanh M1 nằm trong phạm vi dấu thời gian của mỗi thanh cụ thể trên khung thời gian làm việc (cao hơn). Điều này cho phép bạn ước tính giá hiệu quả của một thanh chính xác hơn nhiều so với các loại giá tiêu chuẩn (Close
, Open
, Median
, Typical
, Weighted
, v.v.). Ngoài ra, chúng ta sẽ cung cấp khả năng trung bình hóa các giá như vậy trong một khoảng thời gian nhất định, nhưng ở đây bạn nên chuẩn bị rằng một đường mượt mà đặc biệt sẽ không hoạt động.
Chỉ báo sẽ được hiển thị trong cửa sổ chính và chứa một bộ đệm duy nhất. Cài đặt có thể được thay đổi bằng 3 tham số:
input uint _BarLimit = 100; // BarLimit
input uint BarPeriod = 1;
input ENUM_APPLIED_PRICE M1Price = PRICE_CLOSE;
2
3
BarLimit
đặt số lượng thanh của lịch sử gần nhất để tính toán. Điều này quan trọng vì các biểu đồ khung thời gian cao có thể yêu cầu một số lượng thanh rất lớn khi so sánh với phút M1 (ví dụ, một ngày D1 trong giao dịch 24/7 được biết là chứa 1440 thanh M1). Điều này có thể dẫn đến việc tải thêm dữ liệu và chờ đồng bộ hóa. Hãy thử nghiệm với cài đặt mặc định tiết kiệm (100 thanh của khung thời gian làm việc) trước khi đặt tham số này thành 0, nghĩa là xử lý không giới hạn.
Tuy nhiên, ngay cả khi đặt BarLimit
thành 0, chỉ báo có thể không được tính toán cho toàn bộ lịch sử hiển thị của khung thời gian cao hơn: nếu terminal có giới hạn về số lượng thanh trong biểu đồ, thì nó cũng sẽ ảnh hưởng đến các yêu cầu cho thanh M1. Nói cách khác, độ sâu phân tích được xác định bởi thời gian mà số lượng thanh M1 tối đa được phép đi vào lịch sử.
BarPeriod
đặt số lượng thanh của khung thời gian cao hơn để thực hiện trung bình hóa. Giá trị mặc định ở đây là 1, cho phép bạn thấy giá hiệu quả của từng thanh riêng lẻ.
Tham số M1Price
chỉ định loại giá được sử dụng để tính toán cho các thanh M1.
Trong ngữ cảnh toàn cục, một mảng được mô tả cho bộ đệm, một descriptor và một cờ tự cập nhật, mà chúng ta cần để chờ việc xây dựng chuỗi thời gian của khung thời gian M1 "ngoại lai".
double Buffer[];
int Handle;
int BarLimit;
bool PendingRefresh;
const string MyName = "M1MA (" + StringSubstr(EnumToString(M1Price), 6)
+ "," + (string)BarPeriod + "[" + (string)(PeriodSeconds() / 60) + "]";
const uint P = PeriodSeconds() / 60 * BarPeriod;
2
3
4
5
6
7
8
9
Ngoài ra, tên của chỉ báo và chu kỳ trung bình hóa P
được hình thành ở đây. Hàm PeriodSeconds
, trả về số giây trong một thanh của khung thời gian hiện tại, cho phép bạn tính số thanh M1 trong một thanh hiện tại: PeriodSeconds() / 60
(60 giây là thời gian của thanh M1).
Việc khởi tạo thông thường được thực hiện trong OnInit
.
int OnInit()
{
IndicatorSetString(INDICATOR_SHORTNAME, MyName);
IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
SetIndexBuffer(0, Buffer);
Handle = iMA(_Symbol, PERIOD_M1, P, 0, MODE_SMA, M1Price);
return Handle != INVALID_HANDLE ? INIT_SUCCEEDED : INIT_FAILED;
}
2
3
4
5
6
7
8
9
10
11
Để lấy giá trung bình trên thanh của khung thời gian cao hơn, chúng ta áp dụng đường trung bình động đơn giản, gọi iMA
với chế độ MODE_SMA.
Hàm OnCalculate
dưới đây được đưa ra với các đơn giản hóa. Khi chạy lần đầu hoặc lịch sử thay đổi, chúng ta xóa bộ đệm và điền vào biến BarLimit
(nó cần thiết vì các biến đầu vào không thể chỉnh sửa, và chúng ta muốn diễn giải giá trị 0 là số lượng thanh tối đa có sẵn để tính toán). Trong các lần gọi tiếp theo, các phần tử bộ đệm chỉ được xóa trên các thanh cuối cùng, bắt đầu từ prev_calculated
và không quá BarLimit
.
int OnCalculate(ON_CALCULATE_STD_FULL_PARAM_LIST)
{
if(prev_calculated == 0)
{
ArrayInitialize(Buffer, EMPTY_VALUE);
if(_BarLimit == 0
|| _BarLimit > (uint)rates_total)
{
BarLimit = rates_total;
}
else
{
BarLimit = (int)_BarLimit;
}
}
else
{
for(int i = fmax(prev_calculated - 1, (int)(rates_total - BarLimit));
i < rates_total; ++i)
{
Buffer[i] = EMPTY_VALUE;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Trước khi đọc dữ liệu từ chỉ báo iMA
đã tạo, bạn cần đợi chúng sẵn sàng: để làm điều này, chúng ta so sánh BarsCalculated
với số lượng thanh M1.
if(BarsCalculated(Handle) != iBars(_Symbol, PERIOD_M1))
{
if(prev_calculated == 0)
{
EventSetTimer(1);
PendingRefresh = true;
}
return prev_calculated;
}
...
2
3
4
5
6
7
8
9
10
Nếu dữ liệu chưa sẵn sàng, chúng ta khởi động một bộ đếm thời gian để thử đọc lại sau một giây.
Tiếp theo, chúng ta đi vào phần tính toán chính của thuật toán và do đó phải dừng bộ đếm thời gian nếu nó vẫn đang chạy. Điều này có thể xảy ra nếu sự kiện tick tiếp theo đến nhanh hơn 1 giây, và iMA
M1 đã hoàn tất tính toán. Sẽ hợp lý nếu chỉ gọi hàm tương ứng EventKillTimer. Tuy nhiên, có một sắc thái trong hành vi của nó: nó không xóa hàng đợi sự kiện cho chương trình MQL loại chỉ báo, và nếu một sự kiện bộ đếm thời gian đã được đặt trong hàng đợi, thì trình xử lý OnTimer
sẽ được gọi một lần. Để tránh cập nhật biểu đồ không cần thiết, chúng ta kiểm soát quá trình bằng biến riêng của mình Pending Refresh
, và ở đây chúng ta gán nó thành false
.
...
PendingRefresh = false; // dữ liệu đã sẵn sàng, bộ đếm thời gian sẽ không hoạt động
...
2
3
Đây là cách tất cả được tổ chức trong trình xử lý OnTimer
:
void OnTimer()
{
EventKillTimer();
if(PendingRefresh)
{
ChartSetSymbolPeriod(0, _Symbol, _Period);
}
}
2
3
4
5
6
7
8
Hãy quay lại OnCalculate
và trình bày quy trình làm việc chính.
for(int i = fmax(prev_calculated - 1, (int)(rates_total - BarLimit));
i < rates_total; ++i)
{
static double result[1];
// lấy thanh M1 cuối cùng tương ứng với thanh thứ i của khung thời gian hiện tại
const datetime dt = time[i] + PeriodSeconds() - 60;
const int bar = iBarShift(_Symbol, PERIOD_M1, dt);
if(bar > -1)
{
// yêu cầu giá trị MA trên M1
if(CopyBuffer(Handle, 0, bar, 1, result) == 1)
{
Buffer[i] = result[0];
}
else
{
Print("CopyBuffer failed: ", _LastError);
return prev_calculated;
}
}
}
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
Hoạt động của chỉ báo được minh họa bằng hình ảnh sau trên EURUSD,H1. Đường màu xanh tương ứng với cài đặt mặc định. Mỗi giá trị được lấy bằng cách trung bình PRICE_CLOSE
trên 60 thanh M1. Đường màu cam bổ sung thêm việc làm mượt bằng 5 thanh H1, với giá PRICE_TYPICAL
của M1.
Hai phiên bản của chỉ báo UseM1MA trên EURUSD,H1
Cuốn sách trình bày phiên bản đơn giản hóa của UseM1MASimple.mq5
. Chúng ta đã bỏ qua các chi tiết về việc trung bình hóa thanh cuối cùng (chưa hoàn chỉnh), xử lý các thanh trống (không có dữ liệu trên M1) và cài đặt đúng thuộc tính PLOT_DRAW_BEGIN, cũng như kiểm soát sự xuất hiện của các độ trễ ngắn hạn trong việc tính toán trung bình khi xuất hiện các thanh mới. Phiên bản đầy đủ có sẵn trong tệp UseM1MA.mq5
.
Là ví dụ cuối cùng về việc xây dựng chỉ báo dựa trên các chỉ báo tiêu chuẩn, hãy phân tích việc cải tiến chỉ báo IndUnityPercent.mq5
, được trình bày trong phần Chỉ báo đa tiền tệ và đa khung thời gian. Phiên bản đầu tiên sử dụng giá Close
để tính toán, lấy chúng bằng CopyBuffer
. Trong phiên bản mới UseUnityPercentPro.mq5
, hãy thay thế phương pháp này bằng việc đọc dữ liệu chỉ báo iMA
. Điều này sẽ cho phép chúng ta triển khai các tính năng mới:
- Trung bình giá trong một khoảng thời gian nhất định
- Chọn phương pháp trung bình
- Chọn loại giá để tính toán
Những thay đổi trong mã nguồn là tối thiểu. Chúng ta thêm 3 tham số mới và một mảng toàn cục cho các handle của iMA
:
input ENUM_APPLIED_PRICE PriceType = PRICE_CLOSE;
input ENUM_MA_METHOD PriceMethod = MODE_EMA;
input int PricePeriod = 1;
...
int Handles[];
2
3
4
5
Trong hàm trợ giúp InitSymbols
, được gọi từ OnInit
để phân tích chuỗi với danh sách các ký hiệu làm việc, chúng ta thêm việc cấp phát bộ nhớ cho mảng mới (kích thước SymbolCount
của nó được xác định từ danh sách).
string InitSymbols()
{
SymbolCount = StringSplit(Instruments, ',', Symbols);
...
ArrayResize(Handles, SymbolCount);
ArrayInitialize(Handles, INVALID_HANDLE);
...
for(int i = 0; i < SymbolCount; i++)
{
...
Handles[i] = iMA(Symbols[i], PERIOD_CURRENT, PricePeriod, 0,
PriceMethod, PriceType);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Vào cuối cùng hàm này, chúng ta sẽ tạo các descriptor của các chỉ báo phụ cần thiết.
Trong hàm Calculate
, nơi thực hiện tính toán chính, chúng ta thay thế các lệnh gọi dạng:
CopyClose(Symbols[j], _Period, time0, time1, w);
bằng các lệnh gọi:
CopyBuffer(Handles[j], 0, time0, time1, w); // handle thứ j, bộ đệm thứ 0
Để rõ ràng, chúng ta cũng đã bổ sung tên ngắn của chỉ báo với ba tham số mới.
IndicatorSetString(INDICATOR_SHORTNAME,
StringFormat("Unity [%d] %s(%d,%s)", workCurrencies.getSize(),
StringSubstr(EnumToString(PriceMethod), 5), PricePeriod,
StringSubstr(EnumToString(PriceType), 6)));
2
3
4
Đây là kết quả thu được.
Chỉ báo đa ký hiệu UseUnityPercentPro với các cặp Forex chính
Hình ảnh hiển thị một rổ 8 loại tiền tệ Forex chính (cài đặt mặc định) được trung bình trên 11 thanh và tính toán dựa trên giá typical
. Hai đường dày tương ứng với giá trị tương đối của các loại tiền tệ của biểu đồ hiện tại: EUR được đánh dấu màu xanh lam và USD màu xanh lục.