@blackcube/xgate-sdk 0.21.1 → 0.23.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/README.md CHANGED
@@ -23,13 +23,13 @@ une capacité n'est comptée que si un test réel l'a exercée.
23
23
  | **hyperliquid** | ✔ | ✔ | ✔ | ✔ **prouvée** |
24
24
  | **pacifica** | ✔ | ✔ | ✔ | ✔ **prouvée** |
25
25
  | **aster** | ✔ | ✔ | ✔ | ⚠ **à valider** |
26
- | lighter | ✔ | — | — | — |
26
+ | lighter | ✔ | ✔ *(sans clé)* | — | — |
27
27
  | extended | ✔ | — | — | — |
28
28
  | paradex | ✔ | — | — | — |
29
- | bullet | ✔ | — | — | — |
29
+ | bullet | ✔ *(bougies en WS seulement)* | — | — | — |
30
30
  | blofin | ✔ | ✔ | ✔ | — |
31
- | binance | ✔ *(+ comptant)* | — | — | — |
32
- | bybit | ✔ *(+ comptant)* | — | — | — |
31
+ | binance | ✔ **+ comptant complet** | — | — | — |
32
+ | bybit | ✔ **+ comptant complet** | — | — | — |
33
33
 
34
34
  **hyperliquid et pacifica sont complètes et prouvées.** Le cycle entier — ouvrir avec stop, lire la
35
35
  position, refermer sans reliquat — tourne sur leur testnet à chaque exécution de la suite de tests.
@@ -41,22 +41,112 @@ sur une ouverture protégée, c'est la position nue que ce paquet existe pour re
41
41
  `openWithProtection` y **lève** tant que la preuve manque. Tout le reste (positions, ordres,
42
42
  historique, clôture) fonctionne. Détail complet dans le backlog.
43
43
 
44
+ **lighter se lit sans aucune clé.** Sa documentation classe `/account`, `accountsByL1Address` et
45
+ `positions` parmi les routes **sans authentification**, et `/account` accepte « an account's index,
46
+ **or L1 address** » : une adresse suffit, comme sur les autres DEX. Son trading, lui, demanderait
47
+ une clé d'API — aucune n'existe aujourd'hui.
48
+
49
+ **bullet ne sert aucune bougie en REST.** Sa documentation officielle ne comporte aucune section de
50
+ données historiques, et six noms d'endpoint testés répondent tous 404 — son REST existe pourtant
51
+ (`/fapi/v1/depth`, `trades`, `premiumIndex`, `fundingRate`, `ticker/24hr`, `openInterest`). Ses
52
+ bougies passent donc par `WsCandlesService` ; `CandlesService` y lève.
53
+
44
54
  **Les autres sont en lecture.** Elles servent le catalogue, les bougies et les prix ; il leur manque
