@danielgroen/dxtrade-api 1.0.25 → 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 +2 -1
- package/dist/index.d.mts +124 -89
- package/dist/index.d.ts +124 -89
- package/dist/index.js +1063 -848
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1060 -848
- package/dist/index.mjs.map +1 -1
- package/llms.txt +2 -1
- package/package.json +2 -1
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";
|
|
@@ -195,7 +216,10 @@ var Cookies = class {
|
|
|
195
216
|
function baseHeaders() {
|
|
196
217
|
return {
|
|
197
218
|
"Content-Type": "application/json; charset=UTF-8",
|
|
198
|
-
|
|
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"
|
|
199
223
|
};
|
|
200
224
|
}
|
|
201
225
|
function authHeaders(csrf, cookieStr) {
|
|
@@ -221,7 +245,11 @@ async function retryRequest(config, retries = 3) {
|
|
|
221
245
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
222
246
|
console.warn(`[dxtrade-api] Attempt ${attempt} failed: ${message}`, config.url);
|
|
223
247
|
if ((0, import_axios.isAxiosError)(error) && error.response?.status === 429) {
|
|
224
|
-
|
|
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;
|
|
225
253
|
}
|
|
226
254
|
if (attempt === retries) throw error;
|
|
227
255
|
await new Promise((res) => setTimeout(res, 1e3 * attempt));
|
|
@@ -254,6 +282,11 @@ function parseAtmosphereId(data) {
|
|
|
254
282
|
}
|
|
255
283
|
return null;
|
|
256
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
|
+
}
|
|
257
290
|
function parseWsData(data) {
|
|
258
291
|
const raw = data.toString();
|
|
259
292
|
const pipeIndex = raw.indexOf("|");
|
|
@@ -287,10 +320,12 @@ var WsManager = class extends import_events.EventEmitter {
|
|
|
287
320
|
this.emit(payload.type, payload.body);
|
|
288
321
|
});
|
|
289
322
|
ws.on("error", (error) => {
|
|
323
|
+
checkWsRateLimit(error);
|
|
324
|
+
const err = new Error(`WebSocket manager error: ${error.message}`);
|
|
290
325
|
if (!this._ws) {
|
|
291
|
-
return reject(
|
|
326
|
+
return reject(err);
|
|
292
327
|
}
|
|
293
|
-
this.emit("error",
|
|
328
|
+
this.emit("error", err);
|
|
294
329
|
});
|
|
295
330
|
ws.on("close", () => {
|
|
296
331
|
this._ws = null;
|
|
@@ -332,347 +367,395 @@ var WsManager = class extends import_events.EventEmitter {
|
|
|
332
367
|
};
|
|
333
368
|
|
|
334
369
|
// src/domains/account/account.ts
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const body = await ctx.wsManager.waitFor("ACCOUNT_METRICS" /* ACCOUNT_METRICS */, timeout);
|
|
339
|
-
return body.allMetrics;
|
|
370
|
+
var AccountDomain = class {
|
|
371
|
+
constructor(_ctx) {
|
|
372
|
+
this._ctx = _ctx;
|
|
340
373
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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) => {
|
|
354
404
|
clearTimeout(timer);
|
|
355
405
|
ws.close();
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
ws.on("error", (error) => {
|
|
361
|
-
clearTimeout(timer);
|
|
362
|
-
ws.close();
|
|
363
|
-
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
|
+
});
|
|
364
409
|
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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}`);
|
|
385
439
|
}
|
|
386
|
-
} catch (error) {
|
|
387
|
-
if (error instanceof DxtradeError) throw error;
|
|
388
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
389
|
-
ctx.throwError("TRADE_HISTORY_ERROR" /* TRADE_HISTORY_ERROR */, `Trade history error: ${message}`);
|
|
390
440
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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}`);
|
|
411
470
|
}
|
|
412
|
-
} catch (error) {
|
|
413
|
-
if (error instanceof DxtradeError) throw error;
|
|
414
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
415
|
-
ctx.throwError("TRADE_JOURNAL_ERROR" /* TRADE_JOURNAL_ERROR */, `Trade journal error: ${message}`);
|
|
416
471
|
}
|
|
417
|
-
}
|
|
472
|
+
};
|
|
418
473
|
|
|
419
474
|
// src/domains/assessments/assessments.ts
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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))
|
|
432
494
|
},
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
)
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
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
|
+
}
|
|
442
503
|
}
|
|
443
|
-
}
|
|
504
|
+
};
|
|
444
505
|
|
|
445
506
|
// src/domains/instrument/instrument.ts
|
|
446
507
|
var import_ws3 = __toESM(require("ws"));
|
|
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
|
-
|
|
473
|
-
|
|
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
|
+
}
|
|
474
541
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
+
});
|
|
486
554
|
});
|
|
487
|
-
}
|
|
488
|
-
}
|
|
555
|
+
}
|
|
556
|
+
};
|
|
489
557
|
|
|
490
558
|
// src/domains/ohlc/ohlc.ts
|
|
491
559
|
var import_ws4 = __toESM(require("ws"));
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
"STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
|
|
496
|
-
"Streaming requires a persistent WebSocket. Use connect() instead of auth()."
|
|
497
|
-
);
|
|
560
|
+
var OhlcDomain = class {
|
|
561
|
+
constructor(_ctx) {
|
|
562
|
+
this._ctx = _ctx;
|
|
498
563
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (body?.subtopic !== subtopic) return;
|
|
507
|
-
const data = body.data;
|
|
508
|
-
if (!Array.isArray(data)) return;
|
|
509
|
-
if (!snapshotDone) {
|
|
510
|
-
snapshotBars.push(...data);
|
|
511
|
-
if (body.snapshotEnd) {
|
|
512
|
-
snapshotDone = true;
|
|
513
|
-
callback([...snapshotBars]);
|
|
514
|
-
resolveSnapshot?.();
|
|
515
|
-
}
|
|
516
|
-
} else {
|
|
517
|
-
callback(data);
|
|
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
|
+
);
|
|
518
571
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
chartIds: [],
|
|
537
|
-
requests: [
|
|
538
|
-
{
|
|
539
|
-
aggregationPeriodSeconds: resolution,
|
|
540
|
-
extendedSession: true,
|
|
541
|
-
forexPriceField: priceField,
|
|
542
|
-
id: 0,
|
|
543
|
-
maxBarsCount: maxBars,
|
|
544
|
-
range,
|
|
545
|
-
studySubscription: [],
|
|
546
|
-
subtopic,
|
|
547
|
-
symbol
|
|
548
|
-
}
|
|
549
|
-
]
|
|
550
|
-
},
|
|
551
|
-
headers
|
|
552
|
-
},
|
|
553
|
-
ctx.retries
|
|
554
|
-
);
|
|
555
|
-
} catch (error) {
|
|
556
|
-
ctx.wsManager.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
|
|
557
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
558
|
-
ctx.throwError("OHLC_ERROR" /* OHLC_ERROR */, `OHLC stream subscription error: ${message}`);
|
|
559
|
-
}
|
|
560
|
-
await new Promise((resolve, reject) => {
|
|
561
|
-
if (snapshotDone) return resolve();
|
|
562
|
-
const timer = setTimeout(() => {
|
|
563
|
-
if (snapshotBars.length > 0) {
|
|
564
|
-
snapshotDone = true;
|
|
565
|
-
callback([...snapshotBars]);
|
|
566
|
-
resolve();
|
|
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
|
+
}
|
|
567
589
|
} else {
|
|
568
|
-
|
|
569
|
-
reject(new DxtradeError("OHLC_TIMEOUT" /* OHLC_TIMEOUT */, "OHLC stream snapshot timed out"));
|
|
590
|
+
callback(data);
|
|
570
591
|
}
|
|
571
|
-
}, 3e4);
|
|
572
|
-
resolveSnapshot = () => {
|
|
573
|
-
clearTimeout(timer);
|
|
574
|
-
resolve();
|
|
575
592
|
};
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
await retryRequest(
|
|
607
|
-
{
|
|
608
|
-
method: "PUT",
|
|
609
|
-
url: endpoints.subscribeInstruments(ctx.broker),
|
|
610
|
-
data: { instruments: [symbol] },
|
|
611
|
-
headers
|
|
612
|
-
},
|
|
613
|
-
ctx.retries
|
|
614
|
-
);
|
|
615
|
-
await retryRequest(
|
|
616
|
-
{
|
|
617
|
-
method: "PUT",
|
|
618
|
-
url: endpoints.charts(ctx.broker),
|
|
619
|
-
data: {
|
|
620
|
-
chartIds: [],
|
|
621
|
-
requests: [
|
|
622
|
-
{
|
|
623
|
-
aggregationPeriodSeconds: resolution,
|
|
624
|
-
extendedSession: true,
|
|
625
|
-
forexPriceField: priceField,
|
|
626
|
-
id: 0,
|
|
627
|
-
maxBarsCount: maxBars,
|
|
628
|
-
range,
|
|
629
|
-
studySubscription: [],
|
|
630
|
-
subtopic: WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT,
|
|
631
|
-
symbol
|
|
632
|
-
}
|
|
633
|
-
]
|
|
634
|
-
},
|
|
635
|
-
headers
|
|
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
|
+
]
|
|
636
623
|
},
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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}`);
|
|
644
632
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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);
|
|
650
680
|
if (initSettleTimer) clearTimeout(initSettleTimer);
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
const body = msg.body;
|
|
655
|
-
if (body?.subtopic !== WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT) return;
|
|
656
|
-
if (Array.isArray(body.data)) {
|
|
657
|
-
bars.push(...body.data);
|
|
681
|
+
if (barSettleTimer) clearTimeout(barSettleTimer);
|
|
682
|
+
ws.close();
|
|
658
683
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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) {
|
|
665
742
|
cleanup();
|
|
666
743
|
resolve(bars);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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
|
+
});
|
|
673
756
|
});
|
|
674
|
-
}
|
|
675
|
-
}
|
|
757
|
+
}
|
|
758
|
+
};
|
|
676
759
|
|
|
677
760
|
// src/domains/order/order.ts
|
|
678
761
|
var import_crypto = __toESM(require("crypto"));
|
|
@@ -680,87 +763,96 @@ var import_ws6 = __toESM(require("ws"));
|
|
|
680
763
|
|
|
681
764
|
// src/domains/symbol/symbol.ts
|
|
682
765
|
var import_ws5 = __toESM(require("ws"));
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
766
|
+
var SymbolsDomain = class {
|
|
767
|
+
constructor(_ctx) {
|
|
768
|
+
this._ctx = _ctx;
|
|
769
|
+
}
|
|
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}`);
|
|
698
792
|
}
|
|
699
|
-
return suggests;
|
|
700
|
-
} catch (error) {
|
|
701
|
-
if (error instanceof DxtradeError) throw error;
|
|
702
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
703
|
-
ctx.throwError("SUGGEST_ERROR" /* SUGGEST_ERROR */, `Error getting symbol suggestions: ${message}`);
|
|
704
793
|
}
|
|
705
|
-
|
|
706
|
-
async
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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");
|
|
810
|
+
}
|
|
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}`);
|
|
721
816
|
}
|
|
722
|
-
return response.data;
|
|
723
|
-
} catch (error) {
|
|
724
|
-
if (error instanceof DxtradeError) throw error;
|
|
725
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
726
|
-
ctx.throwError("SYMBOL_INFO_ERROR" /* SYMBOL_INFO_ERROR */, `Error getting symbol info: ${message}`);
|
|
727
817
|
}
|
|
728
|
-
|
|
729
|
-
async
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
+
});
|
|
761
853
|
});
|
|
762
|
-
}
|
|
763
|
-
}
|
|
854
|
+
}
|
|
855
|
+
};
|
|
764
856
|
|
|
765
857
|
// src/domains/order/order.ts
|
|
766
858
|
function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
@@ -791,20 +883,21 @@ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
|
791
883
|
if (msg.type === "MESSAGE" /* MESSAGE */) {
|
|
792
884
|
const messages = msg.body;
|
|
793
885
|
const orderMsg = messages?.findLast?.(
|
|
794
|
-
(m) => m.messageCategory === "TRADE_LOG" && m.messageType === "ORDER" && !m.historyMessage
|
|
886
|
+
(m) => m.messageCategory === "TRADE_LOG" /* TRADE_LOG */ && m.messageType === "ORDER" /* ORDER */ && !m.historyMessage
|
|
795
887
|
);
|
|
796
888
|
if (!orderMsg) return;
|
|
797
889
|
const params = orderMsg.parametersTO;
|
|
798
|
-
if (params.orderStatus === "REJECTED") {
|
|
890
|
+
if (params.orderStatus === "REJECTED" /* REJECTED */) {
|
|
799
891
|
const reason = params.rejectReason?.key ?? "Unknown reason";
|
|
800
892
|
done(new Error(`[dxtrade-api] Order rejected: ${reason}`));
|
|
801
|
-
} else if (params.orderStatus === "FILLED") {
|
|
893
|
+
} else if (params.orderStatus === "FILLED" /* FILLED */) {
|
|
802
894
|
done(null, {
|
|
803
895
|
orderId: params.orderKey,
|
|
804
896
|
status: params.orderStatus,
|
|
805
897
|
symbol: params.symbol,
|
|
806
898
|
filledQuantity: params.filledQuantity,
|
|
807
|
-
filledPrice: params.filledPrice
|
|
899
|
+
filledPrice: params.filledPrice,
|
|
900
|
+
positionCode: params.positionCode
|
|
808
901
|
});
|
|
809
902
|
}
|
|
810
903
|
return;
|
|
@@ -812,9 +905,9 @@ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
|
812
905
|
if (msg.type === "ORDERS" /* ORDERS */) {
|
|
813
906
|
const body = msg.body?.[0];
|
|
814
907
|
if (!body?.orderId) return;
|
|
815
|
-
if (body.status === "REJECTED") {
|
|
908
|
+
if (body.status === "REJECTED" /* REJECTED */) {
|
|
816
909
|
done(new Error(`[dxtrade-api] Order rejected: ${body.statusDescription ?? "Unknown reason"}`));
|
|
817
|
-
} else if (body.status === "FILLED") {
|
|
910
|
+
} else if (body.status === "FILLED" /* FILLED */) {
|
|
818
911
|
done(null, body);
|
|
819
912
|
}
|
|
820
913
|
}
|
|
@@ -824,161 +917,236 @@ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
|
824
917
|
settled = true;
|
|
825
918
|
clearTimeout(timer);
|
|
826
919
|
ws.close();
|
|
827
|
-
|
|
920
|
+
checkWsRateLimit(error);
|
|
921
|
+
reject(new DxtradeError("ORDER_ERROR" /* ORDER_ERROR */, `WebSocket order listener error: ${error.message}`));
|
|
828
922
|
});
|
|
829
923
|
});
|
|
830
924
|
return { promise, ready };
|
|
831
925
|
}
|
|
832
|
-
|
|
833
|
-
ctx.ensureSession();
|
|
834
|
-
if (ctx.wsManager) {
|
|
835
|
-
return ctx.wsManager.waitFor("ORDERS" /* ORDERS */, timeout);
|
|
836
|
-
}
|
|
837
|
-
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
838
|
-
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
926
|
+
function createWsManagerOrderListener(ctx, timeout = 3e4) {
|
|
839
927
|
return new Promise((resolve, reject) => {
|
|
840
|
-
|
|
928
|
+
let settled = false;
|
|
841
929
|
const timer = setTimeout(() => {
|
|
842
|
-
|
|
843
|
-
|
|
930
|
+
if (settled) return;
|
|
931
|
+
settled = true;
|
|
932
|
+
cleanup();
|
|
933
|
+
reject(new Error("[dxtrade-api] Order update timed out"));
|
|
844
934
|
}, timeout);
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
if (typeof msg === "string") return;
|
|
849
|
-
if (msg.type === "ORDERS" /* ORDERS */) {
|
|
850
|
-
clearTimeout(timer);
|
|
851
|
-
ws.close();
|
|
852
|
-
resolve(msg.body);
|
|
853
|
-
}
|
|
854
|
-
});
|
|
855
|
-
ws.on("error", (error) => {
|
|
935
|
+
function done(err, result) {
|
|
936
|
+
if (settled) return;
|
|
937
|
+
settled = true;
|
|
856
938
|
clearTimeout(timer);
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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);
|
|
860
979
|
});
|
|
861
980
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (!accountId) {
|
|
866
|
-
ctx.throwError("CANCEL_ORDER_ERROR" /* CANCEL_ORDER_ERROR */, "accountId is required to cancel an order");
|
|
867
|
-
}
|
|
868
|
-
try {
|
|
869
|
-
await retryRequest(
|
|
870
|
-
{
|
|
871
|
-
method: "DELETE",
|
|
872
|
-
url: endpoints.cancelOrder(ctx.broker, accountId, orderChainId),
|
|
873
|
-
headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
|
|
874
|
-
},
|
|
875
|
-
ctx.retries
|
|
876
|
-
);
|
|
877
|
-
} catch (error) {
|
|
878
|
-
if (error instanceof DxtradeError) throw error;
|
|
879
|
-
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
880
|
-
ctx.throwError("CANCEL_ORDER_ERROR" /* CANCEL_ORDER_ERROR */, `Cancel order error: ${message}`);
|
|
981
|
+
var OrdersDomain = class {
|
|
982
|
+
constructor(_ctx) {
|
|
983
|
+
this._ctx = _ctx;
|
|
881
984
|
}
|
|
882
|
-
|
|
883
|
-
async
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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
|
+
});
|
|
888
1016
|
}
|
|
889
|
-
|
|
890
|
-
async
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
const qty = side === "BUY" /* BUY */ ? units : -units;
|
|
911
|
-
const priceParam = orderType === "STOP" /* STOP */ ? "stopPrice" : "limitPrice";
|
|
912
|
-
const orderData = {
|
|
913
|
-
directExchange: false,
|
|
914
|
-
legs: [
|
|
915
|
-
{
|
|
916
|
-
...instrumentId != null && { instrumentId },
|
|
917
|
-
...positionCode != null && { positionCode },
|
|
918
|
-
positionEffect,
|
|
919
|
-
ratioQuantity: 1,
|
|
920
|
-
symbol
|
|
921
|
-
}
|
|
922
|
-
],
|
|
923
|
-
orderSide: side,
|
|
924
|
-
orderType,
|
|
925
|
-
quantity: qty,
|
|
926
|
-
requestId: orderCode ?? `gwt-uid-931-${import_crypto.default.randomUUID()}`,
|
|
927
|
-
timeInForce: tif,
|
|
928
|
-
...expireDate != null && { expireDate },
|
|
929
|
-
...metadata != null && { metadata }
|
|
930
|
-
};
|
|
931
|
-
if (price != null && orderType !== "MARKET" /* MARKET */) {
|
|
932
|
-
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
|
+
}
|
|
933
1038
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
orderType: "STOP" /* STOP */,
|
|
942
|
-
quantityForProtection: qty,
|
|
943
|
-
removed: false
|
|
944
|
-
};
|
|
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
|
+
}
|
|
945
1046
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
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 }
|
|
956
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
|
+
}
|
|
957
1148
|
}
|
|
958
|
-
|
|
959
|
-
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
960
|
-
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
961
|
-
const listener = createOrderListener(wsUrl, cookieStr, 3e4, ctx.debug);
|
|
962
|
-
await listener.ready;
|
|
963
|
-
const response = await retryRequest(
|
|
964
|
-
{
|
|
965
|
-
method: "POST",
|
|
966
|
-
url: endpoints.submitOrder(ctx.broker),
|
|
967
|
-
data: orderData,
|
|
968
|
-
headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
|
|
969
|
-
},
|
|
970
|
-
ctx.retries
|
|
971
|
-
);
|
|
972
|
-
ctx.callbacks.onOrderPlaced?.(response.data);
|
|
973
|
-
const orderUpdate = await listener.promise;
|
|
974
|
-
ctx.callbacks.onOrderUpdate?.(orderUpdate);
|
|
975
|
-
return orderUpdate;
|
|
976
|
-
} catch (error) {
|
|
977
|
-
if (error instanceof DxtradeError) throw error;
|
|
978
|
-
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
979
|
-
ctx.throwError("ORDER_ERROR" /* ORDER_ERROR */, `Error submitting order: ${message}`);
|
|
980
|
-
}
|
|
981
|
-
}
|
|
1149
|
+
};
|
|
982
1150
|
|
|
983
1151
|
// src/domains/position/position.ts
|
|
984
1152
|
var import_ws7 = __toESM(require("ws"));
|
|
@@ -999,110 +1167,239 @@ function mergePositionsWithMetrics(positions, metrics) {
|
|
|
999
1167
|
};
|
|
1000
1168
|
});
|
|
1001
1169
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
"STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
|
|
1006
|
-
"Streaming requires a persistent WebSocket. Use connect() instead of auth()."
|
|
1007
|
-
);
|
|
1170
|
+
var PositionsDomain = class {
|
|
1171
|
+
constructor(_ctx) {
|
|
1172
|
+
this._ctx = _ctx;
|
|
1008
1173
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
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
|
+
);
|
|
1014
1181
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
ctx.wsManager.on("POSITIONS" /* POSITIONS */, onPositions);
|
|
1019
|
-
ctx.wsManager.on("POSITION_METRICS" /* POSITION_METRICS */, onMetrics);
|
|
1020
|
-
emit();
|
|
1021
|
-
return () => {
|
|
1022
|
-
ctx.wsManager?.removeListener("POSITIONS" /* POSITIONS */, onPositions);
|
|
1023
|
-
ctx.wsManager?.removeListener("POSITION_METRICS" /* POSITION_METRICS */, onMetrics);
|
|
1024
|
-
};
|
|
1025
|
-
}
|
|
1026
|
-
async function getPositions(ctx) {
|
|
1027
|
-
ctx.ensureSession();
|
|
1028
|
-
if (ctx.wsManager) {
|
|
1029
|
-
const [positions, metrics] = await Promise.all([
|
|
1030
|
-
ctx.wsManager.waitFor("POSITIONS" /* POSITIONS */),
|
|
1031
|
-
ctx.wsManager.waitFor("POSITION_METRICS" /* POSITION_METRICS */)
|
|
1032
|
-
]);
|
|
1033
|
-
return mergePositionsWithMetrics(positions, metrics);
|
|
1034
|
-
}
|
|
1035
|
-
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
1036
|
-
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
1037
|
-
return new Promise((resolve, reject) => {
|
|
1038
|
-
const ws = new import_ws7.default(wsUrl, { headers: { Cookie: cookieStr } });
|
|
1039
|
-
let positions = null;
|
|
1040
|
-
let metrics = null;
|
|
1041
|
-
const timer = setTimeout(() => {
|
|
1042
|
-
ws.close();
|
|
1043
|
-
reject(new DxtradeError("ACCOUNT_POSITIONS_TIMEOUT" /* ACCOUNT_POSITIONS_TIMEOUT */, "Account positions timed out"));
|
|
1044
|
-
}, 3e4);
|
|
1045
|
-
ws.on("message", (data) => {
|
|
1046
|
-
const msg = parseWsData(data);
|
|
1047
|
-
if (shouldLog(msg, ctx.debug)) debugLog(msg);
|
|
1048
|
-
if (typeof msg === "string") return;
|
|
1049
|
-
if (msg.type === "POSITIONS" /* POSITIONS */) {
|
|
1050
|
-
positions = msg.body;
|
|
1051
|
-
}
|
|
1052
|
-
if (msg.type === "POSITION_METRICS" /* POSITION_METRICS */) {
|
|
1053
|
-
metrics = msg.body;
|
|
1054
|
-
}
|
|
1182
|
+
const emit = () => {
|
|
1183
|
+
const positions = this._ctx.wsManager.getCached("POSITIONS" /* POSITIONS */);
|
|
1184
|
+
const metrics = this._ctx.wsManager.getCached("POSITION_METRICS" /* POSITION_METRICS */);
|
|
1055
1185
|
if (positions && metrics) {
|
|
1186
|
+
callback(mergePositionsWithMetrics(positions, metrics));
|
|
1187
|
+
}
|
|
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
|
+
};
|
|
1198
|
+
}
|
|
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) => {
|
|
1056
1236
|
clearTimeout(timer);
|
|
1057
1237
|
ws.close();
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
ws.on("error", (error) => {
|
|
1062
|
-
clearTimeout(timer);
|
|
1063
|
-
ws.close();
|
|
1064
|
-
reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR" /* ACCOUNT_POSITIONS_ERROR */, `Account positions error: ${error.message}`));
|
|
1238
|
+
checkWsRateLimit(error);
|
|
1239
|
+
reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR" /* ACCOUNT_POSITIONS_ERROR */, `Account positions error: ${error.message}`));
|
|
1240
|
+
});
|
|
1065
1241
|
});
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
async
|
|
1069
|
-
|
|
1070
|
-
|
|
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
|
+
}
|
|
1071
1272
|
const closeData = {
|
|
1072
1273
|
legs: [
|
|
1073
1274
|
{
|
|
1074
|
-
instrumentId:
|
|
1075
|
-
positionCode:
|
|
1275
|
+
instrumentId: position.positionKey.instrumentId,
|
|
1276
|
+
positionCode: position.positionKey.positionCode,
|
|
1076
1277
|
positionEffect: "CLOSING",
|
|
1077
1278
|
ratioQuantity: 1,
|
|
1078
|
-
symbol:
|
|
1279
|
+
symbol: position.positionKey.positionCode
|
|
1079
1280
|
}
|
|
1080
1281
|
],
|
|
1081
1282
|
limitPrice: 0,
|
|
1082
1283
|
orderType: "MARKET",
|
|
1083
|
-
quantity: -
|
|
1284
|
+
quantity: -position.quantity,
|
|
1084
1285
|
timeInForce: "GTC"
|
|
1085
1286
|
};
|
|
1086
|
-
|
|
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
|
+
});
|
|
1087
1369
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
|
|
1103
|
-
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`);
|
|
1104
1384
|
}
|
|
1105
|
-
|
|
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
|
+
};
|
|
1106
1403
|
|
|
1107
1404
|
// src/domains/session/session.ts
|
|
1108
1405
|
var import_ws8 = __toESM(require("ws"));
|
|
@@ -1130,247 +1427,161 @@ function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
|
|
|
1130
1427
|
ws.on("error", (error) => {
|
|
1131
1428
|
clearTimeout(timer);
|
|
1132
1429
|
ws.close();
|
|
1430
|
+
checkWsRateLimit(error);
|
|
1133
1431
|
reject(new Error(`[dxtrade-api] WebSocket handshake error: ${error.message}`));
|
|
1134
1432
|
});
|
|
1135
1433
|
});
|
|
1136
1434
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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
|
+
}
|
|
1151
1461
|
},
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
+
);
|
|
1157
1490
|
const setCookies = response.headers["set-cookie"] ?? [];
|
|
1158
1491
|
const incoming = Cookies.parse(setCookies);
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
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}`);
|
|
1163
1503
|
}
|
|
1164
|
-
} catch (error) {
|
|
1165
|
-
if (error instanceof DxtradeError) throw error;
|
|
1166
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1167
|
-
ctx.throwError("LOGIN_ERROR" /* LOGIN_ERROR */, `Login error: ${message}`);
|
|
1168
1504
|
}
|
|
1169
|
-
|
|
1170
|
-
async
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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}`);
|
|
1186
1522
|
}
|
|
1187
|
-
} catch (error) {
|
|
1188
|
-
if (error instanceof DxtradeError) throw error;
|
|
1189
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1190
|
-
ctx.throwError("CSRF_ERROR" /* CSRF_ERROR */, `CSRF fetch error: ${message}`);
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
async function switchAccount(ctx, accountId) {
|
|
1194
|
-
ctx.ensureSession();
|
|
1195
|
-
try {
|
|
1196
|
-
await retryRequest(
|
|
1197
|
-
{
|
|
1198
|
-
method: "POST",
|
|
1199
|
-
url: endpoints.switchAccount(ctx.broker, accountId),
|
|
1200
|
-
headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
|
|
1201
|
-
},
|
|
1202
|
-
ctx.retries
|
|
1203
|
-
);
|
|
1204
|
-
ctx.callbacks.onAccountSwitch?.(accountId);
|
|
1205
|
-
} catch (error) {
|
|
1206
|
-
if (error instanceof DxtradeError) throw error;
|
|
1207
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1208
|
-
ctx.throwError("ACCOUNT_SWITCH_ERROR" /* ACCOUNT_SWITCH_ERROR */, `Error switching account: ${message}`);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
async function auth(ctx) {
|
|
1212
|
-
await login(ctx);
|
|
1213
|
-
await fetchCsrf(ctx);
|
|
1214
|
-
if (ctx.debug) clearDebugLog();
|
|
1215
|
-
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
1216
|
-
const handshake = await waitForHandshake(endpoints.websocket(ctx.broker), cookieStr, 3e4, ctx.debug);
|
|
1217
|
-
ctx.atmosphereId = handshake.atmosphereId;
|
|
1218
|
-
ctx.accountId = handshake.accountId;
|
|
1219
|
-
if (ctx.config.accountId) {
|
|
1220
|
-
await switchAccount(ctx, ctx.config.accountId);
|
|
1221
|
-
const reconnect = await waitForHandshake(
|
|
1222
|
-
endpoints.websocket(ctx.broker, ctx.atmosphereId),
|
|
1223
|
-
Cookies.serialize(ctx.cookies),
|
|
1224
|
-
3e4,
|
|
1225
|
-
ctx.debug
|
|
1226
|
-
);
|
|
1227
|
-
ctx.atmosphereId = reconnect.atmosphereId;
|
|
1228
|
-
ctx.accountId = reconnect.accountId;
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
async function connect(ctx) {
|
|
1232
|
-
await auth(ctx);
|
|
1233
|
-
const wsManager = new WsManager();
|
|
1234
|
-
const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
|
|
1235
|
-
const cookieStr = Cookies.serialize(ctx.cookies);
|
|
1236
|
-
await wsManager.connect(wsUrl, cookieStr, ctx.debug);
|
|
1237
|
-
ctx.wsManager = wsManager;
|
|
1238
|
-
}
|
|
1239
|
-
function disconnect(ctx) {
|
|
1240
|
-
if (ctx.wsManager) {
|
|
1241
|
-
ctx.wsManager.close();
|
|
1242
|
-
ctx.wsManager = null;
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
// src/client.ts
|
|
1247
|
-
var PositionsDomain = class {
|
|
1248
|
-
constructor(_ctx) {
|
|
1249
|
-
this._ctx = _ctx;
|
|
1250
|
-
}
|
|
1251
|
-
/** Get all open positions with P&L metrics merged. */
|
|
1252
|
-
get() {
|
|
1253
|
-
return getPositions(this._ctx);
|
|
1254
|
-
}
|
|
1255
|
-
/** Close a position. Supports partial closes by specifying a quantity smaller than the full position size. */
|
|
1256
|
-
close(params) {
|
|
1257
|
-
return closePosition(this._ctx, params);
|
|
1258
|
-
}
|
|
1259
|
-
/** Close all open positions with market orders. */
|
|
1260
|
-
closeAll() {
|
|
1261
|
-
return closeAllPositions(this._ctx);
|
|
1262
1523
|
}
|
|
1263
|
-
/**
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
*/
|
|
1280
|
-
submit(params) {
|
|
1281
|
-
return submitOrder(this._ctx, params);
|
|
1282
|
-
}
|
|
1283
|
-
/** Cancel a single pending order by its order chain ID. */
|
|
1284
|
-
cancel(orderChainId) {
|
|
1285
|
-
return cancelOrder(this._ctx, orderChainId);
|
|
1286
|
-
}
|
|
1287
|
-
/** Cancel all pending orders. */
|
|
1288
|
-
cancelAll() {
|
|
1289
|
-
return cancelAllOrders(this._ctx);
|
|
1290
|
-
}
|
|
1291
|
-
};
|
|
1292
|
-
var AccountDomain = class {
|
|
1293
|
-
constructor(_ctx) {
|
|
1294
|
-
this._ctx = _ctx;
|
|
1295
|
-
}
|
|
1296
|
-
/** Get account metrics including equity, balance, margin, and open P&L. */
|
|
1297
|
-
metrics() {
|
|
1298
|
-
return getAccountMetrics(this._ctx);
|
|
1299
|
-
}
|
|
1300
|
-
/**
|
|
1301
|
-
* Fetch trade journal entries for a date range.
|
|
1302
|
-
* @param params.from - Start timestamp (Unix ms)
|
|
1303
|
-
* @param params.to - End timestamp (Unix ms)
|
|
1304
|
-
*/
|
|
1305
|
-
tradeJournal(params) {
|
|
1306
|
-
return getTradeJournal(this._ctx, params);
|
|
1307
|
-
}
|
|
1308
|
-
/**
|
|
1309
|
-
* Fetch trade history for a date range.
|
|
1310
|
-
* @param params.from - Start timestamp (Unix ms)
|
|
1311
|
-
* @param params.to - End timestamp (Unix ms)
|
|
1312
|
-
*/
|
|
1313
|
-
tradeHistory(params) {
|
|
1314
|
-
return getTradeHistory(this._ctx, params);
|
|
1315
|
-
}
|
|
1316
|
-
};
|
|
1317
|
-
var SymbolsDomain = class {
|
|
1318
|
-
constructor(_ctx) {
|
|
1319
|
-
this._ctx = _ctx;
|
|
1320
|
-
}
|
|
1321
|
-
/** Search for symbols matching the given text (e.g. "EURUSD", "BTC"). */
|
|
1322
|
-
search(text) {
|
|
1323
|
-
return getSymbolSuggestions(this._ctx, text);
|
|
1324
|
-
}
|
|
1325
|
-
/** Get detailed instrument info for a symbol, including volume limits and lot size. */
|
|
1326
|
-
info(symbol) {
|
|
1327
|
-
return getSymbolInfo(this._ctx, symbol);
|
|
1328
|
-
}
|
|
1329
|
-
/** Get order size limits and stop/limit distances for all symbols. */
|
|
1330
|
-
limits() {
|
|
1331
|
-
return getSymbolLimits(this._ctx);
|
|
1332
|
-
}
|
|
1333
|
-
};
|
|
1334
|
-
var InstrumentsDomain = class {
|
|
1335
|
-
constructor(_ctx) {
|
|
1336
|
-
this._ctx = _ctx;
|
|
1337
|
-
}
|
|
1338
|
-
/** Get all available instruments, optionally filtered by partial match (e.g. `{ type: "FOREX" }`). */
|
|
1339
|
-
get(params = {}) {
|
|
1340
|
-
return getInstruments(this._ctx, params);
|
|
1341
|
-
}
|
|
1342
|
-
};
|
|
1343
|
-
var OhlcDomain = class {
|
|
1344
|
-
constructor(_ctx) {
|
|
1345
|
-
this._ctx = _ctx;
|
|
1346
|
-
}
|
|
1347
|
-
/**
|
|
1348
|
-
* Fetch OHLC price bars for a symbol.
|
|
1349
|
-
* @param params.symbol - Instrument symbol (e.g. "EURUSD")
|
|
1350
|
-
* @param params.resolution - Bar period in seconds (default: 60 = 1 min)
|
|
1351
|
-
* @param params.range - Lookback window in seconds (default: 432000 = 5 days)
|
|
1352
|
-
* @param params.maxBars - Maximum bars to return (default: 3500)
|
|
1353
|
-
* @param params.priceField - "bid" or "ask" (default: "bid")
|
|
1354
|
-
*/
|
|
1355
|
-
get(params) {
|
|
1356
|
-
return getOHLC(this._ctx, params);
|
|
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
|
+
}
|
|
1357
1540
|
}
|
|
1358
|
-
/**
|
|
1359
|
-
|
|
1360
|
-
|
|
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
|
+
}
|
|
1361
1562
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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;
|
|
1366
1571
|
}
|
|
1367
|
-
/**
|
|
1368
|
-
|
|
1369
|
-
|
|
1572
|
+
/** Close the persistent WebSocket connection. */
|
|
1573
|
+
disconnect() {
|
|
1574
|
+
if (this._ctx.wsManager) {
|
|
1575
|
+
this._ctx.wsManager.close();
|
|
1576
|
+
this._ctx.wsManager = null;
|
|
1577
|
+
}
|
|
1370
1578
|
}
|
|
1371
1579
|
};
|
|
1580
|
+
|
|
1581
|
+
// src/client.ts
|
|
1372
1582
|
var DxtradeClient = class {
|
|
1373
1583
|
_ctx;
|
|
1584
|
+
_session;
|
|
1374
1585
|
/** Position operations: get, close, metrics, streaming. */
|
|
1375
1586
|
positions;
|
|
1376
1587
|
/** Order operations: get, submit, cancel. */
|
|
@@ -1409,6 +1620,7 @@ var DxtradeClient = class {
|
|
|
1409
1620
|
throw error;
|
|
1410
1621
|
}
|
|
1411
1622
|
};
|
|
1623
|
+
this._session = new SessionDomain(this._ctx);
|
|
1412
1624
|
this.positions = new PositionsDomain(this._ctx);
|
|
1413
1625
|
this.orders = new OrdersDomain(this._ctx);
|
|
1414
1626
|
this.account = new AccountDomain(this._ctx);
|
|
@@ -1419,27 +1631,27 @@ var DxtradeClient = class {
|
|
|
1419
1631
|
}
|
|
1420
1632
|
/** Authenticate with the broker using username and password. */
|
|
1421
1633
|
async login() {
|
|
1422
|
-
return login(
|
|
1634
|
+
return this._session.login();
|
|
1423
1635
|
}
|
|
1424
1636
|
/** Fetch the CSRF token required for authenticated requests. */
|
|
1425
1637
|
async fetchCsrf() {
|
|
1426
|
-
return fetchCsrf(
|
|
1638
|
+
return this._session.fetchCsrf();
|
|
1427
1639
|
}
|
|
1428
1640
|
/** Switch to a specific trading account by ID. */
|
|
1429
1641
|
async switchAccount(accountId) {
|
|
1430
|
-
return switchAccount(
|
|
1642
|
+
return this._session.switchAccount(accountId);
|
|
1431
1643
|
}
|
|
1432
1644
|
/** Authenticate and establish a session: login, fetch CSRF, WebSocket handshake, and optional account switch. */
|
|
1433
1645
|
async auth() {
|
|
1434
|
-
return auth(
|
|
1646
|
+
return this._session.auth();
|
|
1435
1647
|
}
|
|
1436
1648
|
/** Connect to the broker with a persistent WebSocket: auth + persistent WS for data reuse and streaming. */
|
|
1437
1649
|
async connect() {
|
|
1438
|
-
return connect(
|
|
1650
|
+
return this._session.connect();
|
|
1439
1651
|
}
|
|
1440
1652
|
/** Close the persistent WebSocket connection. */
|
|
1441
1653
|
disconnect() {
|
|
1442
|
-
return disconnect(
|
|
1654
|
+
return this._session.disconnect();
|
|
1443
1655
|
}
|
|
1444
1656
|
};
|
|
1445
1657
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1449,6 +1661,9 @@ var DxtradeClient = class {
|
|
|
1449
1661
|
DxtradeClient,
|
|
1450
1662
|
DxtradeError,
|
|
1451
1663
|
ERROR,
|
|
1664
|
+
MESSAGE_CATEGORY,
|
|
1665
|
+
MESSAGE_TYPE,
|
|
1666
|
+
ORDER_STATUS,
|
|
1452
1667
|
ORDER_TYPE,
|
|
1453
1668
|
SIDE,
|
|
1454
1669
|
TIF,
|