@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.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}
|
|
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
|
|
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)
|
|
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
|
|
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: "
|
|
327
|
+
method: "POST",
|
|
253
328
|
url: endpoints.tradeHistory(ctx.broker, params),
|
|
254
|
-
headers:
|
|
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
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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
|
|
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
|
|
634
|
+
import WebSocket6 from "ws";
|
|
471
635
|
|
|
472
636
|
// src/domains/symbol/symbol.ts
|
|
473
|
-
import
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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("
|
|
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
|
|
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
|
|
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
|
|
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
|
|
993
|
-
_ctx
|
|
994
|
-
|
|
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
|
-
/**
|
|
1022
|
-
|
|
1023
|
-
return
|
|
1206
|
+
/** Get all open positions with P&L metrics merged. */
|
|
1207
|
+
get() {
|
|
1208
|
+
return getPositions(this._ctx);
|
|
1024
1209
|
}
|
|
1025
|
-
/**
|
|
1026
|
-
|
|
1027
|
-
return
|
|
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
|
-
/**
|
|
1030
|
-
|
|
1031
|
-
return
|
|
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
|
-
/**
|
|
1038
|
-
|
|
1039
|
-
return
|
|
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
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1222
|
+
};
|
|
1223
|
+
var OrdersDomain = class {
|
|
1224
|
+
constructor(_ctx) {
|
|
1225
|
+
this._ctx = _ctx;
|
|
1044
1226
|
}
|
|
1045
|
-
/** Get
|
|
1046
|
-
|
|
1047
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
1239
|
+
cancel(orderChainId) {
|
|
1062
1240
|
return cancelOrder(this._ctx, orderChainId);
|
|
1063
1241
|
}
|
|
1064
1242
|
/** Cancel all pending orders. */
|
|
1065
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1294
|
+
get(params = {}) {
|
|
1108
1295
|
return getInstruments(this._ctx, params);
|
|
1109
1296
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
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
|
-
|
|
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,
|