@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.mjs CHANGED
@@ -21,7 +21,7 @@ var endpoints = {
21
21
  assessments: (base) => `${base}/api/assessments`,
22
22
  websocket: (base, atmosphereId) => `wss://${base.split("//")[1]}/client/connector` + websocketQuery(atmosphereId),
23
23
  tradeJournal: (base, params) => `${base}/api/tradejournal?from=${params.from}&to=${params.to}`,
24
- tradeHistory: (base, params) => `${base}/api/history?from=${params.from}&to=${params.to}&orderId=`,
24
+ tradeHistory: (base, params) => `${base}/api/history?from=${params.from}&to=${params.to}`,
25
25
  subscribeInstruments: (base) => `${base}/api/instruments/subscribeInstrumentSymbols`,
26
26
  charts: (base) => `${base}/api/charts`
27
27
  };
@@ -80,6 +80,9 @@ var ERROR = /* @__PURE__ */ ((ERROR2) => {
80
80
  ERROR2["TRADE_JOURNAL_ERROR"] = "TRADE_JOURNAL_ERROR";
81
81
  ERROR2["TRADE_HISTORY_ERROR"] = "TRADE_HISTORY_ERROR";
82
82
  ERROR2["ASSESSMENTS_ERROR"] = "ASSESSMENTS_ERROR";
83
+ ERROR2["RATE_LIMITED"] = "RATE_LIMITED";
84
+ ERROR2["WS_MANAGER_ERROR"] = "WS_MANAGER_ERROR";
85
+ ERROR2["STREAM_REQUIRES_CONNECT"] = "STREAM_REQUIRES_CONNECT";
83
86
  return ERROR2;
84
87
  })(ERROR || {});
85
88
  var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
@@ -104,6 +107,7 @@ var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
104
107
  let SUBTOPIC;
105
108
  ((SUBTOPIC2) => {
106
109
  SUBTOPIC2["BIG_CHART_COMPONENT"] = "BigChartComponentPresenter-4";
110
+ SUBTOPIC2["OHLC_STREAM"] = "OHLCStreamPresenter-0";
107
111
  })(SUBTOPIC = WS_MESSAGE2.SUBTOPIC || (WS_MESSAGE2.SUBTOPIC = {}));
108
112
  })(WS_MESSAGE || (WS_MESSAGE = {}));
109
113
 
@@ -118,7 +122,7 @@ var DxtradeError = class extends Error {
118
122
  };
119
123
 
120
124
  // src/domains/account/account.ts
121
- import WebSocket from "ws";
125
+ import WebSocket2 from "ws";
122
126
 
123
127
  // src/utils/cookies.ts
124
128
  var Cookies = class {
@@ -171,7 +175,9 @@ async function retryRequest(config, retries = 3) {
171
175
  } catch (error) {
172
176
  const message = error instanceof Error ? error.message : "Unknown error";
173
177
  console.warn(`[dxtrade-api] Attempt ${attempt} failed: ${message}`, config.url);
174
- if (isAxiosError(error) && error.response?.status === 429) throw error;
178
+ if (isAxiosError(error) && error.response?.status === 429) {
179
+ throw new DxtradeError("RATE_LIMITED" /* RATE_LIMITED */, "Rate limited (429). Too many requests \u2014 try again later.");
180
+ }
175
181
  if (attempt === retries) throw error;
176
182
  await new Promise((res) => setTimeout(res, 1e3 * attempt));
177
183
  }
@@ -214,13 +220,83 @@ function parseWsData(data) {
214
220
  }
215
221
  }
216
222
 
