@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/README.md +70 -44
- package/dist/index.d.mts +152 -61
- package/dist/index.d.ts +152 -61
- package/dist/index.js +407 -133
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +407 -133
- package/dist/index.mjs.map +1 -1
- package/llms.txt +38 -30
- package/package.json +18 -17
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}
|
|
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
|
|
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)
|
|
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
|
|
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: "
|
|
372
|
+
method: "POST",
|
|
298
373
|
url: endpoints.tradeHistory(ctx.broker, params),
|
|
299
|
-
headers:
|
|
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
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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
|
|
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
|
|
679
|
+
var import_ws6 = __toESM(require("ws"));
|
|
516
680
|
|
|
517
681
|
// src/domains/symbol/symbol.ts
|
|
518
|
-
var
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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("
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1038
|
-
_ctx
|
|
1039
|
-
|
|
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
|
-
/**
|
|
1067
|
-
|
|
1068
|
-
return
|
|
1251
|
+
/** Get all open positions with P&L metrics merged. */
|
|
1252
|
+
get() {
|
|
1253
|
+
return getPositions(this._ctx);
|
|
1069
1254
|
}
|
|
1070
|
-
/**
|
|
1071
|
-
|
|
1072
|
-
return
|
|
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
|
-
/**
|
|
1075
|
-
|
|
1076
|
-
return
|
|
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
|
-
/**
|
|
1083
|
-
|
|
1084
|
-
return
|
|
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
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1267
|
+
};
|
|
1268
|
+
var OrdersDomain = class {
|
|
1269
|
+
constructor(_ctx) {
|
|
1270
|
+
this._ctx = _ctx;
|
|
1089
1271
|
}
|
|
1090
|
-
/** Get
|
|
1091
|
-
|
|
1092
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
1284
|
+
cancel(orderChainId) {
|
|
1107
1285
|
return cancelOrder(this._ctx, orderChainId);
|
|
1108
1286
|
}
|
|
1109
1287
|
/** Cancel all pending orders. */
|
|
1110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1339
|
+
get(params = {}) {
|
|
1153
1340
|
return getInstruments(this._ctx, params);
|
|
1154
1341
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
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
|
-
|
|
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 = {
|