Sự kiện OnTrade
Sự kiện OnTrade
xảy ra khi danh sách các lệnh đã đặt và vị thế mở, lịch sử lệnh, và lịch sử giao dịch thay đổi. Bất kỳ hành động giao dịch nào (đặt/kích hoạt/xóa một lệnh chờ, mở/đóng một vị thế, đặt mức bảo vệ, v.v.) đều thay đổi lịch sử lệnh và giao dịch và/hoặc danh sách vị thế và lệnh hiện tại tương ứng. Người khởi tạo hành động có thể là người dùng, chương trình hoặc máy chủ.
Để nhận sự kiện trong một chương trình, bạn cần mô tả trình xử lý tương ứng.
void OnTrade(void)
Trong trường hợp gửi yêu cầu giao dịch bằng OrderSend/OrderSendAsync
, một yêu cầu sẽ kích hoạt nhiều sự kiện OnTrade
vì quá trình xử lý thường diễn ra qua nhiều giai đoạn và mỗi thao tác có thể thay đổi trạng thái của lệnh, vị thế và lịch sử giao dịch.
Nói chung, không có tỷ lệ chính xác về số lượng gọi OnTrade
và OnTradeTransaction
. OnTrade
được gọi sau các cuộc gọi tương ứng của OnTradeTransaction
.
Do sự kiện OnTrade
mang tính chất tổng quát và không chỉ rõ bản chất của thao tác, nó ít phổ biến hơn với các nhà phát triển chương trình MQL. Thông thường, cần kiểm tra tất cả các khía cạnh của trạng thái tài khoản giao dịch trong mã và so sánh với một trạng thái đã lưu, tức là với bộ nhớ đệm áp dụng của các thực thể giao dịch được sử dụng trong chiến lược giao dịch. Trong trường hợp đơn giản nhất, bạn có thể, ví dụ, ghi nhớ vé của lệnh đã tạo trong trình xử lý OnTrade
để truy vấn tất cả các thuộc tính của nó. Tuy nhiên, điều này có thể ngụ ý việc phân tích "không cần thiết" một số lượng lớn các sự kiện ngẫu nhiên không liên quan đến một lệnh cụ thể.
Chúng ta sẽ nói về khả năng áp dụng bộ nhớ đệm của môi trường giao dịch và lịch sử trong phần về Expert Advisors đa tiền tệ.
Để tìm hiểu thêm về OnTrade
, hãy cùng xử lý một Expert Advisor triển khai chiến lược trên hai lệnh chờ OCO ("One Cancels Other"). Nó sẽ đặt một cặp lệnh dừng đột phá và đợi một trong số chúng được kích hoạt, sau đó lệnh còn lại sẽ bị xóa. Để rõ ràng, chúng ta sẽ hỗ trợ cả hai loại sự kiện giao dịch, OnTrade
và OnTradeTransaction
, để logic hoạt động sẽ chạy từ một trong hai trình xử lý, tùy thuộc vào lựa chọn của người dùng.
Mã nguồn có sẵn trong tệp OCO2.mq5
. Các tham số đầu vào của nó bao gồm kích thước lô Volume
(mặc định là 0, nghĩa là tối thiểu), khoảng cách Distance2SLTP
tính bằng điểm để đặt mỗi lệnh và cũng xác định các mức bảo vệ, ngày hết hạn Expiration
tính bằng giây từ thời điểm thiết lập, và bộ chuyển đổi sự kiện ActivationBy
(mặc định là OnTradeTransaction
). Vì Distance2SLTP
đặt cả khoảng cách từ giá hiện tại và khoảng cách đến mức dừng lỗ, các mức dừng lỗ của hai lệnh là giống nhau và bằng với giá tại thời điểm thiết lập.
enum EVENT_TYPE
{
ON_TRANSACTION, // OnTradeTransaction
ON_TRADE // OnTrade
};
input double Volume; // Volume (0 - minimal lot)
input uint Distance2SLTP = 500; // Distance Indent/SL/TP (points)
input ulong Magic = 1234567890;
input ulong Deviation = 10;
input ulong Expiration = 0; // Expiration (seconds in future, 3600 - 1 hour, etc)
input EVENT_TYPE ActivationBy = ON_TRANSACTION;
2
3
4
5
6
7
8
9
10
11
12
Để đơn giản hóa việc khởi tạo các cấu trúc yêu cầu, chúng ta sẽ mô tả cấu trúc MqlTradeRequestSyncOCO
của riêng mình, được dẫn xuất từ MqlTradeRequestSync
.
struct MqlTradeRequestSyncOCO: public MqlTradeRequestSync
{
MqlTradeRequestSyncOCO()
{
symbol = _Symbol;
magic = Magic;
deviation = Deviation;
if(Expiration > 0)
{
type_time = ORDER_TIME_SPECIFIED;
expiration = (datetime)(TimeCurrent() + Expiration);
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
Ở cấp độ toàn cục, hãy giới thiệu một số đối tượng và biến.
OrderFilter orders; // object for selecting orders
PositionFilter trades; // object for selecting positions
bool FirstTick = false; // or single processing of OnTick at start
ulong ExecutionCount = 0; // counter of trading strategy calls RunStrategy()
2
3
4
Tất cả logic giao dịch, ngoại trừ thời điểm bắt đầu, sẽ được kích hoạt bởi các sự kiện giao dịch. Trong trình xử lý OnInit
, chúng ta thiết lập các đối tượng lọc và đợi tick đầu tiên (đặt FirstTick
thành true
).
int OnInit()
{
FirstTick = true;
orders.let(ORDER_MAGIC, Magic).let(ORDER_SYMBOL, _Symbol)
.let(ORDER_TYPE, (1 << ORDER_TYPE_BUY_STOP) | (1 << ORDER_TYPE_SELL_STOP),
IS::OR_BITWISE);
trades.let(POSITION_MAGIC, Magic).let(POSITION_SYMBOL, _Symbol);
return INIT_SUCCEEDED;
}
2
3
4
5
6
7
8
9
10
11
Chúng ta chỉ quan tâm đến các lệnh dừng (mua/bán) và vị thế với số ma thuật cụ thể và biểu tượng hiện tại.
Trong hàm OnTick
, chúng ta gọi một lần phần chính của thuật toán được thiết kế dưới dạng RunStrategy
(sẽ được mô tả dưới đây). Sau đó, hàm này sẽ chỉ được gọi từ OnTrade
hoặc OnTradeTransaction
.
void OnTick()
{
if(FirstTick)
{
RunStrategy();
FirstTick = false;
}
}
2
3
4
5
6
7
8
Ví dụ, khi chế độ OnTrade
được kích hoạt, đoạn mã này hoạt động.
void OnTrade()
{
static ulong count = 0;
PrintFormat("OnTrade(%d)", ++count);
if(ActivationBy == ON_TRADE)
{
RunStrategy();
}
}
2
3
4
5
6
7
8
9
Lưu ý rằng các cuộc gọi trình xử lý OnTrade
được đếm bất kể chiến lược có được kích hoạt tại đây hay không. Tương tự, các sự kiện liên quan được đếm trong trình xử lý OnTradeTransaction
(ngay cả khi chúng xảy ra vô ích). Điều này được thực hiện để có thể thấy cả hai sự kiện và bộ đếm của chúng trong nhật ký cùng một lúc.
Khi chế độ OnTradeTransaction
được bật, rõ ràng, RunStrategy
bắt đầu từ đó.
void OnTradeTransaction(const MqlTradeTransaction &transaction,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
static ulong count = 0;
PrintFormat("OnTradeTransaction(%d)", ++count);
Print(TU::StringOf(transaction));
if(ActivationBy != ON_TRANSACTION) return;
if(transaction.type == TRADE_TRANSACTION_ORDER_DELETE)
{
// why not here? for answer, see the text
/* // this won't work online: m.isReady() == false because order temporarily lost
OrderMonitor m(transaction.order);
if(m.isReady() && m.get(ORDER_MAGIC) == Magic && m.get(ORDER_SYMBOL) == _Symbol)
{
RunStrategy();
}
*/
}
else if(transaction.type == TRADE_TRANSACTION_HISTORY_ADD)
{
OrderMonitor m(transaction.order);
if(m.isReady() && m.get(ORDER_MAGIC) == Magic && m.get(ORDER_SYMBOL) == _Symbol)
{
// the ORDER_STATE property does not matter - in any case, you need to remove the remaining
// if(transaction.order_state == ORDER_STATE_FILLED
// || transaction.order_state == ORDER_STATE_CANCELED ...)
RunStrategy();
}
}
}
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
Cần lưu ý rằng khi giao dịch trực tuyến, một lệnh chờ được kích hoạt có thể biến mất khỏi môi trường giao dịch trong một khoảng thời gian do được chuyển từ danh sách hiện tại sang lịch sử. Khi chúng ta nhận được sự kiện TRADE_TRANSACTION_ORDER_DELETE
, lệnh đã bị xóa khỏi danh sách hoạt động nhưng chưa xuất hiện trong lịch sử. Nó chỉ xuất hiện ở đó khi chúng ta nhận được sự kiện TRADE_TRANSACTION_HISTORY_ADD
. Hành vi này không được quan sát trong trình kiểm tra, tức là một lệnh bị xóa sẽ ngay lập tức được thêm vào lịch sử và có sẵn để chọn và đọc thuộc tính ngay trong giai đoạn TRADE_TRANSACTION_ORDER_DELETE
.
Trong cả hai trình xử lý sự kiện giao dịch, chúng ta đếm và ghi lại số lượng cuộc gọi. Đối với trường hợp với OnTrade
, nó phải khớp với ExecutionCount
mà chúng ta sẽ sớm thấy bên trong RunStrategy
. Nhưng đối với OnTradeTransaction
, bộ đếm và ExecutionCount
sẽ khác biệt đáng kể vì chiến lược ở đây được gọi rất chọn lọc, cho một loại sự kiện. Dựa trên điều này, chúng ta có thể kết luận rằng OnTradeTransaction
cho phép sử dụng tài nguyên hiệu quả hơn bằng cách chỉ gọi thuật toán khi phù hợp.
Bộ đếm ExecutionCount
được xuất ra nhật ký khi Expert Advisor được gỡ bỏ.
void OnDeinit(const int r)
{
Print("ExecutionCount = ", ExecutionCount);
}
2
3
4
Bây giờ, cuối cùng, hãy giới thiệu hàm RunStrategy
. Bộ đếm đã hứa được tăng lên ngay từ đầu.
void RunStrategy()
{
ExecutionCount++;
...
}
2
3
4
5
Tiếp theo, hai mảng được mô tả để nhận vé lệnh và trạng thái của chúng từ đối tượng lọc orders
.
ulong tickets[];
ulong states[];
2
Để bắt đầu, chúng ta sẽ yêu cầu các lệnh đáp ứng điều kiện của chúng ta. Nếu có hai lệnh, mọi thứ đều ổn, và không cần làm gì cả.
orders.select(ORDER_STATE, tickets, states);
const int n = ArraySize(tickets);
if(n == 2) return; // OK - standard state
...
2
3
4
Nếu còn lại một lệnh, thì lệnh kia đã được kích hoạt và lệnh còn lại phải bị xóa.
if(n > 0) // 1 or 2+ orders is an error, you need to delete everything
{
// delete all matching orders, except for partially filled ones
MqlTradeRequestSyncOCO r;
for(int i = 0; i < n; ++i)
{
if(states[i] != ORDER_STATE_PARTIAL)
{
r.remove(tickets[i]) && r.completed();
}
}
}
...
2
3
4
5
6
7
8
9
10
11
12
13
Nếu không, không có lệnh nào. Do đó, bạn cần kiểm tra xem có vị thế mở hay không: để làm điều này, chúng ta sử dụng một đối tượng lọc trades
khác nhưng kết quả được thêm vào cùng mảng nhận tickets
. Nếu không có vị thế, chúng ta đặt một cặp lệnh mới.
else // n == 0
{
// if there are no open positions, place 2 orders
if(!trades.select(tickets))
{
MqlTradeRequestSyncOCO r;
SymbolMonitor sm(_Symbol);
const double point = sm.get(SYMBOL_POINT);
const double lot = Volume == 0 ? sm.get(SYMBOL_VOLUME_MIN) : Volume;
const double buy = sm.get(SYMBOL_BID) + point * Distance2SLTP;
const double sell = sm.get(SYMBOL_BID) - point * Distance2SLTP;
r.buyStop(lot, buy, buy - Distance2SLTP * point,
buy + Distance2SLTP * point) && r.completed();
r.sellStop(lot, sell, sell + Distance2SLTP * point,
sell - Distance2SLTP * point) && r.completed();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Hãy chạy Expert Advisor trong trình kiểm tra với cài đặt mặc định, trên cặp EURUSD. Hình ảnh sau cho thấy quá trình kiểm tra.
Kết quả kiểm tra Expert Advisor với cặp lệnh dừng chờ dựa trên chiến lược OCO trong trình kiểm tra
Tại giai đoạn đặt một cặp lệnh, chúng ta sẽ thấy các mục sau trong nhật ký.
buy stop 0.01 EURUSD at 1.11151 sl: 1.10651 tp: 1.11651 (1.10646 / 1.10683)
sell stop 0.01 EURUSD at 1.10151 sl: 1.10651 tp: 1.09651 (1.10646 / 1.10683)
OnTradeTransaction(1)
TRADE_TRANSACTION_ORDER_ADD, #=2(ORDER_TYPE_BUY_STOP/ORDER_STATE_PLACED), ORDER_TIME_GTC, EURUSD, »
» @ 1.11151, SL=1.10651, TP=1.11651, V=0.01
OnTrade(1)
OnTradeTransaction(2)
TRADE_TRANSACTION_REQUEST
OnTradeTransaction(3)
TRADE_TRANSACTION_ORDER_ADD, #=3(ORDER_TYPE_SELL_STOP/ORDER_STATE_PLACED), ORDER_TIME_GTC, EURUSD, »
» @ 1.10151, SL=1.10651, TP=1.09651, V=0.01
OnTrade(2)
OnTradeTransaction(4)
TRADE_TRANSACTION_REQUEST
2
3
4
5
6
7
8
9
10
11
12
13
14
Ngay khi một trong các lệnh được kích hoạt, đây là những gì xảy ra:
order [#3 sell stop 0.01 EURUSD at 1.10151] triggered
deal #2 sell 0.01 EURUSD at 1.10150 done (based on order #3)
deal performed [#2 sell 0.01 EURUSD at 1.10150]
order performed sell 0.01 at 1.10150 [#3 sell stop 0.01 EURUSD at 1.10151]
OnTradeTransaction(5)
TRADE_TRANSACTION_DEAL_ADD, D=2(DEAL_TYPE_SELL), #=3(ORDER_TYPE_BUY/ORDER_STATE_STARTED), »
» EURUSD, @ 1.10150, SL=1.10651, TP=1.09651, V=0.01, P=3
OnTrade(3)
OnTradeTransaction(6)
TRADE_TRANSACTION_ORDER_DELETE, #=3(ORDER_TYPE_SELL_STOP/ORDER_STATE_FILLED), ORDER_TIME_GTC, »
» EURUSD, @ 1.10151, SL=1.10651, TP=1.09651, V=0.01, P=3
OnTrade(4)
OnTradeTransaction(7)
TRADE_TRANSACTION_HISTORY_ADD, #=3(ORDER_TYPE_SELL_STOP/ORDER_STATE_FILLED), ORDER_TIME_GTC, »
» EURUSD, @ 1.10151, SL=1.10651, TP=1.09651, P=3
order canceled [#2 buy stop 0.01 EURUSD at 1.11151]
OnTrade(5)
OnTradeTransaction(8)
TRADE_TRANSACTION_ORDER_DELETE, #=2(ORDER_TYPE_BUY_STOP/ORDER_STATE_CANCELED), ORDER_TIME_GTC, »
» EURUSD, @ 1.11151, SL=1.10651, TP=1.11651, V=0.01
OnTrade(6)
OnTradeTransaction(9)
TRADE_TRANSACTION_HISTORY_ADD, #=2(ORDER_TYPE_BUY_STOP/ORDER_STATE_CANCELED), ORDER_TIME_GTC, »
» EURUSD, @ 1.11151, SL=1.10651, TP=1.11651, V=0.01
OnTrade(7)
OnTradeTransaction(10)
TRADE_TRANSACTION_REQUEST
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
Lệnh #3 đã tự bị xóa, và lệnh #2 đã bị xóa (hủy) bởi Expert Advisor của chúng ta.
Nếu chúng ta chạy Expert Advisor chỉ với chế độ hoạt động thông qua sự kiện OnTrade
được thay đổi trong cài đặt, chúng ta sẽ nhận được kết quả tài chính hoàn toàn tương tự (ceteris paribus, tức là, ví dụ, nếu không bao gồm các độ trễ ngẫu nhiên trong việc tạo tick). Điều duy nhất khác biệt sẽ là số lượng cuộc gọi hàm RunStrategy
. Ví dụ, trong 4 tháng của năm 2022 trên EURUSD, H1 với 88 giao dịch, chúng ta sẽ nhận được các số liệu gần đúng sau về ExecutionCount
(điều quan trọng là tỷ lệ, không phải giá trị tuyệt đối liên quan đến tick của nhà môi giới của bạn):
OnTradeTransaction
— 132OnTrade
— 438
Đây là bằng chứng thực tế về khả năng xây dựng các thuật toán chọn lọc hơn dựa trên OnTradeTransaction
so với OnTrade
.
Phiên bản Expert Advisor OCO2.mq5
này phản ứng với các hành động với lệnh và vị thế khá đơn giản. Đặc biệt, ngay khi vị thế trước đó bị đóng bởi dừng lỗ hoặc chốt lời, nó sẽ đặt hai lệnh mới. Nếu bạn xóa một trong các lệnh thủ công, Expert Advisor sẽ ngay lập tức xóa lệnh thứ hai và sau đó tạo lại một cặp mới với độ lệch từ giá hiện tại. Bạn có thể cải thiện hành vi bằng cách nhúng một lịch trình tương tự như những gì được thực hiện trong Expert Advisor lưới và không phản ứng với các lệnh bị hủy trong lịch sử (mặc dù, xin lưu ý rằng MQL5 không cung cấp phương tiện để tìm hiểu liệu một lệnh được hủy thủ công hay theo chương trình). Chúng ta sẽ trình bày một hướng cải tiến khác cho Expert Advisor này khi khám phá API lịch kinh tế.
Ngoài ra, một chế độ thú vị đã có sẵn trong phiên bản hiện tại, liên quan đến việc đặt ngày hết hạn cho các lệnh chờ trong biến đầu vào Expiration
. Nếu một cặp lệnh không được kích hoạt, thì ngay sau khi chúng hết hạn, một cặp mới sẽ được đặt tương ứng với giá hiện tại mới đã thay đổi. Như một bài tập độc lập, bạn có thể thử tối ưu hóa Expert Advisor trong trình kiểm tra bằng cách thay đổi Expiration
và Distance2SLTP
. Công việc lập trình với trình kiểm tra, bao gồm cả trong chế độ tối ưu hóa, sẽ được đề cập trong chương tiếp theo.
Dưới đây là một trong những tùy chọn cài đặt (Distance2SLTP=250
, Expiration=5000
) được tìm thấy trong khoảng thời gian 16 tháng từ đầu năm 2021 cho cặp EURUSD.
Kết quả chạy kiểm tra của Expert Advisor OCO2