Lịch trình của các phiên giao dịch và báo giá
Sau này, trong các chương tiếp theo, chúng ta sẽ thảo luận về các hàm API MQL5 cho phép tự động hóa các hoạt động giao dịch. Nhưng trước tiên, chúng ta nên nghiên cứu các tính năng kỹ thuật của nền tảng, những yếu tố quyết định sự thành công khi gọi các API này. Đặc biệt, một số hạn chế được áp đặt bởi đặc điểm kỹ thuật của các công cụ tài chính. Trong chương này, chúng ta sẽ dần dần xem xét phân tích lập trình đầy đủ của chúng, và chúng ta sẽ bắt đầu với một mục như các phiên.
Khi giao dịch các công cụ tài chính, cần lưu ý rằng nhiều thị trường quốc tế, như sàn giao dịch chứng khoán, có giờ mở cửa được xác định trước, và thông tin cùng giao dịch chỉ có sẵn trong những giờ này. Mặc dù terminal luôn kết nối với máy chủ của nhà môi giới, việc cố gắng thực hiện một giao dịch ngoài lịch làm việc sẽ thất bại. Về vấn đề này, đối với mỗi ký hiệu, terminal lưu trữ lịch trình các phiên, tức là các khoảng thời gian trong ngày khi một số hành động nhất định có thể được thực hiện.
Như bạn đã biết, có hai loại phiên chính: báo giá và giao dịch. Trong phiên báo giá, terminal nhận (có thể nhận) các báo giá hiện tại. Trong phiên giao dịch, được phép gửi lệnh giao dịch và thực hiện giao dịch. Trong ngày, có thể có nhiều phiên của mỗi loại, với các khoảng nghỉ (ví dụ, sáng và tối). Rõ ràng, thời gian của các phiên báo giá lớn hơn hoặc bằng với các phiên giao dịch.
Dù sao đi nữa, thời gian phiên, tức là giờ mở và đóng cửa, được terminal chuyển đổi từ múi giờ địa phương của sàn giao dịch sang múi giờ của nhà môi giới (thời gian máy chủ).
API MQL5 cho phép bạn tìm hiểu các phiên báo giá và giao dịch của từng công cụ bằng các hàm SymbolInfoSessionQuote
và SymbolInfoSessionTrade
. Đặc biệt, thông tin quan trọng này cho phép chương trình kiểm tra xem thị trường hiện tại có mở hay không trước khi gửi yêu cầu giao dịch đến máy chủ. Do đó, chúng ta ngăn chặn kết quả lỗi không thể tránh khỏi và tránh tải không cần thiết lên máy chủ. Hãy nhớ rằng trong trường hợp có lượng lớn yêu cầu lỗi đến máy chủ do một chương trình MQL được triển khai không chính xác, máy chủ có thể bắt đầu "bỏ qua" terminal của bạn, từ chối thực hiện các lệnh tiếp theo (ngay cả những lệnh đúng) trong một khoảng thời gian.
bool SymbolInfoSessionQuote(const string symbol, ENUM_DAY_OF_WEEK dayOfWeek, uint sessionIndex, datetime &from, datetime &to)
bool SymbolInfoSessionTrade(const string symbol, ENUM_DAY_OF_WEEK dayOfWeek, uint sessionIndex, datetime &from, datetime &to)
Các hàm hoạt động theo cách tương tự. Đối với một symbol
đã cho và ngày trong tuần dayOfWeek
, chúng điền vào các tham số from
và to
được truyền bằng tham chiếu với thời gian mở và đóng của phiên có sessionIndex
. Chỉ số phiên bắt đầu từ 0. Cấu trúc ENUM_DAY_OF_WEEK đã được mô tả trong phần Các liệt kê.
Không có hàm riêng để truy vấn số lượng phiên: thay vào đó, chúng ta nên gọi SymbolInfoSessionQuote
và SymbolInfoSessionTrade
với chỉ số sessionIndex
tăng dần, cho đến khi hàm trả về cờ lỗi (false
). Khi một phiên với số được chỉ định tồn tại, và các đối số đầu ra from
và to
nhận được giá trị đúng, các hàm trả về chỉ báo thành công (true
).
Theo tài liệu MQL5, trong các giá trị nhận được của from
và to
thuộc kiểu datetime
, ngày nên được bỏ qua và chỉ xem xét thời gian. Điều này là do thông tin là lịch trình trong ngày. Tuy nhiên, có một ngoại lệ quan trọng đối với quy tắc này.
Vì thị trường có khả năng mở cửa 24 giờ một ngày, như trong trường hợp Forex, hoặc một sàn giao dịch ở phía bên kia thế giới, nơi giờ làm việc ban ngày trùng với sự thay đổi ngày trong "múi giờ" của nhà môi giới của bạn, thời gian kết thúc của các phiên có thể bằng hoặc lớn hơn 24 giờ. Ví dụ, nếu phiên Forex bắt đầu lúc 00:00, thì kết thúc là 24:00. Tuy nhiên, từ quan điểm của kiểu datetime
, 24 giờ là 00 giờ 00 phút của ngày hôm sau.
Tình hình trở nên phức tạp hơn đối với những sàn giao dịch mà lịch trình bị lệch so với múi giờ của nhà môi giới của bạn vài giờ theo cách mà phiên bắt đầu vào một ngày và kết thúc vào ngày khác. Do đó, biến to
không chỉ ghi nhận thời gian mà còn một ngày bổ sung không thể bỏ qua, vì nếu không, thời gian trong ngày from
sẽ lớn hơn thời gian trong ngày to
(ví dụ, một phiên có thể kéo dài từ 21:00 hôm nay đến 8:00 ngày mai, tức là 21 > 8). Trong trường hợp này, việc kiểm tra thời gian hiện tại nằm trong phiên ("thời gian x lớn hơn thời gian bắt đầu và nhỏ hơn thời gian kết thúc") sẽ không chính xác (ví dụ, điều kiện x >= 21 && x < 8 không được thỏa mãn cho x = 23, mặc dù phiên thực sự đang hoạt động).
Do đó, chúng ta kết luận rằng không thể bỏ qua ngày trong các tham số from/to
, và điểm này cần được xem xét trong các thuật toán (xem ví dụ).
Để thể hiện khả năng của các hàm, hãy quay lại ví dụ về script EnvPermissions.mq5
đã được trình bày trong phần Quyền. Một trong những loại quyền (hoặc hạn chế, nếu bạn muốn) liên quan cụ thể đến tính khả dụng của giao dịch. Trước đây, script đã tính đến cài đặt terminal (TERMINAL_TRADE_ALLOWED) và cài đặt của một chương trình MQL cụ thể (MQL_TRADE_ALLOWED). Bây giờ chúng ta có thể thêm vào đó các kiểm tra phiên để xác định các quyền giao dịch có hiệu lực tại một thời điểm nhất định cho một ký hiệu cụ thể.
Phiên bản mới của script được gọi là SymbolPermissions.mq5
. Nó cũng chưa phải là cuối cùng: trong một trong những chương sau, chúng ta sẽ nghiên cứu các hạn chế do cài đặt tài khoản giao dịch áp đặt.
Nhớ lại rằng script triển khai lớp Permissions
, cung cấp mô tả tập trung về tất cả các loại quyền/hạn chế áp dụng cho các chương trình MQL. Trong số những thứ khác, lớp có các phương thức để kiểm tra tính khả dụng của giao dịch: isTradeEnabled
và isTradeOnSymbolEnabled
. Phương thức đầu tiên liên quan đến các quyền toàn cục và sẽ hầu như không thay đổi:
class Permissions
{
public:
static bool isTradeEnabled(const string symbol = NULL, const datetime now = 0)
{
return TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)
&& MQLInfoInteger(MQL_TRADE_ALLOWED)
&& isTradeOnSymbolEnabled(symbol == NULL ? _Symbol : symbol, now);
}
...
2
3
4
5
6
7
8
9
10
Sau khi kiểm tra các thuộc tính của terminal và chương trình MQL, script chuyển sang isTradeOnSymbolEnabled
nơi đặc điểm kỹ thuật của ký hiệu được phân tích. Trước đây, phương thức này hầu như trống rỗng.
Ngoài ký hiệu đang hoạt động được truyền trong tham số symbol, hàm isTradeOnSymbolEnabled
nhận thời gian hiện tại (now
) và chế độ giao dịch yêu cầu (mode
). Chúng ta sẽ thảo luận chi tiết hơn về cái sau trong các phần sau (xem Quyền giao dịch). Hiện tại, chỉ cần lưu ý rằng giá trị mặc định của SYMBOL_TRADE_MODE_FULL cho phép tối đa (tất cả các hoạt động giao dịch đều được phép).
static bool isTradeOnSymbolEnabled(string symbol, const datetime now = 0,
const ENUM_SYMBOL_TRADE_MODE mode = SYMBOL_TRADE_MODE_FULL)
{
// kiểm tra phiên
bool found = now == 0;
if(!found)
{
const static ulong day = 60 * 60 * 24;
const ulong time = (ulong)now % day;
datetime from, to;
int i = 0;
ENUM_DAY_OF_WEEK d = TimeDayOfWeek(now);
while(!found && SymbolInfoSessionTrade(symbol, d, i++, from, to))
{
found = time >= (ulong)from && time < (ulong)to;
}
}
// kiểm tra chế độ giao dịch cho ký hiệu
return found && (SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE) == mode);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Nếu thời gian now
không được chỉ định (mặc định bằng 0), chúng ta coi rằng chúng ta không quan tâm đến các phiên. Điều này có nghĩa là biến found
với chỉ báo rằng một phiên phù hợp đã được tìm thấy (tức là một phiên chứa thời gian đã cho) ngay lập tức được đặt thành true
. Nhưng nếu tham số now
được chỉ định, hàm sẽ chuyển vào khối phân tích phiên giao dịch.
Để trích xuất thời gian mà không tính đến ngày từ các giá trị của kiểu datetime
, chúng ta mô tả hằng số day
bằng số giây trong một ngày. Một biểu thức như now % day
sẽ trả về phần dư của việc chia toàn bộ ngày và giờ cho thời gian của một ngày, điều này sẽ chỉ cho thời gian (các chữ số quan trọng nhất trong datetime
sẽ là null).
Hàm TimeDayOfWeek
trả về ngày trong tuần cho giá trị datetime
đã cho. Nó nằm trong tệp tiêu đề MQL5Book/DateTime.mqh
mà chúng ta đã sử dụng trước đây (xem Ngày và giờ).
Tiếp theo trong vòng lặp while
, chúng ta gọi hàm SymbolInfoSessionTrade
trong khi liên tục tăng chỉ số phiên i
cho đến khi tìm thấy một phiên phù hợp hoặc hàm trả về false
(không còn phiên nữa). Do đó, chương trình có thể nhận được danh sách đầy đủ các phiên theo ngày trong tuần, tương tự như những gì được hiển thị trong terminal trong cửa sổ Specifications
của ký hiệu.
Rõ ràng, một phiên phù hợp là phiên chứa giá trị time
được chỉ định giữa thời gian bắt đầu phiên from
và thời gian kết thúc to
. Chính tại đây chúng ta xem xét vấn đề liên quan đến giao dịch có thể kéo dài suốt ngày đêm: from
và to
được so sánh với time
"nguyên trạng", không bỏ qua ngày (from % day
hoặc to % day
).
Khi found
trở thành true
, chúng ta thoát khỏi vòng lặp. Nếu không, vòng lặp sẽ kết thúc khi vượt quá số phiên cho phép (hàm SymbolInfoSessionTrade
sẽ trả về false
) và không bao giờ tìm thấy phiên phù hợp.
Nếu theo lịch trình phiên, giao dịch hiện được phép, chúng ta kiểm tra thêm chế độ giao dịch cho ký hiệu (SYMBOL_TRADE_MODE). Ví dụ, giao dịch ký hiệu có thể bị cấm hoàn toàn ("chỉ báo") hoặc ở chế độ "chỉ đóng vị thế".
Mã trên có một số đơn giản hóa so với phiên bản cuối cùng trong tệp SymbolPermissions.mq5
. Nó bổ sung triển khai một cơ chế để đánh dấu nguồn hạn chế gây ra việc giao dịch bị vô hiệu hóa. Tất cả các nguồn này được tóm tắt trong liệt kê TRADE_RESTRICTIONS.
enum TRADE_RESTRICTIONS
{
TERMINAL_RESTRICTION = 1,
PROGRAM_RESTRICTION = 2,
SYMBOL_RESTRICTION = 4,
SESSION_RESTRICTION = 8,
};
2
3
4
5
6
7
Hiện tại, hạn chế có thể đến từ 4 nguồn: terminal, chương trình, ký hiệu và lịch trình phiên. Chúng ta sẽ thêm các tùy chọn khác sau này.
Để ghi nhận thực tế rằng một hạn chế đã được tìm thấy trong lớp Permissions
, chúng ta có biến lastFailReasonBitMask
cho phép thu thập một mặt nạ bit từ các phần tử của liệt kê bằng một phương thức phụ trợ pass
(bit được thiết lập khi điều kiện được kiểm tra value
là false, và bit bằng false
).
static uint lastFailReasonBitMask;
static bool pass(const bool value, const uint bitflag)
{
if(!value) lastFailReasonBitMask |= bitflag;
return value;
}
2
3
4
5
6
Việc gọi phương thức pass
với một cờ cụ thể được thực hiện tại các bước xác nhận phù hợp. Ví dụ, phương thức isTradeEnabled
đầy đủ trông như sau:
static bool isTradeEnabled(const string symbol = NULL, const datetime now = 0)
{
lastFailReasonBitMask = 0;
return pass(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED), TERMINAL_RESTRICTION)
&& pass(MQLInfoInteger(MQL_TRADE_ALLOWED), PROGRAM_RESTRICTION)
&& isTradeOnSymbolEnabled(symbol == NULL ? _Symbol : symbol, now);
}
2
3
4
5
6
7
Do đó, với kết quả tiêu cực của lệnh gọi TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)
hoặc MQLInfoInteger(MQL_TRADE_ALLOWED)
, cờ TERMINAL_RESTRICTION hoặc PROGRAM_RESTRICTION sẽ được thiết lập tương ứng.
Phương thức isTradeOnSymbolEnabled
cũng thiết lập các cờ riêng khi phát hiện vấn đề, bao gồm cả cờ phiên.
static bool isTradeOnSymbolEnabled(string symbol, const datetime now = 0,
const ENUM_SYMBOL_TRADE_MODE mode = SYMBOL_TRADE_MODE_FULL)
{
...
return pass(found, SESSION_RESTRICTION)
&& pass(SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE) == mode, SYMBOL_RESTRICTION);
}
2
3
4
5
6
7
Kết quả là, chương trình MQL sử dụng truy vấn Permissions::isTradeEnabled
, sau khi nhận được hạn chế, có thể làm rõ ý nghĩa của nó bằng các phương thức getFailReasonBitMask
và explainBitMask
: phương thức đầu tiên trả về mặt nạ của các cờ cấm được thiết lập "nguyên trạng", và phương thức thứ hai tạo ra mô tả văn bản thân thiện với người dùng về các hạn chế.
static uint getFailReasonBitMask()
{
return lastFailReasonBitMask;
}
static string explainBitMask()
{
string result = "";
for(int i = 0; i < 4; ++i)
{
if(((1 << i) & lastFailReasonBitMask) != 0)
{
result += EnumToString((TRADE_RESTRICTIONS)(1 << i));
}
}
return result;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Với lớp Permissions
trên trong trình xử lý OnStart
, một kiểm tra được thực hiện về tính khả dụng của giao dịch cho tất cả các ký hiệu từ Market Watch
(hiện tại, TimeCurrent
).
void OnStart()
{
string disabled = "";
const int n = SymbolsTotal(true);
for(int i = 0; i < n; ++i)
{
const string s = SymbolName(i, true);
if(!Permissions::isTradeEnabled(s, TimeCurrent()))
{
disabled += s + "=" + Permissions::explainBitMask() + "\n";
}
}
if(disabled != "")
{
Print("Trade is disabled for the following symbols and origins:");
Print(disabled);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Nếu giao dịch bị cấm đối với một ký hiệu nhất định, chúng ta sẽ thấy một giải thích trong nhật ký.
Trade is disabled for following symbols and origins:
USDRUB=SESSION_RESTRICTION
SP500m=SYMBOL_RESTRICTION
2
3
Trong trường hợp này, thị trường đóng cửa đối với "USDRUB" và giao dịch bị vô hiệu hóa đối với ký hiệu "SP500m" (nghiêm ngặt hơn, nó không tương ứng với chế độ SYMBOL_TRADE_MODE_FULL).
Giả định rằng khi chạy script, giao dịch thuật toán đã được kích hoạt toàn cục trong terminal. Nếu không, chúng ta sẽ thấy thêm các cấm TERMINAL_RESTRICTION và PROGRAM_RESTRICTION trong nhật ký.