@blackcube/xgate-sdk 0.21.0 → 0.22.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.d.cts CHANGED
@@ -68,6 +68,23 @@ interface IPair {
68
68
  xex: XgateEx;
69
69
  /** Type de marché (`perp`/`spot`). */
70
70
  kind: MarketKind;
71
+ /**
72
+ * FACTEUR D'ÉCHELLE DE COTATION — `1000` pour `kPEPE`, `1000000` pour `1MBABYDOGE`, `1` sinon.
73
+ *
74
+ * **Aucune venue ne le publie** : l'information n'existe que dans le préfixe du nom, et le
75
+ * canonique la fait disparaître. Sans elle, comparer des prix entre venues est faux — `SATS`
76
+ * porte trois échelles sur quatre sources (×1, ×1000, ×10000). Diviser le prix par ce facteur
77
+ * ramène toutes les venues à la même unité.
78
+ */
79
+ multiplier: number;
80
+ /**
81
+ * ÉCHÉANCE du contrat. `undefined` pour un perpétuel — l'immense majorité.
82
+ *
83
+ * Un future daté ne cote pas le comptant : sa base peut s'écarter de plusieurs pour cent. Neuf
84
+ * lignes BTC de bybit sont dans ce cas. Dérivée du **type de contrat**, jamais de la date de
85
+ * livraison : binance publie l'an 2100 sur ses perpétuels.
86
+ */
87
+ expiresAt?: Date;
71
88
  /**
72
89
  * L'identifiant TECHNIQUE que la venue exige pour agir, quand il diffère du symbole (index de
73
90
  * marché chez lighter, index spot chez hyperliquid). `null` quand le symbole suffit.
@@ -407,6 +424,45 @@ declare class SpotCatalogService {
407
424
  private catalogOf;
408
425
  }
409
426
 
427
+ /**
428
+ * LES BOUGIES DU **COMPTANT**, en REST — binance et bybit.
429
+ *
430
+ * **Un service à part, et non un drapeau sur {@link CandlesService}.** C'est la même règle que pour
431
+ * le catalogue : un `candles()` qui servirait tantôt du perpétuel tantôt du comptant selon un
432
+ * paramètre produirait des erreurs silencieuses — on croirait lire des perpétuels et on lirait du
433
+ * comptant, sans que rien ne le signale. Deux marchés, deux services ; l'appelant choisit
434
+ * explicitement ce qu'il interroge.
435
+ *
436
+ * La différence n'est pas cosmétique : le perpétuel porte un funding et peut s'écarter du comptant,
437
+ * et **les échelles de cotation diffèrent d'un marché à l'autre** — `SATSUSDT` au comptant chez
438
+ * bybit cote ×1 quand son perpétuel cote ×10000. Mélanger les deux séries fabriquerait un graphe
439
+ * qui saute d'un facteur 10 000.
440
+ *
441
+ * **Aucune agrégation ici**, contrairement au perpétuel : binance et bybit servent nativement tous
442
+ * les intervalles de `1m` à `1w`. Le jour où une venue comptant en manquerait un, ce serait à
443
+ * ajouter — pas à supposer.
444
+ */
445
+ declare class SpotCandlesService {
446
+ private readonly logger;
447
+ /** Les venues dont XGate sert le comptant. */
448
+ venues(): readonly XgateEx[];
449
+ /**
450
+ * Les bougies comptant d'une venue, sur une plage de **dates**.
451
+ *
452
+ * **Lève** si la venue ne sert pas de comptant, plutôt que de rendre une liste vide : « cette
453
+ * venue n'a pas de marché comptant » et « ce marché n'a pas coté » appellent des suites très
454
+ * différentes.
455
+ */
456
+ candles(xex: XgateEx, query: ICandlesQuery): Promise<ICandle[]>;
457
+ /**
458
+ * Les bougies comptant d'un même marché chez PLUSIEURS venues, en une liste.
459
+ *
460
+ * Même politique d'échec que partout ailleurs : seule, une venue en échec fait échouer l'appel ;
461
+ * parmi d'autres, elle est journalisée et ignorée. Chaque bougie porte son `xex`.
462
+ */
463
+ candlesOf(xexes: XgateEx[], query: ICandlesQuery): Promise<ICandle[]>;
464
+ }
465
+
410
466
  /** Une paire, telle qu'un SDK de venue la rend. */
411
467
  interface IXexPair {
412
468
  name: string;
@@ -535,7 +591,7 @@ interface IXexAccess {
535
591
  * Les venues dont XGate sait lire le portefeuille aujourd'hui.
536
592
  *
537
593
  * Les autres ne sont pas exclues par principe : il leur manque un accès (clé d'API pour bullet, une
538
- * adresse active pour lighter, extended et paradex). Voir le backlog.
594
+ * adresse active pour extended et paradex). Voir le backlog.
539
595
  */
540
596
  declare const WALLET_XEXES: readonly XgateEx[];
541
597
  /**
@@ -660,6 +716,22 @@ declare class WalletService {
660
716
  * regarde autrement qu'une panne.
661
717
  */
662
718
  balances(...accesses: IWalletAccess[]): Promise<IBalance[]>;
719
+ /**
720
+ * LE COLLATÉRAL PERPÉTUEL, ajouté aux soldes comptant — sans lui, un portefeuille peut paraître VIDE.
721
+ *
722
+ * `getBalances` ne rend que le **comptant**, chez les huit venues : c'est leur convention, et la
723
+ * documentation de chacune le dit. Or l'argent qui sert à trader n'y est pas. Sur un compte
724
+ * pacifica dont tout le solde est en marge, `getBalances` rend `[]` — ce qui se lit « pas
725
+ * d'argent » alors qu'il y a 499 USDC. Sur hyperliquid, le comptant s'affiche mais la marge
726
+ * manque.
727
+ *
728
+ * La ligne est **marquée `scope: 'perp'`** dans `xtras` : elle s'additionne au comptant sans le
729
+ * doubler, et un appelant qui ne veut que l'un des deux peut trancher.
730
+ *
731
+ * Le champ diffère par venue et n'est pas devinable — d'où la lecture explicite, venue par venue,
732
+ * plutôt qu'un parcours à l'aveugle d'un objet `unknown`.
733
+ */
734
+ private perpCollateral;
663
735
  /**
664
736
  * Les mouvements d'argent, triés du plus ancien au plus récent, montants **signés**.
665
737
  *
@@ -1088,5 +1160,48 @@ declare function canonicalFromXex(rawSymbol: string, xexId: string): string;
1088
1160
  * symbole.
1089
1161
  */
1090
1162
  declare function canonicalFromBase(base: string): string;
1163
+ /**
1164
+ * LE MULTIPLICATEUR DE COTATION, extrait du nom — **la seule source qui existe**.
1165
+ *
1166
+ * Aucune venue ne le publie : `kPEPE` chez hyperliquid ne porte que `{"marginTableId":52}`,
1167
+ * `1000PEPEUSDT` chez binance ne mentionne aucun facteur. L'information n'est QUE dans le préfixe
1168
+ * du nom, et le canonique la fait disparaître — jusqu'ici sans la conserver nulle part.
1169
+ *
1170
+ * Or elle est indispensable dès qu'on compare des prix entre venues. `SATS` porte **trois échelles
1171
+ * différentes sur quatre sources** : `SATSUSDT` (×1) chez bybit-spot, `1000SATSUSDT` (×1000) chez
1172
+ * binance, `10000SATSUSDT` (×10000) chez bybit. Une médiane calculée sur ces prix bruts ne veut
1173
+ * rien dire ; divisés par leur facteur, ils redeviennent comparables.
1174
+ *
1175
+ * Rend `1` quand le nom ne porte aucune échelle — le cas de l'immense majorité.
1176
+ */
1177
+ declare function multiplierFromXex(rawSymbol: string): number;
1178
+
1179
+ /**
1180
+ * LES CONTRATS À ÉCHÉANCE, séparés des perpétuels — parce qu'un future daté ne cote pas le comptant.
1181
+ *
1182
+ * Le catalogue les mélangeait : 44 lignes portaient `kind: 'perp'` alors qu'elles expirent, dont
1183
+ * neuf sur le seul BTC chez bybit. Un consommateur qui prend « le prix du BTC » y ramassait une
1184
+ * échéance de décembre, dont la base au comptant peut s'écarter de plusieurs pour cent.
1185
+ *
1186
+ * **`contractType` fait foi, JAMAIS la date de livraison.** binance publie `deliveryDate:
1187
+ * 4133404800000` — le 1er janvier 2100 — sur ses **perpétuels** : s'y fier marquerait tout le
1188
+ * catalogue comme daté. bybit, lui, met `deliveryTime: '0'` sur les siens. Deux conventions
1189
+ * incompatibles pour dire « ceci n'expire pas », d'où la lecture du type et de lui seul.
1190
+ */
1191
+ /**
1192
+ * L'ÉCHÉANCE D'UN CONTRAT, ou `undefined` s'il est perpétuel.
1193
+ *
1194
+ * On ne lit la date **que** si le type de contrat annonce une échéance : c'est ce qui neutralise le
1195
+ * `deliveryDate` de l'an 2100 des perpétuels binance. Un type inconnu est traité comme perpétuel —
1196
+ * l'immense majorité des lignes, et se tromper dans ce sens n'invente pas d'échéance.
1197
+ */
1198
+ declare function expiryOf(xtras?: Record<string, unknown>): Date | undefined;
1199
+ /**
1200
+ * LE CONTRAT EXPIRE-T-IL ? Vrai pour un future daté, faux pour un perpétuel.
1201
+ *
1202
+ * Séparé d'{@link expiryOf} à dessein : une venue peut annoncer un type daté sans publier de date
1203
+ * exploitable, et il faut alors pouvoir écarter la ligne quand même.
1204
+ */
1205
+ declare function isDated(xtras?: Record<string, unknown>): boolean;
1091
1206
 
1092
- export { CandlesService, CatalogService, type Direction, type IBalance, type ICandle, type ICandlesQuery, type IEntryWithProtection, type IEquityPoint, type IMoveStop, type IMovement, type IOrder, type IPair, type IPosition, type IPrice, type IProtection, type IProtectionInput, type IProtectionLeg, type ITrade, type ITradingAccess, type IWalletAccess, type IXexAccess, type IXexSpotSource, type MarketKind, type MovementKind, type MovementStatus, ORDER_STATUSES, type OrderStatus, type OrderType, PricesService, SPOT_XEXES, type Side, SpotCatalogService, TIMEFRAME_MINUTES, type TimeInForce, Timeframe, TradingService, type Unsubscribe, WALLET_XEXES, WalletService, WsCandlesService, XGATE_EXCHANGES, XgateEx, XgateModule, buildLevels, buildProtection, canonicalFromBase, canonicalFromXex, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, timeframeMs };
1207
+ export { CandlesService, CatalogService, type Direction, type IBalance, type ICandle, type ICandlesQuery, type IEntryWithProtection, type IEquityPoint, type IMoveStop, type IMovement, type IOrder, type IPair, type IPosition, type IPrice, type IProtection, type IProtectionInput, type IProtectionLeg, type ITrade, type ITradingAccess, type IWalletAccess, type IXexAccess, type IXexSpotSource, type MarketKind, type MovementKind, type MovementStatus, ORDER_STATUSES, type OrderStatus, type OrderType, PricesService, SPOT_XEXES, type Side, SpotCandlesService, SpotCatalogService, TIMEFRAME_MINUTES, type TimeInForce, Timeframe, TradingService, type Unsubscribe, WALLET_XEXES, WalletService, WsCandlesService, XGATE_EXCHANGES, XgateEx, XgateModule, buildLevels, buildProtection, canonicalFromBase, canonicalFromXex, expiryOf, isDated, multiplierFromXex, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, timeframeMs };
package/dist/index.d.ts CHANGED
@@ -68,6 +68,23 @@ interface IPair {
68
68
  xex: XgateEx;
69
69
  /** Type de marché (`perp`/`spot`). */
70
70
  kind: MarketKind;
71
+ /**
72
+ * FACTEUR D'ÉCHELLE DE COTATION — `1000` pour `kPEPE`, `1000000` pour `1MBABYDOGE`, `1` sinon.
73
+ *
74
+ * **Aucune venue ne le publie** : l'information n'existe que dans le préfixe du nom, et le
75
+ * canonique la fait disparaître. Sans elle, comparer des prix entre venues est faux — `SATS`
76
+ * porte trois échelles sur quatre sources (×1, ×1000, ×10000). Diviser le prix par ce facteur
77
+ * ramène toutes les venues à la même unité.
78
+ */
79
+ multiplier: number;
80
+ /**
81
+ * ÉCHÉANCE du contrat. `undefined` pour un perpétuel — l'immense majorité.
82
+ *
83
+ * Un future daté ne cote pas le comptant : sa base peut s'écarter de plusieurs pour cent. Neuf
84
+ * lignes BTC de bybit sont dans ce cas. Dérivée du **type de contrat**, jamais de la date de
85
+ * livraison : binance publie l'an 2100 sur ses perpétuels.
86
+ */
87
+ expiresAt?: Date;
71
88
  /**
72
89
  * L'identifiant TECHNIQUE que la venue exige pour agir, quand il diffère du symbole (index de
73
90
  * marché chez lighter, index spot chez hyperliquid). `null` quand le symbole suffit.
@@ -407,6 +424,45 @@ declare class SpotCatalogService {
407
424
  private catalogOf;
408
425
  }
409
426
 
427
+ /**
428
+ * LES BOUGIES DU **COMPTANT**, en REST — binance et bybit.
429
+ *
430
+ * **Un service à part, et non un drapeau sur {@link CandlesService}.** C'est la même règle que pour
431
+ * le catalogue : un `candles()` qui servirait tantôt du perpétuel tantôt du comptant selon un
432
+ * paramètre produirait des erreurs silencieuses — on croirait lire des perpétuels et on lirait du
433
+ * comptant, sans que rien ne le signale. Deux marchés, deux services ; l'appelant choisit
434
+ * explicitement ce qu'il interroge.
435
+ *
436
+ * La différence n'est pas cosmétique : le perpétuel porte un funding et peut s'écarter du comptant,
437
+ * et **les échelles de cotation diffèrent d'un marché à l'autre** — `SATSUSDT` au comptant chez
438
+ * bybit cote ×1 quand son perpétuel cote ×10000. Mélanger les deux séries fabriquerait un graphe
439
+ * qui saute d'un facteur 10 000.
440
+ *
441
+ * **Aucune agrégation ici**, contrairement au perpétuel : binance et bybit servent nativement tous
442
+ * les intervalles de `1m` à `1w`. Le jour où une venue comptant en manquerait un, ce serait à
443
+ * ajouter — pas à supposer.
444
+ */
445
+ declare class SpotCandlesService {
446
+ private readonly logger;
447
+ /** Les venues dont XGate sert le comptant. */
448
+ venues(): readonly XgateEx[];
449
+ /**
450
+ * Les bougies comptant d'une venue, sur une plage de **dates**.
451
+ *
452
+ * **Lève** si la venue ne sert pas de comptant, plutôt que de rendre une liste vide : « cette
453
+ * venue n'a pas de marché comptant » et « ce marché n'a pas coté » appellent des suites très
454
+ * différentes.
455
+ */
456
+ candles(xex: XgateEx, query: ICandlesQuery): Promise<ICandle[]>;
457
+ /**
458
+ * Les bougies comptant d'un même marché chez PLUSIEURS venues, en une liste.
459
+ *
460
+ * Même politique d'échec que partout ailleurs : seule, une venue en échec fait échouer l'appel ;
461
+ * parmi d'autres, elle est journalisée et ignorée. Chaque bougie porte son `xex`.
462
+ */
463
+ candlesOf(xexes: XgateEx[], query: ICandlesQuery): Promise<ICandle[]>;
464
+ }
465
+
410
466
  /** Une paire, telle qu'un SDK de venue la rend. */
411
467
  interface IXexPair {
412
468
  name: string;
@@ -535,7 +591,7 @@ interface IXexAccess {
535
591
  * Les venues dont XGate sait lire le portefeuille aujourd'hui.
536
592
  *
537
593
  * Les autres ne sont pas exclues par principe : il leur manque un accès (clé d'API pour bullet, une
538
- * adresse active pour lighter, extended et paradex). Voir le backlog.
594
+ * adresse active pour extended et paradex). Voir le backlog.
539
595
  */
540
596
  declare const WALLET_XEXES: readonly XgateEx[];
541
597
  /**
@@ -660,6 +716,22 @@ declare class WalletService {
660
716
  * regarde autrement qu'une panne.
661
717
  */
662
718
  balances(...accesses: IWalletAccess[]): Promise<IBalance[]>;
719
+ /**
720
+ * LE COLLATÉRAL PERPÉTUEL, ajouté aux soldes comptant — sans lui, un portefeuille peut paraître VIDE.
721
+ *
722
+ * `getBalances` ne rend que le **comptant**, chez les huit venues : c'est leur convention, et la
723
+ * documentation de chacune le dit. Or l'argent qui sert à trader n'y est pas. Sur un compte
724
+ * pacifica dont tout le solde est en marge, `getBalances` rend `[]` — ce qui se lit « pas
725
+ * d'argent » alors qu'il y a 499 USDC. Sur hyperliquid, le comptant s'affiche mais la marge
726
+ * manque.
727
+ *
728
+ * La ligne est **marquée `scope: 'perp'`** dans `xtras` : elle s'additionne au comptant sans le
729
+ * doubler, et un appelant qui ne veut que l'un des deux peut trancher.
730
+ *
731
+ * Le champ diffère par venue et n'est pas devinable — d'où la lecture explicite, venue par venue,
732
+ * plutôt qu'un parcours à l'aveugle d'un objet `unknown`.
733
+ */
734
+ private perpCollateral;
663
735
  /**
664
736
  * Les mouvements d'argent, triés du plus ancien au plus récent, montants **signés**.
665
737
  *
@@ -1088,5 +1160,48 @@ declare function canonicalFromXex(rawSymbol: string, xexId: string): string;
1088
1160
  * symbole.
1089
1161
  */
1090
1162
  declare function canonicalFromBase(base: string): string;
1163
+ /**
1164
+ * LE MULTIPLICATEUR DE COTATION, extrait du nom — **la seule source qui existe**.
1165
+ *
1166
+ * Aucune venue ne le publie : `kPEPE` chez hyperliquid ne porte que `{"marginTableId":52}`,
1167
+ * `1000PEPEUSDT` chez binance ne mentionne aucun facteur. L'information n'est QUE dans le préfixe
1168
+ * du nom, et le canonique la fait disparaître — jusqu'ici sans la conserver nulle part.
1169
+ *
1170
+ * Or elle est indispensable dès qu'on compare des prix entre venues. `SATS` porte **trois échelles
1171
+ * différentes sur quatre sources** : `SATSUSDT` (×1) chez bybit-spot, `1000SATSUSDT` (×1000) chez
1172
+ * binance, `10000SATSUSDT` (×10000) chez bybit. Une médiane calculée sur ces prix bruts ne veut
1173
+ * rien dire ; divisés par leur facteur, ils redeviennent comparables.
1174
+ *
1175
+ * Rend `1` quand le nom ne porte aucune échelle — le cas de l'immense majorité.
1176
+ */
1177
+ declare function multiplierFromXex(rawSymbol: string): number;
1178
+
1179
+ /**
1180
+ * LES CONTRATS À ÉCHÉANCE, séparés des perpétuels — parce qu'un future daté ne cote pas le comptant.
1181
+ *
1182
+ * Le catalogue les mélangeait : 44 lignes portaient `kind: 'perp'` alors qu'elles expirent, dont
1183
+ * neuf sur le seul BTC chez bybit. Un consommateur qui prend « le prix du BTC » y ramassait une
1184
+ * échéance de décembre, dont la base au comptant peut s'écarter de plusieurs pour cent.
1185
+ *
1186
+ * **`contractType` fait foi, JAMAIS la date de livraison.** binance publie `deliveryDate:
1187
+ * 4133404800000` — le 1er janvier 2100 — sur ses **perpétuels** : s'y fier marquerait tout le
1188
+ * catalogue comme daté. bybit, lui, met `deliveryTime: '0'` sur les siens. Deux conventions
1189
+ * incompatibles pour dire « ceci n'expire pas », d'où la lecture du type et de lui seul.
1190
+ */
1191
+ /**
1192
+ * L'ÉCHÉANCE D'UN CONTRAT, ou `undefined` s'il est perpétuel.
1193
+ *
1194
+ * On ne lit la date **que** si le type de contrat annonce une échéance : c'est ce qui neutralise le
1195
+ * `deliveryDate` de l'an 2100 des perpétuels binance. Un type inconnu est traité comme perpétuel —
1196
+ * l'immense majorité des lignes, et se tromper dans ce sens n'invente pas d'échéance.
1197
+ */
1198
+ declare function expiryOf(xtras?: Record<string, unknown>): Date | undefined;
1199
+ /**
1200
+ * LE CONTRAT EXPIRE-T-IL ? Vrai pour un future daté, faux pour un perpétuel.
1201
+ *
1202
+ * Séparé d'{@link expiryOf} à dessein : une venue peut annoncer un type daté sans publier de date
1203
+ * exploitable, et il faut alors pouvoir écarter la ligne quand même.
1204
+ */
1205
+ declare function isDated(xtras?: Record<string, unknown>): boolean;
1091
1206
 
1092
- export { CandlesService, CatalogService, type Direction, type IBalance, type ICandle, type ICandlesQuery, type IEntryWithProtection, type IEquityPoint, type IMoveStop, type IMovement, type IOrder, type IPair, type IPosition, type IPrice, type IProtection, type IProtectionInput, type IProtectionLeg, type ITrade, type ITradingAccess, type IWalletAccess, type IXexAccess, type IXexSpotSource, type MarketKind, type MovementKind, type MovementStatus, ORDER_STATUSES, type OrderStatus, type OrderType, PricesService, SPOT_XEXES, type Side, SpotCatalogService, TIMEFRAME_MINUTES, type TimeInForce, Timeframe, TradingService, type Unsubscribe, WALLET_XEXES, WalletService, WsCandlesService, XGATE_EXCHANGES, XgateEx, XgateModule, buildLevels, buildProtection, canonicalFromBase, canonicalFromXex, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, timeframeMs };
1207
+ export { CandlesService, CatalogService, type Direction, type IBalance, type ICandle, type ICandlesQuery, type IEntryWithProtection, type IEquityPoint, type IMoveStop, type IMovement, type IOrder, type IPair, type IPosition, type IPrice, type IProtection, type IProtectionInput, type IProtectionLeg, type ITrade, type ITradingAccess, type IWalletAccess, type IXexAccess, type IXexSpotSource, type MarketKind, type MovementKind, type MovementStatus, ORDER_STATUSES, type OrderStatus, type OrderType, PricesService, SPOT_XEXES, type Side, SpotCandlesService, SpotCatalogService, TIMEFRAME_MINUTES, type TimeInForce, Timeframe, TradingService, type Unsubscribe, WALLET_XEXES, WalletService, WsCandlesService, XGATE_EXCHANGES, XgateEx, XgateModule, buildLevels, buildProtection, canonicalFromBase, canonicalFromXex, expiryOf, isDated, multiplierFromXex, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, timeframeMs };
package/dist/index.js CHANGED
@@ -148,6 +148,8 @@ function sum(left, right) {
148
148
  var CONCAT_QUOTE_XEXS = /* @__PURE__ */ new Set(["aster", "binance", "bybit"]);
149
149
  var K_MULTIPLIER = /^k(?=[A-Z])/u;
150
150
  var NUMERIC_MULTIPLIER = /^10{3,}(?=[A-Z0-9])/u;
151
+ var ABBREV_MULTIPLIER = /^1([MK])(?=[A-Z]{2,})/u;
152
+ var ABBREV_FACTOR = { M: 1e6, K: 1e3 };
151
153
  var STOCK_24_5 = /_24_5$/u;
152
154
  var TICKER_ALIASES = {
153
155
  GOOG: "GOOGL",
@@ -172,13 +174,26 @@ function canonicalFromXex(rawSymbol, xexId) {
172
174
  s = s.replace(/-USD-PERP$/u, "").replace(/-PERP$/u, "").replace(/-USDT$/u, "").replace(/-USDC$/u, "").replace(/-USD$/u, "");
173
175
  }
174
176
  s = s.replace(NUMERIC_MULTIPLIER, "");
177
+ s = s.replace(ABBREV_MULTIPLIER, "");
175
178
  s = s.replace(STOCK_24_5, "");
176
179
  return TICKER_ALIASES[s] ?? s;
177
180
  }
178
181
  function canonicalFromBase(base) {
179
- const s = base.replace(K_MULTIPLIER, "").toUpperCase().replace(NUMERIC_MULTIPLIER, "").replace(STOCK_24_5, "");
182
+ const s = base.replace(K_MULTIPLIER, "").toUpperCase().replace(NUMERIC_MULTIPLIER, "").replace(ABBREV_MULTIPLIER, "").replace(STOCK_24_5, "");
180
183
  return TICKER_ALIASES[s] ?? s;
181
184
  }
185
+ function multiplierFromXex(rawSymbol) {
186
+ if (K_MULTIPLIER.test(rawSymbol)) {
187
+ return 1e3;
188
+ }
189
+ const upper = rawSymbol.toUpperCase();
190
+ const abbrev = ABBREV_MULTIPLIER.exec(upper);
191
+ if (abbrev !== null) {
192
+ return ABBREV_FACTOR[abbrev[1]] ?? 1;
193
+ }
194
+ const zeros = NUMERIC_MULTIPLIER.exec(upper);
195
+ return zeros === null ? 1 : Number(zeros[0]);
196
+ }
182
197
 
183
198
  // src/helpers/candle-mapper.ts
184
199
  function toCandle(wire, xex, interval, quote) {
@@ -263,6 +278,19 @@ function createAccountXex(xex, access) {
263
278
  },
264
279
  { default: "a" }
265
280
  );
281
+ case "lighter" /* Lighter */:
282
+ return new Lighter(
283
+ {
284
+ a: {
285
+ apiPrivateKey: access.privateKey ?? NULL_KEY,
286
+ apiKeyIndex: access.apiKeyIndex ?? 0,
287
+ accountIndex: access.accountIndex ?? 0,
288
+ l1Address: adresse(),
289
+ network: access.network ?? "mainnet"
290
+ }
291
+ },
292
+ { default: "a" }
293
+ );
266
294
  case "aster" /* Aster */:
267
295
  if (access.privateKey === void 0 || access.privateKey === "") {
268
296
  throw new Error(
@@ -303,6 +331,7 @@ var WALLET_XEXES = [
303
331
  "hyperliquid" /* Hyperliquid */,
304
332
  "pacifica" /* Pacifica */,
305
333
  "aster" /* Aster */,
334
+ "lighter" /* Lighter */,
306
335
  "blofin" /* Blofin */
307
336
  ];
308
337
  function createPublicXex(xex) {
@@ -347,8 +376,11 @@ function createPublicSpotXex(xex) {
347
376
  }
348
377
 
349
378
  // src/helpers/xex-timeframes.ts
379
+ var HYPERLIQUID_NATIVE = Object.values(Timeframe).filter(
380
+ (interval) => interval !== "1w" /* W */
381
+ );
350
382
  var NATIVE = {
351
- ["hyperliquid" /* Hyperliquid */]: Object.values(Timeframe),
383
+ ["hyperliquid" /* Hyperliquid */]: HYPERLIQUID_NATIVE,
352
384
  ["pacifica" /* Pacifica */]: Object.values(Timeframe),
353
385
  ["aster" /* Aster */]: Object.values(Timeframe),
354
386
  ["blofin" /* Blofin */]: Object.values(Timeframe),
@@ -476,6 +508,43 @@ var CandlesService = class {
476
508
  CandlesService = __decorateClass([
477
509
  Injectable()
478
510
  ], CandlesService);
511
+
512
+ // src/helpers/contract-expiry.ts
513
+ var DATED_CONTRACT_TYPES = /* @__PURE__ */ new Set([
514
+ // binance : les trimestriels (`BTCUSDT_260925`, `ETHUSDT_261225`).
515
+ "CURRENT_QUARTER",
516
+ "NEXT_QUARTER",
517
+ "CURRENT_QUARTER_DELIVERING",
518
+ "NEXT_QUARTER_DELIVERING",
519
+ "PERPETUAL_DELIVERING",
520
+ // bybit : les datés (`BTCUSDT-25DEC26`), face à `LinearPerpetual`.
521
+ "LinearFutures",
522
+ "InverseFutures"
523
+ ]);
524
+ var EXPIRY_FIELDS = ["deliveryTime", "deliveryDate", "expiryTime", "settleTime"];
525
+ function expiryOf(xtras) {
526
+ if (xtras === void 0) {
527
+ return void 0;
528
+ }
529
+ const type = xtras.contractType;
530
+ if (typeof type !== "string" || DATED_CONTRACT_TYPES.has(type) === false) {
531
+ return void 0;
532
+ }
533
+ for (const field of EXPIRY_FIELDS) {
534
+ const brut = xtras[field];
535
+ const ms = typeof brut === "string" ? Number(brut) : brut;
536
+ if (typeof ms === "number" && Number.isFinite(ms) === true && ms > 0) {
537
+ return new Date(ms);
538
+ }
539
+ }
540
+ return void 0;
541
+ }
542
+ function isDated(xtras) {
543
+ const type = xtras?.contractType;
544
+ return typeof type === "string" && DATED_CONTRACT_TYPES.has(type);
545
+ }
546
+
547
+ // src/services/catalog.service.ts
479
548
  var NAMES_ITS_BASE = /* @__PURE__ */ new Set(["binance" /* Binance */, "bybit" /* Bybit */]);
480
549
  var CatalogService = class {
481
550
  logger = new Logger(CatalogService.name);
@@ -521,6 +590,8 @@ var CatalogService = class {
521
590
  symbolXex: pair.name,
522
591
  xex,
523
592
  kind: pair.kind,
593
+ multiplier: multiplierFromXex(pair.name),
594
+ expiresAt: expiryOf(pair.xtras),
524
595
  ref: pair.ref ?? null,
525
596
  szDecimals: pair.szDecimals,
526
597
  tickSize: pair.tickSize,
@@ -595,6 +666,65 @@ var PricesService = class {
595
666
  PricesService = __decorateClass([
596
667
  Injectable()
597
668
  ], PricesService);
669
+ var SpotCandlesService = class {
670
+ logger = new Logger(SpotCandlesService.name);
671
+ /** Les venues dont XGate sert le comptant. */
672
+ venues() {
673
+ return SPOT_XEXES;
674
+ }
675
+ /**
676
+ * Les bougies comptant d'une venue, sur une plage de **dates**.
677
+ *
678
+ * **Lève** si la venue ne sert pas de comptant, plutôt que de rendre une liste vide : « cette
679
+ * venue n'a pas de marché comptant » et « ce marché n'a pas coté » appellent des suites très
680
+ * différentes.
681
+ */
682
+ async candles(xex, query) {
683
+ if (servesSpot(xex) === false) {
684
+ throw new Error(
685
+ `spotCandles(${xex}) : cette venue ne sert pas de comptant. Disponibles : ${SPOT_XEXES.join(", ")}.`
686
+ );
687
+ }
688
+ const spot = createPublicSpotXex(xex).spot();
689
+ if (spot.getCandles === void 0) {
690
+ throw new Error(`spotCandles(${xex}) : cette venue ne sert pas de bougies comptant en REST.`);
691
+ }
692
+ const wires = await spot.getCandles(toXexQuery(query));
693
+ return wires.map((wire) => toCandle(wire, xex, query.interval, ""));
694
+ }
695
+ /**
696
+ * Les bougies comptant d'un même marché chez PLUSIEURS venues, en une liste.
697
+ *
698
+ * Même politique d'échec que partout ailleurs : seule, une venue en échec fait échouer l'appel ;
699
+ * parmi d'autres, elle est journalisée et ignorée. Chaque bougie porte son `xex`.
700
+ */
701
+ async candlesOf(xexes, query) {
702
+ if (xexes.length === 0) {
703
+ throw new Error("spotCandlesOf() : aucune venue demand\xE9e \u2014 pr\xE9cise au moins un XgateEx.");
704
+ }
705
+ const solo = xexes.length === 1;
706
+ const collected = await Promise.all(
707
+ xexes.map(async (xex) => {
708
+ try {
709
+ return await this.candles(xex, query);
710
+ } catch (error) {
711
+ if (solo === true) {
712
+ throw error;
713
+ }
714
+ const message = error instanceof Error ? error.message : String(error);
715
+ this.logger.error(
716
+ `spotCandles(${xex}, ${query.interval}) a \xE9chou\xE9, venue ignor\xE9e : ${message}`
717
+ );
718
+ return [];
719
+ }
720
+ })
721
+ );
722
+ return collected.flat();
723
+ }
724
+ };
725
+ SpotCandlesService = __decorateClass([
726
+ Injectable()
727
+ ], SpotCandlesService);
598
728
  var SpotCatalogService = class {
599
729
  logger = new Logger(SpotCatalogService.name);
600
730
  /** Les venues dont XGate sert le comptant. */
@@ -646,6 +776,9 @@ var SpotCatalogService = class {
646
776
  symbolXex: pair.name,
647
777
  xex,
648
778
  kind: pair.kind,
779
+ // Le comptant porte les mêmes échelles que le perp : `SATSUSDT` (×1) et `1000SATSUSDT`
780
+ // coexistent, et c'est justement entre eux que la comparaison serait fausse.
781
+ multiplier: multiplierFromXex(pair.name),
649
782
  ref: pair.ref ?? null,
650
783
  szDecimals: pair.szDecimals,
651
784
  tickSize: pair.tickSize,
@@ -1011,8 +1144,9 @@ var WalletService = class {
1011
1144
  */
1012
1145
  async balances(...accesses) {
1013
1146
  return this.collect(accesses, "balances", async (access) => {
1014
- const balances = await createAccountXex(access.xex, access).account().getBalances();
1015
- return balances.map((balance) => ({
1147
+ const source = createAccountXex(access.xex, access);
1148
+ const balances = await source.account().getBalances();
1149
+ const lignes = balances.map((balance) => ({
1016
1150
  xex: access.xex,
1017
1151
  asset: balance.asset,
1018
1152
  total: balance.total,
@@ -1020,8 +1154,48 @@ var WalletService = class {
1020
1154
  usdValue: balance.usdValue,
1021
1155
  xtras: balance.xtras
1022
1156
  }));
1157
+ const collateral = await this.perpCollateral(source, access.xex);
1158
+ return collateral === null ? lignes : [...lignes, collateral];
1023
1159
  });
1024
1160
  }
1161
+ /**
1162
+ * LE COLLATÉRAL PERPÉTUEL, ajouté aux soldes comptant — sans lui, un portefeuille peut paraître VIDE.
1163
+ *
1164
+ * `getBalances` ne rend que le **comptant**, chez les huit venues : c'est leur convention, et la
1165
+ * documentation de chacune le dit. Or l'argent qui sert à trader n'y est pas. Sur un compte
1166
+ * pacifica dont tout le solde est en marge, `getBalances` rend `[]` — ce qui se lit « pas
1167
+ * d'argent » alors qu'il y a 499 USDC. Sur hyperliquid, le comptant s'affiche mais la marge
1168
+ * manque.
1169
+ *
1170
+ * La ligne est **marquée `scope: 'perp'`** dans `xtras` : elle s'additionne au comptant sans le
1171
+ * doubler, et un appelant qui ne veut que l'un des deux peut trancher.
1172
+ *
1173
+ * Le champ diffère par venue et n'est pas devinable — d'où la lecture explicite, venue par venue,
1174
+ * plutôt qu'un parcours à l'aveugle d'un objet `unknown`.
1175
+ */
1176
+ async perpCollateral(source, xex) {
1177
+ const facade = source;
1178
+ if (typeof facade.perp !== "function") {
1179
+ return null;
1180
+ }
1181
+ const scope = facade.perp();
1182
+ if (typeof scope.getAccountInfo !== "function") {
1183
+ return null;
1184
+ }
1185
+ const info = await scope.getAccountInfo();
1186
+ const total = info.balance ?? info.marginSummary?.accountValue;
1187
+ if (total === void 0 || Number(total) === 0) {
1188
+ return null;
1189
+ }
1190
+ return {
1191
+ xex,
1192
+ asset: "USDC",
1193
+ total,
1194
+ available: info.availableToSpend ?? info.withdrawable ?? null,
1195
+ usdValue: total,
1196
+ xtras: { scope: "perp" }
1197
+ };
1198
+ }
1025
1199
  /**
1026
1200
  * Les mouvements d'argent, triés du plus ancien au plus récent, montants **signés**.
1027
1201
  *
@@ -1169,6 +1343,7 @@ XgateModule = __decorateClass([
1169
1343
  WsCandlesService,
1170
1344
  PricesService,
1171
1345
  SpotCatalogService,
1346
+ SpotCandlesService,
1172
1347
  WalletService
1173
1348
  ],
1174
1349
  exports: [
@@ -1177,11 +1352,12 @@ XgateModule = __decorateClass([
1177
1352
  WsCandlesService,
1178
1353
  PricesService,
1179
1354
  SpotCatalogService,
1355
+ SpotCandlesService,
1180
1356
  WalletService
1181
1357
  ]
1182
1358
  })
1183
1359
  ], XgateModule);
1184
1360
 
1185
- export { CandlesService, CatalogService, ORDER_STATUSES, PricesService, SPOT_XEXES, SpotCatalogService, TIMEFRAME_MINUTES, Timeframe, TradingService, WALLET_XEXES, WalletService, WsCandlesService, XGATE_EXCHANGES, XgateEx, XgateModule, buildLevels, buildProtection, canonicalFromBase, canonicalFromXex, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, timeframeMs };
1361
+ export { CandlesService, CatalogService, ORDER_STATUSES, PricesService, SPOT_XEXES, SpotCandlesService, SpotCatalogService, TIMEFRAME_MINUTES, Timeframe, TradingService, WALLET_XEXES, WalletService, WsCandlesService, XGATE_EXCHANGES, XgateEx, XgateModule, buildLevels, buildProtection, canonicalFromBase, canonicalFromXex, expiryOf, isDated, multiplierFromXex, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, timeframeMs };
1186
1362
  //# sourceMappingURL=index.js.map
1187
1363
  //# sourceMappingURL=index.js.map