@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/README.md +12 -10
- package/dist/index.d.mts +29 -13
- package/dist/index.d.ts +29 -13
- package/dist/index.js +141 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +141 -45
- package/dist/index.mjs.map +1 -1
- package/llms.txt +3 -3
- package/package.json +3 -2
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
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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 */,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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("
|
|
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
|
|
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
|
-
/**
|
|
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;
|