@danielgroen/dxtrade-api 1.0.24 → 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
@@ -107,6 +107,7 @@ var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
107
107
  let SUBTOPIC;
108
108
  ((SUBTOPIC2) => {
109
109
  SUBTOPIC2["BIG_CHART_COMPONENT"] = "BigChartComponentPresenter-4";
110
+ SUBTOPIC2["OHLC_STREAM"] = "OHLCStreamPresenter-0";
110
111
  })(SUBTOPIC = WS_MESSAGE2.SUBTOPIC || (WS_MESSAGE2.SUBTOPIC = {}));
111
112
  })(WS_MESSAGE || (WS_MESSAGE = {}));
112
113
 
@@ -443,6 +444,95 @@ async function getInstruments(ctx, params = {}, timeout = 3e4) {
443
444
 
444
445
  // src/domains/ohlc/ohlc.ts
445
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
+ }
446
536
  async function getOHLC(ctx, params, timeout = 3e4) {
447
537
  ctx.ensureSession();
448
538
  const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
@@ -847,6 +937,23 @@ async function submitOrder(ctx, params) {
847
937
 
848
938
  // src/domains/position/position.ts
849
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
+ }
850
957
  function streamPositions(ctx, callback) {
851
958
  if (!ctx.wsManager) {
852
959
  ctx.throwError(
@@ -854,25 +961,38 @@ function streamPositions(ctx, callback) {
854
961
  "Streaming requires a persistent WebSocket. Use connect() instead of auth()."
855
962
  );
856
963
  }
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
- }
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();
863
976
  return () => {
864
- ctx.wsManager?.removeListener("POSITIONS" /* POSITIONS */, listener);
977
+ ctx.wsManager?.removeListener("POSITIONS" /* POSITIONS */, onPositions);
978
+ ctx.wsManager?.removeListener("POSITION_METRICS" /* POSITION_METRICS */, onMetrics);
865
979
  };
866
980
  }
867
981
  async function getPositions(ctx) {
868
982
  ctx.ensureSession();
869
983
  if (ctx.wsManager) {
870
- return ctx.wsManager.waitFor("POSITIONS" /* POSITIONS */);
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);
871
989
  }
872
990
  const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
873
991
  const cookieStr = Cookies.serialize(ctx.cookies);
874
992
  return new Promise((resolve, reject) => {
875
993
  const ws = new WebSocket7(wsUrl, { headers: { Cookie: cookieStr } });
994
+ let positions = null;
995
+ let metrics = null;
876
996
  const timer = setTimeout(() => {
877
997
  ws.close();
878
998
  reject(new DxtradeError("ACCOUNT_POSITIONS_TIMEOUT" /* ACCOUNT_POSITIONS_TIMEOUT */, "Account positions timed out"));
@@ -882,45 +1002,21 @@ async function getPositions(ctx) {
882
1002
  if (shouldLog(msg, ctx.debug)) debugLog(msg);
883
1003
  if (typeof msg === "string") return;
884
1004
  if (msg.type === "POSITIONS" /* POSITIONS */) {
885
- clearTimeout(timer);
886
- ws.close();
887
- resolve(msg.body);
1005
+ positions = msg.body;
888
1006
  }
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);
901
- }
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
1007
  if (msg.type === "POSITION_METRICS" /* POSITION_METRICS */) {
1008
+ metrics = msg.body;
1009
+ }
1010
+ if (positions && metrics) {
915
1011
  clearTimeout(timer);
916
1012
  ws.close();
917
- resolve(msg.body);
1013
+ resolve(mergePositionsWithMetrics(positions, metrics));
918
1014
  }
919
1015
  });
920
1016
  ws.on("error", (error) => {
921
1017
  clearTimeout(timer);
922
1018
  ws.close();
923
- 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}`));
924
1020
  });
925
1021
  });
926
1022
  }
@@ -1107,7 +1203,7 @@ var PositionsDomain = class {
1107
1203
  constructor(_ctx) {
1108
1204
  this._ctx = _ctx;
1109
1205
  }
1110
- /** Get all open positions via WebSocket. */
1206
+ /** Get all open positions with P&L metrics merged. */
1111
1207
  get() {
1112
1208
  return getPositions(this._ctx);
1113
1209
  }
@@ -1119,11 +1215,7 @@ var PositionsDomain = class {
1119
1215
  closeAll() {
1120
1216
  return closeAllPositions(this._ctx);
1121
1217
  }
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. */
1218
+ /** Stream real-time position updates with P&L metrics. Requires connect(). Returns unsubscribe function. */
1127
1219
  stream(callback) {
1128
1220
  return streamPositions(this._ctx, callback);
1129
1221
  }
@@ -1218,6 +1310,10 @@ var OhlcDomain = class {
1218
1310
  get(params) {
1219
1311
  return getOHLC(this._ctx, params);
1220
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
+ }
1221
1317
  };
1222
1318
  var AssessmentsDomain = class {
1223
1319
  constructor(_ctx) {
@@ -1240,7 +1336,7 @@ var DxtradeClient = class {
1240
1336
  symbols;
1241
1337
  /** Instrument operations: get (with optional filtering). */
1242
1338
  instruments;
1243
- /** OHLC price bar operations: get. */
1339
+ /** OHLC price bar operations: get, stream. */
1244
1340
  ohlc;
1245
1341
  /** PnL assessment operations: get. */
1246
1342
  assessments;