Sử dụng dữ liệu Độ sâu Thị trường trong các thuật toán ứng dụng
Độ sâu Thị trường được coi là một công nghệ rất hữu ích để phát triển các hệ thống giao dịch tiên tiến. Đặc biệt, việc phân tích sự phân bố khối lượng Độ sâu Thị trường tại các mức giá gần với thị trường cho phép bạn tìm hiểu trước giá thực hiện trung bình của một lệnh với khối lượng cụ thể: chỉ cần cộng tổng các khối lượng của các mức (theo hướng ngược lại) để đảm bảo việc thực hiện đầy đủ. Trong một thị trường mỏng, với khối lượng không đủ, thuật toán có thể từ chối mở giao dịch để tránh trượt giá đáng kể.
Dựa trên dữ liệu Độ sâu Thị trường, các chiến lược khác cũng có thể được xây dựng. Ví dụ, việc biết các mức giá có khối lượng lớn có thể rất quan trọng.
MarketBookVolumeAlert.mq5
Trong chỉ báo thử nghiệm tiếp theo MarketBookVolumeAlert.mq5
, chúng ta thực hiện một thuật toán đơn giản để theo dõi khối lượng hoặc sự thay đổi của chúng vượt quá một giá trị đã cho.
#property indicator_chart_window
#property indicator_plots 0
input string WorkSymbol = ""; // WorkSymbol (nếu để trống, sử dụng ký hiệu biểu đồ hiện tại)
input bool CountVolumeInLots = false;
input double VolumeLimit = 0;
const string _WorkSymbol = StringLen(WorkSymbol) == 0 ? _Symbol : WorkSymbol;
2
3
4
5
6
7
8
Chỉ báo không có đồ họa. Ký hiệu được kiểm soát được nhập vào tham số WorkSymbol
(nếu để trống, ký hiệu hoạt động của biểu đồ được ngụ ý). Ngưỡng tối thiểu của các đối tượng được theo dõi, tức là độ nhạy của thuật toán, được chỉ định trong tham số VolumeLimit
. Tùy thuộc vào tham số CountVolumeInLots
, các khối lượng được phân tích và hiển thị cho người dùng dưới dạng lô (true
) hoặc đơn vị (false
). Điều này cũng ảnh hưởng đến cách giá trị VolumeLimit
nên được nhập. Việc chuyển đổi từ đơn vị sang phân số lô được cung cấp bởi macro VOL
: kích thước hợp đồng được sử dụng trong đó contract
được khởi tạo trong OnInit
(xem bên dưới).
#define VOL(V) (CountVolumeInLots ? V / contract : V)
Nếu phát hiện khối lượng lớn vượt quá ngưỡng, chương trình sẽ hiển thị thông báo về mức tương ứng trong bình luận. Để lưu lịch sử cảnh báo gần nhất, chúng ta sử dụng lớp bình luận nhiều dòng đã quen thuộc với chúng ta (Comments.mqh
).
#define N_LINES 25 // số dòng trong bộ đệm bình luận
#include <MQL5Book/Comments.mqh>
2
Trong trình xử lý OnInit
, chúng ta chuẩn bị các cài đặt cần thiết và đăng ký nhận sự kiện DOM.
double contract;
int digits;
void OnInit()
{
MarketBookAdd(_WorkSymbol);
contract = SymbolInfoDouble(_WorkSymbol, SYMBOL_TRADE_CONTRACT_SIZE);
digits = (int)MathRound(MathLog10(contract));
Print(SymbolInfoDouble(_WorkSymbol, SYMBOL_SESSION_BUY_ORDERS_VOLUME));
Print(SymbolInfoDouble(_WorkSymbol, SYMBOL_SESSION_SELL_ORDERS_VOLUME));
}
2
3
4
5
6
7
8
9
10
11
Các thuộc tính SYMBOL_SESSION_BUY_ORDERS_VOLUME
và SYMBOL_SESSION_SELL_ORDERS_VOLUME
, nếu được nhà môi giới của bạn điền cho ký hiệu đã chọn, sẽ giúp bạn xác định ngưỡng nào là hợp lý để chọn. Mặc định, VolumeLimit
là 0, đó là lý do tại sao tất cả các thay đổi trong sổ lệnh sẽ tạo ra cảnh báo. Để lọc bỏ các biến động không đáng kể, nên đặt VolumeLimit
thành một giá trị vượt quá kích thước trung bình của khối lượng ở tất cả các mức (xem trước trong sổ lệnh tích hợp hoặc trong chỉ báo MarketBookDisplay.mq5
).
Theo cách thông thường, chúng ta thực hiện việc hoàn tất.
void OnDeinit(const int)
{
MarketBookRelease(_WorkSymbol);
Comment("");
}
2
3
4
5
Công việc chính được thực hiện bởi trình xử lý OnBookEvent
. Nó mô tả một mảng tĩnh MqlBookInfo mbp
để lưu trữ phiên bản trước đó của sổ lệnh (kể từ lần gọi hàm cuối cùng).
void OnBookEvent(const string &symbol)
{
if(symbol != _WorkSymbol) return; // chỉ xử lý ký hiệu được yêu cầu
static MqlBookInfo mbp[]; // bảng/sổ trước đó
MqlBookInfo mbi[];
if(MarketBookGet(symbol, mbi)) // đọc sổ hiện tại
{
if(ArraySize(mbp) == 0) // lần đầu tiên chỉ lưu, vì không có gì để so sánh
{
ArrayCopy(mbp, mbi);
return;
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
Nếu có sổ lệnh cũ và mới, chúng ta so sánh khối lượng tại các mức của chúng với nhau trong các vòng lặp lồng nhau theo i
và j
. Hãy nhớ rằng việc tăng chỉ số có nghĩa là giá giảm.
int j = 0;
for(int i = 0; i < ArraySize(mbi); ++i)
{
bool found = false;
for( ; j < ArraySize(mbp); ++j)
{
if(MathAbs(mbp[j].price - mbi[i].price) < DBL_EPSILON * mbi[i].price)
{ // mbp[j].price == mbi[i].price
if(VOL(mbi[i].volume_real - mbp[j].volume_real) >= VolumeLimit)
{
NotifyVolumeChange("Enlarged", mbp[j].price,
VOL(mbp[j].volume_real), VOL(mbi[i].volume_real));
}
else
if(VOL(mbp[j].volume_real - mbi[i].volume_real) >= VolumeLimit)
{
NotifyVolumeChange("Reduced", mbp[j].price,
VOL(mbp[j].volume_real), VOL(mbi[i].volume_real));
}
found = true;
++j;
break;
}
else if(mbp[j].price > mbi[i].price)
{
if(VOL(mbp[j].volume_real) >= VolumeLimit)
{
NotifyVolumeChange("Removed", mbp[j].price,
VOL(mbp[j].volume_real), 0.0);
}
// tiếp tục vòng lặp tăng ++j đến các giá thấp hơn
}
else // mbp[j].price < mbi[i].price
{
break;
}
}
if(!found) // giá duy nhất (mới)
{
if(VOL(mbi[i].volume_real) >= VolumeLimit)
{
NotifyVolumeChange("Added", mbi[i].price, 0.0, VOL(mbi[i].volume_real));
}
}
}
...
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
45
46
Ở đây, trọng tâm không phải là loại mức, mà chỉ là giá trị khối lượng. Tuy nhiên, nếu bạn muốn, bạn có thể dễ dàng thêm ký hiệu mua hoặc bán vào thông báo, tùy thuộc vào trường type
của mức nơi xảy ra thay đổi quan trọng.
Cuối cùng, chúng ta lưu một bản sao mới của mbi
trong mảng tĩnh mbp
để so sánh với nó trong lần gọi hàm tiếp theo.
if(ArrayCopy(mbp, mbi) <= 0)
{
Print("ArrayCopy failed:", _LastError);
}
if(ArrayResize(mbp, ArraySize(mbi)) <= 0) // thu nhỏ nếu cần
{
Print("ArrayResize failed:", _LastError);
}
}
}
2
3
4
5
6
7
8
9
10
ArrayCopy
không tự động thu nhỏ mảng đích động nếu nó lớn hơn mảng nguồn, vì vậy chúng ta đặt kích thước chính xác của nó bằng ArrayResize
.
Hàm phụ trợ NotifyVolumeChange
chỉ đơn giản thêm thông tin về thay đổi được tìm thấy vào bình luận.
void NotifyVolumeChange(const string action, const double price,
const double previous, const double volume)
{
const string message = StringFormat("%s: %s %s -> %s",
action,
DoubleToString(price, (int)SymbolInfoInteger(_WorkSymbol, SYMBOL_DIGITS)),
DoubleToString(previous, digits),
DoubleToString(volume, digits));
ChronoComment(message);
}
2
3
4
5
6
7
8
9
10
Hình ảnh sau đây cho thấy kết quả của chỉ báo với cài đặt CountVolumeInLots=false
, VolumeLimit=20
.
Thông báo về thay đổi khối lượng trong sổ lệnh
MarketBookQuasiTicks.mq5
Như một ví dụ thứ hai về việc sử dụng sổ lệnh, hãy xem xét vấn đề thu thập tick đa tiền tệ. Chúng ta đã đề cập đến nó trong phần Tạo sự kiện tùy chỉnh, nơi chúng ta đã thấy một trong những giải pháp khả thi và chỉ báo EventTickSpy.mq5
. Bây giờ, sau khi làm quen với API Độ sâu Thị trường, chúng ta có thể thực hiện một phương án thay thế.
Hãy tạo một chỉ báo MarketBookQuasiTicks.mq5
, sẽ đăng ký nhận sổ lệnh của một danh sách các công cụ đã cho và tìm giá của ưu đãi tốt nhất và nhu cầu trong đó, tức là các cặp giá quanh chênh lệch, chính là giá Ask
và Bid
.
Tất nhiên, thông tin này không hoàn toàn tương đương với các tick tiêu chuẩn (hãy nhớ rằng luồng giao dịch/tick và sổ lệnh có thể đến từ các nhà cung cấp hoàn toàn khác nhau), nhưng nó cung cấp một cái nhìn đầy đủ và kịp thời về thị trường.
Các giá trị giá mới theo ký hiệu sẽ được hiển thị trong bình luận nhiều dòng.
Danh sách các ký hiệu hoạt động được chỉ định trong tham số đầu vào SymbolList
dưới dạng danh sách phân cách bằng dấu phẩy. Việc bật và tắt đăng ký nhận sự kiện Độ sâu Thị trường được thực hiện trong các trình xử lý OnInit
và OnDeinit
.
#define N_LINES 25 // số dòng trong bộ đệm bình luận
#include <MQL5Book/Comments.mqh>
input string SymbolList = "EURUSD,GBPUSD,XAUUSD,USDJPY"; // SymbolList (danh sách phân cách bằng dấu phẩy)
const string WorkSymbols = StringLen(SymbolList) == 0 ? _Symbol : SymbolList;
string symbols[];
void OnInit()
{
const int n = StringSplit(WorkSymbols, ',', symbols);
for(int i = 0; i < n; ++i)
{
if(!MarketBookAdd(symbols[i]))
{
PrintFormat("MarketBookAdd(%s) failed with code %d", symbols[i], _LastError);
}
}
}
void OnDeinit(const int)
{
for(int i = 0; i < ArraySize(symbols); ++i)
{
if(!MarketBookRelease(symbols[i]))
{
PrintFormat("MarketBookRelease(%s) failed with code %d", symbols[i], _LastError);
}
}
Comment("");
}
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
Việc phân tích mỗi sổ lệnh mới được thực hiện trong OnBookEvent
.
void OnBookEvent(const string &symbol)
{
MqlBookInfo mbi[];
if(MarketBookGet(symbol, mbi)) // lấy sổ lệnh hiện tại
{
int half = ArraySize(mbi) / 2; // ước tính giữa sổ lệnh
bool correct = true;
for(int i = 0; i < ArraySize(mbi); ++i)
{
if(i > 0)
{
if(mbi[i - 1].type == BOOK_TYPE_SELL
&& mbi[i].type == BOOK_TYPE_BUY)
{
half = i; // xác định giữa sổ lệnh
}
if(mbi[i - 1].price <= mbi[i].price)
{
correct = false;
}
}
}
if(correct) // lấy giá Bid/Ask tốt nhất từ sổ lệnh đúng
{
// mbi[half - 1].price // Ask
// mbi[half].price // Bid
OnSymbolTick(symbol, mbi[half].price);
}
}
}
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
Giá thị trường Ask/Bid
được tìm thấy sẽ được chuyển đến hàm trợ giúp OnSymbolTick
để hiển thị trong bình luận.
void OnSymbolTick(const string &symbol, const double price)
{
const string message = StringFormat("%s %s",
symbol, DoubleToString(price, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)));
ChronoComment(message);
}
2
3
4
5
6
Nếu bạn muốn, bạn có thể kiểm tra rằng các tick tổng hợp của chúng ta không khác nhiều so với các tick tiêu chuẩn.
Đây là cách thông tin về các quasi-tick đến hiển thị trên biểu đồ.
Quasi-tick đa ký hiệu dựa trên sự kiện sổ lệnh
Đồng thời, cần lưu ý một lần nữa rằng các sự kiện sổ lệnh chỉ có sẵn trên nền tảng trực tuyến, không có trong trình kiểm tra. Nếu hệ thống giao dịch được xây dựng hoàn toàn dựa trên quasi-tick từ sổ lệnh, việc kiểm tra nó sẽ yêu cầu sử dụng các giải pháp bên thứ ba để thu thập và phát lại sổ lệnh trong trình kiểm tra.