@mautriz/mt5 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bench.ts +67 -0
- package/dist/index.d.ts +164 -0
- package/dist/index.js +257 -0
- package/dist/index.js.map +1 -0
- package/dist/mt5.test.d.ts +1 -0
- package/dist/mt5.test.js +155 -0
- package/dist/mt5.test.js.map +1 -0
- package/index.ts +477 -0
- package/mt5.test.ts +209 -0
- package/package.json +23 -0
- package/script.ts +15 -0
- package/tsconfig.json +17 -0
package/bench.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { MT5Client } from "./index";
|
|
2
|
+
|
|
3
|
+
const URL = process.argv[2] || "ws://localhost:8080";
|
|
4
|
+
const SYMBOL = process.argv[3] || "BTCUSD";
|
|
5
|
+
const NUM_CLIENTS = parseInt(process.argv[4] || "1");
|
|
6
|
+
const INTERVAL = 1_000; // ms
|
|
7
|
+
|
|
8
|
+
let count = 0;
|
|
9
|
+
let errors = 0;
|
|
10
|
+
let totalMs = 0;
|
|
11
|
+
let minMs = Infinity;
|
|
12
|
+
let maxMs = 0;
|
|
13
|
+
|
|
14
|
+
async function run() {
|
|
15
|
+
const clients = await Promise.all(
|
|
16
|
+
Array.from({ length: NUM_CLIENTS }, async () => {
|
|
17
|
+
const c = new MT5Client(URL, { timeout: 10_000 });
|
|
18
|
+
await c.connect();
|
|
19
|
+
return c;
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
console.log(
|
|
24
|
+
`${NUM_CLIENTS} clients connected. Polling ${SYMBOL} every ${INTERVAL}ms — Ctrl+C to stop\n`,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const interval = setInterval(async () => {
|
|
28
|
+
const start = performance.now();
|
|
29
|
+
try {
|
|
30
|
+
// All clients request simultaneously
|
|
31
|
+
const ticks = await Promise.all(
|
|
32
|
+
clients.map((c) => c.getSymbolTick(SYMBOL)),
|
|
33
|
+
);
|
|
34
|
+
const elapsed = performance.now() - start;
|
|
35
|
+
count += NUM_CLIENTS;
|
|
36
|
+
totalMs += elapsed;
|
|
37
|
+
if (elapsed < minMs) minMs = elapsed;
|
|
38
|
+
if (elapsed > maxMs) maxMs = elapsed;
|
|
39
|
+
const tick = ticks[0];
|
|
40
|
+
process.stdout.write(
|
|
41
|
+
`\r[${count}] bid=${tick.bid} ask=${tick.ask} ${elapsed.toFixed(1)}ms (avg=${(totalMs / Math.ceil(count / NUM_CLIENTS)).toFixed(1)} min=${minMs.toFixed(1)} max=${maxMs.toFixed(1)})`,
|
|
42
|
+
);
|
|
43
|
+
} catch (e: any) {
|
|
44
|
+
errors++;
|
|
45
|
+
process.stdout.write(`\r[${count}] ERROR: ${e.message}`);
|
|
46
|
+
}
|
|
47
|
+
}, INTERVAL);
|
|
48
|
+
|
|
49
|
+
process.on("SIGINT", () => {
|
|
50
|
+
clearInterval(interval);
|
|
51
|
+
const batches = Math.ceil(count / NUM_CLIENTS);
|
|
52
|
+
console.log(`\n\n── Results ──`);
|
|
53
|
+
console.log(` Clients: ${NUM_CLIENTS}`);
|
|
54
|
+
console.log(` Requests: ${count} (${batches} batches of ${NUM_CLIENTS})`);
|
|
55
|
+
console.log(` Errors: ${errors}`);
|
|
56
|
+
console.log(` Avg batch: ${(totalMs / batches).toFixed(1)}ms`);
|
|
57
|
+
console.log(` Min batch: ${minMs.toFixed(1)}ms`);
|
|
58
|
+
console.log(` Max batch: ${maxMs.toFixed(1)}ms`);
|
|
59
|
+
for (const c of clients) c.disconnect();
|
|
60
|
+
process.exit(0);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
run().catch((err) => {
|
|
65
|
+
console.error("Fatal:", err.message);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export interface AccountInfo {
|
|
2
|
+
balance: number;
|
|
3
|
+
equity: number;
|
|
4
|
+
margin: number;
|
|
5
|
+
free_margin: number;
|
|
6
|
+
profit: number;
|
|
7
|
+
leverage: number;
|
|
8
|
+
currency: string;
|
|
9
|
+
name: string;
|
|
10
|
+
number: number;
|
|
11
|
+
server: string;
|
|
12
|
+
company: string;
|
|
13
|
+
}
|
|
14
|
+
export interface Position {
|
|
15
|
+
ticket: number;
|
|
16
|
+
symbol: string;
|
|
17
|
+
type: "buy" | "sell";
|
|
18
|
+
volume: number;
|
|
19
|
+
open_price: number;
|
|
20
|
+
sl: number;
|
|
21
|
+
tp: number;
|
|
22
|
+
profit: number;
|
|
23
|
+
open_time: string;
|
|
24
|
+
}
|
|
25
|
+
export type PendingOrderType = "buy_limit" | "sell_limit" | "buy_stop" | "sell_stop" | "unknown";
|
|
26
|
+
export interface PendingOrder {
|
|
27
|
+
ticket: number;
|
|
28
|
+
symbol: string;
|
|
29
|
+
type: PendingOrderType;
|
|
30
|
+
volume: number;
|
|
31
|
+
price: number;
|
|
32
|
+
sl: number;
|
|
33
|
+
tp: number;
|
|
34
|
+
}
|
|
35
|
+
export type DealType = "buy" | "sell" | "other";
|
|
36
|
+
export type DealEntry = "in" | "out" | "inout" | "unknown";
|
|
37
|
+
export interface Deal {
|
|
38
|
+
ticket: number;
|
|
39
|
+
symbol: string;
|
|
40
|
+
type: DealType;
|
|
41
|
+
entry: DealEntry;
|
|
42
|
+
volume: number;
|
|
43
|
+
price: number;
|
|
44
|
+
profit: number;
|
|
45
|
+
commission: number;
|
|
46
|
+
swap: number;
|
|
47
|
+
time: string;
|
|
48
|
+
}
|
|
49
|
+
export interface SymbolTick {
|
|
50
|
+
symbol: string;
|
|
51
|
+
bid: number;
|
|
52
|
+
ask: number;
|
|
53
|
+
last: number;
|
|
54
|
+
volume: number;
|
|
55
|
+
time: string;
|
|
56
|
+
}
|
|
57
|
+
export type TradeMode = "disabled" | "longonly" | "shortonly" | "closeonly" | "full" | "unknown";
|
|
58
|
+
export interface TimeOffset {
|
|
59
|
+
offset_seconds: number;
|
|
60
|
+
server_time: string;
|
|
61
|
+
gmt_time: string;
|
|
62
|
+
}
|
|
63
|
+
export interface SymbolDetails {
|
|
64
|
+
symbol: string;
|
|
65
|
+
description: string;
|
|
66
|
+
digits: number;
|
|
67
|
+
point: number;
|
|
68
|
+
spread: number;
|
|
69
|
+
trade_mode: TradeMode;
|
|
70
|
+
volume_min: number;
|
|
71
|
+
volume_max: number;
|
|
72
|
+
volume_step: number;
|
|
73
|
+
trade_contract_size: number;
|
|
74
|
+
filling_mode: number;
|
|
75
|
+
bid: number;
|
|
76
|
+
ask: number;
|
|
77
|
+
}
|
|
78
|
+
export type OrderType = "buy" | "sell" | "buy_limit" | "sell_limit" | "buy_stop" | "sell_stop";
|
|
79
|
+
export interface OpenOrderParams {
|
|
80
|
+
type: OrderType;
|
|
81
|
+
symbol: string;
|
|
82
|
+
volume: number;
|
|
83
|
+
price?: number;
|
|
84
|
+
sl?: number;
|
|
85
|
+
tp?: number;
|
|
86
|
+
}
|
|
87
|
+
export interface CloseOrderParams {
|
|
88
|
+
ticket: number;
|
|
89
|
+
volume?: number;
|
|
90
|
+
}
|
|
91
|
+
export interface ModifyOrderParams {
|
|
92
|
+
ticket: number;
|
|
93
|
+
sl?: number;
|
|
94
|
+
tp?: number;
|
|
95
|
+
price?: number;
|
|
96
|
+
}
|
|
97
|
+
export declare class MT5Error extends Error {
|
|
98
|
+
readonly retcode: number | undefined;
|
|
99
|
+
constructor(message: string, retcode?: number);
|
|
100
|
+
}
|
|
101
|
+
export interface HelloMessage {
|
|
102
|
+
type: "hello";
|
|
103
|
+
ea: string;
|
|
104
|
+
symbol: string;
|
|
105
|
+
[key: string]: unknown;
|
|
106
|
+
}
|
|
107
|
+
export interface TickMessage {
|
|
108
|
+
type: "tick";
|
|
109
|
+
symbol: string;
|
|
110
|
+
bid: number;
|
|
111
|
+
ask: number;
|
|
112
|
+
[key: string]: unknown;
|
|
113
|
+
}
|
|
114
|
+
export interface PushMessage {
|
|
115
|
+
type: string;
|
|
116
|
+
[key: string]: unknown;
|
|
117
|
+
}
|
|
118
|
+
type PushEventMap = {
|
|
119
|
+
hello: HelloMessage;
|
|
120
|
+
tick: TickMessage;
|
|
121
|
+
message: PushMessage;
|
|
122
|
+
connected: undefined;
|
|
123
|
+
disconnected: {
|
|
124
|
+
code: number;
|
|
125
|
+
reason: string;
|
|
126
|
+
};
|
|
127
|
+
error: Event;
|
|
128
|
+
};
|
|
129
|
+
type EventCallback<T> = T extends undefined ? () => void : (data: T) => void;
|
|
130
|
+
export declare class MT5Client {
|
|
131
|
+
private readonly url;
|
|
132
|
+
private ws;
|
|
133
|
+
private reqCounter;
|
|
134
|
+
private pending;
|
|
135
|
+
private listeners;
|
|
136
|
+
private timeoutMs;
|
|
137
|
+
constructor(url: string, options?: {
|
|
138
|
+
timeout?: number;
|
|
139
|
+
});
|
|
140
|
+
connect(): Promise<void>;
|
|
141
|
+
disconnect(): void;
|
|
142
|
+
get connected(): boolean;
|
|
143
|
+
on<K extends keyof PushEventMap>(event: K, callback: EventCallback<PushEventMap[K]>): this;
|
|
144
|
+
off<K extends keyof PushEventMap>(event: K, callback: EventCallback<PushEventMap[K]>): this;
|
|
145
|
+
private emit;
|
|
146
|
+
getAccount(): Promise<AccountInfo>;
|
|
147
|
+
openOrder(params: OpenOrderParams): Promise<{
|
|
148
|
+
ticket: number;
|
|
149
|
+
}>;
|
|
150
|
+
closeOrder(params: CloseOrderParams): Promise<{
|
|
151
|
+
ticket: number;
|
|
152
|
+
}>;
|
|
153
|
+
modifyOrder(params: ModifyOrderParams): Promise<void>;
|
|
154
|
+
getPositions(): Promise<Position[]>;
|
|
155
|
+
getOrders(): Promise<PendingOrder[]>;
|
|
156
|
+
getDeals(days?: number): Promise<Deal[]>;
|
|
157
|
+
getSymbolTick(symbol: string): Promise<SymbolTick>;
|
|
158
|
+
getSymbolDetails(symbol: string): Promise<SymbolDetails>;
|
|
159
|
+
getTimeOffset(): Promise<TimeOffset>;
|
|
160
|
+
private nextId;
|
|
161
|
+
private request;
|
|
162
|
+
private setupHandlers;
|
|
163
|
+
}
|
|
164
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// ── Response types ──────────────────────────────────────────────────
|
|
2
|
+
// ── Error type ──────────────────────────────────────────────────────
|
|
3
|
+
export class MT5Error extends Error {
|
|
4
|
+
constructor(message, retcode) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "MT5Error";
|
|
7
|
+
this.retcode = retcode;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
// ── Client ──────────────────────────────────────────────────────────
|
|
11
|
+
export class MT5Client {
|
|
12
|
+
constructor(url, options) {
|
|
13
|
+
this.ws = null;
|
|
14
|
+
this.reqCounter = 0;
|
|
15
|
+
this.pending = new Map();
|
|
16
|
+
this.listeners = new Map();
|
|
17
|
+
this.url = url;
|
|
18
|
+
this.timeoutMs = options?.timeout ?? 30000;
|
|
19
|
+
}
|
|
20
|
+
// ── Connection ──────────────────────────────────────────────────
|
|
21
|
+
connect() {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
24
|
+
resolve();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const ws = new WebSocket(this.url);
|
|
28
|
+
this.ws = ws;
|
|
29
|
+
const onOpen = () => {
|
|
30
|
+
cleanup();
|
|
31
|
+
this.setupHandlers(ws);
|
|
32
|
+
this.emit("connected", undefined);
|
|
33
|
+
resolve();
|
|
34
|
+
};
|
|
35
|
+
const onError = (ev) => {
|
|
36
|
+
cleanup();
|
|
37
|
+
reject(new MT5Error("WebSocket connection failed"));
|
|
38
|
+
};
|
|
39
|
+
const onClose = () => {
|
|
40
|
+
cleanup();
|
|
41
|
+
reject(new MT5Error("WebSocket closed before connection was established"));
|
|
42
|
+
};
|
|
43
|
+
const cleanup = () => {
|
|
44
|
+
ws.removeEventListener("open", onOpen);
|
|
45
|
+
ws.removeEventListener("error", onError);
|
|
46
|
+
ws.removeEventListener("close", onClose);
|
|
47
|
+
};
|
|
48
|
+
ws.addEventListener("open", onOpen);
|
|
49
|
+
ws.addEventListener("error", onError);
|
|
50
|
+
ws.addEventListener("close", onClose);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
disconnect() {
|
|
54
|
+
if (this.ws) {
|
|
55
|
+
this.ws.close();
|
|
56
|
+
this.ws = null;
|
|
57
|
+
}
|
|
58
|
+
for (const [, req] of this.pending) {
|
|
59
|
+
clearTimeout(req.timer);
|
|
60
|
+
req.reject(new MT5Error("Disconnected"));
|
|
61
|
+
}
|
|
62
|
+
this.pending.clear();
|
|
63
|
+
}
|
|
64
|
+
get connected() {
|
|
65
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
66
|
+
}
|
|
67
|
+
// ── Event emitter ───────────────────────────────────────────────
|
|
68
|
+
on(event, callback) {
|
|
69
|
+
let set = this.listeners.get(event);
|
|
70
|
+
if (!set) {
|
|
71
|
+
set = new Set();
|
|
72
|
+
this.listeners.set(event, set);
|
|
73
|
+
}
|
|
74
|
+
set.add(callback);
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
off(event, callback) {
|
|
78
|
+
const set = this.listeners.get(event);
|
|
79
|
+
if (set) {
|
|
80
|
+
set.delete(callback);
|
|
81
|
+
if (set.size === 0)
|
|
82
|
+
this.listeners.delete(event);
|
|
83
|
+
}
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
emit(event, data) {
|
|
87
|
+
const set = this.listeners.get(event);
|
|
88
|
+
if (set) {
|
|
89
|
+
for (const cb of set) {
|
|
90
|
+
cb(data);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ── API methods ─────────────────────────────────────────────────
|
|
95
|
+
async getAccount() {
|
|
96
|
+
const res = await this.request({ action: "get_account" });
|
|
97
|
+
return {
|
|
98
|
+
balance: res.balance,
|
|
99
|
+
equity: res.equity,
|
|
100
|
+
margin: res.margin,
|
|
101
|
+
free_margin: res.free_margin,
|
|
102
|
+
profit: res.profit,
|
|
103
|
+
leverage: res.leverage,
|
|
104
|
+
currency: res.currency,
|
|
105
|
+
name: res.name,
|
|
106
|
+
number: res.number,
|
|
107
|
+
server: res.server,
|
|
108
|
+
company: res.company,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async openOrder(params) {
|
|
112
|
+
const res = await this.request({
|
|
113
|
+
action: "open_order",
|
|
114
|
+
type: params.type,
|
|
115
|
+
symbol: params.symbol,
|
|
116
|
+
volume: params.volume,
|
|
117
|
+
...(params.price !== undefined && { price: params.price }),
|
|
118
|
+
...(params.sl !== undefined && { sl: params.sl }),
|
|
119
|
+
...(params.tp !== undefined && { tp: params.tp }),
|
|
120
|
+
});
|
|
121
|
+
return { ticket: res.ticket };
|
|
122
|
+
}
|
|
123
|
+
async closeOrder(params) {
|
|
124
|
+
const res = await this.request({
|
|
125
|
+
action: "close_order",
|
|
126
|
+
ticket: params.ticket,
|
|
127
|
+
...(params.volume !== undefined && { volume: params.volume }),
|
|
128
|
+
});
|
|
129
|
+
return { ticket: res.ticket };
|
|
130
|
+
}
|
|
131
|
+
async modifyOrder(params) {
|
|
132
|
+
await this.request({
|
|
133
|
+
action: "modify_order",
|
|
134
|
+
ticket: params.ticket,
|
|
135
|
+
...(params.sl !== undefined && { sl: params.sl }),
|
|
136
|
+
...(params.tp !== undefined && { tp: params.tp }),
|
|
137
|
+
...(params.price !== undefined && { price: params.price }),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async getPositions() {
|
|
141
|
+
const res = await this.request({ action: "get_positions" });
|
|
142
|
+
return res.positions;
|
|
143
|
+
}
|
|
144
|
+
async getOrders() {
|
|
145
|
+
const res = await this.request({ action: "get_orders" });
|
|
146
|
+
return res.orders;
|
|
147
|
+
}
|
|
148
|
+
async getDeals(days) {
|
|
149
|
+
const res = await this.request({
|
|
150
|
+
action: "get_deals",
|
|
151
|
+
...(days !== undefined && { days }),
|
|
152
|
+
});
|
|
153
|
+
return res.deals;
|
|
154
|
+
}
|
|
155
|
+
async getSymbolTick(symbol) {
|
|
156
|
+
const res = await this.request({ action: "get_symbol_tick", symbol });
|
|
157
|
+
return {
|
|
158
|
+
symbol: res.symbol,
|
|
159
|
+
bid: res.bid,
|
|
160
|
+
ask: res.ask,
|
|
161
|
+
last: res.last,
|
|
162
|
+
volume: res.volume,
|
|
163
|
+
time: res.time,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async getSymbolDetails(symbol) {
|
|
167
|
+
const res = await this.request({ action: "get_symbol_details", symbol });
|
|
168
|
+
return {
|
|
169
|
+
symbol: res.symbol,
|
|
170
|
+
description: res.description,
|
|
171
|
+
digits: res.digits,
|
|
172
|
+
point: res.point,
|
|
173
|
+
spread: res.spread,
|
|
174
|
+
trade_mode: res.trade_mode,
|
|
175
|
+
volume_min: res.volume_min,
|
|
176
|
+
volume_max: res.volume_max,
|
|
177
|
+
volume_step: res.volume_step,
|
|
178
|
+
trade_contract_size: res.trade_contract_size,
|
|
179
|
+
filling_mode: res.filling_mode,
|
|
180
|
+
bid: res.bid,
|
|
181
|
+
ask: res.ask,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async getTimeOffset() {
|
|
185
|
+
const res = await this.request({ action: "get_time_offset" });
|
|
186
|
+
return {
|
|
187
|
+
offset_seconds: res.offset_seconds,
|
|
188
|
+
server_time: res.server_time,
|
|
189
|
+
gmt_time: res.gmt_time,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// ── Internals ───────────────────────────────────────────────────
|
|
193
|
+
nextId() {
|
|
194
|
+
return `req-${++this.reqCounter}`;
|
|
195
|
+
}
|
|
196
|
+
request(payload) {
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
199
|
+
reject(new MT5Error("Not connected"));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const id = this.nextId();
|
|
203
|
+
const timer = setTimeout(() => {
|
|
204
|
+
this.pending.delete(id);
|
|
205
|
+
reject(new MT5Error(`Request ${id} timed out after ${this.timeoutMs}ms`));
|
|
206
|
+
}, this.timeoutMs);
|
|
207
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
208
|
+
this.ws.send(JSON.stringify({ ...payload, id }));
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
setupHandlers(ws) {
|
|
212
|
+
ws.addEventListener("message", (ev) => {
|
|
213
|
+
let msg;
|
|
214
|
+
try {
|
|
215
|
+
msg = JSON.parse(typeof ev.data === "string" ? ev.data : String(ev.data));
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// Response to a request (has an "id" field matching a pending request)
|
|
221
|
+
if (msg.id && this.pending.has(msg.id)) {
|
|
222
|
+
const req = this.pending.get(msg.id);
|
|
223
|
+
this.pending.delete(msg.id);
|
|
224
|
+
clearTimeout(req.timer);
|
|
225
|
+
if (msg.status === "ok") {
|
|
226
|
+
req.resolve(msg);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
req.reject(new MT5Error(msg.error || "Unknown error", msg.retcode));
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
// Push message from EA (no matching request id)
|
|
234
|
+
if (msg.type) {
|
|
235
|
+
this.emit(msg.type, msg);
|
|
236
|
+
// Also emit on the generic "message" event
|
|
237
|
+
this.emit("message", msg);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
ws.addEventListener("close", (ev) => {
|
|
241
|
+
this.emit("disconnected", {
|
|
242
|
+
code: ev.code,
|
|
243
|
+
reason: ev.reason,
|
|
244
|
+
});
|
|
245
|
+
for (const [, req] of this.pending) {
|
|
246
|
+
clearTimeout(req.timer);
|
|
247
|
+
req.reject(new MT5Error("Connection closed"));
|
|
248
|
+
}
|
|
249
|
+
this.pending.clear();
|
|
250
|
+
this.ws = null;
|
|
251
|
+
});
|
|
252
|
+
ws.addEventListener("error", (ev) => {
|
|
253
|
+
this.emit("error", ev);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAmIvE,uEAAuE;AAEvE,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGjC,YAAY,OAAe,EAAE,OAAgB;QAC3C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AA2CD,uEAAuE;AAEvE,MAAM,OAAO,SAAS;IAQpB,YAAY,GAAW,EAAE,OAA8B;QAN/C,OAAE,GAAqB,IAAI,CAAC;QAC5B,eAAU,GAAG,CAAC,CAAC;QACf,YAAO,GAAG,IAAI,GAAG,EAA0B,CAAC;QAC5C,cAAS,GAAG,IAAI,GAAG,EAAyB,CAAC;QAInD,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,SAAS,GAAG,OAAO,EAAE,OAAO,IAAI,KAAM,CAAC;IAC9C,CAAC;IAED,mEAAmE;IAEnE,OAAO;QACL,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;gBACrD,OAAO,EAAE,CAAC;gBACV,OAAO;YACT,CAAC;YAED,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;YAEb,MAAM,MAAM,GAAG,GAAG,EAAE;gBAClB,OAAO,EAAE,CAAC;gBACV,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;gBACvB,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;gBAClC,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC;YAEF,MAAM,OAAO,GAAG,CAAC,EAAS,EAAE,EAAE;gBAC5B,OAAO,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,QAAQ,CAAC,6BAA6B,CAAC,CAAC,CAAC;YACtD,CAAC,CAAC;YAEF,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,OAAO,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,QAAQ,CAAC,oDAAoD,CAAC,CAAC,CAAC;YAC7E,CAAC,CAAC;YAEF,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,EAAE,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBACvC,EAAE,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACzC,EAAE,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC3C,CAAC,CAAC;YAEF,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACpC,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACtC,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,UAAU;QACR,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,KAAK,MAAM,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACnC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACxB,GAAG,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,CAAC;IACnE,CAAC;IAED,mEAAmE;IAEnE,EAAE,CACA,KAAQ,EACR,QAAwC;QAExC,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACjC,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,GAAG,CACD,KAAQ,EACR,QAAwC;QAExC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,GAAG,EAAE,CAAC;YACR,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACrB,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;gBAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,IAAI,CACV,KAAQ,EACR,IAAqB;QAErB,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,GAAG,EAAE,CAAC;YACR,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;gBACpB,EAAe,CAAC,IAAI,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IAED,mEAAmE;IAEnE,KAAK,CAAC,UAAU;QACd,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;QAC1D,OAAO;YACL,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,MAAuB;QACrC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;YAC7B,MAAM,EAAE,YAAY;YACpB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YAC1D,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,SAAS,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;YACjD,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,SAAS,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;SAClD,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAwB;QACvC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;YAC7B,MAAM,EAAE,aAAa;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;SAC9D,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAyB;QACzC,MAAM,IAAI,CAAC,OAAO,CAAC;YACjB,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,SAAS,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;YACjD,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,SAAS,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;YACjD,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;SAC3D,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;QAC5D,OAAO,GAAG,CAAC,SAAS,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,SAAS;QACb,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;QACzD,OAAO,GAAG,CAAC,MAAM,CAAC;IACpB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAa;QAC1B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;YAC7B,MAAM,EAAE,WAAW;YACnB,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC;SACpC,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,KAAK,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MAAc;QAChC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,CAAC,CAAC;QACtE,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,MAAc;QACnC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,CAAC,CAAC;QACzE,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,mBAAmB,EAAE,GAAG,CAAC,mBAAmB;YAC5C,YAAY,EAAE,GAAG,CAAC,YAAY;YAC9B,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,GAAG,EAAE,GAAG,CAAC,GAAG;SACb,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAC9D,OAAO;YACL,cAAc,EAAE,GAAG,CAAC,cAAc;YAClC,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,QAAQ,EAAE,GAAG,CAAC,QAAQ;SACvB,CAAC;IACJ,CAAC;IAED,mEAAmE;IAE3D,MAAM;QACZ,OAAO,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;IACpC,CAAC;IAEO,OAAO,CAAC,OAAgC;QAC9C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;gBACtD,MAAM,CAAC,IAAI,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;YAED,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACxB,MAAM,CAAC,IAAI,QAAQ,CAAC,WAAW,EAAE,oBAAoB,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;YAC5E,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAEnB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;YACjD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,aAAa,CAAC,EAAa;QACjC,EAAE,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,EAAgB,EAAE,EAAE;YAClD,IAAI,GAAQ,CAAC;YACb,IAAI,CAAC;gBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5E,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO;YACT,CAAC;YAED,uEAAuE;YACvE,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBACvC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAE,CAAC;gBACtC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC5B,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAExB,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;oBACxB,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACnB,CAAC;qBAAM,CAAC;oBACN,GAAG,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,IAAI,eAAe,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;gBACtE,CAAC;gBACD,OAAO;YACT,CAAC;YAED,gDAAgD;YAChD,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;gBACb,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAA0B,EAAE,GAAU,CAAC,CAAC;gBACtD,2CAA2C;gBAC3C,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAkB,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,EAAc,EAAE,EAAE;YAC9C,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE;gBACxB,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,MAAM,EAAE,EAAE,CAAC,MAAM;aAClB,CAAC,CAAC;YACH,KAAK,MAAM,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACnC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACxB,GAAG,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC;YAChD,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACrB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;QACjB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,EAAS,EAAE,EAAE;YACzC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/mt5.test.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { MT5Client } from "./index";
|
|
3
|
+
const SYMBOL = process.env.SYMBOL || "BTCUSD";
|
|
4
|
+
const URL = process.env.WS_URL || "ws://localhost:8080";
|
|
5
|
+
const mt5 = new MT5Client(URL, { timeout: 300000 });
|
|
6
|
+
let buyTicket;
|
|
7
|
+
let sellTicket;
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
await mt5.connect();
|
|
10
|
+
});
|
|
11
|
+
afterAll(() => {
|
|
12
|
+
mt5.disconnect();
|
|
13
|
+
});
|
|
14
|
+
describe("Account", () => {
|
|
15
|
+
it("should return account info", async () => {
|
|
16
|
+
const account = await mt5.getAccount();
|
|
17
|
+
expect(account.balance).toBeTypeOf("number");
|
|
18
|
+
expect(account.equity).toBeTypeOf("number");
|
|
19
|
+
expect(account.leverage).toBeGreaterThan(0);
|
|
20
|
+
console.log(` balance=${account.balance} equity=${account.equity} leverage=${account.leverage}`);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe("Symbol", () => {
|
|
24
|
+
it("should return symbol details", async () => {
|
|
25
|
+
const details = await mt5.getSymbolDetails(SYMBOL);
|
|
26
|
+
expect(details.symbol).toBe(SYMBOL);
|
|
27
|
+
expect(details.trade_mode).toBe("full");
|
|
28
|
+
expect(details.volume_min).toBeGreaterThan(0);
|
|
29
|
+
console.log(` ${details.symbol}: digits=${details.digits} spread=${details.spread} filling_mode=${details.filling_mode}`);
|
|
30
|
+
console.log(` volume: min=${details.volume_min} max=${details.volume_max} step=${details.volume_step}`);
|
|
31
|
+
});
|
|
32
|
+
it("should return symbol tick with bid/ask", async () => {
|
|
33
|
+
let tick;
|
|
34
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
35
|
+
tick = await mt5.getSymbolTick(SYMBOL);
|
|
36
|
+
if (tick.bid > 0)
|
|
37
|
+
break;
|
|
38
|
+
console.log(` attempt ${attempt}/5 — no quotes yet, waiting 1s...`);
|
|
39
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
40
|
+
}
|
|
41
|
+
expect(tick.bid).toBeGreaterThan(0);
|
|
42
|
+
expect(tick.ask).toBeGreaterThan(0);
|
|
43
|
+
console.log(` bid=${tick.bid} ask=${tick.ask} last=${tick.last}`);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("Orders", () => {
|
|
47
|
+
it("should open a buy order", async () => {
|
|
48
|
+
const result = await mt5.openOrder({
|
|
49
|
+
type: "buy",
|
|
50
|
+
symbol: SYMBOL,
|
|
51
|
+
volume: 0.1,
|
|
52
|
+
});
|
|
53
|
+
expect(result.ticket).toBeGreaterThan(0);
|
|
54
|
+
buyTicket = result.ticket;
|
|
55
|
+
console.log(` ticket=${buyTicket}`);
|
|
56
|
+
});
|
|
57
|
+
it("should show the buy in positions", async () => {
|
|
58
|
+
const positions = await mt5.getPositions();
|
|
59
|
+
const our = positions.find((p) => p.ticket === buyTicket);
|
|
60
|
+
expect(our).toBeDefined();
|
|
61
|
+
expect(our.type).toBe("buy");
|
|
62
|
+
expect(our.symbol).toBe(SYMBOL);
|
|
63
|
+
console.log(` found ${positions.length} position(s), ours: ticket=${our.ticket} ${our.type} ${our.volume} ${our.symbol}`);
|
|
64
|
+
});
|
|
65
|
+
it("should open a sell order", async () => {
|
|
66
|
+
const result = await mt5.openOrder({
|
|
67
|
+
type: "sell",
|
|
68
|
+
symbol: SYMBOL,
|
|
69
|
+
volume: 0.01,
|
|
70
|
+
});
|
|
71
|
+
expect(result.ticket).toBeGreaterThan(0);
|
|
72
|
+
sellTicket = result.ticket;
|
|
73
|
+
console.log(` ticket=${sellTicket}`);
|
|
74
|
+
});
|
|
75
|
+
it("should close the buy position", async () => {
|
|
76
|
+
const result = await mt5.closeOrder({ ticket: buyTicket });
|
|
77
|
+
expect(result.ticket).toBeGreaterThan(0);
|
|
78
|
+
});
|
|
79
|
+
it("should close the sell position", async () => {
|
|
80
|
+
const result = await mt5.closeOrder({ ticket: sellTicket });
|
|
81
|
+
expect(result.ticket).toBeGreaterThan(0);
|
|
82
|
+
});
|
|
83
|
+
it("should have no remaining positions for our tickets", async () => {
|
|
84
|
+
const positions = await mt5.getPositions();
|
|
85
|
+
const ours = positions.filter((p) => p.ticket === buyTicket || p.ticket === sellTicket);
|
|
86
|
+
expect(ours).toHaveLength(0);
|
|
87
|
+
console.log(` ${positions.length} position(s) remaining (none are ours)`);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe("Concurrent - Four Clients", () => {
|
|
91
|
+
it("should open and close orders simultaneously from 4 clients", async () => {
|
|
92
|
+
const clients = await Promise.all(Array.from({ length: 3 }, () => {
|
|
93
|
+
const c = new MT5Client(URL, { timeout: 300000 });
|
|
94
|
+
return c.connect().then(() => c);
|
|
95
|
+
}));
|
|
96
|
+
const allClients = [mt5, ...clients];
|
|
97
|
+
// Open 4 orders simultaneously from different clients
|
|
98
|
+
const opens = await Promise.all(allClients.map((c, i) => c.openOrder({
|
|
99
|
+
type: i % 2 === 0 ? "buy" : "sell",
|
|
100
|
+
symbol: SYMBOL,
|
|
101
|
+
volume: 0.01,
|
|
102
|
+
})));
|
|
103
|
+
for (const o of opens) {
|
|
104
|
+
expect(o.ticket).toBeGreaterThan(0);
|
|
105
|
+
}
|
|
106
|
+
console.log(` tickets: ${opens.map((o) => o.ticket).join(", ")}`);
|
|
107
|
+
// Close all 4 simultaneously
|
|
108
|
+
const closes = await Promise.all(allClients.map((c, i) => c.closeOrder({ ticket: opens[i].ticket })));
|
|
109
|
+
for (const c of closes) {
|
|
110
|
+
expect(c.ticket).toBeGreaterThan(0);
|
|
111
|
+
}
|
|
112
|
+
console.log(` all 4 closed simultaneously`);
|
|
113
|
+
for (const c of clients)
|
|
114
|
+
c.disconnect();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe("Concurrent - Same Client", () => {
|
|
118
|
+
it("should open and close 4 positions simultaneously from one client", async () => {
|
|
119
|
+
// Open 4 orders simultaneously
|
|
120
|
+
const opens = await Promise.all(Array.from({ length: 4 }, (_, i) => mt5.openOrder({
|
|
121
|
+
type: i % 2 === 0 ? "buy" : "sell",
|
|
122
|
+
symbol: SYMBOL,
|
|
123
|
+
volume: 0.01,
|
|
124
|
+
})));
|
|
125
|
+
for (const o of opens) {
|
|
126
|
+
expect(o.ticket).toBeGreaterThan(0);
|
|
127
|
+
}
|
|
128
|
+
console.log(` opened: ${opens.map((o) => o.ticket).join(", ")}`);
|
|
129
|
+
// Close all 4 simultaneously
|
|
130
|
+
const closes = await Promise.all(opens.map((o) => mt5.closeOrder({ ticket: o.ticket })));
|
|
131
|
+
for (const c of closes) {
|
|
132
|
+
expect(c.ticket).toBeGreaterThan(0);
|
|
133
|
+
}
|
|
134
|
+
console.log(` all 4 closed simultaneously`);
|
|
135
|
+
// Verify all gone
|
|
136
|
+
const positions = await mt5.getPositions();
|
|
137
|
+
const ours = positions.filter((p) => opens.some((o) => o.ticket === p.ticket));
|
|
138
|
+
expect(ours).toHaveLength(0);
|
|
139
|
+
console.log(` verified: ${positions.length} position(s) remaining`);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe("Deals", () => {
|
|
143
|
+
it("should contain our trades in history", async () => {
|
|
144
|
+
const deals = await mt5.getDeals(1);
|
|
145
|
+
expect(deals.length).toBeGreaterThan(0);
|
|
146
|
+
console.log(` ${deals.length} deal(s) in last 24h`);
|
|
147
|
+
const ourDeals = deals.filter((d) => d.symbol === SYMBOL);
|
|
148
|
+
const entries = ourDeals.filter((d) => d.entry === "in");
|
|
149
|
+
const exits = ourDeals.filter((d) => d.entry === "out");
|
|
150
|
+
console.log(` ${SYMBOL}: ${entries.length} entry deal(s), ${exits.length} exit deal(s)`);
|
|
151
|
+
expect(entries.length).toBeGreaterThanOrEqual(2);
|
|
152
|
+
expect(exits.length).toBeGreaterThanOrEqual(2);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
//# sourceMappingURL=mt5.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mt5.test.js","sourceRoot":"","sources":["../mt5.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,QAAQ,CAAC;AAC9C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,qBAAqB,CAAC;AAExD,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,MAAO,EAAE,CAAC,CAAC;AAErD,IAAI,SAAiB,CAAC;AACtB,IAAI,UAAkB,CAAC;AAEvB,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;AACtB,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,GAAG,EAAE;IACZ,GAAG,CAAC,UAAU,EAAE,CAAC;AACnB,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;IACvB,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,UAAU,EAAE,CAAC;QACvC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CACT,eAAe,OAAO,CAAC,OAAO,WAAW,OAAO,CAAC,MAAM,aAAa,OAAO,CAAC,QAAQ,EAAE,CACvF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,EAAE,CAAC,8BAA8B,EAAE,KAAK,IAAI,EAAE;QAC5C,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACnD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CACT,OAAO,OAAO,CAAC,MAAM,YAAY,OAAO,CAAC,MAAM,WAAW,OAAO,CAAC,MAAM,iBAAiB,OAAO,CAAC,YAAY,EAAE,CAChH,CAAC;QACF,OAAO,CAAC,GAAG,CACT,mBAAmB,OAAO,CAAC,UAAU,QAAQ,OAAO,CAAC,UAAU,SAAS,OAAO,CAAC,WAAW,EAAE,CAC9F,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,IAAI,IAAI,CAAC;QACT,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;YAC9C,IAAI,GAAG,MAAM,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACvC,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC;gBAAE,MAAM;YACxB,OAAO,CAAC,GAAG,CAAC,eAAe,OAAO,mCAAmC,CAAC,CAAC;YACvE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAChD,CAAC;QACD,MAAM,CAAC,IAAK,CAAC,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,IAAK,CAAC,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAK,CAAC,GAAG,QAAQ,IAAK,CAAC,GAAG,SAAS,IAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,EAAE,CAAC,yBAAyB,EAAE,KAAK,IAAI,EAAE;QACvC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC;YACjC,IAAI,EAAE,KAAK;YACX,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG;SACZ,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACzC,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,cAAc,SAAS,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAChD,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1B,MAAM,CAAC,GAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9B,MAAM,CAAC,GAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,CAAC,GAAG,CACT,aAAa,SAAS,CAAC,MAAM,8BAA8B,GAAI,CAAC,MAAM,IAAI,GAAI,CAAC,IAAI,IAAI,GAAI,CAAC,MAAM,IAAI,GAAI,CAAC,MAAM,EAAE,CACpH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC;YACjC,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,IAAI;SACb,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACzC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,cAAc,UAAU,EAAE,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAC3D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAC3B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU,CACzD,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC7B,OAAO,CAAC,GAAG,CACT,OAAO,SAAS,CAAC,MAAM,wCAAwC,CAChE,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE;YAC7B,MAAM,CAAC,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,MAAO,EAAE,CAAC,CAAC;YACnD,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QACnC,CAAC,CAAC,CACH,CAAC;QACF,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC;QAErC,sDAAsD;QACtD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACtB,CAAC,CAAC,SAAS,CAAC;YACV,IAAI,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;YAClC,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,IAAI;SACb,CAAC,CACH,CACF,CAAC;QAEF,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAErE,6BAA6B;QAC7B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CACpE,CAAC;QAEF,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;QAE/C,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,CAAC,CAAC,UAAU,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,kEAAkE,EAAE,KAAK,IAAI,EAAE;QAChF,+BAA+B;QAC/B,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACjC,GAAG,CAAC,SAAS,CAAC;YACZ,IAAI,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;YAClC,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,IAAI;SACb,CAAC,CACH,CACF,CAAC;QAEF,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,eAAe,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEpE,6BAA6B;QAC7B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CACvD,CAAC;QAEF,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;QAE/C,kBAAkB;QAClB,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAClC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,CACzC,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,iBAAiB,SAAS,CAAC,MAAM,wBAAwB,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;IACrB,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,MAAM,sBAAsB,CAAC,CAAC;QAEvD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;QAC1D,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;QACxD,OAAO,CAAC,GAAG,CACT,OAAO,MAAM,KAAK,OAAO,CAAC,MAAM,mBAAmB,KAAK,CAAC,MAAM,eAAe,CAC/E,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|