@danielgroen/dxtrade-api 1.0.23 → 1.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -66,7 +66,7 @@ var endpoints = {
66
66
  assessments: (base) => `${base}/api/assessments`,
67
67
  websocket: (base, atmosphereId) => `wss://${base.split("//")[1]}/client/connector` + websocketQuery(atmosphereId),
68
68
  tradeJournal: (base, params) => `${base}/api/tradejournal?from=${params.from}&to=${params.to}`,
69
- tradeHistory: (base, params) => `${base}/api/history?from=${params.from}&to=${params.to}&orderId=`,
69
+ tradeHistory: (base, params) => `${base}/api/history?from=${params.from}&to=${params.to}`,
70
70
  subscribeInstruments: (base) => `${base}/api/instruments/subscribeInstrumentSymbols`,
71
71
  charts: (base) => `${base}/api/charts`
72
72
  };
@@ -125,6 +125,9 @@ var ERROR = /* @__PURE__ */ ((ERROR2) => {
125
125
  ERROR2["TRADE_JOURNAL_ERROR"] = "TRADE_JOURNAL_ERROR";
126
126
  ERROR2["TRADE_HISTORY_ERROR"] = "TRADE_HISTORY_ERROR";
127
127
  ERROR2["ASSESSMENTS_ERROR"] = "ASSESSMENTS_ERROR";
128
+ ERROR2["RATE_LIMITED"] = "RATE_LIMITED";
129
+ ERROR2["WS_MANAGER_ERROR"] = "WS_MANAGER_ERROR";
130
+ ERROR2["STREAM_REQUIRES_CONNECT"] = "STREAM_REQUIRES_CONNECT";
128
131
  return ERROR2;
129
132
  })(ERROR || {});
130
133
  var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
@@ -149,6 +152,7 @@ var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
149
152
  let SUBTOPIC;
150
153
  ((SUBTOPIC2) => {
151
154
  SUBTOPIC2["BIG_CHART_COMPONENT"] = "BigChartComponentPresenter-4";
155
+ SUBTOPIC2["OHLC_STREAM"] = "OHLCStreamPresenter-0";
152
156
  })(SUBTOPIC = WS_MESSAGE2.SUBTOPIC || (WS_MESSAGE2.SUBTOPIC = {}));
153
157
  })(WS_MESSAGE || (WS_MESSAGE = {}));
154
158
 
@@ -163,7 +167,7 @@ var DxtradeError = class extends Error {
163
167
  };
164
168
 
165
169
  // src/domains/account/account.ts
166
- var import_ws = __toESM(require("ws"));
170
+ var import_ws2 = __toESM(require("ws"));
167
171
 
168
172
  // src/utils/cookies.ts
169
173
  var Cookies = class {
@@ -216,7 +220,9 @@ async function retryRequest(config, retries = 3) {
216
220
  } catch (error) {
217
221
  const message = error instanceof Error ? error.message : "Unknown error";
218
222
  console.warn(`[dxtrade-api] Attempt ${attempt} failed: ${message}`, config.url);
219
- if ((0, import_axios.isAxiosError)(error) && error.response?.status === 429) throw error;
223
+ if ((0, import_axios.isAxiosError)(error) && error.response?.status === 429) {
224
+ throw new DxtradeError("RATE_LIMITED" /* RATE_LIMITED */, "Rate limited (429). Too many requests \u2014 try again later.");
225
+ }
220
226
  if (attempt === retries) throw error;
221
227
  await new Promise((res) => setTimeout(res, 1e3 * attempt));
222
228
  }
@@ -259,13 +265,83 @@ function parseWsData(data) {
259
265
  }
260
266
  }
261
267
 
