Chương trình máy khách dịch vụ tín hiệu trong MQL5
Vậy nên, theo quyết định của chúng ta, văn bản trong các thông điệp dịch vụ sẽ được định dạng JSON.
Trong phiên bản phổ biến nhất, JSON là một mô tả dạng văn bản của một đối tượng, tương tự như cách nó được thực hiện cho các cấu trúc trong MQL5. Đối tượng được bao quanh bởi dấu ngoặc nhọn, bên trong đó các thuộc tính của nó được ghi lại, phân tách bằng dấu phẩy: mỗi thuộc tính có một định danh trong dấu ngoặc kép, theo sau là dấu hai chấm và giá trị của thuộc tính. Ở đây, các thuộc tính của một số kiểu nguyên thủy được hỗ trợ: chuỗi, số nguyên và số thực, giá trị logic true
/false
, và giá trị rỗng null
. Ngoài ra, giá trị của thuộc tính có thể, đến lượt nó, là một đối tượng hoặc một mảng. Các mảng được mô tả bằng dấu ngoặc vuông, bên trong đó các phần tử được phân tách bằng dấu phẩy. Ví dụ,
{
"string": "this is a text",
"number": 0.1,
"integer": 789735095,
"enabled": true,
"subobject" :
{
"option": null
},
"array":
[
1, 2, 3, 5, 8
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Về cơ bản, một mảng ở cấp cao nhất cũng là JSON hợp lệ. Ví dụ,
[
{
"command": "buy",
"volume": 0.1,
"symbol": "EURUSD",
"price": 1.0
},
{
"command": "sell",
"volume": 0.01,
"symbol": "GBPUSD",
"price": 1.5
}
]
2
3
4
5
6
7
8
9
10
11
12
13
14
Để giảm lưu lượng trong các giao thức ứng dụng sử dụng JSON, thường người ta rút ngắn tên các trường xuống còn vài chữ cái (thường là một chữ).
Tên thuộc tính và giá trị chuỗi được bao quanh bởi dấu ngoặc kép. Nếu bạn muốn chỉ định một dấu ngoặc kép trong chuỗi, nó phải được thoát bằng dấu gạch chéo ngược.
Việc sử dụng JSON làm cho giao thức trở nên linh hoạt và có thể mở rộng. Ví dụ, đối với dịch vụ đang được thiết kế (tín hiệu giao dịch và, trong trường hợp tổng quát hơn, sao chép trạng thái tài khoản), có thể giả định cấu trúc thông điệp sau:
{
"origin": "publisher_id", // người gửi thông điệp ("Server" trong thông điệp kỹ thuật)
"msg" : // thông điệp (văn bản hoặc JSON) như được nhận từ người gửi
{
"trade" : // các lệnh giao dịch hiện tại (nếu có tín hiệu)
{
"operation": ..., // mua/bán/đóng
"symbol": "ticker",
"volume": 0.1,
... // các tham số tín hiệu khác
},
"account": // trạng thái tài khoản
{
"positions": // vị thế
{
"n": 10, // số lượng vị thế mở
[ { ... },{ ... } ] // mảng các thuộc tính của vị thế mở
},
"pending_orders": // lệnh chờ
{
"n": ...
[ { ... } ]
}
"drawdown": 2.56,
"margin_level": 12345,
... // các tham số trạng thái khác
},
"hardware": // điều khiển từ xa "sức khỏe" của PC
{
"memory": ...,
"ping_to_broker": ...
}
}
}
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ột số tính năng này có thể được hỗ trợ hoặc không bởi các triển khai cụ thể của chương trình máy khách (mọi thứ mà chúng không "hiểu", chúng sẽ chỉ đơn giản bỏ qua). Ngoài ra, với điều kiện không có xung đột trong tên các thuộc tính ở cùng cấp độ, mỗi nhà cung cấp thông tin có thể thêm dữ liệu cụ thể của riêng mình vào JSON. Dịch vụ nhắn tin sẽ chỉ đơn giản chuyển tiếp thông tin này. Tất nhiên, chương trình ở phía nhận phải có khả năng diễn giải các dữ liệu cụ thể này.
Cuốn sách đi kèm với một bộ phân tích JSON có tên ToyJson
("JSON đồ chơi", tệp toyjson.mqh
) nhỏ và không hiệu quả, không hỗ trợ đầy đủ các khả năng của đặc tả định dạng (ví dụ, trong việc xử lý các chuỗi escape
). Nó được viết đặc biệt cho dịch vụ demo này, được điều chỉnh cho cấu trúc thông tin dự kiến, không quá phức tạp, về tín hiệu giao dịch. Chúng ta sẽ không mô tả chi tiết về nó ở đây, và các nguyên tắc sử dụng của nó sẽ trở nên rõ ràng từ mã nguồn của máy khách MQL của dịch vụ tín hiệu.
Đối với các dự án của bạn và để phát triển thêm dự án này, bạn có thể chọn các bộ phân tích JSON khác có sẵn trong cơ sở mã trên trang web mql5.com
.
Một phần tử (thùng chứa hoặc thuộc tính) trong ToyJson
được mô tả bởi đối tượng lớp JsValue
. Có một số phiên bản nạp chồng của phương thức put(key, value)
được định nghĩa, có thể được sử dụng để thêm các thuộc tính bên trong có tên như trong một đối tượng JSON hoặc put(value)
, để thêm một giá trị như trong một mảng JSON. Ngoài ra, đối tượng này có thể đại diện cho một giá trị đơn lẻ của kiểu nguyên thủy. Để đọc các thuộc tính của một đối tượng JSON, bạn có thể áp dụng ký hiệu toán tử [] theo sau là tên thuộc tính cần thiết trong dấu ngoặc cho JsValue
. Rõ ràng, các chỉ số số nguyên được hỗ trợ để truy cập bên trong một mảng JSON.
Sau khi đã hình thành cấu hình cần thiết của các đối tượng liên quan JsValue
, bạn có thể tuần tự hóa nó thành văn bản JSON bằng phương thức stringify(string&buffer)
.
Lớp thứ hai trong toyjson.mqh
— JsParser
— cho phép bạn thực hiện thao tác ngược lại: chuyển văn bản với mô tả JSON thành một cấu trúc phân cấp của các đối tượng JsValue
.
Xem xét các lớp để làm việc với JSON, hãy bắt đầu viết một Expert Advisor MQL5/Experts/MQL5Book/p7/wsTradeCopier/wstradecopier.mq5
, có thể thực hiện cả hai vai trò trong dịch vụ sao chép giao dịch: một nhà cung cấp thông tin về các giao dịch được thực hiện trên tài khoản hoặc một người nhận thông tin này từ dịch vụ để tái tạo các giao dịch đó.
Khối lượng và nội dung của thông tin được gửi, từ quan điểm chính sách, nằm trong quyền quyết định của nhà cung cấp và có thể khác nhau đáng kể tùy thuộc vào kịch bản (mục đích) sử dụng dịch vụ. Đặc biệt, có thể sao chép chỉ các giao dịch đang diễn ra hoặc toàn bộ số dư tài khoản cùng với các lệnh chờ và mức bảo vệ. Trong ví dụ của chúng ta, chúng ta sẽ chỉ chỉ ra việc triển khai kỹ thuật của việc chuyển giao thông tin, và sau đó bạn có thể chọn một tập hợp cụ thể các đối tượng và thuộc tính theo ý muốn của bạn.
Trong mã, chúng ta sẽ mô tả 3 cấu trúc được kế thừa từ các cấu trúc tích hợp sẵn và cung cấp việc "đóng gói" thông tin trong JSON:
- MqlTradeRequestWeb — MqlTradeRequest
- MqlTradeResultWeb — MqlTradeResult
- DealMonitorWeb — DealMonitor*
Cấu trúc cuối cùng trong danh sách, nghiêm túc mà nói, không phải là tích hợp sẵn, mà được chúng ta định nghĩa trong tệp DealMonitor.mqh
, nhưng nó được điền dựa trên tập hợp tiêu chuẩn các thuộc tính giao dịch.
Hàm tạo của mỗi cấu trúc dẫn xuất điền các trường dựa trên nguồn chính được truyền vào (yêu cầu giao dịch, kết quả của nó, hoặc giao dịch). Mỗi cấu trúc triển khai phương thức asJsValue
, trả về một con trỏ đến đối tượng JsValue
phản ánh tất cả các thuộc tính của cấu trúc: chúng được thêm vào đối tượng JSON bằng phương thức JsValue::put
. Ví dụ, đây là cách nó được thực hiện trong trường hợp của MqlTradeRequest
:
struct MqlTradeRequestWeb: public MqlTradeRequest
{
MqlTradeRequestWeb(const MqlTradeRequest &r)
{
ZeroMemory(this);
action = r.action;
magic = r.magic;
order = r.order;
symbol = r.symbol;
volume = r.volume;
price = r.price;
stoplimit = r.stoplimit;
sl = r.sl;
tp = r.tp;
type = r.type;
type_filling = r.type_filling;
type_time = r.type_time;
expiration = r.expiration;
comment = r.comment;
position = r.position;
position_by = r.position_by;
}
JsValue *asJsValue() const
{
JsValue *req = new JsValue();
// main block: action, symbol, type
req.put("a", VerboseJson ? EnumToString(action) : (string)action);
if(StringLen(symbol) != 0) req.put("s", symbol);
req.put("t", VerboseJson ? EnumToString(type) : (string)type);
// volumes
if(volume != 0) req.put("v", TU::StringOf(volume));
req.put("f", VerboseJson ? EnumToString(type_filling) : (string)type_filling);
// block with prices
if(price != 0) req.put("p", TU::StringOf(price));
if(stoplimit != 0) req.put("x", TU::StringOf(stoplimit));
if(sl != 0) req.put("sl", TU::StringOf(sl));
if(tp != 0) req.put("tp", TU::StringOf(tp));
// block of pending orders
if(TU::IsPendingType(type))
{
req.put("t", VerboseJson ? EnumToString(type_time) : (string)type_time);
if(expiration != 0) req.put("d", TimeToString(expiration));
}
// modification block
if(order != 0) req.put("o", order);
if(position != 0) req.put("q", position);
if(position_by != 0) req.put("b", position_by);
// helper block
if(magic != 0) req.put("m", magic);
if(StringLen(comment)) req.put("c", comment);
return req;
}
};
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
51
52
53
54
55
56
57
58
59
60
Chúng ta chuyển tất cả các thuộc tính sang JSON (điều này phù hợp với dịch vụ giám sát tài khoản), nhưng bạn có thể chỉ giữ lại một tập hợp hạn chế.
Đối với các thuộc tính là kiểu liệt kê, chúng ta đã cung cấp hai cách để biểu diễn chúng trong JSON: dưới dạng số nguyên và dưới dạng tên chuỗi của một phần tử liệt kê. Việc lựa chọn phương pháp được thực hiện bằng tham số đầu vào VerboseJson
(lý tưởng nhất, nó nên được ghi trong mã cấu trúc không trực tiếp mà thông qua một tham số hàm tạo).
input bool VerboseJson = false;
Việc chỉ truyền số sẽ đơn giản hóa việc lập mã vì ở phía nhận, chỉ cần ép kiểu chúng sang kiểu liệt kê mong muốn để thực hiện các hành động "phản chiếu". Tuy nhiên, các số làm khó khăn cho con người trong việc nhận thức thông tin, và họ có thể cần phân tích tình huống (thông điệp). Do đó, việc hỗ trợ một tùy chọn cho biểu diễn chuỗi, như là "thân thiện" hơn, là hợp lý, mặc dù nó đòi hỏi các thao tác bổ sung trong thuật toán nhận.
Các tham số đầu vào cũng chỉ định địa chỉ máy chủ, vai trò ứng dụng, và chi tiết kết nối riêng biệt cho nhà cung cấp và người đăng ký.
enum TRADE_ROLE
{
TRADE_PUBLISHER, // Nhà cung cấp giao dịch
TRADE_SUBSCRIBER // Người đăng ký giao dịch
};
input string Server = "ws://localhost:9000/";
input TRADE_ROLE Role = TRADE_PUBLISHER;
input bool VerboseJson = false;
input group "Publisher";
input string PublisherID = "PUB_ID_001";
input string PublisherPrivateKey = "PUB_KEY_FFF";
input string SymbolFilter = ""; // Bộ lọc ký hiệu (rỗng - hiện tại, '*' - bất kỳ)
input ulong MagicFilter = 0; // Bộ lọc số ma thuật (0 - bất kỳ)
input group "Subscriber";
input string SubscriberID = "SUB_ID_100";
input string SubscribeToPublisherID = "PUB_ID_001";
input string SubscriberAccessKey = "fd3f7a105eae8c2d9afce0a7a4e11bf267a40f04b7c216dd01cf78c7165a2a5a";
input string SymbolSubstitute = "EURUSD=GBPUSD"; // Thay thế ký hiệu (<từ>=<đến>,...)
input ulong SubscriberMagic = 0;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Các tham số SymbolFilter
và MagicFilter
trong nhóm nhà cung cấp cho phép giới hạn hoạt động giao dịch được giám sát đối với một ký hiệu và số ma thuật đã cho. Giá trị rỗng trong SymbolFilter
có nghĩa là chỉ kiểm soát ký hiệu hiện tại của biểu đồ, để chặn bất kỳ giao dịch nào, nhập ký hiệu '*'. Nhà cung cấp tín hiệu sẽ sử dụng hàm FilterMatched
cho mục đích này, hàm này chấp nhận ký hiệu và số ma thuật của giao dịch.
bool FilterMatched(const string s, const ulong m)
{
if(MagicFilter != 0 && MagicFilter != m)
{
return false;
}
if(StringLen(SymbolFilter) == 0)
{
if(s != _Symbol)
{
return false;
}
}
else if(SymbolFilter != s && SymbolFilter != "*")
{
return false;
}
return true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Tham số SymbolSubstitute
trong nhóm đầu vào của người đăng ký cho phép thay thế ký hiệu nhận được trong thông điệp bằng một ký hiệu khác, sẽ được sử dụng cho việc sao chép giao dịch. Tính năng này hữu ích nếu tên của các mã giao dịch của cùng một công cụ tài chính khác nhau giữa các nhà môi giới. Nhưng tham số này cũng thực hiện chức năng của một bộ lọc cho phép lặp lại tín hiệu: chỉ các ký hiệu được chỉ định ở đây sẽ được giao dịch. Ví dụ, để cho phép giao dịch tín hiệu cho ký hiệu EURUSD (ngay cả khi không thay thế mã), bạn cần đặt chuỗi "EURUSD=EURUSD" trong tham số. Ký hiệu từ các thông điệp tín hiệu được chỉ định ở bên trái của dấu '=', và ký hiệu để giao dịch được chỉ định ở bên phải.
Danh sách thay thế ký tự được xử lý bởi hàm FillSubstitutes
trong quá trình khởi tạo và sau đó được sử dụng để thay thế và giải quyết giao dịch bằng hàm FindSubstitute
.
string Substitutes[][2];
void FillSubstitutes()
{
string list[];
const int n = StringSplit(SymbolSubstitute, ',', list);
ArrayResize(Substitutes, n);
for(int i = 0; i < n; ++i)
{
string pair[];
if(StringSplit(list[i], '=', pair) == 2)
{
Substitutes[i][0] = pair[0];
Substitutes[i][1] = pair[1];
}
else
{
Print("Wrong substitute: ", list[i]);
}
}
}
string FindSubstitute(const string s)
{
for(int i = 0; i < ArrayRange(Substitutes, 0); ++i)
{
if(Substitutes[i][0] == s) return Substitutes[i][1];
}
return NULL;
}
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
Để giao tiếp với dịch vụ, chúng ta định nghĩa một lớp dẫn xuất từ WebSocketClient
. Nó cần thiết, trước hết, để bắt đầu giao dịch dựa trên tín hiệu khi một thông điệp đến trong trình xử lý onMessage
. Chúng ta sẽ quay lại vấn đề này sau khi xem xét việc hình thành và gửi tín hiệu từ phía nhà cung cấp.
class MyWebSocket: public WebSocketClient<Hybi>
{
public:
MyWebSocket(const string address): WebSocketClient(address) { }
void onMessage(IWebSocketMessage *msg) override
{
...
}
};
MyWebSocket wss(Server);
2
3
4
5
6
7
8
9
10
11
12
Khởi tạo trong OnInit
bật bộ đếm thời gian (để gọi định kỳ wss.checkMessages(false)
) và chuẩn bị các tiêu đề tùy chỉnh với chi tiết người dùng, tùy thuộc vào vai trò được chọn. Sau đó, chúng ta mở kết nối bằng lệnh gọi wss.open(custom)
.
int OnInit()
{
FillSubstitutes();
EventSetTimer(1);
wss.setTimeOut(1000);
Print("Opening...");
string custom;
if(Role == TRADE_PUBLISHER)
{
custom = "Sec-Websocket-Protocol: X-MQL5-publisher-"
+ PublisherID + "-" + PublisherPrivateKey + "\r\n";
}
else
{
custom = "Sec-Websocket-Protocol: X-MQL5-subscriber-"
+ SubscriberID + "-" + SubscribeToPublisherID
+ "-" + SubscriberAccessKey + "\r\n";
}
return wss.open(custom) ? INIT_SUCCEEDED : INIT_FAILED;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Cơ chế sao chép, tức là chặn các giao dịch và gửi thông tin về chúng đến một dịch vụ web, được khởi động trong trình xử lý OnTradeTransaction
. Như chúng ta biết, đây không phải là cách duy nhất và có thể phân tích "ảnh chụp nhanh" của trạng thái tài khoản trong OnTrade
.
void OnTradeTransaction(const MqlTradeTransaction &transaction,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
if(transaction.type == TRADE_TRANSACTION_REQUEST)
{
Print(TU::StringOf(request));
Print(TU::StringOf(result));
if(result.retcode == TRADE_RETCODE_PLACED // hành động thành công
|| result.retcode == TRADE_RETCODE_DONE
|| result.retcode == TRADE_RETCODE_DONE_PARTIAL)
{
if(FilterMatched(request.symbol, request.magic))
{
... // xem khối mã tiếp theo
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Chúng ta theo dõi các sự kiện về các yêu cầu giao dịch đã hoàn tất thành công đáp ứng các điều kiện của bộ lọc được chỉ định. Tiếp theo, các cấu trúc của yêu cầu, kết quả của yêu cầu và giao dịch được chuyển thành các đối tượng JSON. Tất cả đều được đặt trong một thùng chứa chung msg
dưới các tên "req", "res" và "deal" tương ứng. Hãy nhớ rằng chính thùng chứa này sẽ được bao gồm trong thông điệp dịch vụ web dưới dạng thuộc tính "msg".
// thùng chứa để gắn vào thông điệp dịch vụ sẽ hiển thị dưới dạng thuộc tính "msg":
// {"origin" : "this_publisher_id", "msg" : { dữ liệu của chúng ta ở đây }}
JsValue msg;
MqlTradeRequestWeb req(request);
msg.put("req", req.asJsValue());
MqlTradeResultWeb res(result);
msg.put("res", res.asJsValue());
if(result.deal != 0)
{
DealMonitorWeb deal(result.deal);
msg.put("deal", deal.asJsValue());
}
ulong tickets[];
Positions.select(tickets);
JsValue pos;
pos.put("n", ArraySize(tickets));
msg.put("pos", &pos);
string buffer;
msg.stringify(buffer);
Print(buffer);
wss.send(buffer);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Sau khi được điền đầy, thùng chứa được xuất ra dưới dạng chuỗi vào buffer
, in vào nhật ký và gửi đến máy chủ.
Chúng ta có thể thêm thông tin khác vào thùng chứa này: trạng thái tài khoản (mức sụt giảm, tải), số lượng và thuộc tính của các lệnh chờ, v.v. Vì vậy, chỉ để thể hiện khả năng mở rộng nội dung của thông điệp, chúng ta đã thêm số lượng vị thế mở ở trên. Để chọn các vị thế theo bộ lọc, chúng ta đã sử dụng đối tượng lớp PositionFilter
(PositionFilter.mqh):
PositionFilter Positions;
int OnInit()
{
...
if(MagicFilter) Positions.let(POSITION_MAGIC, MagicFilter);
if(SymbolFilter == "") Positions.let(POSITION_SYMBOL, _Symbol);
else if(SymbolFilter != "*") Positions.let(POSITION_SYMBOL, SymbolFilter);
...
}
2
3
4
5
6
7
8
9
10
Về cơ bản, để tăng độ tin cậy, những người sao chép nên phân tích trạng thái của các vị thế, chứ không chỉ chặn các giao dịch.
Điều này kết thúc việc xem xét phần của Expert Advisor liên quan đến vai trò của nhà cung cấp tín hiệu.
Là một người đăng ký, như chúng ta đã thông báo, Expert Advisor nhận thông điệp trong phương thức MyWebSocket::onMessage
. Tại đây, thông điệp đến được phân tích cú pháp bằng JsParser::jsonify
, và thùng chứa được hình thành bởi phía truyền được lấy từ thuộc tính obj["msg"]
.
class MyWebSocket: public WebSocketClient<Hybi>
{
public:
void onMessage(IWebSocketMessage *msg) override
{
Alert(msg.getString());
JsValue *obj = JsParser::jsonify(msg.getString());
if(obj && obj["msg"])
{
obj["msg"].print();
if(!RemoteTrade(obj["msg"])) { /* xử lý lỗi */ }
delete obj;
}
delete msg;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Hàm RemoteTrade
thực hiện phân tích tín hiệu và các hoạt động giao dịch. Ở đây nó được cung cấp dưới dạng rút gọn, không xử lý các lỗi tiềm ẩn. Hàm này hỗ trợ cả hai cách biểu diễn các liệt kê: dưới dạng giá trị số nguyên hoặc tên phần tử chuỗi. Đối tượng JSON đến được "kiểm tra" để tìm các thuộc tính cần thiết (lệnh và thuộc tính tín hiệu) bằng cách áp dụng toán tử [], bao gồm nhiều lần liên tiếp (để truy cập các đối tượng JSON lồng nhau).
bool RemoteTrade(JsValue *obj)
{
bool success = false;
if(obj["req"]["a"] == TRADE_ACTION_DEAL
|| obj["req"]["a"] == "TRADE_ACTION_DEAL")
{
const string symbol = FindSubstitute(obj["req"]["s"].s);
if(symbol == NULL)
{
Print("Suitable symbol not found for ", obj["req"]["s"].s);
return false; // không tìm thấy hoặc bị cấm
}
JsValue *pType = obj["req"]["t"];
if(pType == ORDER_TYPE_BUY || pType == ORDER_TYPE_SELL
|| pType == "ORDER_TYPE_BUY" || pType == "ORDER_TYPE_SELL")
{
ENUM_ORDER_TYPE type;
if(pType.detect() >= JS_STRING)
{
if(pType == "ORDER_TYPE_BUY") type = ORDER_TYPE_BUY;
else type = ORDER_TYPE_SELL;
}
else
{
type = obj["req"]["t"].get<ENUM_ORDER_TYPE>();
}
MqlTradeRequestSync request;
request.deviation = 10;
request.magic = SubscriberMagic;
request.type = type;
const double lot = obj["req"]["v"].get<double>();
JsValue *pDir = obj["deal"]["entry"];
if(pDir == DEAL_ENTRY_IN || pDir == "DEAL_ENTRY_IN")
{
success = request._market(symbol, lot) && request.completed();
Alert(StringFormat("Trade by subscription: market entry %s %s %s - %s",
EnumToString(type), TU::StringOf(lot), symbol,
success ? "Successful" : "Failed"));
}
else if(pDir == DEAL_ENTRY_OUT || pDir == "DEAL_ENTRY_OUT")
{
// hành động đóng giả định sự hiện diện của một vị thế phù hợp, tìm kiếm nó
PositionFilter filter;
int props[] = {POSITION_TICKET, POSITION_TYPE, POSITION_VOLUME};
Tuple3<long,long,double> values[];
filter.let(POSITION_SYMBOL, symbol).let(POSITION_MAGIC,
SubscriberMagic).select(props, values);
for(int i = 0; i < ArraySize(values); ++i)
{
// cần một vị thế có hướng ngược lại với giao dịch
if(!TU::IsSameType((ENUM_ORDER_TYPE)values[i]._2, type))
{
// cần đủ khối lượng (ở đây chính xác bằng nhau!)
if(TU::Equal(values[i]._3, lot))
{
success = request.close(values[i]._1, lot) && request.completed();
Alert(StringFormat("Trade by subscription: market exit %s %s %s - %s",
EnumToString(type), TU::StringOf(lot), symbol,
success ? "Successful" : "Failed"));
}
}
}
if(!success)
{
Print("No suitable position to close");
}
}
}
}
return success;
}
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
Triển khai này không phân tích giá giao dịch, các giới hạn có thể có về khối lượng, mức dừng và các yếu tố khác. Chúng ta chỉ đơn giản lặp lại giao dịch ở giá hiện tại tại địa phương. Ngoài ra, khi đóng một vị thế, kiểm tra được thực hiện để đảm bảo khối lượng bằng nhau chính xác, điều này phù hợp với tài khoản phòng ngừa rủi ro, nhưng không phù hợp với tài khoản netting, nơi có thể đóng một phần nếu khối lượng giao dịch nhỏ hơn vị thế (và có thể lớn hơn, trong trường hợp đảo chiều, nhưng tùy chọn DEAL_ENTRY_INOUT không được hỗ trợ ở đây). Tất cả các điểm này cần được hoàn thiện cho ứng dụng thực tế.
Hãy khởi động máy chủ node.exe
wspubsub.js
và hai bản sao của Expert Advisor wstradecopier.mq5
trên các biểu đồ khác nhau, trong cùng một terminal. Kịch bản thông thường giả định rằng Expert Advisor cần được khởi chạy trên các tài khoản khác nhau, nhưng một tùy chọn "nghịch lý" cũng phù hợp để kiểm tra hiệu suất: chúng ta sẽ sao chép tín hiệu từ một ký hiệu sang một ký hiệu khác.
Trong một bản sao của Expert Advisor, chúng ta sẽ giữ nguyên cài đặt mặc định, với vai trò là nhà cung cấp. Nó nên được đặt trên biểu đồ EURUSD. Trong bản sao thứ hai chạy trên biểu đồ GBPUSD, chúng ta thay đổi vai trò thành người đăng ký. Chuỗi "EURUSD=GBPUSD" trong tham số đầu vào SymbolSubstitute
cho phép giao dịch GBPUSD dựa trên tín hiệu EURUSD.
Dữ liệu kết nối sẽ được ghi lại, với các tiêu đề HTTP và lời chào mà chúng ta đã thấy, vì vậy chúng ta sẽ bỏ qua chúng.
Hãy mua EURUSD và đảm bảo rằng nó được "nhân bản" với cùng khối lượng cho GBPUSD.
Dưới đây là các đoạn trích từ nhật ký (hãy nhớ rằng do cả hai Expert Advisor hoạt động trong cùng một bản sao của terminal, các thông điệp giao dịch sẽ được gửi đến cả hai biểu đồ và do đó, để dễ phân tích nhật ký, bạn có thể lần lượt đặt các bộ lọc "EURUSD" và "USDUSD"):
(EURUSD,H1) TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 0.99886, #=1461313378
(EURUSD,H1) DONE, D=1439023682, #=1461313378, V=0.01, @ 0.99886, Bid=0.99886, Ask=0.99886, Req=2
(EURUSD,H1) {"req" : {"a" : "TRADE_ACTION_DEAL", "s" : "EURUSD", "t" : "ORDER_TYPE_BUY", "v" : 0.01,
» "f" : "ORDER_FILLING_FOK", "p" : 0.99886, "o" : 1461313378}, "res" : {"code" : 10009, "d" : 1439023682,
» "o" : 1461313378, "v" : 0.01, "p" : 0.99886, "b" : 0.99886, "a" : 0.99886}, "deal" : {"d" : 1439023682,
» "o" : 1461313378, "t" : "2022.09.19 16:45:50", "tmsc" : 1663605950086, "type" : "DEAL_TYPE_BUY",
» "entry" : "DEAL_ENTRY_IN", "pid" : 1461313378, "r" : "DEAL_REASON_CLIENT", "v" : 0.01, "p" : 0.99886,
» "s" : "EURUSD"}, "pos" : {"n" : 1}}
2
3
4
5
6
7
8
Điều này cho thấy nội dung của yêu cầu đã thực hiện và kết quả của nó, cũng như một bộ đệm với chuỗi JSON được gửi đến máy chủ.
Gần như ngay lập tức, ở phía nhận, trên biểu đồ GBPUSD, một cảnh báo được hiển thị với thông điệp từ máy chủ ở dạng "thô" và được định dạng sau khi phân tích cú pháp thành công trong JsParser
. Ở dạng "thô", thuộc tính "origin" được lưu trữ, trong đó máy chủ cho chúng ta biết ai là nguồn của tín hiệu.
(GBPUSD,H1) Alert: {"origin":"publisher PUB_ID_001", "msg":{"req" : {"a" : "TRADE_ACTION_DEAL",
» "s" : "EURUSD", "t" : "ORDER_TYPE_BUY", "v" : 0.01, "f" : "ORDER_FILLING_FOK", "p" : 0.99886,
» "o" : 1461313378}, "res" : {"code" : 10009, "d" : 1439023682, "o" : 1461313378, "v" : 0.01,
» "p" : 0.99886, "b" : 0.99886, "a" : 0.99886}, "deal" : {"d" : 1439023682, "o" : 1461313378,
» "t" : "2022.09.19 16:45:50", "tmsc" : 1663605950086, "type" : "DEAL_TYPE_BUY",
» "entry" : "DEAL_ENTRY_IN", "pid" : 1461313378, "r" : "DEAL_REASON_CLIENT", "v" : 0.01,
» "p" : 0.99886, "s" : "EURUSD"}, "pos" : {"n" : 1}}}
(GBPUSD,H1) {
(GBPUSD,H1) req =
(GBPUSD,H1) {
(GBPUSD,H1) a = TRADE_ACTION_DEAL
(GBPUSD,H1) s = EURUSD
(GBPUSD,H1) t = ORDER_TYPE_BUY
(GBPUSD,H1) v = 0.01
(GBPUSD,H1) f = ORDER_FILLING_FOK
(GBPUSD,H1) p = 0.99886
(GBPUSD,H1) o = 1461313378
(GBPUSD,H1) }
(GBPUSD,H1) res =
(GBPUSD,H1) {
(GBPUSD,H1) code = 10009
(GBPUSD,H1) d = 1439023682
(GBPUSD,H1) o = 1461313378
(GBPUSD,H1) v = 0.01
(GBPUSD,H1) p = 0.99886
(GBPUSD,H1) b = 0.99886
(GBPUSD,H1) a = 0.99886
(GBPUSD,H1) }
(GBPUSD,H1) deal =
(GBPUSD,H1) {
(GBPUSD,H1) d = 1439023682
(GBPUSD,H1) o = 1461313378
(GBPUSD,H1) t = 2022.09.19 16:45:50
(GBPUSD,H1) tmsc = 1663605950086
(GBPUSD,H1) type = DEAL_TYPE_BUY
(GBPUSD,H1) entry = DEAL_ENTRY_IN
(GBPUSD,H1) pid = 1461313378
(GBPUSD,H1) r = DEAL_REASON_CLIENT
(GBPUSD,H1) v = 0.01
(GBPUSD,H1) p = 0.99886
(GBPUSD,H1) s = EURUSD
(GBPUSD,H1) }
(GBPUSD,H1) pos =
(GBPUSD,H1) {
(GBPUSD,H1) n = 1
(GBPUSD,H1) }
(GBPUSD,H1) }
(GBPUSD,H1) Alert: Trade by subscription: market entry ORDER_TYPE_BUY 0.01 GBPUSD - Successful
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
Mục cuối cùng trong các mục trên cho thấy một giao dịch thành công trên GBPUSD. Trên tab giao dịch của tài khoản, 2 vị thế nên được hiển thị.
Sau một thời gian, chúng ta đóng vị thế EURUSD, và vị thế GBPUSD sẽ tự động đóng.
(EURUSD,H1) TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 0.99881, #=1461315206, P=1461313378
(EURUSD,H1) DONE, D=1439025490, #=1461315206, V=0.01, @ 0.99881, Bid=0.99881, Ask=0.99881, Req=4
(EURUSD,H1) {"req" : {"a" : "TRADE_ACTION_DEAL", "s" : "EURUSD", "t" : "ORDER_TYPE_SELL", "v" : 0.01,
» "f" : "ORDER_FILLING_FOK", "p" : 0.99881, "o" : 1461315206, "q" : 1461313378}, "res" : {"code" : 10009,
» "d" : 1439025490, "o" : 1461315206, "v" : 0.01, "p" : 0.99881, "b" : 0.99881, "a" : 0.99881},
» "deal" : {"d" : 1439025490, "o" : 1461315206, "t" : "2022.09.19 16:46:52", "tmsc" : 1663606012990,
» "type" : "DEAL_TYPE_SELL", "entry" : "DEAL_ENTRY_OUT", "pid" : 1461313378, "r" : "DEAL_REASON_CLIENT",
» "v" : 0.01, "p" : 0.99881, "m" : -0.05, "s" : "EURUSD"}, "pos" : {"n" : 0}}
2
3
4
5
6
7
8
Nếu giao dịch lần đầu tiên có loại DEAL_ENTRY_IN, thì bây giờ nó là DEAL_ENTRY_OUT. Cảnh báo xác nhận việc nhận được thông điệp và đóng thành công vị thế sao chép.
(GBPUSD,H1) Alert: {"origin":"publisher PUB_ID_001", "msg":{"req" : {"a" : "TRADE_ACTION_DEAL",
» "s" : "EURUSD", "t" : "ORDER_TYPE_SELL", "v" : 0.01, "f" : "ORDER_FILLING_FOK", "p" : 0.99881,
» "o" : 1461315206, "q" : 1461313378}, "res" : {"code" : 10009, "d" : 1439025490, "o" : 1461315206,
» "v" : 0.01, "p" : 0.99881, "b" : 0.99881, "a" : 0.99881}, "deal" : {"d" : 1439025490,
» "o" : 1461315206, "t" : "2022.09.19 16:46:52", "tmsc" : 1663606012990, "type" : "DEAL_TYPE_SELL",
» "entry" : "DEAL_ENTRY_OUT", "pid" : 1461313378, "r" : "DEAL_REASON_CLIENT", "v" : 0.01,
» "p" : 0.99881, "m" : -0.05, "s" : "EURUSD"}, "pos" : {"n" : 0}}}
...
(GBPUSD,H1) Alert: Trade by subscription: market exit ORDER_TYPE_SELL 0.01 GBPUSD - Successful
2
3
4
5
6
7
8
9
Cuối cùng, bên cạnh Expert Advisor wstradecopier.mq5
, chúng ta tạo một tệp dự án wstradecopier.mqproj
để thêm mô tả và các tệp máy chủ cần thiết vào đó (trong thư mục cũ MQL5/Experts/p7/MQL5Book/Web/
).
Tóm lại: chúng ta đã tổ chức một hệ thống đa người dùng, có thể mở rộng về mặt kỹ thuật để trao đổi thông tin giao dịch qua máy chủ socket. Do các đặc điểm kỹ thuật của web sockets (kết nối mở vĩnh viễn), triển khai này của dịch vụ tín hiệu phù hợp hơn cho giao dịch ngắn hạn và tần suất cao, cũng như để kiểm soát các tình huống chênh lệch trong báo giá.
Việc giải quyết vấn đề đòi hỏi kết hợp nhiều chương trình trên các nền tảng khác nhau và kết nối một số lượng lớn các phụ thuộc, điều này thường đặc trưng cho việc chuyển sang cấp độ dự án. Môi trường phát triển cũng được mở rộng, vượt ra ngoài trình biên dịch và trình chỉnh sửa mã nguồn. Đặc biệt, sự hiện diện trong dự án của phần máy khách hoặc máy chủ thường liên quan đến công việc của các lập trình viên khác nhau chịu trách nhiệm cho chúng. Trong trường hợp này, các dự án chia sẻ trên đám mây và có kiểm soát phiên bản trở nên không thể thiếu.
Xin lưu ý rằng khi phát triển một dự án trong thư mục MQL5/Shared Projects
qua MetaEditor, các tệp tiêu đề từ thư mục tiêu chuẩn MQL5/Include
không được bao gồm trong kho lưu trữ chia sẻ. Mặt khác, việc tạo một thư mục chuyên dụng Include
bên trong dự án của bạn và chuyển các tệp mqh tiêu chuẩn cần thiết vào đó sẽ dẫn đến việc sao chép thông tin và có thể có sự không nhất quán trong các phiên bản của tệp tiêu đề. Hành vi này có thể sẽ được cải thiện trong MetaEditor.
Một điểm khác cho các dự án công cộng là nhu cầu quản lý người dùng và xác thực họ. Trong ví dụ cuối cùng của chúng ta, vấn đề này chỉ được xác định nhưng chưa được triển khai. Tuy nhiên, trang web mql5.com cung cấp một giải pháp sẵn có dựa trên giao thức OAuth nổi tiếng. Bất kỳ ai có tài khoản mql5.com đều có thể làm quen với nguyên tắc của OAuth và cấu hình nó cho dịch vụ web của họ: chỉ cần tìm phần Applications
(liên kết trông giống như https://www.mql5.com/en/users/<login>/apps
) trong hồ sơ của bạn. Bằng cách đăng ký một dịch vụ web trong các ứng dụng mql5.com, bạn sẽ có thể xác thực người dùng thông qua trang web mql5.com.