@blackcube/xgate-sdk 0.20.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.
@@ -0,0 +1,1048 @@
1
+ /**
2
+ * Le module d'XGate : `imports: [XgateModule]`, puis on injecte le SEUL service dont on a besoin.
3
+ *
4
+ * Une capacité = un service, indépendant des autres. Le REST et le temps réel sont eux-mêmes
5
+ * séparés (`CandlesService` / `WsCandlesService`) : rejouer le passé et suivre le présent ne se
6
+ * consomment pas de la même façon, et une application qui ne fait que du backfill n'a aucune
7
+ * socket à ouvrir.
8
+ */
9
+ declare class XgateModule {
10
+ }
11
+
12
+ /**
13
+ * Les venues qu'XGate sait interroger.
14
+ *
15
+ * Une seule énumération pour les CEX et les DEX : la distinction ne change rien à l'API — on
16
+ * interroge un catalogue de la même façon des deux côtés. Elle deviendra une donnée portée par la
17
+ * venue le jour où quelque chose en dépendra.
18
+ *
19
+ * **`drift` en est absent**, et ce n'est pas un oubli : son SDK ne porte aucun test (seul de la
20
+ * famille), sa précision de taille est un placeholder assumé, et son catalogue est une constante
21
+ * figée dans le paquet. On ne l'agrège pas tant qu'il n'est pas vérifiable.
22
+ */
23
+ declare enum XgateEx {
24
+ Hyperliquid = "hyperliquid",
25
+ Pacifica = "pacifica",
26
+ Aster = "aster",
27
+ Lighter = "lighter",
28
+ Extended = "extended",
29
+ Paradex = "paradex",
30
+ Bullet = "bullet",
31
+ Blofin = "blofin",
32
+ /**
33
+ * Les deux seules venues dont XGate sert aussi le **comptant** (cf. `IXexSpotSource`). Leurs SDK
34
+ * ne couvrent que la lecture publique : catalogue, bougies et prix. Toute route signée y lève.
35
+ */
36
+ Binance = "binance",
37
+ Bybit = "bybit"
38
+ }
39
+ /** Toutes les venues connues, dans l'ordre de déclaration. */
40
+ declare const XGATE_EXCHANGES: readonly XgateEx[];
41
+
42
+ /** Type de marché. */
43
+ type MarketKind = 'perp' | 'spot';
44
+ /**
45
+ * UNE PAIRE, TELLE QU'XGATE LA REND — quelle que soit la venue d'où elle vient.
46
+ *
47
+ * Trois identifiants coexistent, et ils ne servent pas à la même chose :
48
+ *
49
+ * - `symbol` est le nom **canonique** de l'actif (celui de CoinMarketCap) : `PEPE`, jamais
50
+ * `kPEPE` ni `1000PEPE`. C'est la clé qui permet de rapprocher le même actif entre venues.
51
+ * - `symbolXex` est ce que **la venue** attend : c'est lui qu'on lui renvoie pour souscrire,
52
+ * passer un ordre ou lire des bougies. Le confondre avec `symbol` fait échouer la requête.
53
+ * - `xex` dit **d'où vient la ligne**. Sans lui, un catalogue multi-venues perd l'information la
54
+ * plus utile qu'il porte : le même actif y figure autant de fois qu'il est listé.
55
+ *
56
+ * Le multiplicateur de cotation (`k`, `1000`) disparaît du `symbol` sans rien fausser : il est
57
+ * absorbé par le prix, donc `prix × taille` reste le notionnel quelle que soit l'unité de la
58
+ * venue.
59
+ */
60
+ interface IPair {
61
+ /** Nom canonique de l'actif (`PEPE`, `BTC`). */
62
+ symbol: string;
63
+ /** Actif de cotation (`USDC`, `USDT`). */
64
+ quote: string;
65
+ /** Le symbole que la venue attend (`kPEPE`, `1000PEPE`, `BTC-USDT`, `@1`). */
66
+ symbolXex: string;
67
+ /** La venue d'où provient cette paire. */
68
+ xex: XgateEx;
69
+ /** Type de marché (`perp`/`spot`). */
70
+ kind: MarketKind;
71
+ /**
72
+ * L'identifiant TECHNIQUE que la venue exige pour agir, quand il diffère du symbole (index de
73
+ * marché chez lighter, index spot chez hyperliquid). `null` quand le symbole suffit.
74
+ */
75
+ ref: string | null;
76
+ /** Décimales de taille → pas de quantité = `10^-szDecimals`. */
77
+ szDecimals: number;
78
+ /** Pas de prix, si fourni. */
79
+ tickSize?: string;
80
+ /** Pas de quantité, si fourni. */
81
+ stepSize?: string;
82
+ /** Quantité minimale d'un ordre, si fournie. */
83
+ minQty?: string;
84
+ /** Notionnel minimum d'un ordre, si fourni. */
85
+ minNotional?: string;
86
+ /** Levier maximum (perp), si fourni. */
87
+ maxLeverage?: number;
88
+ /** État du marché tel que la venue l'écrit, si publié. */
89
+ status?: string;
90
+ /** Marché mort chez la venue. `undefined` = la venue ne se prononce pas. */
91
+ delisted?: boolean;
92
+ /** Date de listing, quand la venue la publie. */
93
+ listedAt?: Date;
94
+ /** Champs natifs hors cœur unifié, tels que le SDK de la venue les a conservés. */
95
+ xtras?: Record<string, unknown>;
96
+ }
97
+
98
+ /**
99
+ * LE CATALOGUE, toutes venues confondues.
100
+ *
101
+ * L'appelant ne connaît qu'{@link XgateEx} et les types unifiés ; il n'importe aucun SDK, ne
102
+ * construit aucune façade, et ne se soucie pas de ce que chaque venue nomme comment.
103
+ *
104
+ * **Un service par capacité, pas une façade unique** : une application qui ne veut que le
105
+ * catalogue n'a aucune raison d'injecter de quoi trader. Les capacités suivantes vivront dans
106
+ * leurs propres services, injectables séparément.
107
+ */
108
+ declare class CatalogService {
109
+ private readonly logger;
110
+ /**
111
+ * Le catalogue **perp** d'une ou plusieurs venues, en une seule liste.
112
+ *
113
+ * Chaque ligne porte sa venue d'origine (`xex`) et les deux symboles — le canonique pour
114
+ * rapprocher un actif entre venues, le brut pour parler à celle-ci.
115
+ *
116
+ * **Le traitement d'une venue en échec dépend de ce qui a été demandé** : sur une seule venue,
117
+ * un échec est l'échec de l'appel et il est relancé ; sur plusieurs, la liste partielle garde
118
+ * sa valeur et l'incident est journalisé. Rendre `[]` en silence serait le pire des deux — un
119
+ * catalogue vide se lit comme « cette venue ne liste rien », pas comme « je n'ai pas pu
120
+ * demander ».
121
+ */
122
+ catalog(...xexes: XgateEx[]): Promise<IPair[]>;
123
+ /** Le catalogue d'UNE venue, traduit vers {@link IPair}. */
124
+ private catalogOf;
125
+ }
126
+
127
+ /**
128
+ * Les intervalles de bougies qu'XGate promet — **les mêmes que Blips**, mêmes noms, mêmes valeurs
129
+ * (`Blips/server/src/shared/timeframe.enum.ts`). Le consommateur n'a pas à traduire pour parler à
130
+ * la passerelle, ni la passerelle à inventer une échelle concurrente.
131
+ *
132
+ * Toutes les venues ne les servent pas : paradex s'arrête à l'heure (`resolution: must be one of
133
+ * [1 3 5 15 30 60]`, dit la venue), lighter refuse `1w`, extended rend une liste vide pour ce même
134
+ * intervalle. Ce qu'une venue ne sert pas se **fabrique par agrégation** depuis un intervalle plus
135
+ * fin, jamais ne se réclame à l'appelant.
136
+ */
137
+ declare enum Timeframe {
138
+ M1 = "1m",
139
+ M5 = "5m",
140
+ M15 = "15m",
141
+ H1 = "1h",
142
+ H4 = "4h",
143
+ D = "1d",
144
+ W = "1w"
145
+ }
146
+ /** Durée d'un intervalle, en minutes. */
147
+ declare const TIMEFRAME_MINUTES: Record<Timeframe, number>;
148
+ /** Durée d'un intervalle, en millisecondes. */
149
+ declare function timeframeMs(timeframe: Timeframe): number;
150
+
151
+ /**
152
+ * UNE BOUGIE, TELLE QU'XGATE LA REND.
153
+ *
154
+ * **Des dates, jamais des timestamps.** Les venues comptent en millisecondes, en secondes ou en
155
+ * chaînes datées ; aucune ne dit laquelle dans son type. Ici, `openedAt` et `closedAt` sont des
156
+ * `Date` — l'unité ne se devine plus. C'est la même règle que `IPair.listedAt`, et le même
157
+ * vocabulaire que Blips (`ICandleRow`).
158
+ *
159
+ * **Des prix en chaîne, pas en nombre.** Les SDK les rendent ainsi pour préserver la précision
160
+ * décimale, et il y a une raison : `0.1 × 0.1` vaut `0.010000000000000002` en JavaScript. XGate est
161
+ * une couche de venue, pas une couche de stockage — celui qui écrit en base convertit chez lui, en
162
+ * connaissance de cause.
163
+ */
164
+ interface ICandle {
165
+ /** Nom canonique de l'actif (`PEPE`, `BTC`). */
166
+ symbol: string;
167
+ /** Actif de cotation (`USDC`, `USDT`). */
168
+ quote: string;
169
+ /** Le symbole que la venue attend. */
170
+ symbolXex: string;
171
+ /** La venue d'où provient cette bougie. */
172
+ xex: XgateEx;
173
+ /** Type de marché (`perp`/`spot`). */
174
+ kind: MarketKind;
175
+ /** L'intervalle de cette bougie. */
176
+ interval: Timeframe;
177
+ /** Ouverture du créneau. */
178
+ openedAt: Date;
179
+ /** Fin du créneau — dernière milliseconde incluse. */
180
+ closedAt: Date;
181
+ open: string;
182
+ high: string;
183
+ low: string;
184
+ close: string;
185
+ /**
186
+ * Volume en actif de base. `null` = **inconnu**, pas « aucun échange » : une bougie venue d'un
187
+ * flux temps réel n'en porte pas toujours. Un `0` reste un vrai volume, celui d'une minute sans
188
+ * échange.
189
+ */
190
+ volume: string | null;
191
+ /** Volume en cotation. `null` quand la venue ne le publie pas. */
192
+ quoteVolume: string | null;
193
+ /** Part achetée à l'initiative du taker, en cotation. Avec `quoteVolume`, donne le CVD. */
194
+ takerBuyQuoteVolume: string | null;
195
+ /** Nombre de transactions. `null` quand la venue ne le compte pas. */
196
+ trades: number | null;
197
+ /**
198
+ * `true` = bougie **fabriquée par agrégation**, la venue ne sert pas cet intervalle. C'est une
199
+ * vraie bougie, mais personne ne l'a servie telle quelle.
200
+ */
201
+ derived: boolean;
202
+ }
203
+ /** Ce qu'on demande à `CandlesService`. */
204
+ interface ICandlesQuery {
205
+ /** Le symbole que la venue attend (`symbolXex` d'un {@link IPair}). */
206
+ symbol: string;
207
+ interval: Timeframe;
208
+ /** Début de la plage. Une **date**, jamais un timestamp. */
209
+ startTime?: Date;
210
+ /** Fin de la plage. */
211
+ endTime?: Date;
212
+ limit?: number;
213
+ }
214
+
215
+ /**
216
+ * LES BOUGIES EN REST — l'historique.
217
+ *
218
+ * Le temps réel vit dans `WsCandlesService` : ce sont deux besoins distincts (rejouer le passé,
219
+ * suivre le présent), et une application qui fait du backfill n'a pas à ouvrir de socket.
220
+ */
221
+ declare class CandlesService {
222
+ private readonly logger;
223
+ /**
224
+ * Les bougies d'une venue, sur une plage de **dates**.
225
+ *
226
+ * L'appelant donne des `Date` et un {@link Timeframe} ; le service traduit vers ce que la venue
227
+ * attend — chaîne UTC pour huit d'entre elles, millisecondes pour blofin.
228
+ *
229
+ * **Un intervalle que la venue ne sert pas est FABRIQUÉ**, pas refusé : on tire l'intervalle
230
+ * exploitable le plus grand et on agrège. Paradex s'arrête à l'heure, lighter et extended ne
231
+ * font pas la semaine — l'appelant n'a pas à le savoir. Les bougies fabriquées portent
232
+ * `derived: true`.
233
+ *
234
+ * **Lève si la venue ne sert rien du tout** (bullet, qui n'a pas de REST). Rendre une liste vide
235
+ * ferait passer une incapacité pour une absence de données, et un backfill silencieusement troué
236
+ * est pire qu'un backfill en erreur.
237
+ */
238
+ candles(xex: XgateEx, query: ICandlesQuery): Promise<ICandle[]>;
239
+ /** Un appel REST à la venue, dans l'intervalle qu'elle sait servir. */
240
+ private fetch;
241
+ /**
242
+ * Les bougies d'un même marché chez PLUSIEURS venues, en une liste.
243
+ *
244
+ * Comme pour le catalogue : une venue en échec parmi d'autres est journalisée et ignorée ; seule,
245
+ * son échec est celui de l'appel. Chaque bougie porte son `xex`, donc la liste reste
246
+ * exploitable telle quelle.
247
+ */
248
+ candlesOf(xexes: XgateEx[], query: ICandlesQuery): Promise<ICandle[]>;
249
+ /**
250
+ * La cotation, déduite du symbole natif. Les bougies ne la portent pas — seul le catalogue la
251
+ * publie —, et refaire un appel de catalogue pour chaque série coûterait plus cher que ce
252
+ * qu'elle apporte. `BTC-USDT` → `USDT` ; à défaut de séparateur, on ne prétend rien.
253
+ */
254
+ private quoteOf;
255
+ }
256
+
257
+ /** Couper le flux. */
258
+ type Unsubscribe = () => void;
259
+ /**
260
+ * LES BOUGIES EN TEMPS RÉEL — le présent.
261
+ *
262
+ * Séparé de `CandlesService` à dessein : rejouer un historique et suivre un flux ne se
263
+ * consomment pas de la même façon, et n'ont pas les mêmes coûts. Une application qui ne fait que
264
+ * du backfill n'a aucune socket à ouvrir.
265
+ */
266
+ declare class WsCandlesService {
267
+ private readonly logger;
268
+ /**
269
+ * Souscrit aux bougies d'un marché chez une venue. Rend la fonction de désabonnement.
270
+ *
271
+ * Le handler reçoit **une bougie à la fois**, au format unifié — c'est la forme commune aux neuf
272
+ * SDK. La bougie en cours est repoussée à chaque mise à jour tant qu'elle n'est pas close ;
273
+ * `closedAt` dit jusqu'où elle porte.
274
+ *
275
+ * **Attention à extended** : sur un intervalle qu'elle ne connaît pas, sa façade rend un
276
+ * désabonnement vide au lieu de lever. L'appelant se croit abonné et n'entend jamais rien — un
277
+ * silence indiscernable d'un marché sans échange. C'est un défaut du SDK, pas d'XGate, et il est
278
+ * dans la file des corrections.
279
+ */
280
+ subscribe(xex: XgateEx, query: {
281
+ symbol: string;
282
+ interval: Timeframe;
283
+ }, handler: (candle: ICandle) => void): Unsubscribe;
284
+ /**
285
+ * Souscrit au MÊME marché chez plusieurs venues, avec un seul handler.
286
+ *
287
+ * Chaque bougie porte son `xex` : l'appelant sait toujours qui parle. La fonction rendue coupe
288
+ * tous les flux d'un coup — sinon il faudrait en garder N et n'en oublier aucun.
289
+ */
290
+ subscribeAll(xexes: XgateEx[], query: {
291
+ symbol: string;
292
+ interval: Timeframe;
293
+ }, handler: (candle: ICandle) => void): Unsubscribe;
294
+ /** Voir `CandlesService.quoteOf` : la cotation se déduit du symbole natif, faute d'être publiée. */
295
+ private quoteOf;
296
+ }
297
+
298
+ /**
299
+ * UNE COTATION, TELLE QU'XGATE LA REND — quelle que soit la venue d'où elle vient.
300
+ *
301
+ * Les trois identifiants sont ceux d'{@link IPair} et jouent le même rôle : `symbol` est le nom
302
+ * **canonique** de l'actif (celui qui permet de comparer un prix entre venues), `symbolXex` est ce
303
+ * que **la venue** attend, et `xex` dit d'où vient la ligne. Sans ce dernier, un relevé
304
+ * multi-venues perd ce qu'il a de plus utile : le même actif y figure autant de fois qu'il est
305
+ * coté, et c'est précisément l'écart entre ces cotations qu'on vient chercher.
306
+ *
307
+ * **Tous les prix sont des `string`**, jamais des `number` : ils viennent de la venue en décimal
308
+ * exact, et les passer en flottant perdrait des décimales avant même le premier calcul.
309
+ *
310
+ * **`null` signifie « la venue ne le publie pas »**, et ce n'est pas la même chose que zéro.
311
+ * Aucune venue ne sert les quatorze champs — voir le DICTIONARY de chaque SDK pour savoir qui
312
+ * remplit quoi. Deux nuances qui coûtent cher si on les ignore :
313
+ *
314
+ * - `mid` n'est servi que par hyperliquid et pacifica. Le dériver de `bid`/`ask` ailleurs
315
+ * fabriquerait une valeur qu'aucune venue n'a cotée.
316
+ * - chez aster, le remplissage varie **d'un symbole à l'autre** : les cotations viennent de deux
317
+ * endpoints aux univers différents, et les marchés exotiques n'ont ni `volume24h`, ni
318
+ * `prevDayPrice`, ni `last`. C'est la seule venue où deux `IPrice` du même appel n'ont pas les
319
+ * mêmes champs servis.
320
+ */
321
+ interface IPrice {
322
+ /** Nom canonique de l'actif (`PEPE`, `BTC`). */
323
+ symbol: string;
324
+ /** Le symbole que la venue attend (`kPEPE`, `1000PEPE`, `BTC-USDT`). */
325
+ symbolXex: string;
326
+ /** La venue d'où provient cette cotation. */
327
+ xex: XgateEx;
328
+ /** Type de marché (`perp`/`spot`). */
329
+ kind: MarketKind;
330
+ /** Prix de valorisation des positions — la référence pour un PnL. */
331
+ mark: string | null;
332
+ /** Prix oracle/index. */
333
+ oracle: string | null;
334
+ /** Milieu de fourchette, tel que la venue le cote. */
335
+ mid: string | null;
336
+ /** Meilleure offre d'achat. */
337
+ bid: string | null;
338
+ /** Meilleure offre de vente. */
339
+ ask: string | null;
340
+ /** Dernier prix négocié. */
341
+ last: string | null;
342
+ /** Taux de financement courant. */
343
+ funding: string | null;
344
+ /** Position ouverte totale sur le marché. */
345
+ openInterest: string | null;
346
+ /** Volume des dernières 24 heures, en notionnel. */
347
+ volume24h: string | null;
348
+ /** Prix de référence de la veille, pour une variation sur 24 heures. */
349
+ prevDayPrice: string | null;
350
+ /** Moment de la cotation ; `null` quand la venue ne le publie pas. */
351
+ quotedAt: Date | null;
352
+ /** Champs natifs hors cœur unifié, tels que le SDK de la venue les a conservés. */
353
+ xtras?: Record<string, unknown>;
354
+ }
355
+
356
+ /**
357
+ * LES PRIX, toutes venues confondues.
358
+ *
359
+ * Même forme que `CatalogService`, et pour la même raison : l'appelant ne connaît qu'{@link XgateEx}
360
+ * et les types unifiés. Chaque ligne porte sa venue d'origine, sans quoi un relevé multi-venues
361
+ * perdrait ce qu'il a de plus utile — l'écart entre les cotations d'un même actif.
362
+ *
363
+ * **Ce service ne calcule rien.** Il ne dérive pas un `mid` d'un `bid`/`ask`, ne convertit pas un
364
+ * volume, ne comble aucun trou : un champ à `null` dit que la venue ne le publie pas, et c'est une
365
+ * information. Fabriquer la valeur manquante la rendrait indiscernable d'une valeur cotée.
366
+ */
367
+ declare class PricesService {
368
+ private readonly logger;
369
+ /**
370
+ * Les prix **perp** d'une ou plusieurs venues, en une seule liste.
371
+ *
372
+ * **Le traitement d'une venue en échec dépend de ce qui a été demandé** : sur une seule venue, un
373
+ * échec est l'échec de l'appel et il est relancé ; sur plusieurs, la liste partielle garde sa
374
+ * valeur et l'incident est journalisé. Rendre `[]` en silence serait le pire des deux — un relevé
375
+ * vide se lit comme « ce marché ne cote rien », pas comme « je n'ai pas pu demander ».
376
+ */
377
+ prices(...xexes: XgateEx[]): Promise<IPrice[]>;
378
+ /** Les prix d'UNE venue, traduits vers {@link IPrice}. */
379
+ private pricesOf;
380
+ }
381
+
382
+ /**
383
+ * LE CATALOGUE COMPTANT, séparé du catalogue perpétuel.
384
+ *
385
+ * **Deux services, pas un service à drapeau.** Un `catalog(xex, { spot: true })` serait plus court
386
+ * à écrire et plus dangereux à lire : on croirait interroger des perpétuels et on interrogerait du
387
+ * comptant, sans que rien ne le signale. Ici, le nom de la classe dit ce qu'on lit.
388
+ *
389
+ * Le comptant est **minoritaire** dans la famille : deux venues sur dix le servent. Ce n'est pas un
390
+ * manque à combler, c'est la réalité d'un ensemble de SDK bâti pour le perpétuel.
391
+ */
392
+ declare class SpotCatalogService {
393
+ private readonly logger;
394
+ /** Les venues dont XGate sert le comptant. */
395
+ venues(): readonly XgateEx[];
396
+ /**
397
+ * Le catalogue **spot** d'une ou plusieurs venues, en une seule liste.
398
+ *
399
+ * Même politique d'échec que le catalogue perpétuel : sur une seule venue, l'échec est relancé ;
400
+ * sur plusieurs, la liste partielle garde sa valeur et l'incident est journalisé.
401
+ *
402
+ * **Une venue sans comptant lève**, elle ne rend pas une liste vide : « cette venue n'a pas de
403
+ * spot » et « cette venue ne cote rien au comptant » ne se corrigent pas de la même façon.
404
+ */
405
+ catalog(...xexes: XgateEx[]): Promise<IPair[]>;
406
+ /** Le catalogue comptant d'UNE venue, traduit vers {@link IPair}. */
407
+ private catalogOf;
408
+ }
409
+
410
+ /** Une paire, telle qu'un SDK de venue la rend. */
411
+ interface IXexPair {
412
+ name: string;
413
+ /**
414
+ * L'actif de base, **tel que la venue le NOMME**. Décisif chez binance et bybit : leur symbole
415
+ * concatène la cotation (`BTCUSDT`), et re-parser cette chaîne donnerait un faux canonique sur
416
+ * tout ce qui n'est pas coté en USDT — `ROSEBTC` deviendrait `ROSEBTC` au lieu de `ROSE`.
417
+ */
418
+ base: string;
419
+ quote: string;
420
+ kind: string;
421
+ ref?: string | null;
422
+ szDecimals: number;
423
+ tickSize?: string;
424
+ stepSize?: string;
425
+ minQty?: string;
426
+ minNotional?: string;
427
+ maxLeverage?: number;
428
+ status?: string;
429
+ delisted?: boolean;
430
+ listedAt?: Date;
431
+ xtras?: Record<string, unknown>;
432
+ }
433
+ /**
434
+ * Une bougie, telle qu'un SDK de venue la rend : **clés longues et `Date`**.
435
+ *
436
+ * Les clés courtes (`t`, `T`, `o`, `qv`…) restent au niveau du wire, dans chaque SDK. Elles ne
437
+ * remontent plus jusqu'ici — un `candle.T` ne se lit pas, et un `number` ne dit pas s'il compte des
438
+ * secondes ou des millisecondes.
439
+ */
440
+ interface IXexCandle {
441
+ openedAt: Date;
442
+ closedAt: Date;
443
+ symbol: string;
444
+ interval: string;
445
+ open: string;
446
+ close: string;
447
+ high: string;
448
+ low: string;
449
+ volume: string;
450
+ trades: number;
451
+ kind: string;
452
+ quoteVolume: string | null;
453
+ takerBuyBaseVolume: string | null;
454
+ takerBuyQuoteVolume: string | null;
455
+ xtras?: Record<string, unknown>;
456
+ }
457
+ /**
458
+ * Une cotation, telle qu'un SDK de venue la rend : **clés longues et `Date`**, comme
459
+ * {@link IXexCandle}. Le `time: number` d'origine est devenu `quotedAt: Date` — un nombre nu ne dit
460
+ * pas s'il compte des secondes ou des millisecondes, et les deux existent selon les venues.
461
+ */
462
+ interface IXexPrice {
463
+ name: string;
464
+ kind: string;
465
+ mark: string | null;
466
+ oracle: string | null;
467
+ mid: string | null;
468
+ bid: string | null;
469
+ ask: string | null;
470
+ last: string | null;
471
+ funding: string | null;
472
+ openInterest: string | null;
473
+ volume24h: string | null;
474
+ prevDayPrice: string | null;
475
+ quotedAt: Date | null;
476
+ xtras?: Record<string, unknown>;
477
+ }
478
+ /**
479
+ * Ce qu'XGate attend d'une façade de venue **côté COMPTANT**.
480
+ *
481
+ * **Volontairement séparé d'{@link IXexSource}, et non fondu dedans avec un paramètre.** Un
482
+ * `perp()` qui servirait parfois du spot selon un drapeau serait une source d'erreurs silencieuses :
483
+ * on croirait lire des perpétuels et on lirait du comptant, sans que rien ne le signale. Deux
484
+ * contrats, deux registres, deux services — l'appelant choisit explicitement ce qu'il interroge.
485
+ *
486
+ * **Toutes les venues n'ont pas de spot** : c'est même minoritaire. Le registre lève pour celles qui
487
+ * n'en servent pas, plutôt que de rendre une liste vide qui se lirait comme « ce marché ne cote
488
+ * rien ».
489
+ */
490
+ interface IXexSpotSource {
491
+ spot(): {
492
+ getPairs(): Promise<IXexPair[]>;
493
+ getCandles?(query: {
494
+ name: string;
495
+ interval: string;
496
+ startTime?: Date;
497
+ endTime?: Date;
498
+ limit?: number;
499
+ }): Promise<IXexCandle[]>;
500
+ getPrices?(): Promise<IXexPrice[]>;
501
+ };
502
+ }
503
+ /**
504
+ * De quoi lire UN compte chez une venue.
505
+ *
506
+ * **Un sac d'accès volontairement lâche**, dont chaque venue prend ce qu'il lui faut : les huit
507
+ * n'ont pas la même notion d'identité. Un DEX se lit à l'adresse ; lighter veut un index de compte,
508
+ * extended une clé d'API plus un coffre, blofin un triplet clé/secret/passphrase. Un type strict par
509
+ * venue obligerait l'appelant à connaître ces huit formes — c'est précisément ce qu'XGate existe
510
+ * pour lui épargner.
511
+ *
512
+ * Le registre **lève** quand ce qu'il faut manque, avec le nom du champ attendu : mieux vaut un
513
+ * message précis qu'un compte vide qui se lirait comme « pas d'argent ».
514
+ */
515
+ interface IXexAccess {
516
+ /** L'adresse publique du compte. Suffit seule sur les DEX en lecture. */
517
+ address?: string;
518
+ privateKey?: string;
519
+ /**
520
+ * Le réseau de la venue. **Défaut `mainnet`** — c'est le cas courant, et un défaut `testnet`
521
+ * ferait lire des soldes fantômes sans que personne ne s'en aperçoive.
522
+ *
523
+ * Il devient indispensable dès qu'on écrit : sans lui, `TradingService` ne pourrait ouvrir que sur
524
+ * de l'argent réel, et le cycle ne serait exerçable nulle part.
525
+ */
526
+ network?: 'mainnet' | 'testnet';
527
+ apiKey?: string;
528
+ secret?: string;
529
+ passphrase?: string;
530
+ accountIndex?: number;
531
+ apiKeyIndex?: number;
532
+ vault?: number;
533
+ }
534
+ /**
535
+ * Les venues dont XGate sait lire le portefeuille aujourd'hui.
536
+ *
537
+ * 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.
539
+ */
540
+ declare const WALLET_XEXES: readonly XgateEx[];
541
+ /**
542
+ * Les venues dont XGate sert le **comptant**. Les autres n'ont pas de marché spot exploitable, ou
543
+ * ne l'exposent pas — et une venue absente d'ici n'est pas un oubli, c'est un fait mesuré.
544
+ */
545
+ declare const SPOT_XEXES: readonly XgateEx[];
546
+ /** Cette venue sert-elle du comptant via XGate ? */
547
+ declare function servesSpot(xex: XgateEx): boolean;
548
+
549
+ /**
550
+ * UN SOLDE, TEL QU'XGATE LE REND.
551
+ *
552
+ * `xex` dit d'où vient la ligne : sans lui, un relevé multi-venues additionnerait des USDC détenus
553
+ * à des endroits différents, et on ne saurait plus lequel est mobilisable où.
554
+ *
555
+ * **Tous les montants sont des `string`**, jamais des `number` : ce sont des décimaux exacts venus
556
+ * de la venue, et les passer en flottant perdrait des décimales avant le premier calcul.
557
+ */
558
+ interface IBalance {
559
+ /** La venue qui détient ce solde. */
560
+ xex: XgateEx;
561
+ /** L'actif (`USDC`, `USDT`). */
562
+ asset: string;
563
+ /**
564
+ * Le solde total. Attention : chez la plupart des venues perp, il porte l'**equity** — dépôt plus
565
+ * PnL latent — et non le seul dépôt. Deux venues ne comptent donc pas forcément la même chose.
566
+ */
567
+ total: string;
568
+ /** Ce qui est mobilisable maintenant ; `null` si la venue ne le distingue pas. */
569
+ available: string | null;
570
+ /** Contre-valeur en dollars, quand la venue la publie. */
571
+ usdValue: string | null;
572
+ /** Champs natifs hors cœur unifié. */
573
+ xtras?: Record<string, unknown>;
574
+ }
575
+ /** Nature normalisée d'un mouvement d'argent. */
576
+ type MovementKind = 'deposit' | 'withdraw' | 'transfer';
577
+ /** État d'un mouvement. **Seul `completed` a réellement déplacé de l'argent.** */
578
+ type MovementStatus = 'completed' | 'pending' | 'failed';
579
+ /**
580
+ * UN DÉPLACEMENT D'ARGENT, entrant ou sortant.
581
+ *
582
+ * **Le montant est SIGNÉ** — `+` entrant, `-` sortant — alors que plusieurs venues le rendent
583
+ * toujours positif et n'indiquent le sens que par un type natif. Sans ce signe, la somme des
584
+ * mouvements ne donnerait pas la variation du solde, ce qui est pourtant le seul usage sérieux de
585
+ * cette liste.
586
+ *
587
+ * Les frais sont **exclus** du montant et portés à part : les mélanger empêcherait de rapprocher un
588
+ * dépôt de ce que la contrepartie a réellement envoyé.
589
+ */
590
+ interface IMovement {
591
+ /** La venue où le mouvement a eu lieu. */
592
+ xex: XgateEx;
593
+ /** Nature normalisée (le type natif exact reste dans `xtras`). */
594
+ kind: MovementKind;
595
+ /** Montant **signé**, frais exclus. */
596
+ amount: string;
597
+ /** Frais prélevés par la venue, positifs ; `'0'` si aucun ou non publié. */
598
+ fee: string;
599
+ /** Actif déplacé. */
600
+ asset: string;
601
+ /** Moment où le mouvement a eu lieu. */
602
+ occurredAt: Date;
603
+ /** État côté venue. */
604
+ status: MovementStatus;
605
+ /**
606
+ * Clé de déduplication **stable** : hash de transaction ou identifiant natif quand la venue en
607
+ * publie un, sinon une clé dérivée. C'est ce qui rend un import idempotent — relire la même
608
+ * fenêtre deux fois ne doit pas créer de doublon.
609
+ */
610
+ externalId: string;
611
+ /** Champs natifs hors cœur unifié. */
612
+ xtras?: Record<string, unknown>;
613
+ }
614
+ /**
615
+ * UN POINT DE VALEUR DE COMPTE dans le temps.
616
+ *
617
+ * **Seules hyperliquid et pacifica le publient.** Les six autres venues n'ont rien d'équivalent, et
618
+ * XGate ne le fabrique pas : reconstituer une courbe depuis les mouvements et les prix produirait
619
+ * une valeur qu'aucune venue n'a cotée, avec une méthode différente de celle des deux qui la
620
+ * publient — donc incomparable avec elles.
621
+ */
622
+ interface IEquityPoint {
623
+ /** La venue mesurée. */
624
+ xex: XgateEx;
625
+ /** Moment de la mesure. */
626
+ measuredAt: Date;
627
+ /** Valeur du compte, marquée au marché. */
628
+ equity: number;
629
+ }
630
+
631
+ /** Une venue et de quoi y lire un compte. */
632
+ interface IWalletAccess extends IXexAccess {
633
+ xex: XgateEx;
634
+ }
635
+ /**
636
+ * LE PORTEFEUILLE, toutes venues confondues : soldes, mouvements d'argent, valeur du compte.
637
+ *
638
+ * **Les positions n'en font pas partie** — elles appartiennent au bloc trading, indissociable des
639
+ * ordres. Un portefeuille répond à « qu'est-ce que j'ai et d'où ça vient » ; une position répond à
640
+ * « qu'est-ce que je risque », et les deux ne se lisent pas au même moment.
641
+ *
642
+ * **Ce service ne calcule rien.** Il n'additionne pas les soldes entre venues, ne convertit aucun
643
+ * montant, ne reconstitue aucune courbe d'équité là où la venue n'en publie pas. Chaque ligne porte
644
+ * son `xex` : c'est à l'appelant de décider ce qui s'additionne, parce que lui seul sait si deux
645
+ * USDC à deux endroits sont fongibles pour son usage.
646
+ *
647
+ * **Lire un portefeuille est PUBLIC sur les DEX** : l'adresse suffit, aucune signature n'est
648
+ * produite. Seuls les CEX exigent une clé, même en lecture.
649
+ */
650
+ declare class WalletService {
651
+ private readonly logger;
652
+ /** Les venues dont le portefeuille est lisible aujourd'hui. */
653
+ venues(): readonly XgateEx[];
654
+ /**
655
+ * Les soldes d'un ou plusieurs comptes.
656
+ *
657
+ * Même politique d'échec que les autres services : sur un seul accès, l'échec est relancé ; sur
658
+ * plusieurs, la liste partielle garde sa valeur et l'incident est journalisé. Rendre `[]` en
659
+ * silence ferait passer une venue injoignable pour un compte vide — et un compte vide, ça se
660
+ * regarde autrement qu'une panne.
661
+ */
662
+ balances(...accesses: IWalletAccess[]): Promise<IBalance[]>;
663
+ /**
664
+ * Les mouvements d'argent, triés du plus ancien au plus récent, montants **signés**.
665
+ *
666
+ * **Lève si la venue ne les sert pas** plutôt que de rendre une liste vide : « cette venue
667
+ * n'expose pas son historique » et « ce compte n'a jamais bougé » appellent des suites très
668
+ * différentes.
669
+ */
670
+ movements(...accesses: IWalletAccess[]): Promise<IMovement[]>;
671
+ /**
672
+ * La valeur du compte dans le temps.
673
+ *
674
+ * **Seules hyperliquid et pacifica la publient**, et XGate ne la fabrique pas ailleurs :
675
+ * reconstituer une courbe depuis les mouvements et les prix donnerait une valeur qu'aucune venue
676
+ * n'a cotée, avec une méthode incomparable à celle des deux qui la publient. Lève sur une venue
677
+ * qui n'en sert pas.
678
+ */
679
+ equityHistory(...accesses: IWalletAccess[]): Promise<IEquityPoint[]>;
680
+ /** Le motif commun aux trois lectures : un accès seul relance, plusieurs journalisent. */
681
+ private collect;
682
+ }
683
+
684
+ /** Sens de la position. Le SL et les TP se posent au sens OPPOSÉ. */
685
+ type Direction = 'long' | 'short';
686
+ /** Un ordre de protection, prêt à être posé : déclenchement, taille, borne d'exécution. */
687
+ interface IProtectionLeg {
688
+ /** Prix auquel l'ordre se déclenche. */
689
+ triggerPrice: string;
690
+ /** Taille **en unités de base**. */
691
+ size: string;
692
+ /**
693
+ * Borne d'exécution du déclenché : sous le trigger en long, au-dessus en short. Hyperliquid
694
+ * l'exige ; les autres venues l'ignorent sans que ce soit une erreur.
695
+ */
696
+ price: string;
697
+ }
698
+ /** Ce qu'xgate rend : un SL pleine taille, et N take-profits partiels. */
699
+ interface IProtection {
700
+ sl: IProtectionLeg;
701
+ tps: IProtectionLeg[];
702
+ }
703
+ /**
704
+ * Ce que l'appelant fournit. **Les pourcentages sont des écarts au prix d'entrée**, en fraction
705
+ * (0.01 = 1 %) : `slPct: 0.02` place le stop 2 % sous l'entrée en long, 2 % au-dessus en short.
706
+ */
707
+ interface IProtectionInput {
708
+ /** Prix d'entrée de référence. */
709
+ entry: number;
710
+ direction: Direction;
711
+ /** Taille de la position, **en unités de base**. */
712
+ size: number;
713
+ /** Écart du stop au prix d'entrée, en fraction. Toujours du côté perdant. */
714
+ slPct: number;
715
+ /**
716
+ * Les take-profits, dans l'ordre. `pct` est l'écart au prix d'entrée (côté gagnant) et `part` la
717
+ * fraction de la position à sortir à ce niveau.
718
+ *
719
+ * **Une part à 0 ne produit AUCUN leg** — c'est un marqueur (un palier de breakeven, par exemple),
720
+ * pas un ordre. Sauf s'il s'agit du dernier : celui-là clôture toujours.
721
+ */
722
+ tps: Array<{
723
+ pct: number;
724
+ part: number;
725
+ }>;
726
+ /** Pas de prix de la paire. */
727
+ tickSize?: string | null;
728
+ /** Pas de quantité de la paire. */
729
+ lotSize?: string | null;
730
+ /**
731
+ * Écart entre le déclenchement et la borne d'exécution, en fraction (défaut 0,5 %). Le déclenché
732
+ * sort SOUS son trigger en long, AU-DESSUS en short.
733
+ */
734
+ slippagePct?: number;
735
+ }
736
+ /**
737
+ * Arrondit à un pas. `floor` pour les tailles — jamais `nearest` : arrondir au plus proche ferait
738
+ * dépasser la somme des legs au-delà de la position, et la venue rejetterait le dernier.
739
+ */
740
+ declare function roundToStep(value: number, step: string | null | undefined, mode: 'floor' | 'nearest'): number;
741
+ /**
742
+ * Arrondit un PRIX à la grille de la paire : `tickSize` s'il est fourni, sinon la règle Hyperliquid
743
+ * — cinq chiffres significatifs ET au plus `6 − szDecimals` décimales, `szDecimals` étant déduit du
744
+ * pas de quantité. Sans cette seconde branche, une venue sans tick fixe reçoit un prix qu'elle
745
+ * refuse.
746
+ */
747
+ declare function roundPrice(value: number, tickSize?: string | null, lotSize?: string | null): number;
748
+ /**
749
+ * LES NIVEAUX DE PROTECTION, calculés depuis des POURCENTAGES.
750
+ *
751
+ * L'appelant donne un prix d'entrée, un sens et des écarts ; il ne calcule aucun prix lui-même.
752
+ * Le stop part du côté perdant, les take-profits du côté gagnant — le sens s'en occupe.
753
+ */
754
+ declare function buildLevels(entry: number, direction: Direction, slPct: number, tpPcts: readonly number[]): {
755
+ sl: number;
756
+ tps: number[];
757
+ };
758
+ /**
759
+ * LA PROTECTION COMPLÈTE — un stop pleine taille et N take-profits partiels, prêts à poser.
760
+ *
761
+ * **GARANTIE ANTI-RÉSIDU, sous condition de seuil.** Quand les parts somment à **plus de 80 %**, le
762
+ * DERNIER take-profit prend le reliquat EXACT (`size − Σ des précédents`) et la position ferme
763
+ * entièrement : trois paliers à 33 % font 99 %, et ce 1 % restant est un arrondi, pas une intention.
764
+ *
765
+ * **À 80 % ou en dessous, on laisse courir.** Deux paliers à 33 % sortent 66 % et laissent 34 % de
766
+ * position ouverte — c'est une sortie partielle voulue, et la compléter d'office la trahirait. Le
767
+ * dernier palier prend alors sa part nominale, sans plus.
768
+ *
769
+ * Les paliers intermédiaires s'arrondissent au **plancher**, jamais au plus proche : arrondir au
770
+ * plus proche ferait dépasser la somme, et c'est le dernier leg — celui qui clôture — que la venue
771
+ * rejetterait.
772
+ *
773
+ * **LE STOP COUVRE TOUJOURS LA TAILLE PLEINE.** Un stop partiel laisserait une fraction de position
774
+ * sans protection, ce qui est précisément ce qu'on veut rendre impossible : jamais une position
775
+ * nue, même partiellement.
776
+ *
777
+ * Deux cas font disparaître un leg, et c'est voulu :
778
+ * · une part à 0 sur un palier intermédiaire — c'est un marqueur, pas un ordre ;
779
+ * · une taille qui tombe à 0 après arrondi — un ordre de taille nulle serait refusé.
780
+ * Le dernier palier, lui, survit toujours : c'est lui qui ferme.
781
+ */
782
+ declare function buildProtection(input: IProtectionInput): IProtection;
783
+
784
+ /** Côté d'un ordre. À ne pas confondre avec le SENS d'une position ({@link Direction}). */
785
+ type Side = 'buy' | 'sell';
786
+ /** Nature d'un ordre, telle qu'XGate la nomme. Le type natif exact reste dans `xtras`. */
787
+ type OrderType = 'limit' | 'market' | 'stop' | 'stopMarket' | 'takeProfit' | 'takeProfitMarket' | 'other';
788
+ /**
789
+ * ÉTAT D'UN ORDRE — **exactement les sept valeurs des SDK de venue**, orthographe comprise.
790
+ *
791
+ * `canceled` prend un seul L : c'est ce que rendent les huit SDK, unanimement. XGate écrivait
792
+ * `cancelled` et perdait `partiallyFilled` et `expired` ; le `as OrderStatus` de la conversion
793
+ * masquait l'écart au lieu de le signaler, si bien qu'un consommateur testant `=== 'cancelled'`
794
+ * ne matchait **jamais** — un ordre annulé passait pour un ordre encore vivant.
795
+ *
796
+ * Seul `filled` a déplacé tout ce qui était demandé ; `partiallyFilled` en a déplacé une part et
797
+ * garde le reste au carnet.
798
+ */
799
+ type OrderStatus = 'open' | 'partiallyFilled' | 'filled' | 'canceled' | 'rejected' | 'expired' | 'other';
800
+ /**
801
+ * Les sept statuts, énumérés — pour VALIDER ce qu'une venue rend plutôt que de le caster.
802
+ *
803
+ * Un `as OrderStatus` sur une chaîne inconnue produit une valeur qui n'existe dans aucun `switch` et
804
+ * que personne ne rattrape ; passer par cette liste fait retomber l'inconnu sur `other`, qui lui est
805
+ * traité partout.
806
+ */
807
+ declare const ORDER_STATUSES: readonly OrderStatus[];
808
+ /** Durée de validité. `alo` = post-only : l'ordre est refusé s'il devait s'exécuter immédiatement. */
809
+ type TimeInForce = 'gtc' | 'ioc' | 'fok' | 'alo';
810
+ /**
811
+ * UN ORDRE, tel qu'XGate le rend.
812
+ *
813
+ * `xex` dit d'où il vient : un identifiant d'ordre n'a de sens que chez la venue qui l'a émis, et
814
+ * deux venues peuvent parfaitement produire le même.
815
+ */
816
+ interface IOrder {
817
+ xex: XgateEx;
818
+ /** Le symbole que la venue attend. */
819
+ symbolXex: string;
820
+ /** Type de marché. */
821
+ kind: 'perp' | 'spot';
822
+ /** L'identifiant de la venue. */
823
+ id: string;
824
+ /** L'identifiant fourni par l'appelant, quand il en a donné un. */
825
+ clientId: string | null;
826
+ side: Side;
827
+ type: OrderType;
828
+ /** `null` pour un ordre au marché. */
829
+ price: string | null;
830
+ /** Taille demandée, **en unités de base**. */
831
+ size: string;
832
+ /** Taille déjà exécutée, **en unités de base**. */
833
+ filled: string;
834
+ status: OrderStatus;
835
+ tif: TimeInForce | null;
836
+ /** L'ordre ne peut que réduire une position existante — jamais en ouvrir une. */
837
+ reduceOnly: boolean | null;
838
+ /** Moment où l'ordre a été soumis. */
839
+ placedAt: Date;
840
+ xtras?: Record<string, unknown>;
841
+ }
842
+ /**
843
+ * UNE POSITION ouverte.
844
+ *
845
+ * `size` est **toujours positive** : le sens est porté par `side`. Mélanger les deux — une taille
846
+ * négative pour un short — oblige chaque consommateur à se souvenir de la convention, et l'un d'eux
847
+ * l'oubliera.
848
+ */
849
+ interface IPosition {
850
+ xex: XgateEx;
851
+ symbolXex: string;
852
+ /** Nom canonique de l'actif, pour rapprocher la même position entre venues. */
853
+ symbol: string;
854
+ side: 'long' | 'short' | null;
855
+ /** Taille **en unités de base**, toujours positive. */
856
+ size: string;
857
+ entryPrice: string | null;
858
+ markPrice: string | null;
859
+ unrealizedPnl: string | null;
860
+ leverage: number | null;
861
+ liquidationPrice: string | null;
862
+ /** Marge immobilisée par la position. */
863
+ margin: string | null;
864
+ xtras?: Record<string, unknown>;
865
+ }
866
+ /** UNE EXÉCUTION du compte — ce qui a réellement été rempli, par opposition à ce qui a été demandé. */
867
+ interface ITrade {
868
+ xex: XgateEx;
869
+ symbolXex: string;
870
+ kind: 'perp' | 'spot';
871
+ id: string;
872
+ /** L'ordre dont provient cette exécution. */
873
+ orderId: string;
874
+ side: Side;
875
+ price: string;
876
+ size: string;
877
+ fee: string;
878
+ feeAsset: string | null;
879
+ /** PnL réalisé, avant et après frais ; `null` quand la venue ne permet pas de l'établir. */
880
+ grossPnl: string | null;
881
+ netPnl: string | null;
882
+ /** L'exécution était côté maker. `null` si la venue ne le dit pas. */
883
+ maker: boolean | null;
884
+ /** Moment de l'exécution. */
885
+ filledAt: Date;
886
+ xtras?: Record<string, unknown>;
887
+ }
888
+ /**
889
+ * CE QU'ON DEMANDE POUR OUVRIR UNE POSITION PROTÉGÉE.
890
+ *
891
+ * **Les protections sont données en POURCENTAGES**, pas en prix : xgate calcule les niveaux depuis
892
+ * le prix d'entrée. L'appelant n'a ni à arrondir au tick, ni à se souvenir que le stop d'un short
893
+ * est au-dessus de l'entrée.
894
+ *
895
+ * **`slPct` est OBLIGATOIRE.** C'est la règle qui a motivé tout ce chemin : jamais un ordre sans
896
+ * stop. Une position ouverte pendant que le serveur tombe reste protégée par la venue elle-même.
897
+ */
898
+ interface IEntryWithProtection {
899
+ xex: XgateEx;
900
+ /** Le symbole que la venue attend. */
901
+ symbolXex: string;
902
+ /** Sens de la POSITION à ouvrir. Le stop et les take-profits se posent au sens opposé. */
903
+ direction: Direction;
904
+ /** Taille à ouvrir, **en unités de base**. */
905
+ size: number;
906
+ /** Prix d'entrée visé. Sert de référence aux pourcentages, et de prix limite. */
907
+ entry: number;
908
+ /** Écart du stop au prix d'entrée, en fraction (0.02 = 2 %). Obligatoire. */
909
+ slPct: number;
910
+ /**
911
+ * Les take-profits, dans l'ordre. `part` est la fraction de la position à sortir.
912
+ *
913
+ * Au-dessus de 80 % cumulés, le dernier absorbe le reliquat et la position ferme entièrement ;
914
+ * à 80 % ou en dessous, le reste court.
915
+ */
916
+ tps: Array<{
917
+ pct: number;
918
+ part: number;
919
+ }>;
920
+ /** `ioc` pour entrer au marché (taker), `alo` pour poster (maker). Défaut : `ioc`. */
921
+ tif?: TimeInForce;
922
+ /** Identifiant applicatif, remonté tel quel par la venue quand elle le supporte. */
923
+ clientId?: string;
924
+ /** Pas de prix et de quantité de la paire — sans eux, la venue refuse des niveaux mal arrondis. */
925
+ tickSize?: string | null;
926
+ lotSize?: string | null;
927
+ /** Écart entre déclenchement et borne d'exécution du déclenché. Défaut 0,5 %. */
928
+ slippagePct?: number;
929
+ }
930
+
931
+ /** Une venue et de quoi y agir. */
932
+ interface ITradingAccess extends IXexAccess {
933
+ xex: XgateEx;
934
+ }
935
+ /**
936
+ * LE TRADING : positions, ordres et exécutions — un seul service, et c'est délibéré.
937
+ *
938
+ * Les séparer laisserait diverger trois vues du même état : une position n'est que la somme des
939
+ * ordres exécutés, et un ordre ne se lit pas sans savoir ce qu'il a ouvert.
940
+ *
941
+ * **JAMAIS UN ORDRE SANS STOP.** `openWithProtection` est la seule façon d'ouvrir une position par
942
+ * ce service, et elle exige un `slPct`. La protection part avec l'entrée, en un geste que la venue
943
+ * traite atomiquement : si l'entrée ne remplit pas, la protection ne naît jamais — aucun orphelin.
944
+ * Poser le stop *après* l'entrée laisse une fenêtre pendant laquelle la position est nue, et cette
945
+ * fenêtre s'ouvre précisément quand le serveur tombe.
946
+ *
947
+ * **L'appelant donne des POURCENTAGES**, jamais des prix : xgate calcule les niveaux, arrondit à la
948
+ * grille de la paire, et garantit qu'au-dessus de 80 % de parts cumulées la position ferme
949
+ * entièrement — sans reliquat.
950
+ */
951
+ declare class TradingService {
952
+ private readonly logger;
953
+ /**
954
+ * OUVRE UNE POSITION AVEC SA PROTECTION, en un geste atomique.
955
+ *
956
+ * Le premier ordre rendu est **l'entrée** ; les suivants sont le stop et les take-profits, dans
957
+ * l'ordre où ils ont été posés.
958
+ *
959
+ * Chaque venue applique son mécanisme natif — hyperliquid groupe les enfants sous l'entrée et les
960
+ * annule lui-même si elle rate, pacifica les embarque dans l'ordre, aster envoie un lot de
961
+ * conditionnels. Le SDK de la venue s'en charge ; xgate fournit des niveaux justes.
962
+ */
963
+ openWithProtection(access: ITradingAccess, input: IEntryWithProtection): Promise<IOrder[]>;
964
+ /** Les positions ouvertes d'un compte. */
965
+ positions(access: ITradingAccess, symbolXex?: string): Promise<IPosition[]>;
966
+ /** Les ordres encore au carnet. */
967
+ openOrders(access: ITradingAccess): Promise<IOrder[]>;
968
+ /**
969
+ * Les exécutions du compte — ce qui a réellement été rempli.
970
+ *
971
+ * À ne pas confondre avec les ordres : un ordre annoncé `filled` peut avoir été rempli en
972
+ * plusieurs fois, à des prix différents. C'est ici que se lit le prix moyen réel.
973
+ */
974
+ trades(access: ITradingAccess): Promise<ITrade[]>;
975
+ /**
976
+ * FERME UNE POSITION ENTIÈREMENT, sur la taille que **la venue** déclare — jamais celle qu'on croit.
977
+ *
978
+ * C'est toute la différence : fermer la taille planifiée laisse un reliquat quand le fill a différé du
979
+ * plan (une entrée partiellement remplie, un take-profit passé entre-temps), et ce reliquat reste
980
+ * ouvert, sans stop, à la charge du compte. On relit donc la position juste avant de la fermer.
981
+ *
982
+ * Rend `null` quand il n'y a rien à fermer — un appel sur une position déjà close n'est pas une erreur.
983
+ */
984
+ closePosition(access: ITradingAccess, symbolXex: string): Promise<IOrder | null>;
985
+ /**
986
+ * LE PRIX DE SORTIE RÉEL d'une position, lu dans l'historique de la venue.
987
+ *
988
+ * Le prix planifié ne dit pas à quel prix on est sorti : un stop se déclenche au marché, un
989
+ * take-profit peut remplir en plusieurs fois. Seul l'historique porte le prix réellement obtenu, et
990
+ * c'est lui qui doit servir au calcul du résultat — sinon le PnL affiché est une fiction.
991
+ *
992
+ * On retient le **dernier** ordre de sortie rempli après `openedAt` : les précédents sont les
993
+ * take-profits partiels, celui-là est la clôture.
994
+ */
995
+ exitPrice(access: ITradingAccess, symbolXex: string, direction: Direction, openedAt: Date): Promise<string | null>;
996
+ /** Annule un ordre par son identifiant de venue. */
997
+ cancel(access: ITradingAccess, symbolXex: string, orderId: string): Promise<void>;
998
+ /** Le scope `perp()` de la venue, typé au plus près de ce que le contrat commun garantit. */
999
+ private perpOf;
1000
+ /**
1001
+ * Le statut de la venue, VALIDÉ — jamais casté. Une venue qui inventerait un statut rendrait sinon
1002
+ * une valeur absente de tous les `switch` en aval, et personne ne la rattraperait.
1003
+ */
1004
+ private toStatus;
1005
+ private toOrder;
1006
+ }
1007
+
1008
+ /** La venue sert-elle cet intervalle de première main ? */
1009
+ declare function servesNatively(xex: XgateEx, interval: Timeframe): boolean;
1010
+ /**
1011
+ * L'intervalle SOURCE à demander pour fabriquer `target` chez cette venue.
1012
+ *
1013
+ * On prend le **plus grand** intervalle servi nativement qui divise la cible : le plus grand,
1014
+ * parce qu'il minimise le nombre de bougies à tirer — reconstruire une semaine depuis des minutes
1015
+ * demanderait 10 080 points là où sept journalières suffisent.
1016
+ *
1017
+ * `undefined` = la venue ne sert rien d'exploitable, il n'y a pas de fabrication possible.
1018
+ */
1019
+ declare function sourceFor(xex: XgateEx, target: Timeframe): Timeframe | undefined;
1020
+
1021
+ /**
1022
+ * SYMBOLE BRUT D'UNE VENUE → CANONIQUE.
1023
+ *
1024
+ * DÉPEND DE LA VENUE, pour ne JAMAIS amputer un stablecoin ni un actif finissant par USD(T/C) :
1025
+ * - aster/binance/bybit : quote concaténé → on strippe `USDT`/`USDC` FINAL (USDEUSDT→USDE, BTCUSDT→BTC) ;
1026
+ * - les autres : uniquement les suffixes SÉPARÉS (`-USD-PERP`, `-PERP`, `-USDT`, `-USDC`, `-USD`). Jamais de
1027
+ * strip nu → un actif nu (USDC, USDT, USDE, BUSD, BTC) reste intact ;
1028
+ * - dans tous les cas, le multiplicateur tombe d'abord (kPEPE → PEPE).
1029
+ *
1030
+ * `-USDT` A ÉTÉ AJOUTÉ le 2026-08-05, en agrégeant blofin pour la première fois : elle cote
1031
+ * `BTC-USDT` avec un tiret, forme qu'aucune venue de Blips n'utilisait. Sans lui, **477 des 487
1032
+ * symboles blofin restaient non canonisés** (`BTC-USDT` au lieu de `BTC`) et aucun ne se
1033
+ * rapprochait de son homologue chez une autre venue. Le tiret rend le strip sûr : l'actif nu
1034
+ * `USDT` n'y correspond pas.
1035
+ */
1036
+ declare function canonicalFromXex(rawSymbol: string, xexId: string): string;
1037
+ /**
1038
+ * ACTIF DE BASE NU → CANONIQUE, pour les venues qui NOMMENT leur actif au lieu de le concaténer : binance rend
1039
+ * `baseAsset`, bybit `baseCoin`. On prend ce qu'elles DISENT plutôt que de re-parser leur symbole — sans quoi
1040
+ * `ROSEBTC` (cotée en BTC, pas en USDT) donnerait le faux canonique `ROSEBTC`.
1041
+ *
1042
+ * Mêmes règles que `canonicalFromXex` MOINS le strip de cotation : l'appliquer à une base nue amputerait les
1043
+ * stablecoins (`USDC` → chaîne VIDE). C'est exactement le piège que la connaissance de la venue évite déjà côté
1044
+ * symbole.
1045
+ */
1046
+ declare function canonicalFromBase(base: string): string;
1047
+
1048
+ export { CandlesService, CatalogService, type Direction, type IBalance, type ICandle, type ICandlesQuery, type IEntryWithProtection, type IEquityPoint, 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 };