268
+ // src/utils/ws-manager.ts
269
+ var import_events = require("events");
270
+ var import_ws = __toESM(require("ws"));
271
+ var WsManager = class extends import_events.EventEmitter {
272
+ _ws = null;
273
+ _cache = /* @__PURE__ */ new Map();
274
+ connect(wsUrl, cookieStr, debug = false) {
275
+ return new Promise((resolve, reject) => {
276
+ const ws = new import_ws.default(wsUrl, { headers: { Cookie: cookieStr } });
277
+ ws.on("open", () => {
278
+ this._ws = ws;
279
+ resolve();
280
+ });
281
+ ws.on("message", (data) => {
282
+ const msg = parseWsData(data);
283
+ if (shouldLog(msg, debug)) debugLog(msg);
284
+ if (typeof msg === "string") return;
285
+ const payload = msg;
286
+ this._cache.set(payload.type, payload.body);
287
+ this.emit(payload.type, payload.body);
288
+ });
289
+ ws.on("error", (error) => {
290
+ if (!this._ws) {
291
+ return reject(error);
292
+ }
293
+ this.emit("error", error);
294
+ });
295
+ ws.on("close", () => {
296
+ this._ws = null;
297
+ this.emit("close");
298
+ });
299
+ });
300
+ }
301
+ waitFor(type, timeout = 3e4) {
302
+ const cached = this._cache.get(type);
303
+ if (cached !== void 0) {
304
+ return Promise.resolve(cached);
305
+ }
306
+ return new Promise((resolve, reject) => {
307
+ const timer = setTimeout(() => {
308
+ this.removeListener(type, onMessage);
309
+ reject(new Error(`WsManager: timed out waiting for ${type}`));
310
+ }, timeout);
311
+ const onMessage = (body) => {
312
+ clearTimeout(timer);
313
+ resolve(body);
314
+ };
315
+ this.once(type, onMessage);
316
+ });
317
+ }
318
+ getCached(type) {
319
+ return this._cache.get(type);
320
+ }
321
+ close() {
322
+ if (this._ws) {
323
+ this._ws.close();
324
+ this._ws = null;
325
+ }
326
+ this._cache.clear();
327
+ this.removeAllListeners();
328
+ }
329
+ get isConnected() {
330
+ return this._ws !== null && this._ws.readyState === import_ws.default.OPEN;
331
+ }
332
+ };
333
+
262
334
  // src/domains/account/account.ts
