Làm việc với mảng tick thực trong cấu trúc MqlTick
MetaTrader 5 cung cấp khả năng làm việc không chỉ với lịch sử báo giá (các thanh giá) mà còn với lịch sử các tick thực tế. Từ giao diện người dùng, tất cả dữ liệu lịch sử đều có sẵn trong hộp thoại Symbols
. Hộp thoại này có ba tab: Specification
, Bars
, và Ticks
. Khi một phần tử cụ thể được chọn trong danh sách dạng cây của các biểu tượng trên tab đầu tiên, việc chuyển sang tab Bars
và Ticks
cho phép yêu cầu báo giá dưới dạng thanh hoặc tick tương ứng.
Từ các chương trình MQL, lịch sử tick thực tế cũng có thể truy cập bằng cách sử dụng các hàm CopyTicks
và CopyTicksRange
.
Hàm int CopyTicks(const string symbol, MqlTick &ticks[], uint flags = COPY_TICKS_ALL, ulong from = 0, uint count = 0)
Hàm int CopyTicksRange(const string symbol, MqlTick &ticks[], uint flags = COPY_TICKS_ALL, ulong from = 0, ulong to = 0)
Cả hai hàm đều yêu cầu các tick cho công cụ được chỉ định bởi tham số symbol
vào mảng ticks
được truyền bằng tham chiếu. Cấu trúc MqlTick
chứa tất cả thông tin về một tick và được mô tả trong MQL5 như sau:
struct MqlTick
{
datetime time; // thời gian cập nhật giá này
double bid; // giá Bid hiện tại
double ask; // giá Ask hiện tại
double last; // giá giao dịch cuối cùng
ulong volume; // khối lượng cho giá Last
long time_msc; // thời gian cập nhật giá này tính bằng mili giây
uint flags; // cờ (cho biết trường nào trong cấu trúc đã thay đổi)
double volume_real; // khối lượng cho giá Last với độ chính xác cao hơn
};
2
3
4
5
6
7
8
9
10
11
Trường flags
được dùng để lưu trữ một mặt nạ bit của các dấu hiệu, cho biết các trường nào trong cấu trúc tick chứa giá trị đã thay đổi.
Hằng số | Giá trị | Mô tả |
---|---|---|
TICK_FLAG_BID | 2 | Giá Bid đã thay đổi |
TICK_FLAG_ASK | 4 | Giá Ask đã thay đổi |
TICK_FLAG_LAST | 8 | Giá Last đã thay đổi |
TICK_FLAG_VOLUME | 16 | Khối lượng đã thay đổi |
TICK_FLAG_BUY | 32 | Tick được tạo ra từ giao dịch mua |
TICK_FLAG_SELL | 64 | Tick được tạo ra từ giao dịch bán |
Điều này là cần thiết vì mỗi tick luôn điền đầy tất cả các trường, bất kể dữ liệu có thay đổi so với tick trước đó hay không. Điều này cho phép bạn luôn có trạng thái giá hiện tại bất kỳ lúc nào mà không cần tìm kiếm các giá trị trước đó trong lịch sử tick. Ví dụ, chỉ giá Bid
có thể thay đổi trong một tick, nhưng ngoài giá mới, các tham số khác cũng sẽ được ghi lại trong cấu trúc: giá Ask
, Last
, khối lượng trước đó, v.v.
Tuy nhiên, cần lưu ý rằng tùy thuộc vào loại công cụ, một số trường trong tick có thể luôn bằng 0 (và các bit tương ứng trong mặt nạ không bao giờ được đặt cho chúng). Đặc biệt, đối với các công cụ Forex, thông thường các trường last
, volume
, volume_real
vẫn để trống.
Mảng nhận ticks
có thể có kích thước cố định hoặc động. Các hàm sẽ sao chép không quá số tick tương ứng với kích thước của mảng cố định, bất kể số lượng tick thực tế trong khoảng thời gian được yêu cầu (được chỉ định bởi các tham số from/to
trong hàm CopyTicksRange
) hoặc tham số count
của hàm CopyTicks
. Trong mảng ticks
, các tick cũ nhất được đặt trước, và các tick mới nhất được đặt sau.
Trong tham số của cả hai hàm, các giá trị thời gian được chỉ định dưới dạng mili giây kể từ 01/01/1970 00:00:00. Trong hàm CopyTicks
, phạm vi tick được yêu cầu được đặt bởi thời gian bắt đầu from
và số lượng tick count
, trong khi trong CopyTicksRange
, nó được đặt bởi from
và to
(cả hai giá trị đều được bao gồm).
Nói cách khác, CopyTicksRange
được thiết kế để nhận tick trong một khoảng thời gian cụ thể, và số lượng của chúng không được biết trước. CopyTicks
đảm bảo không quá số lượng count
tick nhưng không cho phép xác định trước khoảng thời gian mà các tick này sẽ bao phủ.
Thứ tự thời gian của from
và to
trong CopyTicksRange
không quan trọng: hàm sẽ cung cấp các tick trong mọi trường hợp, bắt đầu từ giá trị nhỏ nhất của hai giá trị và kết thúc bằng giá trị lớn nhất.
Hàm CopyTicks
đánh giá tham số from
là biên trái với thời gian tối thiểu và đếm từ đó count
tick về tương lai. Tuy nhiên, có một ngoại lệ quan trọng: from = 0
(mặc định) được coi là thời điểm hiện tại, và các tick được đếm từ đó về quá khứ. Điều này cho phép luôn lấy được số lượng tick cuối cùng được chỉ định. Khi count = 0
(mặc định), hàm sao chép không quá 2000 tick.
Cả hai hàm trả về số lượng tick đã sao chép hoặc -1 trong trường hợp xảy ra lỗi. Đặc biệt, GetLastError
có thể trả về các mã lỗi sau:
ERR_HISTORY_TIMEOUT
— hết thời gian đồng bộ hóa tick, hàm trả về tất cả những gì nó có.ERR_HISTORY_SMALL_BUFFER
— bộ đệm tĩnh quá nhỏ, nên nó chỉ cung cấp những gì phù hợp với mảng.ERR_NOT_ENOUGH_MEMORY
— không thể cấp phát đủ bộ nhớ để lấy lịch sử tick từ khoảng thời gian được chỉ định vào mảng động.
Tham số flags
xác định loại tick được yêu cầu.
Hằng số | Giá trị | Mô tả |
---|---|---|
COPY_TICKS_INFO | 1 | Tick do thay đổi của Bid và/hoặc Ask (TICK_FLAG_BID , TICK_FLAG_ASK ) |
COPY_TICKS_TRADE | 2 | Tick với thay đổi của Last và Volume (TICK_FLAG_LAST , TICK_FLAG_VOLUME , TICK_FLAG_BUY , TICK_FLAG_SELL ) |
COPY_TICKS_ALL | 3 | Tất cả các tick |
Đối với bất kỳ loại yêu cầu nào, các trường còn lại của cấu trúc MqlTick
không khớp với cờ sẽ chứa các giá trị thực tế trước đó. Ví dụ, nếu chỉ yêu cầu tick thông tin (COPY_TICKS_INFO
), các trường còn lại vẫn sẽ được điền. Điều đó có nghĩa là nếu chỉ giá Bid
thay đổi, các giá trị cuối cùng đã biết sẽ được ghi vào các trường ask
và volume
. Để biết điều gì đã thay đổi trong tick, hãy phân tích trường flags
của nó (sẽ có giá trị TICK_FLAG_BID
, hoặc TICK_FLAG_ASK
, hoặc kết hợp của cả hai). Nếu một tick có giá trị Bid
và Ask
bằng 0, và cờ cho biết các giá này đã thay đổi (flags == TICK_FLAG_BID | TICK_FLAG_ASK
), thì điều này cho thấy sổ lệnh đã bị làm trống.
Tương tự, nếu yêu cầu tick giao dịch (COPY_TICKS_TRADE
), các giá trị giá cuối cùng đã biết sẽ được ghi lại trong các trường bid
và ask
của chúng. Trong trường hợp này, trường flags
có thể có sự kết hợp của TICK_FLAG_LAST
, TICK_FLAG_VOLUME
, TICK_FLAG_BUY
, TICK_FLAG_SELL
.
Khi yêu cầu COPY_TICKS_ALL
, tất cả các tick được trả về.
Việc gọi bất kỳ hàm nào trong số CopyTicks
/CopyTicksRange
sẽ kiểm tra sự đồng bộ hóa của cơ sở dữ liệu tick được lưu trữ trên ổ cứng cho biểu tượng đã cho. Nếu không đủ tick trong cơ sở dữ liệu cục bộ, các tick còn thiếu sẽ được tự động tải xuống từ máy chủ giao dịch. Trong trường hợp này, các tick sẽ được đồng bộ hóa dựa trên ngày cũ nhất từ các tham số truy vấn và cho đến thời điểm hiện tại. Sau đó, tất cả các tick đến cho biểu tượng này sẽ được đưa vào cơ sở dữ liệu tick và giữ nó ở trạng thái đồng bộ hóa cập nhật.
Dữ liệu tick lớn hơn nhiều so với báo giá phút. Khi yêu cầu lịch sử tick lần đầu tiên hoặc bắt đầu thử nghiệm bằng tick thực tế, việc tải chúng có thể mất nhiều thời gian. Lịch sử dữ liệu tick được lưu trữ trong các tệp ở định dạng nội bộ TKC trong thư mục
{terminal_dir}/bases/{server_name}/ticks/{symbol_name}
. Mỗi tệp chứa thông tin cho một tháng.
Trong các chỉ báo, các hàm trả về kết quả ngay lập tức, tức là chúng sao chép các tick có sẵn theo biểu tượng và bắt đầu quá trình đồng bộ hóa cơ sở dữ liệu tick ở chế độ nền nếu không đủ dữ liệu. Tất cả các chỉ báo trên một biểu tượng hoạt động trong một luồng chung, vì vậy chúng không có quyền chờ quá trình đồng bộ hóa hoàn tất. Sau khi đồng bộ hóa kết thúc, lần gọi hàm tiếp theo sẽ trả về tất cả các tick được yêu cầu.
Trong các Expert Advisor và script, các hàm có thể chờ tối đa 45 giây để có kết quả: không giống như chỉ báo, mỗi Expert Advisor và script chạy trong luồng riêng của nó và do đó có thể chờ đồng bộ hóa hoàn tất trong khoảng thời gian chờ. Nếu trong thời gian này các tick vẫn chưa được đồng bộ hóa đủ số lượng cần thiết, thì chỉ các tick có sẵn sẽ được trả về, và quá trình đồng bộ hóa sẽ tiếp tục ở chế độ nền.
Hãy nhớ rằng các tick thời gian thực được truyền đến biểu đồ dưới dạng sự kiện: các chỉ báo nhận thông báo về tick mới trong trình xử lý OnCalculate, trong khi Expert Advisor nhận chúng trong trình xử lý OnTick. Cần lưu ý rằng hệ thống không đảm bảo việc truyền tất cả các sự kiện. Nếu các tick mới đến terminal trong khi chương trình đang xử lý sự kiện OnCalculate
/OnTick
hiện tại, các sự kiện mới cho chương trình "bận" này có thể không được thêm vào hàng đợi của nó (xem phần Tổng quan về các hàm xử lý sự kiện). Hơn nữa, nhiều tick có thể đến cùng một lúc, nhưng chỉ một sự kiện sẽ được tạo ra cho mỗi chương trình MQL: sự kiện trạng thái thị trường hiện tại. Trong trường hợp này, bạn có thể sử dụng hàm CopyTicks
để yêu cầu tất cả các tick đã đến kể từ lần xử lý sự kiện trước đó. Dưới đây là thuật toán này dưới dạng mã giả:
void processAllTicks()
{
static ulong prev = 0;
if(!prev)
{
MqlTick ticks[];
const int n = CopyTicks(_Symbol, ticks, COPY_TICKS_ALL, prev + 1, 1000000);
if(n > 0)
{
prev = ticks[n - 1].time_msc;
... // xử lý tất cả các tick bị bỏ lỡ
}
}
else
{
MqlTick tick;
SymbolInfoTick(_Symbol, tick);
prev = tick.time_msc;
... // xử lý tick đầu tiên
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Hàm SymbolInfoTick
được sử dụng ở đây điền dữ liệu tick cuối cùng vào một cấu trúc MqlTick
được truyền bằng tham chiếu. Chúng ta sẽ nghiên cứu nó trong một phần riêng.
Lưu ý rằng khi gọi CopyTicks
, một mili giây được cộng vào dấu thời gian cũ prev
. Điều này đảm bảo rằng tick trước đó không được xử lý lại. Tuy nhiên, nếu có nhiều tick trong cùng một mili giây tương ứng với prev
, thuật toán này sẽ bỏ qua chúng. Nếu bạn muốn bao quát tuyệt đối tất cả các tick, bạn nên ghi nhớ số lượng tick có sẵn với thời gian prev
trong khi cập nhật biến prev
. Trong lần gọi CopyTicks
tiếp theo, yêu cầu các tick từ thời điểm prev
và bỏ qua (bỏ qua trong mảng) số lượng tick "cũ".
Tuy nhiên, xin lưu ý rằng thuật toán trên không phải là bắt buộc đối với mọi chương trình MQL. Hầu hết chúng không phân tích từng tick, trong khi trạng thái giá hiện tại tương ứng với tick cuối cùng đã biết được truyền nhanh chóng đến biểu đồ trong mô hình sự kiện và có sẵn thông qua các thuộc tính biểu tượng và biểu đồ.
Để minh họa các hàm, hãy xem xét hai ví dụ, mỗi ví dụ cho một hàm. Đối với cả hai ví dụ, một tệp tiêu đề chung TickEnum.mqh
đã được phát triển, trong đó các hằng số cho cờ yêu cầu tick và cờ trạng thái tick được tóm tắt thành hai liệt kê.
enum COPY_TICKS
{
ALL_TICKS = /* -1 */ COPY_TICKS_ALL, // tất cả các tick
INFO_TICKS = /* 1 */ COPY_TICKS_INFO, // tick thông tin
TRADE_TICKS = /* 2 */ COPY_TICKS_TRADE, // tick giao dịch
};
enum TICK_FLAGS
{
TF_BID = /* 2 */ TICK_FLAG_BID,
TF_ASK = /* 4 */ TICK_FLAG_ASK,
TF_BID_ASK = TICK_FLAG_BID | TICK_FLAG_ASK,
TF_LAST = /* 8 */ TICK_FLAG_LAST,
TF_BID_LAST = TICK_FLAG_BID | TICK_FLAG_LAST,
TF_ASK_LAST = TICK_FLAG_ASK | TICK_FLAG_LAST,
TF_BID_ASK_LAST = TF_BID_ASK | TICK_FLAG_LAST,
TF_VOLUME = /* 16 */ TICK_FLAG_VOLUME,
TF_LAST_VOLUME = TICK_FLAG_LAST | TICK_FLAG_VOLUME,
TF_BID_VOLUME = TICK_FLAG_BID | TICK_FLAG_VOLUME,
TF_BID_ASK_VOLUME = TF_BID_ASK | TICK_FLAG_VOLUME,
TF_BID_ASK_LAST_VOLUME = TF_BID_ASK | TF_LAST_VOLUME,
TF_BUY = /* 32 */ TICK_FLAG_BUY,
// ... (bị cắt bớt trong tài liệu gố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
Ví dụ về tính toán delta khối lượng
Dưới đây là một phần của ví dụ thứ hai trong tài liệu gốc, nơi tính toán delta khối lượng trên mỗi thanh giá được thực hiện bằng cách sử dụng CopyTicksRange
.
Các tham số đầu vào chứa các cài đặt tương tự như trong script đầu tiên, đặc biệt là tên biểu tượng để phân tích và chế độ yêu cầu tick. Tuy nhiên, trong trường hợp này, bạn sẽ cần chỉ định thêm một khung thời gian, vì delta khối lượng cần được tính toán theo từng thanh. Khung thời gian biểu đồ hiện tại sẽ được sử dụng mặc định. Tham số BarCount
được sử dụng để chỉ định số lượng thanh được tính toán.
input string WorkSymbol = NULL; // Biểu tượng (để trống cho biểu tượng hiện tại)
input ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT;
input int BarCount = 100;
input COPY_TICKS TickType = INFO_TICKS;
2
3
4
Thống kê cho mỗi thanh được lưu trữ trong cấu trúc DeltaVolumePerBar
.
struct DeltaVolumePerBar
{
datetime time; // thời gian của thanh
ulong buy; // khối lượng ròng của các hoạt động mua
ulong sell; // khối lượng ròng của các hoạt động bán
long delta; // chênh lệch khối lượng
};
2
3
4
5
6
7
Hàm OnStart
mô tả một mảng của các cấu trúc này, trong khi kích thước của nó được phân bổ cho số lượng thanh được chỉ định.
void OnStart()
{
DeltaVolumePerBar deltas[];
ArrayResize(deltas, BarCount);
ZeroMemory(deltas);
...
}
2
3
4
5
6
7
Dưới đây là thuật toán chính:
for(int i = 0; i < BarCount; ++i)
{
MqlTick ticks[];
const datetime next = iTime(WorkSymbol, TimeFrame, i);
const datetime prev = iTime(WorkSymbol, TimeFrame, i + 1);
ResetLastError();
const int n = CopyTicksRange(WorkSymbol, ticks, COPY_TICKS_ALL,
prev * 1000, next * 1000 - 1);
if(n > -1 && _LastError == 0)
{
...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
Trong vòng lặp qua các thanh, chúng ta lấy phạm vi thời gian cho mỗi thanh: prev
và next
(thanh thứ 0 chưa hoàn thành không được xử lý). Khi gọi CopyTicksRange
cho khoảng thời gian này, hãy nhớ chuyển đổi datetime
thành mili giây và trừ đi 1 mili giây từ biên phải, vì thời gian này thuộc về thanh tiếp theo. Nếu không có lỗi, chúng ta xử lý mảng các tick nhận được trong một vòng lặp.
deltas[i].time = prev; // ghi nhớ thời gian của thanh
for(int j = 0; j < n; ++j)
{
// khi có khối lượng thực, lấy chúng từ các tick
if(TickType == TRADE_TICKS)
{
// tích lũy riêng khối lượng cho các giao dịch mua và bán
if((ticks[j].flags & TICK_FLAG_BUY) != 0)
{
deltas[i].buy += ticks[j].volume;
}
if((ticks[j].flags & TICK_FLAG_SELL) != 0)
{
deltas[i].sell += ticks[j].volume;
}
}
// khi không có khối lượng thực, chúng ta đánh giá chúng bằng cách giá di chuyển lên/xuống
else if(TickType == INFO_TICKS && j > 0)
{
if((ticks[j].flags & (TICK_FLAG_ASK | TICK_FLAG_BID)) != 0)
{
const long d = (long)(((ticks[j].ask + ticks[j].bid)
- (ticks[j - 1].ask + ticks[j - 1].bid)) / _Point);
if(d > 0) deltas[i].buy += d;
else deltas[i].sell += -d;
}
}
}
deltas[i].delta = (long)(deltas[i].buy - deltas[i].sell);
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
Nếu phân tích theo tick giao dịch (TRADE_TICKS
) được yêu cầu trong cài đặt script, hãy kiểm tra sự hiện diện của cờ TICK_FLAG_BUY
và TICK_FLAG_SELL
, và nếu ít nhất một trong số chúng được đặt, hãy tính đến khối lượng từ trường volume
trong biến tương ứng của cấu trúc DeltaVolumePerBar
. Chế độ này chỉ phù hợp với các công cụ chứng khoán. Đối với các công cụ Forex, khối lượng và cờ hướng giao dịch không được điền, do đó cần sử dụng một cách tiếp cận khác.
Nếu tick thông tin (INFO_TICKS
) khả dụng cho tất cả các công cụ được chỉ định trong cài đặt, thuật toán dựa trên các quy tắc thực nghiệm sau. Như đã biết, mua đẩy giá lên, và bán đẩy giá xuống. Do đó, chúng ta có thể giả định rằng nếu giá trung bình Ask+Bid
tăng lên trong một tick mới so với tick trước đó, một hoạt động mua đã được thực hiện trên đó, và nếu giá giảm xuống, đó là một hoạt động bán. Khối lượng có thể được ước lượng gần đúng bằng số điểm đã qua (_Point
).
Kết quả tính toán được hiển thị đơn giản dưới dạng một mảng của các cấu trúc với thống kê đã thu thập.
PrintFormat("Delta volumes per intraday bar\nProcessed %d bars on %s %s %s",
BarCount, StringLen(WorkSymbol) > 0 ? WorkSymbol : _Symbol,
EnumToString(TimeFrame == PERIOD_CURRENT ? _Period : TimeFrame),
EnumToString(TickType));
ArrayPrint(deltas);
2
3
4
5
Dưới đây là một số nhật ký cho các chế độ TRADE_TICKS
và INFO_TICKS
.
Delta volumes per intraday bar
Processed 100 bars on YNDX.MM PERIOD_H1 TRADE_TICKS
[time] [buy] [sell] [delta]
[ 0] 2021.10.13 11:00:00 7912 14169 -6257
[ 1] 2021.10.13 10:00:00 8470 11467 -2997
[ 2] 2021.10.13 09:00:00 10830 13047 -2217
...
2
3
4
5
6
7
Delta volumes per intraday bar
Processed 100 bars on YNDX.MM PERIOD_H1 INFO_TICKS
[time] [buy] [sell] [delta]
[ 0] 2021.10.13 11:00:00 1939 2548 -609
[ 1] 2021.10.13 10:00:00 2222 2400 -178
[ 2] 2021.10.13 09:00:00 2903 2909 -6
...
2
3
4
5
6
7
Khi chúng ta học cách tạo chỉ báo, chúng ta sẽ có thể nhúng thuật toán này vào một trong số chúng (xem IndDeltaVolume.mq5
trong phần Chờ dữ liệu và quản lý khả năng hiển thị) để hiển thị trực quan các delta trực tiếp trên biểu đồ.