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