@blackcube/xgate-sdk 0.23.0 → 0.25.0

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
@@ -9,6 +9,7 @@ import { Hyperliquid } from '@blackcube/hyperliquid-sdk';
9
9
  import { Lighter } from '@blackcube/lighter-sdk';
10
10
  import { Pacifica } from '@blackcube/pacifica-sdk';
11
11
  import { Paradex } from '@blackcube/paradex-sdk';
12
+ import { randomUUID } from 'crypto';
12
13
 
13
14
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
14
15
  var __decorateClass = (decorators, target, key, kind) => {
@@ -1074,6 +1075,13 @@ SpotWsCandlesService = __decorateClass([
1074
1075
  ], SpotWsCandlesService);
1075
1076
 
1076
1077
  // src/helpers/protection.ts
1078
+ function priceOf(cible, entry, direction, gagnant) {
1079
+ if (cible.price !== void 0) {
1080
+ return round8(cible.price);
1081
+ }
1082
+ const dir = (direction === "long" ? 1 : -1) * (gagnant === true ? 1 : -1);
1083
+ return round8(entry * (1 + dir * cible.pct));
1084
+ }
1077
1085
  var SLIPPAGE_DEFAUT = 5e-3;
1078
1086
  var SEUIL_CLOTURE = 0.8;
1079
1087
  function round8(value) {
@@ -1116,12 +1124,8 @@ function buildLevels(entry, direction, slPct, tpPcts) {
1116
1124
  function buildProtection(input) {
1117
1125
  const slippage = input.slippagePct ?? SLIPPAGE_DEFAUT;
1118
1126
  const long = input.direction === "long";
1119
- const { sl, tps: niveaux } = buildLevels(
1120
- input.entry,
1121
- input.direction,
1122
- input.slPct,
1123
- input.tps.map((tp) => tp.pct)
1124
- );
1127
+ const sl = priceOf(input.sl, input.entry, input.direction, false);
1128
+ const niveaux = input.tps.map((tp) => priceOf(tp, input.entry, input.direction, true));
1125
1129
  const borne = (trigger) => long === true ? trigger * (1 - slippage) : trigger * (1 + slippage);
1126
1130
  const sommeParts = input.tps.reduce((total, tp) => total + tp.part, 0);
1127
1131
  const clotureIntegrale = sommeParts > SEUIL_CLOTURE;
@@ -1167,7 +1171,8 @@ var ORDER_STATUSES = [
1167
1171
  ];
1168
1172
 
1169
1173
  // src/services/trading.service.ts
1170
- var VENUES_PROUVEES = ["pacifica" /* Pacifica */, "hyperliquid" /* Hyperliquid */];
1174
+ var VENUES_PROUVEES = ["pacifica" /* Pacifica */, "hyperliquid" /* Hyperliquid */, "aster" /* Aster */];
1175
+ var ASTER_TIMEOUT = "The request has timed out.";
1171
1176
  var VENUES_MOVE_STOP = ["hyperliquid" /* Hyperliquid */, "pacifica" /* Pacifica */, "aster" /* Aster */];
1172
1177
  var TradingService = class {
1173
1178
  logger = new Logger(TradingService.name);
@@ -1191,7 +1196,7 @@ var TradingService = class {
1191
1196
  entry: input.entry,
1192
1197
  direction: input.direction,
1193
1198
  size: input.size,
1194
- slPct: input.slPct,
1199
+ sl: input.sl,
1195
1200
  tps: input.tps,
1196
1201
  tickSize: input.tickSize,
1197
1202
  lotSize: input.lotSize,
@@ -1204,26 +1209,76 @@ var TradingService = class {
1204
1209
  `openWithProtection(${access.xex}) : cette venue n'expose pas l'ouverture prot\xE9g\xE9e atomique.`
1205
1210
  );
1206
1211
  }
1207
- const orders = await perp.createEntryWithProtection(
1208
- {
1209
- name: input.symbolXex,
1210
- side,
1211
- type: "limit",
1212
- size: String(input.size),
1213
- price: String(input.entry),
1214
- tif: input.tif ?? "ioc",
1215
- reduceOnly: false,
1216
- clientId: input.clientId
1217
- },
1218
- {
1219
- name: input.symbolXex,
1220
- side,
1221
- sl: protection.sl,
1222
- tps: protection.tps,
1223
- clientId: input.clientId
1212
+ const entry = {
1213
+ name: input.symbolXex,
1214
+ side,
1215
+ type: "limit",
1216
+ size: String(input.size),
1217
+ price: String(input.entry),
1218
+ tif: input.tif ?? "ioc",
1219
+ reduceOnly: false,
1220
+ clientId: input.clientId
1221
+ };
1222
+ const consigne = {
1223
+ name: input.symbolXex,
1224
+ side,
1225
+ sl: protection.sl,
1226
+ tps: protection.tps,
1227
+ clientId: input.clientId
1228
+ };
1229
+ try {
1230
+ const orders = await perp.createEntryWithProtection(entry, consigne);
1231
+ return orders.map((order) => this.toOrder(order, access.xex));
1232
+ } catch (error) {
1233
+ const message = error instanceof Error ? error.message : String(error);
1234
+ if (message.includes(ASTER_TIMEOUT) === false) {
1235
+ throw error;
1224
1236
  }
1237
+ this.logger.warn(
1238
+ `openWithProtection(${access.xex}) : la venue a coup\xE9 avant de r\xE9pondre. Statut inconnu \u2014 relecture de l'\xE9tat r\xE9el.`
1239
+ );
1240
+ return await this.etatApres(access, input.symbolXex);
1241
+ }
1242
+ }
1243
+ /**
1244
+ * CE QUE LA VENUE A RÉELLEMENT FAIT, après une réponse perdue.
1245
+ *
1246
+ * Un timeout ne dit pas « rien n'a été créé » : mesuré sur aster, deux exécutions identiques ont
1247
+ * produit deux sous-ensembles différents — une fois le stop et un take-profit, une fois l'entrée
1248
+ * seule. La seule vérité est au carnet, donc on va l'y chercher.
1249
+ *
1250
+ * **Lève si la position est nue** : une entrée remplie sans stop est exactement ce que ce service
1251
+ * existe pour empêcher, et le silence serait pire que l'erreur. L'appelant doit savoir qu'il a une
1252
+ * position à protéger, tout de suite.
1253
+ */
1254
+ async etatApres(access, symbolXex) {
1255
+ const perp = this.perpOf(access);
1256
+ const [ouverts, positions] = await Promise.all([
1257
+ perp.getOpens(),
1258
+ perp.getPositions({ name: symbolXex })
1259
+ ]);
1260
+ const surLaPaire = ouverts.filter((order) => order.name === symbolXex);
1261
+ const position = positions.find(
1262
+ (candidate) => candidate.name === symbolXex && Number(candidate.size) !== 0
1225
1263
  );
1226
- return orders.map((order) => this.toOrder(order, access.xex));
1264
+ const protege = surLaPaire.some((order) => order.reduceOnly === true);
1265
+ if (position !== void 0 && protege === false) {
1266
+ throw new Error(
1267
+ `openWithProtection(${access.xex}/${symbolXex}) : POSITION NUE \u2014 ${position.size} ouvert sans stop au carnet apr\xE8s une r\xE9ponse perdue. \xC0 prot\xE9ger imm\xE9diatement.`
1268
+ );
1269
+ }
1270
+ if (position === void 0 && surLaPaire.length > 0) {
1271
+ for (const orphelin of surLaPaire) {
1272
+ await perp.cancel({ name: symbolXex, id: orphelin.id }).catch(() => void 0);
1273
+ }
1274
+ throw new Error(
1275
+ `openWithProtection(${access.xex}/${symbolXex}) : r\xE9ponse perdue, lot incomplet \u2014 ${surLaPaire.length} ordre(s) orphelin(s) ANNUL\xC9(S). Rien n'est ouvert, rien ne tra\xEEne.`
1276
+ );
1277
+ }
1278
+ this.logger.log(
1279
+ `openWithProtection(${access.xex}/${symbolXex}) : ${surLaPaire.length} ordre(s) au carnet, position ${position === void 0 ? "absente" : position.size}.`
1280
+ );
1281
+ return surLaPaire.map((order) => this.toOrder(order, access.xex));
1227
1282
  }
1228
1283
  /** Les positions ouvertes d'un compte. */
1229
1284
  async positions(access, symbolXex) {
@@ -1365,6 +1420,193 @@ var TradingService = class {
1365
1420
  const last = exits[0];
1366
1421
  return last === void 0 ? null : last.price;
1367
1422
  }
1423
+ /**
1424
+ * L'HISTORIQUE DES ORDRES du compte — ce qui a été soumis, rempli, annulé ou expiré.
1425
+ *
1426
+ * À distinguer de {@link trades} : un ordre est une **intention**, une exécution est un **fait**.
1427
+ * Un ordre `filled` peut avoir été rempli en plusieurs fois, à des prix différents.
1428
+ *
1429
+ * Servi par les huit venues qui tradent.
1430
+ */
1431
+ async orderHistory(access, symbolXex) {
1432
+ const orders = await this.perpOf(access).getHistory(
1433
+ symbolXex === void 0 ? void 0 : { name: symbolXex }
1434
+ );
1435
+ return orders.map((order) => this.toOrder(order, access.xex));
1436
+ }
1437
+ /**
1438
+ * L'ÉTAT DU COMPTE — équité, disponible, marge immobilisée, marge de maintenance, PnL latent.
1439
+ *
1440
+ * Les venues rangent ces valeurs sous des noms qui n'ont rien à voir : `marginSummary.accountValue`
1441
+ * chez hyperliquid, `accountEquity` chez pacifica, `totalMarginBalance` chez aster — **mais elles
1442
+ * disent la même chose**. La forme native complète reste dans `xtras`, rien n'est perdu.
1443
+ *
1444
+ * **blofin ne l'expose pas** et lève ; les sept autres le servent.
1445
+ */
1446
+ async accountInfo(access) {
1447
+ const perp = this.perpOf(access);
1448
+ if (typeof perp.getAccountInfo !== "function") {
1449
+ throw new Error(
1450
+ `accountInfo(${access.xex}) : cette venue n'expose pas l'\xE9tat de son compte.`
1451
+ );
1452
+ }
1453
+ const brut = await perp.getAccountInfo();
1454
+ return this.toAccountState(brut, access.xex);
1455
+ }
1456
+ /**
1457
+ * RÈGLE LE LEVIER d'une paire, avant d'ouvrir.
1458
+ *
1459
+ * Le levier détermine la marge immobilisée, donc la taille tenable : le changer après l'ouverture
1460
+ * ne rejoue pas la position déjà prise. C'est un réglage préalable, pas un ajustement.
1461
+ *
1462
+ * **bullet ne l'expose pas** — son levier se fixe au compte, pas à la position — et lève.
1463
+ */
1464
+ async setLeverage(access, symbolXex, leverage) {
1465
+ const perp = this.perpOf(access);
1466
+ if (typeof perp.updateLeverage !== "function") {
1467
+ throw new Error(`setLeverage(${access.xex}) : cette venue ne r\xE8gle pas le levier par paire.`);
1468
+ }
1469
+ await perp.updateLeverage({ name: symbolXex, leverage });
1470
+ this.logger.log(`setLeverage(${access.xex}/${symbolXex}) : levier port\xE9 \xE0 ${leverage}`);
1471
+ }
1472
+ /**
1473
+ * CHOISIT LE MODE DE MARGE d'une paire : **isolée** ou **croisée**.
1474
+ *
1475
+ * En **croisée**, tout le solde du compte garantit la position : elle tient plus longtemps, mais
1476
+ * une liquidation emporte l'ensemble. En **isolée**, seule la marge affectée est en jeu : la perte
1477
+ * est bornée à ce montant, la liquidation arrive plus tôt.
1478
+ *
1479
+ * **À régler AVANT d'ouvrir.** Changer le mode d'une position vivante est refusé par la plupart
1480
+ * des venues, et là où c'est accepté, cela redistribue la garantie sous les pieds de la position.
1481
+ *
1482
+ * Servi par les trois venues de référence.
1483
+ */
1484
+ async setMarginMode(access, symbolXex, isolated) {
1485
+ const perp = this.perpOf(access);
1486
+ if (typeof perp.setMarginMode !== "function") {
1487
+ throw new Error(
1488
+ `setMarginMode(${access.xex}) : cette venue ne choisit pas son mode de marge.`
1489
+ );
1490
+ }
1491
+ await perp.setMarginMode({ name: symbolXex, isolated });
1492
+ this.logger.log(
1493
+ `setMarginMode(${access.xex}/${symbolXex}) : marge ${isolated === true ? "ISOL\xC9E" : "CROIS\xC9E"}`
1494
+ );
1495
+ }
1496
+ /**
1497
+ * AJOUTE DE LA MARGE à une position isolée — pour éloigner le prix de liquidation.
1498
+ *
1499
+ * C'est le geste qui sauve une position sous pression sans la réduire : on renforce la garantie
1500
+ * plutôt que de couper. **N'a de sens qu'en marge isolée** ; en croisée, tout le solde garantit
1501
+ * déjà la position et il n'y a rien à ajouter.
1502
+ *
1503
+ * Servi par les trois venues de référence.
1504
+ */
1505
+ async addMargin(access, symbolXex, amount) {
1506
+ const perp = this.perpOf(access);
1507
+ if (typeof perp.addIsolatedMargin !== "function") {
1508
+ throw new Error(`addMargin(${access.xex}) : cette venue n'ajoute pas de marge isol\xE9e.`);
1509
+ }
1510
+ await perp.addIsolatedMargin({ name: symbolXex, amount });
1511
+ this.logger.log(`addMargin(${access.xex}/${symbolXex}) : +${amount} de marge isol\xE9e`);
1512
+ }
1513
+ /**
1514
+ * RETIRE DE LA MARGE d'une position isolée — pour libérer du capital.
1515
+ *
1516
+ * L'inverse d'{@link addMargin} : la garantie diminue, donc le prix de liquidation **se rapproche**.
1517
+ * La venue refuse ce qui mettrait la position sous son seuil de maintenance.
1518
+ *
1519
+ * ⚠️ **pacifica ne l'expose pas** et lève : chez elle, la marge s'ajoute mais ne se retire pas.
1520
+ * Fermer partiellement la position libère alors le capital.
1521
+ */
1522
+ async removeMargin(access, symbolXex, amount) {
1523
+ const perp = this.perpOf(access);
1524
+ if (typeof perp.removeIsolatedMargin !== "function") {
1525
+ throw new Error(
1526
+ `removeMargin(${access.xex}) : cette venue ne retire pas de marge isol\xE9e \u2014 ferme partiellement la position pour lib\xE9rer du capital.`
1527
+ );
1528
+ }
1529
+ await perp.removeIsolatedMargin({ name: symbolXex, amount });
1530
+ this.logger.log(`removeMargin(${access.xex}/${symbolXex}) : \u2212${amount} de marge isol\xE9e`);
1531
+ }
1532
+ /**
1533
+ * L'HISTORIQUE DU FUNDING **PAYÉ** sur une paire — ce qui a réellement été prélevé ou reçu.
1534
+ *
1535
+ * À ne pas confondre avec le taux courant, que rend `PricesService` : celui-ci annonce ce qui
1536
+ * *sera* payé, celui-là dit ce qui *l'a été*. Un backtest qui ignore le funding réel surestime le
1537
+ * résultat d'une position tenue longtemps.
1538
+ *
1539
+ * Servi par les huit venues qui tradent.
1540
+ */
1541
+ async fundingHistory(access, symbolXex, range) {
1542
+ const perp = this.perpOf(access);
1543
+ if (typeof perp.getFundingHistory !== "function") {
1544
+ throw new Error(
1545
+ `fundingHistory(${access.xex}) : cette venue ne publie pas son historique de funding.`
1546
+ );
1547
+ }
1548
+ const rates = await perp.getFundingHistory({
1549
+ name: symbolXex,
1550
+ startTime: range?.startTime === void 0 ? void 0 : toXexDate(range.startTime),
1551
+ endTime: range?.endTime === void 0 ? void 0 : toXexDate(range.endTime),
1552
+ limit: range?.limit
1553
+ });
1554
+ return rates.map((rate) => ({
1555
+ xex: access.xex,
1556
+ symbolXex: rate.name,
1557
+ rate: rate.rate,
1558
+ appliedAt: rate.appliedAt,
1559
+ xtras: rate.xtras
1560
+ }));
1561
+ }
1562
+ /**
1563
+ * UN IDENTIFIANT APPLICATIF pour un ordre, **au format que la venue accepte**.
1564
+ *
1565
+ * C'est lui qui relie un ordre à la décision qui l'a produit : la venue le rend tel quel dans
1566
+ * `IOrder.clientId`, ce qui permet de retrouver son origine sans tenir de table de correspondance.
1567
+ *
1568
+ * **hyperliquid exige un hexadécimal préfixé `0x`** (128 bits) là où les autres acceptent un UUID
1569
+ * ordinaire. Passer le mauvais format fait rejeter l'ordre — d'où cette méthode plutôt qu'un
1570
+ * `randomUUID()` chez l'appelant.
1571
+ */
1572
+ newClientOrderId(xex) {
1573
+ const uuid = randomUUID();
1574
+ return xex === "hyperliquid" /* Hyperliquid */ ? `0x${uuid.replace(/-/gu, "")}` : uuid;
1575
+ }
1576
+ /**
1577
+ * LE COUPE-CIRCUIT : annule TOUS les ordres d'une paire, d'un geste.
1578
+ *
1579
+ * À réserver aux situations où l'on veut reprendre la main sans discuter — un état incohérent, un
1580
+ * arrêt d'urgence, une reprise après incident. Annuler un par un laisse une fenêtre pendant
1581
+ * laquelle certains ordres vivent encore.
1582
+ *
1583
+ * ⚠️ **Cela retire aussi les PROTECTIONS.** Stops et take-profits sont des ordres comme les
1584
+ * autres : une position ouverte se retrouve **nue** après ce geste. À n'employer que si la
1585
+ * position est fermée, ou si l'on repose une protection immédiatement — jamais pour « faire le
1586
+ * ménage » sur une position vivante.
1587
+ *
1588
+ * Rend le nombre d'ordres annulés quand la venue le publie, `null` sinon.
1589
+ */
1590
+ async cancelAll(access, symbolXex) {
1591
+ const perp = this.perpOf(access);
1592
+ if (typeof perp.cancelAll !== "function") {
1593
+ throw new Error(`cancelAll(${access.xex}) : cette venue n'annule pas en masse.`);
1594
+ }
1595
+ const positions = await perp.getPositions({ name: symbolXex });
1596
+ const ouverte = positions.find(
1597
+ (candidate) => candidate.name === symbolXex && Number(candidate.size) !== 0
1598
+ );
1599
+ if (ouverte !== void 0) {
1600
+ this.logger.warn(
1601
+ `cancelAll(${access.xex}/${symbolXex}) : ${ouverte.size} EN POSITION \u2014 ses protections partent avec. La position sera NUE.`
1602
+ );
1603
+ }
1604
+ const { cancelled } = await perp.cancelAll({ name: symbolXex });
1605
+ this.logger.log(
1606
+ `cancelAll(${access.xex}/${symbolXex}) : ${cancelled ?? "?"} ordre(s) annul\xE9(s)`
1607
+ );
1608
+ return cancelled;
1609
+ }
1368
1610
  /** Annule un ordre par son identifiant de venue. */
1369
1611
  async cancel(access, symbolXex, orderId) {
1370
1612
  await this.perpOf(access).cancel({ name: symbolXex, id: orderId });
@@ -1374,6 +1616,27 @@ var TradingService = class {
1374
1616
  const source = createAccountXex(access.xex, access);
1375
1617
  return source.perp();
1376
1618
  }
1619
+ /**
1620
+ * L'état natif d'une venue → {@link IAccountState}.
1621
+ *
1622
+ * Une lecture EXPLICITE par venue plutôt qu'un parcours à l'aveugle : les noms ne se devinent pas,
1623
+ * et prendre le premier champ qui ressemble à une équité produirait un chiffre faux sans que rien
1624
+ * ne le signale. `null` là où la venue ne publie pas la valeur — pas `0`, qui se lirait comme
1625
+ * « rien » alors que personne n'a rien dit.
1626
+ */
1627
+ toAccountState(brut, xex) {
1628
+ const texte = (valeur) => valeur === void 0 || valeur === null ? null : String(valeur);
1629
+ const marge = brut.marginSummary;
1630
+ return {
1631
+ xex,
1632
+ equity: texte(marge?.accountValue) ?? texte(brut.accountEquity) ?? texte(brut.totalMarginBalance) ?? "0",
1633
+ available: texte(brut.withdrawable) ?? texte(brut.availableToSpend) ?? texte(brut.availableBalance),
1634
+ marginUsed: texte(marge?.totalMarginUsed) ?? texte(brut.totalMarginUsed) ?? texte(brut.totalInitialMargin),
1635
+ maintenanceMargin: texte(brut.crossMaintenanceMarginUsed) ?? texte(brut.crossMmr) ?? texte(brut.totalMaintMargin),
1636
+ unrealizedPnl: texte(brut.totalUnrealizedProfit),
1637
+ xtras: brut
1638
+ };
1639
+ }
1377
1640
  /**
1378
1641
  * Le statut de la venue, VALIDÉ — jamais casté. Une venue qui inventerait un statut rendrait sinon
1379
1642
  * une valeur absente de tous les `switch` en aval, et personne ne la rattraperait.
@@ -1404,6 +1667,9 @@ var TradingService = class {
1404
1667
  TradingService = __decorateClass([
1405
1668
  Injectable()
1406
1669
  ], TradingService);
1670
+ function toXexDate(date) {
1671
+ return date.toISOString().slice(0, 19).replace("T", " ");
1672
+ }
1407
1673
  var WalletService = class {
1408
1674
  logger = new Logger(WalletService.name);
1409
1675
  /** Les venues dont le portefeuille est lisible aujourd'hui. */
@@ -1607,6 +1873,176 @@ var WsCandlesService = class {
1607
1873
  WsCandlesService = __decorateClass([
1608
1874
  Injectable()
1609
1875
  ], WsCandlesService);
1876
+ var WsTradesService = class {
1877
+ logger = new Logger(WsTradesService.name);
1878
+ /**
1879
+ * S'abonne aux exécutions d'un compte. Rend la fonction de désabonnement.
1880
+ *
1881
+ * Le handler reçoit **une exécution à la fois**, au format unifié, et **uniquement celles
1882
+ * survenues après l'abonnement** — le rejeu d'historique de la venue est écarté.
1883
+ */
1884
+ subscribe(access, handler) {
1885
+ const source = createAccountXex(access.xex, access);
1886
+ if (typeof source.ws !== "function") {
1887
+ throw new Error(`userTrades(${access.xex}) : cette venue n'expose pas de temps r\xE9el.`);
1888
+ }
1889
+ const ws = source.ws();
1890
+ if (typeof ws.subscribeUserTrades !== "function") {
1891
+ throw new Error(
1892
+ `userTrades(${access.xex}) : cette venue ne diffuse pas les ex\xE9cutions du compte.`
1893
+ );
1894
+ }
1895
+ const depuis = Date.now();
1896
+ let ecartees = 0;
1897
+ this.logger.log(`souscription ex\xE9cutions sur ${access.xex}`);
1898
+ return ws.subscribeUserTrades((trade) => {
1899
+ if (trade.filledAt.getTime() < depuis) {
1900
+ ecartees += 1;
1901
+ if (ecartees === 1) {
1902
+ this.logger.log(`${access.xex} : rejeu d'historique \xE9cart\xE9 (ant\xE9rieur \xE0 l'abonnement).`);
1903
+ }
1904
+ return;
1905
+ }
1906
+ handler(this.toTrade(trade, access.xex));
1907
+ });
1908
+ }
1909
+ /**
1910
+ * S'abonne aux exécutions de PLUSIEURS comptes, avec un seul handler.
1911
+ *
1912
+ * Chaque exécution porte son `xex` : l'appelant sait toujours de quelle venue elle vient. La
1913
+ * fonction rendue coupe tous les flux d'un coup — sinon il faudrait en garder N et n'en oublier
1914
+ * aucun.
1915
+ *
1916
+ * Une venue qui refuse est journalisée et ignorée : le reste des comptes doit continuer d'être
1917
+ * suivi. Sur un seul accès, l'échec est celui de l'appel.
1918
+ */
1919
+ subscribeAll(accesses, handler) {
1920
+ if (accesses.length === 0) {
1921
+ throw new Error("userTrades() : aucun compte demand\xE9 \u2014 pr\xE9cise au moins un acc\xE8s.");
1922
+ }
1923
+ const solo = accesses.length === 1;
1924
+ const stops = [];
1925
+ for (const access of accesses) {
1926
+ try {
1927
+ stops.push(this.subscribe(access, handler));
1928
+ } catch (error) {
1929
+ if (solo === true) {
1930
+ throw error;
1931
+ }
1932
+ const message = error instanceof Error ? error.message : String(error);
1933
+ this.logger.error(`souscription ${access.xex} refus\xE9e, venue ignor\xE9e : ${message}`);
1934
+ }
1935
+ }
1936
+ return () => {
1937
+ for (const stop of stops) {
1938
+ try {
1939
+ stop();
1940
+ } catch {
1941
+ }
1942
+ }
1943
+ };
1944
+ }
1945
+ /**
1946
+ * S'abonne aux ORDRES du compte — leur naissance, leur remplissage, leur mort.
1947
+ *
1948
+ * À distinguer des exécutions : un ordre est une **intention** dont on suit le cycle de vie
1949
+ * (`open` → `partiallyFilled` → `filled`, ou `canceled`), une exécution est un **fait** ponctuel.
1950
+ * Pour savoir qu'un stop vient de se déclencher, c'est ici qu'il faut écouter ; pour savoir à quel
1951
+ * prix, c'est {@link subscribe}.
1952
+ *
1953
+ * **Même règle que pour les exécutions** : seuls les ordres postérieurs à l'abonnement sont
1954
+ * transmis, le rejeu d'historique est écarté.
1955
+ */
1956
+ subscribeOrders(access, handler) {
1957
+ const ws = this.wsOf(access);
1958
+ if (typeof ws.subscribeOrders !== "function") {
1959
+ throw new Error(`orders(${access.xex}) : cette venue ne diffuse pas ses ordres.`);
1960
+ }
1961
+ const depuis = Date.now();
1962
+ this.logger.log(`souscription ordres sur ${access.xex}`);
1963
+ return ws.subscribeOrders((order) => {
1964
+ if (order.placedAt.getTime() >= depuis) {
1965
+ handler(this.toOrder(order, access.xex));
1966
+ }
1967
+ });
1968
+ }
1969
+ /**
1970
+ * S'abonne aux TRANSACTIONS PUBLIQUES d'un marché — celles de tout le monde, pas les siennes.
1971
+ *
1972
+ * C'est le flux qui dit ce qui se négocie réellement : prix, taille, sens agresseur. Il sert à
1973
+ * mesurer l'activité d'une paire, pas à suivre son compte — pour cela, {@link subscribe}.
1974
+ *
1975
+ * **Aucun filtre temporel ici** : un trade public est daté de son exécution et arrive en direct,
1976
+ * il n'y a pas d'historique rejoué à écarter.
1977
+ */
1978
+ subscribePublicTrades(access, symbolXex, handler) {
1979
+ const ws = this.wsOf(access);
1980
+ if (typeof ws.subscribeTrades !== "function") {
1981
+ throw new Error(`publicTrades(${access.xex}) : cette venue ne diffuse pas les transactions.`);
1982
+ }
1983
+ this.logger.log(`souscription transactions ${symbolXex} sur ${access.xex}`);
1984
+ return ws.subscribeTrades({ name: symbolXex }, (trade) => {
1985
+ handler({
1986
+ xex: access.xex,
1987
+ symbolXex: trade.name,
1988
+ price: trade.price,
1989
+ size: trade.size,
1990
+ side: trade.side,
1991
+ tradedAt: trade.tradedAt,
1992
+ xtras: trade.xtras
1993
+ });
1994
+ });
1995
+ }
1996
+ /** Le client temps réel d'un compte, avec ses souscriptions signées. */
1997
+ wsOf(access) {
1998
+ const source = createAccountXex(access.xex, access);
1999
+ if (typeof source.ws !== "function") {
2000
+ throw new Error(`${access.xex} : cette venue n'expose pas de temps r\xE9el.`);
2001
+ }
2002
+ return source.ws();
2003
+ }
2004
+ toOrder(order, xex) {
2005
+ return {
2006
+ xex,
2007
+ symbolXex: order.name,
2008
+ kind: order.kind,
2009
+ id: order.id,
2010
+ clientId: order.clientId,
2011
+ side: order.side,
2012
+ type: order.type,
2013
+ price: order.price,
2014
+ size: order.size,
2015
+ filled: order.filled,
2016
+ status: ORDER_STATUSES.includes(order.status) ? order.status : "other",
2017
+ tif: order.tif,
2018
+ reduceOnly: order.reduceOnly,
2019
+ placedAt: order.placedAt,
2020
+ xtras: order.xtras
2021
+ };
2022
+ }
2023
+ toTrade(trade, xex) {
2024
+ return {
2025
+ xex,
2026
+ symbolXex: trade.name,
2027
+ kind: trade.kind,
2028
+ id: trade.id,
2029
+ orderId: trade.orderId,
2030
+ side: trade.side,
2031
+ price: trade.price,
2032
+ size: trade.size,
2033
+ fee: trade.fee,
2034
+ feeAsset: trade.feeAsset,
2035
+ grossPnl: trade.grossPnl,
2036
+ netPnl: trade.netPnl,
2037
+ maker: trade.maker,
2038
+ filledAt: trade.filledAt,
2039
+ xtras: trade.xtras
2040
+ };
2041
+ }
2042
+ };
2043
+ WsTradesService = __decorateClass([
2044
+ Injectable()
2045
+ ], WsTradesService);
1610
2046
 
1611
2047
  // src/xgate.module.ts
1612
2048
  var XgateModule = class {
@@ -1618,6 +2054,7 @@ XgateModule = __decorateClass([
1618
2054
  CandlesService,
1619
2055
  CandlesStreamRegistry,
1620
2056
  WsCandlesService,
2057
+ WsTradesService,
1621
2058
  PricesService,
1622
2059
  SpotCatalogService,
1623
2060
  SpotCandlesService,
@@ -1630,6 +2067,7 @@ XgateModule = __decorateClass([
1630
2067
  CandlesService,
1631
2068
  CandlesStreamRegistry,
1632
2069
  WsCandlesService,
2070
+ WsTradesService,
1633
2071
  PricesService,
1634
2072
  SpotCatalogService,
1635
2073
  SpotCandlesService,
@@ -1640,6 +2078,6 @@ XgateModule = __decorateClass([
1640
2078
  })
1641
2079
  ], XgateModule);
1642
2080
 
1643
- export { CandlesService, CandlesStreamRegistry, CandlesStreamService, CatalogService, ORDER_STATUSES, PricesService, SPOT_XEXES, SpotCandlesService, SpotCatalogService, SpotPricesService, SpotWsCandlesService, TIMEFRAME_MINUTES, Timeframe, TradingService, WALLET_XEXES, WS_SUBSCRIPTION_LIMITS, WalletService, WsCandlesService, XGATE_EXCHANGES, XgateEx, XgateModule, buildLevels, buildProtection, canonicalFromBase, canonicalFromXex, expiryOf, isDated, multiplierFromXex, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, subscriptionLimitOf, timeframeMs };
2081
+ export { CandlesService, CandlesStreamRegistry, CandlesStreamService, CatalogService, ORDER_STATUSES, PricesService, SPOT_XEXES, SpotCandlesService, SpotCatalogService, SpotPricesService, SpotWsCandlesService, TIMEFRAME_MINUTES, Timeframe, TradingService, WALLET_XEXES, WS_SUBSCRIPTION_LIMITS, WalletService, WsCandlesService, WsTradesService, XGATE_EXCHANGES, XgateEx, XgateModule, buildLevels, buildProtection, canonicalFromBase, canonicalFromXex, expiryOf, isDated, multiplierFromXex, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, subscriptionLimitOf, timeframeMs };
1644
2082
  //# sourceMappingURL=index.js.map
1645
2083
  //# sourceMappingURL=index.js.map