Lọc lệnh theo thuộc tính
Trong một trong những phần về thuộc tính ký hiệu, chúng ta đã giới thiệu lớp SymbolFilter
để chọn các công cụ tài chính với các đặc điểm được chỉ định. Bây giờ chúng ta sẽ áp dụng cách tiếp cận tương tự cho các lệnh.
Vì chúng ta phải phân tích không chỉ lệnh mà còn cả giao dịch và vị thế theo cách tương tự, chúng ta sẽ tách phần chung của thuật toán lọc vào lớp cơ sở TradeFilter
(TradeFilter.mqh
). Nó gần như lặp lại hoàn toàn mã nguồn của SymbolFilter
. Do đó, chúng ta sẽ không giải thích lại ở đây.
Những ai muốn có thể thực hiện so sánh ngữ cảnh giữa tệp SymbolFilter.mqh
và TradeFilter.mqh
để thấy chúng giống nhau như thế nào và xác định các chỉnh sửa nhỏ.
Điểm khác biệt chính là lớp TradeFilter
là một mẫu vì nó phải xử lý các thuộc tính của các đối tượng khác nhau: lệnh, giao dịch và vị thế.
enum IS // các điều kiện so sánh được hỗ trợ trong bộ lọc
{
EQUAL,
GREATER,
NOT_EQUAL,
LESS
};
enum ENUM_ANY // liệt kê giả để ép kiểu tất cả các liệt kê sang nó
{
};
template<typename T,typename I,typename D,typename S>
class TradeFilter
{
protected:
MapArray<ENUM_ANY,long> longs;
MapArray<ENUM_ANY,double> doubles;
MapArray<ENUM_ANY,string> strings;
MapArray<ENUM_ANY,IS> conditions;
...
template<typename V>
static bool equal(const V v1, const V v2);
template<typename V>
static bool greater(const V v1, const V v2);
template<typename V>
bool match(const T &m, const MapArray<ENUM_ANY,V> &data) const;
public:
// phương thức để thêm điều kiện vào bộ lọc
TradeFilter *let(const I property, const long value, const IS cmp = EQUAL);
TradeFilter *let(const D property, const double value, const IS cmp = EQUAL);
TradeFilter *let(const S property, const string value, const IS cmp = EQUAL);
// phương thức để lấy vào mảng các bản ghi khớp với bộ lọc
template<typename E,typename V>
bool select(const E property, ulong &tickets[], V &data[],
const bool sort = false) const;
template<typename E,typename V>
bool select(const E &property[], ulong &tickets[], V &data[][],
const bool sort = false) const;
bool select(ulong &tickets[]) const;
...
}
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
Các tham số mẫu I, D và S là các liệt kê cho các nhóm thuộc tính của ba loại chính (số nguyên, số thực và chuỗi): đối với lệnh, chúng đã được mô tả trong các phần trước, vì vậy để rõ ràng, bạn có thể hình dung rằng I=ENUM_ORDER_PROPERTY_INTEGER
, D=ENUM_ORDER_PROPERTY_DOUBLE
, S=ENUM_ORDER_PROPERTY_STRING
.
Loại T được thiết kế để chỉ định một lớp giám sát. Hiện tại chúng ta chỉ có một lớp giám sát sẵn sàng, OrderMonitor
. Sau này chúng ta sẽ triển khai DealMonitor
và PositionMonitor
.
Trước đó, trong lớp SymbolFilter
, chúng ta không sử dụng các tham số mẫu vì đối với ký hiệu, tất cả các loại liệt kê thuộc tính đều được biết rõ ràng và có một lớp duy nhất SymbolMonitor
.
Hãy nhớ lại cấu trúc của lớp bộ lọc. Một nhóm phương thức let
cho phép bạn đăng ký một tổ hợp các cặp property=value
trong bộ lọc, sau đó sẽ được sử dụng để chọn các đối tượng trong các phương thức select
. Thuộc tính ID được chỉ định trong tham số property
, và giá trị được chỉ định trong tham số value
.
Cũng có một số phương thức select
. Chúng cho phép mã gọi điền vào một mảng với các vé đã chọn, cũng như, nếu cần, các mảng bổ sung với các giá trị của các thuộc tính đối tượng được yêu cầu. Các định danh cụ thể của các thuộc tính được yêu cầu được đặt trong tham số đầu tiên của phương thức select
; đó có thể là một thuộc tính hoặc nhiều thuộc tính. Tùy thuộc vào điều này, mảng nhận phải là một chiều hoặc hai chiều.
Sự kết hợp của thuộc tính và giá trị có thể được kiểm tra không chỉ cho sự bằng nhau (EQUAL
) mà còn cho các phép toán lớn hơn/nhỏ hơn (GREATER
/LESS
). Đối với các thuộc tính chuỗi, có thể chấp nhận được việc chỉ định một mẫu tìm kiếm với ký tự *
biểu thị bất kỳ chuỗi ký tự nào (ví dụ, *[tp]*
cho thuộc tính ORDER_COMMENT
sẽ khớp với tất cả các bình luận có chứa [tp]
ở bất kỳ đâu, mặc dù đây chỉ là ví dụ minh họa — trong khi để tìm kiếm các lệnh phát sinh từ Take Profit
đã kích hoạt, bạn nên phân tích ORDER_REASON).
Vì thuật toán yêu cầu thực hiện một vòng lặp qua tất cả các đối tượng và các đối tượng có thể thuộc các loại khác nhau (hiện tại là lệnh, nhưng sau đó sẽ hỗ trợ giao dịch và vị thế), chúng ta cần mô tả hai phương thức trừu tượng trong lớp TradeFilter
: total
và get
:
virtual int total() const = 0;
virtual ulong get(const int i) const = 0;
2
Phương thức đầu tiên trả về số lượng đối tượng và phương thức thứ hai trả về vé lệnh theo số thứ tự của nó. Điều này có thể nhắc bạn về cặp hàm OrdersTotal
và OrderGetTicket
. Thật vậy, chúng được sử dụng trong các triển khai cụ thể của các phương thức để lọc lệnh.
Dưới đây là lớp OrderFilter
(OrderFilter.mqh
) đầy đủ.
#include <MQL5Book/OrderMonitor.mqh>
#include <MQL5Book/TradeFilter.mqh>
class OrderFilter: public TradeFilter<OrderMonitor,
ENUM_ORDER_PROPERTY_INTEGER,
ENUM_ORDER_PROPERTY_DOUBLE,
ENUM_ORDER_PROPERTY_STRING>
{
protected:
virtual int total() const override
{
return OrdersTotal();
}
virtual ulong get(const int i) const override
{
return OrderGetTicket(i);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Sự đơn giản này đặc biệt quan trọng vì các bộ lọc tương tự sẽ được tạo ra một cách dễ dàng cho giao dịch và vị thế.
Với sự trợ giúp của lớp mới, chúng ta có thể kiểm tra sự hiện diện của các lệnh thuộc về Expert Advisor của chúng ta dễ dàng hơn nhiều, tức là thay thế bất kỳ phiên bản tự viết nào của hàm GetMyOrder
được sử dụng trong ví dụ PendingOrderModify.mq5.
OrderFilter filter;
ulong tickets[];
// đặt điều kiện cho các lệnh với ký hiệu hiện tại và số "magic" của chúng ta
filter.let(ORDER_SYMBOL, _Symbol).let(ORDER_MAGIC, Magic);
// chọn các vé phù hợp vào một mảng
if(filter.select(tickets))
{
ArrayPrint(tickets);
}
2
3
4
5
6
7
8
9
10
Bằng "bất kỳ phiên bản" ở đây, chúng ta muốn nói rằng nhờ lớp bộ lọc, chúng ta có thể tạo các điều kiện tùy ý để chọn lệnh và thay đổi chúng "trên đường đi" (ví dụ, theo chỉ dẫn của người dùng, không phải lập trình viên).
Ví dụ về cách sử dụng bộ lọc, hãy sử dụng một Expert Advisor tạo ra một lưới các lệnh chờ để giao dịch dựa trên sự bật lại từ các mức trong một phạm vi giá nhất định, tức là được thiết kế cho thị trường biến động. Bắt đầu từ phần này và trong vài phần tiếp theo, chúng ta sẽ sửa đổi Expert Advisor trong bối cảnh của tài liệu đang được nghiên cứu.
Phiên bản đầu tiên của Expert Advisor PendingOrderGrid1.mq5
xây dựng một lưới có kích thước nhất định từ các lệnh giới hạn và lệnh dừng-giới hạn. Các tham số sẽ là số lượng mức giá và bước tính bằng điểm giữa chúng. Sơ đồ hoạt động được minh họa trong biểu đồ sau.
Lưới lệnh chờ trên 4 mức với bước 200 điểm
Tại một thời điểm ban đầu nhất định, có thể được xác định bởi lịch trình trong ngày và có thể tương ứng, ví dụ, với "đêm phẳng", giá hiện tại được làm tròn lên đến kích thước bước của lưới, và một số mức được chỉ định được đặt lên trên và xuống dưới từ mức này.
Tại mỗi mức phía trên, chúng ta đặt một lệnh bán giới hạn và một lệnh mua stoplimit
với giá của lệnh giới hạn tương lai thấp hơn một mức. Tại mỗi mức phía dưới, chúng ta đặt một lệnh mua giới hạn và một lệnh bán stoplimit
với giá của lệnh giới hạn tương lai cao hơn một mức.
Khi giá chạm vào một trong các mức, lệnh giới hạn tại đó biến thành lệnh mua hoặc bán (vị thế). Đồng thời, một lệnh dừng-giới hạn cùng mức được hệ thống tự động chuyển đổi thành một lệnh giới hạn theo hướng ngược lại tại mức tiếp theo.
Ví dụ, nếu giá phá vỡ mức khi di chuyển lên, chúng ta sẽ nhận được một vị thế bán, và một lệnh giới hạn mua sẽ được tạo ra ở khoảng cách bước bên dưới nó.
Expert Advisor sẽ theo dõi rằng tại mỗi mức có một lệnh dừng-giới hạn đi đôi với một lệnh giới hạn. Do đó, sau khi phát hiện một lệnh giới hạn mua mới, chương trình sẽ thêm một lệnh bán dừng-giới hạn vào đó tại cùng mức, và giá mục tiêu của lệnh giới hạn tương lai là mức tiếp theo phía trên, tức là mức nơi vị thế được mở.
Giả sử giá quay đầu xuống và kích hoạt một lệnh giới hạn ở mức bên dưới — chúng ta sẽ nhận được một vị thế mua. Đồng thời, lệnh dừng-giới hạn được chuyển đổi thành một lệnh giới hạn bán ở mức tiếp theo phía trên. Bây giờ Expert Advisor sẽ lại phát hiện một lệnh giới hạn "trần trụi" và tạo một lệnh dừng-giới hạn mua để ghép đôi với nó, tại cùng mức với giá của lệnh giới hạn tương lai thấp hơn một mức.
Nếu có các vị thế đối lập, chúng ta sẽ đóng chúng. Chúng ta cũng sẽ cung cấp cài đặt cho một khoảng thời gian trong ngày khi hệ thống giao dịch được kích hoạt, và trong thời gian còn lại, tất cả các lệnh và vị thế sẽ bị xóa. Điều này, đặc biệt, hữu ích cho "đêm phẳng", khi các biến động trở lại của thị trường đặc biệt rõ rệt.
Tất nhiên, đây chỉ là một trong nhiều cách triển khai tiềm năng của chiến lược lưới, thiếu nhiều tùy chỉnh của lưới, nhưng chúng ta sẽ không làm phức tạp ví dụ này.
Expert Advisor sẽ phân tích tình hình trên mỗi thanh (giả định khung thời gian H1 hoặc nhỏ hơn). Về lý thuyết, điều này... (bị cắt ngắn 100686 ký tự)... ví dụ:
for(int k = -GridSize; k <= (int)GridSize; ++k)
{
if(k != 0)
{
int m = (k < 0 ? 0 : 1);
if(!request[m][k < 0 ? 0 : 1].completed(order(m ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_BUY_LIMIT,
volume, base + k * GridStep * point) &&
request[m][k < 0 ? 1 : 0].completed(order(m ? ORDER_TYPE_BUY_STOP_LIMIT : ORDER_TYPE_SELL_STOP_LIMIT,
volume, base + k * GridStep * point,
base + (k - 1) * GridStep * point)))
{
Alert(message + (string)m + "/BSL");
return request[m][1].result.retcode;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Sau đó, chúng ta kiểm tra sự sẵn sàng.
for(int i = 0; i < (int)GridSize; ++i)
{
for(int j = 0; j < 2; ++j)
{
if(!request[i][j].completed())
{
Alert(message + (string)i + "/" + (string)j + " post-check");
return request[i][j].result.retcode;
}
}
}
return GRID_OK;
}
2
3
4
5
6
7
8
9
10
11
12
13
Mặc dù việc kiểm tra (gọi completed
) được phân cách với việc gửi lệnh, cấu trúc của chúng ta vẫn sử dụng dạng đồng bộ OrderSend
bên trong. Thực tế, để tăng tốc việc gửi một loạt lệnh (như trong Expert Advisor lưới của chúng ta), tốt hơn nên sử dụng phiên bản không đồng bộ OrderSendAsync
. Nhưng sau đó, trạng thái thực thi lệnh nên được khởi tạo từ trình xử lý sự kiện OnTradeTransaction. Chúng ta sẽ nghiên cứu điều này sau.
Một lỗi khi gửi bất kỳ lệnh nào dẫn đến việc thoát sớm khỏi vòng lặp và trả về mã từ máy chủ. Expert Advisor thử nghiệm này sẽ chỉ đơn giản dừng công việc tiếp theo trong trường hợp có lỗi. Đối với một robot thực sự, cần cung cấp phân tích thông minh về ý nghĩa của lỗi và, nếu cần, xóa tất cả các lệnh và đóng vị thế.
Các vị thế được tạo ra bởi các lệnh chờ được đóng bởi hàm CompactPositions
.
uint CompactPositions(const bool cleanup = false)
Tham số cleanup
bằng false
theo mặc định có nghĩa là "dọn dẹp" định kỳ các vị thế trong khoảng thời gian giao dịch, tức là đóng các vị thế đối lập (nếu có). Giá trị cleanup=true
được sử dụng để buộc đóng tất cả các vị thế vào cuối khoảng thời gian giao dịch.
Hàm điền các mảng ticketsLong
và ticketsShort
với các vé của các vị thế mua và bán bằng hàm trợ giúp GetMyPositions
. Chúng ta đã sử dụng hàm này trong ví dụ TradeCloseBy.mq5
trong phần Đóng các vị thế đối lập: toàn bộ và một phần. Hàm CloseByPosition
từ ví dụ đó đã trải qua những thay đổi tối thiểu trong Expert Advisor mới: nó trả về mã từ máy chủ thay vì chỉ báo logic về thành công hay lỗi.
uint CompactPositions(const bool cleanup = false)
{
uint retcode = 0;
ulong ticketsLong[], ticketsShort[];
const int n = GetMyPositions(_Symbol, Magic, ticketsLong, ticketsShort);
if(n > 0)
{
Print("CompactPositions, pairs: ", n);
for(int i = 0; i < n; ++i)
{
retcode = CloseByPosition(ticketsShort[i], ticketsLong[i]);
if(retcode) return retcode;
}
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Phần thứ hai của CompactPositions
chỉ hoạt động khi cleanup=true
. Nó còn xa mới hoàn hảo và sẽ sớm được viết lại.
if(cleanup)
{
if(ArraySize(ticketsLong) > ArraySize(ticketsShort))
{
retcode = CloseAllPositions(ticketsLong, ArraySize(ticketsShort));
}
else if(ArraySize(ticketsLong) < ArraySize(ticketsShort))
{
retcode = CloseAllPositions(ticketsShort, ArraySize(ticketsLong));
}
}
return retcode;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Đối với tất cả các vị thế còn lại được tìm thấy, việc đóng thông thường được thực hiện bằng cách gọi CloseAllPositions
.
uint CloseAllPositions(const ulong &tickets[], const int start = 0)
{
const int n = ArraySize(tickets);
Print("CloseAllPositions ", n);
for(int i = start; i < n; ++i)
{
MqlTradeRequestSyncLog request;
request.comment = "close down " + (string)(i + 1 - start)
+ " of " + (string)(n - start);
if(!(request.close(tickets[i]) && request.completed()))
{
Print("Error: position is not closed ", tickets[i]);
return request.result.retcode;
}
}
return 0; // thành công
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Bây giờ chúng ta chỉ cần xem xét hàm RemoveOrders
. Nó cũng sử dụng bộ lọc lệnh để lấy danh sách chúng, sau đó gọi phương thức remove
trong một vòng lặp.
uint RemoveOrders()
{
OrderFilter filter;
ulong tickets[];
filter.let(ORDER_SYMBOL, _Symbol).let(ORDER_MAGIC, Magic)
.select(tickets);
const int n = ArraySize(tickets);
for(int i = 0; i < n; ++i)
{
MqlTradeRequestSyncLog request;
request.comment = "removal " + (string)(i + 1) + " of " + (string)n;
if(!(request.remove(tickets[i]) && request.completed()))
{
Print("Error: order is not removed ", tickets[i]);
return request.result.retcode;
}
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Hãy kiểm tra cách Expert Advisor hoạt động trong trình kiểm tra với các cài đặt mặc định (thời gian giao dịch từ 00:00 đến 09:00). Dưới đây là ảnh chụp màn hình khi chạy trên EURUSD, H1.
Chiến lược lưới PendingOrderGrid1.mq5 trong trình kiểm tra
Trong nhật ký, ngoài các mục định kỳ về việc tạo hàng loạt lệnh (vào đầu ngày) và việc xóa chúng vào buổi sáng, chúng ta sẽ thường xuyên thấy việc khôi phục mạng lưới (thêm lệnh thay cho những lệnh đã kích hoạt) và đóng vị thế.
buy stop limit 0.01 EURUSD at 1.14200 (1.14000) (1.13923 / 1.13923)
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, »
» @ 1.14200, X=1.14000, ORDER_TIME_GTC, M=1234567890, repair
DONE, #=159, V=0.01, Bid=1.13923, Ask=1.13923, Request executed, Req=287
CompactPositions, pairs: 1
close position #152 sell 0.01 EURUSD by position #153 buy 0.01 EURUSD (1.13923 / 1.13923)
deal #18 buy 0.01 EURUSD at 1.13996 done (based on order #160)
deal #19 sell 0.01 EURUSD at 1.14202 done (based on order #160)
Positions collapse initiated
OK CloseBy Order/Deal/Position
TRADE_ACTION_CLOSE_BY, EURUSD, ORDER_TYPE_BUY, ORDER_FILLING_FOK, P=152, b=153, »
» M=1234567890, compacting
DONE, D=18, #=160, Request executed, Req=288
2
3
4
5
6
7
8
9
10
11
12
13
Bây giờ là lúc để nghiên cứu các hàm MQL5 để làm việc với vị thế và cải thiện việc lựa chọn cũng như phân tích chúng trong Expert Advisor của chúng ta. Các phần tiếp theo sẽ giải quyết vấn đề này.