Các hàm để đọc thuộc tính vị thế
Một chương trình MQL có thể lấy các thuộc tính vị thế bằng cách sử dụng một số hàm PositionGet
tùy thuộc vào loại thuộc tính. Trong tất cả các hàm, thuộc tính cụ thể được yêu cầu được xác định trong tham số đầu tiên, tham số này nhận ID của một trong các liệt kê ENUM_POSITION_PROPERTY đã được thảo luận trong phần trước.
Đối với mỗi loại thuộc tính, có một dạng ngắn và dạng dài của hàm: dạng đầu tiên trả về giá trị của thuộc tính trực tiếp, còn dạng thứ hai ghi giá trị vào tham số thứ hai, được truyền bằng tham chiếu.
Các thuộc tính số nguyên và các thuộc tính của các loại tương thích (datetime
, liệt kê) có thể được lấy bằng hàm PositionGetInteger
.
long PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER property)
bool PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER property, long &value)
Nếu thất bại, hàm trả về 0 hoặc false
.
Hàm PositionGetDouble
được sử dụng để lấy các thuộc tính số thực.
double PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE property)
bool PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE property, double &value)
Cuối cùng, các thuộc tính chuỗi được trả về bởi hàm PositionGetString
.
string PositionGetString(ENUM_POSITION_PROPERTY_STRING property)
bool PositionGetString(ENUM_POSITION_PROPERTY_STRING property, string &value)
Trong trường hợp thất bại, dạng đầu tiên của hàm trả về một chuỗi rỗng.
Để đọc các thuộc tính vị thế, chúng ta đã có sẵn một giao diện trừu tượng MonitorInterface
(TradeBaseMonitor.mqh
) mà chúng ta đã sử dụng để viết một bộ giám sát lệnh. Bây giờ sẽ dễ dàng triển khai một bộ giám sát tương tự cho các vị thế. Kết quả được đính kèm trong tệp PositionMonitor.mqh
.
Lớp PositionMonitorInterface
được kế thừa từ MonitorInterface
với việc gán cho các kiểu mẫu I, D và S của các liệt kê ENUM_POSITION_PROPERTY đã xem xét, và ghi đè một vài phương thức stringify
dựa trên đặc thù của các thuộc tính vị thế.
class PositionMonitorInterface:
public MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER,
ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING>
{
public:
virtual string stringify(const long v,
const ENUM_POSITION_PROPERTY_INTEGER property) const override
{
switch(property)
{
case POSITION_TYPE:
return enumstr<ENUM_POSITION_TYPE>(v);
case POSITION_REASON:
return enumstr<ENUM_POSITION_REASON>(v);
case POSITION_TIME:
case POSITION_TIME_UPDATE:
return TimeToString(v, TIME_DATE | TIME_SECONDS);
case POSITION_TIME_MSC:
case POSITION_TIME_UPDATE_MSC:
return STR_TIME_MSC(v);
}
return (string)v;
}
virtual string stringify(const ENUM_POSITION_PROPERTY_DOUBLE property,
const string format = NULL) const override
{
if(format == NULL &&
(property == POSITION_PRICE_OPEN || property == POSITION_PRICE_CURRENT
|| property == POSITION_SL || property == POSITION_TP))
{
const int digits = (int)SymbolInfoInteger(PositionGetString(POSITION_SYMBOL),
SYMBOL_DIGITS);
return DoubleToString(PositionGetDouble(property), digits);
}
return MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER,
ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING>
::stringify(property, format);
}
}
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
Lớp giám sát cụ thể, sẵn sàng để xem các vị thế, nằm tiếp theo trong chuỗi kế thừa và dựa trên các hàm PositionGet
. Việc chọn một vị thế theo vé được thực hiện trong hàm tạo.
class PositionMonitor: public PositionMonitorInterface
{
public:
const ulong ticket;
PositionMonitor(const ulong t): ticket(t)
{
if(!PositionSelectByTicket(ticket))
{
PrintFormat("Error: PositionSelectByTicket(%lld) failed: %s",
ticket, E2S(_LastError));
}
else
{
ready = true;
}
}
virtual long get(const ENUM_POSITION_PROPERTY_INTEGER property) const override
{
return PositionGetInteger(property);
}
virtual double get(const ENUM_POSITION_PROPERTY_DOUBLE property) const override
{
return PositionGetDouble(property);
}
virtual string get(const ENUM_POSITION_PROPERTY_STRING property) const override
{
return PositionGetString(property);
}
...
};
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
Một kịch bản đơn giản sẽ cho phép bạn ghi lại tất cả các đặc điểm của vị thế đầu tiên (nếu có ít nhất một vị thế).
void OnStart()
{
PositionMonitor pm(PositionGetTicket(0));
pm.print();
}
2
3
4
5
Trong nhật ký, chúng ta sẽ nhận được kết quả như sau.
MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER, »
» ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING>
ENUM_POSITION_PROPERTY_INTEGER Count=9
0 POSITION_TIME=2022.03.24 23:09:45
1 POSITION_TYPE=POSITION_TYPE_BUY
2 POSITION_MAGIC=0
3 POSITION_IDENTIFIER=1291755067
4 POSITION_TIME_MSC=2022.03.24 23:09:45'261
5 POSITION_TIME_UPDATE=2022.03.24 23:09:45
6 POSITION_TIME_UPDATE_MSC=2022.03.24 23:09:45'261
7 POSITION_TICKET=1291755067
8 POSITION_REASON=POSITION_REASON_EXPERT
ENUM_POSITION_PROPERTY_DOUBLE Count=8
0 POSITION_VOLUME=0.01
1 POSITION_PRICE_OPEN=1.09977
2 POSITION_PRICE_CURRENT=1.09965
3 POSITION_SL=0.00000
4 POSITION_TP=1.10500
5 POSITION_COMMISSION=0.0
6 POSITION_SWAP=0.0
7 POSITION_PROFIT=-0.12
ENUM_POSITION_PROPERTY_STRING Count=3
0 POSITION_SYMBOL=EURUSD
1 POSITION_COMMENT=
2 POSITION_EXTERNAL_ID=
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Nếu hiện tại không có vị thế nào đang mở, chúng ta sẽ thấy thông báo lỗi.
Error: PositionSelectByTicket(0) failed: TRADE_POSITION_NOT_FOUND
Tuy nhiên, bộ giám sát không chỉ hữu ích và không quá quan trọng khi xuất các thuộc tính ra nhật ký. Dựa trên PositionMonitor
, chúng ta tạo một lớp để chọn các vị thế theo điều kiện, tương tự như những gì chúng ta đã làm cho các lệnh (OrderFilter). Mục tiêu cuối cùng là cải thiện Expert Advisor lưới của chúng ta.
Nhờ lập trình hướng đối tượng (OOP), việc tạo một lớp bộ lọc mới hầu như không tốn công sức. Dưới đây là mã nguồn hoàn chỉnh (tệp PositionFilter.mqh
).
class PositionFilter: public TradeFilter<PositionMonitor,
ENUM_POSITION_PROPERTY_INTEGER,
ENUM_POSITION_PROPERTY_DOUBLE,
ENUM_POSITION_PROPERTY_STRING>
{
protected:
virtual int total() const override
{
return PositionsTotal();
}
virtual ulong get(const int i) const override
{
return PositionGetTicket(i);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Bây giờ chúng ta có thể viết một kịch bản như vậy để nhận lợi nhuận cụ thể trên các vị thế với số ma thuật đã cho, chẳng hạn.
input ulong Magic;
void OnStart()
{
PositionFilter filter;
ENUM_POSITION_PROPERTY_DOUBLE properties[] =
{POSITION_PROFIT, POSITION_VOLUME};
double profits[][2];
ulong tickets[];
string symbols[];
filter.let(POSITION_MAGIC, Magic).select(properties, tickets, profits);
filter.select(POSITION_SYMBOL, tickets, symbols);
for(int i = 0; i < ArraySize(symbols); ++i)
{
PrintFormat("%s[%lld]=%f",
symbols[i], tickets[i], profits[i][0] / profits[i][1]);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Trong trường hợp này, chúng ta phải gọi phương thức select
hai lần, vì các loại thuộc tính mà chúng ta quan tâm là khác nhau: lợi nhuận thực và lô, nhưng tên chuỗi của công cụ. Trong một trong những phần ở đầu chương, khi chúng ta phát triển lớp bộ lọc cho các ký hiệu, chúng ta đã mô tả khái niệm tuples. Trong MQL5, chúng ta có thể triển khai nó dưới dạng mẫu cấu trúc với các trường có kiểu tùy ý. Những tuples như vậy sẽ rất hữu ích để hoàn thiện hệ thống phân cấp của các lớp bộ lọc vì sau đó có thể mô tả phương thức select
điền vào một mảng tuples với các trường thuộc bất kỳ kiểu nào.
Các tuples được mô tả trong tệp Tuples.mqh
. Tất cả các cấu trúc trong đó có tên TupleN<T1,...>
, trong đó N là số từ 2 đến 8, và nó tương ứng với số lượng tham số mẫu (các kiểu Ti). Ví dụ, Tuple2
:
template<typename T1,typename T2>
struct Tuple2
{
T1 _1;
T2 _2;
static int size() { return 2; };
// M — order, position, deal monitor class, any MonitorInterface<>
template<typename M>
void assign(const int &properties[], M &m)
{
if(ArraySize(properties) != size()) return;
_1 = m.get(properties[0], _1);
_2 = m.get(properties[1], _2);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Trong lớp TradeFilter
(TradeFilter.mqh
), hãy thêm một phiên bản của hàm select
với tuples.
template<typename T,typename I,typename D,typename S>
class TradeFilter
{
...
template<typename U> // type U must be Tuple<>, e.g. Tuple3<T1,T2,T3>
bool select(const int &property[], U &data[], const bool sort = false) const
{
const int q = ArraySize(property);
static const U u; // PRB: U::size() does not compile
if(q != u.size()) return false; // required condition
const int n = total();
// cycle through orders/positions/deals
for(int i = 0; i < n; ++i)
{
const ulong t = get(i);
// access to properties via monitor T
T m(t);
// check all filter conditions for different types of properties
if(match(m, longs)
&& match(m, doubles)
&& match(m, strings))
{
// for a suitable object, store the properties in an array of tuples
const int k = EXPAND(data);
data[k].assign(property, m);
}
}
if(sort)
{
sortTuple(data, u._1);
}
return true;
}
}
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
Một mảng tuples có thể được sắp xếp tùy chọn theo trường đầu tiên _1, vì vậy bạn có thể nghiên cứu thêm phương thức trợ giúp sortTuple
.
Với tuples, bạn có thể truy vấn một đối tượng bộ lọc cho các thuộc tính của ba kiểu khác nhau trong một lần gọi select
.
Dưới đây là các vị thế với một số Magic
được hiển thị, sắp xếp theo lợi nhuận; đối với mỗi vị thế, một ký hiệu và một vé được lấy thêm.
input ulong Magic;
void OnStart()
{
int props[] = {POSITION_PROFIT, POSITION_SYMBOL, POSITION_TICKET};
Tuple3<double,string,ulong> tuples[];
PositionFilter filter;
filter.let(POSITION_MAGIC, Magic).select(props, tuples, true);
ArrayPrint(tuples);
}
2
3
4
5
6
7
8
9
10
Tất nhiên, các kiểu tham số trong mô tả của mảng tuples (trong trường hợp này, Tuple3<double,string,ulong>
) phải khớp với các kiểu liệt kê thuộc tính được yêu cầu (POSITION_PROFIT, POSITION_SYMBOL, POSITION_TICKET).
Bây giờ chúng ta có thể đơn giản hóa một chút Expert Advisor lưới (có nghĩa là không chỉ ngắn hơn, mà còn dễ hiểu hơn). Phiên bản mới được gọi là PendingOrderGrid2.mq5
. Các thay đổi sẽ ảnh hưởng đến tất cả các hàm liên quan đến quản lý vị thế.
Hàm GetMyPositions
điền vào mảng tuples types4tickets
được truyền bằng tham chiếu. Trong mỗi tuple Tuple2
, nó được cho là lưu trữ kiểu và vé của vị thế. Trong trường hợp cụ thể này, chúng ta có thể chỉ cần sử dụng một mảng hai chiều ulong
thay vì tuples vì cả hai thuộc tính đều có cùng kiểu cơ bản. Tuy nhiên, chúng ta sử dụng tuples để thể hiện cách làm việc với chúng trong mã gọi.
#include <MQL5Book/Tuples.mqh>
#include <MQL5Book/PositionFilter.mqh>
int GetMyPositions(const string s, const ulong m,
Tuple2<ulong,ulong> &types4tickets[])
{
int props[] = {POSITION_TYPE, POSITION_TICKET};
PositionFilter filter;
filter.let(POSITION_SYMBOL, s).let(POSITION_MAGIC, m)
.select(props, types4tickets, true);
return ArraySize(types4tickets);
}
2
3
4
5
6
7
8
9
10
11
12
Lưu ý rằng tham số thứ ba cuối cùng của phương thức select
bằng true
, điều này chỉ thị sắp xếp mảng theo trường đầu tiên, tức là kiểu của các vị thế. Do đó, chúng ta sẽ có các vị thế mua ở đầu và bán ở cuối. Điều này sẽ cần thiết cho việc đóng đối ứng.
Sự tái sinh của phương thức CompactPositions
như sau.
uint CompactPositions(const bool cleanup = false)
{
uint retcode = 0;
Tuple2<ulong,ulong> types4tickets[];
int i = 0, j = 0;
int n = GetMyPositions(_Symbol, Magic, types4tickets);
if(n > 0)
{
Print("CompactPositions: ", n);
for(i = 0, j = n - 1; i < j; ++i, --j)
{
if(types4tickets[i]._1 != types4tickets[j]._1) // as long as the types are different
{
retcode = CloseByPosition(types4tickets[i]._2, types4tickets[j]._2);
if(retcode) return retcode; // error
}
else
{
break;
}
}
}
if(cleanup && j < n)
{
retcode = CloseAllPositions(types4tickets, i, j + 1);
}
return retcode;
}
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
Hàm CloseAllPositions
gần giống như sau:
uint CloseAllPositions(const Tuple2<ulong,ulong> &types4tickets[],
const int start = 0, const int end = 0)
{
const int n = end == 0 ? ArraySize(types4tickets) : end;
Print("CloseAllPositions ", n - start);
for(int i = start; i < n; ++i)
{
MqlTradeRequestSyncLog request;
request.comment = "close down " + (string)(i + 1 - start)
+ " of " + (string)(n - start);
const ulong ticket = types4tickets[i]._2;
if(!(request.close(ticket) && request.completed()))
{
Print("Error: position is not closed ", ticket);
return request.result.retcode; // error
}
}
return 0; // success
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Bạn có thể so sánh hoạt động của các Expert Advisors PendingOrderGrid1.mq5
và PendingOrderGrid2.mq5
trong trình kiểm tra.
Các báo cáo sẽ hơi khác nhau, vì nếu có nhiều vị thế, chúng được đóng theo các kết hợp đối lập, do đó việc đóng các vị thế không ghép đôi khác diễn ra dựa trên spread riêng lẻ của chúng.