Gửi yêu cầu giao dịch: OrderSend và OrderSendAsync
Để thực hiện các hoạt động giao dịch, API MQL5 cung cấp hai hàm: OrderSend
và OrderSendAsync
. Giống như OrderCheck
, chúng thực hiện kiểm tra chính thức các tham số yêu cầu được truyền dưới dạng cấu trúc MqlTradeRequest
và sau đó, nếu thành công, gửi yêu cầu đến máy chủ.
Sự khác biệt giữa hai hàm như sau. OrderSend
chờ lệnh được xếp hàng để xử lý trên máy chủ và nhận dữ liệu có ý nghĩa vào các trường của cấu trúc MqlTradeResult
được truyền làm tham số thứ hai của hàm. OrderSendAsync
ngay lập tức trả lại quyền điều khiển cho mã gọi bất kể máy chủ phản hồi như thế nào. Đồng thời, từ tất cả các trường của cấu trúc MqlTradeResult
, thông tin quan trọng chỉ được điền vào request_id
. Sử dụng định danh yêu cầu này, chương trình MQL có thể nhận thêm thông tin về tiến trình xử lý yêu cầu này trong sự kiện OnTradeTransaction
. Một cách tiếp cận thay thế là định kỳ phân tích danh sách các lệnh, giao dịch và vị thế. Điều này cũng có thể được thực hiện trong một vòng lặp, đặt một số thời gian chờ trong trường hợp có vấn đề liên lạc.
Điều quan trọng cần lưu ý là mặc dù có hậu tố "Async" trong tên hàm thứ hai, hàm đầu tiên không có hậu tố này cũng không hoàn toàn đồng bộ. Thực tế là kết quả xử lý lệnh bởi máy chủ, đặc biệt là việc thực hiện một giao dịch (hoặc có thể là nhiều giao dịch dựa trên một lệnh) và mở một vị thế, thường xảy ra không đồng bộ trong hệ thống giao dịch bên ngoài. Vì vậy, hàm OrderSend
cũng yêu cầu thu thập và phân tích hậu quả của việc thực hiện yêu cầu một cách trì hoãn, điều mà các chương trình MQL phải tự triển khai nếu cần thiết. Chúng ta sẽ xem xét một ví dụ về việc gửi yêu cầu thực sự đồng bộ và nhận tất cả kết quả của nó sau này (xem MqlTradeSync.mqh
).
bool OrderSend(const MqlTradeRequest &request, MqlTradeResult &result)
Hàm trả về true
trong trường hợp kiểm tra cơ bản thành công cấu trúc request
trong terminal và một vài kiểm tra bổ sung trên máy chủ. Tuy nhiên, điều này chỉ cho biết việc máy chủ chấp nhận lệnh và không đảm bảo việc thực hiện hoạt động giao dịch thành công.
Máy chủ giao dịch có thể điền giá trị trường deal
hoặc order
trong cấu trúc result
trả về nếu dữ liệu này được biết tại thời điểm máy chủ định dạng câu trả lời cho lệnh gọi OrderSend
. Tuy nhiên, trong trường hợp chung, các sự kiện thực hiện giao dịch hoặc đặt lệnh giới hạn tương ứng với một lệnh có thể xảy ra sau khi phản hồi được gửi đến chương trình MQL trong terminal. Do đó, đối với bất kỳ loại yêu cầu giao dịch nào, khi nhận kết quả thực hiện OrderSend
, cần kiểm tra mã trả về của máy chủ giao dịch retcode
và mã phản hồi của hệ thống giao dịch bên ngoài retcode_external
(nếu cần) có sẵn trong cấu trúc result
trả về. Dựa trên chúng, bạn nên quyết định liệu có nên chờ các hành động đang chờ xử lý trên máy chủ hay thực hiện hành động của riêng mình.
Mỗi lệnh được chấp nhận được lưu trữ trên máy chủ giao dịch chờ xử lý cho đến khi xảy ra bất kỳ sự kiện nào sau đây ảnh hưởng đến vòng đời của nó:
- Thực hiện khi xuất hiện yêu cầu đối ứng
- Kích hoạt khi giá thực hiện đến
- Ngày hết hạn
- Hủy bỏ bởi người dùng hoặc chương trình MQL
- Xóa bởi nhà môi giới (ví dụ, trong trường hợp thanh toán hoặc thiếu vốn,
Stop Out
)
Nguyên mẫu của OrderSendAsync
hoàn toàn lặp lại nguyên mẫu của OrderSend
.
bool OrderSendAsync(const MqlTradeRequest &request, MqlTradeResult &result)
Hàm này được thiết kế cho giao dịch tần suất cao, khi theo điều kiện của thuật toán, không thể chấp nhận được việc lãng phí thời gian chờ phản hồi từ máy chủ. Việc sử dụng OrderSendAsync
không tăng tốc độ xử lý yêu cầu bởi máy chủ hoặc gửi yêu cầu đến hệ thống giao dịch bên ngoài.
Chú ý! Trong trình kiểm tra, hàm
OrderSendAsync
hoạt động nhưOrderSend
. Điều này gây khó khăn cho việc gỡ lỗi xử lý đang chờ của các yêu cầu không đồng bộ.
Hàm trả về true
khi gửi yêu cầu thành công đến máy chủ MetaTrader 5. Tuy nhiên, điều này không có nghĩa là yêu cầu đã đến máy chủ và được chấp nhận để xử lý. Đồng thời, mã phản hồi trong cấu trúc result
nhận được chứa giá trị TRADE_RETCODE_PLACED (10008), tức là "lệnh đã được đặt".
Khi xử lý yêu cầu nhận được, máy chủ sẽ gửi thông báo phản hồi đến terminal về sự thay đổi trạng thái hiện tại của các vị thế, lệnh và giao dịch, dẫn đến việc tạo ra sự kiện OnTrade
trong chương trình MQL. Tại đó, chương trình có thể phân tích môi trường giao dịch mới và lịch sử tài khoản. Chúng ta sẽ xem xét các ví dụ liên quan dưới đây.
Ngoài ra, chi tiết về việc thực hiện yêu cầu giao dịch trên máy chủ có thể được theo dõi bằng trình xử lý OnTradeTransaction
. Đồng thời, cần xem xét rằng do kết quả của việc thực hiện một yêu cầu giao dịch, trình xử lý OnTradeTransaction
sẽ được gọi nhiều lần. Ví dụ, khi gửi yêu cầu mua trên thị trường, nó được chấp nhận để xử lý bởi máy chủ, một lệnh buy
tương ứng được tạo cho tài khoản, lệnh được thực hiện và giao dịch được thực hiện, kết quả là nó bị xóa khỏi danh sách các lệnh đang mở và được thêm vào lịch sử lệnh. Sau đó, giao dịch được thêm vào lịch sử và một vị thế mới được tạo. Đối với mỗi sự kiện này, hàm OnTradeTransaction
sẽ được gọi.
Hãy bắt đầu với một ví dụ Expert Advisor đơn giản CustomOrderSend.mq5
. Nó cho phép đặt tất cả các trường của yêu cầu trong các tham số đầu vào, tương tự như CustomOrderCheck.mq5
, nhưng khác ở chỗ nó gửi yêu cầu đến máy chủ thay vì chỉ kiểm tra đơn giản trong terminal. Chạy Expert Advisor trên tài khoản demo của bạn. Sau khi hoàn thành các thử nghiệm, đừng quên xóa Expert Advisor khỏi biểu đồ hoặc đóng biểu đồ để không gửi yêu cầu thử nghiệm mỗi lần khởi động terminal tiếp theo.
Ví dụ mới có một số cải tiến khác. Trước tiên, tham số đầu vào Async
được thêm vào.
input bool Async = false;
Tùy chọn này cho phép chọn hàm sẽ gửi yêu cầu đến máy chủ. Theo mặc định, tham số bằng false
và hàm OrderSend
được sử dụng. Nếu bạn đặt nó thành true
, OrderSendAsync
sẽ được gọi.
Ngoài ra, với ví dụ này, chúng ta sẽ bắt đầu mô tả và hoàn thiện một bộ hàm đặc biệt trong tệp tiêu đề TradeUtils.mqh
, sẽ hữu ích để đơn giản hóa việc lập mã cho các robot. Tất cả các hàm được đặt trong không gian tên TU (từ "Trade Utilities"), và trước tiên, chúng ta giới thiệu các hàm để xuất thuận tiện vào nhật ký cấu trúc MqlTradeRequest
và MqlTradeResult
.
namespace TU
{
string StringOf(const MqlTradeRequest &r)
{
SymbolMetrics p(r.symbol);
// khối chính: hành động, loại, biểu tượng
string text = EnumToString(r.action);
if(r.symbol != NULL) text += ", " + r.symbol;
text += ", " + EnumToString(r.type);
// khối khối lượng
if(r.volume != 0) text += ", V=" + p.StringOf(r.volume, p.lotDigits);
text += ", " + EnumToString(r.type_filling);
// khối tất cả các giá
if(r.price != 0) text += ", @ " + p.StringOf(r.price);
if(r.stoplimit != 0) text += ", X=" + p.StringOf(r.stoplimit);
if(r.sl != 0) text += ", SL=" + p.StringOf(r.sl);
if(r.tp != 0) text += ", TP=" + p.StringOf(r.tp);
if(r.deviation != 0) text += ", D=" + (string)r.deviation;
// khối hết hạn lệnh đang chờ
if(IsPendingType(r.type)) text += ", " + EnumToString(r.type_time);
if(r.expiration != 0) text += ", " + TimeToString(r.expiration);
// khối sửa đổi
if(r.order != 0) text += ", #=" + (string)r.order;
if(r.position != 0) text += ", #P=" + (string)r.position;
if(r.position_by != 0) text += ", #b=" + (string)r.position_by;
// dữ liệu phụ trợ
if(r.magic != 0) text += ", M=" + (string)r.magic;
if(StringLen(r.comment)) text += ", " + r.comment;
return text;
}
string StringOf(const MqlTradeResult &r)
{
string text = TRCSTR(r.retcode);
if(r.deal != 0) text += ", D=" + (string)r.deal;
if(r.order != 0) text += ", #=" + (string)r.order;
if(r.volume != 0) text += ", V=" + (string)r.volume;
if(r.price != 0) text += ", @ " + (string)r.price;
if(r.bid != 0) text += ", Bid=" + (string)r.bid;
if(r.ask != 0) text += ", Ask=" + (string)r.ask;
if(StringLen(r.comment)) text += ", " + r.comment;
if(r.request_id != 0) text += ", Req=" + (string)r.request_id;
if(r.retcode_external != 0) text += ", Ext=" + (string)r.retcode_external;
return text;
}
...
};
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
47
48
49
50
Mục đích của các hàm là cung cấp tất cả các trường quan trọng (không trống) ở dạng ngắn gọn nhưng thuận tiện: chúng được hiển thị trong một dòng với ký hiệu duy nhất cho mỗi trường.
Như bạn thấy, hàm sử dụng lớp SymbolMetrics
cho MqlTradeRequest
. Nó hỗ trợ chuẩn hóa nhiều giá hoặc khối lượng cho cùng một công cụ. Đừng quên rằng việc chuẩn hóa giá và khối lượng là điều kiện tiên quyết để chuẩn bị một yêu cầu giao dịch chính xác.
class SymbolMetrics
{
public:
const string symbol;
const int digits;
const int lotDigits;
SymbolMetrics(const string s): symbol(s),
digits((int)SymbolInfoInteger(s, SYMBOL_DIGITS)),
lotDigits((int)MathLog10(1.0 / SymbolInfoDouble(s, SYMBOL_VOLUME_STEP)))
{ }
double price(const double p)
{
return TU::NormalizePrice(p, symbol);
}
double volume(const double v)
{
return TU::NormalizeLot(v, symbol);
}
string StringOf(const double v, const int d = INT_MAX)
{
return DoubleToString(v, d == INT_MAX ? digits : d);
}
};
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
Việc chuẩn hóa trực tiếp các giá trị được giao phó cho các hàm phụ trợ NormalizePrice
và NormalizeLot
(sơ đồ của hàm sau giống với những gì chúng ta đã thấy trong tệp LotMarginExposure.mqh
).
double NormalizePrice(const double price, const string symbol = NULL)
{
const double tick = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
return MathRound(price / tick) * tick;
}
2
3
4
5
Nếu chúng ta kết nối TradeUtils.mqh
, ví dụ CustomOrderSend.mq5
có dạng như sau (các đoạn mã bị lược bỏ '...' vẫn không thay đổi từ CustomOrderCheck.mq5
).
void OnTimer()
{
...
MqlTradeRequest request = {};
MqlTradeCheckResult result = {};
TU::SymbolMetrics sm(symbol);
// điền vào cấu trúc yêu cầu
request.action = Action;
request.magic = Magic;
request.order = Order;
request.symbol = symbol;
request.volume = sm.volume(volume);
request.price = sm.price(price);
request.stoplimit = sm.price(StopLimit);
request.sl = sm.price(SL);
request.tp = sm.price(TP);
request.deviation = Deviation;
request.type = Type;
request.type_filling = Filling;
request.type_time = ExpirationType;
request.expiration = ExpirationTime;
request.comment = Comment;
request.position = Position;
request.position_by = PositionBy;
// gửi yêu cầu và hiển thị kết quả
ResetLastError();
if(Async)
{
PRTF(OrderSendAsync(request, result));
}
else
{
PRTF(OrderSend(request, result));
}
Print(TU::StringOf(request));
Print(TU::StringOf(result));
}
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
Do giá và khối lượng giờ đây được chuẩn hóa, bạn có thể thử nhập các giá trị không đồng đều vào các tham số đầu vào tương ứng. Chúng thường được lấy trong các chương trình trong quá trình tính toán, và mã của chúng ta chuyển đổi chúng theo đặc điểm kỹ thuật của biểu tượng.
Với cài đặt mặc định, Expert Advisor tạo một yêu cầu mua lô tối thiểu của công cụ hiện tại trên thị trường và thực hiện nó bằng hàm OrderSend
.
OrderSend(request,result)=true / ok
TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.12462
DONE, D=1250236209, #=1267684253, V=0.01, @ 1.12462, Bid=1.12456, Ask=1.12462, Request executed, Req=1
2
3
Thông thường, với giao dịch được phép, hoạt động này nên hoàn thành thành công (trạng thái DONE, bình luận "Request executed"). Trong cấu trúc result
, chúng ta ngay lập tức nhận được số giao dịch D
.
Nếu chúng ta mở cài đặt Expert Advisor và thay giá trị của tham số Async
bằng true
, chúng ta sẽ gửi một yêu cầu tương tự nhưng với hàm OrderSendAsync
.
OrderSendAsync(request,result)=true / ok
TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.12449
PLACED, Order placed, Req=2
2
3
Trong trường hợp này, trạng thái là PLACED, và số giao dịch tại thời điểm hàm trả về không được biết. Chúng ta chỉ biết ID yêu cầu duy nhất Req=2
. Để lấy số giao dịch và số vị thế, bạn cần chặn thông điệp TRADE_TRANSACTION_REQUEST với cùng ID yêu cầu trong trình xử lý OnTradeTransaction
, nơi cấu trúc đã điền sẽ được nhận dưới dạng tham số MqlTradeResult
.
Từ quan điểm của người dùng, cả hai yêu cầu nên nhanh như nhau.
Sẽ có thể so sánh hiệu suất của hai hàm này trực tiếp trong mã của chương trình MQL bằng một ví dụ Expert Advisor khác (xem phần về yêu cầu đồng bộ và không đồng bộ), điều mà chúng ta sẽ xem xét sau khi nghiên cứu mô hình của các sự kiện giao dịch.
Cần lưu ý rằng các sự kiện giao dịch được gửi đến trình xử lý
OnTradeTransaction
(nếu có trong mã), bất kể hàm nào được sử dụng để gửi yêu cầu,OrderSend
hayOrderSendAsync
. Tình hình như sau: trong trường hợp áp dụngOrderSend
, một số hoặc toàn bộ thông tin về việc thực hiện lệnh ngay lập tức có sẵn trong cấu trúcMqlTradeResult
nhận được. Tuy nhiên, trong trường hợp chung, kết quả được phân phối theo thời gian và khối lượng, ví dụ, khi một lệnh được "điền" thành nhiều giao dịch. Sau đó, thông tin đầy đủ có thể được lấy từ các sự kiện giao dịch hoặc bằng cách phân tích lịch sử giao dịch và lệnh.
Nếu bạn cố gắng gửi một yêu cầu rõ ràng không chính xác, ví dụ, thay đổi loại lệnh thành lệnh đang chờ ORDER_TYPE_BUY_STOP, bạn sẽ nhận được thông báo lỗi, vì đối với các lệnh như vậy bạn nên sử dụng hành động TRADE_ACTION_PENDING. Hơn nữa, chúng nên được đặt ở một khoảng cách từ giá hiện tại (chúng ta sử dụng giá thị trường theo mặc định). Trước thử nghiệm này, điều quan trọng là không quên thay đổi chế độ truy vấn trở lại đồng bộ (Async=false
) để ngay lập tức thấy lỗi trong cấu trúc MqlTradeResult
sau khi kết thúc lệnh gọi OrderSend
. Nếu không, OrderSendAsync
sẽ trả về true
, nhưng lệnh vẫn không được đặt, và chương trình chỉ có thể nhận thông tin về điều này trong OnTradeTransaction
mà chúng ta chưa có.
OrderSend(request,result)=false / TRADE_SEND_FAILED(4756)
TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, @ 1.12452, ORDER_TIME_GTC
REQUOTE, Bid=1.12449, Ask=1.12452, Requote, Req=5
2
3
Trong trường hợp này, lỗi báo cáo giá Requote không hợp lệ.
Các ví dụ về việc sử dụng các hàm để thực hiện các hành động giao dịch cụ thể sẽ được trình bày trong các phần tiếp theo.