223
+ // src/utils/ws-manager.ts
224
+ import { EventEmitter } from "events";
225
+ import WebSocket from "ws";
226
+ var WsManager = class extends EventEmitter {
227
+ _ws = null;
228
+ _cache = /* @__PURE__ */ new Map();
229
+ connect(wsUrl, cookieStr, debug = false) {
230
+ return new Promise((resolve, reject) => {
231
+ const ws = new WebSocket(wsUrl, { headers: { Cookie: cookieStr } });
232
+ ws.on("open", () => {
233
+ this._ws = ws;
234
+ resolve();
235
+ });
236
+ ws.on("message", (data) => {
237
+ const msg = parseWsData(data);
238
+ if (shouldLog(msg, debug)) debugLog(msg);
239
+ if (typeof msg === "string") return;
240
+ const payload = msg;
241
+ this._cache.set(payload.type, payload.body);
242
+ this.emit(payload.type, payload.body);
243
+ });
244
+ ws.on("error", (error) => {
245
+ if (!this._ws) {
246
+ return reject(error);
247
+ }
248
+ this.emit("error", error);
249
+ });
250
+ ws.on("close", () => {
251
+ this._ws = null;
252
+ this.emit("close");
253
+ });
254
+ });
255
+ }
256
+ waitFor(type, timeout = 3e4) {
257
+ const cached = this._cache.get(type);
258
+ if (cached !== void 0) {
259
+ return Promise.resolve(cached);
260
+ }
261
+ return new Promise((resolve, reject) => {
262
+ const timer = setTimeout(() => {
263
+ this.removeListener(type, onMessage);
264
+ reject(new Error(`WsManager: timed out waiting for ${type}`));
265
+ }, timeout);
266
+ const onMessage = (body) => {
267
+ clearTimeout(timer);
268
+ resolve(body);
269
+ };
270
+ this.once(type, onMessage);
271
+ });
272
+ }
273
+ getCached(type) {
274
+ return this._cache.get(type);
275
+ }
276
+ close() {
277
+ if (this._ws) {
278
+ this._ws.close();
279
+ this._ws = null;
280
+ }
281
+ this._cache.clear();
282
+ this.removeAllListeners();
283
+ }
284
+ get isConnected() {
285
+ return this._ws !== null && this._ws.readyState === WebSocket.OPEN;
286
+ }
287
+ };
288
+
217
289
  // src/domains/account/account.ts
