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