@danielgroen/dxtrade-api 1.0.25 → 1.0.26

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.js CHANGED
@@ -35,6 +35,9 @@ __export(index_exports, {
35
35
  DxtradeClient: () => DxtradeClient,
36
36
  DxtradeError: () => DxtradeError,
37
37
  ERROR: () => ERROR,
38
+ MESSAGE_CATEGORY: () => MESSAGE_CATEGORY,
39
+ MESSAGE_TYPE: () => MESSAGE_TYPE,
40
+ ORDER_STATUS: () => ORDER_STATUS,
38
41
  ORDER_TYPE: () => ORDER_TYPE,
39
42
  SIDE: () => SIDE,
40
43
  TIF: () => TIF,
@@ -115,7 +118,9 @@ var ERROR = /* @__PURE__ */ ((ERROR2) => {
115
118
  ERROR2["ORDERS_TIMEOUT"] = "ORDERS_TIMEOUT";
116
119
  ERROR2["ORDERS_ERROR"] = "ORDERS_ERROR";
117
120
  ERROR2["CANCEL_ORDER_ERROR"] = "CANCEL_ORDER_ERROR";
121
+ ERROR2["POSITION_NOT_FOUND"] = "POSITION_NOT_FOUND";
118
122
  ERROR2["POSITION_CLOSE_ERROR"] = "POSITION_CLOSE_ERROR";
123
+ ERROR2["POSITION_CLOSE_TIMEOUT"] = "POSITION_CLOSE_TIMEOUT";
119
124
  ERROR2["POSITION_METRICS_TIMEOUT"] = "POSITION_METRICS_TIMEOUT";
120
125
  ERROR2["POSITION_METRICS_ERROR"] = "POSITION_METRICS_ERROR";
121
126
  ERROR2["ACCOUNT_METRICS_TIMEOUT"] = "ACCOUNT_METRICS_TIMEOUT";
@@ -130,6 +135,22 @@ var ERROR = /* @__PURE__ */ ((ERROR2) => {
130
135
  ERROR2["STREAM_REQUIRES_CONNECT"] = "STREAM_REQUIRES_CONNECT";
131
136
  return ERROR2;
132
137
  })(ERROR || {});
138
+ var MESSAGE_CATEGORY = /* @__PURE__ */ ((MESSAGE_CATEGORY2) => {
139
+ MESSAGE_CATEGORY2["TRADE_LOG"] = "TRADE_LOG";
140
+ MESSAGE_CATEGORY2["NOTIFICATION"] = "NOTIFICATION";
141
+ return MESSAGE_CATEGORY2;
142
+ })(MESSAGE_CATEGORY || {});
143
+ var MESSAGE_TYPE = /* @__PURE__ */ ((MESSAGE_TYPE2) => {
144
+ MESSAGE_TYPE2["ORDER"] = "ORDER";
145
+ MESSAGE_TYPE2["INSTRUMENT_ACTIVATED"] = "INSTRUMENT_ACTIVATED";
146
+ return MESSAGE_TYPE2;
147
+ })(MESSAGE_TYPE || {});
148
+ var ORDER_STATUS = /* @__PURE__ */ ((ORDER_STATUS2) => {
149
+ ORDER_STATUS2["PLACED"] = "PLACED";
150
+ ORDER_STATUS2["FILLED"] = "FILLED";
151
+ ORDER_STATUS2["REJECTED"] = "REJECTED";
152
+ return ORDER_STATUS2;
153
+ })(ORDER_STATUS || {});
133
154
  var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
134
155
  WS_MESSAGE2["ACCOUNT_METRICS"] = "ACCOUNT_METRICS";
135
156
  WS_MESSAGE2["ACCOUNTS"] = "ACCOUNTS";
@@ -195,7 +216,10 @@ var Cookies = class {
195
216
  function baseHeaders() {
196
217
  return {
197
218
  "Content-Type": "application/json; charset=UTF-8",
198
- "Accept-Language": "en-US,en;q=0.9"
219
+ Accept: "*/*",
220
+ "Accept-Language": "en-US,en;q=0.9",
221
+ "Accept-Encoding": "gzip, deflate, br, zstd",
222
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:147.0) Gecko/20100101 Firefox/147.0"
199
223
  };
200
224
  }
201
225
  function authHeaders(csrf, cookieStr) {
@@ -221,7 +245,11 @@ async function retryRequest(config, retries = 3) {
221
245
  const message = error instanceof Error ? error.message : "Unknown error";
222
246
  console.warn(`[dxtrade-api] Attempt ${attempt} failed: ${message}`, config.url);
223
247
  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.");
248
+ if (attempt === retries) {
249
+ throw new DxtradeError("RATE_LIMITED" /* RATE_LIMITED */, "Rate limited (429). Too many requests \u2014 try again later.");
250
+ }
251
+ await new Promise((res) => setTimeout(res, 3e3 * attempt));
252
+ continue;
225
253
  }
226
254
  if (attempt === retries) throw error;
227
255
  await new Promise((res) => setTimeout(res, 1e3 * attempt));
@@ -254,6 +282,11 @@ function parseAtmosphereId(data) {
254
282
  }
255
283
  return null;
256
284
  }
285
+ function checkWsRateLimit(error) {
286
+ if (error.message.includes("429")) {
287
+ throw new DxtradeError("RATE_LIMITED" /* RATE_LIMITED */, "Rate limited (429). Too many requests \u2014 try again later.");
288
+ }
289
+ }
257
290
  function parseWsData(data) {
258
291
  const raw = data.toString();
259
292
  const pipeIndex = raw.indexOf("|");
@@ -287,10 +320,12 @@ var WsManager = class extends import_events.EventEmitter {
287
320
  this.emit(payload.type, payload.body);
288
321
  });
289
322
  ws.on("error", (error) => {
323
+ checkWsRateLimit(error);
324
+ const err = new Error(`WebSocket manager error: ${error.message}`);
290
325
  if (!this._ws) {
291
- return reject(error);
326
+ return reject(err);
292
327
  }
293
- this.emit("error", error);
328
+ this.emit("error", err);
294
329
  });
295
330
  ws.on("close", () => {
296
331
  this._ws = null;
@@ -332,347 +367,395 @@ var WsManager = class extends import_events.EventEmitter {
332
367
  };
333
368
 
334
369
  // src/domains/account/account.ts
335
- async function getAccountMetrics(ctx, timeout = 3e4) {
336
- ctx.ensureSession();
337
- if (ctx.wsManager) {
338
- const body = await ctx.wsManager.waitFor("ACCOUNT_METRICS" /* ACCOUNT_METRICS */, timeout);
339
- return body.allMetrics;
370
+ var AccountDomain = class {
371
+ constructor(_ctx) {
372
+ this._ctx = _ctx;
340
373
  }
341
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
342
- const cookieStr = Cookies.serialize(ctx.cookies);
343
- return new Promise((resolve, reject) => {
344
- const ws = new import_ws2.default(wsUrl, { headers: { Cookie: cookieStr } });
345
- const timer = setTimeout(() => {
346
- ws.close();
347
- reject(new DxtradeError("ACCOUNT_METRICS_TIMEOUT" /* ACCOUNT_METRICS_TIMEOUT */, "Account metrics timed out"));
348
- }, timeout);
349
- ws.on("message", (data) => {
350
- const msg = parseWsData(data);
351
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
352
- if (typeof msg === "string") return;
353
- if (msg.type === "ACCOUNT_METRICS" /* ACCOUNT_METRICS */) {
374
+ /** Get account metrics including equity, balance, margin, and open P&L. */
375
+ async metrics(timeout = 3e4) {
376
+ this._ctx.ensureSession();
377
+ if (this._ctx.wsManager) {
378
+ const body = await this._ctx.wsManager.waitFor(
379
+ "ACCOUNT_METRICS" /* ACCOUNT_METRICS */,
380
+ timeout
381
+ );
382
+ return body.allMetrics;
383
+ }
384
+ const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
385
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
386
+ return new Promise((resolve, reject) => {
387
+ const ws = new import_ws2.default(wsUrl, { headers: { Cookie: cookieStr } });
388
+ const timer = setTimeout(() => {
389
+ ws.close();
390
+ reject(new DxtradeError("ACCOUNT_METRICS_TIMEOUT" /* ACCOUNT_METRICS_TIMEOUT */, "Account metrics timed out"));
391
+ }, timeout);
392
+ ws.on("message", (data) => {
393
+ const msg = parseWsData(data);
394
+ if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
395
+ if (typeof msg === "string") return;
396
+ if (msg.type === "ACCOUNT_METRICS" /* ACCOUNT_METRICS */) {
397
+ clearTimeout(timer);
398
+ ws.close();
399
+ const body = msg.body;
400
+ resolve(body.allMetrics);
401
+ }
402
+ });
403
+ ws.on("error", (error) => {
354
404
  clearTimeout(timer);
355
405
  ws.close();
356
- const body = msg.body;
357
- resolve(body.allMetrics);
358
- }
359
- });
360
- ws.on("error", (error) => {
361
- clearTimeout(timer);
362
- ws.close();
363
- reject(new DxtradeError("ACCOUNT_METRICS_ERROR" /* ACCOUNT_METRICS_ERROR */, `Account metrics error: ${error.message}`));
406
+ checkWsRateLimit(error);
407
+ reject(new DxtradeError("ACCOUNT_METRICS_ERROR" /* ACCOUNT_METRICS_ERROR */, `Account metrics error: ${error.message}`));
408
+ });
364
409
  });
365
- });
366
- }
367
- async function getTradeHistory(ctx, params) {
368
- ctx.ensureSession();
369
- try {
370
- const response = await retryRequest(
371
- {
372
- method: "POST",
373
- url: endpoints.tradeHistory(ctx.broker, params),
374
- headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
375
- },
376
- ctx.retries
377
- );
378
- if (response.status === 200) {
379
- const setCookies = response.headers["set-cookie"] ?? [];
380
- const incoming = Cookies.parse(setCookies);
381
- ctx.cookies = Cookies.merge(ctx.cookies, incoming);
382
- return response.data;
383
- } else {
384
- ctx.throwError("TRADE_HISTORY_ERROR" /* TRADE_HISTORY_ERROR */, `Trade history failed: ${response.status}`);
410
+ }
411
+ /**
412
+ * Fetch trade history for a date range.
413
+ * @param params.from - Start timestamp (Unix ms)
414
+ * @param params.to - End timestamp (Unix ms)
415
+ */
416
+ async tradeHistory(params) {
417
+ this._ctx.ensureSession();
418
+ try {
419
+ const response = await retryRequest(
420
+ {
421
+ method: "POST",
422
+ url: endpoints.tradeHistory(this._ctx.broker, params),
423
+ headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
424
+ },
425
+ this._ctx.retries
426
+ );
427
+ if (response.status === 200) {
428
+ const setCookies = response.headers["set-cookie"] ?? [];
429
+ const incoming = Cookies.parse(setCookies);
430
+ this._ctx.cookies = Cookies.merge(this._ctx.cookies, incoming);
431
+ return response.data;
432
+ } else {
433
+ this._ctx.throwError("TRADE_HISTORY_ERROR" /* TRADE_HISTORY_ERROR */, `Trade history failed: ${response.status}`);
434
+ }
435
+ } catch (error) {
436
+ if (error instanceof DxtradeError) throw error;
437
+ const message = error instanceof Error ? error.message : "Unknown error";
438
+ this._ctx.throwError("TRADE_HISTORY_ERROR" /* TRADE_HISTORY_ERROR */, `Trade history error: ${message}`);
385
439
  }
386
- } catch (error) {
387
- if (error instanceof DxtradeError) throw error;
388
- const message = error instanceof Error ? error.message : "Unknown error";
389
- ctx.throwError("TRADE_HISTORY_ERROR" /* TRADE_HISTORY_ERROR */, `Trade history error: ${message}`);
390
440
  }
391
- }
392
- async function getTradeJournal(ctx, params) {
393
- ctx.ensureSession();
394
- try {
395
- const cookieStr = Cookies.serialize(ctx.cookies);
396
- const response = await retryRequest(
397
- {
398
- method: "GET",
399
- url: endpoints.tradeJournal(ctx.broker, params),
400
- headers: { ...baseHeaders(), Cookie: cookieStr }
401
- },
402
- ctx.retries
403
- );
404
- if (response.status === 200) {
405
- const setCookies = response.headers["set-cookie"] ?? [];
406
- const incoming = Cookies.parse(setCookies);
407
- ctx.cookies = Cookies.merge(ctx.cookies, incoming);
408
- return response.data;
409
- } else {
410
- ctx.throwError("TRADE_JOURNAL_ERROR" /* TRADE_JOURNAL_ERROR */, `Login failed: ${response.status}`);
441
+ /**
442
+ * Fetch trade journal entries for a date range.
443
+ * @param params.from - Start timestamp (Unix ms)
444
+ * @param params.to - End timestamp (Unix ms)
445
+ */
446
+ async tradeJournal(params) {
447
+ this._ctx.ensureSession();
448
+ try {
449
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
450
+ const response = await retryRequest(
451
+ {
452
+ method: "GET",
453
+ url: endpoints.tradeJournal(this._ctx.broker, params),
454
+ headers: { ...baseHeaders(), Cookie: cookieStr }
455
+ },
456
+ this._ctx.retries
457
+ );
458
+ if (response.status === 200) {
459
+ const setCookies = response.headers["set-cookie"] ?? [];
460
+ const incoming = Cookies.parse(setCookies);
461
+ this._ctx.cookies = Cookies.merge(this._ctx.cookies, incoming);
462
+ return response.data;
463
+ } else {
464
+ this._ctx.throwError("TRADE_JOURNAL_ERROR" /* TRADE_JOURNAL_ERROR */, `Login failed: ${response.status}`);
465
+ }
466
+ } catch (error) {
467
+ if (error instanceof DxtradeError) throw error;
468
+ const message = error instanceof Error ? error.message : "Unknown error";
469
+ this._ctx.throwError("TRADE_JOURNAL_ERROR" /* TRADE_JOURNAL_ERROR */, `Trade journal error: ${message}`);
411
470
  }
412
- } catch (error) {
413
- if (error instanceof DxtradeError) throw error;
414
- const message = error instanceof Error ? error.message : "Unknown error";
415
- ctx.throwError("TRADE_JOURNAL_ERROR" /* TRADE_JOURNAL_ERROR */, `Trade journal error: ${message}`);
416
471
  }
417
- }
472
+ };
418
473
 
419
474
  // src/domains/assessments/assessments.ts
420
- async function getAssessments(ctx, params) {
421
- ctx.ensureSession();
422
- try {
423
- const response = await retryRequest(
424
- {
425
- method: "POST",
426
- url: endpoints.assessments(ctx.broker),
427
- data: {
428
- from: params.from,
429
- instrument: params.instrument,
430
- subtype: params.subtype ?? null,
431
- to: params.to
475
+ var AssessmentsDomain = class {
476
+ constructor(_ctx) {
477
+ this._ctx = _ctx;
478
+ }
479
+ /** Fetch PnL assessments for an instrument within a date range. */
480
+ async get(params) {
481
+ this._ctx.ensureSession();
482
+ try {
483
+ const response = await retryRequest(
484
+ {
485
+ method: "POST",
486
+ url: endpoints.assessments(this._ctx.broker),
487
+ data: {
488
+ from: params.from,
489
+ instrument: params.instrument,
490
+ subtype: params.subtype ?? null,
491
+ to: params.to
492
+ },
493
+ headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
432
494
  },
433
- headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
434
- },
435
- ctx.retries
436
- );
437
- return response.data;
438
- } catch (error) {
439
- if (error instanceof DxtradeError) throw error;
440
- const message = error instanceof Error ? error.message : "Unknown error";
441
- ctx.throwError("ASSESSMENTS_ERROR" /* ASSESSMENTS_ERROR */, `Error fetching assessments: ${message}`);
495
+ this._ctx.retries
496
+ );
497
+ return response.data;
498
+ } catch (error) {
499
+ if (error instanceof DxtradeError) throw error;
500
+ const message = error instanceof Error ? error.message : "Unknown error";
501
+ this._ctx.throwError("ASSESSMENTS_ERROR" /* ASSESSMENTS_ERROR */, `Error fetching assessments: ${message}`);
502
+ }
442
503
  }
443
- }
504
+ };
444
505
 
445
506
  // src/domains/instrument/instrument.ts
446
507
  var import_ws3 = __toESM(require("ws"));
447
- async function getInstruments(ctx, params = {}, timeout = 3e4) {
448
- ctx.ensureSession();
449
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
450
- const cookieStr = Cookies.serialize(ctx.cookies);
451
- return new Promise((resolve, reject) => {
452
- const ws = new import_ws3.default(wsUrl, { headers: { Cookie: cookieStr } });
453
- const timer = setTimeout(() => {
454
- ws.close();
455
- reject(new DxtradeError("INSTRUMENTS_TIMEOUT" /* INSTRUMENTS_TIMEOUT */, "Instruments request timed out"));
456
- }, timeout);
457
- let instruments = [];
458
- let settleTimer = null;
459
- ws.on("message", (data) => {
460
- const msg = parseWsData(data);
461
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
462
- if (typeof msg === "string") return;
463
- if (msg.type === "INSTRUMENTS" /* INSTRUMENTS */) {
464
- instruments.push(...msg.body);
465
- if (settleTimer) clearTimeout(settleTimer);
466
- settleTimer = setTimeout(() => {
467
- clearTimeout(timer);
468
- ws.close();
469
- resolve(
470
- instruments.filter((instrument) => {
471
- for (const key in params) {
472
- if (params[key] !== instrument[key]) {
473
- return false;
508
+ var InstrumentsDomain = class {
509
+ constructor(_ctx) {
510
+ this._ctx = _ctx;
511
+ }
512
+ /** Get all available instruments, optionally filtered by partial match (e.g. `{ type: "FOREX" }`). */
513
+ async get(params = {}, timeout = 3e4) {
514
+ this._ctx.ensureSession();
515
+ const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
516
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
517
+ return new Promise((resolve, reject) => {
518
+ const ws = new import_ws3.default(wsUrl, { headers: { Cookie: cookieStr } });
519
+ const timer = setTimeout(() => {
520
+ ws.close();
521
+ reject(new DxtradeError("INSTRUMENTS_TIMEOUT" /* INSTRUMENTS_TIMEOUT */, "Instruments request timed out"));
522
+ }, timeout);
523
+ let instruments = [];
524
+ let settleTimer = null;
525
+ ws.on("message", (data) => {
526
+ const msg = parseWsData(data);
527
+ if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
528
+ if (typeof msg === "string") return;
529
+ if (msg.type === "INSTRUMENTS" /* INSTRUMENTS */) {
530
+ instruments.push(...msg.body);
531
+ if (settleTimer) clearTimeout(settleTimer);
532
+ settleTimer = setTimeout(() => {
533
+ clearTimeout(timer);
534
+ ws.close();
535
+ resolve(
536
+ instruments.filter((instrument) => {
537
+ for (const key in params) {
538
+ if (params[key] !== instrument[key]) {
539
+ return false;
540
+ }
474
541
  }
475
- }
476
- return true;
477
- })
478
- );
479
- }, 200);
480
- }
481
- });
482
- ws.on("error", (error) => {
483
- clearTimeout(timer);
484
- ws.close();
485
- reject(new DxtradeError("INSTRUMENTS_ERROR" /* INSTRUMENTS_ERROR */, `Instruments error: ${error.message}`));
542
+ return true;
543
+ })
544
+ );
545
+ }, 200);
546
+ }
547
+ });
548
+ ws.on("error", (error) => {
549
+ clearTimeout(timer);
550
+ ws.close();
551
+ checkWsRateLimit(error);
552
+ reject(new DxtradeError("INSTRUMENTS_ERROR" /* INSTRUMENTS_ERROR */, `Instruments error: ${error.message}`));
553
+ });
486
554
  });
487
- });
488
- }
555
+ }
556
+ };
489
557
 
490
558
  // src/domains/ohlc/ohlc.ts
491
559
  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
- );
560
+ var OhlcDomain = class {
561
+ constructor(_ctx) {
562
+ this._ctx = _ctx;
498
563
  }
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);
564
+ /** Stream real-time OHLC bar updates. Requires connect(). Returns unsubscribe function. */
565
+ async stream(params, callback) {
566
+ if (!this._ctx.wsManager) {
567
+ this._ctx.throwError(
568
+ "STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
569
+ "Streaming requires a persistent WebSocket. Use connect() instead of auth()."
570
+ );
518
571
  }
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();
572
+ const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
573
+ const subtopic = WS_MESSAGE.SUBTOPIC.OHLC_STREAM;
574
+ const headers = authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies));
575
+ const snapshotBars = [];
576
+ let snapshotDone = false;
577
+ let resolveSnapshot = null;
578
+ const onChartFeed = (body) => {
579
+ if (body?.subtopic !== subtopic) return;
580
+ const data = body.data;
581
+ if (!Array.isArray(data)) return;
582
+ if (!snapshotDone) {
583
+ snapshotBars.push(...data);
584
+ if (body.snapshotEnd) {
585
+ snapshotDone = true;
586
+ callback([...snapshotBars]);
587
+ resolveSnapshot?.();
588
+ }
567
589
  } else {
568
- ctx.wsManager?.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
569
- reject(new DxtradeError("OHLC_TIMEOUT" /* OHLC_TIMEOUT */, "OHLC stream snapshot timed out"));
590
+ callback(data);
570
591
  }
571
- }, 3e4);
572
- resolveSnapshot = () => {
573
- clearTimeout(timer);
574
- resolve();
575
592
  };
576
- });
577
- return () => {
578
- ctx.wsManager?.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
579
- };
580
- }
581
- async function getOHLC(ctx, params, timeout = 3e4) {
582
- ctx.ensureSession();
583
- const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
584
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
585
- const cookieStr = Cookies.serialize(ctx.cookies);
586
- const headers = authHeaders(ctx.csrf, cookieStr);
587
- return new Promise((resolve, reject) => {
588
- const ws = new import_ws4.default(wsUrl, { headers: { Cookie: cookieStr } });
589
- const bars = [];
590
- let putsSent = false;
591
- let initSettleTimer = null;
592
- let barSettleTimer = null;
593
- const timer = setTimeout(() => {
594
- ws.close();
595
- reject(new DxtradeError("OHLC_TIMEOUT" /* OHLC_TIMEOUT */, "OHLC data timed out"));
596
- }, timeout);
597
- function cleanup() {
598
- clearTimeout(timer);
599
- if (initSettleTimer) clearTimeout(initSettleTimer);
600
- if (barSettleTimer) clearTimeout(barSettleTimer);
601
- ws.close();
602
- }
603
- async function sendPuts() {
604
- putsSent = true;
605
- try {
606
- await retryRequest(
607
- {
608
- method: "PUT",
609
- url: endpoints.subscribeInstruments(ctx.broker),
610
- data: { instruments: [symbol] },
611
- headers
612
- },
613
- ctx.retries
614
- );
615
- await retryRequest(
616
- {
617
- method: "PUT",
618
- url: endpoints.charts(ctx.broker),
619
- data: {
620
- chartIds: [],
621
- requests: [
622
- {
623
- aggregationPeriodSeconds: resolution,
624
- extendedSession: true,
625
- forexPriceField: priceField,
626
- id: 0,
627
- maxBarsCount: maxBars,
628
- range,
629
- studySubscription: [],
630
- subtopic: WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT,
631
- symbol
632
- }
633
- ]
634
- },
635
- headers
593
+ this._ctx.wsManager.on("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
594
+ try {
595
+ await retryRequest(
596
+ {
597
+ method: "PUT",
598
+ url: endpoints.subscribeInstruments(this._ctx.broker),
599
+ data: { instruments: [symbol] },
600
+ headers
601
+ },
602
+ this._ctx.retries
603
+ );
604
+ await retryRequest(
605
+ {
606
+ method: "PUT",
607
+ url: endpoints.charts(this._ctx.broker),
608
+ data: {
609
+ chartIds: [],
610
+ requests: [
611
+ {
612
+ aggregationPeriodSeconds: resolution,
613
+ extendedSession: true,
614
+ forexPriceField: priceField,
615
+ id: 0,
616
+ maxBarsCount: maxBars,
617
+ range,
618
+ studySubscription: [],
619
+ subtopic,
620
+ symbol
621
+ }
622
+ ]
636
623
  },
637
- ctx.retries
638
- );
639
- } catch (error) {
640
- cleanup();
641
- const message = error instanceof Error ? error.message : "Unknown error";
642
- reject(new DxtradeError("OHLC_ERROR" /* OHLC_ERROR */, `Error fetching OHLC data: ${message}`));
643
- }
624
+ headers
625
+ },
626
+ this._ctx.retries
627
+ );
628
+ } catch (error) {
629
+ this._ctx.wsManager.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
630
+ const message = error instanceof Error ? error.message : "Unknown error";
631
+ this._ctx.throwError("OHLC_ERROR" /* OHLC_ERROR */, `OHLC stream subscription error: ${message}`);
644
632
  }
645
- ws.on("message", (data) => {
646
- const msg = parseWsData(data);
647
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
648
- if (typeof msg === "string") return;
649
- if (!putsSent) {
633
+ await new Promise((resolve, reject) => {
634
+ if (snapshotDone) return resolve();
635
+ const timer = setTimeout(() => {
636
+ if (snapshotBars.length > 0) {
637
+ snapshotDone = true;
638
+ callback([...snapshotBars]);
639
+ resolve();
640
+ } else {
641
+ this._ctx.wsManager?.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
642
+ reject(new DxtradeError("OHLC_TIMEOUT" /* OHLC_TIMEOUT */, "OHLC stream snapshot timed out"));
643
+ }
644
+ }, 3e4);
645
+ resolveSnapshot = () => {
646
+ clearTimeout(timer);
647
+ resolve();
648
+ };
649
+ });
650
+ return () => {
651
+ this._ctx.wsManager?.removeListener("chartFeedSubtopic" /* CHART_FEED_SUBTOPIC */, onChartFeed);
652
+ };
653
+ }
654
+ /**
655
+ * Fetch OHLC price bars for a symbol.
656
+ * @param params.symbol - Instrument symbol (e.g. "EURUSD")
657
+ * @param params.resolution - Bar period in seconds (default: 60 = 1 min)
658
+ * @param params.range - Lookback window in seconds (default: 432000 = 5 days)
659
+ * @param params.maxBars - Maximum bars to return (default: 3500)
660
+ * @param params.priceField - "bid" or "ask" (default: "bid")
661
+ */
662
+ async get(params, timeout = 3e4) {
663
+ this._ctx.ensureSession();
664
+ const { symbol, resolution = 60, range = 432e3, maxBars = 3500, priceField = "bid" } = params;
665
+ const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
666
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
667
+ const headers = authHeaders(this._ctx.csrf, cookieStr);
668
+ return new Promise((resolve, reject) => {
669
+ const ws = new import_ws4.default(wsUrl, { headers: { Cookie: cookieStr } });
670
+ const bars = [];
671
+ let putsSent = false;
672
+ let initSettleTimer = null;
673
+ let barSettleTimer = null;
674
+ const timer = setTimeout(() => {
675
+ ws.close();
676
+ reject(new DxtradeError("OHLC_TIMEOUT" /* OHLC_TIMEOUT */, "OHLC data timed out"));
677
+ }, timeout);
678
+ function cleanup() {
679
+ clearTimeout(timer);
650
680
  if (initSettleTimer) clearTimeout(initSettleTimer);
651
- initSettleTimer = setTimeout(() => sendPuts(), 1e3);
652
- return;
653
- }
654
- const body = msg.body;
655
- if (body?.subtopic !== WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT) return;
656
- if (Array.isArray(body.data)) {
657
- bars.push(...body.data);
681
+ if (barSettleTimer) clearTimeout(barSettleTimer);
682
+ ws.close();
658
683
  }
659
- if (barSettleTimer) clearTimeout(barSettleTimer);
660
- if (body.snapshotEnd) {
661
- cleanup();
662
- resolve(bars);
663
- } else {
664
- barSettleTimer = setTimeout(() => {
684
+ const sendPuts = async () => {
685
+ putsSent = true;
686
+ try {
687
+ await retryRequest(
688
+ {
689
+ method: "PUT",
690
+ url: endpoints.subscribeInstruments(this._ctx.broker),
691
+ data: { instruments: [symbol] },
692
+ headers
693
+ },
694
+ this._ctx.retries
695
+ );
696
+ await retryRequest(
697
+ {
698
+ method: "PUT",
699
+ url: endpoints.charts(this._ctx.broker),
700
+ data: {
701
+ chartIds: [],
702
+ requests: [
703
+ {
704
+ aggregationPeriodSeconds: resolution,
705
+ extendedSession: true,
706
+ forexPriceField: priceField,
707
+ id: 0,
708
+ maxBarsCount: maxBars,
709
+ range,
710
+ studySubscription: [],
711
+ subtopic: WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT,
712
+ symbol
713
+ }
714
+ ]
715
+ },
716
+ headers
717
+ },
718
+ this._ctx.retries
719
+ );
720
+ } catch (error) {
721
+ cleanup();
722
+ const message = error instanceof Error ? error.message : "Unknown error";
723
+ reject(new DxtradeError("OHLC_ERROR" /* OHLC_ERROR */, `Error fetching OHLC data: ${message}`));
724
+ }
725
+ };
726
+ ws.on("message", (data) => {
727
+ const msg = parseWsData(data);
728
+ if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
729
+ if (typeof msg === "string") return;
730
+ if (!putsSent) {
731
+ if (initSettleTimer) clearTimeout(initSettleTimer);
732
+ initSettleTimer = setTimeout(() => sendPuts(), 1e3);
733
+ return;
734
+ }
735
+ const body = msg.body;
736
+ if (body?.subtopic !== WS_MESSAGE.SUBTOPIC.BIG_CHART_COMPONENT) return;
737
+ if (Array.isArray(body.data)) {
738
+ bars.push(...body.data);
739
+ }
740
+ if (barSettleTimer) clearTimeout(barSettleTimer);
741
+ if (body.snapshotEnd) {
665
742
  cleanup();
666
743
  resolve(bars);
667
- }, 2e3);
668
- }
669
- });
670
- ws.on("error", (error) => {
671
- cleanup();
672
- reject(new DxtradeError("OHLC_ERROR" /* OHLC_ERROR */, `OHLC WebSocket error: ${error.message}`));
744
+ } else {
745
+ barSettleTimer = setTimeout(() => {
746
+ cleanup();
747
+ resolve(bars);
748
+ }, 2e3);
749
+ }
750
+ });
751
+ ws.on("error", (error) => {
752
+ cleanup();
753
+ checkWsRateLimit(error);
754
+ reject(new DxtradeError("OHLC_ERROR" /* OHLC_ERROR */, `OHLC WebSocket error: ${error.message}`));
755
+ });
673
756
  });
674
- });
675
- }
757
+ }
758
+ };
676
759
 
677
760
  // src/domains/order/order.ts
678
761
  var import_crypto = __toESM(require("crypto"));
@@ -680,87 +763,96 @@ var import_ws6 = __toESM(require("ws"));
680
763
 
681
764
  // src/domains/symbol/symbol.ts
682
765
  var import_ws5 = __toESM(require("ws"));
683
- async function getSymbolSuggestions(ctx, text) {
684
- ctx.ensureSession();
685
- try {
686
- const cookieStr = Cookies.serialize(ctx.cookies);
687
- const response = await retryRequest(
688
- {
689
- method: "GET",
690
- url: endpoints.suggest(ctx.broker, text),
691
- headers: { ...baseHeaders(), Cookie: cookieStr }
692
- },
693
- ctx.retries
694
- );
695
- const suggests = response.data?.suggests;
696
- if (!suggests?.length) {
697
- ctx.throwError("NO_SUGGESTIONS" /* NO_SUGGESTIONS */, "No symbol suggestions found");
766
+ var SymbolsDomain = class {
767
+ constructor(_ctx) {
768
+ this._ctx = _ctx;
769
+ }
770
+ /** Search for symbols matching the given text (e.g. "EURUSD", "BTC"). */
771
+ async search(text) {
772
+ this._ctx.ensureSession();
773
+ try {
774
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
775
+ const response = await retryRequest(
776
+ {
777
+ method: "GET",
778
+ url: endpoints.suggest(this._ctx.broker, text),
779
+ headers: { ...baseHeaders(), Cookie: cookieStr }
780
+ },
781
+ this._ctx.retries
782
+ );
783
+ const suggests = response.data?.suggests;
784
+ if (!suggests?.length) {
785
+ this._ctx.throwError("NO_SUGGESTIONS" /* NO_SUGGESTIONS */, "No symbol suggestions found");
786
+ }
787
+ return suggests;
788
+ } catch (error) {
789
+ if (error instanceof DxtradeError) throw error;
790
+ const message = error instanceof Error ? error.message : "Unknown error";
791
+ this._ctx.throwError("SUGGEST_ERROR" /* SUGGEST_ERROR */, `Error getting symbol suggestions: ${message}`);
698
792
  }
699
- return suggests;
700
- } catch (error) {
701
- if (error instanceof DxtradeError) throw error;
702
- const message = error instanceof Error ? error.message : "Unknown error";
703
- ctx.throwError("SUGGEST_ERROR" /* SUGGEST_ERROR */, `Error getting symbol suggestions: ${message}`);
704
793
  }
705
- }
706
- async function getSymbolInfo(ctx, symbol) {
707
- ctx.ensureSession();
708
- try {
709
- const offsetMinutes = Math.abs((/* @__PURE__ */ new Date()).getTimezoneOffset());
710
- const cookieStr = Cookies.serialize(ctx.cookies);
711
- const response = await retryRequest(
712
- {
713
- method: "GET",
714
- url: endpoints.instrumentInfo(ctx.broker, symbol, offsetMinutes),
715
- headers: { ...baseHeaders(), Cookie: cookieStr }
716
- },
717
- ctx.retries
718
- );
719
- if (!response.data) {
720
- ctx.throwError("NO_SYMBOL_INFO" /* NO_SYMBOL_INFO */, "No symbol info returned");
794
+ /** Get detailed instrument info for a symbol, including volume limits and lot size. */
795
+ async info(symbol) {
796
+ this._ctx.ensureSession();
797
+ try {
798
+ const offsetMinutes = Math.abs((/* @__PURE__ */ new Date()).getTimezoneOffset());
799
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
800
+ const response = await retryRequest(
801
+ {
802
+ method: "GET",
803
+ url: endpoints.instrumentInfo(this._ctx.broker, symbol, offsetMinutes),
804
+ headers: { ...baseHeaders(), Cookie: cookieStr }
805
+ },
806
+ this._ctx.retries
807
+ );
808
+ if (!response.data) {
809
+ this._ctx.throwError("NO_SYMBOL_INFO" /* NO_SYMBOL_INFO */, "No symbol info returned");
810
+ }
811
+ return response.data;
812
+ } catch (error) {
813
+ if (error instanceof DxtradeError) throw error;
814
+ const message = error instanceof Error ? error.message : "Unknown error";
815
+ this._ctx.throwError("SYMBOL_INFO_ERROR" /* SYMBOL_INFO_ERROR */, `Error getting symbol info: ${message}`);
721
816
  }
722
- return response.data;
723
- } catch (error) {
724
- if (error instanceof DxtradeError) throw error;
725
- const message = error instanceof Error ? error.message : "Unknown error";
726
- ctx.throwError("SYMBOL_INFO_ERROR" /* SYMBOL_INFO_ERROR */, `Error getting symbol info: ${message}`);
727
817
  }
728
- }
729
- async function getSymbolLimits(ctx, timeout = 3e4) {
730
- ctx.ensureSession();
731
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
732
- const cookieStr = Cookies.serialize(ctx.cookies);
733
- return new Promise((resolve, reject) => {
734
- const ws = new import_ws5.default(wsUrl, { headers: { Cookie: cookieStr } });
735
- const timer = setTimeout(() => {
736
- ws.close();
737
- reject(new DxtradeError("LIMITS_TIMEOUT" /* LIMITS_TIMEOUT */, "Symbol limits request timed out"));
738
- }, timeout);
739
- let limits = [];
740
- let settleTimer = null;
741
- ws.on("message", (data) => {
742
- const msg = parseWsData(data);
743
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
744
- if (typeof msg === "string") return;
745
- if (msg.type === "LIMITS" /* LIMITS */) {
746
- const batch = msg.body;
747
- if (batch.length === 0) return;
748
- limits.push(...batch);
749
- if (settleTimer) clearTimeout(settleTimer);
750
- settleTimer = setTimeout(() => {
751
- clearTimeout(timer);
752
- ws.close();
753
- resolve(limits);
754
- }, 200);
755
- }
756
- });
757
- ws.on("error", (error) => {
758
- clearTimeout(timer);
759
- ws.close();
760
- reject(new DxtradeError("LIMITS_ERROR" /* LIMITS_ERROR */, `Symbol limits error: ${error.message}`));
818
+ /** Get order size limits and stop/limit distances for all symbols. */
819
+ async limits(timeout = 3e4) {
820
+ this._ctx.ensureSession();
821
+ const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
822
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
823
+ return new Promise((resolve, reject) => {
824
+ const ws = new import_ws5.default(wsUrl, { headers: { Cookie: cookieStr } });
825
+ const timer = setTimeout(() => {
826
+ ws.close();
827
+ reject(new DxtradeError("LIMITS_TIMEOUT" /* LIMITS_TIMEOUT */, "Symbol limits request timed out"));
828
+ }, timeout);
829
+ let limits = [];
830
+ let settleTimer = null;
831
+ ws.on("message", (data) => {
832
+ const msg = parseWsData(data);
833
+ if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
834
+ if (typeof msg === "string") return;
835
+ if (msg.type === "LIMITS" /* LIMITS */) {
836
+ const batch = msg.body;
837
+ if (batch.length === 0) return;
838
+ limits.push(...batch);
839
+ if (settleTimer) clearTimeout(settleTimer);
840
+ settleTimer = setTimeout(() => {
841
+ clearTimeout(timer);
842
+ ws.close();
843
+ resolve(limits);
844
+ }, 200);
845
+ }
846
+ });
847
+ ws.on("error", (error) => {
848
+ clearTimeout(timer);
849
+ ws.close();
850
+ checkWsRateLimit(error);
851
+ reject(new DxtradeError("LIMITS_ERROR" /* LIMITS_ERROR */, `Symbol limits error: ${error.message}`));
852
+ });
761
853
  });
762
- });
763
- }
854
+ }
855
+ };
764
856
 
765
857
  // src/domains/order/order.ts
766
858
  function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
@@ -791,20 +883,21 @@ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
791
883
  if (msg.type === "MESSAGE" /* MESSAGE */) {
792
884
  const messages = msg.body;
793
885
  const orderMsg = messages?.findLast?.(
794
- (m) => m.messageCategory === "TRADE_LOG" && m.messageType === "ORDER" && !m.historyMessage
886
+ (m) => m.messageCategory === "TRADE_LOG" /* TRADE_LOG */ && m.messageType === "ORDER" /* ORDER */ && !m.historyMessage
795
887
  );
796
888
  if (!orderMsg) return;
797
889
  const params = orderMsg.parametersTO;
798
- if (params.orderStatus === "REJECTED") {
890
+ if (params.orderStatus === "REJECTED" /* REJECTED */) {
799
891
  const reason = params.rejectReason?.key ?? "Unknown reason";
800
892
  done(new Error(`[dxtrade-api] Order rejected: ${reason}`));
801
- } else if (params.orderStatus === "FILLED") {
893
+ } else if (params.orderStatus === "FILLED" /* FILLED */) {
802
894
  done(null, {
803
895
  orderId: params.orderKey,
804
896
  status: params.orderStatus,
805
897
  symbol: params.symbol,
806
898
  filledQuantity: params.filledQuantity,
807
- filledPrice: params.filledPrice
899
+ filledPrice: params.filledPrice,
900
+ positionCode: params.positionCode
808
901
  });
809
902
  }
810
903
  return;
@@ -812,9 +905,9 @@ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
812
905
  if (msg.type === "ORDERS" /* ORDERS */) {
813
906
  const body = msg.body?.[0];
814
907
  if (!body?.orderId) return;
815
- if (body.status === "REJECTED") {
908
+ if (body.status === "REJECTED" /* REJECTED */) {
816
909
  done(new Error(`[dxtrade-api] Order rejected: ${body.statusDescription ?? "Unknown reason"}`));
817
- } else if (body.status === "FILLED") {
910
+ } else if (body.status === "FILLED" /* FILLED */) {
818
911
  done(null, body);
819
912
  }
820
913
  }
@@ -824,161 +917,236 @@ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
824
917
  settled = true;
825
918
  clearTimeout(timer);
826
919
  ws.close();
827
- reject(new Error(`[dxtrade-api] WebSocket order listener error: ${error.message}`));
920
+ checkWsRateLimit(error);
921
+ reject(new DxtradeError("ORDER_ERROR" /* ORDER_ERROR */, `WebSocket order listener error: ${error.message}`));
828
922
  });
829
923
  });
830
924
  return { promise, ready };
831
925
  }
832
- async function getOrders(ctx, timeout = 3e4) {
833
- ctx.ensureSession();
834
- if (ctx.wsManager) {
835
- return ctx.wsManager.waitFor("ORDERS" /* ORDERS */, timeout);
836
- }
837
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
838
- const cookieStr = Cookies.serialize(ctx.cookies);
926
+ function createWsManagerOrderListener(ctx, timeout = 3e4) {
839
927
  return new Promise((resolve, reject) => {
840
- const ws = new import_ws6.default(wsUrl, { headers: { Cookie: cookieStr } });
928
+ let settled = false;
841
929
  const timer = setTimeout(() => {
842
- ws.close();
843
- reject(new DxtradeError("ORDERS_TIMEOUT" /* ORDERS_TIMEOUT */, "Orders request timed out"));
930
+ if (settled) return;
931
+ settled = true;
932
+ cleanup();
933
+ reject(new Error("[dxtrade-api] Order update timed out"));
844
934
  }, timeout);
845
- ws.on("message", (data) => {
846
- const msg = parseWsData(data);
847
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
848
- if (typeof msg === "string") return;
849
- if (msg.type === "ORDERS" /* ORDERS */) {
850
- clearTimeout(timer);
851
- ws.close();
852
- resolve(msg.body);
853
- }
854
- });
855
- ws.on("error", (error) => {
935
+ function done(err, result) {
936
+ if (settled) return;
937
+ settled = true;
856
938
  clearTimeout(timer);
857
- ws.close();
858
- reject(new DxtradeError("ORDERS_ERROR" /* ORDERS_ERROR */, `Orders error: ${error.message}`));
859
- });
939
+ cleanup();
940
+ if (err) reject(err);
941
+ else resolve(result);
942
+ }
943
+ function onMessage(body) {
944
+ const messages = body;
945
+ const orderMsg = messages?.findLast?.(
946
+ (m) => m.messageCategory === "TRADE_LOG" /* TRADE_LOG */ && m.messageType === "ORDER" /* ORDER */ && !m.historyMessage
947
+ );
948
+ if (!orderMsg) return;
949
+ const params = orderMsg.parametersTO;
950
+ if (params.orderStatus === "REJECTED" /* REJECTED */) {
951
+ const reason = params.rejectReason?.key ?? "Unknown reason";
952
+ done(new Error(`[dxtrade-api] Order rejected: ${reason}`));
953
+ } else if (params.orderStatus === "FILLED" /* FILLED */) {
954
+ done(null, {
955
+ orderId: params.orderKey,
956
+ status: params.orderStatus,
957
+ symbol: params.symbol,
958
+ filledQuantity: params.filledQuantity,
959
+ filledPrice: params.filledPrice,
960
+ positionCode: params.positionCode
961
+ });
962
+ }
963
+ }
964
+ function onOrders(body) {
965
+ const order = body?.[0];
966
+ if (!order?.orderId) return;
967
+ if (order.status === "REJECTED" /* REJECTED */) {
968
+ done(new Error(`[dxtrade-api] Order rejected: ${order.statusDescription ?? "Unknown reason"}`));
969
+ } else if (order.status === "FILLED" /* FILLED */) {
970
+ done(null, order);
971
+ }
972
+ }
973
+ function cleanup() {
974
+ ctx.wsManager?.removeListener("MESSAGE" /* MESSAGE */, onMessage);
975
+ ctx.wsManager?.removeListener("ORDERS" /* ORDERS */, onOrders);
976
+ }
977
+ ctx.wsManager.on("MESSAGE" /* MESSAGE */, onMessage);
978
+ ctx.wsManager.on("ORDERS" /* ORDERS */, onOrders);
860
979
  });
861
980
  }
862
- async function cancelOrder(ctx, orderChainId) {
863
- ctx.ensureSession();
864
- const accountId = ctx.accountId ?? ctx.config.accountId;
865
- if (!accountId) {
866
- ctx.throwError("CANCEL_ORDER_ERROR" /* CANCEL_ORDER_ERROR */, "accountId is required to cancel an order");
867
- }
868
- try {
869
- await retryRequest(
870
- {
871
- method: "DELETE",
872
- url: endpoints.cancelOrder(ctx.broker, accountId, orderChainId),
873
- headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
874
- },
875
- ctx.retries
876
- );
877
- } catch (error) {
878
- if (error instanceof DxtradeError) throw error;
879
- const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
880
- ctx.throwError("CANCEL_ORDER_ERROR" /* CANCEL_ORDER_ERROR */, `Cancel order error: ${message}`);
981
+ var OrdersDomain = class {
982
+ constructor(_ctx) {
983
+ this._ctx = _ctx;
881
984
  }
882
- }
883
- async function cancelAllOrders(ctx) {
884
- const orders = await getOrders(ctx);
885
- const pending = orders.filter((o) => !o.finalStatus);
886
- for (const order of pending) {
887
- await cancelOrder(ctx, order.orderId);
985
+ /** Get all pending/open orders via WebSocket. */
986
+ async get(timeout = 3e4) {
987
+ this._ctx.ensureSession();
988
+ if (this._ctx.wsManager) {
989
+ return this._ctx.wsManager.waitFor("ORDERS" /* ORDERS */, timeout);
990
+ }
991
+ const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
992
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
993
+ return new Promise((resolve, reject) => {
994
+ const ws = new import_ws6.default(wsUrl, { headers: { Cookie: cookieStr } });
995
+ const timer = setTimeout(() => {
996
+ ws.close();
997
+ reject(new DxtradeError("ORDERS_TIMEOUT" /* ORDERS_TIMEOUT */, "Orders request timed out"));
998
+ }, timeout);
999
+ ws.on("message", (data) => {
1000
+ const msg = parseWsData(data);
1001
+ if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
1002
+ if (typeof msg === "string") return;
1003
+ if (msg.type === "ORDERS" /* ORDERS */) {
1004
+ clearTimeout(timer);
1005
+ ws.close();
1006
+ resolve(msg.body);
1007
+ }
1008
+ });
1009
+ ws.on("error", (error) => {
1010
+ clearTimeout(timer);
1011
+ ws.close();
1012
+ checkWsRateLimit(error);
1013
+ reject(new DxtradeError("ORDERS_ERROR" /* ORDERS_ERROR */, `Orders error: ${error.message}`));
1014
+ });
1015
+ });
888
1016
  }
889
- }
890
- async function submitOrder(ctx, params) {
891
- ctx.ensureSession();
892
- const {
893
- symbol,
894
- side,
895
- quantity,
896
- orderType,
897
- orderCode,
898
- price,
899
- instrumentId,
900
- stopLoss,
901
- takeProfit,
902
- positionEffect = "OPENING" /* OPENING */,
903
- positionCode,
904
- tif = "GTC",
905
- expireDate,
906
- metadata
907
- } = params;
908
- const info = await getSymbolInfo(ctx, symbol);
909
- const units = Math.round(quantity * info.lotSize);
910
- const qty = side === "BUY" /* BUY */ ? units : -units;
911
- const priceParam = orderType === "STOP" /* STOP */ ? "stopPrice" : "limitPrice";
912
- const orderData = {
913
- directExchange: false,
914
- legs: [
915
- {
916
- ...instrumentId != null && { instrumentId },
917
- ...positionCode != null && { positionCode },
918
- positionEffect,
919
- ratioQuantity: 1,
920
- symbol
921
- }
922
- ],
923
- orderSide: side,
924
- orderType,
925
- quantity: qty,
926
- requestId: orderCode ?? `gwt-uid-931-${import_crypto.default.randomUUID()}`,
927
- timeInForce: tif,
928
- ...expireDate != null && { expireDate },
929
- ...metadata != null && { metadata }
930
- };
931
- if (price != null && orderType !== "MARKET" /* MARKET */) {
932
- orderData[priceParam] = price;
1017
+ /** Cancel a single pending order by its order chain ID. */
1018
+ async cancel(orderChainId) {
1019
+ this._ctx.ensureSession();
1020
+ const accountId = this._ctx.accountId ?? this._ctx.config.accountId;
1021
+ if (!accountId) {
1022
+ this._ctx.throwError("CANCEL_ORDER_ERROR" /* CANCEL_ORDER_ERROR */, "accountId is required to cancel an order");
1023
+ }
1024
+ try {
1025
+ await retryRequest(
1026
+ {
1027
+ method: "DELETE",
1028
+ url: endpoints.cancelOrder(this._ctx.broker, accountId, orderChainId),
1029
+ headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
1030
+ },
1031
+ this._ctx.retries
1032
+ );
1033
+ } catch (error) {
1034
+ if (error instanceof DxtradeError) throw error;
1035
+ const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
1036
+ this._ctx.throwError("CANCEL_ORDER_ERROR" /* CANCEL_ORDER_ERROR */, `Cancel order error: ${message}`);
1037
+ }
933
1038
  }
934
- if (stopLoss) {
935
- orderData.stopLoss = {
936
- ...stopLoss.offset != null && { fixedOffset: stopLoss.offset },
937
- ...stopLoss.price != null && { fixedPrice: stopLoss.price },
938
- priceFixed: stopLoss.price != null,
939
- orderChainId: 0,
940
- orderId: 0,
941
- orderType: "STOP" /* STOP */,
942
- quantityForProtection: qty,
943
- removed: false
944
- };
1039
+ /** Cancel all pending orders. */
1040
+ async cancelAll() {
1041
+ const orders = await this.get();
1042
+ const pending = orders.filter((o) => !o.finalStatus);
1043
+ for (const order of pending) {
1044
+ await this.cancel(order.orderId);
1045
+ }
945
1046
  }
946
- if (takeProfit) {
947
- orderData.takeProfit = {
948
- ...takeProfit.offset != null && { fixedOffset: takeProfit.offset },
949
- ...takeProfit.price != null && { fixedPrice: takeProfit.price },
950
- priceFixed: takeProfit.price != null,
951
- orderChainId: 0,
952
- orderId: 0,
953
- orderType: "LIMIT" /* LIMIT */,
954
- quantityForProtection: qty,
955
- removed: false
1047
+ /**
1048
+ * Submit a trading order and wait for WebSocket confirmation.
1049
+ * Supports market, limit, and stop orders with optional stop loss and take profit.
1050
+ */
1051
+ async submit(params) {
1052
+ this._ctx.ensureSession();
1053
+ const {
1054
+ symbol,
1055
+ side,
1056
+ quantity,
1057
+ orderType,
1058
+ orderCode,
1059
+ price,
1060
+ instrumentId,
1061
+ stopLoss,
1062
+ takeProfit,
1063
+ positionEffect = "OPENING" /* OPENING */,
1064
+ positionCode,
1065
+ tif = "GTC",
1066
+ expireDate,
1067
+ metadata
1068
+ } = params;
1069
+ const info = await new SymbolsDomain(this._ctx).info(symbol);
1070
+ const units = quantity * info.lotSize;
1071
+ const qty = side === "BUY" /* BUY */ ? units : -units;
1072
+ const priceParam = orderType === "STOP" /* STOP */ ? "stopPrice" : "limitPrice";
1073
+ const orderData = {
1074
+ directExchange: false,
1075
+ legs: [
1076
+ {
1077
+ ...instrumentId != null && { instrumentId },
1078
+ ...positionCode != null && { positionCode },
1079
+ positionEffect,
1080
+ ratioQuantity: 1,
1081
+ symbol
1082
+ }
1083
+ ],
1084
+ orderSide: side,
1085
+ orderType,
1086
+ quantity: qty,
1087
+ requestId: orderCode ?? `gwt-uid-931-${import_crypto.default.randomUUID()}`,
1088
+ timeInForce: tif,
1089
+ ...expireDate != null && { expireDate },
1090
+ ...metadata != null && { metadata }
956
1091
  };
1092
+ if (price != null) {
1093
+ orderData[priceParam] = price;
1094
+ }
1095
+ if (stopLoss) {
1096
+ orderData.stopLoss = {
1097
+ ...stopLoss.offset != null && { fixedOffset: stopLoss.offset },
1098
+ ...stopLoss.price != null && { fixedPrice: stopLoss.price },
1099
+ priceFixed: stopLoss.price != null,
1100
+ orderChainId: 0,
1101
+ orderId: 0,
1102
+ orderType: "STOP" /* STOP */,
1103
+ quantityForProtection: qty,
1104
+ removed: false
1105
+ };
1106
+ }
1107
+ if (takeProfit) {
1108
+ orderData.takeProfit = {
1109
+ ...takeProfit.offset != null && { fixedOffset: takeProfit.offset },
1110
+ ...takeProfit.price != null && { fixedPrice: takeProfit.price },
1111
+ priceFixed: takeProfit.price != null,
1112
+ orderChainId: 0,
1113
+ orderId: 0,
1114
+ orderType: "LIMIT" /* LIMIT */,
1115
+ quantityForProtection: qty,
1116
+ removed: false
1117
+ };
1118
+ }
1119
+ try {
1120
+ let listenerPromise;
1121
+ if (this._ctx.wsManager) {
1122
+ listenerPromise = createWsManagerOrderListener(this._ctx, 3e4);
1123
+ } else {
1124
+ const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
1125
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
1126
+ const listener = createOrderListener(wsUrl, cookieStr, 3e4, this._ctx.debug);
1127
+ await listener.ready;
1128
+ listenerPromise = listener.promise;
1129
+ }
1130
+ const response = await retryRequest(
1131
+ {
1132
+ method: "POST",
1133
+ url: endpoints.submitOrder(this._ctx.broker),
1134
+ data: orderData,
1135
+ headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
1136
+ },
1137
+ this._ctx.retries
1138
+ );
1139
+ this._ctx.callbacks.onOrderPlaced?.(response.data);
1140
+ const orderUpdate = await listenerPromise;
1141
+ this._ctx.callbacks.onOrderUpdate?.(orderUpdate);
1142
+ return orderUpdate;
1143
+ } catch (error) {
1144
+ if (error instanceof DxtradeError) throw error;
1145
+ const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
1146
+ this._ctx.throwError("ORDER_ERROR" /* ORDER_ERROR */, `Error submitting order: ${message}`);
1147
+ }
957
1148
  }
958
- try {
959
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
960
- const cookieStr = Cookies.serialize(ctx.cookies);
961
- const listener = createOrderListener(wsUrl, cookieStr, 3e4, ctx.debug);
962
- await listener.ready;
963
- const response = await retryRequest(
964
- {
965
- method: "POST",
966
- url: endpoints.submitOrder(ctx.broker),
967
- data: orderData,
968
- headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
969
- },
970
- ctx.retries
971
- );
972
- ctx.callbacks.onOrderPlaced?.(response.data);
973
- const orderUpdate = await listener.promise;
974
- ctx.callbacks.onOrderUpdate?.(orderUpdate);
975
- return orderUpdate;
976
- } catch (error) {
977
- if (error instanceof DxtradeError) throw error;
978
- const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
979
- ctx.throwError("ORDER_ERROR" /* ORDER_ERROR */, `Error submitting order: ${message}`);
980
- }
981
- }
1149
+ };
982
1150
 
983
1151
  // src/domains/position/position.ts
984
1152
  var import_ws7 = __toESM(require("ws"));
@@ -999,110 +1167,239 @@ function mergePositionsWithMetrics(positions, metrics) {
999
1167
  };
1000
1168
  });
1001
1169
  }
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
- );
1170
+ var PositionsDomain = class {
1171
+ constructor(_ctx) {
1172
+ this._ctx = _ctx;
1008
1173
  }
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));
1174
+ /** Stream real-time position updates with P&L metrics. Requires connect(). Returns unsubscribe function. */
1175
+ stream(callback) {
1176
+ if (!this._ctx.wsManager) {
1177
+ this._ctx.throwError(
1178
+ "STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
1179
+ "Streaming requires a persistent WebSocket. Use connect() instead of auth()."
1180
+ );
1014
1181
  }
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
- }
1026
- async function getPositions(ctx) {
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
- }
1035
- const wsUrl = endpoints.websocket(ctx.broker, ctx.atmosphereId);
1036
- const cookieStr = Cookies.serialize(ctx.cookies);
1037
- return new Promise((resolve, reject) => {
1038
- const ws = new import_ws7.default(wsUrl, { headers: { Cookie: cookieStr } });
1039
- let positions = null;
1040
- let metrics = null;
1041
- const timer = setTimeout(() => {
1042
- ws.close();
1043
- reject(new DxtradeError("ACCOUNT_POSITIONS_TIMEOUT" /* ACCOUNT_POSITIONS_TIMEOUT */, "Account positions timed out"));
1044
- }, 3e4);
1045
- ws.on("message", (data) => {
1046
- const msg = parseWsData(data);
1047
- if (shouldLog(msg, ctx.debug)) debugLog(msg);
1048
- if (typeof msg === "string") return;
1049
- if (msg.type === "POSITIONS" /* POSITIONS */) {
1050
- positions = msg.body;
1051
- }
1052
- if (msg.type === "POSITION_METRICS" /* POSITION_METRICS */) {
1053
- metrics = msg.body;
1054
- }
1182
+ const emit = () => {
1183
+ const positions = this._ctx.wsManager.getCached("POSITIONS" /* POSITIONS */);
1184
+ const metrics = this._ctx.wsManager.getCached("POSITION_METRICS" /* POSITION_METRICS */);
1055
1185
  if (positions && metrics) {
1186
+ callback(mergePositionsWithMetrics(positions, metrics));
1187
+ }
1188
+ };
1189
+ const onPositions = () => emit();
1190
+ const onMetrics = () => emit();
1191
+ this._ctx.wsManager.on("POSITIONS" /* POSITIONS */, onPositions);
1192
+ this._ctx.wsManager.on("POSITION_METRICS" /* POSITION_METRICS */, onMetrics);
1193
+ emit();
1194
+ return () => {
1195
+ this._ctx.wsManager?.removeListener("POSITIONS" /* POSITIONS */, onPositions);
1196
+ this._ctx.wsManager?.removeListener("POSITION_METRICS" /* POSITION_METRICS */, onMetrics);
1197
+ };
1198
+ }
1199
+ /** Get all open positions with P&L metrics merged. */
1200
+ async get() {
1201
+ this._ctx.ensureSession();
1202
+ if (this._ctx.wsManager) {
1203
+ const [positions, metrics] = await Promise.all([
1204
+ this._ctx.wsManager.waitFor("POSITIONS" /* POSITIONS */),
1205
+ this._ctx.wsManager.waitFor("POSITION_METRICS" /* POSITION_METRICS */)
1206
+ ]);
1207
+ return mergePositionsWithMetrics(positions, metrics);
1208
+ }
1209
+ const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
1210
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
1211
+ return new Promise((resolve, reject) => {
1212
+ const ws = new import_ws7.default(wsUrl, { headers: { Cookie: cookieStr } });
1213
+ let positions = null;
1214
+ let metrics = null;
1215
+ const timer = setTimeout(() => {
1216
+ ws.close();
1217
+ reject(new DxtradeError("ACCOUNT_POSITIONS_TIMEOUT" /* ACCOUNT_POSITIONS_TIMEOUT */, "Account positions timed out"));
1218
+ }, 3e4);
1219
+ ws.on("message", (data) => {
1220
+ const msg = parseWsData(data);
1221
+ if (shouldLog(msg, this._ctx.debug)) debugLog(msg);
1222
+ if (typeof msg === "string") return;
1223
+ if (msg.type === "POSITIONS" /* POSITIONS */) {
1224
+ positions = msg.body;
1225
+ }
1226
+ if (msg.type === "POSITION_METRICS" /* POSITION_METRICS */) {
1227
+ metrics = msg.body;
1228
+ }
1229
+ if (positions && metrics) {
1230
+ clearTimeout(timer);
1231
+ ws.close();
1232
+ resolve(mergePositionsWithMetrics(positions, metrics));
1233
+ }
1234
+ });
1235
+ ws.on("error", (error) => {
1056
1236
  clearTimeout(timer);
1057
1237
  ws.close();
1058
- resolve(mergePositionsWithMetrics(positions, metrics));
1059
- }
1060
- });
1061
- ws.on("error", (error) => {
1062
- clearTimeout(timer);
1063
- ws.close();
1064
- reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR" /* ACCOUNT_POSITIONS_ERROR */, `Account positions error: ${error.message}`));
1238
+ checkWsRateLimit(error);
1239
+ reject(new DxtradeError("ACCOUNT_POSITIONS_ERROR" /* ACCOUNT_POSITIONS_ERROR */, `Account positions error: ${error.message}`));
1240
+ });
1065
1241
  });
1066
- });
1067
- }
1068
- async function closeAllPositions(ctx) {
1069
- const positions = await getPositions(ctx);
1070
- for (const pos of positions) {
1242
+ }
1243
+ /** Close all open positions with market orders. */
1244
+ async closeAll() {
1245
+ const positions = await this.get();
1246
+ for (const pos of positions) {
1247
+ const closeData = {
1248
+ legs: [
1249
+ {
1250
+ instrumentId: pos.positionKey.instrumentId,
1251
+ positionCode: pos.positionKey.positionCode,
1252
+ positionEffect: "CLOSING",
1253
+ ratioQuantity: 1,
1254
+ symbol: pos.positionKey.positionCode
1255
+ }
1256
+ ],
1257
+ limitPrice: 0,
1258
+ orderType: "MARKET",
1259
+ quantity: -pos.quantity,
1260
+ timeInForce: "GTC"
1261
+ };
1262
+ await this._sendCloseRequest(closeData);
1263
+ }
1264
+ }
1265
+ /** Close a position by its position code. Returns the position with P&L metrics. Optionally wait for close confirmation via `waitForClose: "stream" | "poll"`. */
1266
+ async close(positionCode, options) {
1267
+ const positions = await this.get();
1268
+ const position = positions.find((p) => p.positionKey.positionCode === positionCode);
1269
+ if (!position) {
1270
+ this._ctx.throwError("POSITION_NOT_FOUND" /* POSITION_NOT_FOUND */, `Position with code "${positionCode}" not found`);
1271
+ }
1071
1272
  const closeData = {
1072
1273
  legs: [
1073
1274
  {
1074
- instrumentId: pos.positionKey.instrumentId,
1075
- positionCode: pos.positionKey.positionCode,
1275
+ instrumentId: position.positionKey.instrumentId,
1276
+ positionCode: position.positionKey.positionCode,
1076
1277
  positionEffect: "CLOSING",
1077
1278
  ratioQuantity: 1,
1078
- symbol: pos.positionKey.positionCode
1279
+ symbol: position.positionKey.positionCode
1079
1280
  }
1080
1281
  ],
1081
1282
  limitPrice: 0,
1082
1283
  orderType: "MARKET",
1083
- quantity: -pos.quantity,
1284
+ quantity: -position.quantity,
1084
1285
  timeInForce: "GTC"
1085
1286
  };
1086
- await closePosition(ctx, closeData);
1287
+ if (options?.waitForClose === "stream") {
1288
+ return this._waitForCloseStream(positionCode, position, closeData, options.timeout ?? 3e4);
1289
+ }
1290
+ await this._sendCloseRequest(closeData);
1291
+ if (options?.waitForClose === "poll") {
1292
+ return this._waitForClosePoll(positionCode, position, options.timeout ?? 3e4, options.pollInterval ?? 1e3);
1293
+ }
1294
+ return position;
1295
+ }
1296
+ _waitForCloseStream(positionCode, lastSnapshot, closeData, timeout) {
1297
+ if (!this._ctx.wsManager) {
1298
+ this._ctx.throwError(
1299
+ "STREAM_REQUIRES_CONNECT" /* STREAM_REQUIRES_CONNECT */,
1300
+ 'waitForClose: "stream" requires a persistent WebSocket. Use connect() instead of auth(), or use "poll" mode.'
1301
+ );
1302
+ }
1303
+ return new Promise(async (resolve, reject) => {
1304
+ let settled = false;
1305
+ const result = lastSnapshot;
1306
+ const timer = setTimeout(() => {
1307
+ if (settled) return;
1308
+ settled = true;
1309
+ cleanup();
1310
+ reject(
1311
+ new DxtradeError("POSITION_CLOSE_TIMEOUT" /* POSITION_CLOSE_TIMEOUT */, `Position close confirmation timed out after ${timeout}ms`)
1312
+ );
1313
+ }, timeout);
1314
+ function done(err, res) {
1315
+ if (settled) return;
1316
+ settled = true;
1317
+ clearTimeout(timer);
1318
+ cleanup();
1319
+ if (err) reject(err);
1320
+ else resolve(res);
1321
+ }
1322
+ function onMessage(body) {
1323
+ const messages = body;
1324
+ const orderMsg = messages?.findLast?.(
1325
+ (m) => m.messageCategory === "TRADE_LOG" /* TRADE_LOG */ && m.messageType === "ORDER" /* ORDER */ && !m.historyMessage
1326
+ );
1327
+ if (!orderMsg) return;
1328
+ const params = orderMsg.parametersTO;
1329
+ if (params.positionCode !== positionCode) return;
1330
+ if (params.orderStatus === "REJECTED" /* REJECTED */) {
1331
+ done(
1332
+ new DxtradeError(
1333
+ "POSITION_CLOSE_ERROR" /* POSITION_CLOSE_ERROR */,
1334
+ `Close order rejected: ${params.rejectReason?.key ?? "Unknown reason"}`
1335
+ )
1336
+ );
1337
+ } else if (params.orderStatus === "FILLED" /* FILLED */) {
1338
+ done(null, result);
1339
+ }
1340
+ }
1341
+ function onOrders(body) {
1342
+ const orders = body;
1343
+ const order = orders?.[0];
1344
+ if (!order?.orderId) return;
1345
+ if (order.status === "REJECTED" /* REJECTED */) {
1346
+ done(
1347
+ new DxtradeError(
1348
+ "POSITION_CLOSE_ERROR" /* POSITION_CLOSE_ERROR */,
1349
+ `Close order rejected: ${order.statusDescription ?? "Unknown reason"}`
1350
+ )
1351
+ );
1352
+ } else if (order.status === "FILLED" /* FILLED */) {
1353
+ done(null, result);
1354
+ }
1355
+ }
1356
+ const wsManager = this._ctx.wsManager;
1357
+ function cleanup() {
1358
+ wsManager.removeListener("MESSAGE" /* MESSAGE */, onMessage);
1359
+ wsManager.removeListener("ORDERS" /* ORDERS */, onOrders);
1360
+ }
1361
+ wsManager.on("MESSAGE" /* MESSAGE */, onMessage);
1362
+ wsManager.on("ORDERS" /* ORDERS */, onOrders);
1363
+ try {
1364
+ await this._sendCloseRequest(closeData);
1365
+ } catch (error) {
1366
+ done(error instanceof Error ? error : new Error(String(error)));
1367
+ }
1368
+ });
1087
1369
  }
1088
- }
1089
- async function closePosition(ctx, data) {
1090
- try {
1091
- await retryRequest(
1092
- {
1093
- method: "POST",
1094
- url: endpoints.closePosition(ctx.broker),
1095
- data,
1096
- headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
1097
- },
1098
- ctx.retries
1099
- );
1100
- } catch (error) {
1101
- if (error instanceof DxtradeError) throw error;
1102
- const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
1103
- ctx.throwError("POSITION_CLOSE_ERROR" /* POSITION_CLOSE_ERROR */, `Position close error: ${message}`);
1370
+ async _waitForClosePoll(positionCode, lastSnapshot, timeout, interval) {
1371
+ const deadline = Date.now() + timeout;
1372
+ let result = lastSnapshot;
1373
+ while (Date.now() < deadline) {
1374
+ await new Promise((r) => setTimeout(r, interval));
1375
+ const positions = await this.get();
1376
+ const match = positions.find((p) => p.positionKey.positionCode === positionCode);
1377
+ if (match) {
1378
+ result = match;
1379
+ } else {
1380
+ return result;
1381
+ }
1382
+ }
1383
+ this._ctx.throwError("POSITION_CLOSE_TIMEOUT" /* POSITION_CLOSE_TIMEOUT */, `Position close confirmation timed out after ${timeout}ms`);
1104
1384
  }
1105
- }
1385
+ async _sendCloseRequest(data) {
1386
+ try {
1387
+ await retryRequest(
1388
+ {
1389
+ method: "POST",
1390
+ url: endpoints.closePosition(this._ctx.broker),
1391
+ data,
1392
+ headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
1393
+ },
1394
+ this._ctx.retries
1395
+ );
1396
+ } catch (error) {
1397
+ if (error instanceof DxtradeError) throw error;
1398
+ const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
1399
+ this._ctx.throwError("POSITION_CLOSE_ERROR" /* POSITION_CLOSE_ERROR */, `Position close error: ${message}`);
1400
+ }
1401
+ }
1402
+ };
1106
1403
 
1107
1404
  // src/domains/session/session.ts
1108
1405
  var import_ws8 = __toESM(require("ws"));
@@ -1130,247 +1427,161 @@ function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
1130
1427
  ws.on("error", (error) => {
1131
1428
  clearTimeout(timer);
1132
1429
  ws.close();
1430
+ checkWsRateLimit(error);
1133
1431
  reject(new Error(`[dxtrade-api] WebSocket handshake error: ${error.message}`));
1134
1432
  });
1135
1433
  });
1136
1434
  }
1137
- async function login(ctx) {
1138
- try {
1139
- const response = await retryRequest(
1140
- {
1141
- method: "POST",
1142
- url: endpoints.login(ctx.broker),
1143
- data: {
1144
- username: ctx.config.username,
1145
- password: ctx.config.password,
1146
- // TODO:: take a look at this below, domain nor vendor seems required. it works if i comment out both.
1147
- // however i still use it since i see brokers use it as well in the login endpoint.
1148
- // domain: ctx.config.broker,
1149
- vendor: ctx.config.broker
1150
- // END TODO::
1435
+ var SessionDomain = class {
1436
+ constructor(_ctx) {
1437
+ this._ctx = _ctx;
1438
+ }
1439
+ /** Authenticate with the broker using username and password. */
1440
+ async login() {
1441
+ try {
1442
+ const response = await retryRequest(
1443
+ {
1444
+ method: "POST",
1445
+ url: endpoints.login(this._ctx.broker),
1446
+ data: {
1447
+ username: this._ctx.config.username,
1448
+ password: this._ctx.config.password,
1449
+ // TODO:: take a look at this below, domain nor vendor seems required. it works if i comment out both.
1450
+ // however i still use it since i see brokers use it as well in the login endpoint.
1451
+ // domain: this._ctx.config.broker,
1452
+ vendor: this._ctx.config.broker
1453
+ // END TODO::
1454
+ },
1455
+ headers: {
1456
+ ...baseHeaders(),
1457
+ Origin: this._ctx.broker,
1458
+ Referer: this._ctx.broker + "/",
1459
+ Cookie: Cookies.serialize(this._ctx.cookies)
1460
+ }
1151
1461
  },
1152
- headers: { "Content-Type": "application/json" }
1153
- },
1154
- ctx.retries
1155
- );
1156
- if (response.status === 200) {
1462
+ this._ctx.retries
1463
+ );
1464
+ if (response.status === 200) {
1465
+ const setCookies = response.headers["set-cookie"] ?? [];
1466
+ const incoming = Cookies.parse(setCookies);
1467
+ this._ctx.cookies = Cookies.merge(this._ctx.cookies, incoming);
1468
+ this._ctx.callbacks.onLogin?.();
1469
+ } else {
1470
+ this._ctx.throwError("LOGIN_FAILED" /* LOGIN_FAILED */, `Login failed: ${response.status}`);
1471
+ }
1472
+ } catch (error) {
1473
+ if (error instanceof DxtradeError) throw error;
1474
+ const message = error instanceof Error ? error.message : "Unknown error";
1475
+ this._ctx.throwError("LOGIN_ERROR" /* LOGIN_ERROR */, `Login error: ${message}`);
1476
+ }
1477
+ }
1478
+ /** Fetch the CSRF token required for authenticated requests. */
1479
+ async fetchCsrf() {
1480
+ try {
1481
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
1482
+ const response = await retryRequest(
1483
+ {
1484
+ method: "GET",
1485
+ url: this._ctx.broker,
1486
+ headers: { ...cookieOnlyHeaders(cookieStr), Referer: this._ctx.broker }
1487
+ },
1488
+ this._ctx.retries
1489
+ );
1157
1490
  const setCookies = response.headers["set-cookie"] ?? [];
1158
1491
  const incoming = Cookies.parse(setCookies);
1159
- ctx.cookies = Cookies.merge(ctx.cookies, incoming);
1160
- ctx.callbacks.onLogin?.();
1161
- } else {
1162
- ctx.throwError("LOGIN_FAILED" /* LOGIN_FAILED */, `Login failed: ${response.status}`);
1492
+ this._ctx.cookies = Cookies.merge(this._ctx.cookies, incoming);
1493
+ const csrfMatch = response.data?.match(/name="csrf" content="([^"]+)"/);
1494
+ if (csrfMatch) {
1495
+ this._ctx.csrf = csrfMatch[1];
1496
+ } else {
1497
+ this._ctx.throwError("CSRF_NOT_FOUND" /* CSRF_NOT_FOUND */, "CSRF token not found");
1498
+ }
1499
+ } catch (error) {
1500
+ if (error instanceof DxtradeError) throw error;
1501
+ const message = error instanceof Error ? error.message : "Unknown error";
1502
+ this._ctx.throwError("CSRF_ERROR" /* CSRF_ERROR */, `CSRF fetch error: ${message}`);
1163
1503
  }
1164
- } catch (error) {
1165
- if (error instanceof DxtradeError) throw error;
1166
- const message = error instanceof Error ? error.message : "Unknown error";
1167
- ctx.throwError("LOGIN_ERROR" /* LOGIN_ERROR */, `Login error: ${message}`);
1168
1504
  }
1169
- }
1170
- async function fetchCsrf(ctx) {
1171
- try {
1172
- const cookieStr = Cookies.serialize(ctx.cookies);
1173
- const response = await retryRequest(
1174
- {
1175
- method: "GET",
1176
- url: ctx.broker,
1177
- headers: { ...cookieOnlyHeaders(cookieStr), Referer: ctx.broker }
1178
- },
1179
- ctx.retries
1180
- );
1181
- const csrfMatch = response.data?.match(/name="csrf" content="([^"]+)"/);
1182
- if (csrfMatch) {
1183
- ctx.csrf = csrfMatch[1];
1184
- } else {
1185
- ctx.throwError("CSRF_NOT_FOUND" /* CSRF_NOT_FOUND */, "CSRF token not found");
1505
+ /** Switch to a specific trading account by ID. */
1506
+ async switchAccount(accountId) {
1507
+ this._ctx.ensureSession();
1508
+ try {
1509
+ await retryRequest(
1510
+ {
1511
+ method: "POST",
1512
+ url: endpoints.switchAccount(this._ctx.broker, accountId),
1513
+ headers: authHeaders(this._ctx.csrf, Cookies.serialize(this._ctx.cookies))
1514
+ },
1515
+ this._ctx.retries
1516
+ );
1517
+ this._ctx.callbacks.onAccountSwitch?.(accountId);
1518
+ } catch (error) {
1519
+ if (error instanceof DxtradeError) throw error;
1520
+ const message = error instanceof Error ? error.message : "Unknown error";
1521
+ this._ctx.throwError("ACCOUNT_SWITCH_ERROR" /* ACCOUNT_SWITCH_ERROR */, `Error switching account: ${message}`);
1186
1522
  }
1187
- } catch (error) {
1188
- if (error instanceof DxtradeError) throw error;
1189
- const message = error instanceof Error ? error.message : "Unknown error";
1190
- ctx.throwError("CSRF_ERROR" /* CSRF_ERROR */, `CSRF fetch error: ${message}`);
1191
- }
1192
- }
1193
- async function switchAccount(ctx, accountId) {
1194
- ctx.ensureSession();
1195
- try {
1196
- await retryRequest(
1197
- {
1198
- method: "POST",
1199
- url: endpoints.switchAccount(ctx.broker, accountId),
1200
- headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
1201
- },
1202
- ctx.retries
1203
- );
1204
- ctx.callbacks.onAccountSwitch?.(accountId);
1205
- } catch (error) {
1206
- if (error instanceof DxtradeError) throw error;
1207
- const message = error instanceof Error ? error.message : "Unknown error";
1208
- ctx.throwError("ACCOUNT_SWITCH_ERROR" /* ACCOUNT_SWITCH_ERROR */, `Error switching account: ${message}`);
1209
- }
1210
- }
1211
- async function auth(ctx) {
1212
- await login(ctx);
1213
- await fetchCsrf(ctx);
1214
- if (ctx.debug) clearDebugLog();
1215
- const cookieStr = Cookies.serialize(ctx.cookies);
1216
- const handshake = await waitForHandshake(endpoints.websocket(ctx.broker), cookieStr, 3e4, ctx.debug);
1217
- ctx.atmosphereId = handshake.atmosphereId;
1218
- ctx.accountId = handshake.accountId;
1219
- if (ctx.config.accountId) {
1220
- await switchAccount(ctx, ctx.config.accountId);
1221
- const reconnect = await waitForHandshake(
1222
- endpoints.websocket(ctx.broker, ctx.atmosphereId),
1223
- Cookies.serialize(ctx.cookies),
1224
- 3e4,
1225
- ctx.debug
1226
- );
1227
- ctx.atmosphereId = reconnect.atmosphereId;
1228
- ctx.accountId = reconnect.accountId;
1229
- }
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
- }
1245
-
1246
- // src/client.ts
1247
- var PositionsDomain = class {
1248
- constructor(_ctx) {
1249
- this._ctx = _ctx;
1250
- }
1251
- /** Get all open positions with P&L metrics merged. */
1252
- get() {
1253
- return getPositions(this._ctx);
1254
- }
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);
1258
- }
1259
- /** Close all open positions with market orders. */
1260
- closeAll() {
1261
- return closeAllPositions(this._ctx);
1262
1523
  }
1263
- /** Stream real-time position updates with P&L metrics. Requires connect(). Returns unsubscribe function. */
1264
- stream(callback) {
1265
- return streamPositions(this._ctx, callback);
1266
- }
1267
- };
1268
- var OrdersDomain = class {
1269
- constructor(_ctx) {
1270
- this._ctx = _ctx;
1271
- }
1272
- /** Get all pending/open orders via WebSocket. */
1273
- get() {
1274
- return getOrders(this._ctx);
1275
- }
1276
- /**
1277
- * Submit a trading order and wait for WebSocket confirmation.
1278
- * Supports market, limit, and stop orders with optional stop loss and take profit.
1279
- */
1280
- submit(params) {
1281
- return submitOrder(this._ctx, params);
1282
- }
1283
- /** Cancel a single pending order by its order chain ID. */
1284
- cancel(orderChainId) {
1285
- return cancelOrder(this._ctx, orderChainId);
1286
- }
1287
- /** Cancel all pending orders. */
1288
- cancelAll() {
1289
- return cancelAllOrders(this._ctx);
1290
- }
1291
- };
1292
- var AccountDomain = class {
1293
- constructor(_ctx) {
1294
- this._ctx = _ctx;
1295
- }
1296
- /** Get account metrics including equity, balance, margin, and open P&L. */
1297
- metrics() {
1298
- return getAccountMetrics(this._ctx);
1299
- }
1300
- /**
1301
- * Fetch trade journal entries for a date range.
1302
- * @param params.from - Start timestamp (Unix ms)
1303
- * @param params.to - End timestamp (Unix ms)
1304
- */
1305
- tradeJournal(params) {
1306
- return getTradeJournal(this._ctx, params);
1307
- }
1308
- /**
1309
- * Fetch trade history for a date range.
1310
- * @param params.from - Start timestamp (Unix ms)
1311
- * @param params.to - End timestamp (Unix ms)
1312
- */
1313
- tradeHistory(params) {
1314
- return getTradeHistory(this._ctx, params);
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
- }
1338
- /** Get all available instruments, optionally filtered by partial match (e.g. `{ type: "FOREX" }`). */
1339
- get(params = {}) {
1340
- return getInstruments(this._ctx, params);
1341
- }
1342
- };
1343
- var OhlcDomain = class {
1344
- constructor(_ctx) {
1345
- this._ctx = _ctx;
1346
- }
1347
- /**
1348
- * Fetch OHLC price bars for a symbol.
1349
- * @param params.symbol - Instrument symbol (e.g. "EURUSD")
1350
- * @param params.resolution - Bar period in seconds (default: 60 = 1 min)
1351
- * @param params.range - Lookback window in seconds (default: 432000 = 5 days)
1352
- * @param params.maxBars - Maximum bars to return (default: 3500)
1353
- * @param params.priceField - "bid" or "ask" (default: "bid")
1354
- */
1355
- get(params) {
1356
- return getOHLC(this._ctx, params);
1524
+ /** Hit the broker page to collect Cloudflare cookies before making API calls. */
1525
+ async _preflight() {
1526
+ try {
1527
+ const response = await retryRequest(
1528
+ {
1529
+ method: "GET",
1530
+ url: this._ctx.broker,
1531
+ headers: { ...baseHeaders(), Referer: this._ctx.broker }
1532
+ },
1533
+ this._ctx.retries
1534
+ );
1535
+ const setCookies = response.headers["set-cookie"] ?? [];
1536
+ const incoming = Cookies.parse(setCookies);
1537
+ this._ctx.cookies = Cookies.merge(this._ctx.cookies, incoming);
1538
+ } catch {
1539
+ }
1357
1540
  }
1358
- /** Stream real-time OHLC bar updates. Requires connect(). Returns unsubscribe function. */
1359
- stream(params, callback) {
1360
- return streamOHLC(this._ctx, params, callback);
1541
+ /** Authenticate and establish a session: login, fetch CSRF, WebSocket handshake, and optional account switch. */
1542
+ async auth() {
1543
+ await this._preflight();
1544
+ await this.login();
1545
+ await this.fetchCsrf();
1546
+ if (this._ctx.debug) clearDebugLog();
1547
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
1548
+ const handshake = await waitForHandshake(endpoints.websocket(this._ctx.broker), cookieStr, 3e4, this._ctx.debug);
1549
+ this._ctx.atmosphereId = handshake.atmosphereId;
1550
+ this._ctx.accountId = handshake.accountId;
1551
+ if (this._ctx.config.accountId) {
1552
+ await this.switchAccount(this._ctx.config.accountId);
1553
+ const reconnect = await waitForHandshake(
1554
+ endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId),
1555
+ Cookies.serialize(this._ctx.cookies),
1556
+ 3e4,
1557
+ this._ctx.debug
1558
+ );
1559
+ this._ctx.atmosphereId = reconnect.atmosphereId;
1560
+ this._ctx.accountId = reconnect.accountId;
1561
+ }
1361
1562
  }
1362
- };
1363
- var AssessmentsDomain = class {
1364
- constructor(_ctx) {
1365
- this._ctx = _ctx;
1563
+ /** Connect to the broker with a persistent WebSocket: auth + persistent WS for data reuse and streaming. */
1564
+ async connect() {
1565
+ await this.auth();
1566
+ const wsManager = new WsManager();
1567
+ const wsUrl = endpoints.websocket(this._ctx.broker, this._ctx.atmosphereId);
1568
+ const cookieStr = Cookies.serialize(this._ctx.cookies);
1569
+ await wsManager.connect(wsUrl, cookieStr, this._ctx.debug);
1570
+ this._ctx.wsManager = wsManager;
1366
1571
  }
1367
- /** Fetch PnL assessments for an instrument within a date range. */
1368
- get(params) {
1369
- return getAssessments(this._ctx, params);
1572
+ /** Close the persistent WebSocket connection. */
1573
+ disconnect() {
1574
+ if (this._ctx.wsManager) {
1575
+ this._ctx.wsManager.close();
1576
+ this._ctx.wsManager = null;
1577
+ }
1370
1578
  }
1371
1579
  };
1580
+
1581
+ // src/client.ts
1372
1582
  var DxtradeClient = class {
1373
1583
  _ctx;
1584
+ _session;
1374
1585
  /** Position operations: get, close, metrics, streaming. */
1375
1586
  positions;
1376
1587
  /** Order operations: get, submit, cancel. */
@@ -1409,6 +1620,7 @@ var DxtradeClient = class {
1409
1620
  throw error;
1410
1621
  }
1411
1622
  };
1623
+ this._session = new SessionDomain(this._ctx);
1412
1624
  this.positions = new PositionsDomain(this._ctx);
1413
1625
  this.orders = new OrdersDomain(this._ctx);
1414
1626
  this.account = new AccountDomain(this._ctx);
@@ -1419,27 +1631,27 @@ var DxtradeClient = class {
1419
1631
  }
1420
1632
  /** Authenticate with the broker using username and password. */
1421
1633
  async login() {
1422
- return login(this._ctx);
1634
+ return this._session.login();
1423
1635
  }
1424
1636
  /** Fetch the CSRF token required for authenticated requests. */
1425
1637
  async fetchCsrf() {
1426
- return fetchCsrf(this._ctx);
1638
+ return this._session.fetchCsrf();
1427
1639
  }
1428
1640
  /** Switch to a specific trading account by ID. */
1429
1641
  async switchAccount(accountId) {
1430
- return switchAccount(this._ctx, accountId);
1642
+ return this._session.switchAccount(accountId);
1431
1643
  }
1432
1644
  /** Authenticate and establish a session: login, fetch CSRF, WebSocket handshake, and optional account switch. */
1433
1645
  async auth() {
1434
- return auth(this._ctx);
1646
+ return this._session.auth();
1435
1647
  }
1436
1648
  /** Connect to the broker with a persistent WebSocket: auth + persistent WS for data reuse and streaming. */
1437
1649
  async connect() {
1438
- return connect(this._ctx);
1650
+ return this._session.connect();
1439
1651
  }
1440
1652
  /** Close the persistent WebSocket connection. */
1441
1653
  disconnect() {
1442
- return disconnect(this._ctx);
1654
+ return this._session.disconnect();
1443
1655
  }
1444
1656
  };
1445
1657
  // Annotate the CommonJS export names for ESM import in node:
@@ -1449,6 +1661,9 @@ var DxtradeClient = class {
1449
1661
  DxtradeClient,
1450
1662
  DxtradeError,
1451
1663
  ERROR,
1664
+ MESSAGE_CATEGORY,
1665
+ MESSAGE_TYPE,
1666
+ ORDER_STATUS,
1452
1667
  ORDER_TYPE,
1453
1668
  SIDE,
1454
1669
  TIF,