263
335
  async function getAccountMetrics(ctx, timeout = 3e4) {
264
336
  ctx.ensureSession();
337
+ if (ctx.wsManager) {
338
+ const body = await ctx.wsManager.waitFor("ACCOUNT_METRICS" /* ACCOUNT_METRICS */, timeout);
339
+ return body.allMetrics;
340
+ }
265
341
  const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
266
342
  const cookieStr = Cookies.serialize(ctx.cookies);
267
343
  return new Promise((resolve, reject) => {
268
- const ws = new import_ws.default(wsUrl, { headers: { Cookie: cookieStr } });
344
+ const ws = new import_ws2.default(wsUrl, { headers: { Cookie: cookieStr } });
269
345
  const timer = setTimeout(() => {
270
346
  ws.close();
271
347
  reject(new DxtradeError("ACCOUNT_METRICS_TIMEOUT" /* ACCOUNT_METRICS_TIMEOUT */, "Account metrics timed out"));
@@ -291,12 +367,11 @@ async function getAccountMetrics(ctx, timeout = 3e4) {
291
367
  async function getTradeHistory(ctx, params) {
292
368
  ctx.ensureSession();
293
369
  try {
294
- const cookieStr = Cookies.serialize(ctx.cookies);
295
370
  const response = await retryRequest(
296
371
  {
297
- method: "GET",
372
+ method: "POST",
298
373
  url: endpoints.tradeHistory(ctx.broker, params),
299
- headers: { ...baseHeaders(), Cookie: cookieStr }
374
+ headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
300
375
  },
301
376
  ctx.retries
302
377
  );
@@ -368,13 +443,13 @@ async function getAssessments(ctx, params) {
368
443
  }
369
444
 
370
445
  // src/domains/instrument/instrument.ts
371
- var import_ws2 = __toESM(require("ws"));
446
+ var import_ws3 = __toESM(require("ws"));
372
447
  async function getInstruments(ctx, params = {}, timeout = 3e4) {
373
448
  ctx.ensureSession();
374
449
  const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
375
450
  const cookieStr = Cookies.serialize(ctx.cookies);
376
451
  return new Promise((resolve, reject) => {
377
- const ws = new import_ws2.default(wsUrl, { headers: { Cookie: cookieStr } });
452
+ const ws = new import_ws3.default(wsUrl, { headers: { Cookie: cookieStr } });
378
453
  const timer = setTimeout(() => {
379
454
  ws.close();
380
455
  reject(new DxtradeError("INSTRUMENTS_TIMEOUT" /* INSTRUMENTS_TIMEOUT */, "Instruments request timed out"));
@@ -401,7 +476,7 @@ async function getInstruments(ctx, params = {}, timeout = 3e4) {
401
476
  return true;
402
477
  })
403
478
  );
404
- }, 0);
479
+ }, 200);
405
480
  }
406
481
  });
407
482
  ws.on("error", (error) => {
@@ -413,7 +488,96 @@ async function getInstruments(ctx, params = {}, timeout = 3e4) {
413
488
  }
414
489
 
415
490
  // src/domains/ohlc/ohlc.ts
416
- var import_ws3 = __toESM(require("ws"));
491
+ var import_ws4 = __toESM(require("ws"));
492
+ async function streamOHLC(ctx, params, callback) {
493
+ if (!ctx.wsManager) {
494
+ ctx.throwError(
495
+ "STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
496
+ "Streaming requires a persistent WebSocket. Use connect() instead of auth()."
497
+ );
498
+ }
499
+ const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
500
+ const subtopic = WS_MESSAGE.SUBTOPIC.OHLC_STREAM;
501
+ const headers = authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies));
502
+ const snapshotBars = [];
503
+ let snapshotDone = false;
504
+ let resolveSnapshot = null;
505
+ const onChartFeed = (body) => {
506
+ if (body?.subtopic !== subtopic) return;
507
+ const data = body.data;
508
+ if (!Array.isArray(data)) return;
509
+ if (!snapshotDone) {
510
+ snapshotBars.push(...data);
511
+ if (body.snapshotEnd) {
512
+ snapshotDone = true;
513
+ callback([...snapshotBars]);
514
+ resolveSnapshot?.();
515
+ }
516
+ } else {
517
+ callback(data);
518
+ }
519
+ };
520
+ ctx.wsManager.on("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
521
+ try {
522
+ await retryRequest(
523
+ {
524
+ method: "PUT",
525
+ url: endpoints.subscribeInstruments(ctx.broker),
526
+ data: { instruments: [symbol] },
527
+ headers
528
+ },
529
+ ctx.retries
530
+ );
531
+ await retryRequest(
532
+ {
533
+ method: "PUT",
534
+ url: endpoints.charts(ctx.broker),
535
+ data: {
536
+ chartIds: [],
537
+ requests: [
538
+ {
539
+ aggregationPeriodSeconds: resolution,
540
+ extendedSession: true,
541
+ forexPriceField: priceField,
542
+ id: 0,
543
+ maxBarsCount: maxBars,
544
+ range,
545
+ studySubscription: [],
546
+ subtopic,
547
+ symbol
548
+ }
549
+ ]
550
+ },
551
+ headers
552
+ },
553
+ ctx.retries
554
+ );
555
+ } catch (error) {
556
+ ctx.wsManager.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
557
+ const message = error instanceof Error ? error.message : "Unknown error";
558
+ ctx.throwError("OHLC_ERROR" /* OHLC_ERROR */, `OHLC stream subscription error: ${message}`);
559
+ }
560
+ await new Promise((resolve, reject) => {
561
+ if (snapshotDone) return resolve();
562
+ const timer = setTimeout(() => {
563
+ if (snapshotBars.length > 0) {
564
+ snapshotDone = true;
565
+ callback([...snapshotBars]);
566
+ resolve();
567
+ } else {
568
+ ctx.wsManager?.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
569
+ reject(new DxtradeError("OHLC_TIMEOUT" /* OHLC_TIMEOUT */, "OHLC stream snapshot timed out"));
570
+ }
571
+ }, 3e4);
572
+ resolveSnapshot = () => {
573
+ clearTimeout(timer);
574
+ resolve();
575
+ };
576
+ });
577
+ return () => {
578
+ ctx.wsManager?.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
579
+ };
580
+ }
417
581
  async function getOHLC(ctx, params, timeout = 3e4) {
418
582
  ctx.ensureSession();
419
583
  const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
@@ -421,7 +585,7 @@ async function getOHLC(ctx, params, timeout = 3e4) {
421
585
  const cookieStr = Cookies.serialize(ctx.cookies);
422
586
  const headers = authHeaders(ctx.csrf, cookieStr);
423
587
  return new Promise((resolve, reject) => {
424
- const ws = new import_ws3.default(wsUrl, { headers: { Cookie: cookieStr } });
588
+ const ws = new import_ws4.default(wsUrl, { headers: { Cookie: cookieStr } });
425
589
  const bars = [];
426
590
  let putsSent = false;
427
591
  let initSettleTimer = null;
@@ -512,10 +676,10 @@ async function getOHLC(ctx, params, timeout = 3e4) {
512
676
 
513
677
  // src/domains/order/order.ts
514
678
  var import_crypto = __toESM(require("crypto"));
515
- var import_ws5 = __toESM(require("ws"));
679
+ var import_ws6 = __toESM(require("ws"));
516
680
 
517
681
  // src/domains/symbol/symbol.ts
518
- var import_ws4 = __toESM(require("ws"));
682
+ var import_ws5 = __toESM(require("ws"));
519
683
  async function getSymbolSuggestions(ctx, text) {
520
684
  ctx.ensureSession();
521
685
  try {
@@ -567,7 +731,7 @@ async function getSymbolLimits(ctx, timeout = 3e4) {
567
731
  const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
568
732
  const cookieStr = Cookies.serialize(ctx.cookies);
569
733
  return new Promise((resolve, reject) => {
570
- const ws = new import_ws4.default(wsUrl, { headers: { Cookie: cookieStr } });
734
+ const ws = new import_ws5.default(wsUrl, { headers: { Cookie: cookieStr } });
571
735
  const timer = setTimeout(() => {
572
736
  ws.close();
573
737
  reject(new DxtradeError("LIMITS_TIMEOUT" /* LIMITS_TIMEOUT */, "Symbol limits request timed out"));
@@ -587,7 +751,7 @@ async function getSymbolLimits(ctx, timeout = 3e4) {
587
751
  clearTimeout(timer);
588
752
  ws.close();
589
753
  resolve(limits);
590
- }, 0);
754
+ }, 200);
591
755
  }
592
756
  });
593
757
  ws.on("error", (error) => {
@@ -600,7 +764,7 @@ async function getSymbolLimits(ctx, timeout = 3e4) {
600
764
 
601
765
  // src/domains/order/order.ts
602
766
  function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
603
- const ws = new import_ws5.default(wsUrl, { headers: { Cookie: cookieStr } });
767
+ const ws = new import_ws6.default(wsUrl, { headers: { Cookie: cookieStr } });
604
768
  let settled = false;
605
769
  const ready = new Promise((resolve) => {
606
770
  ws.on("open", resolve);
@@ -667,10 +831,13 @@ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
667
831
  }
668
832
  async function getOrders(ctx, timeout = 3e4) {
669
833
  ctx.ensureSession();
834
+ if (ctx.wsManager) {
835
+ return ctx.wsManager.waitFor("ORDERS" /* ORDERS */, timeout);
836
+ }
670
837
  const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
671
838
  const cookieStr = Cookies.serialize(ctx.cookies);
672
839
  return new Promise((resolve, reject) => {
673
- const ws = new import_ws5.default(wsUrl, { headers: { Cookie: cookieStr } });
840
+ const ws = new import_ws6.default(wsUrl, { headers: { Cookie: cookieStr } });
674
841
  const timer = setTimeout(() => {
675
842
  ws.close();
676
843
  reject(new DxtradeError("ORDERS_TIMEOUT" /* ORDERS_TIMEOUT */, "Orders request timed out"));
@@ -814,13 +981,63 @@ async function submitOrder(ctx, params) {
814
981
  }
815
982
 
816
983
  // src/domains/position/position.ts
817
- var import_ws6 = __toESM(require("ws"));
984
+ var import_ws7 = __toESM(require("ws"));
985
+ function mergePositionsWithMetrics(positions, metrics) {
986
+ const metricsMap = new Map(metrics.map((m) => [m.uid, m]));
987
+ return positions.map((pos) => {
988
+ const m = metricsMap.get(pos.uid);
989
+ return {
990
+ ...pos,
991
+ margin: m?.margin ?? 0,
992
+ plOpen: m?.plOpen ?? 0,
993
+ plClosed: m?.plClosed ?? 0,
994
+ totalCommissions: m?.totalCommissions ?? 0,
995
+ totalFinancing: m?.totalFinancing ?? 0,
996
+ plRate: m?.plRate ?? 0,
997
+ averagePrice: m?.averagePrice ?? 0,
998
+ marketValue: m?.marketValue ?? 0
999
+ };
1000
+ });
1001
+ }
1002
+ function streamPositions(ctx, callback) {
1003
+ if (!ctx.wsManager) {
1004
+ ctx.throwError(
1005
+ "STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
1006
+ "Streaming requires a persistent WebSocket. Use connect() instead of auth()."
1007
+ );
1008
+ }
1009
+ const emit = () => {
1010
+ const positions = ctx.wsManager.getCached("POSITIONS" /* POSITIONS */);
1011
+ const metrics = ctx.wsManager.getCached("POSITION_METRICS" /* POSITION_METRICS */);
1012
+ if (positions && metrics) {
1013
+ callback(mergePositionsWithMetrics(positions, metrics));
1014
+ }
1015
+ };
1016
+ const onPositions = () => emit();
1017
+ const onMetrics = () => emit();
1018
+ ctx.wsManager.on("POSITIONS" /* POSITIONS */, onPositions);
1019
+ ctx.wsManager.on("POSITION_METRICS" /* POSITION_METRICS */, onMetrics);
1020
+ emit();
1021
+ return () => {
1022
+ ctx.wsManager?.removeListener("POSITIONS" /* POSITIONS */, onPositions);
1023
+ ctx.wsManager?.removeListener("POSITION_METRICS" /* POSITION_METRICS */, onMetrics);
1024
+ };
1025
+ }
818
1026
  async function getPositions(ctx) {
819
1027
  ctx.ensureSession();
1028
+ if (ctx.wsManager) {
1029
+ const [positions, metrics] = await Promise.all([
1030
+ ctx.wsManager.waitFor("POSITIONS" /* POSITIONS */),
1031
+ ctx.wsManager.waitFor("POSITION_METRICS" /* POSITION_METRICS */)
1032
+ ]);
1033
+ return mergePositionsWithMetrics(positions, metrics);
1034
+ }
820
1035
  const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
821
1036
  const cookieStr = Cookies.serialize(ctx.cookies);
822
1037
  return new Promise((resolve, reject) => {
823
- const ws = new import_ws6.default(wsUrl, { headers: { Cookie: cookieStr } });
1038
+ const ws = new import_ws7.default(wsUrl, { headers: { Cookie: cookieStr } });
1039
+ let positions = null;
1040
+ let metrics = null;
824
1041
  const timer = setTimeout(() => {
825
1042
  ws.close();
826
1043
  reject(new DxtradeError("ACCOUNT_POSITIONS_TIMEOUT" /* ACCOUNT_POSITIONS_TIMEOUT */, "Account positions timed out"));
@@ -830,42 +1047,21 @@ async function getPositions(ctx) {
830
1047
  if (shouldLog(msg, ctx.debug)) debugLog(msg);
831
1048
  if (typeof msg === "string") return;
832
1049
  if (msg.type === "POSITIONS" /* POSITIONS */) {
833
- clearTimeout(timer);
834
- ws.close();
835
- resolve(msg.body);
1050
+ positions = msg.body;
836
1051
  }
837
- });
838
- ws.on("error", (error) => {
839
- clearTimeout(timer);
840
- ws.close();
841
- reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR" /* ACCOUNT_POSITIONS_ERROR */, `Account positions error: ${error.message}`));
842
- });
843
- });
844
- }
845
- async function getPositionMetrics(ctx, timeout = 3e4) {
846
- ctx.ensureSession();
847
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
848
- const cookieStr = Cookies.serialize(ctx.cookies);
849
- return new Promise((resolve, reject) => {
850
- const ws = new import_ws6.default(wsUrl, { headers: { Cookie: cookieStr } });
851
- const timer = setTimeout(() => {
852
- ws.close();
853
- reject(new DxtradeError("POSITION_METRICS_TIMEOUT" /* POSITION_METRICS_TIMEOUT */, "Position metrics timed out"));
854
- }, timeout);
855
- ws.on("message", (data) => {
856
- const msg = parseWsData(data);
857
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
858
- if (typeof msg === "string") return;
859
1052
  if (msg.type === "POSITION_METRICS" /* POSITION_METRICS */) {
1053
+ metrics = msg.body;
1054
+ }
1055
+ if (positions && metrics) {
860
1056
  clearTimeout(timer);
861
1057
  ws.close();
862
- resolve(msg.body);
1058
+ resolve(mergePositionsWithMetrics(positions, metrics));
863
1059
  }
864
1060
  });
865
1061
  ws.on("error", (error) => {
866
1062
  clearTimeout(timer);
867
1063
  ws.close();
868
- reject(new DxtradeError("POSITION_METRICS_ERROR" /* POSITION_METRICS_ERROR */, `Position metrics error: ${error.message}`));
1064
+ reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR" /* ACCOUNT_POSITIONS_ERROR */, `Account positions error: ${error.message}`));
869
1065
  });
870
1066
  });
871
1067
  }
@@ -909,10 +1105,10 @@ async function closePosition(ctx, data) {
909
1105
  }
910
1106
 
911
1107
  // src/domains/session/session.ts
912
- var import_ws7 = __toESM(require("ws"));
1108
+ var import_ws8 = __toESM(require("ws"));
913
1109
  function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
914
1110
  return new Promise((resolve, reject) => {
915
- const ws = new import_ws7.default(wsUrl, { headers: { Cookie: cookieStr } });
1111
+ const ws = new import_ws8.default(wsUrl, { headers: { Cookie: cookieStr } });
916
1112
  let atmosphereId = null;
917
1113
  const timer = setTimeout(() => {
918
1114
  ws.close();
@@ -1012,7 +1208,7 @@ async function switchAccount(ctx, accountId) {
1012
1208
  ctx.throwError("ACCOUNT_SWITCH_ERROR" /* ACCOUNT_SWITCH_ERROR */, `Error switching account: ${message}`);
1013
1209
  }
1014
1210
  }
1015
- async function connect(ctx) {
1211
+ async function auth(ctx) {
1016
1212
  await login(ctx);
1017
1213
  await fetchCsrf(ctx);
1018
1214
  if (ctx.debug) clearDebugLog();
@@ -1032,112 +1228,81 @@ async function connect(ctx) {
1032
1228
  ctx.accountId = reconnect.accountId;
1033
1229
  }
1034
1230
  }
1231
+ async function connect(ctx) {
1232
+ await auth(ctx);
1233
+ const wsManager = new WsManager();
1234
+ const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
1235
+ const cookieStr = Cookies.serialize(ctx.cookies);
1236
+ await wsManager.connect(wsUrl, cookieStr, ctx.debug);
1237
+ ctx.wsManager = wsManager;
1238
+ }
1239
+ function disconnect(ctx) {
1240
+ if (ctx.wsManager) {
1241
+ ctx.wsManager.close();
1242
+ ctx.wsManager = null;
1243
+ }
1244
+ }
1035
1245
 
1036
1246
  // src/client.ts
1037
- var DxtradeClient = class {
1038
- _ctx;
1039
- constructor(config) {
1040
- const callbacks = config.callbacks ?? {};
1041
- this._ctx = {
1042
- config,
1043
- callbacks,
1044
- cookies: {},
1045
- csrf: null,
1046
- accountId: config.accountId ?? null,
1047
- atmosphereId: null,
1048
- broker: config.broker,
1049
- retries: config.retries ?? 3,
1050
- debug: config.debug ?? false,
1051
- ensureSession() {
1052
- if (!this.csrf) {
1053
- throw new DxtradeError(
1054
- "NO_SESSION" /* NO_SESSION */,
1055
- "No active session. Call login() and fetchCsrf() or connect() first."
1056
- );
1057
- }
1058
- },
1059
- throwError(code, message) {
1060
- const error = new DxtradeError(code, message);
1061
- callbacks.onError?.(error);
1062
- throw error;
1063
- }
1064
- };
1247
+ var PositionsDomain = class {
1248
+ constructor(_ctx) {
1249
+ this._ctx = _ctx;
1065
1250
  }
1066
- /** Authenticate with the broker using username and password. */
1067
- async login() {
1068
- return login(this._ctx);
1251
+ /** Get all open positions with P&L metrics merged. */
1252
+ get() {
1253
+ return getPositions(this._ctx);
1069
1254
  }
1070
- /** Fetch the CSRF token required for authenticated requests. */
1071
- async fetchCsrf() {
1072
- return fetchCsrf(this._ctx);
1255
+ /** Close a position. Supports partial closes by specifying a quantity smaller than the full position size. */
1256
+ close(params) {
1257
+ return closePosition(this._ctx, params);
1073
1258
  }
1074
- /** Switch to a specific trading account by ID. */
1075
- async switchAccount(accountId) {
1076
- return switchAccount(this._ctx, accountId);
1077
- }
1078
- /** Connect to the broker: login, fetch CSRF, WebSocket handshake, and optional account switch. */
1079
- async connect() {
1080
- return connect(this._ctx);
1259
+ /** Close all open positions with market orders. */
1260
+ closeAll() {
1261
+ return closeAllPositions(this._ctx);
1081
1262
  }
1082
- /** Search for symbols matching the given text (e.g. "EURUSD", "BTC"). */
1083
- async getSymbolSuggestions(text) {
1084
- return getSymbolSuggestions(this._ctx, text);
1263
+ /** Stream real-time position updates with P&L metrics. Requires connect(). Returns unsubscribe function. */
1264
+ stream(callback) {
1265
+ return streamPositions(this._ctx, callback);
1085
1266
  }
1086
- /** Get detailed instrument info for a symbol, including volume limits and lot size. */
1087
- async getSymbolInfo(symbol) {
1088
- return getSymbolInfo(this._ctx, symbol);
1267
+ };
1268
+ var OrdersDomain = class {
1269
+ constructor(_ctx) {
1270
+ this._ctx = _ctx;
1089
1271
  }
1090
- /** Get order size limits and stop/limit distances for all symbols. */
1091
- async getSymbolLimits() {
1092
- return getSymbolLimits(this._ctx);
1272
+ /** Get all pending/open orders via WebSocket. */
1273
+ get() {
1274
+ return getOrders(this._ctx);
1093
1275
  }
1094
1276
  /**
1095
1277
  * Submit a trading order and wait for WebSocket confirmation.
1096
1278
  * Supports market, limit, and stop orders with optional stop loss and take profit.
1097
1279
  */
1098
- async submitOrder(params) {
1280
+ submit(params) {
1099
1281
  return submitOrder(this._ctx, params);
1100
1282
  }
1101
- /** Get all pending/open orders via WebSocket. */
1102
- async getOrders() {
1103
- return getOrders(this._ctx);
1104
- }
1105
1283
  /** Cancel a single pending order by its order chain ID. */
1106
- async cancelOrder(orderChainId) {
1284
+ cancel(orderChainId) {
1107
1285
  return cancelOrder(this._ctx, orderChainId);
1108
1286
  }
1109
1287
  /** Cancel all pending orders. */
1110
- async cancelAllOrders() {
1288
+ cancelAll() {
1111
1289
  return cancelAllOrders(this._ctx);
1112
1290
  }
1291
+ };
1292
+ var AccountDomain = class {
1293
+ constructor(_ctx) {
1294
+ this._ctx = _ctx;
1295
+ }
1113
1296
  /** Get account metrics including equity, balance, margin, and open P&L. */
1114
- async getAccountMetrics() {
1297
+ metrics() {
1115
1298
  return getAccountMetrics(this._ctx);
1116
1299
  }
1117
- /** Get all open positions via WebSocket. */
1118
- async getPositions() {
1119
- return getPositions(this._ctx);
1120
- }
1121
- /**
1122
- * Close a position. Supports partial closes by specifying a quantity smaller than the full position size.
1123
- */
1124
- async closePosition(position) {
1125
- return closePosition(this._ctx, position);
1126
- }
1127
- /** Close all open positions with market orders. */
1128
- async closeAllPositions() {
1129
- return closeAllPositions(this._ctx);
1130
- }
1131
- /** Get position-level P&L metrics via WebSocket. */
1132
- async getPositionMetrics() {
1133
- return getPositionMetrics(this._ctx);
1134
- }
1135
1300
  /**
1136
1301
  * Fetch trade journal entries for a date range.
1137
1302
  * @param params.from - Start timestamp (Unix ms)
1138
1303
  * @param params.to - End timestamp (Unix ms)
1139
1304
  */
1140
- async getTradeJournal(params) {
1305
+ tradeJournal(params) {
1141
1306
  return getTradeJournal(this._ctx, params);
1142
1307
  }
1143
1308
  /**
@@ -1145,16 +1310,39 @@ var DxtradeClient = class {
1145
1310
  * @param params.from - Start timestamp (Unix ms)
1146
1311
  * @param params.to - End timestamp (Unix ms)
1147
1312
  */
1148
- async getTradeHistory(params) {
1313
+ tradeHistory(params) {
1149
1314
  return getTradeHistory(this._ctx, params);
1150
1315
  }
1316
+ };
1317
+ var SymbolsDomain = class {
1318
+ constructor(_ctx) {
1319
+ this._ctx = _ctx;
1320
+ }
1321
+ /** Search for symbols matching the given text (e.g. "EURUSD", "BTC"). */
1322
+ search(text) {
1323
+ return getSymbolSuggestions(this._ctx, text);
1324
+ }
1325
+ /** Get detailed instrument info for a symbol, including volume limits and lot size. */
1326
+ info(symbol) {
1327
+ return getSymbolInfo(this._ctx, symbol);
1328
+ }
1329
+ /** Get order size limits and stop/limit distances for all symbols. */
1330
+ limits() {
1331
+ return getSymbolLimits(this._ctx);
1332
+ }
1333
+ };
1334
+ var InstrumentsDomain = class {
1335
+ constructor(_ctx) {
1336
+ this._ctx = _ctx;
1337
+ }
1151
1338
  /** Get all available instruments, optionally filtered by partial match (e.g. `{ type: "FOREX" }`). */
1152
- async getInstruments(params = {}) {
1339
+ get(params = {}) {
1153
1340
  return getInstruments(this._ctx, params);
1154
1341
  }
1155
- /** Fetch PnL assessments for an instrument within a date range. */
1156
- async getAssessments(params) {
1157
- return getAssessments(this._ctx, params);
1342
+ };
1343
+ var OhlcDomain = class {
1344
+ constructor(_ctx) {
1345
+ this._ctx = _ctx;
1158
1346
  }
1159
1347
  /**
1160
1348
  * Fetch OHLC price bars for a symbol.
@@ -1164,9 +1352,95 @@ var DxtradeClient = class {
1164
1352
  * @param params.maxBars - Maximum bars to return (default: 3500)
1165
1353
  * @param params.priceField - "bid" or "ask" (default: "bid")
1166
1354
  */
1167
- async getOHLC(params) {
1355
+ get(params) {
1168
1356
  return getOHLC(this._ctx, params);
1169
1357
  }
1358
+ /** Stream real-time OHLC bar updates. Requires connect(). Returns unsubscribe function. */
1359
+ stream(params, callback) {
1360
+ return streamOHLC(this._ctx, params, callback);
1361
+ }
1362
+ };
1363
+ var AssessmentsDomain = class {
1364
+ constructor(_ctx) {
1365
+ this._ctx = _ctx;
1366
+ }
1367
+ /** Fetch PnL assessments for an instrument within a date range. */
1368
+ get(params) {
1369
+ return getAssessments(this._ctx, params);
1370
+ }
1371
+ };
1372
+ var DxtradeClient = class {
1373
+ _ctx;
1374
+ /** Position operations: get, close, metrics, streaming. */
1375
+ positions;
1376
+ /** Order operations: get, submit, cancel. */
1377
+ orders;
1378
+ /** Account operations: metrics, trade journal, trade history. */
1379
+ account;
1380
+ /** Symbol operations: search, info, limits. */
1381
+ symbols;
1382
+ /** Instrument operations: get (with optional filtering). */
1383
+ instruments;
1384
+ /** OHLC price bar operations: get, stream. */
1385
+ ohlc;
1386
+ /** PnL assessment operations: get. */
1387
+ assessments;
1388
+ constructor(config) {
1389
+ const callbacks = config.callbacks ?? {};
1390
+ this._ctx = {
1391
+ config,
1392
+ callbacks,
1393
+ cookies: {},
1394
+ csrf: null,
1395
+ accountId: config.accountId ?? null,
1396
+ atmosphereId: null,
1397
+ wsManager: null,
1398
+ broker: config.broker,
1399
+ retries: config.retries ?? 3,
1400
+ debug: config.debug ?? false,
1401
+ ensureSession() {
1402
+ if (!this.csrf) {
1403
+ throw new DxtradeError("NO_SESSION" /* NO_SESSION */, "No active session. Call auth() or connect() first.");
1404
+ }
1405
+ },
1406
+ throwError(code, message) {
1407
+ const error = new DxtradeError(code, message);
1408
+ callbacks.onError?.(error);
1409
+ throw error;
1410
+ }
1411
+ };
1412
+ this.positions = new PositionsDomain(this._ctx);
1413
+ this.orders = new OrdersDomain(this._ctx);
1414
+ this.account = new AccountDomain(this._ctx);
1415
+ this.symbols = new SymbolsDomain(this._ctx);
1416
+ this.instruments = new InstrumentsDomain(this._ctx);
1417
+ this.ohlc = new OhlcDomain(this._ctx);
1418
+ this.assessments = new AssessmentsDomain(this._ctx);
1419
+ }
1420
+ /** Authenticate with the broker using username and password. */
1421
+ async login() {
1422
+ return login(this._ctx);
1423
+ }
1424
+ /** Fetch the CSRF token required for authenticated requests. */
1425
+ async fetchCsrf() {
1426
+ return fetchCsrf(this._ctx);
1427
+ }
1428
+ /** Switch to a specific trading account by ID. */
1429
+ async switchAccount(accountId) {
1430
+ return switchAccount(this._ctx, accountId);
1431
+ }
1432
+ /** Authenticate and establish a session: login, fetch CSRF, WebSocket handshake, and optional account switch. */
1433
+ async auth() {
1434
+ return auth(this._ctx);
1435
+ }
1436
+ /** Connect to the broker with a persistent WebSocket: auth + persistent WS for data reuse and streaming. */
1437
+ async connect() {
1438
+ return connect(this._ctx);
1439
+ }
1440
+ /** Close the persistent WebSocket connection. */
1441
+ disconnect() {
1442
+ return disconnect(this._ctx);
1443
+ }
1170
1444
  };
1171
1445
  // Annotate the CommonJS export names for ESM import in node:
1172
1446
  0 && (module.exports = {