@danielgroen/dxtrade-api 1.0.7 → 1.0.8
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/README.md +8 -8
- package/dist/index.d.mts +143 -66
- package/dist/index.d.ts +143 -66
- package/dist/index.js +411 -326
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +409 -326
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -7
package/dist/index.js
CHANGED
|
@@ -36,6 +36,8 @@ __export(index_exports, {
|
|
|
36
36
|
DxtradeError: () => DxtradeError,
|
|
37
37
|
ORDER_TYPE: () => ORDER_TYPE,
|
|
38
38
|
SIDE: () => SIDE,
|
|
39
|
+
TIF: () => TIF,
|
|
40
|
+
WS_MESSAGE: () => WS_MESSAGE,
|
|
39
41
|
endpoints: () => endpoints,
|
|
40
42
|
resolveBrokerUrl: () => resolveBrokerUrl
|
|
41
43
|
});
|
|
@@ -43,13 +45,11 @@ module.exports = __toCommonJS(index_exports);
|
|
|
43
45
|
|
|
44
46
|
// src/constants/brokers.ts
|
|
45
47
|
var BROKER = {
|
|
46
|
-
|
|
48
|
+
LARKFUNDING: "https://trade.gooeytrade.com",
|
|
47
49
|
EIGHTCAP: "https://trader.dx-eightcap.com"
|
|
48
50
|
};
|
|
49
51
|
function resolveBrokerUrl(broker, customUrls) {
|
|
50
|
-
if (customUrls?.[broker])
|
|
51
|
-
return customUrls[broker];
|
|
52
|
-
}
|
|
52
|
+
if (customUrls?.[broker]) return customUrls[broker];
|
|
53
53
|
const key = broker.toUpperCase();
|
|
54
54
|
if (BROKER[key]) {
|
|
55
55
|
return BROKER[key];
|
|
@@ -58,6 +58,7 @@ function resolveBrokerUrl(broker, customUrls) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// src/constants/endpoints.ts
|
|
61
|
+
var websocketQuery = `?X-Atmosphere-tracking-id=0&X-Atmosphere-Framework=2.3.2-javascript&X-Atmosphere-Transport=websocket&X-Atmosphere-TrackMessageSize=true&Content-Type=text/x-gwt-rpc;%20charset=UTF-8&X-atmo-protocol=true&sessionState=dx-new&guest-mode=false`;
|
|
61
62
|
var endpoints = {
|
|
62
63
|
login: (base) => `${base}/api/auth/login`,
|
|
63
64
|
switchAccount: (base, id) => `${base}/api/accounts/switch?accountId=${id}`,
|
|
@@ -65,7 +66,7 @@ var endpoints = {
|
|
|
65
66
|
instrumentInfo: (base, symbol, tzOffset) => `${base}/api/instruments/info?symbol=${symbol}&timezoneOffset=${tzOffset}&withExDividends=true`,
|
|
66
67
|
submitOrder: (base) => `${base}/api/orders/single`,
|
|
67
68
|
assessments: (base) => `${base}/api/assessments`,
|
|
68
|
-
websocket: (base) => `wss://${base.split("//")[1]}/client/connector
|
|
69
|
+
websocket: (base) => `wss://${base.split("//")[1]}/client/connector` + websocketQuery
|
|
69
70
|
};
|
|
70
71
|
|
|
71
72
|
// src/constants/enums.ts
|
|
@@ -85,6 +86,24 @@ var ACTION = /* @__PURE__ */ ((ACTION2) => {
|
|
|
85
86
|
ACTION2["CLOSING"] = "CLOSING";
|
|
86
87
|
return ACTION2;
|
|
87
88
|
})(ACTION || {});
|
|
89
|
+
var TIF = /* @__PURE__ */ ((TIF2) => {
|
|
90
|
+
TIF2["GTC"] = "GTC";
|
|
91
|
+
TIF2["DAY"] = "DAY";
|
|
92
|
+
TIF2["GTD"] = "GTD";
|
|
93
|
+
return TIF2;
|
|
94
|
+
})(TIF || {});
|
|
95
|
+
var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
|
|
96
|
+
WS_MESSAGE2["ACCOUNTS"] = "ACCOUNTS";
|
|
97
|
+
WS_MESSAGE2["AVAILABLE_WATCHLISTS"] = "AVAILABLE_WATCHLISTS";
|
|
98
|
+
WS_MESSAGE2["MESSAGE"] = "MESSAGE";
|
|
99
|
+
WS_MESSAGE2["ORDERS"] = "ORDERS";
|
|
100
|
+
WS_MESSAGE2["POSITIONS"] = "POSITIONS";
|
|
101
|
+
WS_MESSAGE2["POSITION_CASH_TRANSFERS"] = "POSITION_CASH_TRANSFERS";
|
|
102
|
+
WS_MESSAGE2["PRIVATE_LAYOUT_NAMES"] = "PRIVATE_LAYOUT_NAMES";
|
|
103
|
+
WS_MESSAGE2["SHARED_PROPERTIES_MESSAGE"] = "SHARED_PROPERTIES_MESSAGE";
|
|
104
|
+
WS_MESSAGE2["USER_LOGIN_INFO"] = "USER_LOGIN_INFO";
|
|
105
|
+
return WS_MESSAGE2;
|
|
106
|
+
})(WS_MESSAGE || {});
|
|
88
107
|
|
|
89
108
|
// src/constants/errors.ts
|
|
90
109
|
var DxtradeError = class extends Error {
|
|
@@ -96,28 +115,30 @@ var DxtradeError = class extends Error {
|
|
|
96
115
|
}
|
|
97
116
|
};
|
|
98
117
|
|
|
99
|
-
// src/
|
|
100
|
-
var
|
|
118
|
+
// src/domains/session/session.ts
|
|
119
|
+
var import_ws = __toESM(require("ws"));
|
|
101
120
|
|
|
102
121
|
// src/utils/cookies.ts
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
var Cookies = class {
|
|
123
|
+
static parse(setCookieHeaders) {
|
|
124
|
+
const cookies = {};
|
|
125
|
+
for (const cookie of setCookieHeaders) {
|
|
126
|
+
const [nameValue] = cookie.split(";");
|
|
127
|
+
const eqIndex = nameValue.indexOf("=");
|
|
128
|
+
if (eqIndex === -1) continue;
|
|
129
|
+
const name = nameValue.slice(0, eqIndex).trim();
|
|
130
|
+
const value = nameValue.slice(eqIndex + 1).trim();
|
|
131
|
+
cookies[name] = value;
|
|
132
|
+
}
|
|
133
|
+
return cookies;
|
|
112
134
|
}
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
135
|
+
static serialize(cookies) {
|
|
136
|
+
return Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
|
|
137
|
+
}
|
|
138
|
+
static merge(existing, incoming) {
|
|
139
|
+
return { ...existing, ...incoming };
|
|
140
|
+
}
|
|
141
|
+
};
|
|
121
142
|
|
|
122
143
|
// src/utils/headers.ts
|
|
123
144
|
function baseHeaders() {
|
|
@@ -156,7 +177,33 @@ async function retryRequest(config, retries = 3) {
|
|
|
156
177
|
}
|
|
157
178
|
|
|
158
179
|
// src/utils/websocket.ts
|
|
159
|
-
var
|
|
180
|
+
var import_fs = require("fs");
|
|
181
|
+
var DEBUG_LOG = "debug.log";
|
|
182
|
+
function shouldLog(msg, debug) {
|
|
183
|
+
if (!debug) return false;
|
|
184
|
+
if (debug === true || debug === "true") return true;
|
|
185
|
+
if (typeof msg === "string") return false;
|
|
186
|
+
const filters = debug.split(",").map((s) => s.trim().toUpperCase());
|
|
187
|
+
return filters.includes(msg.type);
|
|
188
|
+
}
|
|
189
|
+
function debugLog(msg) {
|
|
190
|
+
(0, import_fs.appendFileSync)(DEBUG_LOG, JSON.stringify(msg) + "\n");
|
|
191
|
+
}
|
|
192
|
+
function clearDebugLog() {
|
|
193
|
+
(0, import_fs.writeFileSync)(DEBUG_LOG, "");
|
|
194
|
+
}
|
|
195
|
+
function parseWsData(data) {
|
|
196
|
+
const raw = data.toString();
|
|
197
|
+
const pipeIndex = raw.indexOf("|");
|
|
198
|
+
if (pipeIndex === -1) return raw;
|
|
199
|
+
try {
|
|
200
|
+
return JSON.parse(raw.slice(pipeIndex + 1));
|
|
201
|
+
} catch {
|
|
202
|
+
return raw;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/domains/session/session.ts
|
|
160
207
|
function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
161
208
|
return new Promise((resolve, reject) => {
|
|
162
209
|
const ws = new import_ws.default(wsUrl, { headers: { Cookie: cookieStr } });
|
|
@@ -165,9 +212,10 @@ function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
|
165
212
|
reject(new Error("[dxtrade-api] Handshake timed out"));
|
|
166
213
|
}, timeout);
|
|
167
214
|
ws.on("message", (data) => {
|
|
168
|
-
const
|
|
169
|
-
if (debug)
|
|
170
|
-
if (
|
|
215
|
+
const msg = parseWsData(data);
|
|
216
|
+
if (shouldLog(msg, debug)) debugLog(msg);
|
|
217
|
+
if (typeof msg === "string") return;
|
|
218
|
+
if (msg.type === "POSITIONS" /* POSITIONS */) {
|
|
171
219
|
clearTimeout(timer);
|
|
172
220
|
ws.close();
|
|
173
221
|
resolve();
|
|
@@ -180,338 +228,373 @@ function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
|
180
228
|
});
|
|
181
229
|
});
|
|
182
230
|
}
|
|
183
|
-
function
|
|
184
|
-
|
|
185
|
-
const
|
|
231
|
+
async function login(ctx) {
|
|
232
|
+
try {
|
|
233
|
+
const response = await retryRequest(
|
|
234
|
+
{
|
|
235
|
+
method: "POST",
|
|
236
|
+
url: endpoints.login(ctx.baseUrl),
|
|
237
|
+
data: {
|
|
238
|
+
username: ctx.config.username,
|
|
239
|
+
password: ctx.config.password,
|
|
240
|
+
domain: ctx.config.broker
|
|
241
|
+
},
|
|
242
|
+
headers: { "Content-Type": "application/json" }
|
|
243
|
+
},
|
|
244
|
+
ctx.retries
|
|
245
|
+
);
|
|
246
|
+
if (response.status === 200) {
|
|
247
|
+
const setCookies = response.headers["set-cookie"] ?? [];
|
|
248
|
+
const incoming = Cookies.parse(setCookies);
|
|
249
|
+
ctx.cookies = Cookies.merge(ctx.cookies, incoming);
|
|
250
|
+
ctx.callbacks.onLogin?.();
|
|
251
|
+
} else {
|
|
252
|
+
ctx.throwError("LOGIN_FAILED", `Login failed: ${response.status}`);
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (error instanceof DxtradeError) throw error;
|
|
256
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
257
|
+
ctx.throwError("LOGIN_ERROR", `Login error: ${message}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function fetchCsrf(ctx) {
|
|
261
|
+
try {
|
|
262
|
+
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
263
|
+
const response = await retryRequest(
|
|
264
|
+
{
|
|
265
|
+
method: "GET",
|
|
266
|
+
url: ctx.baseUrl,
|
|
267
|
+
headers: { ...cookieOnlyHeaders(cookieStr), Referer: ctx.baseUrl }
|
|
268
|
+
},
|
|
269
|
+
ctx.retries
|
|
270
|
+
);
|
|
271
|
+
const csrfMatch = response.data?.match(/name="csrf" content="([^"]+)"/);
|
|
272
|
+
if (csrfMatch) {
|
|
273
|
+
ctx.csrf = csrfMatch[1];
|
|
274
|
+
} else {
|
|
275
|
+
ctx.throwError("CSRF_NOT_FOUND", "CSRF token not found");
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
if (error instanceof DxtradeError) throw error;
|
|
279
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
280
|
+
ctx.throwError("CSRF_ERROR", `CSRF fetch error: ${message}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function switchAccount(ctx, accountId) {
|
|
284
|
+
ctx.ensureSession();
|
|
285
|
+
try {
|
|
286
|
+
await retryRequest(
|
|
287
|
+
{
|
|
288
|
+
method: "POST",
|
|
289
|
+
url: endpoints.switchAccount(ctx.baseUrl, accountId),
|
|
290
|
+
headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
|
|
291
|
+
},
|
|
292
|
+
ctx.retries
|
|
293
|
+
);
|
|
294
|
+
ctx.callbacks.onAccountSwitch?.(accountId);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (error instanceof DxtradeError) throw error;
|
|
297
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
298
|
+
ctx.throwError("ACCOUNT_SWITCH_ERROR", `Error switching account: ${message}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async function connect(ctx) {
|
|
302
|
+
await login(ctx);
|
|
303
|
+
await fetchCsrf(ctx);
|
|
304
|
+
if (ctx.debug) clearDebugLog();
|
|
305
|
+
const wsUrl = endpoints.websocket(ctx.baseUrl);
|
|
306
|
+
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
307
|
+
await waitForHandshake(wsUrl, cookieStr, 3e4, ctx.debug);
|
|
308
|
+
if (ctx.config.accountId) {
|
|
309
|
+
await switchAccount(ctx, ctx.config.accountId);
|
|
310
|
+
await waitForHandshake(endpoints.websocket(ctx.baseUrl), Cookies.serialize(ctx.cookies), 3e4, ctx.debug);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/domains/symbol/symbol.ts
|
|
315
|
+
async function getSymbolSuggestions(ctx, text) {
|
|
316
|
+
ctx.ensureSession();
|
|
317
|
+
try {
|
|
318
|
+
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
319
|
+
const response = await retryRequest(
|
|
320
|
+
{
|
|
321
|
+
method: "GET",
|
|
322
|
+
url: endpoints.suggest(ctx.baseUrl, text),
|
|
323
|
+
headers: { ...baseHeaders(), Cookie: cookieStr }
|
|
324
|
+
},
|
|
325
|
+
ctx.retries
|
|
326
|
+
);
|
|
327
|
+
const suggests = response.data?.suggests;
|
|
328
|
+
if (!suggests?.length) {
|
|
329
|
+
ctx.throwError("NO_SUGGESTIONS", "No symbol suggestions found");
|
|
330
|
+
}
|
|
331
|
+
return suggests;
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (error instanceof DxtradeError) throw error;
|
|
334
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
335
|
+
ctx.throwError("SUGGEST_ERROR", `Error getting symbol suggestions: ${message}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function getSymbolInfo(ctx, symbol) {
|
|
339
|
+
ctx.ensureSession();
|
|
340
|
+
try {
|
|
341
|
+
const offsetMinutes = Math.abs((/* @__PURE__ */ new Date()).getTimezoneOffset());
|
|
342
|
+
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
343
|
+
const response = await retryRequest(
|
|
344
|
+
{
|
|
345
|
+
method: "GET",
|
|
346
|
+
url: endpoints.instrumentInfo(ctx.baseUrl, symbol, offsetMinutes),
|
|
347
|
+
headers: { ...baseHeaders(), Cookie: cookieStr }
|
|
348
|
+
},
|
|
349
|
+
ctx.retries
|
|
350
|
+
);
|
|
351
|
+
if (!response.data) {
|
|
352
|
+
ctx.throwError("NO_SYMBOL_INFO", "No symbol info returned");
|
|
353
|
+
}
|
|
354
|
+
return response.data;
|
|
355
|
+
} catch (error) {
|
|
356
|
+
if (error instanceof DxtradeError) throw error;
|
|
357
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
358
|
+
ctx.throwError("SYMBOL_INFO_ERROR", `Error getting symbol info: ${message}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/domains/order/order.ts
|
|
363
|
+
var import_crypto = __toESM(require("crypto"));
|
|
364
|
+
var import_ws2 = __toESM(require("ws"));
|
|
365
|
+
function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
366
|
+
const ws = new import_ws2.default(wsUrl, { headers: { Cookie: cookieStr } });
|
|
367
|
+
let settled = false;
|
|
368
|
+
const ready = new Promise((resolve) => {
|
|
369
|
+
ws.on("open", resolve);
|
|
370
|
+
});
|
|
371
|
+
const promise = new Promise((resolve, reject) => {
|
|
186
372
|
const timer = setTimeout(() => {
|
|
373
|
+
if (settled) return;
|
|
374
|
+
settled = true;
|
|
187
375
|
ws.close();
|
|
188
376
|
reject(new Error("[dxtrade-api] Order update timed out"));
|
|
189
377
|
}, timeout);
|
|
378
|
+
function done(err, result) {
|
|
379
|
+
if (settled) return;
|
|
380
|
+
settled = true;
|
|
381
|
+
clearTimeout(timer);
|
|
382
|
+
ws.close();
|
|
383
|
+
if (err) reject(err);
|
|
384
|
+
else resolve(result);
|
|
385
|
+
}
|
|
190
386
|
ws.on("message", (data) => {
|
|
191
|
-
const
|
|
192
|
-
if (debug)
|
|
193
|
-
if (
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
387
|
+
const msg = parseWsData(data);
|
|
388
|
+
if (shouldLog(msg, debug)) debugLog(msg);
|
|
389
|
+
if (typeof msg === "string") return;
|
|
390
|
+
if (msg.type === "MESSAGE" /* MESSAGE */) {
|
|
391
|
+
const messages = msg.body;
|
|
392
|
+
const orderMsg = messages?.findLast?.(
|
|
393
|
+
(m) => m.messageCategory === "TRADE_LOG" && m.messageType === "ORDER" && !m.historyMessage
|
|
394
|
+
);
|
|
395
|
+
if (!orderMsg) return;
|
|
396
|
+
const params = orderMsg.parametersTO;
|
|
397
|
+
if (params.orderStatus === "REJECTED") {
|
|
398
|
+
const reason = params.rejectReason?.key ?? "Unknown reason";
|
|
399
|
+
done(new Error(`[dxtrade-api] Order rejected: ${reason}`));
|
|
400
|
+
} else if (params.orderStatus === "FILLED") {
|
|
401
|
+
done(null, {
|
|
402
|
+
orderId: params.orderKey,
|
|
403
|
+
status: params.orderStatus,
|
|
404
|
+
symbol: params.symbol,
|
|
405
|
+
filledQuantity: params.filledQuantity,
|
|
406
|
+
filledPrice: params.filledPrice
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (msg.type === "ORDERS" /* ORDERS */) {
|
|
412
|
+
const body = msg.body?.[0];
|
|
413
|
+
if (!body?.orderId) return;
|
|
203
414
|
if (body.status === "REJECTED") {
|
|
204
|
-
|
|
205
|
-
} else {
|
|
206
|
-
|
|
415
|
+
done(new Error(`[dxtrade-api] Order rejected: ${body.statusDescription ?? "Unknown reason"}`));
|
|
416
|
+
} else if (body.status === "FILLED") {
|
|
417
|
+
done(null, body);
|
|
207
418
|
}
|
|
208
|
-
} catch {
|
|
209
419
|
}
|
|
210
420
|
});
|
|
211
421
|
ws.on("error", (error) => {
|
|
422
|
+
if (settled) return;
|
|
423
|
+
settled = true;
|
|
212
424
|
clearTimeout(timer);
|
|
213
425
|
ws.close();
|
|
214
426
|
reject(new Error(`[dxtrade-api] WebSocket order listener error: ${error.message}`));
|
|
215
427
|
});
|
|
216
428
|
});
|
|
429
|
+
return { promise, ready };
|
|
430
|
+
}
|
|
431
|
+
async function submitOrder(ctx, params) {
|
|
432
|
+
ctx.ensureSession();
|
|
433
|
+
const {
|
|
434
|
+
symbol,
|
|
435
|
+
side,
|
|
436
|
+
quantity,
|
|
437
|
+
orderType,
|
|
438
|
+
orderCode,
|
|
439
|
+
price,
|
|
440
|
+
instrumentId,
|
|
441
|
+
stopLoss,
|
|
442
|
+
takeProfit,
|
|
443
|
+
positionEffect = "OPENING" /* OPENING */,
|
|
444
|
+
positionCode,
|
|
445
|
+
tif = "GTC",
|
|
446
|
+
expireDate,
|
|
447
|
+
metadata
|
|
448
|
+
} = params;
|
|
449
|
+
const info = await getSymbolInfo(ctx, symbol);
|
|
450
|
+
const units = Math.round(quantity * info.lotSize);
|
|
451
|
+
const qty = side === "BUY" /* BUY */ ? units : -units;
|
|
452
|
+
const priceParam = orderType === "STOP" /* STOP */ ? "stopPrice" : "limitPrice";
|
|
453
|
+
const orderData = {
|
|
454
|
+
directExchange: false,
|
|
455
|
+
legs: [
|
|
456
|
+
{
|
|
457
|
+
...instrumentId != null && { instrumentId },
|
|
458
|
+
...positionCode != null && { positionCode },
|
|
459
|
+
positionEffect,
|
|
460
|
+
ratioQuantity: 1,
|
|
461
|
+
symbol
|
|
462
|
+
}
|
|
463
|
+
],
|
|
464
|
+
orderSide: side,
|
|
465
|
+
orderType,
|
|
466
|
+
quantity: qty,
|
|
467
|
+
requestId: orderCode ?? `gwt-uid-931-${import_crypto.default.randomUUID()}`,
|
|
468
|
+
timeInForce: tif,
|
|
469
|
+
...expireDate != null && { expireDate },
|
|
470
|
+
...metadata != null && { metadata }
|
|
471
|
+
};
|
|
472
|
+
if (price != null && orderType !== "MARKET" /* MARKET */) {
|
|
473
|
+
orderData[priceParam] = price;
|
|
474
|
+
}
|
|
475
|
+
if (stopLoss) {
|
|
476
|
+
orderData.stopLoss = {
|
|
477
|
+
...stopLoss.offset != null && { fixedOffset: stopLoss.offset },
|
|
478
|
+
...stopLoss.price != null && { fixedPrice: stopLoss.price },
|
|
479
|
+
priceFixed: stopLoss.price != null,
|
|
480
|
+
orderChainId: 0,
|
|
481
|
+
orderId: 0,
|
|
482
|
+
orderType: "STOP" /* STOP */,
|
|
483
|
+
quantityForProtection: qty,
|
|
484
|
+
removed: false
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
if (takeProfit) {
|
|
488
|
+
orderData.takeProfit = {
|
|
489
|
+
...takeProfit.offset != null && { fixedOffset: takeProfit.offset },
|
|
490
|
+
...takeProfit.price != null && { fixedPrice: takeProfit.price },
|
|
491
|
+
priceFixed: takeProfit.price != null,
|
|
492
|
+
orderChainId: 0,
|
|
493
|
+
orderId: 0,
|
|
494
|
+
orderType: "LIMIT" /* LIMIT */,
|
|
495
|
+
quantityForProtection: qty,
|
|
496
|
+
removed: false
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
const wsUrl = endpoints.websocket(ctx.baseUrl);
|
|
501
|
+
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
502
|
+
const listener = createOrderListener(wsUrl, cookieStr, 3e4, ctx.debug);
|
|
503
|
+
await listener.ready;
|
|
504
|
+
const response = await retryRequest(
|
|
505
|
+
{
|
|
506
|
+
method: "POST",
|
|
507
|
+
url: endpoints.submitOrder(ctx.baseUrl),
|
|
508
|
+
data: orderData,
|
|
509
|
+
headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
|
|
510
|
+
},
|
|
511
|
+
ctx.retries
|
|
512
|
+
);
|
|
513
|
+
ctx.callbacks.onOrderPlaced?.(response.data);
|
|
514
|
+
const orderUpdate = await listener.promise;
|
|
515
|
+
ctx.callbacks.onOrderUpdate?.(orderUpdate);
|
|
516
|
+
return orderUpdate;
|
|
517
|
+
} catch (error) {
|
|
518
|
+
if (error instanceof DxtradeError) throw error;
|
|
519
|
+
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
520
|
+
ctx.throwError("ORDER_ERROR", `Error submitting order: ${message}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/domains/assessments/assessments.ts
|
|
525
|
+
async function getAssessments(ctx, params) {
|
|
526
|
+
ctx.ensureSession();
|
|
527
|
+
try {
|
|
528
|
+
const response = await retryRequest(
|
|
529
|
+
{
|
|
530
|
+
method: "POST",
|
|
531
|
+
url: endpoints.assessments(ctx.baseUrl),
|
|
532
|
+
data: {
|
|
533
|
+
from: params.from,
|
|
534
|
+
instrument: params.instrument,
|
|
535
|
+
subtype: params.subtype ?? null,
|
|
536
|
+
to: params.to
|
|
537
|
+
},
|
|
538
|
+
headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
|
|
539
|
+
},
|
|
540
|
+
ctx.retries
|
|
541
|
+
);
|
|
542
|
+
return response.data;
|
|
543
|
+
} catch (error) {
|
|
544
|
+
if (error instanceof DxtradeError) throw error;
|
|
545
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
546
|
+
ctx.throwError("ASSESSMENTS_ERROR", `Error fetching assessments: ${message}`);
|
|
547
|
+
}
|
|
217
548
|
}
|
|
218
549
|
|
|
219
550
|
// src/client.ts
|
|
220
551
|
var DxtradeClient = class {
|
|
221
|
-
|
|
222
|
-
callbacks;
|
|
223
|
-
cookies = {};
|
|
224
|
-
csrf = null;
|
|
225
|
-
baseUrl;
|
|
226
|
-
retries;
|
|
227
|
-
debug;
|
|
552
|
+
_ctx;
|
|
228
553
|
constructor(config) {
|
|
229
|
-
|
|
230
|
-
this.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
554
|
+
const callbacks = config.callbacks ?? {};
|
|
555
|
+
this._ctx = {
|
|
556
|
+
config,
|
|
557
|
+
callbacks,
|
|
558
|
+
cookies: {},
|
|
559
|
+
csrf: null,
|
|
560
|
+
baseUrl: resolveBrokerUrl(config.broker, config.brokerUrls),
|
|
561
|
+
retries: config.retries ?? 3,
|
|
562
|
+
debug: config.debug ?? false,
|
|
563
|
+
ensureSession() {
|
|
564
|
+
if (!this.csrf) {
|
|
565
|
+
throw new DxtradeError("NO_SESSION", "No active session. Call login() and fetchCsrf() or connect() first.");
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
throwError(code, message) {
|
|
569
|
+
const error = new DxtradeError(code, message);
|
|
570
|
+
callbacks.onError?.(error);
|
|
571
|
+
throw error;
|
|
572
|
+
}
|
|
573
|
+
};
|
|
234
574
|
}
|
|
235
|
-
// ── Session ─────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
236
575
|
async login() {
|
|
237
|
-
|
|
238
|
-
const response = await retryRequest(
|
|
239
|
-
{
|
|
240
|
-
method: "POST",
|
|
241
|
-
url: endpoints.login(this.baseUrl),
|
|
242
|
-
data: {
|
|
243
|
-
username: this.config.username,
|
|
244
|
-
password: this.config.password,
|
|
245
|
-
domain: this.config.broker
|
|
246
|
-
},
|
|
247
|
-
headers: { "Content-Type": "application/json" }
|
|
248
|
-
},
|
|
249
|
-
this.retries
|
|
250
|
-
);
|
|
251
|
-
if (response.status === 200) {
|
|
252
|
-
const setCookies = response.headers["set-cookie"] ?? [];
|
|
253
|
-
const incoming = parseCookies(setCookies);
|
|
254
|
-
this.cookies = mergeCookies(this.cookies, incoming);
|
|
255
|
-
this.callbacks.onLogin?.();
|
|
256
|
-
} else {
|
|
257
|
-
this.throwError("LOGIN_FAILED", `Login failed: ${response.status}`);
|
|
258
|
-
}
|
|
259
|
-
} catch (error) {
|
|
260
|
-
if (error instanceof DxtradeError) throw error;
|
|
261
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
262
|
-
this.throwError("LOGIN_ERROR", `Login error: ${message}`);
|
|
263
|
-
}
|
|
576
|
+
return login(this._ctx);
|
|
264
577
|
}
|
|
265
578
|
async fetchCsrf() {
|
|
266
|
-
|
|
267
|
-
const cookieStr = serializeCookies(this.cookies);
|
|
268
|
-
const response = await retryRequest(
|
|
269
|
-
{
|
|
270
|
-
method: "GET",
|
|
271
|
-
url: this.baseUrl,
|
|
272
|
-
headers: { ...cookieOnlyHeaders(cookieStr), Referer: this.baseUrl }
|
|
273
|
-
},
|
|
274
|
-
this.retries
|
|
275
|
-
);
|
|
276
|
-
const csrfMatch = response.data?.match(/name="csrf" content="([^"]+)"/);
|
|
277
|
-
if (csrfMatch) {
|
|
278
|
-
this.csrf = csrfMatch[1];
|
|
279
|
-
} else {
|
|
280
|
-
this.throwError("CSRF_NOT_FOUND", "CSRF token not found");
|
|
281
|
-
}
|
|
282
|
-
} catch (error) {
|
|
283
|
-
if (error instanceof DxtradeError) throw error;
|
|
284
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
285
|
-
this.throwError("CSRF_ERROR", `CSRF fetch error: ${message}`);
|
|
286
|
-
}
|
|
579
|
+
return fetchCsrf(this._ctx);
|
|
287
580
|
}
|
|
288
581
|
async switchAccount(accountId) {
|
|
289
|
-
this.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
method: "POST",
|
|
294
|
-
url: endpoints.switchAccount(this.baseUrl, accountId),
|
|
295
|
-
headers: authHeaders(this.csrf, serializeCookies(this.cookies))
|
|
296
|
-
},
|
|
297
|
-
this.retries
|
|
298
|
-
);
|
|
299
|
-
this.callbacks.onAccountSwitch?.(accountId);
|
|
300
|
-
} catch (error) {
|
|
301
|
-
if (error instanceof DxtradeError) throw error;
|
|
302
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
303
|
-
this.throwError(
|
|
304
|
-
"ACCOUNT_SWITCH_ERROR",
|
|
305
|
-
`Error switching account: ${message}`
|
|
306
|
-
);
|
|
307
|
-
}
|
|
582
|
+
return switchAccount(this._ctx, accountId);
|
|
583
|
+
}
|
|
584
|
+
async connect() {
|
|
585
|
+
return connect(this._ctx);
|
|
308
586
|
}
|
|
309
|
-
// ── Market Data ─────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
310
587
|
async getSymbolSuggestions(text) {
|
|
311
|
-
this.
|
|
312
|
-
try {
|
|
313
|
-
const cookieStr = serializeCookies(this.cookies);
|
|
314
|
-
const response = await retryRequest(
|
|
315
|
-
{
|
|
316
|
-
method: "GET",
|
|
317
|
-
url: endpoints.suggest(this.baseUrl, text),
|
|
318
|
-
headers: { ...baseHeaders(), Cookie: cookieStr }
|
|
319
|
-
},
|
|
320
|
-
this.retries
|
|
321
|
-
);
|
|
322
|
-
const suggests = response.data?.suggests;
|
|
323
|
-
if (!suggests?.length) {
|
|
324
|
-
this.throwError("NO_SUGGESTIONS", "No symbol suggestions found");
|
|
325
|
-
}
|
|
326
|
-
return suggests;
|
|
327
|
-
} catch (error) {
|
|
328
|
-
if (error instanceof DxtradeError) throw error;
|
|
329
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
330
|
-
this.throwError(
|
|
331
|
-
"SUGGEST_ERROR",
|
|
332
|
-
`Error getting symbol suggestions: ${message}`
|
|
333
|
-
);
|
|
334
|
-
}
|
|
588
|
+
return getSymbolSuggestions(this._ctx, text);
|
|
335
589
|
}
|
|
336
590
|
async getSymbolInfo(symbol) {
|
|
337
|
-
this.
|
|
338
|
-
try {
|
|
339
|
-
const offsetMinutes = Math.abs((/* @__PURE__ */ new Date()).getTimezoneOffset());
|
|
340
|
-
const cookieStr = serializeCookies(this.cookies);
|
|
341
|
-
const response = await retryRequest(
|
|
342
|
-
{
|
|
343
|
-
method: "GET",
|
|
344
|
-
url: endpoints.instrumentInfo(this.baseUrl, symbol, offsetMinutes),
|
|
345
|
-
headers: { ...baseHeaders(), Cookie: cookieStr }
|
|
346
|
-
},
|
|
347
|
-
this.retries
|
|
348
|
-
);
|
|
349
|
-
if (!response.data) {
|
|
350
|
-
this.throwError("NO_SYMBOL_INFO", "No symbol info returned");
|
|
351
|
-
}
|
|
352
|
-
return response.data;
|
|
353
|
-
} catch (error) {
|
|
354
|
-
if (error instanceof DxtradeError) throw error;
|
|
355
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
356
|
-
this.throwError(
|
|
357
|
-
"SYMBOL_INFO_ERROR",
|
|
358
|
-
`Error getting symbol info: ${message}`
|
|
359
|
-
);
|
|
360
|
-
}
|
|
591
|
+
return getSymbolInfo(this._ctx, symbol);
|
|
361
592
|
}
|
|
362
|
-
// ── Trading ─────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
363
593
|
async submitOrder(params) {
|
|
364
|
-
this.
|
|
365
|
-
const {
|
|
366
|
-
symbol,
|
|
367
|
-
side,
|
|
368
|
-
quantity,
|
|
369
|
-
orderType,
|
|
370
|
-
price,
|
|
371
|
-
instrumentId,
|
|
372
|
-
stopLoss,
|
|
373
|
-
takeProfit,
|
|
374
|
-
positionEffect = "OPENING" /* OPENING */
|
|
375
|
-
} = params;
|
|
376
|
-
const qty = side === "BUY" /* BUY */ ? Math.abs(quantity) : -Math.abs(quantity);
|
|
377
|
-
const priceParam = orderType === "STOP" /* STOP */ ? "stopPrice" : "limitPrice";
|
|
378
|
-
const orderData = {
|
|
379
|
-
directExchange: false,
|
|
380
|
-
legs: [
|
|
381
|
-
{
|
|
382
|
-
...instrumentId != null && { instrumentId },
|
|
383
|
-
positionEffect,
|
|
384
|
-
ratioQuantity: 1,
|
|
385
|
-
symbol
|
|
386
|
-
}
|
|
387
|
-
],
|
|
388
|
-
orderSide: side,
|
|
389
|
-
orderType,
|
|
390
|
-
quantity: qty,
|
|
391
|
-
requestId: `gwt-uid-931-${import_crypto.default.randomUUID()}`,
|
|
392
|
-
timeInForce: "GTC"
|
|
393
|
-
};
|
|
394
|
-
if (price != null && orderType !== "MARKET" /* MARKET */) {
|
|
395
|
-
orderData[priceParam] = price;
|
|
396
|
-
}
|
|
397
|
-
if (stopLoss) {
|
|
398
|
-
orderData.stopLoss = {
|
|
399
|
-
...stopLoss.offset != null && { fixedOffset: stopLoss.offset },
|
|
400
|
-
...stopLoss.price != null && { fixedPrice: stopLoss.price },
|
|
401
|
-
priceFixed: stopLoss.price != null,
|
|
402
|
-
orderChainId: 0,
|
|
403
|
-
orderId: 0,
|
|
404
|
-
orderType: "STOP" /* STOP */,
|
|
405
|
-
quantityForProtection: qty,
|
|
406
|
-
removed: false
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
if (takeProfit) {
|
|
410
|
-
orderData.takeProfit = {
|
|
411
|
-
...takeProfit.offset != null && { fixedOffset: takeProfit.offset },
|
|
412
|
-
...takeProfit.price != null && { fixedPrice: takeProfit.price },
|
|
413
|
-
priceFixed: takeProfit.price != null,
|
|
414
|
-
orderChainId: 0,
|
|
415
|
-
orderId: 0,
|
|
416
|
-
orderType: "LIMIT" /* LIMIT */,
|
|
417
|
-
quantityForProtection: qty,
|
|
418
|
-
removed: false
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
try {
|
|
422
|
-
const response = await retryRequest(
|
|
423
|
-
{
|
|
424
|
-
method: "POST",
|
|
425
|
-
url: endpoints.submitOrder(this.baseUrl),
|
|
426
|
-
data: orderData,
|
|
427
|
-
headers: authHeaders(this.csrf, serializeCookies(this.cookies))
|
|
428
|
-
},
|
|
429
|
-
this.retries
|
|
430
|
-
);
|
|
431
|
-
if (response.status !== 200) {
|
|
432
|
-
this.throwError(
|
|
433
|
-
"ORDER_SUBMIT_FAILED",
|
|
434
|
-
`Order submission failed with status: ${response.status}`
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
this.callbacks.onOrderPlaced?.({
|
|
438
|
-
status: response.status,
|
|
439
|
-
data: response.data
|
|
440
|
-
});
|
|
441
|
-
const wsUrl = endpoints.websocket(this.baseUrl);
|
|
442
|
-
const cookieStr = serializeCookies(this.cookies);
|
|
443
|
-
const orderUpdate = await waitForOrderUpdate(
|
|
444
|
-
wsUrl,
|
|
445
|
-
cookieStr,
|
|
446
|
-
3e4,
|
|
447
|
-
this.debug
|
|
448
|
-
);
|
|
449
|
-
this.callbacks.onOrderUpdate?.(orderUpdate);
|
|
450
|
-
return orderUpdate;
|
|
451
|
-
} catch (error) {
|
|
452
|
-
if (error instanceof DxtradeError) throw error;
|
|
453
|
-
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
454
|
-
this.throwError("ORDER_ERROR", `Error submitting order: ${message}`);
|
|
455
|
-
}
|
|
594
|
+
return submitOrder(this._ctx, params);
|
|
456
595
|
}
|
|
457
|
-
// ── Analytics ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
458
596
|
async getAssessments(params) {
|
|
459
|
-
this.
|
|
460
|
-
try {
|
|
461
|
-
const response = await retryRequest(
|
|
462
|
-
{
|
|
463
|
-
method: "POST",
|
|
464
|
-
url: endpoints.assessments(this.baseUrl),
|
|
465
|
-
data: {
|
|
466
|
-
from: params.from,
|
|
467
|
-
instrument: params.instrument,
|
|
468
|
-
subtype: params.subtype ?? null,
|
|
469
|
-
to: params.to
|
|
470
|
-
},
|
|
471
|
-
headers: authHeaders(this.csrf, serializeCookies(this.cookies))
|
|
472
|
-
},
|
|
473
|
-
this.retries
|
|
474
|
-
);
|
|
475
|
-
return response.data;
|
|
476
|
-
} catch (error) {
|
|
477
|
-
if (error instanceof DxtradeError) throw error;
|
|
478
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
479
|
-
this.throwError(
|
|
480
|
-
"ASSESSMENTS_ERROR",
|
|
481
|
-
`Error fetching assessments: ${message}`
|
|
482
|
-
);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
// ── Convenience ─────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
486
|
-
async connect() {
|
|
487
|
-
await this.login();
|
|
488
|
-
await this.fetchCsrf();
|
|
489
|
-
const wsUrl = endpoints.websocket(this.baseUrl);
|
|
490
|
-
const cookieStr = serializeCookies(this.cookies);
|
|
491
|
-
await waitForHandshake(wsUrl, cookieStr, 3e4, this.debug);
|
|
492
|
-
if (this.config.accountId) {
|
|
493
|
-
await this.switchAccount(this.config.accountId);
|
|
494
|
-
await waitForHandshake(
|
|
495
|
-
endpoints.websocket(this.baseUrl),
|
|
496
|
-
serializeCookies(this.cookies),
|
|
497
|
-
3e4,
|
|
498
|
-
this.debug
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
// ── Internals ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
503
|
-
ensureSession() {
|
|
504
|
-
if (!this.csrf) {
|
|
505
|
-
throw new DxtradeError(
|
|
506
|
-
"NO_SESSION",
|
|
507
|
-
"No active session. Call login() and fetchCsrf() or connect() first."
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
throwError(code, message) {
|
|
512
|
-
const error = new DxtradeError(code, message);
|
|
513
|
-
this.callbacks.onError?.(error);
|
|
514
|
-
throw error;
|
|
597
|
+
return getAssessments(this._ctx, params);
|
|
515
598
|
}
|
|
516
599
|
};
|
|
517
600
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -522,6 +605,8 @@ var DxtradeClient = class {
|
|
|
522
605
|
DxtradeError,
|
|
523
606
|
ORDER_TYPE,
|
|
524
607
|
SIDE,
|
|
608
|
+
TIF,
|
|
609
|
+
WS_MESSAGE,
|
|
525
610
|
endpoints,
|
|
526
611
|
resolveBrokerUrl
|
|
527
612
|
});
|