45
- un accès de compte (clé d'API pour bullet, adresse active pour lighter, extended et paradex) ou la
46
- partie signée n'est pas câblée. Binance et bybit sont volontairement limitées au **public** : leurs
47
- SDK ne couvrent que catalogue, bougies et prix, et toute route signée y lève.
55
+ un accès de compte (clé d'API pour bullet, adresse active pour extended et paradex) ou la partie
56
+ signée n'est pas câblée. Binance et bybit sont volontairement limitées au **public**, mais y servent les deux marchés :
57
+ perpétuel ET comptant, chacun avec ses quatre services. Toute route signée y lève.
48
58
 
49
59
  ## Les services
50
60
 
51
61
  Chacun est un `@Injectable()` NestJS, utilisable seul ou via `XgateModule`.
52
62
 
63
+ **Les marchés se lisent par paires de services** : le nom de la classe dit lequel on interroge.
64
+
65
+ | Ce qu'on lit | Perpétuel — 10 venues | Comptant — binance, bybit |
66
+ |---|---|---|
67
+ | catalogue | `CatalogService` | `SpotCatalogService` |
68
+ | bougies REST | `CandlesService` | `SpotCandlesService` |
69
+ | prix | `PricesService` | `SpotPricesService` |
70
+ | bougies temps réel | `WsCandlesService` | `SpotWsCandlesService` |
71
+ | **flux multi-marchés** | `CandlesStreamRegistry` | `CandlesStreamRegistry.of(xex, 'spot')` |
72
+
73
+ `CandlesService` agrège quand la venue ne sert pas l'intervalle demandé ; les autres servent ce que
74
+ la venue publie. Un `Spot*` appelé sur une venue sans comptant **lève**.
75
+
76
+ ### ⚠️ Les sockets réagissent MAL à un symbole invalide
77
+
78
+ **À lire avant d'ouvrir un flux.** Aucune des venues ne rejette proprement un marché inconnu : elles
79
+ l'acceptent, puis se comportent mal — chacune à sa façon, et deux d'entre elles **en silence**.
80
+
81
+ | venue | ce qu'elle fait d'un symbole inconnu | XGate le voit ? |
82
+ |---|---|---|
83
+ | hyperliquid | ferme la connexion et reconnecte **en boucle** | ✔ coupe tout et lève |
84
+ | binance | l'ignore, les autres marchés continuent | sans objet |
85
+ | **bybit** | **fait taire TOUT le flux**, socket ouverte, sans erreur | ✘ **indétectable** |
86
+
87
+ Le cas bybit est le plus grave : `['BTCUSDT', 'INCONNU']` ne rend **aucune** bougie — pas même pour
88
+ `BTCUSDT` — alors que `['BTCUSDT', 'ETHUSDT']` fonctionne. Pas de fermeture, pas d'erreur, pas de
89
+ reconnexion. C'est un **silence**, pas un incident : aucun canal ne peut le remonter, et rien dans
90
+ XGate ne le détectera jamais.
91
+
92
+ Le cas hyperliquid était le même silence avant que XGate ne coupe : cinq reconnexions en douze
93
+ secondes pendant lesquelles les marchés **valides** du même socket se taisaient aussi.
94
+
95
+ **La seule protection : souscrire depuis le catalogue**, jamais depuis une liste écrite à la main.
96
+
97
+ ```typescript
98
+ const paires = await catalog.catalog(XgateEx.Bybit);
99
+ const symboles = paires.filter((p) => /* ta sélection */).map((p) => p.symbolXex);
100
+ registry.of(XgateEx.Bybit).start(symboles, Timeframe.M1, (candle) => { … });
101
+ ```
102
+
103
+ ### Suivre plusieurs marchés : `CandlesStreamRegistry`
104
+
105
+ **Une socket par venue, quel que soit le nombre de marchés.** Porté de Blips, où il tourne en
106
+ production.
107
+
108
+ ```typescript
109
+ const flux = registry.of(XgateEx.Hyperliquid);
110
+ flux.start(['BTC', 'ETH', 'SOL'], Timeframe.M1, (candle) => { … });
111
+ flux.subscriptionCount(); // 3
112
+ flux.maxSubscriptions; // 1000 — plafond documenté, null si la venue n'en publie pas
113
+ registry.stopAll();
114
+ ```
115
+
116
+ `of(xex)` rend **toujours la même instance** pour une venue : deux appels, deux `start()`, une seule
117
+ connexion. C'est ce qui rend tenable de suivre 200 marchés — hyperliquid n'accepte que 10 connexions
118
+ par IP.
119
+
120
+ `WsCandlesService`, lui, ouvre **une socket par souscription** : à réserver au suivi d'un ou deux
121
+ marchés.
122
+
123
+ > **Une erreur de flux coupe tout, puis lève.** Un symbole invalide n'échoue pas à la
124
+ > souscription : la venue l'accepte, puis ferme la connexion en le découvrant — cinq cycles de
125
+ > reconnexion en douze secondes, mesurés le 2026-08-07 sur hyperliquid, pendant lesquels **tous**
126
+ > les marchés valides du même socket cessent de recevoir, en silence.
127
+ >
128
+ > À la première erreur, XGate coupe les souscriptions, journalise en `error`, et lève. L'exception
129
+ > naît dans un rappel de socket : elle ne remonte pas à l'appelant de `start()`, elle sort en
130
+ > erreur non capturée — délibérément, pour être impossible à ignorer.
131
+ >
132
+ > Souscris depuis le catalogue, pas depuis une liste écrite à la main.
133
+ >
134
+ > **Les trois venues ne réagissent pas pareil à un symbole invalide** — mesuré le 2026-08-07 :
135
+ >
136
+ > | venue | ce qu'elle fait | détecté ? |
137
+ > |---|---|---|
138
+ > | hyperliquid | ferme la connexion, reconnecte en boucle | ✔ coupe et lève |
139
+ > | binance | l'ignore, les autres marchés continuent | sans objet |
140
+ > | **bybit** | **tout le flux se tait**, sans fermeture ni erreur | ✘ **rien ne le signale** |
141
+ >
142
+ > ⚠️ **bybit est le cas dangereux** : `['BTCUSDT', 'INCONNU']` ne rend **aucune** bougie, pas même
143
+ > pour `BTCUSDT`, et la socket reste ouverte sans erreur. Aucun canal ne peut le détecter — c'est
144
+ > un silence, pas un incident. La seule protection est de **souscrire depuis le catalogue**.
145
+
146
+ **Deux services sans équivalent comptant**, parce que binance et bybit sont bornées au public :
147
+
53
148
  | Service | Ce qu'il rend |
54
149
  |---|---|
55
- | `CatalogService` | les paires perpétuelles de toutes les venues, normalisées |
56
- | `SpotCatalogService` | les paires **comptant** — binance et bybit uniquement |
57
- | `CandlesService` | les bougies REST, avec agrégation quand la venue ne sert pas l'intervalle |
58
- | `WsCandlesService` | les bougies en temps réel, un handler pour N venues |
59
- | `PricesService` | mark, oracle, mid, bid/ask, funding, open interest |
60
150
  | `WalletService` | soldes, mouvements, historique d'équité |
61
151
  | `TradingService` | positions, ordres, exécutions — et le cycle **ouvrir → gérer → fermer** |
62
152
 
@@ -204,6 +294,139 @@ open · partiallyFilled · filled · canceled · rejected · expired · other
204
294
  `canceled` prend **un seul L**. Un statut inconnu retombe sur `other` plutôt que de traverser la
205
295
  passerelle tel quel : `ORDER_STATUSES` est exporté pour valider plutôt que caster.
206
296
 
297
+ ## Le comptant, au même niveau que le perpétuel
298
+
299
+ **Quatre services de chaque côté**, jamais un drapeau. Le nom de la classe dit quel marché on lit.
300
+
301
+ | | perpétuel | comptant |
302
+ |---|---|---|
303
+ | catalogue | `CatalogService` | `SpotCatalogService` |
304
+ | bougies REST | `CandlesService` | `SpotCandlesService` |
305
+ | prix | `PricesService` | `SpotPricesService` |
306
+ | temps réel | `WsCandlesService` | `SpotWsCandlesService` |
307
+
308
+ ```typescript
309
+ await spotCatalog.catalog(XgateEx.Binance, XgateEx.Bybit);
310
+ await spotCandles.candles(XgateEx.Binance, { symbol: 'BTCUSDT', interval: '1h', limit: 100 });
311
+ await spotPrices.prices(XgateEx.Binance, XgateEx.Bybit);
312
+ spotWs.subscribe(XgateEx.Binance, { symbol: 'BTCUSDT', interval: '1m' }, (candle) => { … });
313
+ ```
314
+
315
+ Le comptant est servi par **binance et bybit** ; toute autre venue lève.
316
+
317
+ **Pourquoi deux jeux de services et non un paramètre** : ce sont deux marchés, pas deux vues du
318
+ même. Le perpétuel porte un funding et s'écarte du comptant — c'est cet écart que certaines
319
+ stratégies exploitent, et le confondre le rendrait invisible. Un `candles(xex, { spot: true })`
320
+ laisserait croire qu'on regarde la même chose sous un autre angle.
321
+
322
+ **Ce qui change concrètement quand on passe au comptant :**
323
+
324
+ - **La cotation n'est plus toujours un dollar.** En perpétuel, tout est en USDT/USDC. Au comptant,
325
+ 495 paires sont cotées en BTC, 375 en BNB, 370 en TRY, 230 en ETH — le champ `quote` devient
326
+ indispensable, et le « prix » d'`ETHBTC` est un ratio, pas des dollars.
327
+ - **`mark`, `oracle`, `funding` et `openInterest` valent `null`** : ils n'existent que sur un
328
+ contrat. Les voir renseignés signalerait qu'on lit en réalité du perpétuel.
329
+
330
+ > Les échelles de cotation, elles, ne posent **aucun** problème entre les deux marchés :
331
+ > `SATSUSDT` (×1) et `10000SATSUSDT` (×10000) sont **deux paires distinctes**, chacune portant son
332
+ > `multiplier` au catalogue. Une bougie ou un prix appartient à une paire, identifiée par
333
+ > `xex` + `symbolXex` + `kind` — il n'y a rien à réconcilier.
334
+
335
+ **419 actifs n'existent qu'au comptant** — le perpétuel en couvre 887, le comptant 962, dont 419
336
+ sans aucun perpétuel nulle part.
337
+
338
+ > Sur `SpotPricesService`, `symbol` est dérivé du symbole concaténé : `BTCUSDT` → `BTC`. Les paires
339
+ > cotées hors dollar gardent leur nom entier (`ETHBTC`), les prix ne portant pas la base déclarée.
340
+ > Pour un `symbol`/`quote` justes sur ces paires, passer par `SpotCatalogService`.
341
+
342
+ ## La semaine s'ouvre le lundi, partout
343
+
344
+ `hyperliquid` sert des bougies `1w` **alignées sur jeudi** (elle aligne ses seaux sur l'epoch Unix,
345
+ qui tombe un jeudi), là où binance et pacifica ouvrent le lundi. XGate ne les prend donc pas : il
346
+ **reconstruit sa semaine depuis le journalier**, et les bougies rendues portent `derived: true`.
347
+
348
+ ```
349
+ hyperliquid lun 2026-06-01* lun 2026-06-08* lun 2026-06-15* ← reconstruites
350
+ binance lun 2026-06-01 lun 2026-06-08 lun 2026-06-15 ← natives
351
+ ```
352
+
353
+ Sans cela, deux séries portant le même intervalle `1w` couvriraient des jours différents et se
354
+ compareraient silencieusement.
355
+
356
+ ## Le catalogue : deux pièges que les venues ne signalent pas
357
+
358
+ **Le multiplicateur de cotation.** Aucune venue ne le publie — il n'existe que dans le préfixe du
359
+ nom, que le canonique fait disparaître. `IPair.multiplier` le conserve :
360
+
361
+ ```
362
+ SATS binance:1000SATSUSDT = ×1000 bybit:10000SATSUSDT = ×10000 bybit-spot:SATSUSDT = ×1
363
+ ```
364
+
365
+ Trois échelles pour le même actif sur trois sources. **Une médiane calculée sur ces prix bruts ne
366
+ veut rien dire** ; divisés par `multiplier`, ils redeviennent comparables. 98 lignes du catalogue
367
+ sont concernées.
368
+
369
+ **Les contrats à échéance.** 44 lignes portent `kind: 'perp'` alors qu'elles expirent, dont neuf
370
+ sur le seul BTC chez bybit. `IPair.expiresAt` les distingue — `undefined` pour un perpétuel.
371
+
372
+ ```typescript
373
+ pairs.filter((pair) => pair.expiresAt === undefined); // les vrais perpétuels
374
+ ```
375
+
376
+ L'échéance est dérivée du **type de contrat**, jamais de la date de livraison : binance publie
377
+ `deliveryDate: 4133404800000` — le 1ᵉʳ janvier 2100 — sur **tous** ses perpétuels. S'y fier
378
+ marquerait le catalogue entier comme daté.
379
+
380
+ ## Les actifs non-crypto du catalogue
381
+
382
+ **Extraction du 2026-08-07, et rien de plus.** Cette liste n'est pas un contrat : elle a été
383
+ relevée un jour donné sur les dix venues, et elle bougera au prochain listing sans que ce fichier
384
+ le sache. Ne l'utilise pas comme référence figée — refais l'extraction quand la question compte.
385
+
386
+ 164 actifs du catalogue perpétuel ne sont pas de la crypto. Ils s'y mélangent sans distinction :
387
+ un balayage qui ramène « les paires BTC-like » y ramasse de l'or, du pétrole et des actions Tesla,
388
+ alors que **ni les horaires de cotation, ni la volatilité, ni la dynamique de funding** ne s'y
389
+ comportent pareil — les actions et indices ne cotent pas 24/7.
390
+
391
+ **forex (7)**
392
+ `AUDUSD EURUSD GBPUSD NZDUSD USDCAD USDCHF USDJPY`
393
+
394
+ **matières premières (11)**
395
+ `BZ CL COPPER GOLD NG SILVER WTIOIL XAG XAU XPD XPT`
396
+
397
+ **indices et ETF (36)** — dont des ETF à effet de levier (`SOXL` ×3, `SQQQ` inverse ×3, `UVXY`)
398
+ `BBX BITO BNC BOT BSP EWJ EWT EWY EWZ FWDI INTW IWM KORU KSTR MUU MVLL QNTX QQQ SHAZ SMH SNXX SOXL
399
+ SOXS SP500 SPY SQQQ STXX TBT TMF TQQQ TZA URNM US100 UVXY XBI XLE`
400
+
401
+ **hors-US et pré-IPO (29)** — `OPENAI`, `ANTHROPIC`, `SPCX`, `ZHIPU`, `MINIMAX` sont des sociétés
402
+ **non cotées** : des marchés sur valorisation privée
403
+ `AAOI ANTHROPIC ARM ASML AXTI BABA BMNR CBRS CRWV DRAM GIGADEV HK0700 HK1810 HYUNDAI MINIMAX NBIS
404
+ NVO OPENAI PENG POPMART SAMSUNG SKHY SKHYNIX SONY SPCX TENCENT TSM USAR ZHIPU`
405
+
406
+ **actions US (81)**
407
+ `AAPL ADBE ALAB AMAT AMD AMZN APP ASTS AVGO BE BRKB BX CAT CIEN COHR COIN COST CRCL CRDO CRM CRWD
408
+ CSCO DELL DIS DKNG EBAY FLEX FLNC GEV GLW GME GOOGL GS HD HIMS HOOD HPE IBM INTC IREN JPM KLAC KO
409
+ LITE LLY LRCX META MRVL MSFT MSTR MU NFLX NOK NOW NVDA ONDS ORCL PANW PAYP PLTR PYPL QCOM RDDT
410
+ RIVN RKLB SMCI SNDK SNOW SOFI STRC TER TSLA TTWO TXN UBER V VRT WDC WEN WMT ZM`
411
+
412
+ ### Ce que cette liste vaut, et ce qu'elle ne vaut pas
413
+
414
+ **Deux venues seulement les déclarent** : extended (`contractType: 'TRADIFI_PERPETUAL'`, 153
415
+ marchés) et bullet (`RwaPerpUsEquity`, `RwaPerpCommodities`, `RwaPerpKrEquity`,
416
+ `RwaPerpUsEquityIndices`, 10 marchés). Les **huit autres n'en disent rien** — bybit en cote 131,
417
+ aster 108, blofin 74, lighter 66, pacifica 20, paradex 19, sans aucun champ pour les distinguer.
418
+ La liste ne tient donc que parce qu'un actif déclaré quelque part se reconnaît ailleurs à son
419
+ symbole canonique.
420
+
421
+ **Le sous-classement est une interprétation**, pas une donnée de venue. Le forex se reconnaît à sa
422
+ forme (six lettres, deux codes ISO) ; le reste vient d'un rangement fait à la main sur les tickers.
423
+ Ce qui est solide, c'est **l'appartenance à la liste** — pas la catégorie.
424
+
425
+ **Aucun champ `assetType` n'existe dans `IPair`**, et c'est délibéré tant qu'aucune source
426
+ d'autorité ne couvre les dix venues. La documentation officielle de pacifica le confirme : son
427
+ `/api/v1/info` ne publie que `instrument_type`, qui vaut `perpetual` aussi bien pour `BTC` que pour
428
+ `TSLA`, `XAU` ou `EURUSD`.
429
+
207
430
  ## Les conventions qui traversent tout
208
431
 
209
432
  - **Une bougie inclut sa dernière milliseconde** : 10:00:00.000 → 10:59:59.999. `closedAt` se
@@ -214,6 +437,10 @@ passerelle tel quel : `ORDER_STATUSES` est exporté pour valider plutôt que cas
214
437
  compte.
215
438
  - **Une taille de position est toujours positive** : le sens est porté par `side`.
216
439
  - **Pas de mensuel.** Les intervalles s'arrêtent à `1w`.
440
+ - **Le suffixe d'un symbole n'est pas sa cotation.** bybit nomme ses perpétuels USDC avec `PERP`
441
+ (`1000BONKPERP` → `BONK`/`USDC`), binance suffixe ses trimestriels par leur date
442
+ (`BTCUSDT_260925`). XGate lit ce que la venue **déclare** (`baseAsset`, `baseCoin`) plutôt que de
443
+ re-parser le nom.
217
444
 
218
445
  ## Versions
219
446