@danielgroen/dxtrade-api 1.0.19 → 1.0.21
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 +7 -2
- package/dist/index.d.mts +72 -2
- package/dist/index.d.ts +72 -2
- package/dist/index.js +215 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +214 -43
- package/dist/index.mjs.map +1 -1
- package/llms.txt +3 -0
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,10 @@ var BROKER = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/constants/endpoints.ts
|
|
9
|
-
|
|
9
|
+
function websocketQuery(atmosphereId) {
|
|
10
|
+
const trackingId = atmosphereId ?? "0";
|
|
11
|
+
return `?X-Atmosphere-tracking-id=${trackingId}&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`;
|
|
12
|
+
}
|
|
10
13
|
var endpoints = {
|
|
11
14
|
login: (base) => `${base}/api/auth/login`,
|
|
12
15
|
switchAccount: (base, id) => `${base}/api/accounts/switch?accountId=${id}`,
|
|
@@ -15,8 +18,10 @@ var endpoints = {
|
|
|
15
18
|
submitOrder: (base) => `${base}/api/orders/single`,
|
|
16
19
|
closePosition: (base) => `${base}/api/positions/close`,
|
|
17
20
|
assessments: (base) => `${base}/api/assessments`,
|
|
18
|
-
websocket: (base) => `wss://${base.split("//")[1]}/client/connector` + websocketQuery,
|
|
19
|
-
tradeJournal: (base, params) => `${base}/api/tradejournal?from=${params.from}&to=${params.to}
|
|
21
|
+
websocket: (base, atmosphereId) => `wss://${base.split("//")[1]}/client/connector` + websocketQuery(atmosphereId),
|
|
22
|
+
tradeJournal: (base, params) => `${base}/api/tradejournal?from=${params.from}&to=${params.to}`,
|
|
23
|
+
subscribeInstruments: (base) => `${base}/api/instruments/subscribeInstrumentSymbols`,
|
|
24
|
+
charts: (base) => `${base}/api/charts`
|
|
20
25
|
};
|
|
21
26
|
|
|
22
27
|
// src/constants/enums.ts
|
|
@@ -42,11 +47,40 @@ var TIF = /* @__PURE__ */ ((TIF2) => {
|
|
|
42
47
|
TIF2["GTD"] = "GTD";
|
|
43
48
|
return TIF2;
|
|
44
49
|
})(TIF || {});
|
|
50
|
+
var ERROR = /* @__PURE__ */ ((ERROR2) => {
|
|
51
|
+
ERROR2["NO_SESSION"] = "NO_SESSION";
|
|
52
|
+
ERROR2["LOGIN_FAILED"] = "LOGIN_FAILED";
|
|
53
|
+
ERROR2["LOGIN_ERROR"] = "LOGIN_ERROR";
|
|
54
|
+
ERROR2["CSRF_NOT_FOUND"] = "CSRF_NOT_FOUND";
|
|
55
|
+
ERROR2["CSRF_ERROR"] = "CSRF_ERROR";
|
|
56
|
+
ERROR2["ACCOUNT_SWITCH_ERROR"] = "ACCOUNT_SWITCH_ERROR";
|
|
57
|
+
ERROR2["NO_SUGGESTIONS"] = "NO_SUGGESTIONS";
|
|
58
|
+
ERROR2["SUGGEST_ERROR"] = "SUGGEST_ERROR";
|
|
59
|
+
ERROR2["NO_SYMBOL_INFO"] = "NO_SYMBOL_INFO";
|
|
60
|
+
ERROR2["SYMBOL_INFO_ERROR"] = "SYMBOL_INFO_ERROR";
|
|
61
|
+
ERROR2["INSTRUMENTS_TIMEOUT"] = "INSTRUMENTS_TIMEOUT";
|
|
62
|
+
ERROR2["INSTRUMENTS_ERROR"] = "INSTRUMENTS_ERROR";
|
|
63
|
+
ERROR2["LIMITS_TIMEOUT"] = "LIMITS_TIMEOUT";
|
|
64
|
+
ERROR2["LIMITS_ERROR"] = "LIMITS_ERROR";
|
|
65
|
+
ERROR2["OHLC_TIMEOUT"] = "OHLC_TIMEOUT";
|
|
66
|
+
ERROR2["OHLC_ERROR"] = "OHLC_ERROR";
|
|
67
|
+
ERROR2["ORDER_ERROR"] = "ORDER_ERROR";
|
|
68
|
+
ERROR2["POSITION_CLOSE_ERROR"] = "POSITION_CLOSE_ERROR";
|
|
69
|
+
ERROR2["ACCOUNT_METRICS_TIMEOUT"] = "ACCOUNT_METRICS_TIMEOUT";
|
|
70
|
+
ERROR2["ACCOUNT_METRICS_ERROR"] = "ACCOUNT_METRICS_ERROR";
|
|
71
|
+
ERROR2["ACCOUNT_POSITIONS_TIMEOUT"] = "ACCOUNT_POSITIONS_TIMEOUT";
|
|
72
|
+
ERROR2["ACCOUNT_POSITIONS_ERROR"] = "ACCOUNT_POSITIONS_ERROR";
|
|
73
|
+
ERROR2["TRADE_JOURNAL_ERROR"] = "TRADE_JOURNAL_ERROR";
|
|
74
|
+
ERROR2["ASSESSMENTS_ERROR"] = "ASSESSMENTS_ERROR";
|
|
75
|
+
return ERROR2;
|
|
76
|
+
})(ERROR || {});
|
|
45
77
|
var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
|
|
46
78
|
WS_MESSAGE2["ACCOUNT_METRICS"] = "ACCOUNT_METRICS";
|
|
47
79
|
WS_MESSAGE2["ACCOUNTS"] = "ACCOUNTS";
|
|
48
80
|
WS_MESSAGE2["AVAILABLE_WATCHLISTS"] = "AVAILABLE_WATCHLISTS";
|
|
81
|
+
WS_MESSAGE2["CHART_FEED_SUBTOPIC"] = "chartFeedSubtopic";
|
|
49
82
|
WS_MESSAGE2["INSTRUMENTS"] = "INSTRUMENTS";
|
|
83
|
+
WS_MESSAGE2["INSTRUMENT_METRICS"] = "INSTRUMENT_METRICS";
|
|
50
84
|
WS_MESSAGE2["LIMITS"] = "LIMITS";
|
|
51
85
|
WS_MESSAGE2["MESSAGE"] = "MESSAGE";
|
|
52
86
|
WS_MESSAGE2["ORDERS"] = "ORDERS";
|
|
@@ -54,9 +88,16 @@ var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
|
|
|
54
88
|
WS_MESSAGE2["POSITION_CASH_TRANSFERS"] = "POSITION_CASH_TRANSFERS";
|
|
55
89
|
WS_MESSAGE2["PRIVATE_LAYOUT_NAMES"] = "PRIVATE_LAYOUT_NAMES";
|
|
56
90
|
WS_MESSAGE2["SHARED_PROPERTIES_MESSAGE"] = "SHARED_PROPERTIES_MESSAGE";
|
|
91
|
+
WS_MESSAGE2["TRADE_STATUSES"] = "TRADE_STATUSES";
|
|
57
92
|
WS_MESSAGE2["USER_LOGIN_INFO"] = "USER_LOGIN_INFO";
|
|
58
93
|
return WS_MESSAGE2;
|
|
59
94
|
})(WS_MESSAGE || {});
|
|
95
|
+
((WS_MESSAGE2) => {
|
|
96
|
+
let SUBTOPIC;
|
|
97
|
+
((SUBTOPIC2) => {
|
|
98
|
+
SUBTOPIC2["BIG_CHART_COMPONENT"] = "BigChartComponentPresenter-4";
|
|
99
|
+
})(SUBTOPIC = WS_MESSAGE2.SUBTOPIC || (WS_MESSAGE2.SUBTOPIC = {}));
|
|
100
|
+
})(WS_MESSAGE || (WS_MESSAGE = {}));
|
|
60
101
|
|
|
61
102
|
// src/constants/errors.ts
|
|
62
103
|
var DxtradeError = class extends Error {
|
|
@@ -145,6 +186,14 @@ function debugLog(msg) {
|
|
|
145
186
|
function clearDebugLog() {
|
|
146
187
|
writeFileSync(DEBUG_LOG, "");
|
|
147
188
|
}
|
|
189
|
+
function parseAtmosphereId(data) {
|
|
190
|
+
const raw = data.toString();
|
|
191
|
+
const parts = raw.split("|");
|
|
192
|
+
if (parts.length >= 2 && /^[0-9a-f-]{36}$/.test(parts[1])) {
|
|
193
|
+
return parts[1];
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
148
197
|
function parseWsData(data) {
|
|
149
198
|
const raw = data.toString();
|
|
150
199
|
const pipeIndex = raw.indexOf("|");
|
|
@@ -159,13 +208,13 @@ function parseWsData(data) {
|
|
|
159
208
|
// src/domains/account/account.ts
|
|
160
209
|
async function getAccountMetrics(ctx, timeout = 3e4) {
|
|
161
210
|
ctx.ensureSession();
|
|
162
|
-
const wsUrl = endpoints.websocket(ctx.broker);
|
|
211
|
+
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
163
212
|
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
164
213
|
return new Promise((resolve, reject) => {
|
|
165
214
|
const ws = new WebSocket(wsUrl, { headers: { Cookie: cookieStr } });
|
|
166
215
|
const timer = setTimeout(() => {
|
|
167
216
|
ws.close();
|
|
168
|
-
reject(new DxtradeError("ACCOUNT_METRICS_TIMEOUT"
|
|
217
|
+
reject(new DxtradeError("ACCOUNT_METRICS_TIMEOUT" /* ACCOUNT_METRICS_TIMEOUT */, "Account metrics timed out"));
|
|
169
218
|
}, timeout);
|
|
170
219
|
ws.on("message", (data) => {
|
|
171
220
|
const msg = parseWsData(data);
|
|
@@ -181,7 +230,7 @@ async function getAccountMetrics(ctx, timeout = 3e4) {
|
|
|
181
230
|
ws.on("error", (error) => {
|
|
182
231
|
clearTimeout(timer);
|
|
183
232
|
ws.close();
|
|
184
|
-
reject(new DxtradeError("ACCOUNT_METRICS_ERROR"
|
|
233
|
+
reject(new DxtradeError("ACCOUNT_METRICS_ERROR" /* ACCOUNT_METRICS_ERROR */, `Account metrics error: ${error.message}`));
|
|
185
234
|
});
|
|
186
235
|
});
|
|
187
236
|
}
|
|
@@ -203,12 +252,12 @@ async function getTradeJournal(ctx, params) {
|
|
|
203
252
|
ctx.cookies = Cookies.merge(ctx.cookies, incoming);
|
|
204
253
|
return response.data;
|
|
205
254
|
} else {
|
|
206
|
-
ctx.throwError("TRADE_JOURNAL_ERROR"
|
|
255
|
+
ctx.throwError("TRADE_JOURNAL_ERROR" /* TRADE_JOURNAL_ERROR */, `Login failed: ${response.status}`);
|
|
207
256
|
}
|
|
208
257
|
} catch (error) {
|
|
209
258
|
if (error instanceof DxtradeError) throw error;
|
|
210
259
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
211
|
-
ctx.throwError("TRADE_JOURNAL_ERROR"
|
|
260
|
+
ctx.throwError("TRADE_JOURNAL_ERROR" /* TRADE_JOURNAL_ERROR */, `Trade journal error: ${message}`);
|
|
212
261
|
}
|
|
213
262
|
}
|
|
214
263
|
|
|
@@ -234,7 +283,7 @@ async function getAssessments(ctx, params) {
|
|
|
234
283
|
} catch (error) {
|
|
235
284
|
if (error instanceof DxtradeError) throw error;
|
|
236
285
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
237
|
-
ctx.throwError("ASSESSMENTS_ERROR"
|
|
286
|
+
ctx.throwError("ASSESSMENTS_ERROR" /* ASSESSMENTS_ERROR */, `Error fetching assessments: ${message}`);
|
|
238
287
|
}
|
|
239
288
|
}
|
|
240
289
|
|
|
@@ -242,13 +291,13 @@ async function getAssessments(ctx, params) {
|
|
|
242
291
|
import WebSocket2 from "ws";
|
|
243
292
|
async function getInstruments(ctx, params = {}, timeout = 3e4) {
|
|
244
293
|
ctx.ensureSession();
|
|
245
|
-
const wsUrl = endpoints.websocket(ctx.broker);
|
|
294
|
+
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
246
295
|
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
247
296
|
return new Promise((resolve, reject) => {
|
|
248
297
|
const ws = new WebSocket2(wsUrl, { headers: { Cookie: cookieStr } });
|
|
249
298
|
const timer = setTimeout(() => {
|
|
250
299
|
ws.close();
|
|
251
|
-
reject(new DxtradeError("INSTRUMENTS_TIMEOUT"
|
|
300
|
+
reject(new DxtradeError("INSTRUMENTS_TIMEOUT" /* INSTRUMENTS_TIMEOUT */, "Instruments request timed out"));
|
|
252
301
|
}, timeout);
|
|
253
302
|
let instruments = [];
|
|
254
303
|
let settleTimer = null;
|
|
@@ -278,17 +327,115 @@ async function getInstruments(ctx, params = {}, timeout = 3e4) {
|
|
|
278
327
|
ws.on("error", (error) => {
|
|
279
328
|
clearTimeout(timer);
|
|
280
329
|
ws.close();
|
|
281
|
-
reject(new DxtradeError("INSTRUMENTS_ERROR"
|
|
330
|
+
reject(new DxtradeError("INSTRUMENTS_ERROR" /* INSTRUMENTS_ERROR */, `Instruments error: ${error.message}`));
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/domains/ohlc/ohlc.ts
|
|
336
|
+
import WebSocket3 from "ws";
|
|
337
|
+
async function getOHLC(ctx, params, timeout = 3e4) {
|
|
338
|
+
ctx.ensureSession();
|
|
339
|
+
const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
|
|
340
|
+
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
341
|
+
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
342
|
+
const headers = authHeaders(ctx.csrf, cookieStr);
|
|
343
|
+
return new Promise((resolve, reject) => {
|
|
344
|
+
const ws = new WebSocket3(wsUrl, { headers: { Cookie: cookieStr } });
|
|
345
|
+
const bars = [];
|
|
346
|
+
let putsSent = false;
|
|
347
|
+
let initSettleTimer = null;
|
|
348
|
+
let barSettleTimer = null;
|
|
349
|
+
const timer = setTimeout(() => {
|
|
350
|
+
ws.close();
|
|
351
|
+
reject(new DxtradeError("OHLC_TIMEOUT" /* OHLC_TIMEOUT */, "OHLC data timed out"));
|
|
352
|
+
}, timeout);
|
|
353
|
+
function cleanup() {
|
|
354
|
+
clearTimeout(timer);
|
|
355
|
+
if (initSettleTimer) clearTimeout(initSettleTimer);
|
|
356
|
+
if (barSettleTimer) clearTimeout(barSettleTimer);
|
|
357
|
+
ws.close();
|
|
358
|
+
}
|
|
359
|
+
async function sendPuts() {
|
|
360
|
+
putsSent = true;
|
|
361
|
+
try {
|
|
362
|
+
await retryRequest(
|
|
363
|
+
{
|
|
364
|
+
method: "PUT",
|
|
365
|
+
url: endpoints.subscribeInstruments(ctx.broker),
|
|
366
|
+
data: { instruments: [symbol] },
|
|
367
|
+
headers
|
|
368
|
+
},
|
|
369
|
+
ctx.retries
|
|
370
|
+
);
|
|
371
|
+
await retryRequest(
|
|
372
|
+
{
|
|
373
|
+
method: "PUT",
|
|
374
|
+
url: endpoints.charts(ctx.broker),
|
|
375
|
+
data: {
|
|
376
|
+
chartIds: [],
|
|
377
|
+
requests: [
|
|
378
|
+
{
|
|
379
|
+
aggregationPeriodSeconds: resolution,
|
|
380
|
+
extendedSession: true,
|
|
381
|
+
forexPriceField: priceField,
|
|
382
|
+
id: 0,
|
|
383
|
+
maxBarsCount: maxBars,
|
|
384
|
+
range,
|
|
385
|
+
studySubscription: [],
|
|
386
|
+
subtopic: WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT,
|
|
387
|
+
symbol
|
|
388
|
+
}
|
|
389
|
+
]
|
|
390
|
+
},
|
|
391
|
+
headers
|
|
392
|
+
},
|
|
393
|
+
ctx.retries
|
|
394
|
+
);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
cleanup();
|
|
397
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
398
|
+
reject(new DxtradeError("OHLC_ERROR" /* OHLC_ERROR */, `Error fetching OHLC data: ${message}`));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
ws.on("message", (data) => {
|
|
402
|
+
const msg = parseWsData(data);
|
|
403
|
+
if (shouldLog(msg, ctx.debug)) debugLog(msg);
|
|
404
|
+
if (typeof msg === "string") return;
|
|
405
|
+
if (!putsSent) {
|
|
406
|
+
if (initSettleTimer) clearTimeout(initSettleTimer);
|
|
407
|
+
initSettleTimer = setTimeout(() => sendPuts(), 1e3);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const body = msg.body;
|
|
411
|
+
if (body?.subtopic !== WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT) return;
|
|
412
|
+
if (Array.isArray(body.data)) {
|
|
413
|
+
bars.push(...body.data);
|
|
414
|
+
}
|
|
415
|
+
if (barSettleTimer) clearTimeout(barSettleTimer);
|
|
416
|
+
if (body.snapshotEnd) {
|
|
417
|
+
cleanup();
|
|
418
|
+
resolve(bars);
|
|
419
|
+
} else {
|
|
420
|
+
barSettleTimer = setTimeout(() => {
|
|
421
|
+
cleanup();
|
|
422
|
+
resolve(bars);
|
|
423
|
+
}, 2e3);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
ws.on("error", (error) => {
|
|
427
|
+
cleanup();
|
|
428
|
+
reject(new DxtradeError("OHLC_ERROR" /* OHLC_ERROR */, `OHLC WebSocket error: ${error.message}`));
|
|
282
429
|
});
|
|
283
430
|
});
|
|
284
431
|
}
|
|
285
432
|
|
|
286
433
|
// src/domains/order/order.ts
|
|
287
434
|
import crypto from "crypto";
|
|
288
|
-
import
|
|
435
|
+
import WebSocket5 from "ws";
|
|
289
436
|
|
|
290
437
|
// src/domains/symbol/symbol.ts
|
|
291
|
-
import
|
|
438
|
+
import WebSocket4 from "ws";
|
|
292
439
|
async function getSymbolSuggestions(ctx, text) {
|
|
293
440
|
ctx.ensureSession();
|
|
294
441
|
try {
|
|
@@ -303,13 +450,13 @@ async function getSymbolSuggestions(ctx, text) {
|
|
|
303
450
|
);
|
|
304
451
|
const suggests = response.data?.suggests;
|
|
305
452
|
if (!suggests?.length) {
|
|
306
|
-
ctx.throwError("NO_SUGGESTIONS"
|
|
453
|
+
ctx.throwError("NO_SUGGESTIONS" /* NO_SUGGESTIONS */, "No symbol suggestions found");
|
|
307
454
|
}
|
|
308
455
|
return suggests;
|
|
309
456
|
} catch (error) {
|
|
310
457
|
if (error instanceof DxtradeError) throw error;
|
|
311
458
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
312
|
-
ctx.throwError("SUGGEST_ERROR"
|
|
459
|
+
ctx.throwError("SUGGEST_ERROR" /* SUGGEST_ERROR */, `Error getting symbol suggestions: ${message}`);
|
|
313
460
|
}
|
|
314
461
|
}
|
|
315
462
|
async function getSymbolInfo(ctx, symbol) {
|
|
@@ -326,24 +473,24 @@ async function getSymbolInfo(ctx, symbol) {
|
|
|
326
473
|
ctx.retries
|
|
327
474
|
);
|
|
328
475
|
if (!response.data) {
|
|
329
|
-
ctx.throwError("NO_SYMBOL_INFO"
|
|
476
|
+
ctx.throwError("NO_SYMBOL_INFO" /* NO_SYMBOL_INFO */, "No symbol info returned");
|
|
330
477
|
}
|
|
331
478
|
return response.data;
|
|
332
479
|
} catch (error) {
|
|
333
480
|
if (error instanceof DxtradeError) throw error;
|
|
334
481
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
335
|
-
ctx.throwError("SYMBOL_INFO_ERROR"
|
|
482
|
+
ctx.throwError("SYMBOL_INFO_ERROR" /* SYMBOL_INFO_ERROR */, `Error getting symbol info: ${message}`);
|
|
336
483
|
}
|
|
337
484
|
}
|
|
338
485
|
async function getSymbolLimits(ctx, timeout = 3e4) {
|
|
339
486
|
ctx.ensureSession();
|
|
340
|
-
const wsUrl = endpoints.websocket(ctx.broker);
|
|
487
|
+
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
341
488
|
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
342
489
|
return new Promise((resolve, reject) => {
|
|
343
|
-
const ws = new
|
|
490
|
+
const ws = new WebSocket4(wsUrl, { headers: { Cookie: cookieStr } });
|
|
344
491
|
const timer = setTimeout(() => {
|
|
345
492
|
ws.close();
|
|
346
|
-
reject(new DxtradeError("LIMITS_TIMEOUT"
|
|
493
|
+
reject(new DxtradeError("LIMITS_TIMEOUT" /* LIMITS_TIMEOUT */, "Symbol limits request timed out"));
|
|
347
494
|
}, timeout);
|
|
348
495
|
let limits = [];
|
|
349
496
|
let settleTimer = null;
|
|
@@ -366,14 +513,14 @@ async function getSymbolLimits(ctx, timeout = 3e4) {
|
|
|
366
513
|
ws.on("error", (error) => {
|
|
367
514
|
clearTimeout(timer);
|
|
368
515
|
ws.close();
|
|
369
|
-
reject(new DxtradeError("LIMITS_ERROR"
|
|
516
|
+
reject(new DxtradeError("LIMITS_ERROR" /* LIMITS_ERROR */, `Symbol limits error: ${error.message}`));
|
|
370
517
|
});
|
|
371
518
|
});
|
|
372
519
|
}
|
|
373
520
|
|
|
374
521
|
// src/domains/order/order.ts
|
|
375
522
|
function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
376
|
-
const ws = new
|
|
523
|
+
const ws = new WebSocket5(wsUrl, { headers: { Cookie: cookieStr } });
|
|
377
524
|
let settled = false;
|
|
378
525
|
const ready = new Promise((resolve) => {
|
|
379
526
|
ws.on("open", resolve);
|
|
@@ -507,7 +654,7 @@ async function submitOrder(ctx, params) {
|
|
|
507
654
|
};
|
|
508
655
|
}
|
|
509
656
|
try {
|
|
510
|
-
const wsUrl = endpoints.websocket(ctx.broker);
|
|
657
|
+
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
511
658
|
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
512
659
|
const listener = createOrderListener(wsUrl, cookieStr, 3e4, ctx.debug);
|
|
513
660
|
await listener.ready;
|
|
@@ -527,21 +674,21 @@ async function submitOrder(ctx, params) {
|
|
|
527
674
|
} catch (error) {
|
|
528
675
|
if (error instanceof DxtradeError) throw error;
|
|
529
676
|
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
530
|
-
ctx.throwError("ORDER_ERROR"
|
|
677
|
+
ctx.throwError("ORDER_ERROR" /* ORDER_ERROR */, `Error submitting order: ${message}`);
|
|
531
678
|
}
|
|
532
679
|
}
|
|
533
680
|
|
|
534
681
|
// src/domains/position/position.ts
|
|
535
|
-
import
|
|
682
|
+
import WebSocket6 from "ws";
|
|
536
683
|
async function getPositions(ctx) {
|
|
537
684
|
ctx.ensureSession();
|
|
538
|
-
const wsUrl = endpoints.websocket(ctx.broker);
|
|
685
|
+
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
539
686
|
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
540
687
|
return new Promise((resolve, reject) => {
|
|
541
|
-
const ws = new
|
|
688
|
+
const ws = new WebSocket6(wsUrl, { headers: { Cookie: cookieStr } });
|
|
542
689
|
const timer = setTimeout(() => {
|
|
543
690
|
ws.close();
|
|
544
|
-
reject(new DxtradeError("ACCOUNT_POSITIONS_TIMEOUT"
|
|
691
|
+
reject(new DxtradeError("ACCOUNT_POSITIONS_TIMEOUT" /* ACCOUNT_POSITIONS_TIMEOUT */, "Account positions timed out"));
|
|
545
692
|
}, 3e4);
|
|
546
693
|
ws.on("message", (data) => {
|
|
547
694
|
const msg = parseWsData(data);
|
|
@@ -556,7 +703,7 @@ async function getPositions(ctx) {
|
|
|
556
703
|
ws.on("error", (error) => {
|
|
557
704
|
clearTimeout(timer);
|
|
558
705
|
ws.close();
|
|
559
|
-
reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR"
|
|
706
|
+
reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR" /* ACCOUNT_POSITIONS_ERROR */, `Account positions error: ${error.message}`));
|
|
560
707
|
});
|
|
561
708
|
});
|
|
562
709
|
}
|
|
@@ -574,27 +721,31 @@ async function closePosition(ctx, data) {
|
|
|
574
721
|
} catch (error) {
|
|
575
722
|
if (error instanceof DxtradeError) throw error;
|
|
576
723
|
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
577
|
-
ctx.throwError("POSITION_CLOSE_ERROR"
|
|
724
|
+
ctx.throwError("POSITION_CLOSE_ERROR" /* POSITION_CLOSE_ERROR */, `Position close error: ${message}`);
|
|
578
725
|
}
|
|
579
726
|
}
|
|
580
727
|
|
|
581
728
|
// src/domains/session/session.ts
|
|
582
|
-
import
|
|
729
|
+
import WebSocket7 from "ws";
|
|
583
730
|
function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
584
731
|
return new Promise((resolve, reject) => {
|
|
585
|
-
const ws = new
|
|
732
|
+
const ws = new WebSocket7(wsUrl, { headers: { Cookie: cookieStr } });
|
|
733
|
+
let atmosphereId = null;
|
|
586
734
|
const timer = setTimeout(() => {
|
|
587
735
|
ws.close();
|
|
588
736
|
reject(new Error("[dxtrade-api] Handshake timed out"));
|
|
589
737
|
}, timeout);
|
|
590
738
|
ws.on("message", (data) => {
|
|
739
|
+
if (!atmosphereId) {
|
|
740
|
+
atmosphereId = parseAtmosphereId(data);
|
|
741
|
+
}
|
|
591
742
|
const msg = parseWsData(data);
|
|
592
743
|
if (shouldLog(msg, debug)) debugLog(msg);
|
|
593
744
|
if (typeof msg === "string") return;
|
|
594
745
|
if (msg.accountId) {
|
|
595
746
|
clearTimeout(timer);
|
|
596
747
|
ws.close();
|
|
597
|
-
resolve();
|
|
748
|
+
resolve(atmosphereId);
|
|
598
749
|
}
|
|
599
750
|
});
|
|
600
751
|
ws.on("error", (error) => {
|
|
@@ -625,12 +776,12 @@ async function login(ctx) {
|
|
|
625
776
|
ctx.cookies = Cookies.merge(ctx.cookies, incoming);
|
|
626
777
|
ctx.callbacks.onLogin?.();
|
|
627
778
|
} else {
|
|
628
|
-
ctx.throwError("LOGIN_FAILED"
|
|
779
|
+
ctx.throwError("LOGIN_FAILED" /* LOGIN_FAILED */, `Login failed: ${response.status}`);
|
|
629
780
|
}
|
|
630
781
|
} catch (error) {
|
|
631
782
|
if (error instanceof DxtradeError) throw error;
|
|
632
783
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
633
|
-
ctx.throwError("LOGIN_ERROR"
|
|
784
|
+
ctx.throwError("LOGIN_ERROR" /* LOGIN_ERROR */, `Login error: ${message}`);
|
|
634
785
|
}
|
|
635
786
|
}
|
|
636
787
|
async function fetchCsrf(ctx) {
|
|
@@ -648,12 +799,12 @@ async function fetchCsrf(ctx) {
|
|
|
648
799
|
if (csrfMatch) {
|
|
649
800
|
ctx.csrf = csrfMatch[1];
|
|
650
801
|
} else {
|
|
651
|
-
ctx.throwError("CSRF_NOT_FOUND"
|
|
802
|
+
ctx.throwError("CSRF_NOT_FOUND" /* CSRF_NOT_FOUND */, "CSRF token not found");
|
|
652
803
|
}
|
|
653
804
|
} catch (error) {
|
|
654
805
|
if (error instanceof DxtradeError) throw error;
|
|
655
806
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
656
|
-
ctx.throwError("CSRF_ERROR"
|
|
807
|
+
ctx.throwError("CSRF_ERROR" /* CSRF_ERROR */, `CSRF fetch error: ${message}`);
|
|
657
808
|
}
|
|
658
809
|
}
|
|
659
810
|
async function switchAccount(ctx, accountId) {
|
|
@@ -671,19 +822,23 @@ async function switchAccount(ctx, accountId) {
|
|
|
671
822
|
} catch (error) {
|
|
672
823
|
if (error instanceof DxtradeError) throw error;
|
|
673
824
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
674
|
-
ctx.throwError("ACCOUNT_SWITCH_ERROR"
|
|
825
|
+
ctx.throwError("ACCOUNT_SWITCH_ERROR" /* ACCOUNT_SWITCH_ERROR */, `Error switching account: ${message}`);
|
|
675
826
|
}
|
|
676
827
|
}
|
|
677
828
|
async function connect(ctx) {
|
|
678
829
|
await login(ctx);
|
|
679
830
|
await fetchCsrf(ctx);
|
|
680
831
|
if (ctx.debug) clearDebugLog();
|
|
681
|
-
const wsUrl = endpoints.websocket(ctx.broker);
|
|
682
832
|
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
683
|
-
await waitForHandshake(
|
|
833
|
+
ctx.atmosphereId = await waitForHandshake(endpoints.websocket(ctx.broker), cookieStr, 3e4, ctx.debug);
|
|
684
834
|
if (ctx.config.accountId) {
|
|
685
835
|
await switchAccount(ctx, ctx.config.accountId);
|
|
686
|
-
|
|
836
|
+
ctx.atmosphereId = await waitForHandshake(
|
|
837
|
+
endpoints.websocket(ctx.broker, ctx.atmosphereId),
|
|
838
|
+
Cookies.serialize(ctx.cookies),
|
|
839
|
+
3e4,
|
|
840
|
+
ctx.debug
|
|
841
|
+
);
|
|
687
842
|
}
|
|
688
843
|
}
|
|
689
844
|
|
|
@@ -697,12 +852,16 @@ var DxtradeClient = class {
|
|
|
697
852
|
callbacks,
|
|
698
853
|
cookies: {},
|
|
699
854
|
csrf: null,
|
|
855
|
+
atmosphereId: null,
|
|
700
856
|
broker: config.broker,
|
|
701
857
|
retries: config.retries ?? 3,
|
|
702
858
|
debug: config.debug ?? false,
|
|
703
859
|
ensureSession() {
|
|
704
860
|
if (!this.csrf) {
|
|
705
|
-
throw new DxtradeError(
|
|
861
|
+
throw new DxtradeError(
|
|
862
|
+
"NO_SESSION" /* NO_SESSION */,
|
|
863
|
+
"No active session. Call login() and fetchCsrf() or connect() first."
|
|
864
|
+
);
|
|
706
865
|
}
|
|
707
866
|
},
|
|
708
867
|
throwError(code, message) {
|
|
@@ -775,12 +934,24 @@ var DxtradeClient = class {
|
|
|
775
934
|
async getAssessments(params) {
|
|
776
935
|
return getAssessments(this._ctx, params);
|
|
777
936
|
}
|
|
937
|
+
/**
|
|
938
|
+
* Fetch OHLC price bars for a symbol.
|
|
939
|
+
* @param params.symbol - Instrument symbol (e.g. "EURUSD")
|
|
940
|
+
* @param params.resolution - Bar period in seconds (default: 60 = 1 min)
|
|
941
|
+
* @param params.range - Lookback window in seconds (default: 432000 = 5 days)
|
|
942
|
+
* @param params.maxBars - Maximum bars to return (default: 3500)
|
|
943
|
+
* @param params.priceField - "bid" or "ask" (default: "bid")
|
|
944
|
+
*/
|
|
945
|
+
async getOHLC(params) {
|
|
946
|
+
return getOHLC(this._ctx, params);
|
|
947
|
+
}
|
|
778
948
|
};
|
|
779
949
|
export {
|
|
780
950
|
ACTION,
|
|
781
951
|
BROKER,
|
|
782
952
|
DxtradeClient,
|
|
783
953
|
DxtradeError,
|
|
954
|
+
ERROR,
|
|
784
955
|
ORDER_TYPE,
|
|
785
956
|
SIDE,
|
|
786
957
|
TIF,
|