Sửa đổi mức Stop Loss và/hoặc Take Profit của một vị thế
Một chương trình MQL có thể thay đổi mức giá bảo vệ Stop Loss
và Take Profit
cho một vị thế đang mở. Phần tử TRADE_ACTION_SLTP
trong liệt kê ENUM_TRADE_REQUEST_ACTIONS được thiết kế cho mục đích này, tức là khi điền cấu trúc MqlTradeRequest
, chúng ta nên ghi TRADE_ACTION_SLTP
vào trường action
.
Đây là trường bắt buộc duy nhất. Việc cần điền các trường khác được xác định bởi chế độ hoạt động của tài khoản ENUM_ACCOUNT_MARGIN_MODE. Trên tài khoản hedging, bạn nên điền trường symbol
, nhưng có thể bỏ qua vé vị thế. Ngược lại, trên tài khoản hedging, việc chỉ định vé vị thế position
là bắt buộc, nhưng bạn có thể bỏ qua biểu tượng. Điều này liên quan đến đặc thù của việc xác định vị thế trên các loại tài khoản khác nhau. Trong chế độ netting, chỉ một vị thế duy nhất có thể tồn tại cho mỗi biểu tượng.
Để thống nhất mã nguồn, nên điền cả hai trường nếu có thông tin.
Mức giá bảo vệ được đặt trong các trường sl
và tp
. Có thể chỉ thiết lập một trong hai trường. Để xóa các mức bảo vệ, gán giá trị bằng 0 cho chúng.
Bảng sau tóm tắt các yêu cầu điền các trường tùy thuộc vào chế độ tính toán. Các trường bắt buộc được đánh dấu bằng dấu sao (*), các trường tùy chọn được đánh dấu bằng dấu cộng (+).
Trường | Netting | Hedging |
---|---|---|
action | * | * |
symbol | * | + |
position | + | * |
sl | + | + |
tp | + | + |
Để thực hiện thao tác sửa đổi mức bảo vệ, chúng ta giới thiệu một số biến thể của phương thức adjust
trong cấu trúc MqlTradeRequestSync
.
struct MqlTradeRequestSync : public MqlTradeRequest
{
...
bool adjust(const ulong pos, const double stop = 0, const double take = 0);
bool adjust(const string name, const double stop = 0, const double take = 0);
bool adjust(const double stop = 0, const double take = 0);
...
};
2
3
4
5
6
7
8
Như đã thấy ở trên, tùy thuộc vào môi trường, việc sửa đổi có thể được thực hiện chỉ bằng vé hoặc chỉ bằng biểu tượng vị thế. Các tùy chọn này được tính đến trong hai nguyên mẫu đầu tiên.
Ngoài ra, vì cấu trúc có thể đã được sử dụng cho các yêu cầu trước đó, nó có thể đã điền các trường position
và symbols
. Khi đó, bạn có thể gọi phương thức với nguyên mẫu cuối cùng.
Chúng ta chưa hiển thị việc triển khai của ba phương thức này, vì rõ ràng nó phải có một phần thân chung với việc gửi yêu cầu. Phần này được đóng khung dưới dạng phương thức trợ giúp riêng adjust
với đầy đủ các tùy chọn. Dưới đây là mã của nó với một số rút gọn không ảnh hưởng đến logic hoạt động.
private:
bool _adjust(const ulong pos, const string name,
const double stop = 0, const double take = 0)
{
action = TRADE_ACTION_SLTP;
position = pos;
type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE);
if(!setSymbol(name)) return false;
if(!setSLTP(stop, take)) return false;
ZeroMemory(result);
return OrderSend(this, result);
}
2
3
4
5
6
7
8
9
10
11
12
Chúng ta điền tất cả các trường của cấu trúc theo các quy tắc trên, gọi các phương thức setSymbol
và setSLTP
đã mô tả trước đó, sau đó gửi yêu cầu đến máy chủ. Kết quả là trạng thái thành công (true
) hoặc lỗi (false
).
Mỗi phương thức adjust
được nạp chồng riêng biệt chuẩn bị các tham số nguồn cho yêu cầu. Đây là cách thực hiện khi có vé vị thế.
public:
bool adjust(const ulong pos, const double stop = 0, const double take = 0)
{
if(!PositionSelectByTicket(pos))
{
Print("No position: P=" + (string)pos);
return false;
}
return _adjust(pos, PositionGetString(POSITION_SYMBOL), stop, take);
}
2
3
4
5
6
7
8
9
10
Ở đây, sử dụng hàm tích hợp PositionSelectByTicket
, chúng ta kiểm tra sự hiện diện của một vị thế và chọn nó trong môi trường giao dịch của terminal, điều này cần thiết để đọc các thuộc tính tiếp theo, trong trường hợp này là biểu tượng (PositionGetString(POSITION_SYMBOL)
). Sau đó, biến thể phổ quát adjust
được gọi.
Khi sửa đổi một vị thế bằng tên biểu tượng (chỉ khả dụng trên tài khoản netting), bạn có thể sử dụng một tùy chọn khác của adjust
.
bool adjust(const string name, const double stop = 0, const double take = 0)
{
if(!PositionSelect(name))
{
Print("No position: " + s);
return false;
}
return _adjust(PositionGetInteger(POSITION_TICKET), name, stop, take);
}
2
3
4
5
6
7
8
9
Ở đây, việc chọn vị thế được thực hiện bằng hàm tích hợp PositionSelect
, và số vé được lấy từ các thuộc tính của nó (PositionGetInteger(POSITION_TICKET)
).
Tất cả các tính năng này sẽ được thảo luận chi tiết trong các phần tương ứng về làm việc với vị thế và thuộc tính vị thế.
Phiên bản phương thức adjust
với tập hợp tham số tối giản nhất, tức là chỉ với mức stop
và take
, như sau.
bool adjust(const double stop = 0, const double take = 0)
{
if(position != 0)
{
if(!PositionSelectByTicket(position))
{
Print("No position with ticket P=" + (string)position);
return false;
}
const string s = PositionGetString(POSITION_SYMBOL);
if(symbol != NULL && symbol != s)
{
Print("Position symbol is adjusted from " + symbol + " to " + s);
}
symbol = s;
}
else if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)
!= ACCOUNT_MARGIN_MODE_RETAIL_HEDGING
&& StringLen(symbol) > 0)
{
if(!PositionSelect(symbol))
{
Print("Can't select position for " + symbol);
return false;
}
position = PositionGetInteger(POSITION_TICKET);
}
else
{
Print("Neither position ticket nor symbol was provided");
return false;
}
return _adjust(position, symbol, stop, take);
}
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
Mã này đảm bảo rằng các trường position
và symbols
được điền chính xác trong các chế độ khác nhau hoặc thoát sớm với thông báo lỗi trong nhật ký. Cuối cùng, phiên bản riêng của _adjust
được gọi, gửi yêu cầu qua OrderSend
.
Tương tự như các phương thức buy/sell
, tập hợp các phương thức adjust
hoạt động "không đồng bộ": khi hoàn thành, chỉ trạng thái gửi yêu cầu được biết, nhưng không có xác nhận về việc sửa đổi các mức. Như chúng ta biết, đối với sàn giao dịch, mức Take Profit
có thể được chuyển tiếp dưới dạng lệnh giới hạn. Do đó, trong cấu trúc MqlTradeResultSync
, chúng ta nên cung cấp một chế độ chờ "đồng bộ" cho đến khi các thay đổi có hiệu lực.
Cơ chế chờ chung được hình thành dưới dạng phương thức MqlTradeResultSync::wait
đã sẵn sàng và đã được sử dụng để chờ mở vị thế. Phương thức wait
nhận tham số đầu tiên là con trỏ đến một phương thức khác với nguyên mẫu định trước condition
để thăm dò trong vòng lặp cho đến khi điều kiện cần thiết được đáp ứng hoặc xảy ra hết thời gian. Trong trường hợp này, phương thức tương thích với condition
nên thực hiện kiểm tra ứng dụng các mức dừng trong vị thế.
Hãy thêm một phương thức mới gọi là adjusted
.
struct MqlTradeResultSync : public MqlTradeResult
{
...
bool adjusted(const ulong msc = 1000)
{
if(retcode != TRADE_RETCODE_DONE || retcode != TRADE_RETCODE_PLACED)
{
return false;
}
if(!wait(checkSLTP, msc))
{
Print("SL/TP modification timeout: P=" + (string)position);
return false;
}
return true;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Trước tiên, chúng ta kiểm tra trạng thái trong trường retcode
. Nếu có trạng thái tiêu chuẩn, chúng ta tiếp tục kiểm tra chính các mức, truyền cho wait
một phương thức trợ giúp checkSLTP
.
struct MqlTradeResultSync : public MqlTradeResult
{
...
static bool checkSLTP(MqlTradeResultSync &ref)
{
if(PositionSelectByTicket(ref.position))
{
return TU::Equal(PositionGetDouble(POSITION_SL), /*.?.*/)
&& TU::Equal(PositionGetDouble(POSITION_TP), /*.?.*/);
}
else
{
Print("PositionSelectByTicket failed: P=" + (string)ref.position);
}
return false;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Mã này đảm bảo rằng vị thế được chọn bằng vé trong môi trường giao dịch của terminal sử dụng PositionSelectByTicket
và đọc các thuộc tính vị thế POSITION_SL
và POSITION_TP
, cần được so sánh với những gì có trong yêu cầu. Vấn đề là ở đây chúng ta không có quyền truy cập vào đối tượng yêu cầu và phải truyền một cặp giá trị cho các vị trí được đánh dấu bằng '.?.'.
Về cơ bản, vì chúng ta đang thiết kế cấu trúc MqlTradeResultSync
, chúng ta có thể thêm các trường sl
và tp
vào đó và điền chúng bằng các giá trị từ MqlTradeRequestSync
trước khi gửi yêu cầu (hạt nhân không "biết" về các trường chúng ta thêm vào và sẽ để chúng không thay đổi trong quá trình gọi OrderSend
). Nhưng để đơn giản, chúng ta sẽ sử dụng những gì đã có sẵn. Các trường bid
và ask
trong cấu trúc MqlTradeResultSync
chỉ được sử dụng để báo cáo giá requote (trạng thái TRADE_RETCODE_REQUOTE
), không liên quan đến yêu cầu TRADE_ACTION_SLTP
, nên chúng ta có thể lưu sl
và tp
từ MqlTradeRequestSync
đã hoàn thành vào đó.
Hợp lý là thực hiện điều này trong phương thức completed
của cấu trúc MqlTradeRequestSync
, phương thức bắt đầu một chế độ chờ chặn cho kết quả hoạt động giao dịch với thời gian chờ định trước. Cho đến nay, mã của nó chỉ có một nhánh cho hành động TRADE_ACTION_DEAL
. Để tiếp tục, hãy thêm một nhánh cho TRADE_ACTION_SLTP
.
struct MqlTradeRequestSync : public MqlTradeRequest
{
...
bool completed()
{
if(action == TRADE_ACTION_DEAL)
{
const bool success = result.opened(timeout);
if(success) position = result.position;
return success;
}
else if(action == TRADE_ACTION_SLTP)
{
// truyền dữ liệu yêu cầu ban đầu để so sánh với thuộc tính vị thế,
// mặc định chúng không có trong cấu trúc kết quả
result.position = position;
result.bid = sl; // trường bid trống trong loại kết quả này, sử dụng cho StopLoss
result.ask = tp; // trường ask trống trong loại kết quả này, sử dụng cho TakeProfit
return result.adjusted(timeout);
}
return false;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Như bạn thấy, sau khi đặt vé vị thế và mức giá từ yêu cầu, chúng ta gọi phương thức adjusted
đã thảo luận ở trên, kiểm tra wait(checkSLTP)
. Bây giờ chúng ta có thể quay lại phương thức trợ giúp checkSLTP
trong cấu trúc MqlTradeResultSync
và đưa nó vào dạng cuối cùng.
struct MqlTradeResultSync : public MqlTradeResult
{
...
static bool checkSLTP(MqlTradeResultSync &ref)
{
if(PositionSelectByTicket(ref.position))
{
return TU::Equal(PositionGetDouble(POSITION_SL), ref.bid) // sl từ yêu cầu
&& TU::Equal(PositionGetDouble(POSITION_TP), ref.ask); // tp từ yêu cầu
}
else
{
Print("PositionSelectByTicket failed: P=" + (string)ref.position);
}
return false;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Điều này hoàn tất việc mở rộng chức năng của các cấu trúc MqlTradeRequestSync
và MqlTradeResultSync
cho thao tác sửa đổi Stop Loss
và Take Profit
.
Với điều này, hãy tiếp tục với ví dụ về Expert Advisor MarketOrderSend.mq5
mà chúng ta đã bắt đầu trong phần trước. Hãy thêm vào đó một tham số đầu vào Distance2SLTP
, cho phép chỉ định khoảng cách tính bằng điểm đến các mức Stop Loss
và Take Profit
.
input int Distance2SLTP = 0; // Khoảng cách đến SL/TP tính bằng điểm (0 = không)
Khi nó bằng 0, không có mức bảo vệ nào sẽ được đặt.
Trong mã hoạt động, sau khi nhận được xác nhận mở vị thế, chúng ta tính toán giá trị của các mức trong các biến SL
và TP
và thực hiện sửa đổi đồng bộ: request.adjust(SL, TP) && request.completed()
.
...
const ulong order = (wantToBuy ?
request.buy(symbol, volume, Price) :
request.sell(symbol, volume, Price));
if(order != 0)
{
Print("OK Order: #=", order);
if(request.completed()) // chờ mở vị thế
{
Print("OK Position: P=", request.result.position);
if(Distance2SLTP != 0)
{
// vị thế "được chọn" trong môi trường giao dịch của terminal bên trong 'complete',
// nên không cần làm điều này rõ ràng trên vé
// PositionSelectByTicket(request.result.position);
// với vị thế đã chọn, có thể tìm hiểu thuộc tính của nó, nhưng chúng ta cần giá,
// để lùi lại từ đó một số điểm đã cho
const double price = PositionGetDouble(POSITION_PRICE_OPEN);
const double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
// tính toán các mức bằng lớp trợ giúp TradeDirection
TU::TradeDirection dir((ENUM_ORDER_TYPE)Type);
// SL luôn "tệ hơn" và TP luôn "tốt hơn" giá: mã giống nhau cho mua và bán
const double SL = dir.negative(price, Distance2SLTP * point);
const double TP = dir.positive(price, Distance2SLTP * point);
if(request.adjust(SL, TP) && request.completed())
{
Print("OK Adjust");
}
}
}
}
Print(TU::StringOf(request));
Print(TU::StringOf(request.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
Trong lần gọi đầu tiên của completed
sau khi thực hiện thành công thao tác mua hoặc bán, vé vị thế được lưu trong trường position
của cấu trúc yêu cầu. Do đó, để sửa đổi các mức dừng, chỉ cần mức giá là đủ, và biểu tượng cùng vé của vị thế đã có trong request
.
Hãy thử thực hiện một thao tác mua bằng Expert Advisor với cài đặt mặc định nhưng với Distance2SLTP
được đặt ở mức 500 điểm.
OK Order: #=1273913958
Waiting for position for deal D=1256506526
OK Position: P=1273913958
OK Adjust
TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10889, »
» SL=1.10389, TP=1.11389, P=1273913958
DONE, Bid=1.10389, Ask=1.11389, Request executed, Req=26
2
3
4
5
6
7
Hai dòng cuối cùng tương ứng với đầu ra gỡ lỗi vào nhật ký của nội dung các cấu trúc request
và request.result
, được khởi tạo ở cuối hàm. Trong các dòng này, điều thú vị là các trường lưu trữ sự kết hợp của các giá trị từ hai yêu cầu: đầu tiên, một vị thế được mở, sau đó nó được sửa đổi. Đặc biệt, các trường với khối lượng (0.01) và giá (1.10889) trong yêu cầu vẫn còn sau TRADE_ACTION_DEAL
, nhưng không ngăn cản việc thực hiện TRADE_ACTION_SLTP
. Về lý thuyết, có thể dễ dàng loại bỏ điều này bằng cách đặt lại cấu trúc giữa hai yêu cầu, tuy nhiên, chúng ta chọn để nguyên như vậy, vì trong số các trường đã điền cũng có những trường hữu ích: trường position
nhận được vé mà chúng ta cần để yêu cầu sửa đổi. Nếu chúng ta đặt lại cấu trúc, thì sẽ cần giới thiệu một biến để lưu trữ tạm thời vé.
Trong các trường hợp chung, tất nhiên, nên tuân thủ chính sách khởi tạo dữ liệu nghiêm ngặt, nhưng việc biết cách sử dụng chúng trong các kịch bản cụ thể (như hai hoặc nhiều yêu cầu liên quan của một loại đã xác định trước) cho phép bạn tối ưu hóa mã của mình.
Ngoài ra, không nên ngạc nhiên rằng trong cấu trúc với kết quả, chúng ta thấy các mức sl
và tp
được yêu cầu trong các trường cho giá Bid
và Ask
: chúng được ghi vào đó bởi phương thức MqlTradeRequestSync::completed
nhằm mục đích so sánh với các thay đổi thực tế của vị thế. Khi thực hiện yêu cầu, hạt nhân hệ thống chỉ điền retcode
(DONE), comment
("Request executed"), và request_id
(26) trong cấu trúc result
.
Tiếp theo, chúng ta sẽ xem xét một ví dụ khác về sửa đổi mức thực hiện trailing stop.