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