@danielgroen/dxtrade-api 1.0.24 → 1.0.26
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 +14 -11
- package/dist/index.d.mts +147 -96
- package/dist/index.d.ts +147 -96
- package/dist/index.js +1087 -776
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1084 -776
- package/dist/index.mjs.map +1 -1
- package/llms.txt +5 -4
- package/package.json +4 -2
package/dist/index.mjs
CHANGED
|
@@ -70,7 +70,9 @@ var ERROR = /* @__PURE__ */ ((ERROR2) => {
|
|
|
70
70
|
ERROR2["ORDERS_TIMEOUT"] = "ORDERS_TIMEOUT";
|
|
71
71
|
ERROR2["ORDERS_ERROR"] = "ORDERS_ERROR";
|
|
72
72
|
ERROR2["CANCEL_ORDER_ERROR"] = "CANCEL_ORDER_ERROR";
|
|
73
|
+
ERROR2["POSITION_NOT_FOUND"] = "POSITION_NOT_FOUND";
|
|
73
74
|
ERROR2["POSITION_CLOSE_ERROR"] = "POSITION_CLOSE_ERROR";
|
|
75
|
+
ERROR2["POSITION_CLOSE_TIMEOUT"] = "POSITION_CLOSE_TIMEOUT";
|
|
74
76
|
ERROR2["POSITION_METRICS_TIMEOUT"] = "POSITION_METRICS_TIMEOUT";
|
|
75
77
|
ERROR2["POSITION_METRICS_ERROR"] = "POSITION_METRICS_ERROR";
|
|
76
78
|
ERROR2["ACCOUNT_METRICS_TIMEOUT"] = "ACCOUNT_METRICS_TIMEOUT";
|
|
@@ -85,6 +87,22 @@ var ERROR = /* @__PURE__ */ ((ERROR2) => {
|
|
|
85
87
|
ERROR2["STREAM_REQUIRES_CONNECT"] = "STREAM_REQUIRES_CONNECT";
|
|
86
88
|
return ERROR2;
|
|
87
89
|
})(ERROR || {});
|
|
90
|
+
var MESSAGE_CATEGORY = /* @__PURE__ */ ((MESSAGE_CATEGORY2) => {
|
|
91
|
+
MESSAGE_CATEGORY2["TRADE_LOG"] = "TRADE_LOG";
|
|
92
|
+
MESSAGE_CATEGORY2["NOTIFICATION"] = "NOTIFICATION";
|
|
93
|
+
return MESSAGE_CATEGORY2;
|
|
94
|
+
})(MESSAGE_CATEGORY || {});
|
|
95
|
+
var MESSAGE_TYPE = /* @__PURE__ */ ((MESSAGE_TYPE2) => {
|
|
96
|
+
MESSAGE_TYPE2["ORDER"] = "ORDER";
|
|
97
|
+
MESSAGE_TYPE2["INSTRUMENT_ACTIVATED"] = "INSTRUMENT_ACTIVATED";
|
|
98
|
+
return MESSAGE_TYPE2;
|
|
99
|
+
})(MESSAGE_TYPE || {});
|
|
100
|
+
var ORDER_STATUS = /* @__PURE__ */ ((ORDER_STATUS2) => {
|
|
101
|
+
ORDER_STATUS2["PLACED"] = "PLACED";
|
|
102
|
+
ORDER_STATUS2["FILLED"] = "FILLED";
|
|
103
|
+
ORDER_STATUS2["REJECTED"] = "REJECTED";
|
|
104
|
+
return ORDER_STATUS2;
|
|
105
|
+
})(ORDER_STATUS || {});
|
|
88
106
|
var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
|
|
89
107
|
WS_MESSAGE2["ACCOUNT_METRICS"] = "ACCOUNT_METRICS";
|
|
90
108
|
WS_MESSAGE2["ACCOUNTS"] = "ACCOUNTS";
|
|
@@ -107,6 +125,7 @@ var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
|
|
|
107
125
|
let SUBTOPIC;
|
|
108
126
|
((SUBTOPIC2) => {
|
|
109
127
|
SUBTOPIC2["BIG_CHART_COMPONENT"] = "BigChartComponentPresenter-4";
|
|
128
|
+
SUBTOPIC2["OHLC_STREAM"] = "OHLCStreamPresenter-0";
|
|
110
129
|
})(SUBTOPIC = WS_MESSAGE2.SUBTOPIC || (WS_MESSAGE2.SUBTOPIC = {}));
|
|
111
130
|
})(WS_MESSAGE || (WS_MESSAGE = {}));
|
|
112
131
|
|
|
@@ -149,7 +168,10 @@ var Cookies = class {
|
|
|
149
168
|
function baseHeaders() {
|
|
150
169
|
return {
|
|
151
170
|
"Content-Type": "application/json; charset=UTF-8",
|
|
152
|
-
|
|
171
|
+
Accept: "*/*",
|
|
172
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
173
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
174
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:147.0) Gecko/20100101 Firefox/147.0"
|
|
153
175
|
};
|
|
154
176
|
}
|
|
155
177
|
function authHeaders(csrf, cookieStr) {
|
|
@@ -175,7 +197,11 @@ async function retryRequest(config, retries = 3) {
|
|
|
175
197
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
176
198
|
console.warn(`[dxtrade-api] Attempt ${attempt} failed: ${message}`, config.url);
|
|
177
199
|
if (isAxiosError(error) && error.response?.status === 429) {
|
|
178
|
-
|
|
200
|
+
if (attempt === retries) {
|
|
201
|
+
throw new DxtradeError("RATE_LIMITED" /* RATE_LIMITED */, "Rate limited (429). Too many requests \u2014 try again later.");
|
|
202
|
+
}
|
|
203
|
+
await new Promise((res) => setTimeout(res, 3e3 * attempt));
|
|
204
|
+
continue;
|
|
179
205
|
}
|
|
180
206
|
if (attempt === retries) throw error;
|
|
181
207
|
await new Promise((res) => setTimeout(res, 1e3 * attempt));
|
|
@@ -208,6 +234,11 @@ function parseAtmosphereId(data) {
|
|
|
208
234
|
}
|
|
209
235
|
return null;
|
|
210
236
|
}
|
|
237
|
+
function checkWsRateLimit(error) {
|
|
238
|
+
if (error.message.includes("429")) {
|
|
239
|
+
throw new DxtradeError("RATE_LIMITED" /* RATE_LIMITED */, "Rate limited (429). Too many requests \u2014 try again later.");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
211
242
|
function parseWsData(data) {
|
|
212
243
|
const raw = data.toString();
|
|
213
244
|
const pipeIndex = raw.indexOf("|");
|
|
@@ -241,10 +272,12 @@ var WsManager = class extends EventEmitter {
|
|
|
241
272
|
this.emit(payload.type, payload.body);
|
|
242
273
|
});
|
|
243
274
|
ws.on("error", (error) => {
|
|
275
|
+
checkWsRateLimit(error);
|
|
276
|
+
const err = new Error(`WebSocket manager error: ${error.message}`);
|
|
244
277
|
if (!this._ws) {
|
|
245
|
-
return reject(
|
|
278
|
+
return reject(err);
|
|
246
279
|
}
|
|
247
|
-
this.emit("error",
|
|
280
|
+
this.emit("error", err);
|
|
248
281
|
});
|
|
249
282
|
ws.on("close", () => {
|
|
250
283
|
this._ws = null;
|
|
@@ -286,258 +319,395 @@ var WsManager = class extends EventEmitter {
|
|
|
286
319
|
};
|
|
287
320
|
|
|
288
321
|
// src/domains/account/account.ts
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const body = await ctx.wsManager.waitFor("ACCOUNT_METRICS" /* ACCOUNT_METRICS */, timeout);
|
|
293
|
-
return body.allMetrics;
|
|
322
|
+
var AccountDomain = class {
|
|
323
|
+
constructor(_ctx) {
|
|
324
|
+
this._ctx = _ctx;
|
|
294
325
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
326
|
+
/** Get account metrics including equity, balance, margin, and open P&L. */
|
|
327
|
+
async metrics(timeout = 3e4) {
|
|
328
|
+
this._ctx.ensureSession();
|
|
329
|
+
if (this._ctx.wsManager) {
|
|
330
|
+
const body = await this._ctx.wsManager.waitFor(
|
|
331
|
+
"ACCOUNT_METRICS" /* ACCOUNT_METRICS */,
|
|
332
|
+
timeout
|
|
333
|
+
);
|
|
334
|
+
return body.allMetrics;
|
|
335
|
+
}
|
|
336
|
+
const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
|
|
337
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
338
|
+
return new Promise((resolve, reject) => {
|
|
339
|
+
const ws = new WebSocket2(wsUrl, { headers: { Cookie: cookieStr } });
|
|
340
|
+
const timer = setTimeout(() => {
|
|
341
|
+
ws.close();
|
|
342
|
+
reject(new DxtradeError("ACCOUNT_METRICS_TIMEOUT" /* ACCOUNT_METRICS_TIMEOUT */, "Account metrics timed out"));
|
|
343
|
+
}, timeout);
|
|
344
|
+
ws.on("message", (data) => {
|
|
345
|
+
const msg = parseWsData(data);
|
|
346
|
+
if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
|
|
347
|
+
if (typeof msg === "string") return;
|
|
348
|
+
if (msg.type === "ACCOUNT_METRICS" /* ACCOUNT_METRICS */) {
|
|
349
|
+
clearTimeout(timer);
|
|
350
|
+
ws.close();
|
|
351
|
+
const body = msg.body;
|
|
352
|
+
resolve(body.allMetrics);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
ws.on("error", (error) => {
|
|
308
356
|
clearTimeout(timer);
|
|
309
357
|
ws.close();
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
ws.on("error", (error) => {
|
|
315
|
-
clearTimeout(timer);
|
|
316
|
-
ws.close();
|
|
317
|
-
reject(new DxtradeError("ACCOUNT_METRICS_ERROR" /* ACCOUNT_METRICS_ERROR */, `Account metrics error: ${error.message}`));
|
|
358
|
+
checkWsRateLimit(error);
|
|
359
|
+
reject(new DxtradeError("ACCOUNT_METRICS_ERROR" /* ACCOUNT_METRICS_ERROR */, `Account metrics error: ${error.message}`));
|
|
360
|
+
});
|
|
318
361
|
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Fetch trade history for a date range.
|
|
365
|
+
* @param params.from - Start timestamp (Unix ms)
|
|
366
|
+
* @param params.to - End timestamp (Unix ms)
|
|
367
|
+
*/
|
|
368
|
+
async tradeHistory(params) {
|
|
369
|
+
this._ctx.ensureSession();
|
|
370
|
+
try {
|
|
371
|
+
const response = await retryRequest(
|
|
372
|
+
{
|
|
373
|
+
method: "POST",
|
|
374
|
+
url: endpoints.tradeHistory(this._ctx.broker, params),
|
|
375
|
+
headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
|
|
376
|
+
},
|
|
377
|
+
this._ctx.retries
|
|
378
|
+
);
|
|
379
|
+
if (response.status === 200) {
|
|
380
|
+
const setCookies = response.headers["set-cookie"] ?? [];
|
|
381
|
+
const incoming = Cookies.parse(setCookies);
|
|
382
|
+
this._ctx.cookies = Cookies.merge(this._ctx.cookies, incoming);
|
|
383
|
+
return response.data;
|
|
384
|
+
} else {
|
|
385
|
+
this._ctx.throwError("TRADE_HISTORY_ERROR" /* TRADE_HISTORY_ERROR */, `Trade history failed: ${response.status}`);
|
|
386
|
+
}
|
|
387
|
+
} catch (error) {
|
|
388
|
+
if (error instanceof DxtradeError) throw error;
|
|
389
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
390
|
+
this._ctx.throwError("TRADE_HISTORY_ERROR" /* TRADE_HISTORY_ERROR */, `Trade history error: ${message}`);
|
|
339
391
|
}
|
|
340
|
-
} catch (error) {
|
|
341
|
-
if (error instanceof DxtradeError) throw error;
|
|
342
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
343
|
-
ctx.throwError("TRADE_HISTORY_ERROR" /* TRADE_HISTORY_ERROR */, `Trade history error: ${message}`);
|
|
344
392
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
393
|
+
/**
|
|
394
|
+
* Fetch trade journal entries for a date range.
|
|
395
|
+
* @param params.from - Start timestamp (Unix ms)
|
|
396
|
+
* @param params.to - End timestamp (Unix ms)
|
|
397
|
+
*/
|
|
398
|
+
async tradeJournal(params) {
|
|
399
|
+
this._ctx.ensureSession();
|
|
400
|
+
try {
|
|
401
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
402
|
+
const response = await retryRequest(
|
|
403
|
+
{
|
|
404
|
+
method: "GET",
|
|
405
|
+
url: endpoints.tradeJournal(this._ctx.broker, params),
|
|
406
|
+
headers: { ...baseHeaders(), Cookie: cookieStr }
|
|
407
|
+
},
|
|
408
|
+
this._ctx.retries
|
|
409
|
+
);
|
|
410
|
+
if (response.status === 200) {
|
|
411
|
+
const setCookies = response.headers["set-cookie"] ?? [];
|
|
412
|
+
const incoming = Cookies.parse(setCookies);
|
|
413
|
+
this._ctx.cookies = Cookies.merge(this._ctx.cookies, incoming);
|
|
414
|
+
return response.data;
|
|
415
|
+
} else {
|
|
416
|
+
this._ctx.throwError("TRADE_JOURNAL_ERROR" /* TRADE_JOURNAL_ERROR */, `Login failed: ${response.status}`);
|
|
417
|
+
}
|
|
418
|
+
} catch (error) {
|
|
419
|
+
if (error instanceof DxtradeError) throw error;
|
|
420
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
421
|
+
this._ctx.throwError("TRADE_JOURNAL_ERROR" /* TRADE_JOURNAL_ERROR */, `Trade journal error: ${message}`);
|
|
365
422
|
}
|
|
366
|
-
} catch (error) {
|
|
367
|
-
if (error instanceof DxtradeError) throw error;
|
|
368
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
369
|
-
ctx.throwError("TRADE_JOURNAL_ERROR" /* TRADE_JOURNAL_ERROR */, `Trade journal error: ${message}`);
|
|
370
423
|
}
|
|
371
|
-
}
|
|
424
|
+
};
|
|
372
425
|
|
|
373
426
|
// src/domains/assessments/assessments.ts
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
427
|
+
var AssessmentsDomain = class {
|
|
428
|
+
constructor(_ctx) {
|
|
429
|
+
this._ctx = _ctx;
|
|
430
|
+
}
|
|
431
|
+
/** Fetch PnL assessments for an instrument within a date range. */
|
|
432
|
+
async get(params) {
|
|
433
|
+
this._ctx.ensureSession();
|
|
434
|
+
try {
|
|
435
|
+
const response = await retryRequest(
|
|
436
|
+
{
|
|
437
|
+
method: "POST",
|
|
438
|
+
url: endpoints.assessments(this._ctx.broker),
|
|
439
|
+
data: {
|
|
440
|
+
from: params.from,
|
|
441
|
+
instrument: params.instrument,
|
|
442
|
+
subtype: params.subtype ?? null,
|
|
443
|
+
to: params.to
|
|
444
|
+
},
|
|
445
|
+
headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
|
|
386
446
|
},
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
)
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
ctx.throwError("ASSESSMENTS_ERROR" /* ASSESSMENTS_ERROR */, `Error fetching assessments: ${message}`);
|
|
447
|
+
this._ctx.retries
|
|
448
|
+
);
|
|
449
|
+
return response.data;
|
|
450
|
+
} catch (error) {
|
|
451
|
+
if (error instanceof DxtradeError) throw error;
|
|
452
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
453
|
+
this._ctx.throwError("ASSESSMENTS_ERROR" /* ASSESSMENTS_ERROR */, `Error fetching assessments: ${message}`);
|
|
454
|
+
}
|
|
396
455
|
}
|
|
397
|
-
}
|
|
456
|
+
};
|
|
398
457
|
|
|
399
458
|
// src/domains/instrument/instrument.ts
|
|
400
459
|
import WebSocket3 from "ws";
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
460
|
+
var InstrumentsDomain = class {
|
|
461
|
+
constructor(_ctx) {
|
|
462
|
+
this._ctx = _ctx;
|
|
463
|
+
}
|
|
464
|
+
/** Get all available instruments, optionally filtered by partial match (e.g. `{ type: "FOREX" }`). */
|
|
465
|
+
async get(params = {}, timeout = 3e4) {
|
|
466
|
+
this._ctx.ensureSession();
|
|
467
|
+
const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
|
|
468
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
469
|
+
return new Promise((resolve, reject) => {
|
|
470
|
+
const ws = new WebSocket3(wsUrl, { headers: { Cookie: cookieStr } });
|
|
471
|
+
const timer = setTimeout(() => {
|
|
472
|
+
ws.close();
|
|
473
|
+
reject(new DxtradeError("INSTRUMENTS_TIMEOUT" /* INSTRUMENTS_TIMEOUT */, "Instruments request timed out"));
|
|
474
|
+
}, timeout);
|
|
475
|
+
let instruments = [];
|
|
476
|
+
let settleTimer = null;
|
|
477
|
+
ws.on("message", (data) => {
|
|
478
|
+
const msg = parseWsData(data);
|
|
479
|
+
if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
|
|
480
|
+
if (typeof msg === "string") return;
|
|
481
|
+
if (msg.type === "INSTRUMENTS" /* INSTRUMENTS */) {
|
|
482
|
+
instruments.push(...msg.body);
|
|
483
|
+
if (settleTimer) clearTimeout(settleTimer);
|
|
484
|
+
settleTimer = setTimeout(() => {
|
|
485
|
+
clearTimeout(timer);
|
|
486
|
+
ws.close();
|
|
487
|
+
resolve(
|
|
488
|
+
instruments.filter((instrument) => {
|
|
489
|
+
for (const key in params) {
|
|
490
|
+
if (params[key] !== instrument[key]) {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
428
493
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
494
|
+
return true;
|
|
495
|
+
})
|
|
496
|
+
);
|
|
497
|
+
}, 200);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
ws.on("error", (error) => {
|
|
501
|
+
clearTimeout(timer);
|
|
502
|
+
ws.close();
|
|
503
|
+
checkWsRateLimit(error);
|
|
504
|
+
reject(new DxtradeError("INSTRUMENTS_ERROR" /* INSTRUMENTS_ERROR */, `Instruments error: ${error.message}`));
|
|
505
|
+
});
|
|
440
506
|
});
|
|
441
|
-
}
|
|
442
|
-
}
|
|
507
|
+
}
|
|
508
|
+
};
|
|
443
509
|
|
|
444
510
|
// src/domains/ohlc/ohlc.ts
|
|
445
511
|
import WebSocket4 from "ws";
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
let barSettleTimer = null;
|
|
458
|
-
const timer = setTimeout(() => {
|
|
459
|
-
ws.close();
|
|
460
|
-
reject(new DxtradeError("OHLC_TIMEOUT" /* OHLC_TIMEOUT */, "OHLC data timed out"));
|
|
461
|
-
}, timeout);
|
|
462
|
-
function cleanup() {
|
|
463
|
-
clearTimeout(timer);
|
|
464
|
-
if (initSettleTimer) clearTimeout(initSettleTimer);
|
|
465
|
-
if (barSettleTimer) clearTimeout(barSettleTimer);
|
|
466
|
-
ws.close();
|
|
512
|
+
var OhlcDomain = class {
|
|
513
|
+
constructor(_ctx) {
|
|
514
|
+
this._ctx = _ctx;
|
|
515
|
+
}
|
|
516
|
+
/** Stream real-time OHLC bar updates. Requires connect(). Returns unsubscribe function. */
|
|
517
|
+
async stream(params, callback) {
|
|
518
|
+
if (!this._ctx.wsManager) {
|
|
519
|
+
this._ctx.throwError(
|
|
520
|
+
"STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
|
|
521
|
+
"Streaming requires a persistent WebSocket. Use connect() instead of auth()."
|
|
522
|
+
);
|
|
467
523
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
);
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
{
|
|
488
|
-
aggregationPeriodSeconds: resolution,
|
|
489
|
-
extendedSession: true,
|
|
490
|
-
forexPriceField: priceField,
|
|
491
|
-
id: 0,
|
|
492
|
-
maxBarsCount: maxBars,
|
|
493
|
-
range,
|
|
494
|
-
studySubscription: [],
|
|
495
|
-
subtopic: WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT,
|
|
496
|
-
symbol
|
|
497
|
-
}
|
|
498
|
-
]
|
|
499
|
-
},
|
|
500
|
-
headers
|
|
501
|
-
},
|
|
502
|
-
ctx.retries
|
|
503
|
-
);
|
|
504
|
-
} catch (error) {
|
|
505
|
-
cleanup();
|
|
506
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
507
|
-
reject(new DxtradeError("OHLC_ERROR" /* OHLC_ERROR */, `Error fetching OHLC data: ${message}`));
|
|
524
|
+
const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
|
|
525
|
+
const subtopic = WS_MESSAGE.SUBTOPIC.OHLC_STREAM;
|
|
526
|
+
const headers = authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies));
|
|
527
|
+
const snapshotBars = [];
|
|
528
|
+
let snapshotDone = false;
|
|
529
|
+
let resolveSnapshot = null;
|
|
530
|
+
const onChartFeed = (body) => {
|
|
531
|
+
if (body?.subtopic !== subtopic) return;
|
|
532
|
+
const data = body.data;
|
|
533
|
+
if (!Array.isArray(data)) return;
|
|
534
|
+
if (!snapshotDone) {
|
|
535
|
+
snapshotBars.push(...data);
|
|
536
|
+
if (body.snapshotEnd) {
|
|
537
|
+
snapshotDone = true;
|
|
538
|
+
callback([...snapshotBars]);
|
|
539
|
+
resolveSnapshot?.();
|
|
540
|
+
}
|
|
541
|
+
} else {
|
|
542
|
+
callback(data);
|
|
508
543
|
}
|
|
544
|
+
};
|
|
545
|
+
this._ctx.wsManager.on("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
|
|
546
|
+
try {
|
|
547
|
+
await retryRequest(
|
|
548
|
+
{
|
|
549
|
+
method: "PUT",
|
|
550
|
+
url: endpoints.subscribeInstruments(this._ctx.broker),
|
|
551
|
+
data: { instruments: [symbol] },
|
|
552
|
+
headers
|
|
553
|
+
},
|
|
554
|
+
this._ctx.retries
|
|
555
|
+
);
|
|
556
|
+
await retryRequest(
|
|
557
|
+
{
|
|
558
|
+
method: "PUT",
|
|
559
|
+
url: endpoints.charts(this._ctx.broker),
|
|
560
|
+
data: {
|
|
561
|
+
chartIds: [],
|
|
562
|
+
requests: [
|
|
563
|
+
{
|
|
564
|
+
aggregationPeriodSeconds: resolution,
|
|
565
|
+
extendedSession: true,
|
|
566
|
+
forexPriceField: priceField,
|
|
567
|
+
id: 0,
|
|
568
|
+
maxBarsCount: maxBars,
|
|
569
|
+
range,
|
|
570
|
+
studySubscription: [],
|
|
571
|
+
subtopic,
|
|
572
|
+
symbol
|
|
573
|
+
}
|
|
574
|
+
]
|
|
575
|
+
},
|
|
576
|
+
headers
|
|
577
|
+
},
|
|
578
|
+
this._ctx.retries
|
|
579
|
+
);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
this._ctx.wsManager.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
|
|
582
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
583
|
+
this._ctx.throwError("OHLC_ERROR" /* OHLC_ERROR */, `OHLC stream subscription error: ${message}`);
|
|
509
584
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
585
|
+
await new Promise((resolve, reject) => {
|
|
586
|
+
if (snapshotDone) return resolve();
|
|
587
|
+
const timer = setTimeout(() => {
|
|
588
|
+
if (snapshotBars.length > 0) {
|
|
589
|
+
snapshotDone = true;
|
|
590
|
+
callback([...snapshotBars]);
|
|
591
|
+
resolve();
|
|
592
|
+
} else {
|
|
593
|
+
this._ctx.wsManager?.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
|
|
594
|
+
reject(new DxtradeError("OHLC_TIMEOUT" /* OHLC_TIMEOUT */, "OHLC stream snapshot timed out"));
|
|
595
|
+
}
|
|
596
|
+
}, 3e4);
|
|
597
|
+
resolveSnapshot = () => {
|
|
598
|
+
clearTimeout(timer);
|
|
599
|
+
resolve();
|
|
600
|
+
};
|
|
601
|
+
});
|
|
602
|
+
return () => {
|
|
603
|
+
this._ctx.wsManager?.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Fetch OHLC price bars for a symbol.
|
|
608
|
+
* @param params.symbol - Instrument symbol (e.g. "EURUSD")
|
|
609
|
+
* @param params.resolution - Bar period in seconds (default: 60 = 1 min)
|
|
610
|
+
* @param params.range - Lookback window in seconds (default: 432000 = 5 days)
|
|
611
|
+
* @param params.maxBars - Maximum bars to return (default: 3500)
|
|
612
|
+
* @param params.priceField - "bid" or "ask" (default: "bid")
|
|
613
|
+
*/
|
|
614
|
+
async get(params, timeout = 3e4) {
|
|
615
|
+
this._ctx.ensureSession();
|
|
616
|
+
const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
|
|
617
|
+
const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
|
|
618
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
619
|
+
const headers = authHeaders(this._ctx.csrf, cookieStr);
|
|
620
|
+
return new Promise((resolve, reject) => {
|
|
621
|
+
const ws = new WebSocket4(wsUrl, { headers: { Cookie: cookieStr } });
|
|
622
|
+
const bars = [];
|
|
623
|
+
let putsSent = false;
|
|
624
|
+
let initSettleTimer = null;
|
|
625
|
+
let barSettleTimer = null;
|
|
626
|
+
const timer = setTimeout(() => {
|
|
627
|
+
ws.close();
|
|
628
|
+
reject(new DxtradeError("OHLC_TIMEOUT" /* OHLC_TIMEOUT */, "OHLC data timed out"));
|
|
629
|
+
}, timeout);
|
|
630
|
+
function cleanup() {
|
|
631
|
+
clearTimeout(timer);
|
|
515
632
|
if (initSettleTimer) clearTimeout(initSettleTimer);
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
const body = msg.body;
|
|
520
|
-
if (body?.subtopic !== WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT) return;
|
|
521
|
-
if (Array.isArray(body.data)) {
|
|
522
|
-
bars.push(...body.data);
|
|
633
|
+
if (barSettleTimer) clearTimeout(barSettleTimer);
|
|
634
|
+
ws.close();
|
|
523
635
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
636
|
+
const sendPuts = async () => {
|
|
637
|
+
putsSent = true;
|
|
638
|
+
try {
|
|
639
|
+
await retryRequest(
|
|
640
|
+
{
|
|
641
|
+
method: "PUT",
|
|
642
|
+
url: endpoints.subscribeInstruments(this._ctx.broker),
|
|
643
|
+
data: { instruments: [symbol] },
|
|
644
|
+
headers
|
|
645
|
+
},
|
|
646
|
+
this._ctx.retries
|
|
647
|
+
);
|
|
648
|
+
await retryRequest(
|
|
649
|
+
{
|
|
650
|
+
method: "PUT",
|
|
651
|
+
url: endpoints.charts(this._ctx.broker),
|
|
652
|
+
data: {
|
|
653
|
+
chartIds: [],
|
|
654
|
+
requests: [
|
|
655
|
+
{
|
|
656
|
+
aggregationPeriodSeconds: resolution,
|
|
657
|
+
extendedSession: true,
|
|
658
|
+
forexPriceField: priceField,
|
|
659
|
+
id: 0,
|
|
660
|
+
maxBarsCount: maxBars,
|
|
661
|
+
range,
|
|
662
|
+
studySubscription: [],
|
|
663
|
+
subtopic: WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT,
|
|
664
|
+
symbol
|
|
665
|
+
}
|
|
666
|
+
]
|
|
667
|
+
},
|
|
668
|
+
headers
|
|
669
|
+
},
|
|
670
|
+
this._ctx.retries
|
|
671
|
+
);
|
|
672
|
+
} catch (error) {
|
|
673
|
+
cleanup();
|
|
674
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
675
|
+
reject(new DxtradeError("OHLC_ERROR" /* OHLC_ERROR */, `Error fetching OHLC data: ${message}`));
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
ws.on("message", (data) => {
|
|
679
|
+
const msg = parseWsData(data);
|
|
680
|
+
if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
|
|
681
|
+
if (typeof msg === "string") return;
|
|
682
|
+
if (!putsSent) {
|
|
683
|
+
if (initSettleTimer) clearTimeout(initSettleTimer);
|
|
684
|
+
initSettleTimer = setTimeout(() => sendPuts(), 1e3);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
const body = msg.body;
|
|
688
|
+
if (body?.subtopic !== WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT) return;
|
|
689
|
+
if (Array.isArray(body.data)) {
|
|
690
|
+
bars.push(...body.data);
|
|
691
|
+
}
|
|
692
|
+
if (barSettleTimer) clearTimeout(barSettleTimer);
|
|
693
|
+
if (body.snapshotEnd) {
|
|
530
694
|
cleanup();
|
|
531
695
|
resolve(bars);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
696
|
+
} else {
|
|
697
|
+
barSettleTimer = setTimeout(() => {
|
|
698
|
+
cleanup();
|
|
699
|
+
resolve(bars);
|
|
700
|
+
}, 2e3);
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
ws.on("error", (error) => {
|
|
704
|
+
cleanup();
|
|
705
|
+
checkWsRateLimit(error);
|
|
706
|
+
reject(new DxtradeError("OHLC_ERROR" /* OHLC_ERROR */, `OHLC WebSocket error: ${error.message}`));
|
|
707
|
+
});
|
|
538
708
|
});
|
|
539
|
-
}
|
|
540
|
-
}
|
|
709
|
+
}
|
|
710
|
+
};
|
|
541
711
|
|
|
542
712
|
// src/domains/order/order.ts
|
|
543
713
|
import crypto from "crypto";
|
|
@@ -545,88 +715,97 @@ import WebSocket6 from "ws";
|
|
|
545
715
|
|
|
546
716
|
// src/domains/symbol/symbol.ts
|
|
547
717
|
import WebSocket5 from "ws";
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
552
|
-
const response = await retryRequest(
|
|
553
|
-
{
|
|
554
|
-
method: "GET",
|
|
555
|
-
url: endpoints.suggest(ctx.broker, text),
|
|
556
|
-
headers: { ...baseHeaders(), Cookie: cookieStr }
|
|
557
|
-
},
|
|
558
|
-
ctx.retries
|
|
559
|
-
);
|
|
560
|
-
const suggests = response.data?.suggests;
|
|
561
|
-
if (!suggests?.length) {
|
|
562
|
-
ctx.throwError("NO_SUGGESTIONS" /* NO_SUGGESTIONS */, "No symbol suggestions found");
|
|
563
|
-
}
|
|
564
|
-
return suggests;
|
|
565
|
-
} catch (error) {
|
|
566
|
-
if (error instanceof DxtradeError) throw error;
|
|
567
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
568
|
-
ctx.throwError("SUGGEST_ERROR" /* SUGGEST_ERROR */, `Error getting symbol suggestions: ${message}`);
|
|
718
|
+
var SymbolsDomain = class {
|
|
719
|
+
constructor(_ctx) {
|
|
720
|
+
this._ctx = _ctx;
|
|
569
721
|
}
|
|
570
|
-
|
|
571
|
-
async
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
722
|
+
/** Search for symbols matching the given text (e.g. "EURUSD", "BTC"). */
|
|
723
|
+
async search(text) {
|
|
724
|
+
this._ctx.ensureSession();
|
|
725
|
+
try {
|
|
726
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
727
|
+
const response = await retryRequest(
|
|
728
|
+
{
|
|
729
|
+
method: "GET",
|
|
730
|
+
url: endpoints.suggest(this._ctx.broker, text),
|
|
731
|
+
headers: { ...baseHeaders(), Cookie: cookieStr }
|
|
732
|
+
},
|
|
733
|
+
this._ctx.retries
|
|
734
|
+
);
|
|
735
|
+
const suggests = response.data?.suggests;
|
|
736
|
+
if (!suggests?.length) {
|
|
737
|
+
this._ctx.throwError("NO_SUGGESTIONS" /* NO_SUGGESTIONS */, "No symbol suggestions found");
|
|
738
|
+
}
|
|
739
|
+
return suggests;
|
|
740
|
+
} catch (error) {
|
|
741
|
+
if (error instanceof DxtradeError) throw error;
|
|
742
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
743
|
+
this._ctx.throwError("SUGGEST_ERROR" /* SUGGEST_ERROR */, `Error getting symbol suggestions: ${message}`);
|
|
586
744
|
}
|
|
587
|
-
return response.data;
|
|
588
|
-
} catch (error) {
|
|
589
|
-
if (error instanceof DxtradeError) throw error;
|
|
590
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
591
|
-
ctx.throwError("SYMBOL_INFO_ERROR" /* SYMBOL_INFO_ERROR */, `Error getting symbol info: ${message}`);
|
|
592
745
|
}
|
|
593
|
-
|
|
594
|
-
async
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if (typeof msg === "string") return;
|
|
610
|
-
if (msg.type === "LIMITS" /* LIMITS */) {
|
|
611
|
-
const batch = msg.body;
|
|
612
|
-
if (batch.length === 0) return;
|
|
613
|
-
limits.push(...batch);
|
|
614
|
-
if (settleTimer) clearTimeout(settleTimer);
|
|
615
|
-
settleTimer = setTimeout(() => {
|
|
616
|
-
clearTimeout(timer);
|
|
617
|
-
ws.close();
|
|
618
|
-
resolve(limits);
|
|
619
|
-
}, 200);
|
|
746
|
+
/** Get detailed instrument info for a symbol, including volume limits and lot size. */
|
|
747
|
+
async info(symbol) {
|
|
748
|
+
this._ctx.ensureSession();
|
|
749
|
+
try {
|
|
750
|
+
const offsetMinutes = Math.abs((/* @__PURE__ */ new Date()).getTimezoneOffset());
|
|
751
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
752
|
+
const response = await retryRequest(
|
|
753
|
+
{
|
|
754
|
+
method: "GET",
|
|
755
|
+
url: endpoints.instrumentInfo(this._ctx.broker, symbol, offsetMinutes),
|
|
756
|
+
headers: { ...baseHeaders(), Cookie: cookieStr }
|
|
757
|
+
},
|
|
758
|
+
this._ctx.retries
|
|
759
|
+
);
|
|
760
|
+
if (!response.data) {
|
|
761
|
+
this._ctx.throwError("NO_SYMBOL_INFO" /* NO_SYMBOL_INFO */, "No symbol info returned");
|
|
620
762
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
|
|
763
|
+
return response.data;
|
|
764
|
+
} catch (error) {
|
|
765
|
+
if (error instanceof DxtradeError) throw error;
|
|
766
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
767
|
+
this._ctx.throwError("SYMBOL_INFO_ERROR" /* SYMBOL_INFO_ERROR */, `Error getting symbol info: ${message}`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
/** Get order size limits and stop/limit distances for all symbols. */
|
|
771
|
+
async limits(timeout = 3e4) {
|
|
772
|
+
this._ctx.ensureSession();
|
|
773
|
+
const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
|
|
774
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
775
|
+
return new Promise((resolve, reject) => {
|
|
776
|
+
const ws = new WebSocket5(wsUrl, { headers: { Cookie: cookieStr } });
|
|
777
|
+
const timer = setTimeout(() => {
|
|
778
|
+
ws.close();
|
|
779
|
+
reject(new DxtradeError("LIMITS_TIMEOUT" /* LIMITS_TIMEOUT */, "Symbol limits request timed out"));
|
|
780
|
+
}, timeout);
|
|
781
|
+
let limits = [];
|
|
782
|
+
let settleTimer = null;
|
|
783
|
+
ws.on("message", (data) => {
|
|
784
|
+
const msg = parseWsData(data);
|
|
785
|
+
if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
|
|
786
|
+
if (typeof msg === "string") return;
|
|
787
|
+
if (msg.type === "LIMITS" /* LIMITS */) {
|
|
788
|
+
const batch = msg.body;
|
|
789
|
+
if (batch.length === 0) return;
|
|
790
|
+
limits.push(...batch);
|
|
791
|
+
if (settleTimer) clearTimeout(settleTimer);
|
|
792
|
+
settleTimer = setTimeout(() => {
|
|
793
|
+
clearTimeout(timer);
|
|
794
|
+
ws.close();
|
|
795
|
+
resolve(limits);
|
|
796
|
+
}, 200);
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
ws.on("error", (error) => {
|
|
800
|
+
clearTimeout(timer);
|
|
801
|
+
ws.close();
|
|
802
|
+
checkWsRateLimit(error);
|
|
803
|
+
reject(new DxtradeError("LIMITS_ERROR" /* LIMITS_ERROR */, `Symbol limits error: ${error.message}`));
|
|
804
|
+
});
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
|
|
630
809
|
// src/domains/order/order.ts
|
|
631
810
|
function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
632
811
|
const ws = new WebSocket6(wsUrl, { headers: { Cookie: cookieStr } });
|
|
@@ -656,20 +835,21 @@ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
|
656
835
|
if (msg.type === "MESSAGE" /* MESSAGE */) {
|
|
657
836
|
const messages = msg.body;
|
|
658
837
|
const orderMsg = messages?.findLast?.(
|
|
659
|
-
(m) => m.messageCategory === "TRADE_LOG" && m.messageType === "ORDER" && !m.historyMessage
|
|
838
|
+
(m) => m.messageCategory === "TRADE_LOG" /* TRADE_LOG */ && m.messageType === "ORDER" /* ORDER */ && !m.historyMessage
|
|
660
839
|
);
|
|
661
840
|
if (!orderMsg) return;
|
|
662
841
|
const params = orderMsg.parametersTO;
|
|
663
|
-
if (params.orderStatus === "REJECTED") {
|
|
842
|
+
if (params.orderStatus === "REJECTED" /* REJECTED */) {
|
|
664
843
|
const reason = params.rejectReason?.key ?? "Unknown reason";
|
|
665
844
|
done(new Error(`[dxtrade-api] Order rejected: ${reason}`));
|
|
666
|
-
} else if (params.orderStatus === "FILLED") {
|
|
845
|
+
} else if (params.orderStatus === "FILLED" /* FILLED */) {
|
|
667
846
|
done(null, {
|
|
668
847
|
orderId: params.orderKey,
|
|
669
848
|
status: params.orderStatus,
|
|
670
849
|
symbol: params.symbol,
|
|
671
850
|
filledQuantity: params.filledQuantity,
|
|
672
|
-
filledPrice: params.filledPrice
|
|
851
|
+
filledPrice: params.filledPrice,
|
|
852
|
+
positionCode: params.positionCode
|
|
673
853
|
});
|
|
674
854
|
}
|
|
675
855
|
return;
|
|
@@ -677,9 +857,9 @@ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
|
677
857
|
if (msg.type === "ORDERS" /* ORDERS */) {
|
|
678
858
|
const body = msg.body?.[0];
|
|
679
859
|
if (!body?.orderId) return;
|
|
680
|
-
if (body.status === "REJECTED") {
|
|
860
|
+
if (body.status === "REJECTED" /* REJECTED */) {
|
|
681
861
|
done(new Error(`[dxtrade-api] Order rejected: ${body.statusDescription ?? "Unknown reason"}`));
|
|
682
|
-
} else if (body.status === "FILLED") {
|
|
862
|
+
} else if (body.status === "FILLED" /* FILLED */) {
|
|
683
863
|
done(null, body);
|
|
684
864
|
}
|
|
685
865
|
}
|
|
@@ -689,279 +869,489 @@ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
|
689
869
|
settled = true;
|
|
690
870
|
clearTimeout(timer);
|
|
691
871
|
ws.close();
|
|
692
|
-
|
|
872
|
+
checkWsRateLimit(error);
|
|
873
|
+
reject(new DxtradeError("ORDER_ERROR" /* ORDER_ERROR */, `WebSocket order listener error: ${error.message}`));
|
|
693
874
|
});
|
|
694
875
|
});
|
|
695
876
|
return { promise, ready };
|
|
696
877
|
}
|
|
697
|
-
|
|
698
|
-
ctx.ensureSession();
|
|
699
|
-
if (ctx.wsManager) {
|
|
700
|
-
return ctx.wsManager.waitFor("ORDERS" /* ORDERS */, timeout);
|
|
701
|
-
}
|
|
702
|
-
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
703
|
-
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
878
|
+
function createWsManagerOrderListener(ctx, timeout = 3e4) {
|
|
704
879
|
return new Promise((resolve, reject) => {
|
|
705
|
-
|
|
880
|
+
let settled = false;
|
|
706
881
|
const timer = setTimeout(() => {
|
|
707
|
-
|
|
708
|
-
|
|
882
|
+
if (settled) return;
|
|
883
|
+
settled = true;
|
|
884
|
+
cleanup();
|
|
885
|
+
reject(new Error("[dxtrade-api] Order update timed out"));
|
|
709
886
|
}, timeout);
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (typeof msg === "string") return;
|
|
714
|
-
if (msg.type === "ORDERS" /* ORDERS */) {
|
|
715
|
-
clearTimeout(timer);
|
|
716
|
-
ws.close();
|
|
717
|
-
resolve(msg.body);
|
|
718
|
-
}
|
|
719
|
-
});
|
|
720
|
-
ws.on("error", (error) => {
|
|
887
|
+
function done(err, result) {
|
|
888
|
+
if (settled) return;
|
|
889
|
+
settled = true;
|
|
721
890
|
clearTimeout(timer);
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
891
|
+
cleanup();
|
|
892
|
+
if (err) reject(err);
|
|
893
|
+
else resolve(result);
|
|
894
|
+
}
|
|
895
|
+
function onMessage(body) {
|
|
896
|
+
const messages = body;
|
|
897
|
+
const orderMsg = messages?.findLast?.(
|
|
898
|
+
(m) => m.messageCategory === "TRADE_LOG" /* TRADE_LOG */ && m.messageType === "ORDER" /* ORDER */ && !m.historyMessage
|
|
899
|
+
);
|
|
900
|
+
if (!orderMsg) return;
|
|
901
|
+
const params = orderMsg.parametersTO;
|
|
902
|
+
if (params.orderStatus === "REJECTED" /* REJECTED */) {
|
|
903
|
+
const reason = params.rejectReason?.key ?? "Unknown reason";
|
|
904
|
+
done(new Error(`[dxtrade-api] Order rejected: ${reason}`));
|
|
905
|
+
} else if (params.orderStatus === "FILLED" /* FILLED */) {
|
|
906
|
+
done(null, {
|
|
907
|
+
orderId: params.orderKey,
|
|
908
|
+
status: params.orderStatus,
|
|
909
|
+
symbol: params.symbol,
|
|
910
|
+
filledQuantity: params.filledQuantity,
|
|
911
|
+
filledPrice: params.filledPrice,
|
|
912
|
+
positionCode: params.positionCode
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
function onOrders(body) {
|
|
917
|
+
const order = body?.[0];
|
|
918
|
+
if (!order?.orderId) return;
|
|
919
|
+
if (order.status === "REJECTED" /* REJECTED */) {
|
|
920
|
+
done(new Error(`[dxtrade-api] Order rejected: ${order.statusDescription ?? "Unknown reason"}`));
|
|
921
|
+
} else if (order.status === "FILLED" /* FILLED */) {
|
|
922
|
+
done(null, order);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
function cleanup() {
|
|
926
|
+
ctx.wsManager?.removeListener("MESSAGE" /* MESSAGE */, onMessage);
|
|
927
|
+
ctx.wsManager?.removeListener("ORDERS" /* ORDERS */, onOrders);
|
|
928
|
+
}
|
|
929
|
+
ctx.wsManager.on("MESSAGE" /* MESSAGE */, onMessage);
|
|
930
|
+
ctx.wsManager.on("ORDERS" /* ORDERS */, onOrders);
|
|
725
931
|
});
|
|
726
932
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if (!accountId) {
|
|
731
|
-
ctx.throwError("CANCEL_ORDER_ERROR" /* CANCEL_ORDER_ERROR */, "accountId is required to cancel an order");
|
|
732
|
-
}
|
|
733
|
-
try {
|
|
734
|
-
await retryRequest(
|
|
735
|
-
{
|
|
736
|
-
method: "DELETE",
|
|
737
|
-
url: endpoints.cancelOrder(ctx.broker, accountId, orderChainId),
|
|
738
|
-
headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
|
|
739
|
-
},
|
|
740
|
-
ctx.retries
|
|
741
|
-
);
|
|
742
|
-
} catch (error) {
|
|
743
|
-
if (error instanceof DxtradeError) throw error;
|
|
744
|
-
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
745
|
-
ctx.throwError("CANCEL_ORDER_ERROR" /* CANCEL_ORDER_ERROR */, `Cancel order error: ${message}`);
|
|
933
|
+
var OrdersDomain = class {
|
|
934
|
+
constructor(_ctx) {
|
|
935
|
+
this._ctx = _ctx;
|
|
746
936
|
}
|
|
747
|
-
|
|
748
|
-
async
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
937
|
+
/** Get all pending/open orders via WebSocket. */
|
|
938
|
+
async get(timeout = 3e4) {
|
|
939
|
+
this._ctx.ensureSession();
|
|
940
|
+
if (this._ctx.wsManager) {
|
|
941
|
+
return this._ctx.wsManager.waitFor("ORDERS" /* ORDERS */, timeout);
|
|
942
|
+
}
|
|
943
|
+
const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
|
|
944
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
945
|
+
return new Promise((resolve, reject) => {
|
|
946
|
+
const ws = new WebSocket6(wsUrl, { headers: { Cookie: cookieStr } });
|
|
947
|
+
const timer = setTimeout(() => {
|
|
948
|
+
ws.close();
|
|
949
|
+
reject(new DxtradeError("ORDERS_TIMEOUT" /* ORDERS_TIMEOUT */, "Orders request timed out"));
|
|
950
|
+
}, timeout);
|
|
951
|
+
ws.on("message", (data) => {
|
|
952
|
+
const msg = parseWsData(data);
|
|
953
|
+
if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
|
|
954
|
+
if (typeof msg === "string") return;
|
|
955
|
+
if (msg.type === "ORDERS" /* ORDERS */) {
|
|
956
|
+
clearTimeout(timer);
|
|
957
|
+
ws.close();
|
|
958
|
+
resolve(msg.body);
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
ws.on("error", (error) => {
|
|
962
|
+
clearTimeout(timer);
|
|
963
|
+
ws.close();
|
|
964
|
+
checkWsRateLimit(error);
|
|
965
|
+
reject(new DxtradeError("ORDERS_ERROR" /* ORDERS_ERROR */, `Orders error: ${error.message}`));
|
|
966
|
+
});
|
|
967
|
+
});
|
|
753
968
|
}
|
|
754
|
-
|
|
755
|
-
async
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
const qty = side === "BUY" /* BUY */ ? units : -units;
|
|
776
|
-
const priceParam = orderType === "STOP" /* STOP */ ? "stopPrice" : "limitPrice";
|
|
777
|
-
const orderData = {
|
|
778
|
-
directExchange: false,
|
|
779
|
-
legs: [
|
|
780
|
-
{
|
|
781
|
-
...instrumentId != null && { instrumentId },
|
|
782
|
-
...positionCode != null && { positionCode },
|
|
783
|
-
positionEffect,
|
|
784
|
-
ratioQuantity: 1,
|
|
785
|
-
symbol
|
|
786
|
-
}
|
|
787
|
-
],
|
|
788
|
-
orderSide: side,
|
|
789
|
-
orderType,
|
|
790
|
-
quantity: qty,
|
|
791
|
-
requestId: orderCode ?? `gwt-uid-931-${crypto.randomUUID()}`,
|
|
792
|
-
timeInForce: tif,
|
|
793
|
-
...expireDate != null && { expireDate },
|
|
794
|
-
...metadata != null && { metadata }
|
|
795
|
-
};
|
|
796
|
-
if (price != null && orderType !== "MARKET" /* MARKET */) {
|
|
797
|
-
orderData[priceParam] = price;
|
|
969
|
+
/** Cancel a single pending order by its order chain ID. */
|
|
970
|
+
async cancel(orderChainId) {
|
|
971
|
+
this._ctx.ensureSession();
|
|
972
|
+
const accountId = this._ctx.accountId ?? this._ctx.config.accountId;
|
|
973
|
+
if (!accountId) {
|
|
974
|
+
this._ctx.throwError("CANCEL_ORDER_ERROR" /* CANCEL_ORDER_ERROR */, "accountId is required to cancel an order");
|
|
975
|
+
}
|
|
976
|
+
try {
|
|
977
|
+
await retryRequest(
|
|
978
|
+
{
|
|
979
|
+
method: "DELETE",
|
|
980
|
+
url: endpoints.cancelOrder(this._ctx.broker, accountId, orderChainId),
|
|
981
|
+
headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
|
|
982
|
+
},
|
|
983
|
+
this._ctx.retries
|
|
984
|
+
);
|
|
985
|
+
} catch (error) {
|
|
986
|
+
if (error instanceof DxtradeError) throw error;
|
|
987
|
+
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
988
|
+
this._ctx.throwError("CANCEL_ORDER_ERROR" /* CANCEL_ORDER_ERROR */, `Cancel order error: ${message}`);
|
|
989
|
+
}
|
|
798
990
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
orderType: "STOP" /* STOP */,
|
|
807
|
-
quantityForProtection: qty,
|
|
808
|
-
removed: false
|
|
809
|
-
};
|
|
991
|
+
/** Cancel all pending orders. */
|
|
992
|
+
async cancelAll() {
|
|
993
|
+
const orders = await this.get();
|
|
994
|
+
const pending = orders.filter((o) => !o.finalStatus);
|
|
995
|
+
for (const order of pending) {
|
|
996
|
+
await this.cancel(order.orderId);
|
|
997
|
+
}
|
|
810
998
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
999
|
+
/**
|
|
1000
|
+
* Submit a trading order and wait for WebSocket confirmation.
|
|
1001
|
+
* Supports market, limit, and stop orders with optional stop loss and take profit.
|
|
1002
|
+
*/
|
|
1003
|
+
async submit(params) {
|
|
1004
|
+
this._ctx.ensureSession();
|
|
1005
|
+
const {
|
|
1006
|
+
symbol,
|
|
1007
|
+
side,
|
|
1008
|
+
quantity,
|
|
1009
|
+
orderType,
|
|
1010
|
+
orderCode,
|
|
1011
|
+
price,
|
|
1012
|
+
instrumentId,
|
|
1013
|
+
stopLoss,
|
|
1014
|
+
takeProfit,
|
|
1015
|
+
positionEffect = "OPENING" /* OPENING */,
|
|
1016
|
+
positionCode,
|
|
1017
|
+
tif = "GTC",
|
|
1018
|
+
expireDate,
|
|
1019
|
+
metadata
|
|
1020
|
+
} = params;
|
|
1021
|
+
const info = await new SymbolsDomain(this._ctx).info(symbol);
|
|
1022
|
+
const units = quantity * info.lotSize;
|
|
1023
|
+
const qty = side === "BUY" /* BUY */ ? units : -units;
|
|
1024
|
+
const priceParam = orderType === "STOP" /* STOP */ ? "stopPrice" : "limitPrice";
|
|
1025
|
+
const orderData = {
|
|
1026
|
+
directExchange: false,
|
|
1027
|
+
legs: [
|
|
1028
|
+
{
|
|
1029
|
+
...instrumentId != null && { instrumentId },
|
|
1030
|
+
...positionCode != null && { positionCode },
|
|
1031
|
+
positionEffect,
|
|
1032
|
+
ratioQuantity: 1,
|
|
1033
|
+
symbol
|
|
1034
|
+
}
|
|
1035
|
+
],
|
|
1036
|
+
orderSide: side,
|
|
1037
|
+
orderType,
|
|
1038
|
+
quantity: qty,
|
|
1039
|
+
requestId: orderCode ?? `gwt-uid-931-${crypto.randomUUID()}`,
|
|
1040
|
+
timeInForce: tif,
|
|
1041
|
+
...expireDate != null && { expireDate },
|
|
1042
|
+
...metadata != null && { metadata }
|
|
821
1043
|
};
|
|
1044
|
+
if (price != null) {
|
|
1045
|
+
orderData[priceParam] = price;
|
|
1046
|
+
}
|
|
1047
|
+
if (stopLoss) {
|
|
1048
|
+
orderData.stopLoss = {
|
|
1049
|
+
...stopLoss.offset != null && { fixedOffset: stopLoss.offset },
|
|
1050
|
+
...stopLoss.price != null && { fixedPrice: stopLoss.price },
|
|
1051
|
+
priceFixed: stopLoss.price != null,
|
|
1052
|
+
orderChainId: 0,
|
|
1053
|
+
orderId: 0,
|
|
1054
|
+
orderType: "STOP" /* STOP */,
|
|
1055
|
+
quantityForProtection: qty,
|
|
1056
|
+
removed: false
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
if (takeProfit) {
|
|
1060
|
+
orderData.takeProfit = {
|
|
1061
|
+
...takeProfit.offset != null && { fixedOffset: takeProfit.offset },
|
|
1062
|
+
...takeProfit.price != null && { fixedPrice: takeProfit.price },
|
|
1063
|
+
priceFixed: takeProfit.price != null,
|
|
1064
|
+
orderChainId: 0,
|
|
1065
|
+
orderId: 0,
|
|
1066
|
+
orderType: "LIMIT" /* LIMIT */,
|
|
1067
|
+
quantityForProtection: qty,
|
|
1068
|
+
removed: false
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
try {
|
|
1072
|
+
let listenerPromise;
|
|
1073
|
+
if (this._ctx.wsManager) {
|
|
1074
|
+
listenerPromise = createWsManagerOrderListener(this._ctx, 3e4);
|
|
1075
|
+
} else {
|
|
1076
|
+
const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
|
|
1077
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
1078
|
+
const listener = createOrderListener(wsUrl, cookieStr, 3e4, this._ctx.debug);
|
|
1079
|
+
await listener.ready;
|
|
1080
|
+
listenerPromise = listener.promise;
|
|
1081
|
+
}
|
|
1082
|
+
const response = await retryRequest(
|
|
1083
|
+
{
|
|
1084
|
+
method: "POST",
|
|
1085
|
+
url: endpoints.submitOrder(this._ctx.broker),
|
|
1086
|
+
data: orderData,
|
|
1087
|
+
headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
|
|
1088
|
+
},
|
|
1089
|
+
this._ctx.retries
|
|
1090
|
+
);
|
|
1091
|
+
this._ctx.callbacks.onOrderPlaced?.(response.data);
|
|
1092
|
+
const orderUpdate = await listenerPromise;
|
|
1093
|
+
this._ctx.callbacks.onOrderUpdate?.(orderUpdate);
|
|
1094
|
+
return orderUpdate;
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
if (error instanceof DxtradeError) throw error;
|
|
1097
|
+
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
1098
|
+
this._ctx.throwError("ORDER_ERROR" /* ORDER_ERROR */, `Error submitting order: ${message}`);
|
|
1099
|
+
}
|
|
822
1100
|
}
|
|
823
|
-
|
|
824
|
-
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
825
|
-
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
826
|
-
const listener = createOrderListener(wsUrl, cookieStr, 3e4, ctx.debug);
|
|
827
|
-
await listener.ready;
|
|
828
|
-
const response = await retryRequest(
|
|
829
|
-
{
|
|
830
|
-
method: "POST",
|
|
831
|
-
url: endpoints.submitOrder(ctx.broker),
|
|
832
|
-
data: orderData,
|
|
833
|
-
headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
|
|
834
|
-
},
|
|
835
|
-
ctx.retries
|
|
836
|
-
);
|
|
837
|
-
ctx.callbacks.onOrderPlaced?.(response.data);
|
|
838
|
-
const orderUpdate = await listener.promise;
|
|
839
|
-
ctx.callbacks.onOrderUpdate?.(orderUpdate);
|
|
840
|
-
return orderUpdate;
|
|
841
|
-
} catch (error) {
|
|
842
|
-
if (error instanceof DxtradeError) throw error;
|
|
843
|
-
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
844
|
-
ctx.throwError("ORDER_ERROR" /* ORDER_ERROR */, `Error submitting order: ${message}`);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
1101
|
+
};
|
|
847
1102
|
|
|
848
1103
|
// src/domains/position/position.ts
|
|
849
1104
|
import WebSocket7 from "ws";
|
|
850
|
-
function
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
};
|
|
1105
|
+
function mergePositionsWithMetrics(positions, metrics) {
|
|
1106
|
+
const metricsMap = new Map(metrics.map((m) => [m.uid, m]));
|
|
1107
|
+
return positions.map((pos) => {
|
|
1108
|
+
const m = metricsMap.get(pos.uid);
|
|
1109
|
+
return {
|
|
1110
|
+
...pos,
|
|
1111
|
+
margin: m?.margin ?? 0,
|
|
1112
|
+
plOpen: m?.plOpen ?? 0,
|
|
1113
|
+
plClosed: m?.plClosed ?? 0,
|
|
1114
|
+
totalCommissions: m?.totalCommissions ?? 0,
|
|
1115
|
+
totalFinancing: m?.totalFinancing ?? 0,
|
|
1116
|
+
plRate: m?.plRate ?? 0,
|
|
1117
|
+
averagePrice: m?.averagePrice ?? 0,
|
|
1118
|
+
marketValue: m?.marketValue ?? 0
|
|
1119
|
+
};
|
|
1120
|
+
});
|
|
866
1121
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
return ctx.wsManager.waitFor("POSITIONS" /* POSITIONS */);
|
|
1122
|
+
var PositionsDomain = class {
|
|
1123
|
+
constructor(_ctx) {
|
|
1124
|
+
this._ctx = _ctx;
|
|
871
1125
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
if (
|
|
884
|
-
|
|
885
|
-
clearTimeout(timer);
|
|
886
|
-
ws.close();
|
|
887
|
-
resolve(msg.body);
|
|
1126
|
+
/** Stream real-time position updates with P&L metrics. Requires connect(). Returns unsubscribe function. */
|
|
1127
|
+
stream(callback) {
|
|
1128
|
+
if (!this._ctx.wsManager) {
|
|
1129
|
+
this._ctx.throwError(
|
|
1130
|
+
"STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
|
|
1131
|
+
"Streaming requires a persistent WebSocket. Use connect() instead of auth()."
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
const emit = () => {
|
|
1135
|
+
const positions = this._ctx.wsManager.getCached("POSITIONS" /* POSITIONS */);
|
|
1136
|
+
const metrics = this._ctx.wsManager.getCached("POSITION_METRICS" /* POSITION_METRICS */);
|
|
1137
|
+
if (positions && metrics) {
|
|
1138
|
+
callback(mergePositionsWithMetrics(positions, metrics));
|
|
888
1139
|
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
if (ctx.wsManager) {
|
|
900
|
-
return ctx.wsManager.waitFor("POSITION_METRICS" /* POSITION_METRICS */, timeout);
|
|
1140
|
+
};
|
|
1141
|
+
const onPositions = () => emit();
|
|
1142
|
+
const onMetrics = () => emit();
|
|
1143
|
+
this._ctx.wsManager.on("POSITIONS" /* POSITIONS */, onPositions);
|
|
1144
|
+
this._ctx.wsManager.on("POSITION_METRICS" /* POSITION_METRICS */, onMetrics);
|
|
1145
|
+
emit();
|
|
1146
|
+
return () => {
|
|
1147
|
+
this._ctx.wsManager?.removeListener("POSITIONS" /* POSITIONS */, onPositions);
|
|
1148
|
+
this._ctx.wsManager?.removeListener("POSITION_METRICS" /* POSITION_METRICS */, onMetrics);
|
|
1149
|
+
};
|
|
901
1150
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1151
|
+
/** Get all open positions with P&L metrics merged. */
|
|
1152
|
+
async get() {
|
|
1153
|
+
this._ctx.ensureSession();
|
|
1154
|
+
if (this._ctx.wsManager) {
|
|
1155
|
+
const [positions, metrics] = await Promise.all([
|
|
1156
|
+
this._ctx.wsManager.waitFor("POSITIONS" /* POSITIONS */),
|
|
1157
|
+
this._ctx.wsManager.waitFor("POSITION_METRICS" /* POSITION_METRICS */)
|
|
1158
|
+
]);
|
|
1159
|
+
return mergePositionsWithMetrics(positions, metrics);
|
|
1160
|
+
}
|
|
1161
|
+
const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
|
|
1162
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
1163
|
+
return new Promise((resolve, reject) => {
|
|
1164
|
+
const ws = new WebSocket7(wsUrl, { headers: { Cookie: cookieStr } });
|
|
1165
|
+
let positions = null;
|
|
1166
|
+
let metrics = null;
|
|
1167
|
+
const timer = setTimeout(() => {
|
|
1168
|
+
ws.close();
|
|
1169
|
+
reject(new DxtradeError("ACCOUNT_POSITIONS_TIMEOUT" /* ACCOUNT_POSITIONS_TIMEOUT */, "Account positions timed out"));
|
|
1170
|
+
}, 3e4);
|
|
1171
|
+
ws.on("message", (data) => {
|
|
1172
|
+
const msg = parseWsData(data);
|
|
1173
|
+
if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
|
|
1174
|
+
if (typeof msg === "string") return;
|
|
1175
|
+
if (msg.type === "POSITIONS" /* POSITIONS */) {
|
|
1176
|
+
positions = msg.body;
|
|
1177
|
+
}
|
|
1178
|
+
if (msg.type === "POSITION_METRICS" /* POSITION_METRICS */) {
|
|
1179
|
+
metrics = msg.body;
|
|
1180
|
+
}
|
|
1181
|
+
if (positions && metrics) {
|
|
1182
|
+
clearTimeout(timer);
|
|
1183
|
+
ws.close();
|
|
1184
|
+
resolve(mergePositionsWithMetrics(positions, metrics));
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
ws.on("error", (error) => {
|
|
915
1188
|
clearTimeout(timer);
|
|
916
1189
|
ws.close();
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
ws.on("error", (error) => {
|
|
921
|
-
clearTimeout(timer);
|
|
922
|
-
ws.close();
|
|
923
|
-
reject(new DxtradeError("POSITION_METRICS_ERROR" /* POSITION_METRICS_ERROR */, `Position metrics error: ${error.message}`));
|
|
1190
|
+
checkWsRateLimit(error);
|
|
1191
|
+
reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR" /* ACCOUNT_POSITIONS_ERROR */, `Account positions error: ${error.message}`));
|
|
1192
|
+
});
|
|
924
1193
|
});
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
async
|
|
928
|
-
|
|
929
|
-
|
|
1194
|
+
}
|
|
1195
|
+
/** Close all open positions with market orders. */
|
|
1196
|
+
async closeAll() {
|
|
1197
|
+
const positions = await this.get();
|
|
1198
|
+
for (const pos of positions) {
|
|
1199
|
+
const closeData = {
|
|
1200
|
+
legs: [
|
|
1201
|
+
{
|
|
1202
|
+
instrumentId: pos.positionKey.instrumentId,
|
|
1203
|
+
positionCode: pos.positionKey.positionCode,
|
|
1204
|
+
positionEffect: "CLOSING",
|
|
1205
|
+
ratioQuantity: 1,
|
|
1206
|
+
symbol: pos.positionKey.positionCode
|
|
1207
|
+
}
|
|
1208
|
+
],
|
|
1209
|
+
limitPrice: 0,
|
|
1210
|
+
orderType: "MARKET",
|
|
1211
|
+
quantity: -pos.quantity,
|
|
1212
|
+
timeInForce: "GTC"
|
|
1213
|
+
};
|
|
1214
|
+
await this._sendCloseRequest(closeData);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
/** Close a position by its position code. Returns the position with P&L metrics. Optionally wait for close confirmation via `waitForClose: "stream" | "poll"`. */
|
|
1218
|
+
async close(positionCode, options) {
|
|
1219
|
+
const positions = await this.get();
|
|
1220
|
+
const position = positions.find((p) => p.positionKey.positionCode === positionCode);
|
|
1221
|
+
if (!position) {
|
|
1222
|
+
this._ctx.throwError("POSITION_NOT_FOUND" /* POSITION_NOT_FOUND */, `Position with code "${positionCode}" not found`);
|
|
1223
|
+
}
|
|
930
1224
|
const closeData = {
|
|
931
1225
|
legs: [
|
|
932
1226
|
{
|
|
933
|
-
instrumentId:
|
|
934
|
-
positionCode:
|
|
1227
|
+
instrumentId: position.positionKey.instrumentId,
|
|
1228
|
+
positionCode: position.positionKey.positionCode,
|
|
935
1229
|
positionEffect: "CLOSING",
|
|
936
1230
|
ratioQuantity: 1,
|
|
937
|
-
symbol:
|
|
1231
|
+
symbol: position.positionKey.positionCode
|
|
938
1232
|
}
|
|
939
1233
|
],
|
|
940
1234
|
limitPrice: 0,
|
|
941
1235
|
orderType: "MARKET",
|
|
942
|
-
quantity: -
|
|
1236
|
+
quantity: -position.quantity,
|
|
943
1237
|
timeInForce: "GTC"
|
|
944
1238
|
};
|
|
945
|
-
|
|
1239
|
+
if (options?.waitForClose === "stream") {
|
|
1240
|
+
return this._waitForCloseStream(positionCode, position, closeData, options.timeout ?? 3e4);
|
|
1241
|
+
}
|
|
1242
|
+
await this._sendCloseRequest(closeData);
|
|
1243
|
+
if (options?.waitForClose === "poll") {
|
|
1244
|
+
return this._waitForClosePoll(positionCode, position, options.timeout ?? 3e4, options.pollInterval ?? 1e3);
|
|
1245
|
+
}
|
|
1246
|
+
return position;
|
|
1247
|
+
}
|
|
1248
|
+
_waitForCloseStream(positionCode, lastSnapshot, closeData, timeout) {
|
|
1249
|
+
if (!this._ctx.wsManager) {
|
|
1250
|
+
this._ctx.throwError(
|
|
1251
|
+
"STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
|
|
1252
|
+
'waitForClose: "stream" requires a persistent WebSocket. Use connect() instead of auth(), or use "poll" mode.'
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
return new Promise(async (resolve, reject) => {
|
|
1256
|
+
let settled = false;
|
|
1257
|
+
const result = lastSnapshot;
|
|
1258
|
+
const timer = setTimeout(() => {
|
|
1259
|
+
if (settled) return;
|
|
1260
|
+
settled = true;
|
|
1261
|
+
cleanup();
|
|
1262
|
+
reject(
|
|
1263
|
+
new DxtradeError("POSITION_CLOSE_TIMEOUT" /* POSITION_CLOSE_TIMEOUT */, `Position close confirmation timed out after ${timeout}ms`)
|
|
1264
|
+
);
|
|
1265
|
+
}, timeout);
|
|
1266
|
+
function done(err, res) {
|
|
1267
|
+
if (settled) return;
|
|
1268
|
+
settled = true;
|
|
1269
|
+
clearTimeout(timer);
|
|
1270
|
+
cleanup();
|
|
1271
|
+
if (err) reject(err);
|
|
1272
|
+
else resolve(res);
|
|
1273
|
+
}
|
|
1274
|
+
function onMessage(body) {
|
|
1275
|
+
const messages = body;
|
|
1276
|
+
const orderMsg = messages?.findLast?.(
|
|
1277
|
+
(m) => m.messageCategory === "TRADE_LOG" /* TRADE_LOG */ && m.messageType === "ORDER" /* ORDER */ && !m.historyMessage
|
|
1278
|
+
);
|
|
1279
|
+
if (!orderMsg) return;
|
|
1280
|
+
const params = orderMsg.parametersTO;
|
|
1281
|
+
if (params.positionCode !== positionCode) return;
|
|
1282
|
+
if (params.orderStatus === "REJECTED" /* REJECTED */) {
|
|
1283
|
+
done(
|
|
1284
|
+
new DxtradeError(
|
|
1285
|
+
"POSITION_CLOSE_ERROR" /* POSITION_CLOSE_ERROR */,
|
|
1286
|
+
`Close order rejected: ${params.rejectReason?.key ?? "Unknown reason"}`
|
|
1287
|
+
)
|
|
1288
|
+
);
|
|
1289
|
+
} else if (params.orderStatus === "FILLED" /* FILLED */) {
|
|
1290
|
+
done(null, result);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
function onOrders(body) {
|
|
1294
|
+
const orders = body;
|
|
1295
|
+
const order = orders?.[0];
|
|
1296
|
+
if (!order?.orderId) return;
|
|
1297
|
+
if (order.status === "REJECTED" /* REJECTED */) {
|
|
1298
|
+
done(
|
|
1299
|
+
new DxtradeError(
|
|
1300
|
+
"POSITION_CLOSE_ERROR" /* POSITION_CLOSE_ERROR */,
|
|
1301
|
+
`Close order rejected: ${order.statusDescription ?? "Unknown reason"}`
|
|
1302
|
+
)
|
|
1303
|
+
);
|
|
1304
|
+
} else if (order.status === "FILLED" /* FILLED */) {
|
|
1305
|
+
done(null, result);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
const wsManager = this._ctx.wsManager;
|
|
1309
|
+
function cleanup() {
|
|
1310
|
+
wsManager.removeListener("MESSAGE" /* MESSAGE */, onMessage);
|
|
1311
|
+
wsManager.removeListener("ORDERS" /* ORDERS */, onOrders);
|
|
1312
|
+
}
|
|
1313
|
+
wsManager.on("MESSAGE" /* MESSAGE */, onMessage);
|
|
1314
|
+
wsManager.on("ORDERS" /* ORDERS */, onOrders);
|
|
1315
|
+
try {
|
|
1316
|
+
await this._sendCloseRequest(closeData);
|
|
1317
|
+
} catch (error) {
|
|
1318
|
+
done(error instanceof Error ? error : new Error(String(error)));
|
|
1319
|
+
}
|
|
1320
|
+
});
|
|
946
1321
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
962
|
-
ctx.throwError("POSITION_CLOSE_ERROR" /* POSITION_CLOSE_ERROR */, `Position close error: ${message}`);
|
|
1322
|
+
async _waitForClosePoll(positionCode, lastSnapshot, timeout, interval) {
|
|
1323
|
+
const deadline = Date.now() + timeout;
|
|
1324
|
+
let result = lastSnapshot;
|
|
1325
|
+
while (Date.now() < deadline) {
|
|
1326
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
1327
|
+
const positions = await this.get();
|
|
1328
|
+
const match = positions.find((p) => p.positionKey.positionCode === positionCode);
|
|
1329
|
+
if (match) {
|
|
1330
|
+
result = match;
|
|
1331
|
+
} else {
|
|
1332
|
+
return result;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
this._ctx.throwError("POSITION_CLOSE_TIMEOUT" /* POSITION_CLOSE_TIMEOUT */, `Position close confirmation timed out after ${timeout}ms`);
|
|
963
1336
|
}
|
|
964
|
-
|
|
1337
|
+
async _sendCloseRequest(data) {
|
|
1338
|
+
try {
|
|
1339
|
+
await retryRequest(
|
|
1340
|
+
{
|
|
1341
|
+
method: "POST",
|
|
1342
|
+
url: endpoints.closePosition(this._ctx.broker),
|
|
1343
|
+
data,
|
|
1344
|
+
headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
|
|
1345
|
+
},
|
|
1346
|
+
this._ctx.retries
|
|
1347
|
+
);
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
if (error instanceof DxtradeError) throw error;
|
|
1350
|
+
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
1351
|
+
this._ctx.throwError("POSITION_CLOSE_ERROR" /* POSITION_CLOSE_ERROR */, `Position close error: ${message}`);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
965
1355
|
|
|
966
1356
|
// src/domains/session/session.ts
|
|
967
1357
|
import WebSocket8 from "ws";
|
|
@@ -989,247 +1379,161 @@ function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
|
989
1379
|
ws.on("error", (error) => {
|
|
990
1380
|
clearTimeout(timer);
|
|
991
1381
|
ws.close();
|
|
1382
|
+
checkWsRateLimit(error);
|
|
992
1383
|
reject(new Error(`[dxtrade-api] WebSocket handshake error: ${error.message}`));
|
|
993
1384
|
});
|
|
994
1385
|
});
|
|
995
1386
|
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1387
|
+
var SessionDomain = class {
|
|
1388
|
+
constructor(_ctx) {
|
|
1389
|
+
this._ctx = _ctx;
|
|
1390
|
+
}
|
|
1391
|
+
/** Authenticate with the broker using username and password. */
|
|
1392
|
+
async login() {
|
|
1393
|
+
try {
|
|
1394
|
+
const response = await retryRequest(
|
|
1395
|
+
{
|
|
1396
|
+
method: "POST",
|
|
1397
|
+
url: endpoints.login(this._ctx.broker),
|
|
1398
|
+
data: {
|
|
1399
|
+
username: this._ctx.config.username,
|
|
1400
|
+
password: this._ctx.config.password,
|
|
1401
|
+
// TODO:: take a look at this below, domain nor vendor seems required. it works if i comment out both.
|
|
1402
|
+
// however i still use it since i see brokers use it as well in the login endpoint.
|
|
1403
|
+
// domain: this._ctx.config.broker,
|
|
1404
|
+
vendor: this._ctx.config.broker
|
|
1405
|
+
// END TODO::
|
|
1406
|
+
},
|
|
1407
|
+
headers: {
|
|
1408
|
+
...baseHeaders(),
|
|
1409
|
+
Origin: this._ctx.broker,
|
|
1410
|
+
Referer: this._ctx.broker + "/",
|
|
1411
|
+
Cookie: Cookies.serialize(this._ctx.cookies)
|
|
1412
|
+
}
|
|
1010
1413
|
},
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1414
|
+
this._ctx.retries
|
|
1415
|
+
);
|
|
1416
|
+
if (response.status === 200) {
|
|
1417
|
+
const setCookies = response.headers["set-cookie"] ?? [];
|
|
1418
|
+
const incoming = Cookies.parse(setCookies);
|
|
1419
|
+
this._ctx.cookies = Cookies.merge(this._ctx.cookies, incoming);
|
|
1420
|
+
this._ctx.callbacks.onLogin?.();
|
|
1421
|
+
} else {
|
|
1422
|
+
this._ctx.throwError("LOGIN_FAILED" /* LOGIN_FAILED */, `Login failed: ${response.status}`);
|
|
1423
|
+
}
|
|
1424
|
+
} catch (error) {
|
|
1425
|
+
if (error instanceof DxtradeError) throw error;
|
|
1426
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1427
|
+
this._ctx.throwError("LOGIN_ERROR" /* LOGIN_ERROR */, `Login error: ${message}`);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
/** Fetch the CSRF token required for authenticated requests. */
|
|
1431
|
+
async fetchCsrf() {
|
|
1432
|
+
try {
|
|
1433
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
1434
|
+
const response = await retryRequest(
|
|
1435
|
+
{
|
|
1436
|
+
method: "GET",
|
|
1437
|
+
url: this._ctx.broker,
|
|
1438
|
+
headers: { ...cookieOnlyHeaders(cookieStr), Referer: this._ctx.broker }
|
|
1439
|
+
},
|
|
1440
|
+
this._ctx.retries
|
|
1441
|
+
);
|
|
1016
1442
|
const setCookies = response.headers["set-cookie"] ?? [];
|
|
1017
1443
|
const incoming = Cookies.parse(setCookies);
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1444
|
+
this._ctx.cookies = Cookies.merge(this._ctx.cookies, incoming);
|
|
1445
|
+
const csrfMatch = response.data?.match(/name="csrf" content="([^"]+)"/);
|
|
1446
|
+
if (csrfMatch) {
|
|
1447
|
+
this._ctx.csrf = csrfMatch[1];
|
|
1448
|
+
} else {
|
|
1449
|
+
this._ctx.throwError("CSRF_NOT_FOUND" /* CSRF_NOT_FOUND */, "CSRF token not found");
|
|
1450
|
+
}
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
if (error instanceof DxtradeError) throw error;
|
|
1453
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1454
|
+
this._ctx.throwError("CSRF_ERROR" /* CSRF_ERROR */, `CSRF fetch error: ${message}`);
|
|
1022
1455
|
}
|
|
1023
|
-
} catch (error) {
|
|
1024
|
-
if (error instanceof DxtradeError) throw error;
|
|
1025
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1026
|
-
ctx.throwError("LOGIN_ERROR" /* LOGIN_ERROR */, `Login error: ${message}`);
|
|
1027
1456
|
}
|
|
1028
|
-
|
|
1029
|
-
async
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1457
|
+
/** Switch to a specific trading account by ID. */
|
|
1458
|
+
async switchAccount(accountId) {
|
|
1459
|
+
this._ctx.ensureSession();
|
|
1460
|
+
try {
|
|
1461
|
+
await retryRequest(
|
|
1462
|
+
{
|
|
1463
|
+
method: "POST",
|
|
1464
|
+
url: endpoints.switchAccount(this._ctx.broker, accountId),
|
|
1465
|
+
headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
|
|
1466
|
+
},
|
|
1467
|
+
this._ctx.retries
|
|
1468
|
+
);
|
|
1469
|
+
this._ctx.callbacks.onAccountSwitch?.(accountId);
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
if (error instanceof DxtradeError) throw error;
|
|
1472
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1473
|
+
this._ctx.throwError("ACCOUNT_SWITCH_ERROR" /* ACCOUNT_SWITCH_ERROR */, `Error switching account: ${message}`);
|
|
1045
1474
|
}
|
|
1046
|
-
} catch (error) {
|
|
1047
|
-
if (error instanceof DxtradeError) throw error;
|
|
1048
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1049
|
-
ctx.throwError("CSRF_ERROR" /* CSRF_ERROR */, `CSRF fetch error: ${message}`);
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
async function switchAccount(ctx, accountId) {
|
|
1053
|
-
ctx.ensureSession();
|
|
1054
|
-
try {
|
|
1055
|
-
await retryRequest(
|
|
1056
|
-
{
|
|
1057
|
-
method: "POST",
|
|
1058
|
-
url: endpoints.switchAccount(ctx.broker, accountId),
|
|
1059
|
-
headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
|
|
1060
|
-
},
|
|
1061
|
-
ctx.retries
|
|
1062
|
-
);
|
|
1063
|
-
ctx.callbacks.onAccountSwitch?.(accountId);
|
|
1064
|
-
} catch (error) {
|
|
1065
|
-
if (error instanceof DxtradeError) throw error;
|
|
1066
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1067
|
-
ctx.throwError("ACCOUNT_SWITCH_ERROR" /* ACCOUNT_SWITCH_ERROR */, `Error switching account: ${message}`);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
async function auth(ctx) {
|
|
1071
|
-
await login(ctx);
|
|
1072
|
-
await fetchCsrf(ctx);
|
|
1073
|
-
if (ctx.debug) clearDebugLog();
|
|
1074
|
-
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
1075
|
-
const handshake = await waitForHandshake(endpoints.websocket(ctx.broker), cookieStr, 3e4, ctx.debug);
|
|
1076
|
-
ctx.atmosphereId = handshake.atmosphereId;
|
|
1077
|
-
ctx.accountId = handshake.accountId;
|
|
1078
|
-
if (ctx.config.accountId) {
|
|
1079
|
-
await switchAccount(ctx, ctx.config.accountId);
|
|
1080
|
-
const reconnect = await waitForHandshake(
|
|
1081
|
-
endpoints.websocket(ctx.broker, ctx.atmosphereId),
|
|
1082
|
-
Cookies.serialize(ctx.cookies),
|
|
1083
|
-
3e4,
|
|
1084
|
-
ctx.debug
|
|
1085
|
-
);
|
|
1086
|
-
ctx.atmosphereId = reconnect.atmosphereId;
|
|
1087
|
-
ctx.accountId = reconnect.accountId;
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
async function connect(ctx) {
|
|
1091
|
-
await auth(ctx);
|
|
1092
|
-
const wsManager = new WsManager();
|
|
1093
|
-
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
1094
|
-
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
1095
|
-
await wsManager.connect(wsUrl, cookieStr, ctx.debug);
|
|
1096
|
-
ctx.wsManager = wsManager;
|
|
1097
|
-
}
|
|
1098
|
-
function disconnect(ctx) {
|
|
1099
|
-
if (ctx.wsManager) {
|
|
1100
|
-
ctx.wsManager.close();
|
|
1101
|
-
ctx.wsManager = null;
|
|
1102
1475
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
closeAll() {
|
|
1120
|
-
return closeAllPositions(this._ctx);
|
|
1121
|
-
}
|
|
1122
|
-
/** Get position-level P&L metrics via WebSocket. */
|
|
1123
|
-
metrics() {
|
|
1124
|
-
return getPositionMetrics(this._ctx);
|
|
1125
|
-
}
|
|
1126
|
-
/** Stream real-time position updates. Requires connect(). Returns unsubscribe function. */
|
|
1127
|
-
stream(callback) {
|
|
1128
|
-
return streamPositions(this._ctx, callback);
|
|
1129
|
-
}
|
|
1130
|
-
};
|
|
1131
|
-
var OrdersDomain = class {
|
|
1132
|
-
constructor(_ctx) {
|
|
1133
|
-
this._ctx = _ctx;
|
|
1134
|
-
}
|
|
1135
|
-
/** Get all pending/open orders via WebSocket. */
|
|
1136
|
-
get() {
|
|
1137
|
-
return getOrders(this._ctx);
|
|
1138
|
-
}
|
|
1139
|
-
/**
|
|
1140
|
-
* Submit a trading order and wait for WebSocket confirmation.
|
|
1141
|
-
* Supports market, limit, and stop orders with optional stop loss and take profit.
|
|
1142
|
-
*/
|
|
1143
|
-
submit(params) {
|
|
1144
|
-
return submitOrder(this._ctx, params);
|
|
1145
|
-
}
|
|
1146
|
-
/** Cancel a single pending order by its order chain ID. */
|
|
1147
|
-
cancel(orderChainId) {
|
|
1148
|
-
return cancelOrder(this._ctx, orderChainId);
|
|
1149
|
-
}
|
|
1150
|
-
/** Cancel all pending orders. */
|
|
1151
|
-
cancelAll() {
|
|
1152
|
-
return cancelAllOrders(this._ctx);
|
|
1153
|
-
}
|
|
1154
|
-
};
|
|
1155
|
-
var AccountDomain = class {
|
|
1156
|
-
constructor(_ctx) {
|
|
1157
|
-
this._ctx = _ctx;
|
|
1158
|
-
}
|
|
1159
|
-
/** Get account metrics including equity, balance, margin, and open P&L. */
|
|
1160
|
-
metrics() {
|
|
1161
|
-
return getAccountMetrics(this._ctx);
|
|
1162
|
-
}
|
|
1163
|
-
/**
|
|
1164
|
-
* Fetch trade journal entries for a date range.
|
|
1165
|
-
* @param params.from - Start timestamp (Unix ms)
|
|
1166
|
-
* @param params.to - End timestamp (Unix ms)
|
|
1167
|
-
*/
|
|
1168
|
-
tradeJournal(params) {
|
|
1169
|
-
return getTradeJournal(this._ctx, params);
|
|
1170
|
-
}
|
|
1171
|
-
/**
|
|
1172
|
-
* Fetch trade history for a date range.
|
|
1173
|
-
* @param params.from - Start timestamp (Unix ms)
|
|
1174
|
-
* @param params.to - End timestamp (Unix ms)
|
|
1175
|
-
*/
|
|
1176
|
-
tradeHistory(params) {
|
|
1177
|
-
return getTradeHistory(this._ctx, params);
|
|
1178
|
-
}
|
|
1179
|
-
};
|
|
1180
|
-
var SymbolsDomain = class {
|
|
1181
|
-
constructor(_ctx) {
|
|
1182
|
-
this._ctx = _ctx;
|
|
1183
|
-
}
|
|
1184
|
-
/** Search for symbols matching the given text (e.g. "EURUSD", "BTC"). */
|
|
1185
|
-
search(text) {
|
|
1186
|
-
return getSymbolSuggestions(this._ctx, text);
|
|
1187
|
-
}
|
|
1188
|
-
/** Get detailed instrument info for a symbol, including volume limits and lot size. */
|
|
1189
|
-
info(symbol) {
|
|
1190
|
-
return getSymbolInfo(this._ctx, symbol);
|
|
1191
|
-
}
|
|
1192
|
-
/** Get order size limits and stop/limit distances for all symbols. */
|
|
1193
|
-
limits() {
|
|
1194
|
-
return getSymbolLimits(this._ctx);
|
|
1195
|
-
}
|
|
1196
|
-
};
|
|
1197
|
-
var InstrumentsDomain = class {
|
|
1198
|
-
constructor(_ctx) {
|
|
1199
|
-
this._ctx = _ctx;
|
|
1200
|
-
}
|
|
1201
|
-
/** Get all available instruments, optionally filtered by partial match (e.g. `{ type: "FOREX" }`). */
|
|
1202
|
-
get(params = {}) {
|
|
1203
|
-
return getInstruments(this._ctx, params);
|
|
1204
|
-
}
|
|
1205
|
-
};
|
|
1206
|
-
var OhlcDomain = class {
|
|
1207
|
-
constructor(_ctx) {
|
|
1208
|
-
this._ctx = _ctx;
|
|
1476
|
+
/** Hit the broker page to collect Cloudflare cookies before making API calls. */
|
|
1477
|
+
async _preflight() {
|
|
1478
|
+
try {
|
|
1479
|
+
const response = await retryRequest(
|
|
1480
|
+
{
|
|
1481
|
+
method: "GET",
|
|
1482
|
+
url: this._ctx.broker,
|
|
1483
|
+
headers: { ...baseHeaders(), Referer: this._ctx.broker }
|
|
1484
|
+
},
|
|
1485
|
+
this._ctx.retries
|
|
1486
|
+
);
|
|
1487
|
+
const setCookies = response.headers["set-cookie"] ?? [];
|
|
1488
|
+
const incoming = Cookies.parse(setCookies);
|
|
1489
|
+
this._ctx.cookies = Cookies.merge(this._ctx.cookies, incoming);
|
|
1490
|
+
} catch {
|
|
1491
|
+
}
|
|
1209
1492
|
}
|
|
1210
|
-
/**
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1493
|
+
/** Authenticate and establish a session: login, fetch CSRF, WebSocket handshake, and optional account switch. */
|
|
1494
|
+
async auth() {
|
|
1495
|
+
await this._preflight();
|
|
1496
|
+
await this.login();
|
|
1497
|
+
await this.fetchCsrf();
|
|
1498
|
+
if (this._ctx.debug) clearDebugLog();
|
|
1499
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
1500
|
+
const handshake = await waitForHandshake(endpoints.websocket(this._ctx.broker), cookieStr, 3e4, this._ctx.debug);
|
|
1501
|
+
this._ctx.atmosphereId = handshake.atmosphereId;
|
|
1502
|
+
this._ctx.accountId = handshake.accountId;
|
|
1503
|
+
if (this._ctx.config.accountId) {
|
|
1504
|
+
await this.switchAccount(this._ctx.config.accountId);
|
|
1505
|
+
const reconnect = await waitForHandshake(
|
|
1506
|
+
endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId),
|
|
1507
|
+
Cookies.serialize(this._ctx.cookies),
|
|
1508
|
+
3e4,
|
|
1509
|
+
this._ctx.debug
|
|
1510
|
+
);
|
|
1511
|
+
this._ctx.atmosphereId = reconnect.atmosphereId;
|
|
1512
|
+
this._ctx.accountId = reconnect.accountId;
|
|
1513
|
+
}
|
|
1220
1514
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1515
|
+
/** Connect to the broker with a persistent WebSocket: auth + persistent WS for data reuse and streaming. */
|
|
1516
|
+
async connect() {
|
|
1517
|
+
await this.auth();
|
|
1518
|
+
const wsManager = new WsManager();
|
|
1519
|
+
const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
|
|
1520
|
+
const cookieStr = Cookies.serialize(this._ctx.cookies);
|
|
1521
|
+
await wsManager.connect(wsUrl, cookieStr, this._ctx.debug);
|
|
1522
|
+
this._ctx.wsManager = wsManager;
|
|
1225
1523
|
}
|
|
1226
|
-
/**
|
|
1227
|
-
|
|
1228
|
-
|
|
1524
|
+
/** Close the persistent WebSocket connection. */
|
|
1525
|
+
disconnect() {
|
|
1526
|
+
if (this._ctx.wsManager) {
|
|
1527
|
+
this._ctx.wsManager.close();
|
|
1528
|
+
this._ctx.wsManager = null;
|
|
1529
|
+
}
|
|
1229
1530
|
}
|
|
1230
1531
|
};
|
|
1532
|
+
|
|
1533
|
+
// src/client.ts
|
|
1231
1534
|
var DxtradeClient = class {
|
|
1232
1535
|
_ctx;
|
|
1536
|
+
_session;
|
|
1233
1537
|
/** Position operations: get, close, metrics, streaming. */
|
|
1234
1538
|
positions;
|
|
1235
1539
|
/** Order operations: get, submit, cancel. */
|
|
@@ -1240,7 +1544,7 @@ var DxtradeClient = class {
|
|
|
1240
1544
|
symbols;
|
|
1241
1545
|
/** Instrument operations: get (with optional filtering). */
|
|
1242
1546
|
instruments;
|
|
1243
|
-
/** OHLC price bar operations: get. */
|
|
1547
|
+
/** OHLC price bar operations: get, stream. */
|
|
1244
1548
|
ohlc;
|
|
1245
1549
|
/** PnL assessment operations: get. */
|
|
1246
1550
|
assessments;
|
|
@@ -1268,6 +1572,7 @@ var DxtradeClient = class {
|
|
|
1268
1572
|
throw error;
|
|
1269
1573
|
}
|
|
1270
1574
|
};
|
|
1575
|
+
this._session = new SessionDomain(this._ctx);
|
|
1271
1576
|
this.positions = new PositionsDomain(this._ctx);
|
|
1272
1577
|
this.orders = new OrdersDomain(this._ctx);
|
|
1273
1578
|
this.account = new AccountDomain(this._ctx);
|
|
@@ -1278,27 +1583,27 @@ var DxtradeClient = class {
|
|
|
1278
1583
|
}
|
|
1279
1584
|
/** Authenticate with the broker using username and password. */
|
|
1280
1585
|
async login() {
|
|
1281
|
-
return login(
|
|
1586
|
+
return this._session.login();
|
|
1282
1587
|
}
|
|
1283
1588
|
/** Fetch the CSRF token required for authenticated requests. */
|
|
1284
1589
|
async fetchCsrf() {
|
|
1285
|
-
return fetchCsrf(
|
|
1590
|
+
return this._session.fetchCsrf();
|
|
1286
1591
|
}
|
|
1287
1592
|
/** Switch to a specific trading account by ID. */
|
|
1288
1593
|
async switchAccount(accountId) {
|
|
1289
|
-
return switchAccount(
|
|
1594
|
+
return this._session.switchAccount(accountId);
|
|
1290
1595
|
}
|
|
1291
1596
|
/** Authenticate and establish a session: login, fetch CSRF, WebSocket handshake, and optional account switch. */
|
|
1292
1597
|
async auth() {
|
|
1293
|
-
return auth(
|
|
1598
|
+
return this._session.auth();
|
|
1294
1599
|
}
|
|
1295
1600
|
/** Connect to the broker with a persistent WebSocket: auth + persistent WS for data reuse and streaming. */
|
|
1296
1601
|
async connect() {
|
|
1297
|
-
return connect(
|
|
1602
|
+
return this._session.connect();
|
|
1298
1603
|
}
|
|
1299
1604
|
/** Close the persistent WebSocket connection. */
|
|
1300
1605
|
disconnect() {
|
|
1301
|
-
return disconnect(
|
|
1606
|
+
return this._session.disconnect();
|
|
1302
1607
|
}
|
|
1303
1608
|
};
|
|
1304
1609
|
export {
|
|
@@ -1307,6 +1612,9 @@ export {
|
|
|
1307
1612
|
DxtradeClient,
|
|
1308
1613
|
DxtradeError,
|
|
1309
1614
|
ERROR,
|
|
1615
|
+
MESSAGE_CATEGORY,
|
|
1616
|
+
MESSAGE_TYPE,
|
|
1617
|
+
ORDER_STATUS,
|
|
1310
1618
|
ORDER_TYPE,
|
|
1311
1619
|
SIDE,
|
|
1312
1620
|
TIF,
|