218
290
  async function getAccountMetrics(ctx, timeout = 3e4) {
219
291
  ctx.ensureSession();
292
+ if (ctx.wsManager) {
293
+ const body = await ctx.wsManager.waitFor("ACCOUNT_METRICS" /* ACCOUNT_METRICS */, timeout);
294
+ return body.allMetrics;
295
+ }
220
296
  const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
221
297
  const cookieStr = Cookies.serialize(ctx.cookies);
222
298
  return new Promise((resolve, reject) => {
223
- const ws = new WebSocket(wsUrl, { headers: { Cookie: cookieStr } });
299
+ const ws = new WebSocket2(wsUrl, { headers: { Cookie: cookieStr } });
224
300
  const timer = setTimeout(() => {
225
301
  ws.close();
226
302
  reject(new DxtradeError("ACCOUNT_METRICS_TIMEOUT" /* ACCOUNT_METRICS_TIMEOUT */, "Account metrics timed out"));
@@ -246,12 +322,11 @@ async function getAccountMetrics(ctx, timeout = 3e4) {
246
322
  async function getTradeHistory(ctx, params) {
247
323
  ctx.ensureSession();
248
324
  try {
249
- const cookieStr = Cookies.serialize(ctx.cookies);
250
325
  const response = await retryRequest(
251
326
  {
252
- method: "GET",
327
+ method: "POST",
253
328
  url: endpoints.tradeHistory(ctx.broker, params),
254
- headers: { ...baseHeaders(), Cookie: cookieStr }
329
+ headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
255
330
  },
256
331
  ctx.retries
257
332
  );
@@ -323,13 +398,13 @@ async function getAssessments(ctx, params) {
323
398
  }
324
399
 
325
400
  // src/domains/instrument/instrument.ts
326
- import WebSocket2 from "ws";
401
+ import WebSocket3 from "ws";
327
402
  async function getInstruments(ctx, params = {}, timeout = 3e4) {
328
403
  ctx.ensureSession();
329
404
  const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
330
405
  const cookieStr = Cookies.serialize(ctx.cookies);
331
406
  return new Promise((resolve, reject) => {
332
- const ws = new WebSocket2(wsUrl, { headers: { Cookie: cookieStr } });
407
+ const ws = new WebSocket3(wsUrl, { headers: { Cookie: cookieStr } });
333
408
  const timer = setTimeout(() => {
334
409
  ws.close();
335
410
  reject(new DxtradeError("INSTRUMENTS_TIMEOUT" /* INSTRUMENTS_TIMEOUT */, "Instruments request timed out"));
@@ -356,7 +431,7 @@ async function getInstruments(ctx, params = {}, timeout = 3e4) {
356
431
  return true;
357
432
  })
358
433
  );
359
- }, 0);
434
+ }, 200);
360
435
  }
361
436
  });
362
437
  ws.on("error", (error) => {
@@ -368,7 +443,96 @@ async function getInstruments(ctx, params = {}, timeout = 3e4) {
368
443
  }
369
444
 
370
445
  // src/domains/ohlc/ohlc.ts
371
- import WebSocket3 from "ws";
446
+ import WebSocket4 from "ws";
447
+ async function streamOHLC(ctx, params, callback) {
448
+ if (!ctx.wsManager) {
449
+ ctx.throwError(
450
+ "STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
451
+ "Streaming requires a persistent WebSocket. Use connect() instead of auth()."
452
+ );
453
+ }
454
+ const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
455
+ const subtopic = WS_MESSAGE.SUBTOPIC.OHLC_STREAM;
456
+ const headers = authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies));
457
+ const snapshotBars = [];
458
+ let snapshotDone = false;
459
+ let resolveSnapshot = null;
460
+ const onChartFeed = (body) => {
461
+ if (body?.subtopic !== subtopic) return;
462
+ const data = body.data;
463
+ if (!Array.isArray(data)) return;
464
+ if (!snapshotDone) {
465
+ snapshotBars.push(...data);
466
+ if (body.snapshotEnd) {
467
+ snapshotDone = true;
468
+ callback([...snapshotBars]);
469
+ resolveSnapshot?.();
470
+ }
471
+ } else {
472
+ callback(data);
473
+ }
474
+ };
475
+ ctx.wsManager.on("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
476
+ try {
477
+ await retryRequest(
478
+ {
479
+ method: "PUT",
480
+ url: endpoints.subscribeInstruments(ctx.broker),
481
+ data: { instruments: [symbol] },
482
+ headers
483
+ },
484
+ ctx.retries
485
+ );
486
+ await retryRequest(
487
+ {
488
+ method: "PUT",
489
+ url: endpoints.charts(ctx.broker),
490
+ data: {
491
+ chartIds: [],
492
+ requests: [
493
+ {
494
+ aggregationPeriodSeconds: resolution,
495
+ extendedSession: true,
496
+ forexPriceField: priceField,
497
+ id: 0,
498
+ maxBarsCount: maxBars,
499
+ range,
500
+ studySubscription: [],
501
+ subtopic,
502
+ symbol
503
+ }
504
+ ]
505
+ },
506
+ headers
507
+ },
508
+ ctx.retries
509
+ );
510
+ } catch (error) {
511
+ ctx.wsManager.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
512
+ const message = error instanceof Error ? error.message : "Unknown error";
513
+ ctx.throwError("OHLC_ERROR" /* OHLC_ERROR */, `OHLC stream subscription error: ${message}`);
514
+ }
515
+ await new Promise((resolve, reject) => {
516
+ if (snapshotDone) return resolve();
517
+ const timer = setTimeout(() => {
518
+ if (snapshotBars.length > 0) {
519
+ snapshotDone = true;
520
+ callback([...snapshotBars]);
521
+ resolve();
522
+ } else {
523
+ ctx.wsManager?.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
524
+ reject(new DxtradeError("OHLC_TIMEOUT" /* OHLC_TIMEOUT */, "OHLC stream snapshot timed out"));
525
+ }
526
+ }, 3e4);
527
+ resolveSnapshot = () => {
528
+ clearTimeout(timer);
529
+ resolve();
530
+ };
531
+ });
532
+ return () => {
533
+ ctx.wsManager?.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
534
+ };
535
+ }
372
536
  async function getOHLC(ctx, params, timeout = 3e4) {
373
537
  ctx.ensureSession();
374
538
  const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
@@ -376,7 +540,7 @@ async function getOHLC(ctx, params, timeout = 3e4) {
376
540
  const cookieStr = Cookies.serialize(ctx.cookies);
377
541
  const headers = authHeaders(ctx.csrf, cookieStr);
378
542
  return new Promise((resolve, reject) => {
379
- const ws = new WebSocket3(wsUrl, { headers: { Cookie: cookieStr } });
543
+ const ws = new WebSocket4(wsUrl, { headers: { Cookie: cookieStr } });
380
544
  const bars = [];
381
545
  let putsSent = false;
382
546
  let initSettleTimer = null;
@@ -467,10 +631,10 @@ async function getOHLC(ctx, params, timeout = 3e4) {
467
631
 
468
632
  // src/domains/order/order.ts
469
633
  import crypto from "crypto";
470
- import WebSocket5 from "ws";
634
+ import WebSocket6 from "ws";
471
635
 
472
636
  // src/domains/symbol/symbol.ts
473
- import WebSocket4 from "ws";
637
+ import WebSocket5 from "ws";
474
638
  async function getSymbolSuggestions(ctx, text) {
475
639
  ctx.ensureSession();
476
640
  try {
@@ -522,7 +686,7 @@ async function getSymbolLimits(ctx, timeout = 3e4) {
522
686
  const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
523
687
  const cookieStr = Cookies.serialize(ctx.cookies);
524
688
  return new Promise((resolve, reject) => {
525
- const ws = new WebSocket4(wsUrl, { headers: { Cookie: cookieStr } });
689
+ const ws = new WebSocket5(wsUrl, { headers: { Cookie: cookieStr } });
526
690
  const timer = setTimeout(() => {
527
691
  ws.close();
528
692
  reject(new DxtradeError("LIMITS_TIMEOUT" /* LIMITS_TIMEOUT */, "Symbol limits request timed out"));
@@ -542,7 +706,7 @@ async function getSymbolLimits(ctx, timeout = 3e4) {
542
706
  clearTimeout(timer);
543
707
  ws.close();
544
708
  resolve(limits);
545
- }, 0);
709
+ }, 200);
546
710
  }
547
711
  });
548
712
  ws.on("error", (error) => {
@@ -555,7 +719,7 @@ async function getSymbolLimits(ctx, timeout = 3e4) {
555
719
 
556
720
  // src/domains/order/order.ts
557
721
  function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
558
- const ws = new WebSocket5(wsUrl, { headers: { Cookie: cookieStr } });
722
+ const ws = new WebSocket6(wsUrl, { headers: { Cookie: cookieStr } });
559
723
  let settled = false;
560
724
  const ready = new Promise((resolve) => {
561
725
  ws.on("open", resolve);
@@ -622,10 +786,13 @@ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
622
786
  }
623
787
  async function getOrders(ctx, timeout = 3e4) {
624
788
  ctx.ensureSession();
789
+ if (ctx.wsManager) {
790
+ return ctx.wsManager.waitFor("ORDERS" /* ORDERS */, timeout);
791
+ }
625
792
  const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
626
793
  const cookieStr = Cookies.serialize(ctx.cookies);
627
794
  return new Promise((resolve, reject) => {
628
- const ws = new WebSocket5(wsUrl, { headers: { Cookie: cookieStr } });
795
+ const ws = new WebSocket6(wsUrl, { headers: { Cookie: cookieStr } });
629
796
  const timer = setTimeout(() => {
630
797
  ws.close();
631
798
  reject(new DxtradeError("ORDERS_TIMEOUT" /* ORDERS_TIMEOUT */, "Orders request timed out"));
@@ -769,13 +936,63 @@ async function submitOrder(ctx, params) {
769
936
  }
770
937
 
771
938
  // src/domains/position/position.ts
772
- import WebSocket6 from "ws";
939
+ import WebSocket7 from "ws";
940
+ function mergePositionsWithMetrics(positions, metrics) {
941
+ const metricsMap = new Map(metrics.map((m) => [m.uid, m]));
942
+ return positions.map((pos) => {
943
+ const m = metricsMap.get(pos.uid);
944
+ return {
945
+ ...pos,
946
+ margin: m?.margin ?? 0,
947
+ plOpen: m?.plOpen ?? 0,
948
+ plClosed: m?.plClosed ?? 0,
949
+ totalCommissions: m?.totalCommissions ?? 0,
950
+ totalFinancing: m?.totalFinancing ?? 0,
951
+ plRate: m?.plRate ?? 0,
952
+ averagePrice: m?.averagePrice ?? 0,
953
+ marketValue: m?.marketValue ?? 0
954
+ };
955
+ });
956
+ }
957
+ function streamPositions(ctx, callback) {
958
+ if (!ctx.wsManager) {
959
+ ctx.throwError(
960
+ "STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
961
+ "Streaming requires a persistent WebSocket. Use connect() instead of auth()."
962
+ );
963
+ }
964
+ const emit = () => {
965
+ const positions = ctx.wsManager.getCached("POSITIONS" /* POSITIONS */);
966
+ const metrics = ctx.wsManager.getCached("POSITION_METRICS" /* POSITION_METRICS */);
967
+ if (positions && metrics) {
968
+ callback(mergePositionsWithMetrics(positions, metrics));
969
+ }
970
+ };
971
+ const onPositions = () => emit();
972
+ const onMetrics = () => emit();
973
+ ctx.wsManager.on("POSITIONS" /* POSITIONS */, onPositions);
974
+ ctx.wsManager.on("POSITION_METRICS" /* POSITION_METRICS */, onMetrics);
975
+ emit();
976
+ return () => {
977
+ ctx.wsManager?.removeListener("POSITIONS" /* POSITIONS */, onPositions);
978
+ ctx.wsManager?.removeListener("POSITION_METRICS" /* POSITION_METRICS */, onMetrics);
979
+ };
980
+ }
773
981
  async function getPositions(ctx) {
774
982
  ctx.ensureSession();
983
+ if (ctx.wsManager) {
984
+ const [positions, metrics] = await Promise.all([
985
+ ctx.wsManager.waitFor("POSITIONS" /* POSITIONS */),
986
+ ctx.wsManager.waitFor("POSITION_METRICS" /* POSITION_METRICS */)
987
+ ]);
988
+ return mergePositionsWithMetrics(positions, metrics);
989
+ }
775
990
  const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
776
991
  const cookieStr = Cookies.serialize(ctx.cookies);
777
992
  return new Promise((resolve, reject) => {
778
- const ws = new WebSocket6(wsUrl, { headers: { Cookie: cookieStr } });
993
+ const ws = new WebSocket7(wsUrl, { headers: { Cookie: cookieStr } });
994
+ let positions = null;
995
+ let metrics = null;
779
996
  const timer = setTimeout(() => {
780
997
  ws.close();
781
998
  reject(new DxtradeError("ACCOUNT_POSITIONS_TIMEOUT" /* ACCOUNT_POSITIONS_TIMEOUT */, "Account positions timed out"));
@@ -785,42 +1002,21 @@ async function getPositions(ctx) {
785
1002
  if (shouldLog(msg, ctx.debug)) debugLog(msg);
786
1003
  if (typeof msg === "string") return;
787
1004
  if (msg.type === "POSITIONS" /* POSITIONS */) {
788
- clearTimeout(timer);
789
- ws.close();
790
- resolve(msg.body);
1005
+ positions = msg.body;
791
1006
  }
792
- });
793
- ws.on("error", (error) => {
794
- clearTimeout(timer);
795
- ws.close();
796
- reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR" /* ACCOUNT_POSITIONS_ERROR */, `Account positions error: ${error.message}`));
797
- });
798
- });
799
- }
800
- async function getPositionMetrics(ctx, timeout = 3e4) {
801
- ctx.ensureSession();
802
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
803
- const cookieStr = Cookies.serialize(ctx.cookies);
804
- return new Promise((resolve, reject) => {
805
- const ws = new WebSocket6(wsUrl, { headers: { Cookie: cookieStr } });
806
- const timer = setTimeout(() => {
807
- ws.close();
808
- reject(new DxtradeError("POSITION_METRICS_TIMEOUT" /* POSITION_METRICS_TIMEOUT */, "Position metrics timed out"));
809
- }, timeout);
810
- ws.on("message", (data) => {
811
- const msg = parseWsData(data);
812
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
813
- if (typeof msg === "string") return;
814
1007
  if (msg.type === "POSITION_METRICS" /* POSITION_METRICS */) {
1008
+ metrics = msg.body;
1009
+ }
1010
+ if (positions && metrics) {
815
1011
  clearTimeout(timer);
816
1012
  ws.close();
817
- resolve(msg.body);
1013
+ resolve(mergePositionsWithMetrics(positions, metrics));
818
1014
  }
819
1015
  });
820
1016
  ws.on("error", (error) => {
821
1017
  clearTimeout(timer);
822
1018
  ws.close();
823
- reject(new DxtradeError("POSITION_METRICS_ERROR" /* POSITION_METRICS_ERROR */, `Position metrics error: ${error.message}`));
1019
+ reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR" /* ACCOUNT_POSITIONS_ERROR */, `Account positions error: ${error.message}`));
824
1020
  });
825
1021
  });
826
1022
  }
@@ -864,10 +1060,10 @@ async function closePosition(ctx, data) {
864
1060
  }
865
1061
 
866
1062
  // src/domains/session/session.ts
867
- import WebSocket7 from "ws";
1063
+ import WebSocket8 from "ws";
868
1064
  function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
869
1065
  return new Promise((resolve, reject) => {
870
- const ws = new WebSocket7(wsUrl, { headers: { Cookie: cookieStr } });
1066
+ const ws = new WebSocket8(wsUrl, { headers: { Cookie: cookieStr } });
871
1067
  let atmosphereId = null;
872
1068
  const timer = setTimeout(() => {
873
1069
  ws.close();
@@ -967,7 +1163,7 @@ async function switchAccount(ctx, accountId) {
967
1163
  ctx.throwError("ACCOUNT_SWITCH_ERROR" /* ACCOUNT_SWITCH_ERROR */, `Error switching account: ${message}`);
968
1164
  }
969
1165
  }
970
- async function connect(ctx) {
1166
+ async function auth(ctx) {
971
1167
  await login(ctx);
972
1168
  await fetchCsrf(ctx);
973
1169
  if (ctx.debug) clearDebugLog();
@@ -987,112 +1183,81 @@ async function connect(ctx) {
987
1183
  ctx.accountId = reconnect.accountId;
988
1184
  }
989
1185
  }
1186
+ async function connect(ctx) {
1187
+ await auth(ctx);
1188
+ const wsManager = new WsManager();
1189
+ const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
1190
+ const cookieStr = Cookies.serialize(ctx.cookies);
1191
+ await wsManager.connect(wsUrl, cookieStr, ctx.debug);
1192
+ ctx.wsManager = wsManager;
1193
+ }
1194
+ function disconnect(ctx) {
1195
+ if (ctx.wsManager) {
1196
+ ctx.wsManager.close();
1197
+ ctx.wsManager = null;
1198
+ }
1199
+ }
990
1200
 
991
1201
  // src/client.ts
992
- var DxtradeClient = class {
993
- _ctx;
994
- constructor(config) {
995
- const callbacks = config.callbacks ?? {};
996
- this._ctx = {
997
- config,
998
- callbacks,
999
- cookies: {},
1000
- csrf: null,
1001
- accountId: config.accountId ?? null,
1002
- atmosphereId: null,
1003
- broker: config.broker,
1004
- retries: config.retries ?? 3,
1005
- debug: config.debug ?? false,
1006
- ensureSession() {
1007
- if (!this.csrf) {
1008
- throw new DxtradeError(
1009
- "NO_SESSION" /* NO_SESSION */,
1010
- "No active session. Call login() and fetchCsrf() or connect() first."
1011
- );
1012
- }
1013
- },
1014
- throwError(code, message) {
1015
- const error = new DxtradeError(code, message);
1016
- callbacks.onError?.(error);
1017
- throw error;
1018
- }
1019
- };
1202
+ var PositionsDomain = class {
1203
+ constructor(_ctx) {
1204
+ this._ctx = _ctx;
1020
1205
  }
1021
- /** Authenticate with the broker using username and password. */
1022
- async login() {
1023
- return login(this._ctx);
1206
+ /** Get all open positions with P&L metrics merged. */
1207
+ get() {
1208
+ return getPositions(this._ctx);
1024
1209
  }
1025
- /** Fetch the CSRF token required for authenticated requests. */
1026
- async fetchCsrf() {
1027
- return fetchCsrf(this._ctx);
1210
+ /** Close a position. Supports partial closes by specifying a quantity smaller than the full position size. */
1211
+ close(params) {
1212
+ return closePosition(this._ctx, params);
1028
1213
  }
1029
- /** Switch to a specific trading account by ID. */
1030
- async switchAccount(accountId) {
1031
- return switchAccount(this._ctx, accountId);
1032
- }
1033
- /** Connect to the broker: login, fetch CSRF, WebSocket handshake, and optional account switch. */
1034
- async connect() {
1035
- return connect(this._ctx);
1214
+ /** Close all open positions with market orders. */
1215
+ closeAll() {
1216
+ return closeAllPositions(this._ctx);
1036
1217
  }
1037
- /** Search for symbols matching the given text (e.g. "EURUSD", "BTC"). */
1038
- async getSymbolSuggestions(text) {
1039
- return getSymbolSuggestions(this._ctx, text);
1218
+ /** Stream real-time position updates with P&L metrics. Requires connect(). Returns unsubscribe function. */
1219
+ stream(callback) {
1220
+ return streamPositions(this._ctx, callback);
1040
1221
  }
1041
- /** Get detailed instrument info for a symbol, including volume limits and lot size. */
1042
- async getSymbolInfo(symbol) {
1043
- return getSymbolInfo(this._ctx, symbol);
1222
+ };
1223
+ var OrdersDomain = class {
1224
+ constructor(_ctx) {
1225
+ this._ctx = _ctx;
1044
1226
  }
1045
- /** Get order size limits and stop/limit distances for all symbols. */
1046
- async getSymbolLimits() {
1047
- return getSymbolLimits(this._ctx);
1227
+ /** Get all pending/open orders via WebSocket. */
1228
+ get() {
1229
+ return getOrders(this._ctx);
1048
1230
  }
1049
1231
  /**
1050
1232
  * Submit a trading order and wait for WebSocket confirmation.
1051
1233
  * Supports market, limit, and stop orders with optional stop loss and take profit.
1052
1234
  */
1053
- async submitOrder(params) {
1235
+ submit(params) {
1054
1236
  return submitOrder(this._ctx, params);
1055
1237
  }
1056
- /** Get all pending/open orders via WebSocket. */
1057
- async getOrders() {
1058
- return getOrders(this._ctx);
1059
- }
1060
1238
  /** Cancel a single pending order by its order chain ID. */
1061
- async cancelOrder(orderChainId) {
1239
+ cancel(orderChainId) {
1062
1240
  return cancelOrder(this._ctx, orderChainId);
1063
1241
  }
1064
1242
  /** Cancel all pending orders. */
1065
- async cancelAllOrders() {
1243
+ cancelAll() {
1066
1244
  return cancelAllOrders(this._ctx);
1067
1245
  }
1246
+ };
1247
+ var AccountDomain = class {
1248
+ constructor(_ctx) {
1249
+ this._ctx = _ctx;
1250
+ }
1068
1251
  /** Get account metrics including equity, balance, margin, and open P&L. */
1069
- async getAccountMetrics() {
1252
+ metrics() {
1070
1253
  return getAccountMetrics(this._ctx);
1071
1254
  }
1072
- /** Get all open positions via WebSocket. */
1073
- async getPositions() {
1074
- return getPositions(this._ctx);
1075
- }
1076
- /**
1077
- * Close a position. Supports partial closes by specifying a quantity smaller than the full position size.
1078
- */
1079
- async closePosition(position) {
1080
- return closePosition(this._ctx, position);
1081
- }
1082
- /** Close all open positions with market orders. */
1083
- async closeAllPositions() {
1084
- return closeAllPositions(this._ctx);
1085
- }
1086
- /** Get position-level P&L metrics via WebSocket. */
1087
- async getPositionMetrics() {
1088
- return getPositionMetrics(this._ctx);
1089
- }
1090
1255
  /**
1091
1256
  * Fetch trade journal entries for a date range.
1092
1257
  * @param params.from - Start timestamp (Unix ms)
1093
1258
  * @param params.to - End timestamp (Unix ms)
1094
1259
  */
1095
- async getTradeJournal(params) {
1260
+ tradeJournal(params) {
1096
1261
  return getTradeJournal(this._ctx, params);
1097
1262
  }
1098
1263
  /**
@@ -1100,16 +1265,39 @@ var DxtradeClient = class {
1100
1265
  * @param params.from - Start timestamp (Unix ms)
1101
1266
  * @param params.to - End timestamp (Unix ms)
1102
1267
  */
1103
- async getTradeHistory(params) {
1268
+ tradeHistory(params) {
1104
1269
  return getTradeHistory(this._ctx, params);
1105
1270
  }
1271
+ };
1272
+ var SymbolsDomain = class {
1273
+ constructor(_ctx) {
1274
+ this._ctx = _ctx;
1275
+ }
1276
+ /** Search for symbols matching the given text (e.g. "EURUSD", "BTC"). */
1277
+ search(text) {
1278
+ return getSymbolSuggestions(this._ctx, text);
1279
+ }
1280
+ /** Get detailed instrument info for a symbol, including volume limits and lot size. */
1281
+ info(symbol) {
1282
+ return getSymbolInfo(this._ctx, symbol);
1283
+ }
1284
+ /** Get order size limits and stop/limit distances for all symbols. */
1285
+ limits() {
1286
+ return getSymbolLimits(this._ctx);
1287
+ }
1288
+ };
1289
+ var InstrumentsDomain = class {
1290
+ constructor(_ctx) {
1291
+ this._ctx = _ctx;
1292
+ }
1106
1293
  /** Get all available instruments, optionally filtered by partial match (e.g. `{ type: "FOREX" }`). */
1107
- async getInstruments(params = {}) {
1294
+ get(params = {}) {
1108
1295
  return getInstruments(this._ctx, params);
1109
1296
  }
1110
- /** Fetch PnL assessments for an instrument within a date range. */
1111
- async getAssessments(params) {
1112
- return getAssessments(this._ctx, params);
1297
+ };
1298
+ var OhlcDomain = class {
1299
+ constructor(_ctx) {
1300
+ this._ctx = _ctx;
1113
1301
  }
1114
1302
  /**
1115
1303
  * Fetch OHLC price bars for a symbol.
@@ -1119,9 +1307,95 @@ var DxtradeClient = class {
1119
1307
  * @param params.maxBars - Maximum bars to return (default: 3500)
1120
1308
  * @param params.priceField - "bid" or "ask" (default: "bid")
1121
1309
  */
1122
- async getOHLC(params) {
1310
+ get(params) {
1123
1311
  return getOHLC(this._ctx, params);
1124
1312
  }
1313
+ /** Stream real-time OHLC bar updates. Requires connect(). Returns unsubscribe function. */
1314
+ stream(params, callback) {
1315
+ return streamOHLC(this._ctx, params, callback);
1316
+ }
1317
+ };
1318
+ var AssessmentsDomain = class {
1319
+ constructor(_ctx) {
1320
+ this._ctx = _ctx;
1321
+ }
1322
+ /** Fetch PnL assessments for an instrument within a date range. */
1323
+ get(params) {
1324
+ return getAssessments(this._ctx, params);
1325
+ }
1326
+ };
1327
+ var DxtradeClient = class {
1328
+ _ctx;
1329
+ /** Position operations: get, close, metrics, streaming. */
1330
+ positions;
1331
+ /** Order operations: get, submit, cancel. */
1332
+ orders;
1333
+ /** Account operations: metrics, trade journal, trade history. */
1334
+ account;
1335
+ /** Symbol operations: search, info, limits. */
1336
+ symbols;
1337
+ /** Instrument operations: get (with optional filtering). */
1338
+ instruments;
1339
+ /** OHLC price bar operations: get, stream. */
1340
+ ohlc;
1341
+ /** PnL assessment operations: get. */
1342
+ assessments;
1343
+ constructor(config) {
1344
+ const callbacks = config.callbacks ?? {};
1345
+ this._ctx = {
1346
+ config,
1347
+ callbacks,
1348
+ cookies: {},
1349
+ csrf: null,
1350
+ accountId: config.accountId ?? null,
1351
+ atmosphereId: null,
1352
+ wsManager: null,
1353
+ broker: config.broker,
1354
+ retries: config.retries ?? 3,
1355
+ debug: config.debug ?? false,
1356
+ ensureSession() {
1357
+ if (!this.csrf) {
1358
+ throw new DxtradeError("NO_SESSION" /* NO_SESSION */, "No active session. Call auth() or connect() first.");
1359
+ }
1360
+ },
1361
+ throwError(code, message) {
1362
+ const error = new DxtradeError(code, message);
1363
+ callbacks.onError?.(error);
1364
+ throw error;
1365
+ }
1366
+ };
1367
+ this.positions = new PositionsDomain(this._ctx);
1368
+ this.orders = new OrdersDomain(this._ctx);
1369
+ this.account = new AccountDomain(this._ctx);
1370
+ this.symbols = new SymbolsDomain(this._ctx);
1371
+ this.instruments = new InstrumentsDomain(this._ctx);
1372
+ this.ohlc = new OhlcDomain(this._ctx);
1373
+ this.assessments = new AssessmentsDomain(this._ctx);
1374
+ }
1375
+ /** Authenticate with the broker using username and password. */
1376
+ async login() {
1377
+ return login(this._ctx);
1378
+ }
1379
+ /** Fetch the CSRF token required for authenticated requests. */
1380
+ async fetchCsrf() {
1381
+ return fetchCsrf(this._ctx);
1382
+ }
1383
+ /** Switch to a specific trading account by ID. */
1384
+ async switchAccount(accountId) {
1385
+ return switchAccount(this._ctx, accountId);
1386
+ }
1387
+ /** Authenticate and establish a session: login, fetch CSRF, WebSocket handshake, and optional account switch. */
1388
+ async auth() {
1389
+ return auth(this._ctx);
1390
+ }
1391
+ /** Connect to the broker with a persistent WebSocket: auth + persistent WS for data reuse and streaming. */
1392
+ async connect() {
1393
+ return connect(this._ctx);
1394
+ }
1395
+ /** Close the persistent WebSocket connection. */
1396
+ disconnect() {
1397
+ return disconnect(this._ctx);
1398
+ }
1125
1399
  };
1126
1400
  export {
1127
1401
  ACTION,