@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/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
- "Accept-Language": "en-US,en;q=0.9"
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
- throw new DxtradeError("RATE_LIMITED" /* RATE_LIMITED */, "Rate limited (429). Too many requests \u2014 try again later.");
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(error);
326
+ return reject(err);
291
327
  }
292
- this.emit("error", 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
- async function getAccountMetrics(ctx, timeout = 3e4) {
335
- ctx.ensureSession();
336
- if (ctx.wsManager) {
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
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
341
- const cookieStr = Cookies.serialize(ctx.cookies);
342
- return new Promise((resolve, reject) => {
343
- const ws = new import_ws2.default(wsUrl, { headers: { Cookie: cookieStr } });
344
- const timer = setTimeout(() => {
345
- ws.close();
346
- reject(new DxtradeError("ACCOUNT_METRICS_TIMEOUT" /* ACCOUNT_METRICS_TIMEOUT */, "Account metrics timed out"));
347
- }, timeout);
348
- ws.on("message", (data) => {
349
- const msg = parseWsData(data);
350
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
351
- if (typeof msg === "string") return;
352
- if (msg.type === "ACCOUNT_METRICS" /* ACCOUNT_METRICS */) {
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
- const body = msg.body;
356
- resolve(body.allMetrics);
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
- async function getTradeHistory(ctx, params) {
367
- ctx.ensureSession();
368
- try {
369
- const response = await retryRequest(
370
- {
371
- method: "POST",
372
- url: endpoints.tradeHistory(ctx.broker, params),
373
- headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
374
- },
375
- ctx.retries
376
- );
377
- if (response.status === 200) {
378
- const setCookies = response.headers["set-cookie"] ?? [];
379
- const incoming = Cookies.parse(setCookies);
380
- ctx.cookies = Cookies.merge(ctx.cookies, incoming);
381
- return response.data;
382
- } else {
383
- ctx.throwError("TRADE_HISTORY_ERROR" /* TRADE_HISTORY_ERROR */, `Trade history failed: ${response.status}`);
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
- async function getTradeJournal(ctx, params) {
392
- ctx.ensureSession();
393
- try {
394
- const cookieStr = Cookies.serialize(ctx.cookies);
395
- const response = await retryRequest(
396
- {
397
- method: "GET",
398
- url: endpoints.tradeJournal(ctx.broker, params),
399
- headers: { ...baseHeaders(), Cookie: cookieStr }
400
- },
401
- ctx.retries
402
- );
403
- if (response.status === 200) {
404
- const setCookies = response.headers["set-cookie"] ?? [];
405
- const incoming = Cookies.parse(setCookies);
406
- ctx.cookies = Cookies.merge(ctx.cookies, incoming);
407
- return response.data;
408
- } else {
409
- ctx.throwError("TRADE_JOURNAL_ERROR" /* TRADE_JOURNAL_ERROR */, `Login failed: ${response.status}`);
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
- async function getAssessments(ctx, params) {
420
- ctx.ensureSession();
421
- try {
422
- const response = await retryRequest(
423
- {
424
- method: "POST",
425
- url: endpoints.assessments(ctx.broker),
426
- data: {
427
- from: params.from,
428
- instrument: params.instrument,
429
- subtype: params.subtype ?? null,
430
- to: params.to
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
- headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
433
- },
434
- ctx.retries
435
- );
436
- return response.data;
437
- } catch (error) {
438
- if (error instanceof DxtradeError) throw error;
439
- const message = error instanceof Error ? error.message : "Unknown error";
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
- async function getInstruments(ctx, params = {}, timeout = 3e4) {
447
- ctx.ensureSession();
448
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
449
- const cookieStr = Cookies.serialize(ctx.cookies);
450
- return new Promise((resolve, reject) => {
451
- const ws = new import_ws3.default(wsUrl, { headers: { Cookie: cookieStr } });
452
- const timer = setTimeout(() => {
453
- ws.close();
454
- reject(new DxtradeError("INSTRUMENTS_TIMEOUT" /* INSTRUMENTS_TIMEOUT */, "Instruments request timed out"));
455
- }, timeout);
456
- let instruments = [];
457
- let settleTimer = null;
458
- ws.on("message", (data) => {
459
- const msg = parseWsData(data);
460
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
461
- if (typeof msg === "string") return;
462
- if (msg.type === "INSTRUMENTS" /* INSTRUMENTS */) {
463
- instruments.push(...msg.body);
464
- if (settleTimer) clearTimeout(settleTimer);
465
- settleTimer = setTimeout(() => {
466
- clearTimeout(timer);
467
- ws.close();
468
- resolve(
469
- instruments.filter((instrument) => {
470
- for (const key in params) {
471
- if (params[key] !== instrument[key]) {
472
- return false;
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
- return true;
476
- })
477
- );
478
- }, 200);
479
- }
480
- });
481
- ws.on("error", (error) => {
482
- clearTimeout(timer);
483
- ws.close();
484
- reject(new DxtradeError("INSTRUMENTS_ERROR" /* INSTRUMENTS_ERROR */, `Instruments error: ${error.message}`));
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
- async function getOHLC(ctx, params, timeout = 3e4) {
492
- ctx.ensureSession();
493
- const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
494
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
495
- const cookieStr = Cookies.serialize(ctx.cookies);
496
- const headers = authHeaders(ctx.csrf, cookieStr);
497
- return new Promise((resolve, reject) => {
498
- const ws = new import_ws4.default(wsUrl, { headers: { Cookie: cookieStr } });
499
- const bars = [];
500
- let putsSent = false;
501
- let initSettleTimer = null;
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
- async function sendPuts() {
514
- putsSent = true;
515
- try {
516
- await retryRequest(
517
- {
518
- method: "PUT",
519
- url: endpoints.subscribeInstruments(ctx.broker),
520
- data: { instruments: [symbol] },
521
- headers
522
- },
523
- ctx.retries
524
- );
525
- await retryRequest(
526
- {
527
- method: "PUT",
528
- url: endpoints.charts(ctx.broker),
529
- data: {
530
- chartIds: [],
531
- requests: [
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
- ws.on("message", (data) => {
556
- const msg = parseWsData(data);
557
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
558
- if (typeof msg === "string") return;
559
- if (!putsSent) {
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
- initSettleTimer = setTimeout(() => sendPuts(), 1e3);
562
- return;
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
- if (barSettleTimer) clearTimeout(barSettleTimer);
570
- if (body.snapshotEnd) {
571
- cleanup();
572
- resolve(bars);
573
- } else {
574
- barSettleTimer = setTimeout(() => {
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
- }, 2e3);
578
- }
579
- });
580
- ws.on("error", (error) => {
581
- cleanup();
582
- reject(new DxtradeError("OHLC_ERROR" /* OHLC_ERROR */, `OHLC WebSocket error: ${error.message}`));
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
- async function getSymbolSuggestions(ctx, text) {
594
- ctx.ensureSession();
595
- try {
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 function getSymbolInfo(ctx, symbol) {
617
- ctx.ensureSession();
618
- try {
619
- const offsetMinutes = Math.abs((/* @__PURE__ */ new Date()).getTimezoneOffset());
620
- const cookieStr = Cookies.serialize(ctx.cookies);
621
- const response = await retryRequest(
622
- {
623
- method: "GET",
624
- url: endpoints.instrumentInfo(ctx.broker, symbol, offsetMinutes),
625
- headers: { ...baseHeaders(), Cookie: cookieStr }
626
- },
627
- ctx.retries
628
- );
629
- if (!response.data) {
630
- ctx.throwError("NO_SYMBOL_INFO" /* NO_SYMBOL_INFO */, "No symbol info returned");
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 function getSymbolLimits(ctx, timeout = 3e4) {
640
- ctx.ensureSession();
641
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
642
- const cookieStr = Cookies.serialize(ctx.cookies);
643
- return new Promise((resolve, reject) => {
644
- const ws = new import_ws5.default(wsUrl, { headers: { Cookie: cookieStr } });
645
- const timer = setTimeout(() => {
646
- ws.close();
647
- reject(new DxtradeError("LIMITS_TIMEOUT" /* LIMITS_TIMEOUT */, "Symbol limits request timed out"));
648
- }, timeout);
649
- let limits = [];
650
- let settleTimer = null;
651
- ws.on("message", (data) => {
652
- const msg = parseWsData(data);
653
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
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
- ws.on("error", (error) => {
668
- clearTimeout(timer);
669
- ws.close();
670
- reject(new DxtradeError("LIMITS_ERROR" /* LIMITS_ERROR */, `Symbol limits error: ${error.message}`));
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
- reject(new Error(`[dxtrade-api] WebSocket order listener error: ${error.message}`));
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
- async function getOrders(ctx, timeout = 3e4) {
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
- const ws = new import_ws6.default(wsUrl, { headers: { Cookie: cookieStr } });
928
+ let settled = false;
751
929
  const timer = setTimeout(() => {
752
- ws.close();
753
- reject(new DxtradeError("ORDERS_TIMEOUT" /* ORDERS_TIMEOUT */, "Orders request timed out"));
930
+ if (settled) return;
931
+ settled = true;
932
+ cleanup();
933
+ reject(new Error("[dxtrade-api] Order update timed out"));
754
934
  }, timeout);
755
- ws.on("message", (data) => {
756
- const msg = parseWsData(data);
757
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
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
- ws.close();
768
- reject(new DxtradeError("ORDERS_ERROR" /* ORDERS_ERROR */, `Orders error: ${error.message}`));
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
- async function cancelOrder(ctx, orderChainId) {
773
- ctx.ensureSession();
774
- const accountId = ctx.accountId ?? ctx.config.accountId;
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 function cancelAllOrders(ctx) {
794
- const orders = await getOrders(ctx);
795
- const pending = orders.filter((o) => !o.finalStatus);
796
- for (const order of pending) {
797
- await cancelOrder(ctx, order.orderId);
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 function submitOrder(ctx, params) {
801
- ctx.ensureSession();
802
- const {
803
- symbol,
804
- side,
805
- quantity,
806
- orderType,
807
- orderCode,
808
- price,
809
- instrumentId,
810
- stopLoss,
811
- takeProfit,
812
- positionEffect = "OPENING" /* OPENING */,
813
- positionCode,
814
- tif = "GTC",
815
- expireDate,
816
- metadata
817
- } = params;
818
- const info = await getSymbolInfo(ctx, symbol);
819
- const units = Math.round(quantity * info.lotSize);
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
- if (stopLoss) {
845
- orderData.stopLoss = {
846
- ...stopLoss.offset != null && { fixedOffset: stopLoss.offset },
847
- ...stopLoss.price != null && { fixedPrice: stopLoss.price },
848
- priceFixed: stopLoss.price != null,
849
- orderChainId: 0,
850
- orderId: 0,
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
- if (takeProfit) {
857
- orderData.takeProfit = {
858
- ...takeProfit.offset != null && { fixedOffset: takeProfit.offset },
859
- ...takeProfit.price != null && { fixedPrice: takeProfit.price },
860
- priceFixed: takeProfit.price != null,
861
- orderChainId: 0,
862
- orderId: 0,
863
- orderType: "LIMIT" /* LIMIT */,
864
- quantityForProtection: qty,
865
- removed: false
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
- try {
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 streamPositions(ctx, callback) {
896
- if (!ctx.wsManager) {
897
- ctx.throwError(
898
- "STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
899
- "Streaming requires a persistent WebSocket. Use connect() instead of auth()."
900
- );
901
- }
902
- const listener = (body) => callback(body);
903
- ctx.wsManager.on("POSITIONS" /* POSITIONS */, listener);
904
- const cached = ctx.wsManager.getCached("POSITIONS" /* POSITIONS */);
905
- if (cached !== void 0) {
906
- callback(cached);
907
- }
908
- return () => {
909
- ctx.wsManager?.removeListener("POSITIONS" /* POSITIONS */, listener);
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
- async function getPositions(ctx) {
913
- ctx.ensureSession();
914
- if (ctx.wsManager) {
915
- return ctx.wsManager.waitFor("POSITIONS" /* POSITIONS */);
1170
+ var PositionsDomain = class {
1171
+ constructor(_ctx) {
1172
+ this._ctx = _ctx;
916
1173
  }
917
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
918
- const cookieStr = Cookies.serialize(ctx.cookies);
919
- return new Promise((resolve, reject) => {
920
- const ws = new import_ws7.default(wsUrl, { headers: { Cookie: cookieStr } });
921
- const timer = setTimeout(() => {
922
- ws.close();
923
- reject(new DxtradeError("ACCOUNT_POSITIONS_TIMEOUT" /* ACCOUNT_POSITIONS_TIMEOUT */, "Account positions timed out"));
924
- }, 3e4);
925
- ws.on("message", (data) => {
926
- const msg = parseWsData(data);
927
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
928
- if (typeof msg === "string") return;
929
- if (msg.type === "POSITIONS" /* POSITIONS */) {
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
- ws.on("error", (error) => {
936
- clearTimeout(timer);
937
- ws.close();
938
- reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR" /* ACCOUNT_POSITIONS_ERROR */, `Account positions error: ${error.message}`));
939
- });
940
- });
941
- }
942
- async function getPositionMetrics(ctx, timeout = 3e4) {
943
- ctx.ensureSession();
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
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
948
- const cookieStr = Cookies.serialize(ctx.cookies);
949
- return new Promise((resolve, reject) => {
950
- const ws = new import_ws7.default(wsUrl, { headers: { Cookie: cookieStr } });
951
- const timer = setTimeout(() => {
952
- ws.close();
953
- reject(new DxtradeError("POSITION_METRICS_TIMEOUT" /* POSITION_METRICS_TIMEOUT */, "Position metrics timed out"));
954
- }, timeout);
955
- ws.on("message", (data) => {
956
- const msg = parseWsData(data);
957
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
958
- if (typeof msg === "string") return;
959
- if (msg.type === "POSITION_METRICS" /* POSITION_METRICS */) {
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
- resolve(msg.body);
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 function closeAllPositions(ctx) {
973
- const positions = await getPositions(ctx);
974
- for (const pos of positions) {
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: pos.positionKey.instrumentId,
979
- positionCode: pos.positionKey.positionCode,
1275
+ instrumentId: position.positionKey.instrumentId,
1276
+ positionCode: position.positionKey.positionCode,
980
1277
  positionEffect: "CLOSING",
981
1278
  ratioQuantity: 1,
982
- symbol: pos.positionKey.positionCode
1279
+ symbol: position.positionKey.positionCode
983
1280
  }
984
1281
  ],
985
1282
  limitPrice: 0,
986
1283
  orderType: "MARKET",
987
- quantity: -pos.quantity,
1284
+ quantity: -position.quantity,
988
1285
  timeInForce: "GTC"
989
1286
  };
990
- await closePosition(ctx, closeData);
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
- async function closePosition(ctx, data) {
994
- try {
995
- await retryRequest(
996
- {
997
- method: "POST",
998
- url: endpoints.closePosition(ctx.broker),
999
- data,
1000
- headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
1001
- },
1002
- ctx.retries
1003
- );
1004
- } catch (error) {
1005
- if (error instanceof DxtradeError) throw error;
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
- async function login(ctx) {
1042
- try {
1043
- const response = await retryRequest(
1044
- {
1045
- method: "POST",
1046
- url: endpoints.login(ctx.broker),
1047
- data: {
1048
- username: ctx.config.username,
1049
- password: ctx.config.password,
1050
- // TODO:: take a look at this below, domain nor vendor seems required. it works if i comment out both.
1051
- // however i still use it since i see brokers use it as well in the login endpoint.
1052
- // domain: ctx.config.broker,
1053
- vendor: ctx.config.broker
1054
- // END TODO::
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
- headers: { "Content-Type": "application/json" }
1057
- },
1058
- ctx.retries
1059
- );
1060
- if (response.status === 200) {
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
- ctx.cookies = Cookies.merge(ctx.cookies, incoming);
1064
- ctx.callbacks.onLogin?.();
1065
- } else {
1066
- ctx.throwError("LOGIN_FAILED" /* LOGIN_FAILED */, `Login failed: ${response.status}`);
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 function fetchCsrf(ctx) {
1075
- try {
1076
- const cookieStr = Cookies.serialize(ctx.cookies);
1077
- const response = await retryRequest(
1078
- {
1079
- method: "GET",
1080
- url: ctx.broker,
1081
- headers: { ...cookieOnlyHeaders(cookieStr), Referer: ctx.broker }
1082
- },
1083
- ctx.retries
1084
- );
1085
- const csrfMatch = response.data?.match(/name="csrf" content="([^"]+)"/);
1086
- if (csrfMatch) {
1087
- ctx.csrf = csrfMatch[1];
1088
- } else {
1089
- ctx.throwError("CSRF_NOT_FOUND" /* CSRF_NOT_FOUND */, "CSRF token not found");
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
- // src/client.ts
1151
- var PositionsDomain = class {
1152
- constructor(_ctx) {
1153
- this._ctx = _ctx;
1154
- }
1155
- /** Get all open positions via WebSocket. */
1156
- get() {
1157
- return getPositions(this._ctx);
1158
- }
1159
- /** Close a position. Supports partial closes by specifying a quantity smaller than the full position size. */
1160
- close(params) {
1161
- return closePosition(this._ctx, params);
1162
- }
1163
- /** Close all open positions with market orders. */
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
- * Fetch OHLC price bars for a symbol.
1257
- * @param params.symbol - Instrument symbol (e.g. "EURUSD")
1258
- * @param params.resolution - Bar period in seconds (default: 60 = 1 min)
1259
- * @param params.range - Lookback window in seconds (default: 432000 = 5 days)
1260
- * @param params.maxBars - Maximum bars to return (default: 3500)
1261
- * @param params.priceField - "bid" or "ask" (default: "bid")
1262
- */
1263
- get(params) {
1264
- return getOHLC(this._ctx, params);
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
- var AssessmentsDomain = class {
1268
- constructor(_ctx) {
1269
- this._ctx = _ctx;
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
- /** Fetch PnL assessments for an instrument within a date range. */
1272
- get(params) {
1273
- return getAssessments(this._ctx, params);
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(this._ctx);
1634
+ return this._session.login();
1327
1635
  }
1328
1636
  /** Fetch the CSRF token required for authenticated requests. */
1329
1637
  async fetchCsrf() {
1330
- return fetchCsrf(this._ctx);
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(this._ctx, accountId);
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(this._ctx);
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(this._ctx);
1650
+ return this._session.connect();
1343
1651
  }
1344
1652
  /** Close the persistent WebSocket connection. */
1345
1653
  disconnect() {
1346
- return disconnect(this._ctx);
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,