@blackcube/xgate-sdk 0.25.4 → 0.56.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/helpers/symbol-naming.ts","../src/helpers/candle-mapper.ts","../src/helpers/socket-error.ts","../src/helpers/ws-limits.ts","../src/enums/xgate-ex.enums.ts","../src/helpers/xex-registry.ts","../src/services/candles-stream.service.ts","../src/enums/timeframe.enums.ts","../src/helpers/decimal.ts","../src/helpers/candle-aggregator.ts","../src/helpers/candle-query.ts","../src/helpers/xex-timeframes.ts","../src/services/candles.service.ts","../src/helpers/contract-expiry.ts","../src/services/catalog.service.ts","../src/services/prices.service.ts","../src/services/spot-candles.service.ts","../src/services/spot-catalog.service.ts","../src/services/spot-prices.service.ts","../src/services/spot-ws-candles.service.ts","../src/helpers/protection.ts","../src/interfaces/trading.interfaces.ts","../src/services/trading.service.ts","../src/services/wallet.service.ts","../src/services/ws-candles.service.ts","../src/services/ws-trades.service.ts","../src/xgate.module.ts","../src/helpers/rest-limits.ts"],"names":["XgateEx","Hyperliquid","Pacifica","Lighter","Aster","Blofin","Extended","Paradex","Bullet","Binance","Bybit","Logger","CandlesStreamRegistry","Injectable","Timeframe","CandlesService","CatalogService","PricesService","SpotCandlesService","SpotCatalogService","SpotPricesService","SpotWsCandlesService","TradingService","randomUUID","WalletService","WsCandlesService","WsTradesService","XgateModule","Module"],"mappings":"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","file":"index.cjs","sourcesContent":["// Repris tel quel de `Blips/server/src/shared/symbol-naming.ts` (2026-08-05). Ce code a été écrit\n// et éprouvé côté consommateur, mais sa place est ICI : à la frontière des venues, pas dans\n// l'application qui les consomme. Zéro dépendance — le « nom CoinMarketCap » est la convention\n// visée, jamais un appel réseau.\n//\n// Normalisation des symboles. Un même actif porte une forme différente sur chaque venue :\n// kPEPE (hyperliquid/pacifica/paradex) · 1000PEPEUSDT (aster) · 1000PEPE (lighter) · 1000PEPE-USD (extended)\n// Tous donnent le MÊME canonique : `PEPE` — le nom de l'actif chez CoinMarketCap, sans multiplicateur de cotation.\n// Le catalogue porte DEUX symboles par ligne :\n// - symbolXex : le BRUT de la venue (ce qu'on lui envoie : souscription, ordre, fetch REST) ;\n// - symbol : le CANONIQUE (clé interne : oracle, signaux, UI).\n// Ce fichier est l'unique frontière de conversion.\n\n// Venues « binance-like » : le quote est CONCATÉNÉ à l'actif (BTCUSDT, USDEUSDT). On strippe le quote final.\n// Ailleurs (hyperliquid/pacifica/lighter/extended/paradex) : soit actif nu (BTC, USDC), soit quote SÉPARÉ (BTC-USD).\nconst CONCAT_QUOTE_XEXS = new Set(['aster', 'binance', 'bybit']);\n\n// LE MULTIPLICATEUR DE COTATION DISPARAÎT DU CANONIQUE (règle Philippe, 2026-08-02).\n//\n// Les memecoins se cotent par paquets — `kPEPE` (hyperliquid/pacifica/paradex), `1000PEPE` (aster/bybit/lighter/\n// extended), `10000SATS`, `1000000BABYDOGE` — mais c'est une UNITÉ DE COTATION, pas un actif différent : même\n// token, même capitalisation. Le canonique doit être le nom de l'actif, c'est-à-dire celui de CoinMarketCap.\n//\n// MESURÉ le 2026-08-02 sur le top 1000 CMC : aucun `1000*` n'y existe. `PEPE` est au rang 48, `SATS` au 552\n// (« SATS (Ordinals) » — bien le token que binance cote `1000SATSUSDT`), `BONK`, `SHIB`, `FLOKI`, `BTT`, `LUNC`,\n// `XEC` tous sans préfixe. Sans ce strip, aucun de ces actifs ne se rattacherait à son classement.\n//\n// Deux formes, deux règles :\n// - `k` MINUSCULE en tête suivi d'une majuscule, retiré AVANT l'uppercase — sinon on amputerait les vrais actifs\n// en K majuscule (KAVA, KAS, KSM, KNC) ;\n// - `1` suivi d'AU MOINS TROIS zéros (1000, 10000, 1000000). Trois au minimum : `100X` ou `10X` resteraient\n// intacts, et aucun actif mesuré ne commence réellement par `1000`.\nconst K_MULTIPLIER = /^k(?=[A-Z])/u;\nconst NUMERIC_MULTIPLIER = /^10{3,}(?=[A-Z0-9])/u;\n\n/**\n * `1M` / `1K` EN TÊTE — l'échelle écrite en abrégé plutôt qu'en zéros.\n *\n * binance cote `1MBABYDOGEUSDT` là où bybit cote `1000000BABYDOGEUSDT` : **même actif, même\n * échelle, deux écritures**. Sans cette règle, binance produisait le canonique `1MBABYDOGE` et\n * bybit `BABYDOGE` — deux lignes qui ne se rejoignaient jamais, alors qu'elles cotent la même\n * chose au même facteur.\n *\n * **Deux lettres majuscules au minimum derrière**, et c'est délibéré : `1INCH` ne matche pas (`I`\n * n'est ni `M` ni `K`), et un hypothétique `1MX` resterait intact. Le risque inverse — amputer un\n * token qui commencerait réellement par `1M` — n'existe sur aucune des 7 865 lignes mesurées.\n */\nconst ABBREV_MULTIPLIER = /^1([MK])(?=[A-Z]{2,})/u;\n\n/** Facteurs d'échelle, par la forme écrite dans le nom. */\nconst ABBREV_FACTOR: Record<string, number> = { M: 1_000_000, K: 1_000 };\n\n// Marqueur « 24/5 » d'extended : contrats actions/ETF (cotés 24h/5j, vs crypto 24/7). Ex. `AAPL_24_5-USD`. On le\n// strippe à la normalisation (AAPL_24_5 → AAPL) pour obtenir le ticker canonique ; le type d'actif vient ensuite\n// du catalogue.\nconst STOCK_24_5 = /_24_5$/u;\n\n// Variantes d'un même actif → canonique unique, pour consolider entre venues (un actif listé sous des noms\n// différents par chacune : ex. S&P 500 = US500/SPX500M/SP500). Appliqué APRÈS strip du suffixe et uppercase.\nconst TICKER_ALIASES: Record<string, string> = {\n GOOG: 'GOOGL', // classe d'action (extended `GOOG` ≡ `GOOGL` ailleurs)\n // Indices\n US500: 'SP500',\n SPX500M: 'SP500',\n TECH100M: 'US100',\n // Matières premières\n BRENTOIL: 'BRENT',\n NATGAS: 'NG',\n // Actions (USD collé sur lighter, non strippé → aliasé explicitement)\n SAMSUNGUSD: 'SAMSUNG',\n SKHYNIXUSD: 'SKHYNIX',\n HYUNDAIUSD: 'HYUNDAI',\n};\n\n/**\n * SYMBOLE BRUT D'UNE VENUE → CANONIQUE.\n *\n * DÉPEND DE LA VENUE, pour ne JAMAIS amputer un stablecoin ni un actif finissant par USD(T/C) :\n * - aster/binance/bybit : quote concaténé → on strippe `USDT`/`USDC` FINAL (USDEUSDT→USDE, BTCUSDT→BTC) ;\n * - les autres : uniquement les suffixes SÉPARÉS (`-USD-PERP`, `-PERP`, `-USDT`, `-USDC`, `-USD`). Jamais de\n * strip nu → un actif nu (USDC, USDT, USDE, BUSD, BTC) reste intact ;\n * - dans tous les cas, le multiplicateur tombe d'abord (kPEPE → PEPE).\n *\n * `-USDT` A ÉTÉ AJOUTÉ le 2026-08-05, en agrégeant blofin pour la première fois : elle cote\n * `BTC-USDT` avec un tiret, forme qu'aucune venue de Blips n'utilisait. Sans lui, **477 des 487\n * symboles blofin restaient non canonisés** (`BTC-USDT` au lieu de `BTC`) et aucun ne se\n * rapprochait de son homologue chez une autre venue. Le tiret rend le strip sûr : l'actif nu\n * `USDT` n'y correspond pas.\n */\nexport function canonicalFromXex(rawSymbol: string, xexId: string): string {\n let s = rawSymbol.replace(K_MULTIPLIER, '').toUpperCase(); // kPEPE → PEPE (KAVA intact)\n if (CONCAT_QUOTE_XEXS.has(xexId)) {\n s = s.replace(/(USDT|USDC)$/u, ''); // quote concaténé final (le reste = actif, même si stablecoin)\n } else {\n s = s\n .replace(/-USD-PERP$/u, '')\n .replace(/-PERP$/u, '')\n .replace(/-USDT$/u, '') // blofin cote `BTC-USDT` (tiret + USDT) — cf. note ci-dessous\n .replace(/-USDC$/u, '')\n .replace(/-USD$/u, ''); // quotes SÉPARÉS uniquement → stablecoins nus préservés\n }\n s = s.replace(NUMERIC_MULTIPLIER, ''); // 1000PEPE → PEPE, 10000SATS → SATS, 1000000BABYDOGE → BABYDOGE\n s = s.replace(ABBREV_MULTIPLIER, ''); // 1MBABYDOGE → BABYDOGE (binance l'écrit en abrégé)\n s = s.replace(STOCK_24_5, ''); // contrat 24/5 extended (AAPL_24_5 → AAPL). Inoffensif ailleurs.\n return TICKER_ALIASES[s] ?? s; // variantes de classe (GOOG → GOOGL)\n}\n\n/**\n * ACTIF DE BASE NU → CANONIQUE, pour les venues qui NOMMENT leur actif au lieu de le concaténer : binance rend\n * `baseAsset`, bybit `baseCoin`. On prend ce qu'elles DISENT plutôt que de re-parser leur symbole — sans quoi\n * `ROSEBTC` (cotée en BTC, pas en USDT) donnerait le faux canonique `ROSEBTC`.\n *\n * Mêmes règles que `canonicalFromXex` MOINS le strip de cotation : l'appliquer à une base nue amputerait les\n * stablecoins (`USDC` → chaîne VIDE). C'est exactement le piège que la connaissance de la venue évite déjà côté\n * symbole.\n */\nexport function canonicalFromBase(base: string): string {\n const s = base\n .replace(K_MULTIPLIER, '') // kPEPE → PEPE, avant l'uppercase (KAVA intact)\n .toUpperCase()\n .replace(NUMERIC_MULTIPLIER, '') // 1000SATS → SATS (le nom CMC de l'actif)\n .replace(ABBREV_MULTIPLIER, '') // 1MBABYDOGE → BABYDOGE\n .replace(STOCK_24_5, '');\n return TICKER_ALIASES[s] ?? s;\n}\n\n/**\n * LE MULTIPLICATEUR DE COTATION, extrait du nom — **la seule source qui existe**.\n *\n * Aucune venue ne le publie : `kPEPE` chez hyperliquid ne porte que `{\"marginTableId\":52}`,\n * `1000PEPEUSDT` chez binance ne mentionne aucun facteur. L'information n'est QUE dans le préfixe\n * du nom, et le canonique la fait disparaître — jusqu'ici sans la conserver nulle part.\n *\n * Or elle est indispensable dès qu'on compare des prix entre venues. `SATS` porte **trois échelles\n * différentes sur quatre sources** : `SATSUSDT` (×1) chez bybit-spot, `1000SATSUSDT` (×1000) chez\n * binance, `10000SATSUSDT` (×10000) chez bybit. Une médiane calculée sur ces prix bruts ne veut\n * rien dire ; divisés par leur facteur, ils redeviennent comparables.\n *\n * Rend `1` quand le nom ne porte aucune échelle — le cas de l'immense majorité.\n */\nexport function multiplierFromXex(rawSymbol: string): number {\n if (K_MULTIPLIER.test(rawSymbol)) {\n return 1_000; // `k` minuscule : convention hyperliquid/pacifica/paradex\n }\n const upper = rawSymbol.toUpperCase();\n const abbrev = ABBREV_MULTIPLIER.exec(upper);\n if (abbrev !== null) {\n return ABBREV_FACTOR[abbrev[1] as string] ?? 1;\n }\n const zeros = NUMERIC_MULTIPLIER.exec(upper);\n return zeros === null ? 1 : Number(zeros[0]);\n}\n","import type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport type { ICandle } from '../interfaces/candle.interfaces';\nimport type { MarketKind } from '../interfaces/pair.interfaces';\nimport { canonicalFromXex } from './symbol-naming';\nimport type { IXexCandle } from './xex-registry';\n\n/**\n * Une bougie de venue → une bougie XGate.\n *\n * Deux traductions, et rien d'autre — le mapper ne calcule aucune valeur :\n *\n * - **les temps deviennent des `Date`.** Les SDK rendent `t`/`T` en millisecondes ; personne ne\n * devrait avoir à savoir dans quelle unité compte quelle venue.\n * - **`n: 0` devient `trades: null`.** Les venues qui ne comptent pas les transactions posent `0`\n * dans le cœur des SDK (convention de lighter, extended, paradex, drift, blofin). Un zéro se lit\n * « aucune transaction sur ce créneau », ce qui est une information — et elle serait fausse.\n * L'absence se dit `null`.\n */\nexport function toCandle(\n wire: IXexCandle,\n xex: XgateEx,\n interval: Timeframe,\n quote: string,\n): ICandle {\n return {\n symbol: canonicalFromXex(wire.symbol, xex),\n quote,\n symbolXex: wire.symbol,\n xex,\n kind: wire.kind as MarketKind,\n interval,\n openedAt: wire.openedAt,\n closedAt: wire.closedAt,\n open: wire.open,\n high: wire.high,\n low: wire.low,\n close: wire.close,\n volume: wire.volume,\n quoteVolume: wire.quoteVolume,\n takerBuyQuoteVolume: wire.takerBuyQuoteVolume,\n trades: wire.trades > 0 ? wire.trades : null,\n // Servie par la venue, pas fabriquée. L'agrégation posera `true`.\n derived: false,\n };\n}\n","/**\n * Un incident de socket en texte lisible.\n *\n * `String(error)` sur un `ErrorEvent` rend `[object ErrorEvent]` — un journal où toutes les pannes\n * se ressemblent. Les sockets viennent de `globalThis.WebSocket`, donc de l'undici embarqué dans\n * Node, dont l'événement d'erreur n'est **pas** une `Error` : ni pile, ni message. On va chercher\n * ce que l'événement porte réellement, dans l'ordre où les implémentations le renseignent.\n *\n * Partagée par les deux services de flux ({@link WsCandlesService}, {@link CandlesStreamService}) :\n * un incident doit se lire pareil quel que soit le service qui l'a reçu.\n */\nexport function lisible(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n const event = error as { message?: unknown; error?: unknown; type?: unknown } | null;\n if (event !== null && typeof event === 'object') {\n const message = event.message ?? (event.error as { message?: unknown } | undefined)?.message;\n if (typeof message === 'string' && message !== '') {\n return message;\n }\n if (typeof event.type === 'string' && event.type !== '') {\n return `événement « ${event.type} »`;\n }\n }\n return String(error);\n}\n","import type { XgateEx } from '../enums/xgate-ex.enums';\n\n/**\n * LES PLAFONDS DE SOUSCRIPTION, PAR VENUE — uniquement ceux qu'une documentation officielle donne.\n *\n * Repris de `Blips/server/src/candles/services/internal/dex-candles-stream.service.ts`, où ils ont\n * été établis puis vérifiés en réel.\n *\n * **aster**, section « Websocket Market Streams » : « A single connection can listen to a maximum\n * of 200 streams », et « A connection that goes beyond the limit will be disconnected; IPs that are\n * repeatedly disconnected may be banned ». Vérifié le 2026-08-03 sur `fstream.asterdex.com` : 200\n * tiennent, 300 tuent la connexion.\n * <https://github.com/asterdex/api-docs/blob/master/V3(Recommended)/EN/aster-finance-futures-api-v3.md>\n *\n * **hyperliquid** : 1 000 souscriptions et 10 connexions par IP.\n *\n * **Une venue ABSENTE de cette table n'a pas de plafond DOCUMENTÉ** — ce qui n'est pas la même\n * chose que « pas de plafond ». On ne devine aucun chiffre : le jour où l'une d'elles s'en\n * approche, on lit sa documentation et on l'ajoute.\n */\nexport const WS_SUBSCRIPTION_LIMITS: Partial<Record<XgateEx, number>> = {\n aster: 200,\n hyperliquid: 1_000,\n} as Partial<Record<XgateEx, number>>;\n\n/**\n * Le plafond documenté d'une venue, ou `undefined` si personne ne l'a publié.\n *\n * Rendre `Infinity` par défaut serait un mensonge commode : l'absence de plafond connu n'est pas\n * une garantie d'illimité.\n */\nexport function subscriptionLimitOf(xex: XgateEx): number | undefined {\n return WS_SUBSCRIPTION_LIMITS[xex];\n}\n","/**\n * Les venues qu'XGate sait interroger.\n *\n * Une seule énumération pour les CEX et les DEX : la distinction ne change rien à l'API — on\n * interroge un catalogue de la même façon des deux côtés. Elle deviendra une donnée portée par la\n * venue le jour où quelque chose en dépendra.\n *\n * **`drift` en est absent**, et ce n'est pas un oubli : son SDK ne porte aucun test (seul de la\n * famille), sa précision de taille est un placeholder assumé, et son catalogue est une constante\n * figée dans le paquet. On ne l'agrège pas tant qu'il n'est pas vérifiable.\n */\nexport enum XgateEx {\n Hyperliquid = 'hyperliquid',\n Pacifica = 'pacifica',\n Aster = 'aster',\n Lighter = 'lighter',\n Extended = 'extended',\n Paradex = 'paradex',\n Bullet = 'bullet',\n Blofin = 'blofin',\n /**\n * Les deux seules venues dont XGate sert aussi le **comptant** (cf. `IXexSpotSource`). Leurs SDK\n * ne couvrent que la lecture publique : catalogue, bougies et prix. Toute route signée y lève.\n */\n Binance = 'binance',\n Bybit = 'bybit',\n}\n\n/** Toutes les venues connues, dans l'ordre de déclaration. */\nexport const XGATE_EXCHANGES: readonly XgateEx[] = Object.values(XgateEx);\n","import { Aster } from '@blackcube/aster-sdk';\nimport { Binance } from '@blackcube/binance-sdk';\nimport { Blofin } from '@blackcube/blofin-sdk';\nimport { Bullet } from '@blackcube/bullet-sdk';\nimport { Bybit } from '@blackcube/bybit-sdk';\nimport { Extended } from '@blackcube/extended-sdk';\nimport { Hyperliquid } from '@blackcube/hyperliquid-sdk';\nimport { Lighter } from '@blackcube/lighter-sdk';\nimport { Pacifica } from '@blackcube/pacifica-sdk';\nimport { Paradex } from '@blackcube/paradex-sdk';\nimport { XgateEx } from '../enums/xgate-ex.enums';\n\n/** Une paire, telle qu'un SDK de venue la rend. */\nexport interface IXexPair {\n name: string;\n /**\n * L'actif de base, **tel que la venue le NOMME**. Décisif chez binance et bybit : leur symbole\n * concatène la cotation (`BTCUSDT`), et re-parser cette chaîne donnerait un faux canonique sur\n * tout ce qui n'est pas coté en USDT — `ROSEBTC` deviendrait `ROSEBTC` au lieu de `ROSE`.\n */\n base: string;\n quote: string;\n kind: string;\n ref?: string | null;\n szDecimals: number;\n tickSize?: string;\n stepSize?: string;\n minQty?: string;\n minNotional?: string;\n maxLeverage?: number;\n status?: string;\n delisted?: boolean;\n listedAt?: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * Une bougie, telle qu'un SDK de venue la rend : **clés longues et `Date`**.\n *\n * Les clés courtes (`t`, `T`, `o`, `qv`…) restent au niveau du wire, dans chaque SDK. Elles ne\n * remontent plus jusqu'ici — un `candle.T` ne se lit pas, et un `number` ne dit pas s'il compte des\n * secondes ou des millisecondes.\n */\nexport interface IXexCandle {\n openedAt: Date;\n closedAt: Date;\n symbol: string;\n interval: string;\n open: string;\n close: string;\n high: string;\n low: string;\n volume: string;\n trades: number;\n kind: string;\n quoteVolume: string | null;\n takerBuyBaseVolume: string | null;\n takerBuyQuoteVolume: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * Une cotation, telle qu'un SDK de venue la rend : **clés longues et `Date`**, comme\n * {@link IXexCandle}. Le `time: number` d'origine est devenu `quotedAt: Date` — un nombre nu ne dit\n * pas s'il compte des secondes ou des millisecondes, et les deux existent selon les venues.\n */\nexport interface IXexPrice {\n name: string;\n kind: string;\n mark: string | null;\n oracle: string | null;\n mid: string | null;\n bid: string | null;\n ask: string | null;\n last: string | null;\n funding: string | null;\n openInterest: string | null;\n volume24h: string | null;\n prevDayPrice: string | null;\n quotedAt: Date | null;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * Ce qu'XGate attend d'une façade de venue. Le plus petit dénominateur réellement commun aux huit\n * — mesuré, pas supposé.\n *\n * **`getCandles` est OPTIONNEL**, et le type doit le dire : bullet ne l'expose pas\n * (`perp().getCandles is not a function`, vérifié le 2026-08-05), il ne sert ses bougies qu'en\n * WebSocket. Le déclarer requis ferait échouer la compilation sur une venue pourtant valide pour\n * le catalogue.\n *\n * Les dates y sont des **chaînes** (`\"YYYY-MM-DD HH:MM:SS\"` UTC) chez huit venues et des\n * millisecondes chez blofin : c'est la frontière que les services traduisent.\n */\nexport interface IXexSource {\n perp(): {\n getPairs(): Promise<IXexPair[]>;\n getCandles?(query: {\n name: string;\n interval: string;\n /** Un `Date` : c'est au SDK de la venue de le traduire dans son format à elle. */\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n }): Promise<IXexCandle[]>;\n getPrices(): Promise<IXexPrice[]>;\n };\n ws(): {\n subscribeCandles(\n query: { name: string; interval: string },\n handler: (candle: IXexCandle) => void,\n ): () => void;\n /**\n * Notifié à chaque erreur socket. **Optionnel : binance et bybit ne l'exposent pas encore.**\n *\n * Sans lui, un symbole invalide est invisible — la venue accepte la souscription puis ferme la\n * connexion, le SDK reconnecte, elle referme. Une boucle silencieuse où les marchés valides du\n * même socket cessent eux aussi de recevoir.\n */\n onError?: ((error: unknown) => void) | null;\n /** Notifié à chaque fermeture, avant une éventuelle reconnexion. Même réserve. */\n onClose?: (() => void) | null;\n };\n}\n\n/**\n * Ce qu'XGate attend d'une façade de venue **côté COMPTANT**.\n *\n * **Volontairement séparé d'{@link IXexSource}, et non fondu dedans avec un paramètre.** Un\n * `perp()` qui servirait parfois du spot selon un drapeau serait une source d'erreurs silencieuses :\n * on croirait lire des perpétuels et on lirait du comptant, sans que rien ne le signale. Deux\n * contrats, deux registres, deux services — l'appelant choisit explicitement ce qu'il interroge.\n *\n * **Toutes les venues n'ont pas de spot** : c'est même minoritaire. Le registre lève pour celles qui\n * n'en servent pas, plutôt que de rendre une liste vide qui se lirait comme « ce marché ne cote\n * rien ».\n */\nexport interface IXexSpotSource {\n spot(): {\n getPairs(): Promise<IXexPair[]>;\n getCandles?(query: {\n name: string;\n interval: string;\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n }): Promise<IXexCandle[]>;\n getPrices?(): Promise<IXexPrice[]>;\n };\n /**\n * Le temps réel du COMPTANT. **`kind` est obligatoire ici**, contrairement aux façades qui le\n * rendent optionnel : leur défaut est `'perp'`, et un `ws()` nu ouvre donc un flux de perpétuels\n * en croyant écouter le comptant — deux marchés dont les prix diffèrent, sans que rien ne le\n * signale.\n */\n ws(kind: 'spot'): {\n subscribeCandles(\n query: { name: string; interval: string },\n handler: (candle: IXexCandle) => void,\n ): () => void;\n };\n}\n\n/**\n * De quoi lire UN compte chez une venue.\n *\n * **Un sac d'accès volontairement lâche**, dont chaque venue prend ce qu'il lui faut : les huit\n * n'ont pas la même notion d'identité. Un DEX se lit à l'adresse ; lighter veut un index de compte,\n * extended une clé d'API plus un coffre, blofin un triplet clé/secret/passphrase. Un type strict par\n * venue obligerait l'appelant à connaître ces huit formes — c'est précisément ce qu'XGate existe\n * pour lui épargner.\n *\n * Le registre **lève** quand ce qu'il faut manque, avec le nom du champ attendu : mieux vaut un\n * message précis qu'un compte vide qui se lirait comme « pas d'argent ».\n */\nexport interface IXexAccess {\n /** L'adresse publique du compte. Suffit seule sur les DEX en lecture. */\n address?: string;\n privateKey?: string;\n /**\n * Le réseau de la venue. **Défaut `mainnet`** — c'est le cas courant, et un défaut `testnet`\n * ferait lire des soldes fantômes sans que personne ne s'en aperçoive.\n *\n * Il devient indispensable dès qu'on écrit : sans lui, `TradingService` ne pourrait ouvrir que sur\n * de l'argent réel, et le cycle ne serait exerçable nulle part.\n */\n network?: 'mainnet' | 'testnet';\n apiKey?: string;\n secret?: string;\n passphrase?: string;\n accountIndex?: number;\n apiKeyIndex?: number;\n vault?: number;\n}\n\n/** Un solde tel qu'un SDK de venue le rend. */\nexport interface IXexBalance {\n asset: string;\n total: string;\n available: string | null;\n usdValue: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/** Un mouvement tel qu'un SDK de venue le rend : **clés longues et `Date`**. */\nexport interface IXexMovement {\n kind: string;\n amount: string;\n fee: string;\n asset: string;\n occurredAt: Date;\n status: string;\n externalId: string;\n xtras?: Record<string, unknown>;\n}\n\n/** Un point d'équité tel qu'un SDK de venue le rend. */\nexport interface IXexEquityPoint {\n measuredAt: Date;\n equity: number;\n}\n\n/**\n * Ce qu'XGate attend d'une façade de venue **côté COMPTE**.\n *\n * `getBalances` est le seul point commun aux huit — c'est donc le seul requis. `getMovements` manque\n * encore là où la venue ne l'expose pas, et `getEquityHistory` n'est publié que par hyperliquid et\n * pacifica : les déclarer requis ferait échouer la compilation sur des venues pourtant valides.\n */\nexport interface IXexAccountSource {\n account(): {\n getBalances(): Promise<IXexBalance[]>;\n getMovements?(query?: unknown): Promise<IXexMovement[]>;\n getEquityHistory?(range?: unknown): Promise<IXexEquityPoint[]>;\n };\n}\n\n/**\n * Instancie la façade d'une venue **avec un accès de compte**, en LECTURE.\n *\n * Sur les DEX, lire un portefeuille est **public** : l'adresse suffit et aucune signature n'est\n * produite — d'où la clé privée nulle passée à hyperliquid, aster, paradex et bullet, dont le\n * constructeur l'exige sans que la lecture s'en serve. Les CEX (blofin) réclament en revanche un\n * vrai triplet, même pour lire.\n */\nexport function createAccountXex(xex: XgateEx, access: IXexAccess): IXexAccountSource {\n const NULL_KEY = `0x${'0'.repeat(64)}`;\n const adresse = (): string => {\n if (access.address === undefined || access.address === '') {\n throw new Error(`wallet(${xex}) : il faut au moins une adresse (\\`access.address\\`).`);\n }\n return access.address;\n };\n switch (xex) {\n case XgateEx.Hyperliquid:\n return new Hyperliquid(\n {\n a: {\n privateKey: (access.privateKey ?? NULL_KEY) as `0x${string}`,\n publicKey: adresse() as `0x${string}`,\n network: access.network ?? 'mainnet',\n },\n },\n { default: 'a' },\n ) as unknown as IXexAccountSource;\n case XgateEx.Pacifica:\n // `secretKey` est exigée par le type du signer mais ne sert qu'à SIGNER : la chaîne vide\n // suffit pour lire, comme la clé nulle côté EVM.\n return new Pacifica(\n {\n a: {\n secretKey: access.privateKey ?? '',\n publicKey: adresse(),\n network: access.network ?? 'mainnet',\n },\n },\n { default: 'a' },\n ) as unknown as IXexAccountSource;\n case XgateEx.Lighter:\n // Lighter se lit **sans clé** : sa documentation classe `/account`, `accountsByL1Address` et\n // `positions` parmi les routes sans authentification, et `/account` accepte aussi bien un\n // index de sous-compte qu'une adresse L1. L'adresse suffit donc, comme sur les autres DEX —\n // `accountIndex` ne sert qu'à viser un sous-compte précis.\n return new Lighter(\n {\n a: {\n apiPrivateKey: access.privateKey ?? NULL_KEY,\n apiKeyIndex: access.apiKeyIndex ?? 0,\n accountIndex: access.accountIndex ?? 0,\n l1Address: adresse(),\n network: access.network ?? 'mainnet',\n },\n },\n { default: 'a' },\n ) as unknown as IXexAccountSource;\n case XgateEx.Aster:\n // Aster signe avec une clé d'API pour le compte d'une ADRESSE EVM : les deux sont exigées, et\n // l'adresse n'est pas déductible de la clé (modèle agent/master — l'agent signe, le master\n // détient). Sans `privateKey`, la façade se construirait et échouerait à la première écriture.\n if (access.privateKey === undefined || access.privateKey === '') {\n throw new Error(\n `wallet(${xex}) : Aster signe avec une clé d'API — il faut \\`privateKey\\` (la clé) et \\`address\\` (le compte EVM).`,\n );\n }\n return new Aster(\n {\n a: {\n privateKey: access.privateKey,\n user: adresse(),\n network: access.network ?? 'mainnet',\n },\n },\n { default: 'a' },\n ) as unknown as IXexAccountSource;\n case XgateEx.Blofin:\n if (access.apiKey === undefined || access.secret === undefined) {\n throw new Error(\n `wallet(${xex}) : BloFin signe même en lecture — il faut \\`apiKey\\`, \\`secret\\` et \\`passphrase\\`.`,\n );\n }\n // BloFin nomme son second réseau « demo trading » : seul l'hôte change, l'API est identique.\n // Sans ce réseau, une clé de démo tape le mainnet et la venue répond « Access key does not\n // exist » — un message qui accuse la clé alors que c'est l'adresse qui est fausse.\n return new Blofin({\n credentials: {\n apiKey: access.apiKey,\n secret: access.secret,\n passphrase: access.passphrase ?? '',\n },\n network: access.network ?? 'mainnet',\n }) as unknown as IXexAccountSource;\n default:\n throw new Error(\n `wallet(${xex}) : venue pas encore câblée côté compte. Disponibles : ${WALLET_XEXES.join(', ')}.`,\n );\n }\n}\n\n/**\n * Les venues dont XGate sait lire le portefeuille aujourd'hui.\n *\n * Les autres ne sont pas exclues par principe : il leur manque un accès (clé d'API pour bullet, une\n * adresse active pour extended et paradex). Voir le backlog.\n */\nexport const WALLET_XEXES: readonly XgateEx[] = [\n XgateEx.Hyperliquid,\n XgateEx.Pacifica,\n XgateEx.Aster,\n XgateEx.Lighter,\n XgateEx.Blofin,\n];\n\n/** Conservé pour la lisibilité des appels de catalogue. */\nexport type IXexCatalogSource = IXexSource;\n\n/**\n * Instancie la façade d'une venue, en **lecture publique** : aucun credential.\n *\n * Un `switch` explicite plutôt qu'une table de constructeurs, parce que les signatures ne\n * coïncident pas — les DEX prennent `(signers, options)`, BloFin prend `(options)` seul. C'est\n * exactement l'écart que la normalisation doit résorber ; en attendant, il vit ici, à un seul\n * endroit, plutôt que chez chaque appelant.\n */\nexport function createPublicXex(xex: XgateEx): IXexSource {\n switch (xex) {\n case XgateEx.Hyperliquid:\n return new Hyperliquid();\n case XgateEx.Pacifica:\n return new Pacifica();\n case XgateEx.Aster:\n return new Aster();\n case XgateEx.Lighter:\n return new Lighter();\n case XgateEx.Extended:\n return new Extended();\n case XgateEx.Paradex:\n return new Paradex();\n case XgateEx.Bullet:\n return new Bullet();\n case XgateEx.Blofin:\n return new Blofin();\n case XgateEx.Binance:\n return new Binance();\n case XgateEx.Bybit:\n return new Bybit();\n }\n}\n\n/**\n * Les venues dont XGate sert le **comptant**. Les autres n'ont pas de marché spot exploitable, ou\n * ne l'exposent pas — et une venue absente d'ici n'est pas un oubli, c'est un fait mesuré.\n */\nexport const SPOT_XEXES: readonly XgateEx[] = [XgateEx.Binance, XgateEx.Bybit];\n\n/** Cette venue sert-elle du comptant via XGate ? */\nexport function servesSpot(xex: XgateEx): boolean {\n return SPOT_XEXES.includes(xex);\n}\n\n/**\n * Instancie la façade **comptant** d'une venue, en lecture publique.\n *\n * **Lève sur une venue sans spot**, plutôt que de rendre une source vide : un catalogue spot vide se\n * lirait comme « cette venue ne liste rien au comptant », alors que la vraie information est\n * « cette venue n'a pas de comptant ici ». Les deux ne se corrigent pas de la même façon.\n */\nexport function createPublicSpotXex(xex: XgateEx): IXexSpotSource {\n switch (xex) {\n case XgateEx.Binance:\n return new Binance();\n case XgateEx.Bybit:\n return new Bybit();\n default:\n throw new Error(\n `spot(${xex}) : cette venue ne sert pas de comptant via XGate. Venues avec spot : ${SPOT_XEXES.join(', ')}.`,\n );\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { lisible } from '../helpers/socket-error';\nimport { subscriptionLimitOf } from '../helpers/ws-limits';\nimport {\n type IXexSource,\n type IXexSpotSource,\n createPublicSpotXex,\n createPublicXex,\n servesSpot,\n} from '../helpers/xex-registry';\nimport type { ICandle } from '../interfaces/candle.interfaces';\nimport type { MarketKind } from '../interfaces/pair.interfaces';\n\n/**\n * LE FLUX DE BOUGIES D'UNE VENUE — porté de\n * `Blips/server/src/candles/services/internal/dex-candles-stream.service.ts`, où il tourne en\n * production. Même surface, mêmes garanties ; seul le type de bougie change (celui d'XGate).\n *\n * **UNE INSTANCE PAR VENUE, DONC UNE SOCKET PAR VENUE.** La façade est mémoïsée (`instance()`) et\n * `ws()` n'est appelé qu'une fois, hors de la boucle : les N souscriptions partagent la même\n * connexion. Créer la façade à chaque souscription ouvrirait une socket par marché — 200 marchés\n * suivis, 200 sockets, quand hyperliquid n'en accepte que 10 par IP.\n *\n * **UNE SOUSCRIPTION PAR MARCHÉ, et non `subscribeAllCandles`.** Ce dernier ne coûte qu'un\n * abonnement, mais il est bâti sur un flux de prix agrégé : mesuré le 2026-08-02, il rend un prix\n * MILIEU (40 % des valeurs sur un demi-tick contre 8 % au REST) et AUCUN volume (`\"v\":\"0\"` sur les\n * six dex). Le canal `candle` par marché rend la vraie bougie de trades, avec son volume.\n *\n * **LE SYMBOLE VIENT DU MESSAGE**, jamais de la souscription. Les SDK hyperliquid et pacifica\n * livraient tout le canal à tous les abonnés jusqu'à la 0.15.0 — trois souscriptions recevaient le\n * même flux. C'est corrigé, mais la règle reste : on lit ce que le wire déclare, on ne suppose pas.\n */\nexport class CandlesStreamService {\n private readonly logger: Logger;\n private source: IXexSource | IXexSpotSource | null = null;\n private readonly unsubscribes: Array<() => void> = [];\n\n /** Le plafond de souscriptions documenté par la venue, ou `null` si personne ne l'a publié. */\n public readonly maxSubscriptions: number | null;\n\n /**\n * Notifié quand le flux meurt — après que les souscriptions ont été coupées.\n *\n * **Ce crochet a remplacé un `throw`.** Lever depuis un rappel de socket ne remonte à aucun\n * appelant : l'exception sort en `unhandledRejection` et **tue le process entier**, ce qui est\n * arrivé le 2026-08-13 sur Blips par un autre chemin. « Impossible à ignorer » et « fatal pour\n * tout le monde » ne sont pas la même chose : la première est une garantie, la seconde une panne.\n *\n * Le contrat reste franc — le flux est coupé, il ne repartira pas tout seul, et le silence est\n * journalisé en `.error` de toute façon. Mais c'est à l'appelant de décider ce qu'il en fait :\n * rouvrir, basculer de venue, ou s'arrêter.\n */\n public onFailure: ((error: Error) => void) | null = null;\n\n constructor(\n public readonly xex: XgateEx,\n /** Le marché écouté. Le comptant n'est servi que par binance et bybit. */\n public readonly kind: MarketKind = 'perp',\n ) {\n this.logger = new Logger(`CandlesStream:${xex}${kind === 'spot' ? ':spot' : ''}`);\n this.maxSubscriptions = subscriptionLimitOf(xex) ?? null;\n }\n\n /**\n * Ouvre le flux sur une LISTE de marchés, sur une seule socket.\n *\n * **UNE ERREUR DE FLUX COUPE TOUT, PUIS NOTIFIE.** Elle n'arrive pas à la souscription : mesuré le\n * 2026-08-07, la venue accepte un symbole inconnu sans broncher, puis ferme la connexion en le\n * découvrant. Le SDK reconnecte, elle referme — cinq cycles en douze secondes, et sur une socket\n * partagée **tous** les marchés valides cessent de recevoir, en silence.\n *\n * On refuse cette boucle : à la première erreur, les souscriptions sont coupées, l'incident est\n * journalisé en `error`, et {@link onFailure} est notifié. Un flux à moitié mort qui se tait\n * coûte plus cher qu'un arrêt franc — on ne découvre le premier qu'en constatant l'absence de\n * données.\n *\n * ⚠️ **On ne lève pas depuis le rappel de socket.** Une exception née là ne remonte à aucun\n * appelant de `start()` : elle sort en `unhandledRejection` et emporte le process entier — pas\n * seulement le flux fautif, mais les crons et tout ce qui tournait à côté. C'est {@link onFailure}\n * qui porte l'information, et l'appelant qui décide.\n *\n * **binance et bybit ne remontent pas encore leurs erreurs** : leur client gère la fermeture en\n * interne sans l'exposer. Sur ces deux venues, la boucle silencieuse reste possible.\n */\n public start(\n symbolsXex: string[],\n interval: Timeframe,\n onCandle: (candle: ICandle) => void,\n ): void {\n const ws = this.wire();\n if ('onError' in ws) {\n ws.onError = (error: unknown) => {\n const raison = lisible(error);\n this.logger.error(`${this.xex} : erreur de flux — ${raison}. Souscriptions coupées.`);\n this.stop();\n this.onFailure?.(new Error(`stream(${this.xex}) : flux interrompu — ${raison}`));\n };\n }\n for (const symbolXex of symbolsXex) {\n this.unsubscribes.push(\n ws.subscribeCandles({ name: symbolXex, interval }, (wire) => {\n onCandle(toCandle(wire, this.xex, interval, this.quoteOf(symbolXex)));\n }),\n );\n }\n this.logger.log(`${this.unsubscribes.length}/${symbolsXex.length} souscription(s) ouvertes`);\n }\n\n /** Coupe tout. Un désabonnement qui échoue ne doit pas empêcher les autres de se fermer. */\n public stop(): void {\n for (const unsubscribe of this.unsubscribes.splice(0)) {\n try {\n unsubscribe();\n } catch {\n // volontairement silencieux : voir la doc de la méthode\n }\n }\n }\n\n /** Le nombre de souscriptions ouvertes. La liste des désabonnements EST le compteur. */\n public subscriptionCount(): number {\n return this.unsubscribes.length;\n }\n\n /**\n * Le client temps réel, sur le bon marché.\n *\n * Les deux contrats ne signent pas `ws()` pareil, et c'est voulu : côté comptant le marché est\n * **obligatoire** (`ws('spot')`), parce que les façades ouvrent du perpétuel par défaut — un\n * `ws()` nu y diffuserait des prix de perpétuel à qui croit écouter le comptant.\n */\n private wire(): {\n subscribeCandles(\n query: { name: string; interval: string },\n handler: (candle: Parameters<typeof toCandle>[0]) => void,\n ): () => void;\n onError?: ((error: unknown) => void) | null;\n } {\n const source = this.instance();\n return this.kind === 'spot'\n ? (source as IXexSpotSource).ws('spot')\n : (source as IXexSource).ws();\n }\n\n /** La façade de la venue, créée une seule fois : c'est elle qui porte la socket partagée. */\n private instance(): IXexSource | IXexSpotSource {\n if (this.source === null) {\n if (this.kind === 'spot' && servesSpot(this.xex) === false) {\n throw new Error(`stream(${this.xex}) : cette venue ne sert pas de comptant.`);\n }\n this.source =\n this.kind === 'spot' ? createPublicSpotXex(this.xex) : createPublicXex(this.xex);\n }\n return this.source;\n }\n\n /** Voir `CandlesService.quoteOf` : la cotation se déduit du symbole natif, faute d'être publiée. */\n private quoteOf(symbolXex: string): string {\n const parts = symbolXex.split('-');\n const last = parts[parts.length - 1];\n return parts.length > 1 && last !== undefined ? last : '';\n }\n}\n\n/**\n * LA FAÇADE : un flux par venue, et un seul.\n *\n * Blips instancie `DexCandlesStreamService` une fois par venue et garde la référence. XGate offre\n * la même chose sans que l'appelant ait à tenir cette table : `of(xex)` rend **toujours la même\n * instance** pour une venue et un marché donnés — donc toujours la même socket.\n *\n * Demander deux fois `of(hyperliquid)` et souscrire 100 marchés à chaque fois ouvre **une** socket\n * avec 200 souscriptions, pas deux sockets. C'est exactement ce qui manquait.\n */\n@Injectable()\nexport class CandlesStreamRegistry {\n private readonly streams = new Map<string, CandlesStreamService>();\n\n /** Le flux d'une venue, créé au premier appel puis réutilisé. */\n public of(xex: XgateEx, kind: MarketKind = 'perp'): CandlesStreamService {\n const cle = `${xex}:${kind}`;\n let stream = this.streams.get(cle);\n if (stream === undefined) {\n stream = new CandlesStreamService(xex, kind);\n this.streams.set(cle, stream);\n }\n return stream;\n }\n\n /** Coupe tous les flux ouverts, toutes venues confondues. */\n public stopAll(): void {\n for (const stream of this.streams.values()) {\n stream.stop();\n }\n }\n\n /** Ce qui est ouvert, par venue — pour surveiller sans avoir à tenir de compteur soi-même. */\n public counts(): Record<string, number> {\n const total: Record<string, number> = {};\n for (const [cle, stream] of this.streams) {\n total[cle] = stream.subscriptionCount();\n }\n return total;\n }\n}\n","/**\n * Les intervalles de bougies qu'XGate promet — **les mêmes que Blips**, mêmes noms, mêmes valeurs\n * (`Blips/server/src/shared/timeframe.enum.ts`). Le consommateur n'a pas à traduire pour parler à\n * la passerelle, ni la passerelle à inventer une échelle concurrente.\n *\n * Toutes les venues ne les servent pas : paradex s'arrête à l'heure (`resolution: must be one of\n * [1 3 5 15 30 60]`, dit la venue), lighter refuse `1w`, extended rend une liste vide pour ce même\n * intervalle. Ce qu'une venue ne sert pas se **fabrique par agrégation** depuis un intervalle plus\n * fin, jamais ne se réclame à l'appelant.\n */\nexport enum Timeframe {\n M1 = '1m',\n M5 = '5m',\n M15 = '15m',\n H1 = '1h',\n H4 = '4h',\n D = '1d',\n W = '1w',\n}\n\n/** Durée d'un intervalle, en minutes. */\nexport const TIMEFRAME_MINUTES: Record<Timeframe, number> = {\n [Timeframe.M1]: 1,\n [Timeframe.M5]: 5,\n [Timeframe.M15]: 15,\n [Timeframe.H1]: 60,\n [Timeframe.H4]: 240,\n [Timeframe.D]: 1440,\n [Timeframe.W]: 10080,\n};\n\n/** Durée d'un intervalle, en millisecondes. */\nexport function timeframeMs(timeframe: Timeframe): number {\n return TIMEFRAME_MINUTES[timeframe] * 60_000;\n}\n","/**\n * Addition décimale EXACTE, sur les chaînes que les venues rendent.\n *\n * XGate garde les prix et les volumes en `string` pour préserver leur précision — sommer en\n * `number` reviendrait à annuler ce choix au premier agrégat. `0.1 + 0.2` vaut\n * `0.30000000000000004` en JavaScript ; sur une semaine de volumes horaires, l'écart s'accumule.\n *\n * On lit donc la chaîne comme un entier et un nombre de décimales, on additionne en `bigint`, et\n * on reconstruit. Aucun `number` n'intervient.\n */\nfunction parse(value: string): { digits: bigint; scale: number } {\n const trimmed = value.trim();\n if (/^-?\\d+(\\.\\d+)?$/.test(trimmed) === false) {\n throw new Error(`Décimal illisible : \"${value}\".`);\n }\n const dot = trimmed.indexOf('.');\n if (dot < 0) {\n return { digits: BigInt(trimmed), scale: 0 };\n }\n return {\n digits: BigInt(trimmed.slice(0, dot) + trimmed.slice(dot + 1)),\n scale: trimmed.length - dot - 1,\n };\n}\n\nfunction format(digits: bigint, scale: number): string {\n const negative = digits < 0n;\n const plain = (negative === true ? -digits : digits).toString().padStart(scale + 1, '0');\n const sign = negative === true ? '-' : '';\n if (scale === 0) {\n return sign + plain;\n }\n const whole = plain.slice(0, plain.length - scale);\n const fraction = plain.slice(plain.length - scale).replace(/0+$/, '');\n return fraction === '' ? sign + whole : `${sign + whole}.${fraction}`;\n}\n\n/** Somme exacte de deux décimaux. Les échelles sont alignées sur la plus fine. */\nexport function addDecimals(left: string, right: string): string {\n const a = parse(left);\n const b = parse(right);\n const scale = Math.max(a.scale, b.scale);\n const digits =\n a.digits * 10n ** BigInt(scale - a.scale) + b.digits * 10n ** BigInt(scale - b.scale);\n return format(digits, scale);\n}\n\n/** Compare deux décimaux sans passer par `number` : `-1`, `0` ou `1`. */\nexport function compareDecimals(left: string, right: string): number {\n const a = parse(left);\n const b = parse(right);\n const scale = Math.max(a.scale, b.scale);\n const x = a.digits * 10n ** BigInt(scale - a.scale);\n const y = b.digits * 10n ** BigInt(scale - b.scale);\n if (x === y) {\n return 0;\n }\n return x > y ? 1 : -1;\n}\n","import { Timeframe, timeframeMs } from '../enums/timeframe.enums';\nimport type { ICandle } from '../interfaces/candle.interfaces';\nimport { addDecimals, compareDecimals } from './decimal';\n\n/**\n * L'epoch (1970-01-01) tombe un JEUDI. Aligner les semaines sur lundi 00:00 UTC impose donc de\n * décaler les seaux de quatre jours.\n */\nconst MONDAY_OFFSET_MS = 4 * 24 * 60 * 60 * 1000;\n\n/** Un seau en construction : la bougie, et de quoi juger si elle est complète. */\ninterface Bucket {\n candle: ICandle;\n sources: number;\n}\n\n/**\n * AGRÈGE des bougies fines en bougies d'un intervalle plus grand.\n *\n * Porté de `Blips/server/src/candles/helpers/internal/candle-aggregator.util.ts`, dont les règles\n * ont été payées en incidents. Deux sont contre-intuitives et se perdent si on les réécrit de\n * mémoire :\n *\n * **1. UN SEAU INCOMPLET EST REJETÉ, sauf s'il est encore ouvert.** Agréger 300 bougies horaires\n * en journalières donne un premier seau tronqué : il commence au milieu d'une journée, donc son\n * `open` n'est pas l'ouverture du jour et son volume en manque une part. Une telle bougie est\n * FAUSSE, pas approximative. Et « garder le dernier » ne suffit pas — sur paradex, 49 bougies 4h\n * sur 116 étaient closes ET incomplètes, parce que la venue ne rendait que quatre horaires\n * éparses. Être le dernier d'une série ne veut pas dire être en cours.\n *\n * **2. LE VOLUME PROPAGE L'INCONNU.** Une seule source à `null` et la somme vaut `null`. Compter\n * la source muette pour zéro donnerait un total plus petit que la réalité, sans que rien ne le\n * signale — la même faute que traiter un `null` comme un `0`.\n *\n * Les bougies rendues portent `derived: true` : personne ne les a servies telles quelles.\n */\nexport function aggregate(\n source: ICandle[],\n target: Timeframe,\n now: number = Date.now(),\n): ICandle[] {\n if (source.length === 0) {\n return [];\n }\n const sourceMs = timeframeMs(source[0]?.interval ?? Timeframe.M1);\n const targetMs = timeframeMs(target);\n if (targetMs <= sourceMs) {\n return [];\n }\n const offsetMs = target === Timeframe.W ? MONDAY_OFFSET_MS : 0;\n const expected = targetMs / sourceMs;\n const buckets = new Map<number, Bucket>();\n\n const sorted = [...source].sort(\n (left, right) => left.openedAt.getTime() - right.openedAt.getTime(),\n );\n for (const candle of sorted) {\n const start =\n Math.floor((candle.openedAt.getTime() - offsetMs) / targetMs) * targetMs + offsetMs;\n const bucket = buckets.get(start);\n if (bucket === undefined) {\n buckets.set(start, {\n sources: 1,\n candle: {\n ...candle,\n interval: target,\n openedAt: new Date(start),\n // Dernière milliseconde du créneau, comme les bougies servies par les venues.\n closedAt: new Date(start + targetMs - 1),\n derived: true,\n },\n });\n } else {\n bucket.sources += 1;\n const built = bucket.candle;\n built.high = compareDecimals(candle.high, built.high) > 0 ? candle.high : built.high;\n built.low = compareDecimals(candle.low, built.low) < 0 ? candle.low : built.low;\n // `open` reste celui de la première bougie du seau ; `close` suit la dernière.\n built.close = candle.close;\n built.volume = sum(built.volume, candle.volume);\n built.quoteVolume = sum(built.quoteVolume, candle.quoteVolume);\n built.takerBuyQuoteVolume = sum(built.takerBuyQuoteVolume, candle.takerBuyQuoteVolume);\n built.trades =\n built.trades === null || candle.trades === null ? null : built.trades + candle.trades;\n }\n }\n\n return [...buckets.keys()]\n .sort((left, right) => left - right)\n .map((start) => buckets.get(start))\n .filter((bucket): bucket is Bucket => bucket !== undefined)\n .filter((bucket) => {\n // Complet : le seau vaut ce qu'il annonce. Incomplet : acceptable seulement si la période\n // court toujours — il lui manque des bougies qui n'existent pas ENCORE.\n const stillOpen = bucket.candle.openedAt.getTime() + targetMs > now;\n return bucket.sources >= expected || stillOpen === true;\n })\n .map((bucket) => bucket.candle);\n}\n\n/** Somme qui propage l'inconnu : `null` domine. */\nfunction sum(left: string | null, right: string | null): string | null {\n if (left === null || right === null) {\n return null;\n }\n return addDecimals(left, right);\n}\n","import type { Timeframe } from '../enums/timeframe.enums';\n\n/** Ce qu'un SDK de venue attend pour une lecture de bougies. */\nexport interface IXexCandlesQuery {\n name: string;\n interval: string;\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n}\n\n/**\n * Adapte une requête XGate à la forme des SDK.\n *\n * Il ne reste plus qu'un renommage (`symbol` → `name`) : **les dates passent telles quelles**,\n * parce que les SDK prennent désormais des `Date`. Cette fonction traduisait autrefois vers des\n * chaînes UTC — et vers des millisecondes pour blofin, avec des bornes inversées. Ces conversions\n * vivent maintenant dans chaque SDK, à la frontière de sa venue, là où elles appartiennent.\n */\nexport function toXexQuery(query: {\n symbol: string;\n interval: Timeframe;\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n}): IXexCandlesQuery {\n return {\n name: query.symbol,\n interval: query.interval,\n startTime: query.startTime,\n endTime: query.endTime,\n limit: query.limit,\n };\n}\n","import { TIMEFRAME_MINUTES, Timeframe } from '../enums/timeframe.enums';\nimport { XgateEx } from '../enums/xgate-ex.enums';\n\n/**\n * CE QUE CHAQUE VENUE SERT NATIVEMENT — mesuré le 2026-08-05 contre les API, pas lu dans une doc.\n *\n * Ce qui n'y figure pas n'est pas indisponible : c'est **fabriqué par agrégation** depuis un\n * intervalle plus fin. La table dit seulement qui sert quoi de première main.\n *\n * - **paradex** s'arrête à l'heure, et c'est la venue qui le dit :\n * `resolution: must be one of [1 3 5 15 30 60]`.\n * - **lighter** refuse `1w` (`invalid param`) ; **extended** rend une liste vide pour ce même\n * intervalle — deux façons de dire non, dont une muette.\n * - **bullet** ne sert aucune bougie en REST (`perp().getCandles is not a function`) : rien à\n * agréger, la source manque entièrement.\n */\n/**\n * HYPERLIQUID SERT BIEN LA SEMAINE, MAIS ALIGNÉE SUR JEUDI — donc on ne la prend pas.\n *\n * Mesuré le 2026-08-07 : ses bougies `1w` ouvrent les jeudis (2026-07-09, 07-16, 07-23, 07-30),\n * quand binance et pacifica ouvrent les lundis. La venue aligne ses seaux sur l'epoch Unix, qui\n * tombe un jeudi ; elle n'a pas tort, elle a une autre convention.\n *\n * Le résultat serait pire qu'un manque : une semaine hyperliquid et une semaine binance porteraient\n * le même intervalle `1w` en couvrant des jours différents, et se compareraient silencieusement.\n * En l'excluant d'ici, XGate la **reconstruit depuis le journalier** — l'agrégateur, lui, décale\n * ses seaux de quatre jours pour ouvrir le lundi. Les bougies rendues portent `derived: true`.\n */\nconst HYPERLIQUID_NATIVE: readonly Timeframe[] = Object.values(Timeframe).filter(\n (interval) => interval !== Timeframe.W,\n);\n\nconst NATIVE: Record<XgateEx, readonly Timeframe[]> = {\n [XgateEx.Hyperliquid]: HYPERLIQUID_NATIVE,\n [XgateEx.Pacifica]: Object.values(Timeframe),\n [XgateEx.Aster]: Object.values(Timeframe),\n [XgateEx.Blofin]: Object.values(Timeframe),\n [XgateEx.Lighter]: [\n Timeframe.M1,\n Timeframe.M5,\n Timeframe.M15,\n Timeframe.H1,\n Timeframe.H4,\n Timeframe.D,\n ],\n [XgateEx.Extended]: [\n Timeframe.M1,\n Timeframe.M5,\n Timeframe.M15,\n Timeframe.H1,\n Timeframe.H4,\n Timeframe.D,\n ],\n [XgateEx.Paradex]: [Timeframe.M1, Timeframe.M5, Timeframe.M15, Timeframe.H1],\n [XgateEx.Bullet]: [],\n // Les deux CEX servent les sept, et sous les mêmes noms côté binance. Bybit compte en minutes\n // (`60`, `240`) puis en lettres (`D`, `W`) : son SDK traduit, la table reste dans notre grille.\n [XgateEx.Binance]: Object.values(Timeframe),\n [XgateEx.Bybit]: Object.values(Timeframe),\n};\n\n/** La venue sert-elle cet intervalle de première main ? */\nexport function servesNatively(xex: XgateEx, interval: Timeframe): boolean {\n return NATIVE[xex].includes(interval);\n}\n\n/**\n * L'intervalle SOURCE à demander pour fabriquer `target` chez cette venue.\n *\n * On prend le **plus grand** intervalle servi nativement qui divise la cible : le plus grand,\n * parce qu'il minimise le nombre de bougies à tirer — reconstruire une semaine depuis des minutes\n * demanderait 10 080 points là où sept journalières suffisent.\n *\n * `undefined` = la venue ne sert rien d'exploitable, il n'y a pas de fabrication possible.\n */\nexport function sourceFor(xex: XgateEx, target: Timeframe): Timeframe | undefined {\n const targetMinutes = TIMEFRAME_MINUTES[target];\n const candidates = NATIVE[xex]\n .filter((interval) => {\n const minutes = TIMEFRAME_MINUTES[interval];\n return minutes < targetMinutes && targetMinutes % minutes === 0;\n })\n .sort((left, right) => TIMEFRAME_MINUTES[right] - TIMEFRAME_MINUTES[left]);\n return candidates[0];\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { aggregate } from '../helpers/candle-aggregator';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { toXexQuery } from '../helpers/candle-query';\nimport { createPublicXex } from '../helpers/xex-registry';\nimport { servesNatively, sourceFor } from '../helpers/xex-timeframes';\nimport type { ICandle, ICandlesQuery } from '../interfaces/candle.interfaces';\n\n/**\n * LES BOUGIES EN REST — l'historique.\n *\n * Le temps réel vit dans `WsCandlesService` : ce sont deux besoins distincts (rejouer le passé,\n * suivre le présent), et une application qui fait du backfill n'a pas à ouvrir de socket.\n */\n@Injectable()\nexport class CandlesService {\n private readonly logger = new Logger(CandlesService.name);\n\n /**\n * Les bougies d'une venue, sur une plage de **dates**.\n *\n * L'appelant donne des `Date` et un {@link Timeframe} ; le service traduit vers ce que la venue\n * attend — chaîne UTC pour huit d'entre elles, millisecondes pour blofin.\n *\n * **Un intervalle que la venue ne sert pas est FABRIQUÉ**, pas refusé : on tire l'intervalle\n * exploitable le plus grand et on agrège. Paradex s'arrête à l'heure, lighter et extended ne\n * font pas la semaine — l'appelant n'a pas à le savoir. Les bougies fabriquées portent\n * `derived: true`.\n *\n * **Lève si la venue ne sert rien du tout** (bullet, qui n'a pas de REST). Rendre une liste vide\n * ferait passer une incapacité pour une absence de données, et un backfill silencieusement troué\n * est pire qu'un backfill en erreur.\n */\n public async candles(xex: XgateEx, query: ICandlesQuery): Promise<ICandle[]> {\n if (servesNatively(xex, query.interval) === true) {\n return this.fetch(xex, query, query.interval);\n }\n const source = sourceFor(xex, query.interval);\n if (source === undefined) {\n throw new Error(\n `candles(${xex}) : ni ${query.interval} servi, ni intervalle plus fin pour le fabriquer.`,\n );\n }\n this.logger.log(\n `${xex} ne sert pas ${query.interval} : agrégation depuis ${source} (bougies dérivées).`,\n );\n const fine = await this.fetch(xex, query, source);\n return aggregate(fine, query.interval);\n }\n\n /** Un appel REST à la venue, dans l'intervalle qu'elle sait servir. */\n private async fetch(xex: XgateEx, query: ICandlesQuery, interval: Timeframe): Promise<ICandle[]> {\n const perp = createPublicXex(xex).perp();\n if (perp.getCandles === undefined) {\n throw new Error(\n `candles(${xex}) : cette venue ne sert pas de bougies en REST — utilise WsCandlesService.`,\n );\n }\n const wires = await perp.getCandles(toXexQuery({ ...query, interval }));\n return wires.map((wire) => toCandle(wire, xex, interval, this.quoteOf(wire.symbol)));\n }\n\n /**\n * Les bougies d'un même marché chez PLUSIEURS venues, en une liste.\n *\n * Comme pour le catalogue : une venue en échec parmi d'autres est journalisée et ignorée ; seule,\n * son échec est celui de l'appel. Chaque bougie porte son `xex`, donc la liste reste\n * exploitable telle quelle.\n */\n public async candlesOf(xexes: XgateEx[], query: ICandlesQuery): Promise<ICandle[]> {\n if (xexes.length === 0) {\n throw new Error('candlesOf() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.candles(xex, query);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(\n `candles(${xex}, ${query.interval}) a échoué, venue ignorée : ${message}`,\n );\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /**\n * La cotation, déduite du symbole natif. Les bougies ne la portent pas — seul le catalogue la\n * publie —, et refaire un appel de catalogue pour chaque série coûterait plus cher que ce\n * qu'elle apporte. `BTC-USDT` → `USDT` ; à défaut de séparateur, on ne prétend rien.\n */\n private quoteOf(symbolXex: string): string {\n const parts = symbolXex.split('-');\n const last = parts[parts.length - 1];\n return parts.length > 1 && last !== undefined ? last : '';\n }\n}\n","/**\n * LES CONTRATS À ÉCHÉANCE, séparés des perpétuels — parce qu'un future daté ne cote pas le comptant.\n *\n * Le catalogue les mélangeait : 44 lignes portaient `kind: 'perp'` alors qu'elles expirent, dont\n * neuf sur le seul BTC chez bybit. Un consommateur qui prend « le prix du BTC » y ramassait une\n * échéance de décembre, dont la base au comptant peut s'écarter de plusieurs pour cent.\n *\n * **`contractType` fait foi, JAMAIS la date de livraison.** binance publie `deliveryDate:\n * 4133404800000` — le 1er janvier 2100 — sur ses **perpétuels** : s'y fier marquerait tout le\n * catalogue comme daté. bybit, lui, met `deliveryTime: '0'` sur les siens. Deux conventions\n * incompatibles pour dire « ceci n'expire pas », d'où la lecture du type et de lui seul.\n */\n\n/** Valeurs de `contractType` qui désignent un contrat **à échéance**, venue par venue. */\nconst DATED_CONTRACT_TYPES: ReadonlySet<string> = new Set([\n // binance : les trimestriels (`BTCUSDT_260925`, `ETHUSDT_261225`).\n 'CURRENT_QUARTER',\n 'NEXT_QUARTER',\n 'CURRENT_QUARTER_DELIVERING',\n 'NEXT_QUARTER_DELIVERING',\n 'PERPETUAL_DELIVERING',\n // bybit : les datés (`BTCUSDT-25DEC26`), face à `LinearPerpetual`.\n 'LinearFutures',\n 'InverseFutures',\n]);\n\n/** Champs où les venues rangent l'échéance, dans l'ordre où on les interroge. */\nconst EXPIRY_FIELDS = ['deliveryTime', 'deliveryDate', 'expiryTime', 'settleTime'] as const;\n\n/**\n * L'ÉCHÉANCE D'UN CONTRAT, ou `undefined` s'il est perpétuel.\n *\n * On ne lit la date **que** si le type de contrat annonce une échéance : c'est ce qui neutralise le\n * `deliveryDate` de l'an 2100 des perpétuels binance. Un type inconnu est traité comme perpétuel —\n * l'immense majorité des lignes, et se tromper dans ce sens n'invente pas d'échéance.\n */\nexport function expiryOf(xtras?: Record<string, unknown>): Date | undefined {\n if (xtras === undefined) {\n return undefined;\n }\n const type = xtras.contractType;\n if (typeof type !== 'string' || DATED_CONTRACT_TYPES.has(type) === false) {\n return undefined;\n }\n for (const field of EXPIRY_FIELDS) {\n const brut = xtras[field];\n const ms = typeof brut === 'string' ? Number(brut) : brut;\n if (typeof ms === 'number' && Number.isFinite(ms) === true && ms > 0) {\n return new Date(ms);\n }\n }\n // Type daté mais aucune date exploitable : on le signale par une échéance absente plutôt que\n // d'inventer un horodatage. `isDated` reste vrai, lui, et suffit à écarter la ligne.\n return undefined;\n}\n\n/**\n * LE CONTRAT EXPIRE-T-IL ? Vrai pour un future daté, faux pour un perpétuel.\n *\n * Séparé d'{@link expiryOf} à dessein : une venue peut annoncer un type daté sans publier de date\n * exploitable, et il faut alors pouvoir écarter la ligne quand même.\n */\nexport function isDated(xtras?: Record<string, unknown>): boolean {\n const type = xtras?.contractType;\n return typeof type === 'string' && DATED_CONTRACT_TYPES.has(type);\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport { XgateEx } from '../enums/xgate-ex.enums';\nimport { expiryOf } from '../helpers/contract-expiry';\nimport { canonicalFromBase, canonicalFromXex, multiplierFromXex } from '../helpers/symbol-naming';\nimport { createPublicXex } from '../helpers/xex-registry';\nimport type { IPair, MarketKind } from '../interfaces/pair.interfaces';\n\n/**\n * Les venues qui NOMMENT leur actif de base au lieu de le laisser deviner : on prend ce qu'elles\n * disent (`baseAsset` chez binance, `baseCoin` chez bybit) plutôt que de re-parser leur symbole.\n *\n * Sans cela, le strip de cotation ne connaît que `USDT` et `USDC` : `ROSEBTC` — cotée en BTC —\n * donnerait le faux canonique `ROSEBTC`. Le cas est massif sur le spot binance, dont une grande\n * partie des 3 680 marchés se cote en BTC, ETH ou BNB.\n */\nconst NAMES_ITS_BASE: ReadonlySet<XgateEx> = new Set([XgateEx.Binance, XgateEx.Bybit]);\n\n/**\n * LE CATALOGUE, toutes venues confondues.\n *\n * L'appelant ne connaît qu'{@link XgateEx} et les types unifiés ; il n'importe aucun SDK, ne\n * construit aucune façade, et ne se soucie pas de ce que chaque venue nomme comment.\n *\n * **Un service par capacité, pas une façade unique** : une application qui ne veut que le\n * catalogue n'a aucune raison d'injecter de quoi trader. Les capacités suivantes vivront dans\n * leurs propres services, injectables séparément.\n */\n@Injectable()\nexport class CatalogService {\n private readonly logger = new Logger(CatalogService.name);\n\n /**\n * Le catalogue **perp** d'une ou plusieurs venues, en une seule liste.\n *\n * Chaque ligne porte sa venue d'origine (`xex`) et les deux symboles — le canonique pour\n * rapprocher un actif entre venues, le brut pour parler à celle-ci.\n *\n * **Le traitement d'une venue en échec dépend de ce qui a été demandé** : sur une seule venue,\n * un échec est l'échec de l'appel et il est relancé ; sur plusieurs, la liste partielle garde\n * sa valeur et l'incident est journalisé. Rendre `[]` en silence serait le pire des deux — un\n * catalogue vide se lit comme « cette venue ne liste rien », pas comme « je n'ai pas pu\n * demander ».\n */\n public async catalog(...xexes: XgateEx[]): Promise<IPair[]> {\n if (xexes.length === 0) {\n throw new Error('catalog() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.catalogOf(xex);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`catalog(${xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /** Le catalogue d'UNE venue, traduit vers {@link IPair}. */\n private async catalogOf(xex: XgateEx): Promise<IPair[]> {\n const pairs = await createPublicXex(xex).perp().getPairs();\n return pairs.map((pair) => ({\n symbol: NAMES_ITS_BASE.has(xex)\n ? canonicalFromBase(pair.base)\n : canonicalFromXex(pair.name, xex),\n quote: pair.quote,\n symbolXex: pair.name,\n xex,\n kind: pair.kind as MarketKind,\n multiplier: multiplierFromXex(pair.name),\n expiresAt: expiryOf(pair.xtras),\n ref: pair.ref ?? null,\n szDecimals: pair.szDecimals,\n tickSize: pair.tickSize,\n stepSize: pair.stepSize,\n minQty: pair.minQty,\n minNotional: pair.minNotional,\n maxLeverage: pair.maxLeverage,\n status: pair.status,\n delisted: pair.delisted,\n listedAt: pair.listedAt,\n xtras: pair.xtras,\n }));\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { canonicalFromXex } from '../helpers/symbol-naming';\nimport { createPublicXex } from '../helpers/xex-registry';\nimport type { MarketKind } from '../interfaces/pair.interfaces';\nimport type { IPrice } from '../interfaces/price.interfaces';\n\n/**\n * LES PRIX, toutes venues confondues.\n *\n * Même forme que `CatalogService`, et pour la même raison : l'appelant ne connaît qu'{@link XgateEx}\n * et les types unifiés. Chaque ligne porte sa venue d'origine, sans quoi un relevé multi-venues\n * perdrait ce qu'il a de plus utile — l'écart entre les cotations d'un même actif.\n *\n * **Ce service ne calcule rien.** Il ne dérive pas un `mid` d'un `bid`/`ask`, ne convertit pas un\n * volume, ne comble aucun trou : un champ à `null` dit que la venue ne le publie pas, et c'est une\n * information. Fabriquer la valeur manquante la rendrait indiscernable d'une valeur cotée.\n */\n@Injectable()\nexport class PricesService {\n private readonly logger = new Logger(PricesService.name);\n\n /**\n * Les prix **perp** d'une ou plusieurs venues, en une seule liste.\n *\n * **Le traitement d'une venue en échec dépend de ce qui a été demandé** : sur une seule venue, un\n * échec est l'échec de l'appel et il est relancé ; sur plusieurs, la liste partielle garde sa\n * valeur et l'incident est journalisé. Rendre `[]` en silence serait le pire des deux — un relevé\n * vide se lit comme « ce marché ne cote rien », pas comme « je n'ai pas pu demander ».\n */\n public async prices(...xexes: XgateEx[]): Promise<IPrice[]> {\n if (xexes.length === 0) {\n throw new Error('prices() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.pricesOf(xex);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`prices(${xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /** Les prix d'UNE venue, traduits vers {@link IPrice}. */\n private async pricesOf(xex: XgateEx): Promise<IPrice[]> {\n const prices = await createPublicXex(xex).perp().getPrices();\n return prices.map((price) => ({\n symbol: canonicalFromXex(price.name, xex),\n symbolXex: price.name,\n xex,\n kind: price.kind as MarketKind,\n mark: price.mark,\n oracle: price.oracle,\n mid: price.mid,\n bid: price.bid,\n ask: price.ask,\n last: price.last,\n funding: price.funding,\n openInterest: price.openInterest,\n volume24h: price.volume24h,\n prevDayPrice: price.prevDayPrice,\n quotedAt: price.quotedAt,\n xtras: price.xtras,\n }));\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { toXexQuery } from '../helpers/candle-query';\nimport { SPOT_XEXES, createPublicSpotXex, servesSpot } from '../helpers/xex-registry';\nimport type { ICandle, ICandlesQuery } from '../interfaces/candle.interfaces';\n\n/**\n * LES BOUGIES DU **COMPTANT**, en REST — binance et bybit.\n *\n * **Un service à part, et non un drapeau sur {@link CandlesService}.** C'est la même règle que pour\n * le catalogue : un `candles()` qui servirait tantôt du perpétuel tantôt du comptant selon un\n * paramètre produirait des erreurs silencieuses — on croirait lire des perpétuels et on lirait du\n * comptant, sans que rien ne le signale. Deux marchés, deux services ; l'appelant choisit\n * explicitement ce qu'il interroge.\n *\n * La différence n'est pas cosmétique : **le perpétuel porte un funding et s'écarte du comptant**.\n * C'est cet écart que certaines stratégies exploitent — le confondre le rendrait invisible.\n *\n * En revanche, les échelles de cotation ne posent aucun problème entre les deux marchés :\n * `SATSUSDT` (×1) et `10000SATSUSDT` (×10000) sont **deux paires distinctes**, chacune portant son\n * `multiplier` au catalogue. Une bougie appartient à une paire, identifiée par `xex` +\n * `symbolXex` + `kind` : il n'y a rien à réconcilier.\n *\n * **Aucune agrégation ici**, contrairement au perpétuel : binance et bybit servent nativement tous\n * les intervalles de `1m` à `1w`. Le jour où une venue comptant en manquerait un, ce serait à\n * ajouter — pas à supposer.\n */\n@Injectable()\nexport class SpotCandlesService {\n private readonly logger = new Logger(SpotCandlesService.name);\n\n /** Les venues dont XGate sert le comptant. */\n public venues(): readonly XgateEx[] {\n return SPOT_XEXES;\n }\n\n /**\n * Les bougies comptant d'une venue, sur une plage de **dates**.\n *\n * **Lève** si la venue ne sert pas de comptant, plutôt que de rendre une liste vide : « cette\n * venue n'a pas de marché comptant » et « ce marché n'a pas coté » appellent des suites très\n * différentes.\n */\n public async candles(xex: XgateEx, query: ICandlesQuery): Promise<ICandle[]> {\n if (servesSpot(xex) === false) {\n throw new Error(\n `spotCandles(${xex}) : cette venue ne sert pas de comptant. Disponibles : ${SPOT_XEXES.join(', ')}.`,\n );\n }\n const spot = createPublicSpotXex(xex).spot();\n if (spot.getCandles === undefined) {\n throw new Error(`spotCandles(${xex}) : cette venue ne sert pas de bougies comptant en REST.`);\n }\n const wires = await spot.getCandles(toXexQuery(query));\n return wires.map((wire) => toCandle(wire, xex, query.interval as Timeframe, ''));\n }\n\n /**\n * Les bougies comptant d'un même marché chez PLUSIEURS venues, en une liste.\n *\n * Même politique d'échec que partout ailleurs : seule, une venue en échec fait échouer l'appel ;\n * parmi d'autres, elle est journalisée et ignorée. Chaque bougie porte son `xex`.\n */\n public async candlesOf(xexes: XgateEx[], query: ICandlesQuery): Promise<ICandle[]> {\n if (xexes.length === 0) {\n throw new Error('spotCandlesOf() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.candles(xex, query);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(\n `spotCandles(${xex}, ${query.interval}) a échoué, venue ignorée : ${message}`,\n );\n return [];\n }\n }),\n );\n return collected.flat();\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { canonicalFromBase, multiplierFromXex } from '../helpers/symbol-naming';\nimport { SPOT_XEXES, createPublicSpotXex, servesSpot } from '../helpers/xex-registry';\nimport type { IPair, MarketKind } from '../interfaces/pair.interfaces';\n\n/**\n * LE CATALOGUE COMPTANT, séparé du catalogue perpétuel.\n *\n * **Deux services, pas un service à drapeau.** Un `catalog(xex, { spot: true })` serait plus court\n * à écrire et plus dangereux à lire : on croirait interroger des perpétuels et on interrogerait du\n * comptant, sans que rien ne le signale. Ici, le nom de la classe dit ce qu'on lit.\n *\n * Le comptant est **minoritaire** dans la famille : deux venues sur dix le servent. Ce n'est pas un\n * manque à combler, c'est la réalité d'un ensemble de SDK bâti pour le perpétuel.\n */\n@Injectable()\nexport class SpotCatalogService {\n private readonly logger = new Logger(SpotCatalogService.name);\n\n /** Les venues dont XGate sert le comptant. */\n public venues(): readonly XgateEx[] {\n return SPOT_XEXES;\n }\n\n /**\n * Le catalogue **spot** d'une ou plusieurs venues, en une seule liste.\n *\n * Même politique d'échec que le catalogue perpétuel : sur une seule venue, l'échec est relancé ;\n * sur plusieurs, la liste partielle garde sa valeur et l'incident est journalisé.\n *\n * **Une venue sans comptant lève**, elle ne rend pas une liste vide : « cette venue n'a pas de\n * spot » et « cette venue ne cote rien au comptant » ne se corrigent pas de la même façon.\n */\n public async catalog(...xexes: XgateEx[]): Promise<IPair[]> {\n if (xexes.length === 0) {\n throw new Error('spot catalog() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.catalogOf(xex);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`spot catalog(${xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /** Le catalogue comptant d'UNE venue, traduit vers {@link IPair}. */\n private async catalogOf(xex: XgateEx): Promise<IPair[]> {\n if (servesSpot(xex) === false) {\n throw new Error(`spot catalog(${xex}) : cette venue ne sert pas de comptant via XGate.`);\n }\n const pairs = await createPublicSpotXex(xex).spot().getPairs();\n return pairs.map((pair) => ({\n // Les deux venues à comptant NOMMENT leur actif de base : on prend ce qu'elles disent. Re-parser\n // `ROSEBTC` donnerait `ROSEBTC`, le strip de cotation ne connaissant que USDT et USDC — et le\n // spot se cote massivement en BTC, ETH ou BNB.\n symbol: canonicalFromBase(pair.base),\n quote: pair.quote,\n symbolXex: pair.name,\n xex,\n kind: pair.kind as MarketKind,\n // Le comptant porte les mêmes échelles que le perp : `SATSUSDT` (×1) et `1000SATSUSDT`\n // coexistent, et c'est justement entre eux que la comparaison serait fausse.\n multiplier: multiplierFromXex(pair.name),\n ref: pair.ref ?? null,\n szDecimals: pair.szDecimals,\n tickSize: pair.tickSize,\n stepSize: pair.stepSize,\n minQty: pair.minQty,\n minNotional: pair.minNotional,\n maxLeverage: pair.maxLeverage,\n status: pair.status,\n delisted: pair.delisted,\n listedAt: pair.listedAt,\n xtras: pair.xtras,\n }));\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { canonicalFromXex } from '../helpers/symbol-naming';\nimport { SPOT_XEXES, createPublicSpotXex, servesSpot } from '../helpers/xex-registry';\nimport type { MarketKind } from '../interfaces/pair.interfaces';\nimport type { IPrice } from '../interfaces/price.interfaces';\n\n/**\n * LES PRIX DU **COMPTANT** — binance et bybit.\n *\n * Séparé de `PricesService` comme le catalogue et les bougies le sont : deux marchés, deux\n * services. Ici la raison est encore plus directe qu'ailleurs — **un prix comptant et un prix\n * perpétuel ne sont pas le même nombre**. Le perpétuel porte un funding et s'écarte du comptant ;\n * c'est justement cet écart que certaines stratégies exploitent, et le confondre le rendrait\n * invisible.\n *\n * **La moitié des champs sont `null` au comptant, et c'est correct** : `mark`, `oracle`, `funding`\n * et `openInterest` n'existent que pour un perpétuel. Le comptant renseigne `bid`, `ask`, `last`,\n * `volume24h` — ce qui est mesuré, pas ce qui est dérivé d'un contrat.\n */\n@Injectable()\nexport class SpotPricesService {\n private readonly logger = new Logger(SpotPricesService.name);\n\n /** Les venues dont XGate sert les prix comptant. */\n public venues(): readonly XgateEx[] {\n return SPOT_XEXES;\n }\n\n /**\n * Les prix comptant d'une ou plusieurs venues, en une seule liste.\n *\n * Même politique d'échec que partout : seule, une venue en échec fait échouer l'appel ; parmi\n * d'autres, elle est journalisée et ignorée. Chaque prix porte son `xex`.\n */\n public async prices(...xexes: XgateEx[]): Promise<IPrice[]> {\n if (xexes.length === 0) {\n throw new Error('spot prices() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.pricesOf(xex);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`spot prices(${xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /** Les prix comptant d'UNE venue, traduits vers {@link IPrice}. */\n private async pricesOf(xex: XgateEx): Promise<IPrice[]> {\n if (servesSpot(xex) === false) {\n throw new Error(\n `spot prices(${xex}) : cette venue ne sert pas de comptant. Disponibles : ${SPOT_XEXES.join(', ')}.`,\n );\n }\n const spot = createPublicSpotXex(xex).spot();\n if (spot.getPrices === undefined) {\n throw new Error(`spot prices(${xex}) : cette venue ne sert pas de prix comptant.`);\n }\n const prices = await spot.getPrices();\n return prices.map((price) => ({\n // `canonicalFromXex`, et non `canonicalFromBase` : les prix ne portent QUE le symbole\n // concaténé (`BTCUSDT`), jamais la base — contrairement au catalogue, où la venue déclare\n // `baseAsset`. Passer le symbole à `canonicalFromBase` rendrait `BTCUSDT` tel quel, puisque\n // cette fonction ne strippe volontairement aucune cotation.\n //\n // ⚠️ La limite est connue et assumée : le strip ne retire que `USDT`/`USDC` finaux, donc une\n // paire cotée en BTC (`ROSEBTC`, `ETHBTC`) garde son nom entier. Le catalogue comptant, lui,\n // résout ces cas — c'est là qu'il faut aller chercher `symbol` et `quote` justes.\n symbol: canonicalFromXex(price.name, xex),\n symbolXex: price.name,\n xex,\n kind: price.kind as MarketKind,\n mark: price.mark,\n oracle: price.oracle,\n mid: price.mid,\n bid: price.bid,\n ask: price.ask,\n last: price.last,\n funding: price.funding,\n openInterest: price.openInterest,\n volume24h: price.volume24h,\n prevDayPrice: price.prevDayPrice,\n quotedAt: price.quotedAt,\n xtras: price.xtras,\n }));\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { SPOT_XEXES, createPublicSpotXex, servesSpot } from '../helpers/xex-registry';\nimport type { ICandle } from '../interfaces/candle.interfaces';\nimport type { Unsubscribe } from './ws-candles.service';\n\n/**\n * LES BOUGIES DU **COMPTANT** EN TEMPS RÉEL — binance et bybit.\n *\n * Le pendant de `WsCandlesService`, sur l'autre marché. La séparation n'est pas une symétrie\n * décorative : les façades exposent `ws(kind = 'perp')`, donc **un `ws()` nu ouvre un flux de\n * perpétuels**. Un service unique qui aurait oublié de passer le marché aurait diffusé des prix\n * de perpétuel à qui croyait écouter le comptant — deux séries voisines, jamais égales, et rien\n * pour le signaler.\n *\n * Le contrat {@link IXexSpotSource} exige donc `ws('spot')` explicitement, sans valeur par défaut.\n */\n@Injectable()\nexport class SpotWsCandlesService {\n private readonly logger = new Logger(SpotWsCandlesService.name);\n\n /** Les venues dont XGate diffuse le comptant en temps réel. */\n public venues(): readonly XgateEx[] {\n return SPOT_XEXES;\n }\n\n /**\n * Souscrit aux bougies **comptant** d'un marché. Rend la fonction de désabonnement.\n *\n * Le handler reçoit une bougie à la fois, au format unifié ; la bougie en cours est repoussée à\n * chaque mise à jour tant qu'elle n'est pas close.\n */\n public subscribe(\n xex: XgateEx,\n query: { symbol: string; interval: Timeframe },\n handler: (candle: ICandle) => void,\n ): Unsubscribe {\n if (servesSpot(xex) === false) {\n throw new Error(\n `spot subscribe(${xex}) : cette venue ne sert pas de comptant. Disponibles : ${SPOT_XEXES.join(', ')}.`,\n );\n }\n this.logger.log(`souscription bougies COMPTANT ${query.symbol} ${query.interval} sur ${xex}`);\n return createPublicSpotXex(xex)\n .ws('spot')\n .subscribeCandles({ name: query.symbol, interval: query.interval }, (wire) => {\n // La cotation n'est pas déductible d'un symbole concaténé (`ROSEBTC` est cotée en BTC) :\n // on ne prétend rien plutôt que d'affirmer un faux `USDT`.\n handler(toCandle(wire, xex, query.interval, ''));\n });\n }\n\n /**\n * Souscrit au MÊME marché comptant chez plusieurs venues, avec un seul handler.\n *\n * Chaque bougie porte son `xex`. La fonction rendue coupe tous les flux d'un coup.\n */\n public subscribeAll(\n xexes: XgateEx[],\n query: { symbol: string; interval: Timeframe },\n handler: (candle: ICandle) => void,\n ): Unsubscribe {\n if (xexes.length === 0) {\n throw new Error('spot subscribeAll() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const stops: Unsubscribe[] = [];\n for (const xex of xexes) {\n try {\n stops.push(this.subscribe(xex, query, handler));\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`souscription comptant ${xex} refusée, venue ignorée : ${message}`);\n }\n }\n return () => {\n for (const stop of stops) {\n stop();\n }\n };\n }\n}\n","// Repris de `Blips/server/src/execution/components/{protection-math,paper-math}.component.ts` (2026-08-06).\n// Ce calcul a été écrit et éprouvé côté application, mais sa place est ICI : c'est une règle de\n// protection d'ordre, pas une règle de signal. Blips garde ce qui lui appartient — le CHOIX des\n// parts selon le moteur (`tpParts`) et la garde « borne d'entrée vs tp1 ».\n\n/** Sens de la position. Le SL et les TP se posent au sens OPPOSÉ. */\nexport type Direction = 'long' | 'short';\n\n/** Un ordre de protection, prêt à être posé : déclenchement, taille, borne d'exécution. */\nexport interface IProtectionLeg {\n /** Prix auquel l'ordre se déclenche. */\n triggerPrice: string;\n /** Taille **en unités de base**. */\n size: string;\n /**\n * Borne d'exécution du déclenché : sous le trigger en long, au-dessus en short. Hyperliquid\n * l'exige ; les autres venues l'ignorent sans que ce soit une erreur.\n */\n price: string;\n}\n\n/** Ce qu'xgate rend : un SL pleine taille, et N take-profits partiels. */\nexport interface IProtection {\n sl: IProtectionLeg;\n tps: IProtectionLeg[];\n}\n\n/**\n * Ce que l'appelant fournit. **Les pourcentages sont des écarts au prix d'entrée**, en fraction\n * (0.01 = 1 %) : `slPct: 0.02` place le stop 2 % sous l'entrée en long, 2 % au-dessus en short.\n */\n/**\n * UN NIVEAU DE SORTIE — dit en ÉCART ou en PRIX, jamais les deux.\n *\n * Les deux façons de penser un objectif coexistent réellement : « je sors si ça monte de 10 % »\n * raisonne en risque, « je sors si ça touche 62 500 » raisonne en niveau technique. Obliger à\n * convertir l'un dans l'autre revient à faire calculer l'appelant, donc à déplacer l'erreur chez lui.\n *\n * L'union est **exclusive** par construction : donner les deux ne compile pas, et n'en donner aucun\n * non plus. C'est ce qui rend impossible un objectif à moitié défini.\n */\nexport type ITarget = { pct: number; price?: never } | { price: number; pct?: never };\n\n/**\n * Le prix d'un niveau, quel que soit la façon dont il a été exprimé.\n *\n * Un prix absolu est pris tel quel — c'est un niveau que l'appelant a choisi, pas une dérivée de\n * l'entrée. Un pourcentage se projette depuis l'entrée, du côté gagnant pour un take-profit et\n * perdant pour un stop, selon le sens de la position.\n */\nexport function priceOf(\n cible: ITarget,\n entry: number,\n direction: Direction,\n gagnant: boolean,\n): number {\n if (cible.price !== undefined) {\n return round8(cible.price);\n }\n const dir = (direction === 'long' ? 1 : -1) * (gagnant === true ? 1 : -1);\n return round8(entry * (1 + dir * (cible.pct as number)));\n}\n\nexport interface IProtectionInput {\n /** Prix d'entrée de référence. */\n entry: number;\n direction: Direction;\n /** Taille de la position, **en unités de base**. */\n size: number;\n /**\n * Le stop — en **écart** au prix d'entrée (`{ pct: 0.02 }`) ou en **prix absolu**\n * (`{ price: 62500 }`). Toujours du côté perdant quand il est donné en écart.\n */\n sl: ITarget;\n /**\n * Les take-profits, dans l'ordre. Chacun porte son niveau — en écart ou en prix — et `part`, la\n * fraction de la POSITION à sortir là (toujours un pourcentage, jamais un prix).\n *\n * **Une part à 0 ne produit AUCUN leg** — c'est un marqueur (un palier de breakeven, par exemple),\n * pas un ordre. Sauf s'il s'agit du dernier : celui-là clôture toujours.\n */\n tps: Array<ITarget & { part: number }>;\n /** Pas de prix de la paire. */\n tickSize?: string | null;\n /** Pas de quantité de la paire. */\n lotSize?: string | null;\n /**\n * Écart entre le déclenchement et la borne d'exécution, en fraction (défaut 0,5 %). Le déclenché\n * sort SOUS son trigger en long, AU-DESSUS en short.\n */\n slippagePct?: number;\n}\n\nconst SLIPPAGE_DEFAUT = 0.005;\n\n/**\n * LE SEUIL DE CLÔTURE INTÉGRALE — 80 % de la position.\n *\n * Strictement au-dessus, les take-profits sont manifestement censés tout sortir : le dernier absorbe le\n * reliquat et la position ferme entièrement. Trois paliers à 33 % somment à 99 % — ce 1 % restant\n * n'est pas une intention, c'est un arrondi, et le laisser ouvert créerait une position résiduelle\n * que personne ne surveille.\n *\n * À 80 % ou en dessous, l'inverse : deux paliers à 33 % laissent délibérément courir 34 % de la position. Le\n * dernier take-profit prend alors sa part nominale, sans plus — c'est une sortie partielle voulue,\n * et la compléter d'office trahirait l'intention.\n *\n * Le stop, lui, couvre la taille pleine dans les deux cas : ce qui court reste protégé.\n */\nconst SEUIL_CLOTURE = 0.8;\n\n/** Arrondi à huit décimales — la précision de travail, avant tout arrondi à la grille de la paire. */\nfunction round8(value: number): number {\n return Number(value.toFixed(8));\n}\n\n/**\n * Arrondit à un pas. `floor` pour les tailles — jamais `nearest` : arrondir au plus proche ferait\n * dépasser la somme des legs au-delà de la position, et la venue rejetterait le dernier.\n */\nexport function roundToStep(\n value: number,\n step: string | null | undefined,\n mode: 'floor' | 'nearest',\n): number {\n if (step === null || step === undefined || step === '') {\n return Math.round(value * 1e4) / 1e4;\n }\n const stepNum = Number(step);\n if (Number.isFinite(stepNum) === false || stepNum <= 0) {\n return Math.round(value * 1e4) / 1e4;\n }\n // LE PLANCHER SE PREND SUR UN QUOTIENT NETTOYÉ, et c'est indispensable. `1.7 - 1.3` vaut\n // `0.39999999999999991` en flottant : divisé par un pas de `0.1`, on obtient `3.9999…`, dont le\n // plancher est `3` au lieu de `4`. Un pas entier de position se volatilise — précisément le\n // résidu que ce module existe pour empêcher. Neuf décimales suffisent à recoller le quotient sans\n // jamais franchir un pas réel (`0.39 / 0.1` reste `3.9`, donc `3`).\n const quotient = value / stepNum;\n const units = mode === 'floor' ? Math.floor(Number(quotient.toFixed(9))) : Math.round(quotient);\n const dot = step.indexOf('.');\n const decimals = dot === -1 ? 0 : step.length - dot - 1;\n return Number((units * stepNum).toFixed(decimals));\n}\n\n/**\n * Arrondit un PRIX à la grille de la paire : `tickSize` s'il est fourni, sinon la règle Hyperliquid\n * — cinq chiffres significatifs ET au plus `6 − szDecimals` décimales, `szDecimals` étant déduit du\n * pas de quantité. Sans cette seconde branche, une venue sans tick fixe reçoit un prix qu'elle\n * refuse.\n */\nexport function roundPrice(\n value: number,\n tickSize?: string | null,\n lotSize?: string | null,\n): number {\n if (tickSize !== null && tickSize !== undefined && tickSize !== '') {\n return roundToStep(value, tickSize, 'nearest');\n }\n const dot = (lotSize ?? '').indexOf('.');\n const szDecimals = dot === -1 ? 0 : (lotSize as string).length - dot - 1;\n const maxDecimals = 6 - szDecimals;\n const sig = Number(value.toPrecision(5));\n if (maxDecimals <= 0) {\n return Math.round(sig);\n }\n return Number(sig.toFixed(maxDecimals));\n}\n\n/**\n * LES NIVEAUX DE PROTECTION, calculés depuis des POURCENTAGES.\n *\n * L'appelant donne un prix d'entrée, un sens et des écarts ; il ne calcule aucun prix lui-même.\n * Le stop part du côté perdant, les take-profits du côté gagnant — le sens s'en occupe.\n */\n/**\n * Les niveaux depuis des POURCENTAGES seuls — forme courte quand tout est exprimé en écart.\n *\n * {@link buildProtection} ne l'utilise plus : il résout chaque niveau séparément, puisqu'un stop\n * peut être donné en écart et un take-profit en prix absolu dans le même appel.\n */\nexport function buildLevels(\n entry: number,\n direction: Direction,\n slPct: number,\n tpPcts: readonly number[],\n): { sl: number; tps: number[] } {\n const dir = direction === 'long' ? 1 : -1;\n return {\n sl: round8(entry * (1 - dir * slPct)),\n tps: tpPcts.map((pct) => round8(entry * (1 + dir * pct))),\n };\n}\n\n/**\n * LA PROTECTION COMPLÈTE — un stop pleine taille et N take-profits partiels, prêts à poser.\n *\n * **GARANTIE ANTI-RÉSIDU, sous condition de seuil.** Quand les parts somment à **plus de 80 %**, le\n * DERNIER take-profit prend le reliquat EXACT (`size − Σ des précédents`) et la position ferme\n * entièrement : trois paliers à 33 % font 99 %, et ce 1 % restant est un arrondi, pas une intention.\n *\n * **À 80 % ou en dessous, on laisse courir.** Deux paliers à 33 % sortent 66 % et laissent 34 % de\n * position ouverte — c'est une sortie partielle voulue, et la compléter d'office la trahirait. Le\n * dernier palier prend alors sa part nominale, sans plus.\n *\n * Les paliers intermédiaires s'arrondissent au **plancher**, jamais au plus proche : arrondir au\n * plus proche ferait dépasser la somme, et c'est le dernier leg — celui qui clôture — que la venue\n * rejetterait.\n *\n * **LE STOP COUVRE TOUJOURS LA TAILLE PLEINE.** Un stop partiel laisserait une fraction de position\n * sans protection, ce qui est précisément ce qu'on veut rendre impossible : jamais une position\n * nue, même partiellement.\n *\n * Deux cas font disparaître un leg, et c'est voulu :\n * · une part à 0 sur un palier intermédiaire — c'est un marqueur, pas un ordre ;\n * · une taille qui tombe à 0 après arrondi — un ordre de taille nulle serait refusé.\n * Le dernier palier, lui, survit toujours : c'est lui qui ferme.\n */\nexport function buildProtection(input: IProtectionInput): IProtection {\n const slippage = input.slippagePct ?? SLIPPAGE_DEFAUT;\n const long = input.direction === 'long';\n // Chaque niveau se résout INDÉPENDAMMENT : l'un peut être donné en écart, le suivant en prix.\n const sl = priceOf(input.sl, input.entry, input.direction, false);\n const niveaux = input.tps.map((tp) => priceOf(tp, input.entry, input.direction, true));\n // La borne d'exécution : sous le trigger en long, au-dessus en short.\n const borne = (trigger: number): number =>\n long === true ? trigger * (1 - slippage) : trigger * (1 + slippage);\n\n // LA SOMME DES PARTS DÉCIDE DE CE QUE FAIT LE DERNIER PALIER. AU-DESSUS de 80 %, il clôture : le\n // reliquat lui revient. À 80 % ou en dessous, il s'en tient à sa part et le reste court.\n const sommeParts = input.tps.reduce((total, tp) => total + tp.part, 0);\n const clotureIntegrale = sommeParts > SEUIL_CLOTURE;\n\n const legs: IProtectionLeg[] = [];\n let alloue = 0;\n niveaux.forEach((trigger, index) => {\n const dernier = index === niveaux.length - 1;\n const part = input.tps[index]?.part ?? 0;\n if (dernier === false && part <= 0) {\n return;\n }\n // Le dernier prend le RELIQUAT EXACT — mais seulement si l'ensemble vise la clôture.\n const size =\n dernier === true && clotureIntegrale === true\n ? roundToStep(input.size - alloue, input.lotSize, 'floor')\n : roundToStep(part * input.size, input.lotSize, 'floor');\n if (size <= 0) {\n return;\n }\n alloue += size;\n legs.push({\n triggerPrice: String(roundPrice(trigger, input.tickSize, input.lotSize)),\n size: String(size),\n price: String(roundPrice(borne(trigger), input.tickSize, input.lotSize)),\n });\n });\n\n return {\n sl: {\n triggerPrice: String(roundPrice(sl, input.tickSize, input.lotSize)),\n // Taille PLEINE, toujours : le stop couvre l'intégralité de la position.\n size: String(input.size),\n price: String(roundPrice(borne(sl), input.tickSize, input.lotSize)),\n },\n tps: legs,\n };\n}\n","import type { XgateEx } from '../enums/xgate-ex.enums';\nimport type { Direction, ITarget } from '../helpers/protection';\n\n/** Côté d'un ordre. À ne pas confondre avec le SENS d'une position ({@link Direction}). */\nexport type Side = 'buy' | 'sell';\n\n/** Nature d'un ordre, telle qu'XGate la nomme. Le type natif exact reste dans `xtras`. */\nexport type OrderType =\n | 'limit'\n | 'market'\n | 'stop'\n | 'stopMarket'\n | 'takeProfit'\n | 'takeProfitMarket'\n | 'other';\n\n/**\n * ÉTAT D'UN ORDRE — **exactement les sept valeurs des SDK de venue**, orthographe comprise.\n *\n * `canceled` prend un seul L : c'est ce que rendent les huit SDK, unanimement. XGate écrivait\n * `cancelled` et perdait `partiallyFilled` et `expired` ; le `as OrderStatus` de la conversion\n * masquait l'écart au lieu de le signaler, si bien qu'un consommateur testant `=== 'cancelled'`\n * ne matchait **jamais** — un ordre annulé passait pour un ordre encore vivant.\n *\n * Seul `filled` a déplacé tout ce qui était demandé ; `partiallyFilled` en a déplacé une part et\n * garde le reste au carnet.\n */\nexport type OrderStatus =\n | 'open'\n | 'partiallyFilled'\n | 'filled'\n | 'canceled'\n | 'rejected'\n | 'expired'\n | 'other';\n\n/**\n * Les sept statuts, énumérés — pour VALIDER ce qu'une venue rend plutôt que de le caster.\n *\n * Un `as OrderStatus` sur une chaîne inconnue produit une valeur qui n'existe dans aucun `switch` et\n * que personne ne rattrape ; passer par cette liste fait retomber l'inconnu sur `other`, qui lui est\n * traité partout.\n */\nexport const ORDER_STATUSES: readonly OrderStatus[] = [\n 'open',\n 'partiallyFilled',\n 'filled',\n 'canceled',\n 'rejected',\n 'expired',\n 'other',\n];\n\n/** Durée de validité. `alo` = post-only : l'ordre est refusé s'il devait s'exécuter immédiatement. */\nexport type TimeInForce = 'gtc' | 'ioc' | 'fok' | 'alo';\n\n/**\n * UN ORDRE, tel qu'XGate le rend.\n *\n * `xex` dit d'où il vient : un identifiant d'ordre n'a de sens que chez la venue qui l'a émis, et\n * deux venues peuvent parfaitement produire le même.\n */\nexport interface IOrder {\n xex: XgateEx;\n /** Le symbole que la venue attend. */\n symbolXex: string;\n /** Type de marché. */\n kind: 'perp' | 'spot';\n /** L'identifiant de la venue. */\n id: string;\n /** L'identifiant fourni par l'appelant, quand il en a donné un. */\n clientId: string | null;\n side: Side;\n type: OrderType;\n /** `null` pour un ordre au marché. */\n price: string | null;\n /** Taille demandée, **en unités de base**. */\n size: string;\n /** Taille déjà exécutée, **en unités de base**. */\n filled: string;\n status: OrderStatus;\n tif: TimeInForce | null;\n /** L'ordre ne peut que réduire une position existante — jamais en ouvrir une. */\n reduceOnly: boolean | null;\n /** Moment où l'ordre a été soumis. */\n placedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * UNE POSITION ouverte.\n *\n * `size` est **toujours positive** : le sens est porté par `side`. Mélanger les deux — une taille\n * négative pour un short — oblige chaque consommateur à se souvenir de la convention, et l'un d'eux\n * l'oubliera.\n */\nexport interface IPosition {\n xex: XgateEx;\n symbolXex: string;\n /** Nom canonique de l'actif, pour rapprocher la même position entre venues. */\n symbol: string;\n side: 'long' | 'short' | null;\n /** Taille **en unités de base**, toujours positive. */\n size: string;\n entryPrice: string | null;\n markPrice: string | null;\n unrealizedPnl: string | null;\n leverage: number | null;\n liquidationPrice: string | null;\n /** Marge immobilisée par la position. */\n margin: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * UNE TRANSACTION PUBLIQUE — celles de tout le monde sur un marché, pas les siennes.\n *\n * À ne pas confondre avec {@link ITrade}, qui est une exécution **du compte**. Celle-ci n'a ni\n * frais, ni PnL, ni identifiant d'ordre : on ne voit que ce que le marché montre.\n */\nexport interface IPublicTrade {\n xex: XgateEx;\n symbolXex: string;\n price: string;\n size: string;\n /** Sens de l'AGRESSEUR : `buy` = quelqu'un a pris l'offre. */\n side: Side;\n tradedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * L'ÉTAT D'UN COMPTE — ce que toutes les venues disent, sous des noms différents.\n *\n * Les formes natives ne se ressemblent pas : hyperliquid range son équité sous\n * `marginSummary.accountValue`, pacifica sous `accountEquity`, aster sous `totalMarginBalance`.\n * **Mais les concepts, eux, sont les mêmes** — c'est exactement ce qu'XGate existe pour normaliser.\n *\n * `xtras` conserve la forme native complète : ce qui est propre à une venue n'est pas perdu, il\n * n'est simplement pas promu au rang de contrat.\n */\nexport interface IAccountState {\n xex: XgateEx;\n /** Valeur totale du compte, PnL latent compris. */\n equity: string;\n /** Ce qui reste mobilisable — pour ouvrir ou retirer. `null` si la venue ne le publie pas. */\n available: string | null;\n /** Marge immobilisée par les positions et ordres en cours. */\n marginUsed: string | null;\n /** Marge de maintenance : sous ce seuil, la liquidation menace. */\n maintenanceMargin: string | null;\n /**\n * PnL non réalisé des positions ouvertes.\n *\n * **Seule aster le publie au niveau du compte.** hyperliquid et pacifica ne l'exposent pas là —\n * il se lit alors sur les positions (`IPosition.unrealizedPnl`), qui le portent toutes. On rend\n * `null` plutôt que de le reconstituer : une somme calculée ici passerait pour une donnée de la\n * venue.\n */\n unrealizedPnl: string | null;\n /** L'état natif COMPLET, tel que la venue le publie. */\n xtras?: Record<string, unknown>;\n}\n\n/**\n * UN FUNDING PAYÉ — ce qui a réellement été prélevé ou reçu sur une position.\n *\n * À ne pas confondre avec le taux courant d'`IPrice.funding`, qui annonce ce qui *sera* payé. Un\n * calcul de résultat qui ignore ce qui l'a été surestime toute position tenue longtemps.\n */\nexport interface IFundingPaid {\n xex: XgateEx;\n symbolXex: string;\n /** Le taux appliqué, en fraction. */\n rate: string;\n /** Moment où il a été prélevé. */\n appliedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/** UNE EXÉCUTION du compte — ce qui a réellement été rempli, par opposition à ce qui a été demandé. */\nexport interface ITrade {\n xex: XgateEx;\n symbolXex: string;\n kind: 'perp' | 'spot';\n id: string;\n /** L'ordre dont provient cette exécution. */\n orderId: string;\n side: Side;\n price: string;\n size: string;\n fee: string;\n feeAsset: string | null;\n /** PnL réalisé, avant et après frais ; `null` quand la venue ne permet pas de l'établir. */\n grossPnl: string | null;\n netPnl: string | null;\n /** L'exécution était côté maker. `null` si la venue ne le dit pas. */\n maker: boolean | null;\n /** Moment de l'exécution. */\n filledAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * CE QU'ON DEMANDE POUR DÉPLACER LE STOP D'UNE POSITION OUVERTE.\n *\n * Le geste du point mort : une position qui a couru se sécurise en remontant son stop, sans jamais\n * la laisser nue. Les SDK posent le NOUVEAU stop avant d'annuler l'ANCIEN — l'ordre compte, et il\n * est tenu par la venue, pas par l'appelant.\n *\n * **Le sens est celui de la POSITION**, comme partout ailleurs ici : le stop se pose au sens opposé,\n * et c'est le SDK de la venue qui s'en charge.\n */\nexport interface IMoveStop {\n xex: XgateEx;\n /** Le symbole que la venue attend. */\n symbolXex: string;\n /** Sens de la POSITION protégée. */\n direction: Direction;\n /** L'identifiant du stop à remplacer, tel que la venue l'a rendu. */\n stopId: string;\n /** Le nouveau niveau de déclenchement. Arrondi au tick de la paire par XGate. */\n triggerPrice: number;\n /** Taille couverte — normalement la position entière. */\n size: number;\n /** Pas de prix et de quantité de la paire. */\n tickSize?: string | null;\n lotSize?: string | null;\n /** Écart entre déclenchement et borne d'exécution du déclenché. Défaut 0,5 %. */\n slippagePct?: number;\n}\n\n/**\n * CE QU'ON DEMANDE POUR OUVRIR UNE POSITION PROTÉGÉE.\n *\n * **Les protections sont données en POURCENTAGES**, pas en prix : xgate calcule les niveaux depuis\n * le prix d'entrée. L'appelant n'a ni à arrondir au tick, ni à se souvenir que le stop d'un short\n * est au-dessus de l'entrée.\n *\n * **`slPct` est OBLIGATOIRE.** C'est la règle qui a motivé tout ce chemin : jamais un ordre sans\n * stop. Une position ouverte pendant que le serveur tombe reste protégée par la venue elle-même.\n */\nexport interface IEntryWithProtection {\n xex: XgateEx;\n /** Le symbole que la venue attend. */\n symbolXex: string;\n /** Sens de la POSITION à ouvrir. Le stop et les take-profits se posent au sens opposé. */\n direction: Direction;\n /** Taille à ouvrir, **en unités de base**. */\n size: number;\n /** Prix d'entrée visé. Sert de référence aux pourcentages, et de prix limite. */\n entry: number;\n /**\n * LE STOP — en **écart** au prix d'entrée ou en **prix absolu**, jamais les deux.\n *\n * ```typescript\n * sl: { pct: 0.02 } // 2 % sous l'entrée en long, 2 % au-dessus en short\n * sl: { price: 62500 } // exactement 62 500, quel que soit le prix d'entrée\n * ```\n *\n * **Obligatoire.** C'est la règle qui a motivé tout ce chemin : jamais un ordre sans stop.\n */\n sl: ITarget;\n /**\n * LES TAKE-PROFITS, dans l'ordre. Chacun dit son NIVEAU et sa PART.\n *\n * ```typescript\n * tps: [{ pct: 0.03, part: 0.5 }, // sortir 50 % si ça monte de 3 %\n * { price: 62500, part: 0.5 }] // sortir 50 % si ça touche 62 500\n * ```\n *\n * **`part` est toujours une fraction de la POSITION**, jamais un prix : les deux modes ne portent\n * que sur le niveau. Un objectif se pense soit en risque (« +3 % »), soit en niveau technique\n * (« 62 500 ») — obliger à convertir l'un dans l'autre déplacerait le calcul, donc l'erreur, chez\n * l'appelant.\n *\n * Au-dessus de 80 % cumulés, le dernier absorbe le reliquat et la position ferme entièrement ;\n * à 80 % ou en dessous, le reste court.\n */\n tps: Array<ITarget & { part: number }>;\n /** `ioc` pour entrer au marché (taker), `alo` pour poster (maker). Défaut : `ioc`. */\n tif?: TimeInForce;\n /** Identifiant applicatif, remonté tel quel par la venue quand elle le supporte. */\n clientId?: string;\n /** Pas de prix et de quantité de la paire — sans eux, la venue refuse des niveaux mal arrondis. */\n tickSize?: string | null;\n lotSize?: string | null;\n /** Écart entre déclenchement et borne d'exécution du déclenché. Défaut 0,5 %. */\n slippagePct?: number;\n}\n","import { randomUUID } from 'node:crypto';\nimport { Injectable, Logger } from '@nestjs/common';\nimport { XgateEx } from '../enums/xgate-ex.enums';\nimport { type Direction, buildProtection, roundPrice, roundToStep } from '../helpers/protection';\nimport { canonicalFromXex } from '../helpers/symbol-naming';\nimport { type IXexAccess, createAccountXex } from '../helpers/xex-registry';\nimport { ORDER_STATUSES } from '../interfaces/trading.interfaces';\nimport type {\n IAccountState,\n IEntryWithProtection,\n IFundingPaid,\n IMoveStop,\n IOrder,\n IPosition,\n ITrade,\n OrderStatus,\n OrderType,\n Side,\n TimeInForce,\n} from '../interfaces/trading.interfaces';\n\n/** Une venue et de quoi y agir. */\nexport interface ITradingAccess extends IXexAccess {\n xex: XgateEx;\n}\n\n/**\n * LES VENUES OÙ L'OUVERTURE PROTÉGÉE A ÉTÉ PROUVÉE EN RÉEL — pas déduite du code, exécutée.\n *\n * Une venue n'entre ici qu'après un cycle complet observé sur son testnet : le lot part, la venue\n * répond, et le carnet montre l'entrée AVEC son stop. Tant que la preuve manque, ouvrir une position\n * par ce service reviendrait à parier qu'un chemin jamais exercé protège de l'argent réel.\n *\n * **aster en est absent, et c'est un constat mesuré** : son `/fapi/v3/batchOrders` coupe à ~3,26 s\n * (constante, quel que soit le nombre de legs — un lot invalide est pourtant rejeté en 264 ms) en\n * répondant « The request has timed out. », alors que la venue a DÉJÀ créé une partie des ordres.\n * Deux exécutions identiques ont produit deux sous-ensembles différents : une fois le stop et un\n * take-profit, une fois l'entrée seule. Chaque leg s'écrit on-chain (`newChainData.hash`), d'où le\n * dépassement. Un statut inconnu sur une ouverture protégée, c'est exactement la position nue que ce\n * service existe pour rendre impossible — donc on refuse d'ouvrir plutôt que d'ouvrir peut-être.\n */\nconst VENUES_PROUVEES: readonly XgateEx[] = [XgateEx.Pacifica, XgateEx.Hyperliquid, XgateEx.Aster];\n\n/**\n * LE TIMEOUT D'ASTER, QUI N'EST PAS UN ÉCHEC — sa propre documentation le dit.\n *\n * « HTTP 503 return code is used when the API successfully sent the message but not get a response\n * within the timeout period. It is important to **NOT** treat this as a failure operation; the\n * execution status is **UNKNOWN** and could have been a success. »\n * (`V3(Recommended)/EN/aster-finance-futures-api-testnet.md`, l. 147-149)\n *\n * **Sauf qu'elle renvoie `400`, pas `503`** — mesuré le 2026-08-09 : `HTTP 400` avec le message\n * ci-dessous, sur un lot dont les ordres avaient bel et bien été créés. Un `400` annonce une requête\n * malformée ; s'y fier ferait conclure à un rejet là où la venue a exécuté. Le message est donc le\n * seul discriminant, faute de code juste.\n */\nconst ASTER_TIMEOUT = 'The request has timed out.';\n\n/**\n * LES VENUES QUI SAVENT DÉPLACER UN STOP — mesuré sur les dix, pas supposé.\n *\n * Elles sont trois, et ce sont exactement les trois qui comptent. Ailleurs, déplacer un stop\n * demanderait d'annuler puis de reposer, ce qui rouvre la fenêtre nue que tout ceci existe pour\n * fermer : on préfère refuser.\n */\nconst VENUES_MOVE_STOP: readonly XgateEx[] = [XgateEx.Hyperliquid, XgateEx.Pacifica, XgateEx.Aster];\n\n/**\n * LE TRADING : positions, ordres et exécutions — un seul service, et c'est délibéré.\n *\n * Les séparer laisserait diverger trois vues du même état : une position n'est que la somme des\n * ordres exécutés, et un ordre ne se lit pas sans savoir ce qu'il a ouvert.\n *\n * **JAMAIS UN ORDRE SANS STOP.** `openWithProtection` est la seule façon d'ouvrir une position par\n * ce service, et elle exige un `slPct`. La protection part avec l'entrée, en un geste que la venue\n * traite atomiquement : si l'entrée ne remplit pas, la protection ne naît jamais — aucun orphelin.\n * Poser le stop *après* l'entrée laisse une fenêtre pendant laquelle la position est nue, et cette\n * fenêtre s'ouvre précisément quand le serveur tombe.\n *\n * **L'appelant donne des POURCENTAGES**, jamais des prix : xgate calcule les niveaux, arrondit à la\n * grille de la paire, et garantit qu'au-dessus de 80 % de parts cumulées la position ferme\n * entièrement — sans reliquat.\n */\n@Injectable()\nexport class TradingService {\n private readonly logger = new Logger(TradingService.name);\n\n /**\n * OUVRE UNE POSITION AVEC SA PROTECTION, en un geste atomique.\n *\n * Le premier ordre rendu est **l'entrée** ; les suivants sont le stop et les take-profits, dans\n * l'ordre où ils ont été posés.\n *\n * Chaque venue applique son mécanisme natif — hyperliquid groupe les enfants sous l'entrée et les\n * annule lui-même si elle rate, pacifica les embarque dans l'ordre, aster envoie un lot de\n * conditionnels. Le SDK de la venue s'en charge ; xgate fournit des niveaux justes.\n */\n public async openWithProtection(\n access: ITradingAccess,\n input: IEntryWithProtection,\n ): Promise<IOrder[]> {\n if (VENUES_PROUVEES.includes(access.xex) === false) {\n throw new Error(\n `openWithProtection(${access.xex}) : l'ouverture protégée n'a pas été prouvée en réel sur cette venue. Seules ${VENUES_PROUVEES.join(', ')} le sont. Ouvrir ici reviendrait à parier qu'un chemin jamais exercé pose bien le stop.`,\n );\n }\n const protection = buildProtection({\n entry: input.entry,\n direction: input.direction,\n size: input.size,\n sl: input.sl,\n tps: input.tps,\n tickSize: input.tickSize,\n lotSize: input.lotSize,\n slippagePct: input.slippagePct,\n });\n // Le sens de l'ENTRÉE suit la position ; le SDK de la venue pose stop et take-profits au sens\n // opposé — c'est le contrat commun, et le lui rappeler ici le contredirait.\n const side: Side = input.direction === 'long' ? 'buy' : 'sell';\n const perp = this.perpOf(access);\n if (typeof perp.createEntryWithProtection !== 'function') {\n throw new Error(\n `openWithProtection(${access.xex}) : cette venue n'expose pas l'ouverture protégée atomique.`,\n );\n }\n const entry = {\n name: input.symbolXex,\n side,\n type: 'limit',\n size: String(input.size),\n price: String(input.entry),\n tif: input.tif ?? 'ioc',\n reduceOnly: false,\n clientId: input.clientId,\n };\n const consigne = {\n name: input.symbolXex,\n side,\n sl: protection.sl,\n tps: protection.tps,\n clientId: input.clientId,\n };\n try {\n const orders = await perp.createEntryWithProtection(entry, consigne);\n return orders.map((order) => this.toOrder(order, access.xex));\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(ASTER_TIMEOUT) === false) {\n throw error;\n }\n // Statut INCONNU, pas échec : on va LIRE ce que la venue a réellement fait.\n this.logger.warn(\n `openWithProtection(${access.xex}) : la venue a coupé avant de répondre. Statut inconnu — relecture de l'état réel.`,\n );\n return await this.etatApres(access, input.symbolXex);\n }\n }\n\n /**\n * CE QUE LA VENUE A RÉELLEMENT FAIT, après une réponse perdue.\n *\n * Un timeout ne dit pas « rien n'a été créé » : mesuré sur aster, deux exécutions identiques ont\n * produit deux sous-ensembles différents — une fois le stop et un take-profit, une fois l'entrée\n * seule. La seule vérité est au carnet, donc on va l'y chercher.\n *\n * **Lève si la position est nue** : une entrée remplie sans stop est exactement ce que ce service\n * existe pour empêcher, et le silence serait pire que l'erreur. L'appelant doit savoir qu'il a une\n * position à protéger, tout de suite.\n */\n private async etatApres(access: ITradingAccess, symbolXex: string): Promise<IOrder[]> {\n const perp = this.perpOf(access);\n const [ouverts, positions] = await Promise.all([\n perp.getOpens(),\n perp.getPositions({ name: symbolXex }),\n ]);\n const surLaPaire = ouverts.filter((order) => order.name === symbolXex);\n const position = positions.find(\n (candidate) => candidate.name === symbolXex && Number(candidate.size) !== 0,\n );\n const protege = surLaPaire.some((order) => order.reduceOnly === true);\n if (position !== undefined && protege === false) {\n throw new Error(\n `openWithProtection(${access.xex}/${symbolXex}) : POSITION NUE — ${position.size} ouvert sans stop au carnet après une réponse perdue. À protéger immédiatement.`,\n );\n }\n // ENTRÉE ORPHELINE : au carnet sans protection, et pas encore remplie. Rien n'est nu à cet\n // instant, mais elle le deviendra si elle remplit — une bombe à retardement que personne ne\n // surveille. Mesuré sur aster : le lot perd son stop et garde l'entrée. On l'annule, parce\n // qu'aucune position vaut mieux qu'une position sans stop.\n // RIEN N'EST OUVERT : le lot n'a créé qu'une partie de lui-même. Ce qui reste au carnet est du\n // déchet — une entrée sans stop deviendrait nue en remplissant, un stop sans position ne protège\n // rien et s'accumulerait à chaque tentative. Mesuré sur aster, où le lot est NON DÉTERMINISTE :\n // deux essais identiques ont laissé une fois l'entrée seule, une fois le stop seul.\n //\n // On balaie et on lève : `openWithProtection` ouvre protégé ou ne laisse rien.\n if (position === undefined && surLaPaire.length > 0) {\n for (const orphelin of surLaPaire) {\n await perp.cancel({ name: symbolXex, id: orphelin.id }).catch(() => undefined);\n }\n throw new Error(\n `openWithProtection(${access.xex}/${symbolXex}) : réponse perdue, lot incomplet — ${surLaPaire.length} ordre(s) orphelin(s) ANNULÉ(S). Rien n'est ouvert, rien ne traîne.`,\n );\n }\n this.logger.log(\n `openWithProtection(${access.xex}/${symbolXex}) : ${surLaPaire.length} ordre(s) au carnet, position ${position === undefined ? 'absente' : position.size}.`,\n );\n return surLaPaire.map((order) => this.toOrder(order, access.xex));\n }\n\n /** Les positions ouvertes d'un compte. */\n public async positions(access: ITradingAccess, symbolXex?: string): Promise<IPosition[]> {\n const perp = this.perpOf(access);\n // On ne passe la requête QUE s'il y a un symbole : un `{ name: undefined }` se sérialise en\n // `instId=undefined` chez blofin, qui répond « Parameter instId error » — un filtre vide n'est\n // pas la même chose qu'aucun filtre.\n const positions = await perp.getPositions(\n symbolXex === undefined ? undefined : { name: symbolXex },\n );\n return positions.map((position) => ({\n xex: access.xex,\n symbolXex: position.name,\n symbol: canonicalFromXex(position.name, access.xex),\n side: position.side,\n size: position.size,\n entryPrice: position.entryPrice,\n markPrice: position.markPrice,\n unrealizedPnl: position.unrealizedPnl,\n leverage: position.leverage,\n liquidationPrice: position.liquidationPrice,\n margin: position.margin,\n xtras: position.xtras,\n }));\n }\n\n /** Les ordres encore au carnet. */\n public async openOrders(access: ITradingAccess): Promise<IOrder[]> {\n const orders = await this.perpOf(access).getOpens();\n return orders.map((order) => this.toOrder(order, access.xex));\n }\n\n /**\n * Les exécutions du compte — ce qui a réellement été rempli.\n *\n * À ne pas confondre avec les ordres : un ordre annoncé `filled` peut avoir été rempli en\n * plusieurs fois, à des prix différents. C'est ici que se lit le prix moyen réel.\n */\n public async trades(access: ITradingAccess): Promise<ITrade[]> {\n const trades = await this.perpOf(access).getUserTrades();\n return trades.map((trade) => ({\n xex: access.xex,\n symbolXex: trade.name,\n kind: trade.kind as 'perp' | 'spot',\n id: trade.id,\n orderId: trade.orderId,\n side: trade.side as Side,\n price: trade.price,\n size: trade.size,\n fee: trade.fee,\n feeAsset: trade.feeAsset,\n grossPnl: trade.grossPnl,\n netPnl: trade.netPnl,\n maker: trade.maker,\n filledAt: trade.filledAt,\n xtras: trade.xtras,\n }));\n }\n\n /**\n * FERME UNE POSITION ENTIÈREMENT, sur la taille que **la venue** déclare — jamais celle qu'on croit.\n *\n * C'est toute la différence : fermer la taille planifiée laisse un reliquat quand le fill a différé du\n * plan (une entrée partiellement remplie, un take-profit passé entre-temps), et ce reliquat reste\n * ouvert, sans stop, à la charge du compte. On relit donc la position juste avant de la fermer.\n *\n * Rend `null` quand il n'y a rien à fermer — un appel sur une position déjà close n'est pas une erreur.\n */\n public async closePosition(access: ITradingAccess, symbolXex: string): Promise<IOrder | null> {\n const perp = this.perpOf(access);\n const positions = await perp.getPositions({ name: symbolXex });\n const position = positions.find(\n (candidate) => candidate.name === symbolXex && Number(candidate.size) !== 0,\n );\n let closed: IOrder | null = null;\n if (position !== undefined) {\n // `side` porte le sens de la POSITION ; on sort au sens opposé, en `reduceOnly` pour qu'un\n // décalage de taille ne puisse jamais ouvrir une position inverse.\n const exit: Side = position.side === 'long' ? 'sell' : 'buy';\n const order = await perp.place({\n name: symbolXex,\n side: exit,\n type: 'market',\n size: position.size,\n reduceOnly: true,\n });\n closed = this.toOrder(order, access.xex);\n this.logger.log(\n `closePosition(${access.xex}/${symbolXex}) : ${position.size} fermé au marché`,\n );\n }\n return closed;\n }\n\n /**\n * DÉPLACE LE STOP d'une position ouverte — le geste du point mort.\n *\n * Le SDK de la venue pose le NOUVEAU stop **avant** d'annuler l'ancien : la position n'est jamais\n * nue, pas même une milliseconde. C'est le même invariant qu'à l'ouverture, appliqué au milieu de\n * la vie de la position.\n *\n * **Un stop du mauvais côté est refusé ici**, avant l'appel réseau. Pour un long, un stop au-dessus\n * du marché se déclenche immédiatement et solde la position — la venue l'accepte sans broncher, et\n * l'erreur ne se voit qu'une fois l'argent parti. Ce refus n'est pas de la prudence décorative :\n * il a été écrit après avoir vu le cas se produire.\n */\n public async moveStop(\n access: ITradingAccess,\n input: IMoveStop,\n markPrice: number,\n ): Promise<{ symbolXex: string; id: string }> {\n const trigger = roundPrice(input.triggerPrice, input.tickSize, input.lotSize);\n const perdant = input.direction === 'long' ? trigger >= markPrice : trigger <= markPrice;\n if (perdant === true) {\n throw new Error(\n `moveStop(${access.xex}/${input.symbolXex}) : un stop ${input.direction} à ${trigger} est du mauvais côté du marché (${markPrice}) — il se déclencherait aussitôt et solderait la position.`,\n );\n }\n const perp = this.perpOf(access);\n if (typeof perp.moveStop !== 'function') {\n throw new Error(\n `moveStop(${access.xex}) : cette venue ne sait pas déplacer un stop. Disponible sur ${VENUES_MOVE_STOP.join(', ')}.`,\n );\n }\n // La borne d'exécution du déclenché part du DÉCLENCHEMENT, du côté défavorable : un stop de long\n // s'exécute en vendant, donc sa borne est SOUS le seuil. Sans borne, un marché qui saute laisse\n // partir la position à n'importe quel prix.\n const glissement = input.slippagePct ?? 0.005;\n const borne = input.direction === 'long' ? 1 - glissement : 1 + glissement;\n const moved = await perp.moveStop({\n name: input.symbolXex,\n side: input.direction === 'long' ? 'buy' : 'sell',\n stopId: input.stopId,\n triggerPrice: String(trigger),\n size: String(roundToStep(input.size, input.lotSize, 'floor')),\n price: String(roundPrice(trigger * borne, input.tickSize, input.lotSize)),\n });\n this.logger.log(`moveStop(${access.xex}/${input.symbolXex}) : stop porté à ${trigger}`);\n return { symbolXex: moved.name, id: moved.id };\n }\n\n /**\n * LE PRIX DE SORTIE RÉEL d'une position, lu dans l'historique de la venue.\n *\n * Le prix planifié ne dit pas à quel prix on est sorti : un stop se déclenche au marché, un\n * take-profit peut remplir en plusieurs fois. Seul l'historique porte le prix réellement obtenu, et\n * c'est lui qui doit servir au calcul du résultat — sinon le PnL affiché est une fiction.\n *\n * On retient le **dernier** ordre de sortie rempli après `openedAt` : les précédents sont les\n * take-profits partiels, celui-là est la clôture.\n */\n public async exitPrice(\n access: ITradingAccess,\n symbolXex: string,\n direction: Direction,\n openedAt: Date,\n ): Promise<string | null> {\n const exit: Side = direction === 'long' ? 'sell' : 'buy';\n const history = await this.perpOf(access).getHistory({ name: symbolXex });\n const exits = history\n .filter((order) => order.name === symbolXex && order.side === exit)\n .filter((order) => order.status === 'filled' || order.status === 'partiallyFilled')\n .filter((order) => order.placedAt.getTime() > openedAt.getTime())\n .filter((order) => order.price !== null && Number(order.price) > 0)\n .sort((left, right) => right.placedAt.getTime() - left.placedAt.getTime());\n const last = exits[0];\n return last === undefined ? null : last.price;\n }\n\n /**\n * L'HISTORIQUE DES ORDRES du compte — ce qui a été soumis, rempli, annulé ou expiré.\n *\n * À distinguer de {@link trades} : un ordre est une **intention**, une exécution est un **fait**.\n * Un ordre `filled` peut avoir été rempli en plusieurs fois, à des prix différents.\n *\n * Servi par les huit venues qui tradent.\n */\n public async orderHistory(access: ITradingAccess, symbolXex?: string): Promise<IOrder[]> {\n const orders = await this.perpOf(access).getHistory(\n symbolXex === undefined ? undefined : { name: symbolXex },\n );\n return orders.map((order) => this.toOrder(order, access.xex));\n }\n\n /**\n * L'ÉTAT DU COMPTE — équité, disponible, marge immobilisée, marge de maintenance, PnL latent.\n *\n * Les venues rangent ces valeurs sous des noms qui n'ont rien à voir : `marginSummary.accountValue`\n * chez hyperliquid, `accountEquity` chez pacifica, `totalMarginBalance` chez aster — **mais elles\n * disent la même chose**. La forme native complète reste dans `xtras`, rien n'est perdu.\n *\n * **blofin ne l'expose pas** et lève ; les sept autres le servent.\n */\n public async accountInfo(access: ITradingAccess): Promise<IAccountState> {\n const perp = this.perpOf(access);\n if (typeof perp.getAccountInfo !== 'function') {\n throw new Error(\n `accountInfo(${access.xex}) : cette venue n'expose pas l'état de son compte.`,\n );\n }\n const brut = (await perp.getAccountInfo()) as Record<string, unknown>;\n return this.toAccountState(brut, access.xex);\n }\n\n /**\n * RÈGLE LE LEVIER d'une paire, avant d'ouvrir.\n *\n * Le levier détermine la marge immobilisée, donc la taille tenable : le changer après l'ouverture\n * ne rejoue pas la position déjà prise. C'est un réglage préalable, pas un ajustement.\n *\n * **bullet ne l'expose pas** — son levier se fixe au compte, pas à la position — et lève.\n */\n public async setLeverage(\n access: ITradingAccess,\n symbolXex: string,\n leverage: number,\n ): Promise<void> {\n const perp = this.perpOf(access);\n if (typeof perp.updateLeverage !== 'function') {\n throw new Error(`setLeverage(${access.xex}) : cette venue ne règle pas le levier par paire.`);\n }\n await perp.updateLeverage({ name: symbolXex, leverage });\n this.logger.log(`setLeverage(${access.xex}/${symbolXex}) : levier porté à ${leverage}`);\n }\n\n /**\n * CHOISIT LE MODE DE MARGE d'une paire : **isolée** ou **croisée**.\n *\n * En **croisée**, tout le solde du compte garantit la position : elle tient plus longtemps, mais\n * une liquidation emporte l'ensemble. En **isolée**, seule la marge affectée est en jeu : la perte\n * est bornée à ce montant, la liquidation arrive plus tôt.\n *\n * **À régler AVANT d'ouvrir.** Changer le mode d'une position vivante est refusé par la plupart\n * des venues, et là où c'est accepté, cela redistribue la garantie sous les pieds de la position.\n *\n * Servi par les trois venues de référence.\n */\n public async setMarginMode(\n access: ITradingAccess,\n symbolXex: string,\n isolated: boolean,\n ): Promise<void> {\n const perp = this.perpOf(access);\n if (typeof perp.setMarginMode !== 'function') {\n throw new Error(\n `setMarginMode(${access.xex}) : cette venue ne choisit pas son mode de marge.`,\n );\n }\n await perp.setMarginMode({ name: symbolXex, isolated });\n this.logger.log(\n `setMarginMode(${access.xex}/${symbolXex}) : marge ${isolated === true ? 'ISOLÉE' : 'CROISÉE'}`,\n );\n }\n\n /**\n * AJOUTE DE LA MARGE à une position isolée — pour éloigner le prix de liquidation.\n *\n * C'est le geste qui sauve une position sous pression sans la réduire : on renforce la garantie\n * plutôt que de couper. **N'a de sens qu'en marge isolée** ; en croisée, tout le solde garantit\n * déjà la position et il n'y a rien à ajouter.\n *\n * Servi par les trois venues de référence.\n */\n public async addMargin(access: ITradingAccess, symbolXex: string, amount: string): Promise<void> {\n const perp = this.perpOf(access);\n if (typeof perp.addIsolatedMargin !== 'function') {\n throw new Error(`addMargin(${access.xex}) : cette venue n'ajoute pas de marge isolée.`);\n }\n await perp.addIsolatedMargin({ name: symbolXex, amount });\n this.logger.log(`addMargin(${access.xex}/${symbolXex}) : +${amount} de marge isolée`);\n }\n\n /**\n * RETIRE DE LA MARGE d'une position isolée — pour libérer du capital.\n *\n * L'inverse d'{@link addMargin} : la garantie diminue, donc le prix de liquidation **se rapproche**.\n * La venue refuse ce qui mettrait la position sous son seuil de maintenance.\n *\n * ⚠️ **pacifica ne l'expose pas** et lève : chez elle, la marge s'ajoute mais ne se retire pas.\n * Fermer partiellement la position libère alors le capital.\n */\n public async removeMargin(\n access: ITradingAccess,\n symbolXex: string,\n amount: string,\n ): Promise<void> {\n const perp = this.perpOf(access);\n if (typeof perp.removeIsolatedMargin !== 'function') {\n throw new Error(\n `removeMargin(${access.xex}) : cette venue ne retire pas de marge isolée — ferme partiellement la position pour libérer du capital.`,\n );\n }\n await perp.removeIsolatedMargin({ name: symbolXex, amount });\n this.logger.log(`removeMargin(${access.xex}/${symbolXex}) : −${amount} de marge isolée`);\n }\n\n /**\n * L'HISTORIQUE DU FUNDING **PAYÉ** sur une paire — ce qui a réellement été prélevé ou reçu.\n *\n * À ne pas confondre avec le taux courant, que rend `PricesService` : celui-ci annonce ce qui\n * *sera* payé, celui-là dit ce qui *l'a été*. Un backtest qui ignore le funding réel surestime le\n * résultat d'une position tenue longtemps.\n *\n * Servi par les huit venues qui tradent.\n */\n public async fundingHistory(\n access: ITradingAccess,\n symbolXex: string,\n range?: { startTime?: Date; endTime?: Date; limit?: number },\n ): Promise<IFundingPaid[]> {\n const perp = this.perpOf(access);\n if (typeof perp.getFundingHistory !== 'function') {\n throw new Error(\n `fundingHistory(${access.xex}) : cette venue ne publie pas son historique de funding.`,\n );\n }\n const rates = await perp.getFundingHistory({\n name: symbolXex,\n startTime: range?.startTime === undefined ? undefined : toXexDate(range.startTime),\n endTime: range?.endTime === undefined ? undefined : toXexDate(range.endTime),\n limit: range?.limit,\n });\n return rates.map((rate) => ({\n xex: access.xex,\n symbolXex: rate.name,\n rate: rate.rate,\n appliedAt: rate.appliedAt,\n xtras: rate.xtras,\n }));\n }\n\n /**\n * UN IDENTIFIANT APPLICATIF pour un ordre, **au format que la venue accepte**.\n *\n * C'est lui qui relie un ordre à la décision qui l'a produit : la venue le rend tel quel dans\n * `IOrder.clientId`, ce qui permet de retrouver son origine sans tenir de table de correspondance.\n *\n * **hyperliquid exige un hexadécimal préfixé `0x`** (128 bits) là où les autres acceptent un UUID\n * ordinaire. Passer le mauvais format fait rejeter l'ordre — d'où cette méthode plutôt qu'un\n * `randomUUID()` chez l'appelant.\n */\n public newClientOrderId(xex: XgateEx): string {\n const uuid = randomUUID();\n return xex === XgateEx.Hyperliquid ? `0x${uuid.replace(/-/gu, '')}` : uuid;\n }\n\n /**\n * LE COUPE-CIRCUIT : annule TOUS les ordres d'une paire, d'un geste.\n *\n * À réserver aux situations où l'on veut reprendre la main sans discuter — un état incohérent, un\n * arrêt d'urgence, une reprise après incident. Annuler un par un laisse une fenêtre pendant\n * laquelle certains ordres vivent encore.\n *\n * ⚠️ **Cela retire aussi les PROTECTIONS.** Stops et take-profits sont des ordres comme les\n * autres : une position ouverte se retrouve **nue** après ce geste. À n'employer que si la\n * position est fermée, ou si l'on repose une protection immédiatement — jamais pour « faire le\n * ménage » sur une position vivante.\n *\n * Rend le nombre d'ordres annulés quand la venue le publie, `null` sinon.\n */\n public async cancelAll(access: ITradingAccess, symbolXex: string): Promise<number | null> {\n const perp = this.perpOf(access);\n if (typeof perp.cancelAll !== 'function') {\n throw new Error(`cancelAll(${access.xex}) : cette venue n'annule pas en masse.`);\n }\n const positions = await perp.getPositions({ name: symbolXex });\n const ouverte = positions.find(\n (candidate) => candidate.name === symbolXex && Number(candidate.size) !== 0,\n );\n if (ouverte !== undefined) {\n this.logger.warn(\n `cancelAll(${access.xex}/${symbolXex}) : ${ouverte.size} EN POSITION — ses protections partent avec. La position sera NUE.`,\n );\n }\n const { cancelled } = await perp.cancelAll({ name: symbolXex });\n this.logger.log(\n `cancelAll(${access.xex}/${symbolXex}) : ${cancelled ?? '?'} ordre(s) annulé(s)`,\n );\n return cancelled;\n }\n\n /** Annule un ordre par son identifiant de venue. */\n public async cancel(access: ITradingAccess, symbolXex: string, orderId: string): Promise<void> {\n await this.perpOf(access).cancel({ name: symbolXex, id: orderId });\n }\n\n /** Le scope `perp()` de la venue, typé au plus près de ce que le contrat commun garantit. */\n private perpOf(access: ITradingAccess): {\n getPositions(query?: { name?: string }): Promise<IXexPosition[]>;\n getOpens(): Promise<IXexOrder[]>;\n getHistory(query?: { name?: string }): Promise<IXexOrder[]>;\n getAccountInfo?(): Promise<unknown>;\n updateLeverage?(input: { name: string; leverage: number }): Promise<unknown>;\n setMarginMode?(input: { name: string; isolated: boolean }): Promise<void>;\n addIsolatedMargin?(input: { name: string; amount: string }): Promise<void>;\n removeIsolatedMargin?(input: { name: string; amount: string }): Promise<void>;\n getFundingHistory?(query: {\n name: string;\n startTime?: string;\n endTime?: string;\n limit?: number;\n }): Promise<IXexFundingRate[]>;\n getUserTrades(): Promise<IXexTrade[]>;\n place(input: {\n name: string;\n side: Side;\n type: string;\n size: string;\n reduceOnly: boolean;\n }): Promise<IXexOrder>;\n cancel(input: { name: string; id: string }): Promise<unknown>;\n cancelAll?(input: { name: string }): Promise<{ cancelled: number | null }>;\n moveStop?(input: {\n name: string;\n side: Side;\n stopId: string;\n triggerPrice: string;\n size: string;\n price?: string;\n }): Promise<{ name: string; id: string }>;\n createEntryWithProtection?(entry: unknown, protection: unknown): Promise<IXexOrder[]>;\n } {\n const source = createAccountXex(access.xex, access) as unknown as {\n perp(): ReturnType<TradingService['perpOf']>;\n };\n return source.perp();\n }\n\n /**\n * L'état natif d'une venue → {@link IAccountState}.\n *\n * Une lecture EXPLICITE par venue plutôt qu'un parcours à l'aveugle : les noms ne se devinent pas,\n * et prendre le premier champ qui ressemble à une équité produirait un chiffre faux sans que rien\n * ne le signale. `null` là où la venue ne publie pas la valeur — pas `0`, qui se lirait comme\n * « rien » alors que personne n'a rien dit.\n */\n private toAccountState(brut: Record<string, unknown>, xex: XgateEx): IAccountState {\n const texte = (valeur: unknown): string | null =>\n valeur === undefined || valeur === null ? null : String(valeur);\n const marge = brut.marginSummary as Record<string, unknown> | undefined;\n return {\n xex,\n equity:\n texte(marge?.accountValue) ??\n texte(brut.accountEquity) ??\n texte(brut.totalMarginBalance) ??\n '0',\n available:\n texte(brut.withdrawable) ?? texte(brut.availableToSpend) ?? texte(brut.availableBalance),\n marginUsed:\n texte(marge?.totalMarginUsed) ??\n texte(brut.totalMarginUsed) ??\n texte(brut.totalInitialMargin),\n maintenanceMargin:\n texte(brut.crossMaintenanceMarginUsed) ??\n texte(brut.crossMmr) ??\n texte(brut.totalMaintMargin),\n unrealizedPnl: texte(brut.totalUnrealizedProfit),\n xtras: brut,\n };\n }\n\n /**\n * Le statut de la venue, VALIDÉ — jamais casté. Une venue qui inventerait un statut rendrait sinon\n * une valeur absente de tous les `switch` en aval, et personne ne la rattraperait.\n */\n private toStatus(status: string): OrderStatus {\n return ORDER_STATUSES.includes(status as OrderStatus) === true\n ? (status as OrderStatus)\n : 'other';\n }\n\n private toOrder(order: IXexOrder, xex: XgateEx): IOrder {\n return {\n xex,\n symbolXex: order.name,\n kind: order.kind as 'perp' | 'spot',\n id: order.id,\n clientId: order.clientId,\n side: order.side as Side,\n type: order.type as OrderType,\n price: order.price,\n size: order.size,\n filled: order.filled,\n status: this.toStatus(order.status),\n tif: order.tif as TimeInForce | null,\n reduceOnly: order.reduceOnly,\n placedAt: order.placedAt,\n xtras: order.xtras,\n };\n }\n}\n\n/** Un taux de funding tel qu'un SDK de venue le rend. */\ninterface IXexFundingRate {\n name: string;\n rate: string;\n appliedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * Une venue attend ses dates en chaîne UTC `YYYY-MM-DD HH:MM:SS` — c'est la convention commune aux\n * huit SDK. On traduit ici, l'appelant d'XGate ne manipule que des `Date`.\n */\nfunction toXexDate(date: Date): string {\n return date.toISOString().slice(0, 19).replace('T', ' ');\n}\n\n/** Un ordre tel qu'un SDK de venue le rend : **clés longues et `Date`**. */\ninterface IXexOrder {\n name: string;\n kind: string;\n id: string;\n clientId: string | null;\n side: string;\n type: string;\n price: string | null;\n size: string;\n filled: string;\n status: string;\n tif: string | null;\n reduceOnly: boolean | null;\n placedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/** Une position telle qu'un SDK de venue la rend. */\ninterface IXexPosition {\n name: string;\n side: 'long' | 'short' | null;\n size: string;\n entryPrice: string | null;\n markPrice: string | null;\n unrealizedPnl: string | null;\n leverage: number | null;\n liquidationPrice: string | null;\n margin: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/** Une exécution telle qu'un SDK de venue la rend. */\ninterface IXexTrade {\n name: string;\n kind: string;\n id: string;\n orderId: string;\n side: string;\n price: string;\n size: string;\n fee: string;\n feeAsset: string | null;\n grossPnl: string | null;\n netPnl: string | null;\n maker: boolean | null;\n filledAt: Date;\n xtras?: Record<string, unknown>;\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { type IXexAccess, WALLET_XEXES, createAccountXex } from '../helpers/xex-registry';\nimport type {\n IBalance,\n IEquityPoint,\n IMovement,\n MovementKind,\n MovementStatus,\n} from '../interfaces/wallet.interfaces';\n\n/** Une venue et de quoi y lire un compte. */\nexport interface IWalletAccess extends IXexAccess {\n xex: XgateEx;\n}\n\n/**\n * LE PORTEFEUILLE, toutes venues confondues : soldes, mouvements d'argent, valeur du compte.\n *\n * **Les positions n'en font pas partie** — elles appartiennent au bloc trading, indissociable des\n * ordres. Un portefeuille répond à « qu'est-ce que j'ai et d'où ça vient » ; une position répond à\n * « qu'est-ce que je risque », et les deux ne se lisent pas au même moment.\n *\n * **Ce service ne calcule rien.** Il n'additionne pas les soldes entre venues, ne convertit aucun\n * montant, ne reconstitue aucune courbe d'équité là où la venue n'en publie pas. Chaque ligne porte\n * son `xex` : c'est à l'appelant de décider ce qui s'additionne, parce que lui seul sait si deux\n * USDC à deux endroits sont fongibles pour son usage.\n *\n * **Lire un portefeuille est PUBLIC sur les DEX** : l'adresse suffit, aucune signature n'est\n * produite. Seuls les CEX exigent une clé, même en lecture.\n */\n@Injectable()\nexport class WalletService {\n private readonly logger = new Logger(WalletService.name);\n\n /** Les venues dont le portefeuille est lisible aujourd'hui. */\n public venues(): readonly XgateEx[] {\n return WALLET_XEXES;\n }\n\n /**\n * Les soldes d'un ou plusieurs comptes.\n *\n * Même politique d'échec que les autres services : sur un seul accès, l'échec est relancé ; sur\n * plusieurs, la liste partielle garde sa valeur et l'incident est journalisé. Rendre `[]` en\n * silence ferait passer une venue injoignable pour un compte vide — et un compte vide, ça se\n * regarde autrement qu'une panne.\n */\n public async balances(...accesses: IWalletAccess[]): Promise<IBalance[]> {\n return this.collect(accesses, 'balances', async (access) => {\n const source = createAccountXex(access.xex, access);\n const balances = await source.account().getBalances();\n const lignes: IBalance[] = balances.map((balance) => ({\n xex: access.xex,\n asset: balance.asset,\n total: balance.total,\n available: balance.available,\n usdValue: balance.usdValue,\n xtras: balance.xtras,\n }));\n const collateral = await this.perpCollateral(source, access.xex);\n return collateral === null ? lignes : [...lignes, collateral];\n });\n }\n\n /**\n * LE COLLATÉRAL PERPÉTUEL, ajouté aux soldes comptant — sans lui, un portefeuille peut paraître VIDE.\n *\n * `getBalances` ne rend que le **comptant**, chez les huit venues : c'est leur convention, et la\n * documentation de chacune le dit. Or l'argent qui sert à trader n'y est pas. Sur un compte\n * pacifica dont tout le solde est en marge, `getBalances` rend `[]` — ce qui se lit « pas\n * d'argent » alors qu'il y a 499 USDC. Sur hyperliquid, le comptant s'affiche mais la marge\n * manque.\n *\n * La ligne est **marquée `scope: 'perp'`** dans `xtras` : elle s'additionne au comptant sans le\n * doubler, et un appelant qui ne veut que l'un des deux peut trancher.\n *\n * Le champ diffère par venue et n'est pas devinable — d'où la lecture explicite, venue par venue,\n * plutôt qu'un parcours à l'aveugle d'un objet `unknown`.\n */\n private async perpCollateral(\n source: ReturnType<typeof createAccountXex>,\n xex: XgateEx,\n ): Promise<IBalance | null> {\n // Les méthodes s'appellent SUR leur objet : les extraire (`const f = obj.f`) leur retire leur\n // `this`, et la façade échoue sur son propre client.\n const facade = source as unknown as {\n perp?: () => { getAccountInfo?: () => Promise<unknown> };\n };\n if (typeof facade.perp !== 'function') {\n return null;\n }\n const scope = facade.perp();\n if (typeof scope.getAccountInfo !== 'function') {\n return null;\n }\n const info = (await scope.getAccountInfo()) as {\n // pacifica : le solde du compte, en USD, hors comptant (cf. `spot_market_value`).\n balance?: string;\n availableToSpend?: string;\n // hyperliquid : l'équité du compte perp, comptant exclu.\n marginSummary?: { accountValue?: string };\n withdrawable?: string;\n };\n const total = info.balance ?? info.marginSummary?.accountValue;\n if (total === undefined || Number(total) === 0) {\n return null;\n }\n return {\n xex,\n asset: 'USDC',\n total,\n available: info.availableToSpend ?? info.withdrawable ?? null,\n usdValue: total,\n xtras: { scope: 'perp' },\n };\n }\n\n /**\n * Les mouvements d'argent, triés du plus ancien au plus récent, montants **signés**.\n *\n * **Lève si la venue ne les sert pas** plutôt que de rendre une liste vide : « cette venue\n * n'expose pas son historique » et « ce compte n'a jamais bougé » appellent des suites très\n * différentes.\n */\n public async movements(...accesses: IWalletAccess[]): Promise<IMovement[]> {\n const collected = await this.collect(accesses, 'movements', async (access) => {\n const account = createAccountXex(access.xex, access).account();\n if (account.getMovements === undefined) {\n throw new Error(`movements(${access.xex}) : cette venue n'expose pas ses mouvements.`);\n }\n const movements = await account.getMovements();\n return movements.map((movement) => ({\n xex: access.xex,\n kind: movement.kind as MovementKind,\n amount: movement.amount,\n fee: movement.fee,\n asset: movement.asset,\n occurredAt: movement.occurredAt,\n status: movement.status as MovementStatus,\n externalId: movement.externalId,\n xtras: movement.xtras,\n }));\n });\n return collected.sort((left, right) => left.occurredAt.getTime() - right.occurredAt.getTime());\n }\n\n /**\n * La valeur du compte dans le temps.\n *\n * **Seules hyperliquid et pacifica la publient**, et XGate ne la fabrique pas ailleurs :\n * reconstituer une courbe depuis les mouvements et les prix donnerait une valeur qu'aucune venue\n * n'a cotée, avec une méthode incomparable à celle des deux qui la publient. Lève sur une venue\n * qui n'en sert pas.\n */\n public async equityHistory(...accesses: IWalletAccess[]): Promise<IEquityPoint[]> {\n const collected = await this.collect(accesses, 'equityHistory', async (access) => {\n const account = createAccountXex(access.xex, access).account();\n if (account.getEquityHistory === undefined) {\n throw new Error(\n `equityHistory(${access.xex}) : cette venue ne publie pas la valeur de compte dans le temps.`,\n );\n }\n const points = await account.getEquityHistory();\n return points.map((point) => ({\n xex: access.xex,\n measuredAt: point.measuredAt,\n equity: point.equity,\n }));\n });\n return collected.sort((left, right) => left.measuredAt.getTime() - right.measuredAt.getTime());\n }\n\n /** Le motif commun aux trois lectures : un accès seul relance, plusieurs journalisent. */\n private async collect<T>(\n accesses: IWalletAccess[],\n capacite: string,\n lire: (access: IWalletAccess) => Promise<T[]>,\n ): Promise<T[]> {\n if (accesses.length === 0) {\n throw new Error(`${capacite}() : aucun accès fourni — précise au moins une venue.`);\n }\n const solo = accesses.length === 1;\n const collected = await Promise.all(\n accesses.map(async (access) => {\n try {\n return await lire(access);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`${capacite}(${access.xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { lisible } from '../helpers/socket-error';\nimport { type IXexSource, createPublicXex } from '../helpers/xex-registry';\nimport type { ICandle } from '../interfaces/candle.interfaces';\n\n/** Couper le flux. */\nexport type Unsubscribe = () => void;\n\n/** Le client temps réel d'une venue, tel que sa façade le rend. */\ntype WireTempsReel = ReturnType<IXexSource['ws']> & {\n onError?: ((error: unknown) => void) | null;\n};\n\n/**\n * LES BOUGIES EN TEMPS RÉEL — le présent.\n *\n * Séparé de `CandlesService` à dessein : rejouer un historique et suivre un flux ne se\n * consomment pas de la même façon, et n'ont pas les mêmes coûts. Une application qui ne fait que\n * du backfill n'a aucune socket à ouvrir.\n */\n@Injectable()\nexport class WsCandlesService {\n private readonly logger = new Logger(WsCandlesService.name);\n\n /** Un client temps réel par venue — voir {@link wireOf}. La clé EST la venue. */\n private readonly wires = new Map<XgateEx, WireTempsReel>();\n\n /**\n * Souscrit aux bougies d'un marché chez une venue. Rend la fonction de désabonnement.\n *\n * Le handler reçoit **une bougie à la fois**, au format unifié — c'est la forme commune aux neuf\n * SDK. La bougie en cours est repoussée à chaque mise à jour tant qu'elle n'est pas close ;\n * `closedAt` dit jusqu'où elle porte.\n *\n * **Attention à extended** : sur un intervalle qu'elle ne connaît pas, sa façade rend un\n * désabonnement vide au lieu de lever. L'appelant se croit abonné et n'entend jamais rien — un\n * silence indiscernable d'un marché sans échange. C'est un défaut du SDK, pas d'XGate, et il est\n * dans la file des corrections.\n */\n public subscribe(\n xex: XgateEx,\n query: { symbol: string; interval: Timeframe },\n handler: (candle: ICandle) => void,\n ): Unsubscribe {\n const quote = this.quoteOf(query.symbol);\n this.logger.log(`souscription bougies ${query.symbol} ${query.interval} sur ${xex}`);\n return this.wireOf(xex).subscribeCandles(\n { name: query.symbol, interval: query.interval },\n (wire) => {\n handler(toCandle(wire, xex, query.interval, quote));\n },\n );\n }\n\n /**\n * Souscrit au MÊME marché chez plusieurs venues, avec un seul handler.\n *\n * Chaque bougie porte son `xex` : l'appelant sait toujours qui parle. La fonction rendue coupe\n * tous les flux d'un coup — sinon il faudrait en garder N et n'en oublier aucun.\n */\n public subscribeAll(\n xexes: XgateEx[],\n query: { symbol: string; interval: Timeframe },\n handler: (candle: ICandle) => void,\n ): Unsubscribe {\n if (xexes.length === 0) {\n throw new Error('subscribeAll() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const stops: Unsubscribe[] = [];\n for (const xex of xexes) {\n try {\n stops.push(this.subscribe(xex, query, handler));\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`souscription ${xex} refusée, venue ignorée : ${message}`);\n }\n }\n return () => {\n for (const stop of stops) {\n stop();\n }\n };\n }\n\n /**\n * Le client temps réel d'une venue — **créé une seule fois, donc UNE SOCKET PAR VENUE**.\n *\n * C'est la règle que `CandlesStreamService` énonce déjà et que ce service violait : chaque\n * `subscribe()` appelait `createPublicXex(xex)`, qui construit une façade NEUVE. Or la façade\n * mémoïse son client temps réel (`unifiedWs()`), et c'est lui qui porte la socket : une façade\n * par souscription, c'est une socket par souscription.\n *\n * Mesuré le 2026-08-13 sur Blips : 1 104 souscriptions ont ouvert **1 104 sockets** sur sept\n * venues, quand hyperliquid n'en accepte que 10 par IP. Les descripteurs s'épuisent, les venues\n * refusent, et chaque connexion refusée rejetait une promesse que personne n'attendait — le\n * process entier mourait deux minutes après l'ouverture des flux. Ici : **sept sockets**.\n *\n * Rien à fermer explicitement : le ref-count vit dans les SDK (`refs += 1` / `release()`), donc\n * la socket se ferme d'elle-même au dernier désabonnement de la venue, et se rouvre au suivant.\n */\n private wireOf(xex: XgateEx): WireTempsReel {\n let wire = this.wires.get(xex);\n if (wire === undefined) {\n wire = createPublicXex(xex).ws() as WireTempsReel;\n // Le crochet d'erreur du SDK : sans lui, une socket qui casse ne dit rien à personne — les\n // SDK n'écrivent jamais sur la sortie standard, ils ne font que notifier. On le branche donc\n // au moment même où le client naît, sinon un incident survenu avant reste muet.\n if ('onError' in wire) {\n wire.onError = (error: unknown) => {\n this.logger.error(`${xex} : erreur de flux — ${lisible(error)}`);\n };\n }\n this.wires.set(xex, wire);\n }\n return wire;\n }\n\n /** Voir `CandlesService.quoteOf` : la cotation se déduit du symbole natif, faute d'être publiée. */\n private quoteOf(symbolXex: string): string {\n const parts = symbolXex.split('-');\n const last = parts[parts.length - 1];\n return parts.length > 1 && last !== undefined ? last : '';\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { createAccountXex } from '../helpers/xex-registry';\nimport { ORDER_STATUSES } from '../interfaces/trading.interfaces';\nimport type { IOrder, IPublicTrade, ITrade, Side } from '../interfaces/trading.interfaces';\nimport type { ITradingAccess } from './trading.service';\nimport type { Unsubscribe } from './ws-candles.service';\n\n/** Un ordre tel qu'un SDK de venue le diffuse. */\ninterface IXexOrderWire {\n name: string;\n kind: string;\n id: string;\n clientId: string | null;\n side: string;\n type: string;\n price: string | null;\n size: string;\n filled: string;\n status: string;\n tif: string | null;\n reduceOnly: boolean | null;\n placedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/** Une transaction publique telle qu'un SDK de venue la diffuse. */\ninterface IXexPublicTrade {\n name: string;\n price: string;\n size: string;\n side: string;\n tradedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/** Ce qu'un SDK de venue rend sur son flux d'exécutions : **clés longues et `Date`**. */\ninterface IXexUserTrade {\n name: string;\n kind: string;\n id: string;\n orderId: string;\n side: string;\n price: string;\n size: string;\n fee: string;\n feeAsset: string | null;\n grossPnl: string | null;\n netPnl: string | null;\n maker: boolean | null;\n filledAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * LE FLUX DES EXÉCUTIONS DU COMPTE — savoir ce qui s'est rempli, à l'instant où ça se remplit.\n *\n * **C'est la seule façon de connaître son état exact.** Sans lui, on interroge en boucle et on\n * apprend un remplissage avec le retard du sondage : entre-temps, un take-profit partiel a pu\n * modifier la taille de la position, et toute décision prise sur l'ancienne valeur est fausse.\n *\n * Le flux ne dit pas ce qu'on a **demandé** mais ce qui a été **obtenu** : un ordre annoncé `filled`\n * peut l'avoir été en plusieurs fois, à des prix différents. Chaque exécution porte son prix réel.\n *\n * **ON NE TRANSMET QUE CE QUI ARRIVE APRÈS L'OUVERTURE DU FLUX.** Les venues rejouent leur\n * historique à la souscription : mesuré le 2026-08-09 sur hyperliquid, 30 exécutions arrivent dans\n * les six premières secondes, **toutes antérieures** à l'abonnement, la plus ancienne de plus de\n * deux jours. Sans filtre, chaque reconnexion rejouerait deux jours de trades — et un consommateur\n * qui compte les remplissages compterait deux fois les mêmes.\n *\n * Le filtre se fait sur `filledAt` contre l'instant d'abonnement. Un flux dit le **présent** ;\n * l'historique se lit avec `TradingService.trades()`, qui est fait pour ça.\n *\n * **blofin est la seule venue qui ne l'expose pas** ; elle lève. Les sept autres le servent.\n */\n@Injectable()\nexport class WsTradesService {\n private readonly logger = new Logger(WsTradesService.name);\n\n /**\n * S'abonne aux exécutions d'un compte. Rend la fonction de désabonnement.\n *\n * Le handler reçoit **une exécution à la fois**, au format unifié, et **uniquement celles\n * survenues après l'abonnement** — le rejeu d'historique de la venue est écarté.\n */\n public subscribe(access: ITradingAccess, handler: (trade: ITrade) => void): Unsubscribe {\n const source = createAccountXex(access.xex, access) as unknown as {\n ws?: (label?: string) => {\n subscribeUserTrades?: (cb: (trade: IXexUserTrade) => void) => Unsubscribe;\n };\n };\n if (typeof source.ws !== 'function') {\n throw new Error(`userTrades(${access.xex}) : cette venue n'expose pas de temps réel.`);\n }\n const ws = source.ws();\n if (typeof ws.subscribeUserTrades !== 'function') {\n throw new Error(\n `userTrades(${access.xex}) : cette venue ne diffuse pas les exécutions du compte.`,\n );\n }\n // L'instant d'abonnement fait la frontière : tout ce qui porte un `filledAt` antérieur est du\n // rejeu, pas un événement. Relevé AVANT la souscription — la venue peut pousser dès l'ouverture.\n const depuis = Date.now();\n let ecartees = 0;\n this.logger.log(`souscription exécutions sur ${access.xex}`);\n return ws.subscribeUserTrades((trade) => {\n if (trade.filledAt.getTime() < depuis) {\n ecartees += 1;\n if (ecartees === 1) {\n this.logger.log(`${access.xex} : rejeu d'historique écarté (antérieur à l'abonnement).`);\n }\n return;\n }\n handler(this.toTrade(trade, access.xex));\n });\n }\n\n /**\n * S'abonne aux exécutions de PLUSIEURS comptes, avec un seul handler.\n *\n * Chaque exécution porte son `xex` : l'appelant sait toujours de quelle venue elle vient. La\n * fonction rendue coupe tous les flux d'un coup — sinon il faudrait en garder N et n'en oublier\n * aucun.\n *\n * Une venue qui refuse est journalisée et ignorée : le reste des comptes doit continuer d'être\n * suivi. Sur un seul accès, l'échec est celui de l'appel.\n */\n public subscribeAll(accesses: ITradingAccess[], handler: (trade: ITrade) => void): Unsubscribe {\n if (accesses.length === 0) {\n throw new Error('userTrades() : aucun compte demandé — précise au moins un accès.');\n }\n const solo = accesses.length === 1;\n const stops: Unsubscribe[] = [];\n for (const access of accesses) {\n try {\n stops.push(this.subscribe(access, handler));\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`souscription ${access.xex} refusée, venue ignorée : ${message}`);\n }\n }\n return () => {\n for (const stop of stops) {\n try {\n stop();\n } catch {\n // un désabonnement qui échoue ne doit pas empêcher les autres de se fermer\n }\n }\n };\n }\n\n /**\n * S'abonne aux ORDRES du compte — leur naissance, leur remplissage, leur mort.\n *\n * À distinguer des exécutions : un ordre est une **intention** dont on suit le cycle de vie\n * (`open` → `partiallyFilled` → `filled`, ou `canceled`), une exécution est un **fait** ponctuel.\n * Pour savoir qu'un stop vient de se déclencher, c'est ici qu'il faut écouter ; pour savoir à quel\n * prix, c'est {@link subscribe}.\n *\n * **Même règle que pour les exécutions** : seuls les ordres postérieurs à l'abonnement sont\n * transmis, le rejeu d'historique est écarté.\n */\n public subscribeOrders(access: ITradingAccess, handler: (order: IOrder) => void): Unsubscribe {\n const ws = this.wsOf(access);\n if (typeof ws.subscribeOrders !== 'function') {\n throw new Error(`orders(${access.xex}) : cette venue ne diffuse pas ses ordres.`);\n }\n const depuis = Date.now();\n this.logger.log(`souscription ordres sur ${access.xex}`);\n return ws.subscribeOrders((order) => {\n if (order.placedAt.getTime() >= depuis) {\n handler(this.toOrder(order, access.xex));\n }\n });\n }\n\n /**\n * S'abonne aux TRANSACTIONS PUBLIQUES d'un marché — celles de tout le monde, pas les siennes.\n *\n * C'est le flux qui dit ce qui se négocie réellement : prix, taille, sens agresseur. Il sert à\n * mesurer l'activité d'une paire, pas à suivre son compte — pour cela, {@link subscribe}.\n *\n * **Aucun filtre temporel ici** : un trade public est daté de son exécution et arrive en direct,\n * il n'y a pas d'historique rejoué à écarter.\n */\n public subscribePublicTrades(\n access: ITradingAccess,\n symbolXex: string,\n handler: (trade: IPublicTrade) => void,\n ): Unsubscribe {\n const ws = this.wsOf(access);\n if (typeof ws.subscribeTrades !== 'function') {\n throw new Error(`publicTrades(${access.xex}) : cette venue ne diffuse pas les transactions.`);\n }\n this.logger.log(`souscription transactions ${symbolXex} sur ${access.xex}`);\n return ws.subscribeTrades({ name: symbolXex }, (trade) => {\n handler({\n xex: access.xex,\n symbolXex: trade.name,\n price: trade.price,\n size: trade.size,\n side: trade.side as Side,\n tradedAt: trade.tradedAt,\n xtras: trade.xtras,\n });\n });\n }\n\n /** Le client temps réel d'un compte, avec ses souscriptions signées. */\n private wsOf(access: ITradingAccess): {\n subscribeUserTrades?: (cb: (trade: IXexUserTrade) => void) => Unsubscribe;\n subscribeOrders?: (cb: (order: IXexOrderWire) => void) => Unsubscribe;\n subscribeTrades?: (\n query: { name: string },\n cb: (trade: IXexPublicTrade) => void,\n ) => Unsubscribe;\n } {\n const source = createAccountXex(access.xex, access) as unknown as {\n ws?: (label?: string) => ReturnType<WsTradesService['wsOf']>;\n };\n if (typeof source.ws !== 'function') {\n throw new Error(`${access.xex} : cette venue n'expose pas de temps réel.`);\n }\n return source.ws();\n }\n\n private toOrder(order: IXexOrderWire, xex: XgateEx): IOrder {\n return {\n xex,\n symbolXex: order.name,\n kind: order.kind as 'perp' | 'spot',\n id: order.id,\n clientId: order.clientId,\n side: order.side as Side,\n type: order.type as IOrder['type'],\n price: order.price,\n size: order.size,\n filled: order.filled,\n status: ORDER_STATUSES.includes(order.status as IOrder['status'])\n ? (order.status as IOrder['status'])\n : 'other',\n tif: order.tif as IOrder['tif'],\n reduceOnly: order.reduceOnly,\n placedAt: order.placedAt,\n xtras: order.xtras,\n };\n }\n\n private toTrade(trade: IXexUserTrade, xex: XgateEx): ITrade {\n return {\n xex,\n symbolXex: trade.name,\n kind: trade.kind as 'perp' | 'spot',\n id: trade.id,\n orderId: trade.orderId,\n side: trade.side as Side,\n price: trade.price,\n size: trade.size,\n fee: trade.fee,\n feeAsset: trade.feeAsset,\n grossPnl: trade.grossPnl,\n netPnl: trade.netPnl,\n maker: trade.maker,\n filledAt: trade.filledAt,\n xtras: trade.xtras,\n };\n }\n}\n","import { Module } from '@nestjs/common';\nimport { CandlesStreamRegistry } from './services/candles-stream.service';\nimport { CandlesService } from './services/candles.service';\nimport { CatalogService } from './services/catalog.service';\nimport { PricesService } from './services/prices.service';\nimport { SpotCandlesService } from './services/spot-candles.service';\nimport { SpotCatalogService } from './services/spot-catalog.service';\nimport { SpotPricesService } from './services/spot-prices.service';\nimport { SpotWsCandlesService } from './services/spot-ws-candles.service';\nimport { TradingService } from './services/trading.service';\nimport { WalletService } from './services/wallet.service';\nimport { WsCandlesService } from './services/ws-candles.service';\nimport { WsTradesService } from './services/ws-trades.service';\n\n/**\n * Le module d'XGate : `imports: [XgateModule]`, puis on injecte le SEUL service dont on a besoin.\n *\n * Une capacité = un service, indépendant des autres. Le REST et le temps réel sont eux-mêmes\n * séparés (`CandlesService` / `WsCandlesService`) : rejouer le passé et suivre le présent ne se\n * consomment pas de la même façon, et une application qui ne fait que du backfill n'a aucune\n * socket à ouvrir.\n */\n@Module({\n providers: [\n CatalogService,\n CandlesService,\n CandlesStreamRegistry,\n WsCandlesService,\n WsTradesService,\n PricesService,\n SpotCatalogService,\n SpotCandlesService,\n SpotPricesService,\n SpotWsCandlesService,\n WalletService,\n ],\n exports: [\n CatalogService,\n CandlesService,\n CandlesStreamRegistry,\n WsCandlesService,\n WsTradesService,\n PricesService,\n SpotCatalogService,\n SpotCandlesService,\n SpotPricesService,\n SpotWsCandlesService,\n WalletService,\n ],\n})\nexport class XgateModule {}\n","import type { XgateEx } from '../enums/xgate-ex.enums';\n\n/**\n * COMBIEN DE BOUGIES UNE VENUE SERT PAR APPEL — uniquement ce qu'une documentation ou un SDK affirme.\n *\n * Le budget d'une venue se compte en APPELS, pas en bougies : dix fois plus de barres par appel, c'est dix fois\n * moins d'appels ET dix fois moins d'attente cumulée. C'est le seul levier qui décide de la durée d'un backfill.\n *\n * **binance** — « `limit` est plafonné à 1500 par la venue **sur les deux marchés** ; au-delà, elle refuse la\n * requête au lieu de tronquer » (`binance-sdk`, docblock de `getCandles`). Le plafond vaut donc pour le comptant\n * comme pour le perpétuel.\n *\n * **bybit** — `limit [1, 1000]`, défaut **200** (doc officielle v5, `/v5/market/kline`). C'est `category` qui\n * distingue comptant et perpétuel chez elle, pas la limite. ⚠️ SANS `limit` EXPLICITE, BYBIT EN REND 200, quelle\n * que soit la fenêtre demandée — mesuré le 2026-08-16 : 197 bougies par appel sur les six intervalles, les\n * huit cents manquantes n'étant jamais redemandées puisque le curseur avance de la fenêtre entière.\n *\n * **blofin** — 1440 ; la venue clampe EN SILENCE et le SDK lève au-delà (`blofin-sdk`, `getCandles`).\n *\n * **aster** — 1500 ; non documenté, API de forme binance.\n *\n * **hyperliquid** et **pacifica** n'acceptent AUCUN `limit` : leurs SDK n'envoient que `startTime`/`endTime`\n * (`get-candle-snapshot.ts`, `get-candle-data.ts`). Le nombre sert alors à dimensionner la FENÊTRE demandée, pas\n * à remplir un paramètre. Pour hyperliquid, la venue borne d'elle-même à ~5 000 bougies par (coin, intervalle) —\n * et l'horizon que ça couvre dépend de l'activité du coin, ce n'est donc pas une durée fixe.\n *\n * ⚠️ UNE VENUE ABSENTE DE CETTE TABLE N'A PAS DE PLAFOND DOCUMENTÉ — ce qui n'est pas « pas de plafond ». On ne\n * devine aucun chiffre : le jour où l'une d'elles s'en approche, on lit sa documentation et on l'ajoute.\n */\nexport const CANDLES_PER_CALL: Partial<Record<XgateEx, number>> = {\n binance: 1_500,\n bybit: 1_000,\n blofin: 1_440,\n aster: 1_500,\n hyperliquid: 5_000,\n pacifica: 500,\n} as Partial<Record<XgateEx, number>>;\n\n/**\n * Combien de bougies demander à une venue en un appel, ou `undefined` si personne ne l'a publié.\n *\n * ⚠️ LA CLÉ EST LA VENUE, PAS LA LIGNE DE CATALOGUE DE L'APPELANT. Un consommateur qui distingue le comptant du\n * perpétuel par deux identifiants à lui (`binance` / `binance-spot`) doit résoudre vers la venue avant de\n * demander : le plafond appartient à binance, pas à l'un de ses marchés. Interroger cette table avec un\n * identifiant maison rendait `undefined` et faisait retomber l'appelant sur une valeur de repli — trois fois trop\n * petite pour binance, deux fois pour bybit.\n */\nexport function candlesPerCallOf(xex: XgateEx): number | undefined {\n return CANDLES_PER_CALL[xex];\n}\n\n/**\n * LA CADENCE MINIMALE ENTRE DEUX APPELS, PAR VENUE — en millisecondes.\n *\n * ⚠️ INTERDIT DE DEVINER UN CHIFFRE ICI. Toute valeur se source dans la documentation de la venue, et la source\n * s'écrit dans ce commentaire. Un throttle inventé se paie en bannissement d'IP, pas en lenteur.\n *\n * Ces cadences sont un PLANCHER : si la venue répond plus lentement que son throttle, c'est sa latence qui\n * commande. Trois d'entre elles ont été corrigées PAR LA MESURE, jamais par confort — deux tours complets du\n * 2026-08-02 ayant compté les refus venue par venue :\n *\n * venue avant → après refus au 1er tour refus au 2e\n * pacifica 2 500 → 4 000 51 / 525 (9,7 %) 0\n * lighter 600 → 1 200 126 / 1 218 (10,3 %) 0\n * hyperliquid 200 → 500 17 / 1 239 (1,4 %) 39 / 708 (5,5 %) à 300 ms\n *\n * ⚠️ LEÇON DU CAS HYPERLIQUID : un premier réglage à 300 ms reposait sur un poids de requête SUPPOSÉ, et il a\n * EMPIRÉ la situation. Une cadence ne se déduit pas d'une hypothèse sur ce que coûte un appel — elle se dérive du\n * budget documenté, puis se vérifie au tour suivant.\n *\n * Détail des budgets :\n *\n * binance weight 2 par kline, budget IP 6 000 weight/min → ~3 000 req/min ; 150 ms est très en dessous.\n * 429 et 418 (ban d'IP) remontés par la venue.\n * bybit 600 req / 5 s en public → 150 ms reste prudent.\n * hyperliquid 1 200 weight/min par IP. À 387 ms réels on tirait 155 req/min en se faisant encore refouler :\n * la requête pèse donc ~8 weight et le budget est atteint vers 150 req/min. 500 ms garde 20 % de\n * marge, la même que celle qui a ramené lighter et pacifica à zéro refus.\n * blofin 500 req/min ET 1 500 req/5 min (docs.blofin.com). C'EST LA SECONDE QUI MORD : 300 req/min, deux\n * fois plus strict — et sa sanction est d'UNE HEURE contre cinq minutes. 250 ms tient 240 req/min.\n * pacifica 125 crédits / 60 s sur IP non identifiée, une kline étant un GET lourd à 3-12 crédits. La plus\n * contraignante de loin, et sa pénalité s'ALLONGE si on insiste. À 4 000 ms on tire 15 req/min,\n * soit 45 à 180 crédits — sous le budget pour tout coût jusqu'à 8.\n * lighter ~60 requêtes pondérées/min (palier Standard), les endpoints pesant ~300. Le budget donne\n * 1 000 ms tout juste ; 1 200 parce qu'un réglage COLLÉ au budget laisse un résidu de refus.\n * aster 2 400 weight/min, une kline au-delà de 1 000 bougies coûtant 10 → ~240 req/min.\n * paradex 1 500 req/min par IP.\n * extended API lente (2 à 4 s par requête observées) — le throttle ne mord jamais.\n */\nexport const REST_THROTTLE_MS: Partial<Record<XgateEx, number>> = {\n binance: 150,\n bybit: 150,\n hyperliquid: 500,\n extended: 200,\n paradex: 200,\n aster: 400,\n lighter: 1_200,\n blofin: 250,\n pacifica: 4_000,\n} as Partial<Record<XgateEx, number>>;\n\n/**\n * La cadence documentée d'une venue, ou `undefined` si personne ne l'a publiée.\n *\n * ⚠️ LE BUDGET EST CELUI DE LA VENUE, PAS DU MARCHÉ. Une IP qui interroge le comptant ET le perpétuel de binance\n * consomme UN SEUL budget : un appelant qui traite les deux en parallèle, chacun à cette cadence, tire deux fois\n * plus vite que ce que la table annonce. C'est à lui de sérialiser par venue.\n */\nexport function restThrottleOf(xex: XgateEx): number | undefined {\n return REST_THROTTLE_MS[xex];\n}\n\n/**\n * TOUT L'HISTORIQUE QU'UNE VENUE ACCEPTE DE SERVIR, en bougies par (marché, intervalle).\n *\n * ⚠️ CE N'EST PAS `CANDLES_PER_CALL`, ET LES CONFONDRE FAIT LIRE UN BACKFILL DE TRAVERS. Le premier plafond dit\n * combien de bougies tiennent dans UN appel : binance en rend 1 500 à la fois et sert pourtant quarante-quatre\n * mois, parce qu'on pagine. Celui-ci dit combien la venue en garde EN TOUT — au-delà, aucune pagination ne sert à\n * rien, elle répond une liste vide.\n *\n * **hyperliquid** : « Only the most recent 5000 candles are available » (documentation officielle,\n * `info` endpoint, `candleSnapshot`). La fenêtre GLISSE : ce qui sort des 5 000 dernières devient définitivement\n * hors de portée. Sondé le 2026-08-16 sur dix symboles au 2024-09-14 en 15m — POL rend ~500 barres (coin peu\n * traité, ses 5 000 bougies s'étalent sur deux ans), les neuf autres rendent ZÉRO.\n *\n * L'horizon couvert dépend donc de l'ACTIVITÉ du marché, jamais d'une durée fixe : en 1d et 1w la venue est\n * complète, le plafond ne mord que sous le jour.\n *\n * ⚠️ UNE VENUE ABSENTE DE CETTE TABLE SERT SON HISTORIQUE COMPLET par pagination, pour autant qu'on sache. Ce\n * n'est pas une valeur par défaut confortable : c'est ce qui a été constaté. Le jour où l'une d'elles s'arrête,\n * on lit sa documentation et on l'ajoute — on ne devine aucun chiffre ici.\n */\nexport const CANDLES_MAX_HISTORY: Partial<Record<XgateEx, number>> = {\n hyperliquid: 5_000,\n} as Partial<Record<XgateEx, number>>;\n\n/**\n * Combien de bougies une venue garde au total, ou `undefined` quand elle sert tout son historique.\n *\n * ⚠️ LA CLÉ EST LA VENUE, PAS LA LIGNE DE CATALOGUE DE L'APPELANT — même règle que `candlesPerCallOf` : un\n * consommateur qui sépare le comptant du perpétuel (`binance` / `binance-spot`) résout vers la venue avant de\n * demander, sinon il lit `undefined` et croit à un historique complet.\n */\nexport function candlesMaxHistoryOf(xex: XgateEx): number | undefined {\n return CANDLES_MAX_HISTORY[xex];\n}\n"]}
1
+ {"version":3,"sources":["../src/helpers/symbol-naming.ts","../src/helpers/candle-mapper.ts","../src/helpers/socket-error.ts","../src/helpers/ws-limits.ts","../src/enums/xgate-ex.enums.ts","../src/helpers/xex-registry.ts","../src/services/candles-stream.service.ts","../src/enums/timeframe.enums.ts","../src/helpers/decimal.ts","../src/helpers/candle-aggregator.ts","../src/helpers/candle-query.ts","../src/helpers/xex-timeframes.ts","../src/services/candles.service.ts","../src/helpers/asset-class.ts","../src/helpers/contract-expiry.ts","../src/services/catalog.service.ts","../src/services/prices.service.ts","../src/services/spot-candles.service.ts","../src/services/spot-catalog.service.ts","../src/services/spot-prices.service.ts","../src/services/spot-ws-candles.service.ts","../src/helpers/protection.ts","../src/interfaces/trading.interfaces.ts","../src/services/trading.service.ts","../src/services/wallet.service.ts","../src/services/ws-candles.service.ts","../src/services/ws-trades.service.ts","../src/xgate.module.ts","../src/helpers/rest-limits.ts"],"names":["XgateEx","Hyperliquid","Pacifica","Lighter","Aster","Blofin","Extended","Paradex","Bullet","Binance","Bybit","Logger","CandlesStreamRegistry","Injectable","Timeframe","CandlesService","CatalogService","PricesService","SpotCandlesService","SpotCatalogService","SpotPricesService","SpotWsCandlesService","TradingService","randomUUID","WalletService","WsCandlesService","WsTradesService","XgateModule","Module"],"mappings":"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","file":"index.cjs","sourcesContent":["// Repris tel quel de `Blips/server/src/shared/symbol-naming.ts` (2026-08-05). Ce code a été écrit\n// et éprouvé côté consommateur, mais sa place est ICI : à la frontière des venues, pas dans\n// l'application qui les consomme. Zéro dépendance — le « nom CoinMarketCap » est la convention\n// visée, jamais un appel réseau.\n//\n// Normalisation des symboles. Un même actif porte une forme différente sur chaque venue :\n// kPEPE (hyperliquid/pacifica/paradex) · 1000PEPEUSDT (aster) · 1000PEPE (lighter) · 1000PEPE-USD (extended)\n// Tous donnent le MÊME canonique : `PEPE` — le nom de l'actif chez CoinMarketCap, sans multiplicateur de cotation.\n// Le catalogue porte DEUX symboles par ligne :\n// - symbolXex : le BRUT de la venue (ce qu'on lui envoie : souscription, ordre, fetch REST) ;\n// - symbol : le CANONIQUE (clé interne : oracle, signaux, UI).\n// Ce fichier est l'unique frontière de conversion.\n\n// Venues « binance-like » : le quote est CONCATÉNÉ à l'actif (BTCUSDT, USDEUSDT). On strippe le quote final.\n// Ailleurs (hyperliquid/pacifica/lighter/extended/paradex) : soit actif nu (BTC, USDC), soit quote SÉPARÉ (BTC-USD).\nconst CONCAT_QUOTE_XEXS = new Set(['aster', 'binance', 'bybit']);\n\n// LE MULTIPLICATEUR DE COTATION DISPARAÎT DU CANONIQUE (règle Philippe, 2026-08-02).\n//\n// Les memecoins se cotent par paquets — `kPEPE` (hyperliquid/pacifica/paradex), `1000PEPE` (aster/bybit/lighter/\n// extended), `10000SATS`, `1000000BABYDOGE` — mais c'est une UNITÉ DE COTATION, pas un actif différent : même\n// token, même capitalisation. Le canonique doit être le nom de l'actif, c'est-à-dire celui de CoinMarketCap.\n//\n// MESURÉ le 2026-08-02 sur le top 1000 CMC : aucun `1000*` n'y existe. `PEPE` est au rang 48, `SATS` au 552\n// (« SATS (Ordinals) » — bien le token que binance cote `1000SATSUSDT`), `BONK`, `SHIB`, `FLOKI`, `BTT`, `LUNC`,\n// `XEC` tous sans préfixe. Sans ce strip, aucun de ces actifs ne se rattacherait à son classement.\n//\n// Deux formes, deux règles :\n// - `k` MINUSCULE en tête suivi d'une majuscule, retiré AVANT l'uppercase — sinon on amputerait les vrais actifs\n// en K majuscule (KAVA, KAS, KSM, KNC) ;\n// - `1` suivi d'AU MOINS TROIS zéros (1000, 10000, 1000000). Trois au minimum : `100X` ou `10X` resteraient\n// intacts, et aucun actif mesuré ne commence réellement par `1000`.\nconst K_MULTIPLIER = /^k(?=[A-Z])/u;\nconst NUMERIC_MULTIPLIER = /^10{3,}(?=[A-Z0-9])/u;\n\n/**\n * `1M` / `1K` EN TÊTE — l'échelle écrite en abrégé plutôt qu'en zéros.\n *\n * binance cote `1MBABYDOGEUSDT` là où bybit cote `1000000BABYDOGEUSDT` : **même actif, même\n * échelle, deux écritures**. Sans cette règle, binance produisait le canonique `1MBABYDOGE` et\n * bybit `BABYDOGE` — deux lignes qui ne se rejoignaient jamais, alors qu'elles cotent la même\n * chose au même facteur.\n *\n * **Deux lettres majuscules au minimum derrière**, et c'est délibéré : `1INCH` ne matche pas (`I`\n * n'est ni `M` ni `K`), et un hypothétique `1MX` resterait intact. Le risque inverse — amputer un\n * token qui commencerait réellement par `1M` — n'existe sur aucune des 7 865 lignes mesurées.\n */\nconst ABBREV_MULTIPLIER = /^1([MK])(?=[A-Z]{2,})/u;\n\n/** Facteurs d'échelle, par la forme écrite dans le nom. */\nconst ABBREV_FACTOR: Record<string, number> = { M: 1_000_000, K: 1_000 };\n\n// Marqueur « 24/5 » d'extended : contrats actions/ETF (cotés 24h/5j, vs crypto 24/7). Ex. `AAPL_24_5-USD`. On le\n// strippe à la normalisation (AAPL_24_5 → AAPL) pour obtenir le ticker canonique ; le type d'actif vient ensuite\n// du catalogue.\nconst STOCK_24_5 = /_24_5$/u;\n\n// Variantes d'un même actif → canonique unique, pour consolider entre venues (un actif listé sous des noms\n// différents par chacune : ex. S&P 500 = US500/SPX500M/SP500). Appliqué APRÈS strip du suffixe et uppercase.\nconst TICKER_ALIASES: Record<string, string> = {\n GOOG: 'GOOGL', // classe d'action (extended `GOOG` ≡ `GOOGL` ailleurs)\n // Indices\n US500: 'SP500',\n SPX500M: 'SP500',\n TECH100M: 'US100',\n // Matières premières\n BRENTOIL: 'BRENT',\n NATGAS: 'NG',\n // Actions (USD collé sur lighter, non strippé → aliasé explicitement)\n SAMSUNGUSD: 'SAMSUNG',\n SKHYNIXUSD: 'SKHYNIX',\n HYUNDAIUSD: 'HYUNDAI',\n};\n\n/**\n * SYMBOLE BRUT D'UNE VENUE → CANONIQUE.\n *\n * DÉPEND DE LA VENUE, pour ne JAMAIS amputer un stablecoin ni un actif finissant par USD(T/C) :\n * - aster/binance/bybit : quote concaténé → on strippe `USDT`/`USDC` FINAL (USDEUSDT→USDE, BTCUSDT→BTC) ;\n * - les autres : uniquement les suffixes SÉPARÉS (`-USD-PERP`, `-PERP`, `-USDT`, `-USDC`, `-USD`). Jamais de\n * strip nu → un actif nu (USDC, USDT, USDE, BUSD, BTC) reste intact ;\n * - dans tous les cas, le multiplicateur tombe d'abord (kPEPE → PEPE).\n *\n * `-USDT` A ÉTÉ AJOUTÉ le 2026-08-05, en agrégeant blofin pour la première fois : elle cote\n * `BTC-USDT` avec un tiret, forme qu'aucune venue de Blips n'utilisait. Sans lui, **477 des 487\n * symboles blofin restaient non canonisés** (`BTC-USDT` au lieu de `BTC`) et aucun ne se\n * rapprochait de son homologue chez une autre venue. Le tiret rend le strip sûr : l'actif nu\n * `USDT` n'y correspond pas.\n */\nexport function canonicalFromXex(rawSymbol: string, xexId: string): string {\n let s = rawSymbol.replace(K_MULTIPLIER, '').toUpperCase(); // kPEPE → PEPE (KAVA intact)\n if (CONCAT_QUOTE_XEXS.has(xexId)) {\n s = s.replace(/(USDT|USDC)$/u, ''); // quote concaténé final (le reste = actif, même si stablecoin)\n } else {\n s = s\n .replace(/-USD-PERP$/u, '')\n .replace(/-PERP$/u, '')\n .replace(/-USDT$/u, '') // blofin cote `BTC-USDT` (tiret + USDT) — cf. note ci-dessous\n .replace(/-USDC$/u, '')\n .replace(/-USD$/u, ''); // quotes SÉPARÉS uniquement → stablecoins nus préservés\n }\n s = s.replace(NUMERIC_MULTIPLIER, ''); // 1000PEPE → PEPE, 10000SATS → SATS, 1000000BABYDOGE → BABYDOGE\n s = s.replace(ABBREV_MULTIPLIER, ''); // 1MBABYDOGE → BABYDOGE (binance l'écrit en abrégé)\n s = s.replace(STOCK_24_5, ''); // contrat 24/5 extended (AAPL_24_5 → AAPL). Inoffensif ailleurs.\n return TICKER_ALIASES[s] ?? s; // variantes de classe (GOOG → GOOGL)\n}\n\n/**\n * ACTIF DE BASE NU → CANONIQUE, pour les venues qui NOMMENT leur actif au lieu de le concaténer : binance rend\n * `baseAsset`, bybit `baseCoin`. On prend ce qu'elles DISENT plutôt que de re-parser leur symbole — sans quoi\n * `ROSEBTC` (cotée en BTC, pas en USDT) donnerait le faux canonique `ROSEBTC`.\n *\n * Mêmes règles que `canonicalFromXex` MOINS le strip de cotation : l'appliquer à une base nue amputerait les\n * stablecoins (`USDC` → chaîne VIDE). C'est exactement le piège que la connaissance de la venue évite déjà côté\n * symbole.\n */\nexport function canonicalFromBase(base: string): string {\n const s = base\n .replace(K_MULTIPLIER, '') // kPEPE → PEPE, avant l'uppercase (KAVA intact)\n .toUpperCase()\n .replace(NUMERIC_MULTIPLIER, '') // 1000SATS → SATS (le nom CMC de l'actif)\n .replace(ABBREV_MULTIPLIER, '') // 1MBABYDOGE → BABYDOGE\n .replace(STOCK_24_5, '');\n return TICKER_ALIASES[s] ?? s;\n}\n\n/**\n * LE MULTIPLICATEUR DE COTATION, extrait du nom — **la seule source qui existe**.\n *\n * Aucune venue ne le publie : `kPEPE` chez hyperliquid ne porte que `{\"marginTableId\":52}`,\n * `1000PEPEUSDT` chez binance ne mentionne aucun facteur. L'information n'est QUE dans le préfixe\n * du nom, et le canonique la fait disparaître — jusqu'ici sans la conserver nulle part.\n *\n * Or elle est indispensable dès qu'on compare des prix entre venues. `SATS` porte **trois échelles\n * différentes sur quatre sources** : `SATSUSDT` (×1) chez bybit-spot, `1000SATSUSDT` (×1000) chez\n * binance, `10000SATSUSDT` (×10000) chez bybit. Une médiane calculée sur ces prix bruts ne veut\n * rien dire ; divisés par leur facteur, ils redeviennent comparables.\n *\n * Rend `1` quand le nom ne porte aucune échelle — le cas de l'immense majorité.\n */\nexport function multiplierFromXex(rawSymbol: string): number {\n if (K_MULTIPLIER.test(rawSymbol)) {\n return 1_000; // `k` minuscule : convention hyperliquid/pacifica/paradex\n }\n const upper = rawSymbol.toUpperCase();\n const abbrev = ABBREV_MULTIPLIER.exec(upper);\n if (abbrev !== null) {\n return ABBREV_FACTOR[abbrev[1] as string] ?? 1;\n }\n const zeros = NUMERIC_MULTIPLIER.exec(upper);\n return zeros === null ? 1 : Number(zeros[0]);\n}\n","import type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport type { ICandle } from '../interfaces/candle.interfaces';\nimport type { MarketKind } from '../interfaces/pair.interfaces';\nimport { canonicalFromXex } from './symbol-naming';\nimport type { IXexCandle } from './xex-registry';\n\n/**\n * Une bougie de venue → une bougie XGate.\n *\n * Deux traductions, et rien d'autre — le mapper ne calcule aucune valeur :\n *\n * - **les temps deviennent des `Date`.** Les SDK rendent `t`/`T` en millisecondes ; personne ne\n * devrait avoir à savoir dans quelle unité compte quelle venue.\n * - **`n: 0` devient `trades: null`.** Les venues qui ne comptent pas les transactions posent `0`\n * dans le cœur des SDK (convention de lighter, extended, paradex, drift, blofin). Un zéro se lit\n * « aucune transaction sur ce créneau », ce qui est une information — et elle serait fausse.\n * L'absence se dit `null`.\n */\nexport function toCandle(\n wire: IXexCandle,\n xex: XgateEx,\n interval: Timeframe,\n quote: string,\n): ICandle {\n return {\n symbol: canonicalFromXex(wire.symbol, xex),\n quote,\n symbolXex: wire.symbol,\n xex,\n kind: wire.kind as MarketKind,\n interval,\n openedAt: wire.openedAt,\n closedAt: wire.closedAt,\n open: wire.open,\n high: wire.high,\n low: wire.low,\n close: wire.close,\n volume: wire.volume,\n quoteVolume: wire.quoteVolume,\n takerBuyQuoteVolume: wire.takerBuyQuoteVolume,\n trades: wire.trades > 0 ? wire.trades : null,\n // Servie par la venue, pas fabriquée. L'agrégation posera `true`.\n derived: false,\n };\n}\n","/**\n * Un incident de socket en texte lisible.\n *\n * `String(error)` sur un `ErrorEvent` rend `[object ErrorEvent]` — un journal où toutes les pannes\n * se ressemblent. Les sockets viennent de `globalThis.WebSocket`, donc de l'undici embarqué dans\n * Node, dont l'événement d'erreur n'est **pas** une `Error` : ni pile, ni message. On va chercher\n * ce que l'événement porte réellement, dans l'ordre où les implémentations le renseignent.\n *\n * Partagée par les deux services de flux ({@link WsCandlesService}, {@link CandlesStreamService}) :\n * un incident doit se lire pareil quel que soit le service qui l'a reçu.\n */\nexport function lisible(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n const event = error as { message?: unknown; error?: unknown; type?: unknown } | null;\n if (event !== null && typeof event === 'object') {\n const message = event.message ?? (event.error as { message?: unknown } | undefined)?.message;\n if (typeof message === 'string' && message !== '') {\n return message;\n }\n if (typeof event.type === 'string' && event.type !== '') {\n return `événement « ${event.type} »`;\n }\n }\n return String(error);\n}\n","import type { XgateEx } from '../enums/xgate-ex.enums';\n\n/**\n * LES PLAFONDS DE SOUSCRIPTION, PAR VENUE — uniquement ceux qu'une documentation officielle donne.\n *\n * Repris de `Blips/server/src/candles/services/internal/dex-candles-stream.service.ts`, où ils ont\n * été établis puis vérifiés en réel.\n *\n * **aster**, section « Websocket Market Streams » : « A single connection can listen to a maximum\n * of 200 streams », et « A connection that goes beyond the limit will be disconnected; IPs that are\n * repeatedly disconnected may be banned ». Vérifié le 2026-08-03 sur `fstream.asterdex.com` : 200\n * tiennent, 300 tuent la connexion.\n * <https://github.com/asterdex/api-docs/blob/master/V3(Recommended)/EN/aster-finance-futures-api-v3.md>\n *\n * **hyperliquid** : 1 000 souscriptions et 10 connexions par IP.\n *\n * **Une venue ABSENTE de cette table n'a pas de plafond DOCUMENTÉ** — ce qui n'est pas la même\n * chose que « pas de plafond ». On ne devine aucun chiffre : le jour où l'une d'elles s'en\n * approche, on lit sa documentation et on l'ajoute.\n */\nexport const WS_SUBSCRIPTION_LIMITS: Partial<Record<XgateEx, number>> = {\n aster: 200,\n hyperliquid: 1_000,\n} as Partial<Record<XgateEx, number>>;\n\n/**\n * Le plafond documenté d'une venue, ou `undefined` si personne ne l'a publié.\n *\n * Rendre `Infinity` par défaut serait un mensonge commode : l'absence de plafond connu n'est pas\n * une garantie d'illimité.\n */\nexport function subscriptionLimitOf(xex: XgateEx): number | undefined {\n return WS_SUBSCRIPTION_LIMITS[xex];\n}\n","/**\n * Les venues qu'XGate sait interroger.\n *\n * Une seule énumération pour les CEX et les DEX : la distinction ne change rien à l'API — on\n * interroge un catalogue de la même façon des deux côtés. Elle deviendra une donnée portée par la\n * venue le jour où quelque chose en dépendra.\n *\n * **`drift` en est absent**, et ce n'est pas un oubli : son SDK ne porte aucun test (seul de la\n * famille), sa précision de taille est un placeholder assumé, et son catalogue est une constante\n * figée dans le paquet. On ne l'agrège pas tant qu'il n'est pas vérifiable.\n */\nexport enum XgateEx {\n Hyperliquid = 'hyperliquid',\n Pacifica = 'pacifica',\n Aster = 'aster',\n Lighter = 'lighter',\n Extended = 'extended',\n Paradex = 'paradex',\n Bullet = 'bullet',\n Blofin = 'blofin',\n /**\n * Les deux seules venues dont XGate sert aussi le **comptant** (cf. `IXexSpotSource`). Leurs SDK\n * ne couvrent que la lecture publique : catalogue, bougies et prix. Toute route signée y lève.\n */\n Binance = 'binance',\n Bybit = 'bybit',\n}\n\n/** Toutes les venues connues, dans l'ordre de déclaration. */\nexport const XGATE_EXCHANGES: readonly XgateEx[] = Object.values(XgateEx);\n","import { Aster } from '@blackcube/aster-sdk';\nimport { Binance } from '@blackcube/binance-sdk';\nimport { Blofin } from '@blackcube/blofin-sdk';\nimport { Bullet } from '@blackcube/bullet-sdk';\nimport { Bybit } from '@blackcube/bybit-sdk';\nimport { Extended } from '@blackcube/extended-sdk';\nimport { Hyperliquid } from '@blackcube/hyperliquid-sdk';\nimport { Lighter } from '@blackcube/lighter-sdk';\nimport { Pacifica } from '@blackcube/pacifica-sdk';\nimport { Paradex } from '@blackcube/paradex-sdk';\nimport { XgateEx } from '../enums/xgate-ex.enums';\n\n/** Une paire, telle qu'un SDK de venue la rend. */\nexport interface IXexPair {\n name: string;\n /**\n * L'actif de base, **tel que la venue le NOMME**. Décisif chez binance et bybit : leur symbole\n * concatène la cotation (`BTCUSDT`), et re-parser cette chaîne donnerait un faux canonique sur\n * tout ce qui n'est pas coté en USDT — `ROSEBTC` deviendrait `ROSEBTC` au lieu de `ROSE`.\n */\n base: string;\n quote: string;\n kind: string;\n ref?: string | null;\n szDecimals: number;\n tickSize?: string;\n stepSize?: string;\n minQty?: string;\n minNotional?: string;\n maxLeverage?: number;\n status?: string;\n delisted?: boolean;\n listedAt?: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * Une bougie, telle qu'un SDK de venue la rend : **clés longues et `Date`**.\n *\n * Les clés courtes (`t`, `T`, `o`, `qv`…) restent au niveau du wire, dans chaque SDK. Elles ne\n * remontent plus jusqu'ici — un `candle.T` ne se lit pas, et un `number` ne dit pas s'il compte des\n * secondes ou des millisecondes.\n */\nexport interface IXexCandle {\n openedAt: Date;\n closedAt: Date;\n symbol: string;\n interval: string;\n open: string;\n close: string;\n high: string;\n low: string;\n volume: string;\n trades: number;\n kind: string;\n quoteVolume: string | null;\n takerBuyBaseVolume: string | null;\n takerBuyQuoteVolume: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * Une cotation, telle qu'un SDK de venue la rend : **clés longues et `Date`**, comme\n * {@link IXexCandle}. Le `time: number` d'origine est devenu `quotedAt: Date` — un nombre nu ne dit\n * pas s'il compte des secondes ou des millisecondes, et les deux existent selon les venues.\n */\nexport interface IXexPrice {\n name: string;\n kind: string;\n mark: string | null;\n oracle: string | null;\n mid: string | null;\n bid: string | null;\n ask: string | null;\n last: string | null;\n funding: string | null;\n openInterest: string | null;\n volume24h: string | null;\n prevDayPrice: string | null;\n quotedAt: Date | null;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * Ce qu'XGate attend d'une façade de venue. Le plus petit dénominateur réellement commun aux huit\n * — mesuré, pas supposé.\n *\n * **`getCandles` est OPTIONNEL**, et le type doit le dire : bullet ne l'expose pas\n * (`perp().getCandles is not a function`, vérifié le 2026-08-05), il ne sert ses bougies qu'en\n * WebSocket. Le déclarer requis ferait échouer la compilation sur une venue pourtant valide pour\n * le catalogue.\n *\n * Les dates y sont des **chaînes** (`\"YYYY-MM-DD HH:MM:SS\"` UTC) chez huit venues et des\n * millisecondes chez blofin : c'est la frontière que les services traduisent.\n */\nexport interface IXexSource {\n perp(): {\n getPairs(): Promise<IXexPair[]>;\n getCandles?(query: {\n name: string;\n interval: string;\n /** Un `Date` : c'est au SDK de la venue de le traduire dans son format à elle. */\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n }): Promise<IXexCandle[]>;\n getPrices(): Promise<IXexPrice[]>;\n };\n ws(): {\n subscribeCandles(\n query: { name: string; interval: string },\n handler: (candle: IXexCandle) => void,\n ): () => void;\n /**\n * Notifié à chaque erreur socket. **Optionnel : binance et bybit ne l'exposent pas encore.**\n *\n * Sans lui, un symbole invalide est invisible — la venue accepte la souscription puis ferme la\n * connexion, le SDK reconnecte, elle referme. Une boucle silencieuse où les marchés valides du\n * même socket cessent eux aussi de recevoir.\n */\n onError?: ((error: unknown) => void) | null;\n /** Notifié à chaque fermeture, avant une éventuelle reconnexion. Même réserve. */\n onClose?: (() => void) | null;\n };\n}\n\n/**\n * Ce qu'XGate attend d'une façade de venue **côté COMPTANT**.\n *\n * **Volontairement séparé d'{@link IXexSource}, et non fondu dedans avec un paramètre.** Un\n * `perp()` qui servirait parfois du spot selon un drapeau serait une source d'erreurs silencieuses :\n * on croirait lire des perpétuels et on lirait du comptant, sans que rien ne le signale. Deux\n * contrats, deux registres, deux services — l'appelant choisit explicitement ce qu'il interroge.\n *\n * **Toutes les venues n'ont pas de spot** : c'est même minoritaire. Le registre lève pour celles qui\n * n'en servent pas, plutôt que de rendre une liste vide qui se lirait comme « ce marché ne cote\n * rien ».\n */\nexport interface IXexSpotSource {\n spot(): {\n getPairs(): Promise<IXexPair[]>;\n getCandles?(query: {\n name: string;\n interval: string;\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n }): Promise<IXexCandle[]>;\n getPrices?(): Promise<IXexPrice[]>;\n };\n /**\n * Le temps réel du COMPTANT. **`kind` est obligatoire ici**, contrairement aux façades qui le\n * rendent optionnel : leur défaut est `'perp'`, et un `ws()` nu ouvre donc un flux de perpétuels\n * en croyant écouter le comptant — deux marchés dont les prix diffèrent, sans que rien ne le\n * signale.\n */\n ws(kind: 'spot'): {\n subscribeCandles(\n query: { name: string; interval: string },\n handler: (candle: IXexCandle) => void,\n ): () => void;\n };\n}\n\n/**\n * De quoi lire UN compte chez une venue.\n *\n * **Un sac d'accès volontairement lâche**, dont chaque venue prend ce qu'il lui faut : les huit\n * n'ont pas la même notion d'identité. Un DEX se lit à l'adresse ; lighter veut un index de compte,\n * extended une clé d'API plus un coffre, blofin un triplet clé/secret/passphrase. Un type strict par\n * venue obligerait l'appelant à connaître ces huit formes — c'est précisément ce qu'XGate existe\n * pour lui épargner.\n *\n * Le registre **lève** quand ce qu'il faut manque, avec le nom du champ attendu : mieux vaut un\n * message précis qu'un compte vide qui se lirait comme « pas d'argent ».\n */\nexport interface IXexAccess {\n /** L'adresse publique du compte. Suffit seule sur les DEX en lecture. */\n address?: string;\n privateKey?: string;\n /**\n * Le réseau de la venue. **Défaut `mainnet`** — c'est le cas courant, et un défaut `testnet`\n * ferait lire des soldes fantômes sans que personne ne s'en aperçoive.\n *\n * Il devient indispensable dès qu'on écrit : sans lui, `TradingService` ne pourrait ouvrir que sur\n * de l'argent réel, et le cycle ne serait exerçable nulle part.\n */\n network?: 'mainnet' | 'testnet';\n apiKey?: string;\n secret?: string;\n passphrase?: string;\n accountIndex?: number;\n apiKeyIndex?: number;\n vault?: number;\n}\n\n/** Un solde tel qu'un SDK de venue le rend. */\nexport interface IXexBalance {\n asset: string;\n total: string;\n available: string | null;\n usdValue: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/** Un mouvement tel qu'un SDK de venue le rend : **clés longues et `Date`**. */\nexport interface IXexMovement {\n kind: string;\n amount: string;\n fee: string;\n asset: string;\n occurredAt: Date;\n status: string;\n externalId: string;\n xtras?: Record<string, unknown>;\n}\n\n/** Un point d'équité tel qu'un SDK de venue le rend. */\nexport interface IXexEquityPoint {\n measuredAt: Date;\n equity: number;\n}\n\n/**\n * Ce qu'XGate attend d'une façade de venue **côté COMPTE**.\n *\n * `getBalances` est le seul point commun aux huit — c'est donc le seul requis. `getMovements` manque\n * encore là où la venue ne l'expose pas, et `getEquityHistory` n'est publié que par hyperliquid et\n * pacifica : les déclarer requis ferait échouer la compilation sur des venues pourtant valides.\n */\nexport interface IXexAccountSource {\n account(): {\n getBalances(): Promise<IXexBalance[]>;\n getMovements?(query?: unknown): Promise<IXexMovement[]>;\n getEquityHistory?(range?: unknown): Promise<IXexEquityPoint[]>;\n };\n}\n\n/**\n * Instancie la façade d'une venue **avec un accès de compte**, en LECTURE.\n *\n * Sur les DEX, lire un portefeuille est **public** : l'adresse suffit et aucune signature n'est\n * produite — d'où la clé privée nulle passée à hyperliquid, aster, paradex et bullet, dont le\n * constructeur l'exige sans que la lecture s'en serve. Les CEX (blofin) réclament en revanche un\n * vrai triplet, même pour lire.\n */\nexport function createAccountXex(xex: XgateEx, access: IXexAccess): IXexAccountSource {\n const NULL_KEY = `0x${'0'.repeat(64)}`;\n const adresse = (): string => {\n if (access.address === undefined || access.address === '') {\n throw new Error(`wallet(${xex}) : il faut au moins une adresse (\\`access.address\\`).`);\n }\n return access.address;\n };\n switch (xex) {\n case XgateEx.Hyperliquid:\n return new Hyperliquid(\n {\n a: {\n privateKey: (access.privateKey ?? NULL_KEY) as `0x${string}`,\n publicKey: adresse() as `0x${string}`,\n network: access.network ?? 'mainnet',\n },\n },\n { default: 'a' },\n ) as unknown as IXexAccountSource;\n case XgateEx.Pacifica:\n // `secretKey` est exigée par le type du signer mais ne sert qu'à SIGNER : la chaîne vide\n // suffit pour lire, comme la clé nulle côté EVM.\n return new Pacifica(\n {\n a: {\n secretKey: access.privateKey ?? '',\n publicKey: adresse(),\n network: access.network ?? 'mainnet',\n },\n },\n { default: 'a' },\n ) as unknown as IXexAccountSource;\n case XgateEx.Lighter:\n // Lighter se lit **sans clé** : sa documentation classe `/account`, `accountsByL1Address` et\n // `positions` parmi les routes sans authentification, et `/account` accepte aussi bien un\n // index de sous-compte qu'une adresse L1. L'adresse suffit donc, comme sur les autres DEX —\n // `accountIndex` ne sert qu'à viser un sous-compte précis.\n return new Lighter(\n {\n a: {\n apiPrivateKey: access.privateKey ?? NULL_KEY,\n apiKeyIndex: access.apiKeyIndex ?? 0,\n accountIndex: access.accountIndex ?? 0,\n l1Address: adresse(),\n network: access.network ?? 'mainnet',\n },\n },\n { default: 'a' },\n ) as unknown as IXexAccountSource;\n case XgateEx.Aster:\n // Aster signe avec une clé d'API pour le compte d'une ADRESSE EVM : les deux sont exigées, et\n // l'adresse n'est pas déductible de la clé (modèle agent/master — l'agent signe, le master\n // détient). Sans `privateKey`, la façade se construirait et échouerait à la première écriture.\n if (access.privateKey === undefined || access.privateKey === '') {\n throw new Error(\n `wallet(${xex}) : Aster signe avec une clé d'API — il faut \\`privateKey\\` (la clé) et \\`address\\` (le compte EVM).`,\n );\n }\n return new Aster(\n {\n a: {\n privateKey: access.privateKey,\n user: adresse(),\n network: access.network ?? 'mainnet',\n },\n },\n { default: 'a' },\n ) as unknown as IXexAccountSource;\n case XgateEx.Blofin:\n if (access.apiKey === undefined || access.secret === undefined) {\n throw new Error(\n `wallet(${xex}) : BloFin signe même en lecture — il faut \\`apiKey\\`, \\`secret\\` et \\`passphrase\\`.`,\n );\n }\n // BloFin nomme son second réseau « demo trading » : seul l'hôte change, l'API est identique.\n // Sans ce réseau, une clé de démo tape le mainnet et la venue répond « Access key does not\n // exist » — un message qui accuse la clé alors que c'est l'adresse qui est fausse.\n return new Blofin({\n credentials: {\n apiKey: access.apiKey,\n secret: access.secret,\n passphrase: access.passphrase ?? '',\n },\n network: access.network ?? 'mainnet',\n }) as unknown as IXexAccountSource;\n default:\n throw new Error(\n `wallet(${xex}) : venue pas encore câblée côté compte. Disponibles : ${WALLET_XEXES.join(', ')}.`,\n );\n }\n}\n\n/**\n * Les venues dont XGate sait lire le portefeuille aujourd'hui.\n *\n * Les autres ne sont pas exclues par principe : il leur manque un accès (clé d'API pour bullet, une\n * adresse active pour extended et paradex). Voir le backlog.\n */\nexport const WALLET_XEXES: readonly XgateEx[] = [\n XgateEx.Hyperliquid,\n XgateEx.Pacifica,\n XgateEx.Aster,\n XgateEx.Lighter,\n XgateEx.Blofin,\n];\n\n/** Conservé pour la lisibilité des appels de catalogue. */\nexport type IXexCatalogSource = IXexSource;\n\n/**\n * Instancie la façade d'une venue, en **lecture publique** : aucun credential.\n *\n * Un `switch` explicite plutôt qu'une table de constructeurs, parce que les signatures ne\n * coïncident pas — les DEX prennent `(signers, options)`, BloFin prend `(options)` seul. C'est\n * exactement l'écart que la normalisation doit résorber ; en attendant, il vit ici, à un seul\n * endroit, plutôt que chez chaque appelant.\n */\nexport function createPublicXex(xex: XgateEx): IXexSource {\n switch (xex) {\n case XgateEx.Hyperliquid:\n return new Hyperliquid();\n case XgateEx.Pacifica:\n return new Pacifica();\n case XgateEx.Aster:\n return new Aster();\n case XgateEx.Lighter:\n return new Lighter();\n case XgateEx.Extended:\n return new Extended();\n case XgateEx.Paradex:\n return new Paradex();\n case XgateEx.Bullet:\n return new Bullet();\n case XgateEx.Blofin:\n return new Blofin();\n case XgateEx.Binance:\n return new Binance();\n case XgateEx.Bybit:\n return new Bybit();\n }\n}\n\n/**\n * Les venues dont XGate sert le **comptant**. Les autres n'ont pas de marché spot exploitable, ou\n * ne l'exposent pas — et une venue absente d'ici n'est pas un oubli, c'est un fait mesuré.\n */\nexport const SPOT_XEXES: readonly XgateEx[] = [XgateEx.Binance, XgateEx.Bybit];\n\n/** Cette venue sert-elle du comptant via XGate ? */\nexport function servesSpot(xex: XgateEx): boolean {\n return SPOT_XEXES.includes(xex);\n}\n\n/**\n * Instancie la façade **comptant** d'une venue, en lecture publique.\n *\n * **Lève sur une venue sans spot**, plutôt que de rendre une source vide : un catalogue spot vide se\n * lirait comme « cette venue ne liste rien au comptant », alors que la vraie information est\n * « cette venue n'a pas de comptant ici ». Les deux ne se corrigent pas de la même façon.\n */\nexport function createPublicSpotXex(xex: XgateEx): IXexSpotSource {\n switch (xex) {\n case XgateEx.Binance:\n return new Binance();\n case XgateEx.Bybit:\n return new Bybit();\n default:\n throw new Error(\n `spot(${xex}) : cette venue ne sert pas de comptant via XGate. Venues avec spot : ${SPOT_XEXES.join(', ')}.`,\n );\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { lisible } from '../helpers/socket-error';\nimport { subscriptionLimitOf } from '../helpers/ws-limits';\nimport {\n type IXexSource,\n type IXexSpotSource,\n createPublicSpotXex,\n createPublicXex,\n servesSpot,\n} from '../helpers/xex-registry';\nimport type { ICandle } from '../interfaces/candle.interfaces';\nimport type { MarketKind } from '../interfaces/pair.interfaces';\n\n/**\n * LE FLUX DE BOUGIES D'UNE VENUE — porté de\n * `Blips/server/src/candles/services/internal/dex-candles-stream.service.ts`, où il tourne en\n * production. Même surface, mêmes garanties ; seul le type de bougie change (celui d'XGate).\n *\n * **UNE INSTANCE PAR VENUE, DONC UNE SOCKET PAR VENUE.** La façade est mémoïsée (`instance()`) et\n * `ws()` n'est appelé qu'une fois, hors de la boucle : les N souscriptions partagent la même\n * connexion. Créer la façade à chaque souscription ouvrirait une socket par marché — 200 marchés\n * suivis, 200 sockets, quand hyperliquid n'en accepte que 10 par IP.\n *\n * **UNE SOUSCRIPTION PAR MARCHÉ, et non `subscribeAllCandles`.** Ce dernier ne coûte qu'un\n * abonnement, mais il est bâti sur un flux de prix agrégé : mesuré le 2026-08-02, il rend un prix\n * MILIEU (40 % des valeurs sur un demi-tick contre 8 % au REST) et AUCUN volume (`\"v\":\"0\"` sur les\n * six dex). Le canal `candle` par marché rend la vraie bougie de trades, avec son volume.\n *\n * **LE SYMBOLE VIENT DU MESSAGE**, jamais de la souscription. Les SDK hyperliquid et pacifica\n * livraient tout le canal à tous les abonnés jusqu'à la 0.15.0 — trois souscriptions recevaient le\n * même flux. C'est corrigé, mais la règle reste : on lit ce que le wire déclare, on ne suppose pas.\n */\nexport class CandlesStreamService {\n private readonly logger: Logger;\n private source: IXexSource | IXexSpotSource | null = null;\n private readonly unsubscribes: Array<() => void> = [];\n\n /** Le plafond de souscriptions documenté par la venue, ou `null` si personne ne l'a publié. */\n public readonly maxSubscriptions: number | null;\n\n /**\n * Notifié quand le flux meurt — après que les souscriptions ont été coupées.\n *\n * **Ce crochet a remplacé un `throw`.** Lever depuis un rappel de socket ne remonte à aucun\n * appelant : l'exception sort en `unhandledRejection` et **tue le process entier**, ce qui est\n * arrivé le 2026-08-13 sur Blips par un autre chemin. « Impossible à ignorer » et « fatal pour\n * tout le monde » ne sont pas la même chose : la première est une garantie, la seconde une panne.\n *\n * Le contrat reste franc — le flux est coupé, il ne repartira pas tout seul, et le silence est\n * journalisé en `.error` de toute façon. Mais c'est à l'appelant de décider ce qu'il en fait :\n * rouvrir, basculer de venue, ou s'arrêter.\n */\n public onFailure: ((error: Error) => void) | null = null;\n\n constructor(\n public readonly xex: XgateEx,\n /** Le marché écouté. Le comptant n'est servi que par binance et bybit. */\n public readonly kind: MarketKind = 'perp',\n ) {\n this.logger = new Logger(`CandlesStream:${xex}${kind === 'spot' ? ':spot' : ''}`);\n this.maxSubscriptions = subscriptionLimitOf(xex) ?? null;\n }\n\n /**\n * Ouvre le flux sur une LISTE de marchés, sur une seule socket.\n *\n * **UNE ERREUR DE FLUX COUPE TOUT, PUIS NOTIFIE.** Elle n'arrive pas à la souscription : mesuré le\n * 2026-08-07, la venue accepte un symbole inconnu sans broncher, puis ferme la connexion en le\n * découvrant. Le SDK reconnecte, elle referme — cinq cycles en douze secondes, et sur une socket\n * partagée **tous** les marchés valides cessent de recevoir, en silence.\n *\n * On refuse cette boucle : à la première erreur, les souscriptions sont coupées, l'incident est\n * journalisé en `error`, et {@link onFailure} est notifié. Un flux à moitié mort qui se tait\n * coûte plus cher qu'un arrêt franc — on ne découvre le premier qu'en constatant l'absence de\n * données.\n *\n * ⚠️ **On ne lève pas depuis le rappel de socket.** Une exception née là ne remonte à aucun\n * appelant de `start()` : elle sort en `unhandledRejection` et emporte le process entier — pas\n * seulement le flux fautif, mais les crons et tout ce qui tournait à côté. C'est {@link onFailure}\n * qui porte l'information, et l'appelant qui décide.\n *\n * **binance et bybit ne remontent pas encore leurs erreurs** : leur client gère la fermeture en\n * interne sans l'exposer. Sur ces deux venues, la boucle silencieuse reste possible.\n */\n public start(\n symbolsXex: string[],\n interval: Timeframe,\n onCandle: (candle: ICandle) => void,\n ): void {\n const ws = this.wire();\n if ('onError' in ws) {\n ws.onError = (error: unknown) => {\n const raison = lisible(error);\n this.logger.error(`${this.xex} : erreur de flux — ${raison}. Souscriptions coupées.`);\n this.stop();\n this.onFailure?.(new Error(`stream(${this.xex}) : flux interrompu — ${raison}`));\n };\n }\n for (const symbolXex of symbolsXex) {\n this.unsubscribes.push(\n ws.subscribeCandles({ name: symbolXex, interval }, (wire) => {\n onCandle(toCandle(wire, this.xex, interval, this.quoteOf(symbolXex)));\n }),\n );\n }\n this.logger.log(`${this.unsubscribes.length}/${symbolsXex.length} souscription(s) ouvertes`);\n }\n\n /** Coupe tout. Un désabonnement qui échoue ne doit pas empêcher les autres de se fermer. */\n public stop(): void {\n for (const unsubscribe of this.unsubscribes.splice(0)) {\n try {\n unsubscribe();\n } catch {\n // volontairement silencieux : voir la doc de la méthode\n }\n }\n }\n\n /** Le nombre de souscriptions ouvertes. La liste des désabonnements EST le compteur. */\n public subscriptionCount(): number {\n return this.unsubscribes.length;\n }\n\n /**\n * Le client temps réel, sur le bon marché.\n *\n * Les deux contrats ne signent pas `ws()` pareil, et c'est voulu : côté comptant le marché est\n * **obligatoire** (`ws('spot')`), parce que les façades ouvrent du perpétuel par défaut — un\n * `ws()` nu y diffuserait des prix de perpétuel à qui croit écouter le comptant.\n */\n private wire(): {\n subscribeCandles(\n query: { name: string; interval: string },\n handler: (candle: Parameters<typeof toCandle>[0]) => void,\n ): () => void;\n onError?: ((error: unknown) => void) | null;\n } {\n const source = this.instance();\n return this.kind === 'spot'\n ? (source as IXexSpotSource).ws('spot')\n : (source as IXexSource).ws();\n }\n\n /** La façade de la venue, créée une seule fois : c'est elle qui porte la socket partagée. */\n private instance(): IXexSource | IXexSpotSource {\n if (this.source === null) {\n if (this.kind === 'spot' && servesSpot(this.xex) === false) {\n throw new Error(`stream(${this.xex}) : cette venue ne sert pas de comptant.`);\n }\n this.source =\n this.kind === 'spot' ? createPublicSpotXex(this.xex) : createPublicXex(this.xex);\n }\n return this.source;\n }\n\n /** Voir `CandlesService.quoteOf` : la cotation se déduit du symbole natif, faute d'être publiée. */\n private quoteOf(symbolXex: string): string {\n const parts = symbolXex.split('-');\n const last = parts[parts.length - 1];\n return parts.length > 1 && last !== undefined ? last : '';\n }\n}\n\n/**\n * LA FAÇADE : un flux par venue, et un seul.\n *\n * Blips instancie `DexCandlesStreamService` une fois par venue et garde la référence. XGate offre\n * la même chose sans que l'appelant ait à tenir cette table : `of(xex)` rend **toujours la même\n * instance** pour une venue et un marché donnés — donc toujours la même socket.\n *\n * Demander deux fois `of(hyperliquid)` et souscrire 100 marchés à chaque fois ouvre **une** socket\n * avec 200 souscriptions, pas deux sockets. C'est exactement ce qui manquait.\n */\n@Injectable()\nexport class CandlesStreamRegistry {\n private readonly streams = new Map<string, CandlesStreamService>();\n\n /** Le flux d'une venue, créé au premier appel puis réutilisé. */\n public of(xex: XgateEx, kind: MarketKind = 'perp'): CandlesStreamService {\n const cle = `${xex}:${kind}`;\n let stream = this.streams.get(cle);\n if (stream === undefined) {\n stream = new CandlesStreamService(xex, kind);\n this.streams.set(cle, stream);\n }\n return stream;\n }\n\n /** Coupe tous les flux ouverts, toutes venues confondues. */\n public stopAll(): void {\n for (const stream of this.streams.values()) {\n stream.stop();\n }\n }\n\n /** Ce qui est ouvert, par venue — pour surveiller sans avoir à tenir de compteur soi-même. */\n public counts(): Record<string, number> {\n const total: Record<string, number> = {};\n for (const [cle, stream] of this.streams) {\n total[cle] = stream.subscriptionCount();\n }\n return total;\n }\n}\n","/**\n * Les intervalles de bougies qu'XGate promet — **les mêmes que Blips**, mêmes noms, mêmes valeurs\n * (`Blips/server/src/shared/timeframe.enum.ts`). Le consommateur n'a pas à traduire pour parler à\n * la passerelle, ni la passerelle à inventer une échelle concurrente.\n *\n * Toutes les venues ne les servent pas : paradex s'arrête à l'heure (`resolution: must be one of\n * [1 3 5 15 30 60]`, dit la venue), lighter refuse `1w`, extended rend une liste vide pour ce même\n * intervalle. Ce qu'une venue ne sert pas se **fabrique par agrégation** depuis un intervalle plus\n * fin, jamais ne se réclame à l'appelant.\n */\nexport enum Timeframe {\n M1 = '1m',\n M5 = '5m',\n M15 = '15m',\n H1 = '1h',\n H4 = '4h',\n D = '1d',\n W = '1w',\n}\n\n/** Durée d'un intervalle, en minutes. */\nexport const TIMEFRAME_MINUTES: Record<Timeframe, number> = {\n [Timeframe.M1]: 1,\n [Timeframe.M5]: 5,\n [Timeframe.M15]: 15,\n [Timeframe.H1]: 60,\n [Timeframe.H4]: 240,\n [Timeframe.D]: 1440,\n [Timeframe.W]: 10080,\n};\n\n/** Durée d'un intervalle, en millisecondes. */\nexport function timeframeMs(timeframe: Timeframe): number {\n return TIMEFRAME_MINUTES[timeframe] * 60_000;\n}\n","/**\n * Addition décimale EXACTE, sur les chaînes que les venues rendent.\n *\n * XGate garde les prix et les volumes en `string` pour préserver leur précision — sommer en\n * `number` reviendrait à annuler ce choix au premier agrégat. `0.1 + 0.2` vaut\n * `0.30000000000000004` en JavaScript ; sur une semaine de volumes horaires, l'écart s'accumule.\n *\n * On lit donc la chaîne comme un entier et un nombre de décimales, on additionne en `bigint`, et\n * on reconstruit. Aucun `number` n'intervient.\n */\nfunction parse(value: string): { digits: bigint; scale: number } {\n const trimmed = value.trim();\n if (/^-?\\d+(\\.\\d+)?$/.test(trimmed) === false) {\n throw new Error(`Décimal illisible : \"${value}\".`);\n }\n const dot = trimmed.indexOf('.');\n if (dot < 0) {\n return { digits: BigInt(trimmed), scale: 0 };\n }\n return {\n digits: BigInt(trimmed.slice(0, dot) + trimmed.slice(dot + 1)),\n scale: trimmed.length - dot - 1,\n };\n}\n\nfunction format(digits: bigint, scale: number): string {\n const negative = digits < 0n;\n const plain = (negative === true ? -digits : digits).toString().padStart(scale + 1, '0');\n const sign = negative === true ? '-' : '';\n if (scale === 0) {\n return sign + plain;\n }\n const whole = plain.slice(0, plain.length - scale);\n const fraction = plain.slice(plain.length - scale).replace(/0+$/, '');\n return fraction === '' ? sign + whole : `${sign + whole}.${fraction}`;\n}\n\n/** Somme exacte de deux décimaux. Les échelles sont alignées sur la plus fine. */\nexport function addDecimals(left: string, right: string): string {\n const a = parse(left);\n const b = parse(right);\n const scale = Math.max(a.scale, b.scale);\n const digits =\n a.digits * 10n ** BigInt(scale - a.scale) + b.digits * 10n ** BigInt(scale - b.scale);\n return format(digits, scale);\n}\n\n/** Compare deux décimaux sans passer par `number` : `-1`, `0` ou `1`. */\nexport function compareDecimals(left: string, right: string): number {\n const a = parse(left);\n const b = parse(right);\n const scale = Math.max(a.scale, b.scale);\n const x = a.digits * 10n ** BigInt(scale - a.scale);\n const y = b.digits * 10n ** BigInt(scale - b.scale);\n if (x === y) {\n return 0;\n }\n return x > y ? 1 : -1;\n}\n","import { Timeframe, timeframeMs } from '../enums/timeframe.enums';\nimport type { ICandle } from '../interfaces/candle.interfaces';\nimport { addDecimals, compareDecimals } from './decimal';\n\n/**\n * L'epoch (1970-01-01) tombe un JEUDI. Aligner les semaines sur lundi 00:00 UTC impose donc de\n * décaler les seaux de quatre jours.\n */\nconst MONDAY_OFFSET_MS = 4 * 24 * 60 * 60 * 1000;\n\n/** Un seau en construction : la bougie, et de quoi juger si elle est complète. */\ninterface Bucket {\n candle: ICandle;\n sources: number;\n}\n\n/**\n * AGRÈGE des bougies fines en bougies d'un intervalle plus grand.\n *\n * Porté de `Blips/server/src/candles/helpers/internal/candle-aggregator.util.ts`, dont les règles\n * ont été payées en incidents. Deux sont contre-intuitives et se perdent si on les réécrit de\n * mémoire :\n *\n * **1. UN SEAU INCOMPLET EST REJETÉ, sauf s'il est encore ouvert.** Agréger 300 bougies horaires\n * en journalières donne un premier seau tronqué : il commence au milieu d'une journée, donc son\n * `open` n'est pas l'ouverture du jour et son volume en manque une part. Une telle bougie est\n * FAUSSE, pas approximative. Et « garder le dernier » ne suffit pas — sur paradex, 49 bougies 4h\n * sur 116 étaient closes ET incomplètes, parce que la venue ne rendait que quatre horaires\n * éparses. Être le dernier d'une série ne veut pas dire être en cours.\n *\n * **2. LE VOLUME PROPAGE L'INCONNU.** Une seule source à `null` et la somme vaut `null`. Compter\n * la source muette pour zéro donnerait un total plus petit que la réalité, sans que rien ne le\n * signale — la même faute que traiter un `null` comme un `0`.\n *\n * Les bougies rendues portent `derived: true` : personne ne les a servies telles quelles.\n */\nexport function aggregate(\n source: ICandle[],\n target: Timeframe,\n now: number = Date.now(),\n): ICandle[] {\n if (source.length === 0) {\n return [];\n }\n const sourceMs = timeframeMs(source[0]?.interval ?? Timeframe.M1);\n const targetMs = timeframeMs(target);\n if (targetMs <= sourceMs) {\n return [];\n }\n const offsetMs = target === Timeframe.W ? MONDAY_OFFSET_MS : 0;\n const expected = targetMs / sourceMs;\n const buckets = new Map<number, Bucket>();\n\n const sorted = [...source].sort(\n (left, right) => left.openedAt.getTime() - right.openedAt.getTime(),\n );\n for (const candle of sorted) {\n const start =\n Math.floor((candle.openedAt.getTime() - offsetMs) / targetMs) * targetMs + offsetMs;\n const bucket = buckets.get(start);\n if (bucket === undefined) {\n buckets.set(start, {\n sources: 1,\n candle: {\n ...candle,\n interval: target,\n openedAt: new Date(start),\n // Dernière milliseconde du créneau, comme les bougies servies par les venues.\n closedAt: new Date(start + targetMs - 1),\n derived: true,\n },\n });\n } else {\n bucket.sources += 1;\n const built = bucket.candle;\n built.high = compareDecimals(candle.high, built.high) > 0 ? candle.high : built.high;\n built.low = compareDecimals(candle.low, built.low) < 0 ? candle.low : built.low;\n // `open` reste celui de la première bougie du seau ; `close` suit la dernière.\n built.close = candle.close;\n built.volume = sum(built.volume, candle.volume);\n built.quoteVolume = sum(built.quoteVolume, candle.quoteVolume);\n built.takerBuyQuoteVolume = sum(built.takerBuyQuoteVolume, candle.takerBuyQuoteVolume);\n built.trades =\n built.trades === null || candle.trades === null ? null : built.trades + candle.trades;\n }\n }\n\n return [...buckets.keys()]\n .sort((left, right) => left - right)\n .map((start) => buckets.get(start))\n .filter((bucket): bucket is Bucket => bucket !== undefined)\n .filter((bucket) => {\n // Complet : le seau vaut ce qu'il annonce. Incomplet : acceptable seulement si la période\n // court toujours — il lui manque des bougies qui n'existent pas ENCORE.\n const stillOpen = bucket.candle.openedAt.getTime() + targetMs > now;\n return bucket.sources >= expected || stillOpen === true;\n })\n .map((bucket) => bucket.candle);\n}\n\n/** Somme qui propage l'inconnu : `null` domine. */\nfunction sum(left: string | null, right: string | null): string | null {\n if (left === null || right === null) {\n return null;\n }\n return addDecimals(left, right);\n}\n","import type { Timeframe } from '../enums/timeframe.enums';\n\n/** Ce qu'un SDK de venue attend pour une lecture de bougies. */\nexport interface IXexCandlesQuery {\n name: string;\n interval: string;\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n}\n\n/**\n * Adapte une requête XGate à la forme des SDK.\n *\n * Il ne reste plus qu'un renommage (`symbol` → `name`) : **les dates passent telles quelles**,\n * parce que les SDK prennent désormais des `Date`. Cette fonction traduisait autrefois vers des\n * chaînes UTC — et vers des millisecondes pour blofin, avec des bornes inversées. Ces conversions\n * vivent maintenant dans chaque SDK, à la frontière de sa venue, là où elles appartiennent.\n */\nexport function toXexQuery(query: {\n symbol: string;\n interval: Timeframe;\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n}): IXexCandlesQuery {\n return {\n name: query.symbol,\n interval: query.interval,\n startTime: query.startTime,\n endTime: query.endTime,\n limit: query.limit,\n };\n}\n","import { TIMEFRAME_MINUTES, Timeframe } from '../enums/timeframe.enums';\nimport { XgateEx } from '../enums/xgate-ex.enums';\n\n/**\n * CE QUE CHAQUE VENUE SERT NATIVEMENT — mesuré le 2026-08-05 contre les API, pas lu dans une doc.\n *\n * Ce qui n'y figure pas n'est pas indisponible : c'est **fabriqué par agrégation** depuis un\n * intervalle plus fin. La table dit seulement qui sert quoi de première main.\n *\n * - **paradex** s'arrête à l'heure, et c'est la venue qui le dit :\n * `resolution: must be one of [1 3 5 15 30 60]`.\n * - **lighter** refuse `1w` (`invalid param`) ; **extended** rend une liste vide pour ce même\n * intervalle — deux façons de dire non, dont une muette.\n * - **bullet** ne sert aucune bougie en REST (`perp().getCandles is not a function`) : rien à\n * agréger, la source manque entièrement.\n */\n/**\n * HYPERLIQUID SERT BIEN LA SEMAINE, MAIS ALIGNÉE SUR JEUDI — donc on ne la prend pas.\n *\n * Mesuré le 2026-08-07 : ses bougies `1w` ouvrent les jeudis (2026-07-09, 07-16, 07-23, 07-30),\n * quand binance et pacifica ouvrent les lundis. La venue aligne ses seaux sur l'epoch Unix, qui\n * tombe un jeudi ; elle n'a pas tort, elle a une autre convention.\n *\n * Le résultat serait pire qu'un manque : une semaine hyperliquid et une semaine binance porteraient\n * le même intervalle `1w` en couvrant des jours différents, et se compareraient silencieusement.\n * En l'excluant d'ici, XGate la **reconstruit depuis le journalier** — l'agrégateur, lui, décale\n * ses seaux de quatre jours pour ouvrir le lundi. Les bougies rendues portent `derived: true`.\n */\nconst HYPERLIQUID_NATIVE: readonly Timeframe[] = Object.values(Timeframe).filter(\n (interval) => interval !== Timeframe.W,\n);\n\nconst NATIVE: Record<XgateEx, readonly Timeframe[]> = {\n [XgateEx.Hyperliquid]: HYPERLIQUID_NATIVE,\n [XgateEx.Pacifica]: Object.values(Timeframe),\n [XgateEx.Aster]: Object.values(Timeframe),\n [XgateEx.Blofin]: Object.values(Timeframe),\n [XgateEx.Lighter]: [\n Timeframe.M1,\n Timeframe.M5,\n Timeframe.M15,\n Timeframe.H1,\n Timeframe.H4,\n Timeframe.D,\n ],\n [XgateEx.Extended]: [\n Timeframe.M1,\n Timeframe.M5,\n Timeframe.M15,\n Timeframe.H1,\n Timeframe.H4,\n Timeframe.D,\n ],\n [XgateEx.Paradex]: [Timeframe.M1, Timeframe.M5, Timeframe.M15, Timeframe.H1],\n [XgateEx.Bullet]: [],\n // Les deux CEX servent les sept, et sous les mêmes noms côté binance. Bybit compte en minutes\n // (`60`, `240`) puis en lettres (`D`, `W`) : son SDK traduit, la table reste dans notre grille.\n [XgateEx.Binance]: Object.values(Timeframe),\n [XgateEx.Bybit]: Object.values(Timeframe),\n};\n\n/** La venue sert-elle cet intervalle de première main ? */\nexport function servesNatively(xex: XgateEx, interval: Timeframe): boolean {\n return NATIVE[xex].includes(interval);\n}\n\n/**\n * L'intervalle SOURCE à demander pour fabriquer `target` chez cette venue.\n *\n * On prend le **plus grand** intervalle servi nativement qui divise la cible : le plus grand,\n * parce qu'il minimise le nombre de bougies à tirer — reconstruire une semaine depuis des minutes\n * demanderait 10 080 points là où sept journalières suffisent.\n *\n * `undefined` = la venue ne sert rien d'exploitable, il n'y a pas de fabrication possible.\n */\nexport function sourceFor(xex: XgateEx, target: Timeframe): Timeframe | undefined {\n const targetMinutes = TIMEFRAME_MINUTES[target];\n const candidates = NATIVE[xex]\n .filter((interval) => {\n const minutes = TIMEFRAME_MINUTES[interval];\n return minutes < targetMinutes && targetMinutes % minutes === 0;\n })\n .sort((left, right) => TIMEFRAME_MINUTES[right] - TIMEFRAME_MINUTES[left]);\n return candidates[0];\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { aggregate } from '../helpers/candle-aggregator';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { toXexQuery } from '../helpers/candle-query';\nimport { createPublicXex } from '../helpers/xex-registry';\nimport { servesNatively, sourceFor } from '../helpers/xex-timeframes';\nimport type { ICandle, ICandlesQuery } from '../interfaces/candle.interfaces';\n\n/**\n * LES BOUGIES EN REST — l'historique.\n *\n * Le temps réel vit dans `WsCandlesService` : ce sont deux besoins distincts (rejouer le passé,\n * suivre le présent), et une application qui fait du backfill n'a pas à ouvrir de socket.\n */\n@Injectable()\nexport class CandlesService {\n private readonly logger = new Logger(CandlesService.name);\n\n /**\n * Les bougies d'une venue, sur une plage de **dates**.\n *\n * L'appelant donne des `Date` et un {@link Timeframe} ; le service traduit vers ce que la venue\n * attend — chaîne UTC pour huit d'entre elles, millisecondes pour blofin.\n *\n * **Un intervalle que la venue ne sert pas est FABRIQUÉ**, pas refusé : on tire l'intervalle\n * exploitable le plus grand et on agrège. Paradex s'arrête à l'heure, lighter et extended ne\n * font pas la semaine — l'appelant n'a pas à le savoir. Les bougies fabriquées portent\n * `derived: true`.\n *\n * **Lève si la venue ne sert rien du tout** (bullet, qui n'a pas de REST). Rendre une liste vide\n * ferait passer une incapacité pour une absence de données, et un backfill silencieusement troué\n * est pire qu'un backfill en erreur.\n */\n public async candles(xex: XgateEx, query: ICandlesQuery): Promise<ICandle[]> {\n if (servesNatively(xex, query.interval) === true) {\n return this.fetch(xex, query, query.interval);\n }\n const source = sourceFor(xex, query.interval);\n if (source === undefined) {\n throw new Error(\n `candles(${xex}) : ni ${query.interval} servi, ni intervalle plus fin pour le fabriquer.`,\n );\n }\n this.logger.log(\n `${xex} ne sert pas ${query.interval} : agrégation depuis ${source} (bougies dérivées).`,\n );\n const fine = await this.fetch(xex, query, source);\n return aggregate(fine, query.interval);\n }\n\n /** Un appel REST à la venue, dans l'intervalle qu'elle sait servir. */\n private async fetch(xex: XgateEx, query: ICandlesQuery, interval: Timeframe): Promise<ICandle[]> {\n const perp = createPublicXex(xex).perp();\n if (perp.getCandles === undefined) {\n throw new Error(\n `candles(${xex}) : cette venue ne sert pas de bougies en REST — utilise WsCandlesService.`,\n );\n }\n const wires = await perp.getCandles(toXexQuery({ ...query, interval }));\n return wires.map((wire) => toCandle(wire, xex, interval, this.quoteOf(wire.symbol)));\n }\n\n /**\n * Les bougies d'un même marché chez PLUSIEURS venues, en une liste.\n *\n * Comme pour le catalogue : une venue en échec parmi d'autres est journalisée et ignorée ; seule,\n * son échec est celui de l'appel. Chaque bougie porte son `xex`, donc la liste reste\n * exploitable telle quelle.\n */\n public async candlesOf(xexes: XgateEx[], query: ICandlesQuery): Promise<ICandle[]> {\n if (xexes.length === 0) {\n throw new Error('candlesOf() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.candles(xex, query);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(\n `candles(${xex}, ${query.interval}) a échoué, venue ignorée : ${message}`,\n );\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /**\n * La cotation, déduite du symbole natif. Les bougies ne la portent pas — seul le catalogue la\n * publie —, et refaire un appel de catalogue pour chaque série coûterait plus cher que ce\n * qu'elle apporte. `BTC-USDT` → `USDT` ; à défaut de séparateur, on ne prétend rien.\n */\n private quoteOf(symbolXex: string): string {\n const parts = symbolXex.split('-');\n const last = parts[parts.length - 1];\n return parts.length > 1 && last !== undefined ? last : '';\n }\n}\n","import type { XgateEx } from '../enums/xgate-ex.enums';\n\n/**\n * CE QUE COTE UNE PAIRE — de la crypto, ou autre chose.\n *\n * Les venues perpétuelles ne listent plus seulement des cryptos : bybit publie 164 actions, 43 ETF et\n * quatre matières premières, binance ouvre des `TRADIFI_PERPETUAL` sur NVDA et l'or, extended range\n * ses actions en `RWA`. Le catalogue les rendait toutes indistinctes.\n *\n * ⚠️ CE N'EST PAS UNE COMMODITÉ DE CLASSEMENT. Le 2026-08-24, `PURR` cotait 11 $ chez bybit et\n * 0,13 $ chez hyperliquid : le premier est une ACTION, le second le memecoin natif d'Hyperliquid.\n * Même ticker, deux actifs — et un consommateur qui médiane « le prix de PURR » entre venues\n * publiait un nombre qui n'était le prix d'aucun des deux. Le rapport n'était même pas constant\n * (71 à 116 sur douze jours), ce qui excluait une échelle de cotation.\n *\n * ⚠️ ET CE N'EST PAS DÉDUCTIBLE DU TICKER. `SPX` est le memecoin SPX6900 chez binance, bybit et\n * extended — qui le disent tous les trois (`underlyingSubType: [\"Meme\"]`, `subCategory: \"Meme\"`) —\n * alors que l'indice S&P 500 s'appelle `SP500` chez pacifica. Une règle sur le nom aurait classé les\n * deux à l'envers.\n */\nexport type AssetClass = 'crypto' | 'stock' | 'commodity' | 'index' | 'etf' | 'forex';\n\n/**\n * binance : `underlyingType`, posé à côté de `contractType: 'TRADIFI_PERPETUAL'`.\n *\n * Les actions sont déclarées PAR PLACE — `EQUITY` vaut pour les américaines, et Séoul, Hong Kong et\n * Shanghai ont chacune la leur (SAMSUNG, TENCENT, CXMT). `PREMARKET` couvre les sociétés pas encore\n * cotées (OPENAI, ANTHROPIC) : ce sont des actions, pré-introduction.\n */\nconst BINANCE: Readonly<Record<string, AssetClass>> = {\n COIN: 'crypto',\n EQUITY: 'stock',\n CN_EQUITY: 'stock',\n HK_EQUITY: 'stock',\n KR_EQUITY: 'stock',\n PREMARKET: 'stock',\n COMMODITY: 'commodity',\n INDEX: 'index',\n};\n\n/** bybit : `symbolType`. La chaîne VIDE et `innovation` désignent toutes deux de la crypto. */\nconst BYBIT: Readonly<Record<string, AssetClass>> = {\n '': 'crypto',\n innovation: 'crypto',\n stock: 'stock',\n commodity: 'commodity',\n ETF: 'etf',\n};\n\n/**\n * extended : `subCategory`, quand `category` vaut `RWA`.\n *\n * ⚠️ Ses catégories CRYPTO sont multiples — `Crypto`, mais aussi `L1`, `L2`, `Infra`. C'est donc\n * `RWA` qui fait exception, et tout le reste est de la crypto : lister les catégories crypto une à\n * une laisserait FTM, MATIC et WAL sans classe à la prochaine que la venue invente.\n */\nconst EXTENDED: Readonly<Record<string, AssetClass>> = {\n Equity: 'stock',\n Commodity: 'commodity',\n 'ETF/Index': 'index',\n FX: 'forex',\n 'Pre-market': 'stock',\n};\n\n/**\n * LA CLASSE D'ACTIF D'UNE PAIRE, ou `undefined` quand la venue ne se prononce pas.\n *\n * ⚠️ `undefined` N'EST PAS « CRYPTO ». hyperliquid et pacifica cotent NVDA, TSLA, XAU et PLTR sans\n * publier la moindre catégorie : les classer d'office en crypto reproduirait exactement l'erreur\n * qu'on corrige ici. « On ne sait pas » est une information ; une valeur inventée n'en est pas une.\n *\n * Une catégorie inconnue rend `undefined` plutôt que de retomber sur `crypto` — le jour où une venue\n * en ajoute une, on veut le voir, pas l'absorber.\n */\nexport function assetClassOf(\n xex: XgateEx,\n xtras?: Record<string, unknown>,\n): AssetClass | undefined {\n if (xtras === undefined) {\n return undefined;\n }\n if (xex === 'binance') {\n const brut = xtras.underlyingType;\n return typeof brut === 'string' ? BINANCE[brut] : undefined;\n }\n if (xex === 'bybit') {\n const brut = xtras.symbolType;\n return typeof brut === 'string' ? BYBIT[brut] : undefined;\n }\n if (xex === 'extended') {\n const categorie = xtras.category;\n if (typeof categorie !== 'string') {\n return undefined;\n }\n if (categorie !== 'RWA') {\n return 'crypto';\n }\n const sous = xtras.subCategory;\n return typeof sous === 'string' ? EXTENDED[sous] : undefined;\n }\n return undefined;\n}\n","/**\n * LES CONTRATS À ÉCHÉANCE, séparés des perpétuels — parce qu'un future daté ne cote pas le comptant.\n *\n * Le catalogue les mélangeait : 44 lignes portaient `kind: 'perp'` alors qu'elles expirent, dont\n * neuf sur le seul BTC chez bybit. Un consommateur qui prend « le prix du BTC » y ramassait une\n * échéance de décembre, dont la base au comptant peut s'écarter de plusieurs pour cent.\n *\n * **`contractType` fait foi, JAMAIS la date de livraison.** binance publie `deliveryDate:\n * 4133404800000` — le 1er janvier 2100 — sur ses **perpétuels** : s'y fier marquerait tout le\n * catalogue comme daté. bybit, lui, met `deliveryTime: '0'` sur les siens. Deux conventions\n * incompatibles pour dire « ceci n'expire pas », d'où la lecture du type et de lui seul.\n */\n\n/** Valeurs de `contractType` qui désignent un contrat **à échéance**, venue par venue. */\nconst DATED_CONTRACT_TYPES: ReadonlySet<string> = new Set([\n // binance : les trimestriels (`BTCUSDT_260925`, `ETHUSDT_261225`).\n 'CURRENT_QUARTER',\n 'NEXT_QUARTER',\n 'CURRENT_QUARTER_DELIVERING',\n 'NEXT_QUARTER_DELIVERING',\n 'PERPETUAL_DELIVERING',\n // bybit : les datés (`BTCUSDT-25DEC26`), face à `LinearPerpetual`.\n 'LinearFutures',\n 'InverseFutures',\n]);\n\n/** Champs où les venues rangent l'échéance, dans l'ordre où on les interroge. */\nconst EXPIRY_FIELDS = ['deliveryTime', 'deliveryDate', 'expiryTime', 'settleTime'] as const;\n\n/**\n * L'ÉCHÉANCE D'UN CONTRAT, ou `undefined` s'il est perpétuel.\n *\n * On ne lit la date **que** si le type de contrat annonce une échéance : c'est ce qui neutralise le\n * `deliveryDate` de l'an 2100 des perpétuels binance. Un type inconnu est traité comme perpétuel —\n * l'immense majorité des lignes, et se tromper dans ce sens n'invente pas d'échéance.\n */\nexport function expiryOf(xtras?: Record<string, unknown>): Date | undefined {\n if (xtras === undefined) {\n return undefined;\n }\n const type = xtras.contractType;\n if (typeof type !== 'string' || DATED_CONTRACT_TYPES.has(type) === false) {\n return undefined;\n }\n for (const field of EXPIRY_FIELDS) {\n const brut = xtras[field];\n const ms = typeof brut === 'string' ? Number(brut) : brut;\n if (typeof ms === 'number' && Number.isFinite(ms) === true && ms > 0) {\n return new Date(ms);\n }\n }\n // Type daté mais aucune date exploitable : on le signale par une échéance absente plutôt que\n // d'inventer un horodatage. `isDated` reste vrai, lui, et suffit à écarter la ligne.\n return undefined;\n}\n\n/**\n * LE CONTRAT EXPIRE-T-IL ? Vrai pour un future daté, faux pour un perpétuel.\n *\n * Séparé d'{@link expiryOf} à dessein : une venue peut annoncer un type daté sans publier de date\n * exploitable, et il faut alors pouvoir écarter la ligne quand même.\n */\nexport function isDated(xtras?: Record<string, unknown>): boolean {\n const type = xtras?.contractType;\n return typeof type === 'string' && DATED_CONTRACT_TYPES.has(type);\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport { XgateEx } from '../enums/xgate-ex.enums';\nimport { assetClassOf } from '../helpers/asset-class';\nimport { expiryOf } from '../helpers/contract-expiry';\nimport { canonicalFromBase, canonicalFromXex, multiplierFromXex } from '../helpers/symbol-naming';\nimport { createPublicXex } from '../helpers/xex-registry';\nimport type { IPair, MarketKind } from '../interfaces/pair.interfaces';\n\n/**\n * Les venues qui NOMMENT leur actif de base au lieu de le laisser deviner : on prend ce qu'elles\n * disent (`baseAsset` chez binance, `baseCoin` chez bybit) plutôt que de re-parser leur symbole.\n *\n * Sans cela, le strip de cotation ne connaît que `USDT` et `USDC` : `ROSEBTC` — cotée en BTC —\n * donnerait le faux canonique `ROSEBTC`. Le cas est massif sur le spot binance, dont une grande\n * partie des 3 680 marchés se cote en BTC, ETH ou BNB.\n */\nconst NAMES_ITS_BASE: ReadonlySet<XgateEx> = new Set([XgateEx.Binance, XgateEx.Bybit]);\n\n/**\n * LE CATALOGUE, toutes venues confondues.\n *\n * L'appelant ne connaît qu'{@link XgateEx} et les types unifiés ; il n'importe aucun SDK, ne\n * construit aucune façade, et ne se soucie pas de ce que chaque venue nomme comment.\n *\n * **Un service par capacité, pas une façade unique** : une application qui ne veut que le\n * catalogue n'a aucune raison d'injecter de quoi trader. Les capacités suivantes vivront dans\n * leurs propres services, injectables séparément.\n */\n@Injectable()\nexport class CatalogService {\n private readonly logger = new Logger(CatalogService.name);\n\n /**\n * Le catalogue **perp** d'une ou plusieurs venues, en une seule liste.\n *\n * Chaque ligne porte sa venue d'origine (`xex`) et les deux symboles — le canonique pour\n * rapprocher un actif entre venues, le brut pour parler à celle-ci.\n *\n * **Le traitement d'une venue en échec dépend de ce qui a été demandé** : sur une seule venue,\n * un échec est l'échec de l'appel et il est relancé ; sur plusieurs, la liste partielle garde\n * sa valeur et l'incident est journalisé. Rendre `[]` en silence serait le pire des deux — un\n * catalogue vide se lit comme « cette venue ne liste rien », pas comme « je n'ai pas pu\n * demander ».\n */\n public async catalog(...xexes: XgateEx[]): Promise<IPair[]> {\n if (xexes.length === 0) {\n throw new Error('catalog() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.catalogOf(xex);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`catalog(${xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /** Le catalogue d'UNE venue, traduit vers {@link IPair}. */\n private async catalogOf(xex: XgateEx): Promise<IPair[]> {\n const pairs = await createPublicXex(xex).perp().getPairs();\n return pairs.map((pair) => ({\n symbol: NAMES_ITS_BASE.has(xex)\n ? canonicalFromBase(pair.base)\n : canonicalFromXex(pair.name, xex),\n quote: pair.quote,\n symbolXex: pair.name,\n xex,\n kind: pair.kind as MarketKind,\n multiplier: multiplierFromXex(pair.name),\n assetClass: assetClassOf(xex, pair.xtras),\n expiresAt: expiryOf(pair.xtras),\n ref: pair.ref ?? null,\n szDecimals: pair.szDecimals,\n tickSize: pair.tickSize,\n stepSize: pair.stepSize,\n minQty: pair.minQty,\n minNotional: pair.minNotional,\n maxLeverage: pair.maxLeverage,\n status: pair.status,\n delisted: pair.delisted,\n listedAt: pair.listedAt,\n xtras: pair.xtras,\n }));\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { canonicalFromXex } from '../helpers/symbol-naming';\nimport { createPublicXex } from '../helpers/xex-registry';\nimport type { MarketKind } from '../interfaces/pair.interfaces';\nimport type { IPrice } from '../interfaces/price.interfaces';\n\n/**\n * LES PRIX, toutes venues confondues.\n *\n * Même forme que `CatalogService`, et pour la même raison : l'appelant ne connaît qu'{@link XgateEx}\n * et les types unifiés. Chaque ligne porte sa venue d'origine, sans quoi un relevé multi-venues\n * perdrait ce qu'il a de plus utile — l'écart entre les cotations d'un même actif.\n *\n * **Ce service ne calcule rien.** Il ne dérive pas un `mid` d'un `bid`/`ask`, ne convertit pas un\n * volume, ne comble aucun trou : un champ à `null` dit que la venue ne le publie pas, et c'est une\n * information. Fabriquer la valeur manquante la rendrait indiscernable d'une valeur cotée.\n */\n@Injectable()\nexport class PricesService {\n private readonly logger = new Logger(PricesService.name);\n\n /**\n * Les prix **perp** d'une ou plusieurs venues, en une seule liste.\n *\n * **Le traitement d'une venue en échec dépend de ce qui a été demandé** : sur une seule venue, un\n * échec est l'échec de l'appel et il est relancé ; sur plusieurs, la liste partielle garde sa\n * valeur et l'incident est journalisé. Rendre `[]` en silence serait le pire des deux — un relevé\n * vide se lit comme « ce marché ne cote rien », pas comme « je n'ai pas pu demander ».\n */\n public async prices(...xexes: XgateEx[]): Promise<IPrice[]> {\n if (xexes.length === 0) {\n throw new Error('prices() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.pricesOf(xex);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`prices(${xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /** Les prix d'UNE venue, traduits vers {@link IPrice}. */\n private async pricesOf(xex: XgateEx): Promise<IPrice[]> {\n const prices = await createPublicXex(xex).perp().getPrices();\n return prices.map((price) => ({\n symbol: canonicalFromXex(price.name, xex),\n symbolXex: price.name,\n xex,\n kind: price.kind as MarketKind,\n mark: price.mark,\n oracle: price.oracle,\n mid: price.mid,\n bid: price.bid,\n ask: price.ask,\n last: price.last,\n funding: price.funding,\n openInterest: price.openInterest,\n volume24h: price.volume24h,\n prevDayPrice: price.prevDayPrice,\n quotedAt: price.quotedAt,\n xtras: price.xtras,\n }));\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { toXexQuery } from '../helpers/candle-query';\nimport { SPOT_XEXES, createPublicSpotXex, servesSpot } from '../helpers/xex-registry';\nimport type { ICandle, ICandlesQuery } from '../interfaces/candle.interfaces';\n\n/**\n * LES BOUGIES DU **COMPTANT**, en REST — binance et bybit.\n *\n * **Un service à part, et non un drapeau sur {@link CandlesService}.** C'est la même règle que pour\n * le catalogue : un `candles()` qui servirait tantôt du perpétuel tantôt du comptant selon un\n * paramètre produirait des erreurs silencieuses — on croirait lire des perpétuels et on lirait du\n * comptant, sans que rien ne le signale. Deux marchés, deux services ; l'appelant choisit\n * explicitement ce qu'il interroge.\n *\n * La différence n'est pas cosmétique : **le perpétuel porte un funding et s'écarte du comptant**.\n * C'est cet écart que certaines stratégies exploitent — le confondre le rendrait invisible.\n *\n * En revanche, les échelles de cotation ne posent aucun problème entre les deux marchés :\n * `SATSUSDT` (×1) et `10000SATSUSDT` (×10000) sont **deux paires distinctes**, chacune portant son\n * `multiplier` au catalogue. Une bougie appartient à une paire, identifiée par `xex` +\n * `symbolXex` + `kind` : il n'y a rien à réconcilier.\n *\n * **Aucune agrégation ici**, contrairement au perpétuel : binance et bybit servent nativement tous\n * les intervalles de `1m` à `1w`. Le jour où une venue comptant en manquerait un, ce serait à\n * ajouter — pas à supposer.\n */\n@Injectable()\nexport class SpotCandlesService {\n private readonly logger = new Logger(SpotCandlesService.name);\n\n /** Les venues dont XGate sert le comptant. */\n public venues(): readonly XgateEx[] {\n return SPOT_XEXES;\n }\n\n /**\n * Les bougies comptant d'une venue, sur une plage de **dates**.\n *\n * **Lève** si la venue ne sert pas de comptant, plutôt que de rendre une liste vide : « cette\n * venue n'a pas de marché comptant » et « ce marché n'a pas coté » appellent des suites très\n * différentes.\n */\n public async candles(xex: XgateEx, query: ICandlesQuery): Promise<ICandle[]> {\n if (servesSpot(xex) === false) {\n throw new Error(\n `spotCandles(${xex}) : cette venue ne sert pas de comptant. Disponibles : ${SPOT_XEXES.join(', ')}.`,\n );\n }\n const spot = createPublicSpotXex(xex).spot();\n if (spot.getCandles === undefined) {\n throw new Error(`spotCandles(${xex}) : cette venue ne sert pas de bougies comptant en REST.`);\n }\n const wires = await spot.getCandles(toXexQuery(query));\n return wires.map((wire) => toCandle(wire, xex, query.interval as Timeframe, ''));\n }\n\n /**\n * Les bougies comptant d'un même marché chez PLUSIEURS venues, en une liste.\n *\n * Même politique d'échec que partout ailleurs : seule, une venue en échec fait échouer l'appel ;\n * parmi d'autres, elle est journalisée et ignorée. Chaque bougie porte son `xex`.\n */\n public async candlesOf(xexes: XgateEx[], query: ICandlesQuery): Promise<ICandle[]> {\n if (xexes.length === 0) {\n throw new Error('spotCandlesOf() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.candles(xex, query);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(\n `spotCandles(${xex}, ${query.interval}) a échoué, venue ignorée : ${message}`,\n );\n return [];\n }\n }),\n );\n return collected.flat();\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { assetClassOf } from '../helpers/asset-class';\nimport { canonicalFromBase, multiplierFromXex } from '../helpers/symbol-naming';\nimport { SPOT_XEXES, createPublicSpotXex, servesSpot } from '../helpers/xex-registry';\nimport type { IPair, MarketKind } from '../interfaces/pair.interfaces';\n\n/**\n * LE CATALOGUE COMPTANT, séparé du catalogue perpétuel.\n *\n * **Deux services, pas un service à drapeau.** Un `catalog(xex, { spot: true })` serait plus court\n * à écrire et plus dangereux à lire : on croirait interroger des perpétuels et on interrogerait du\n * comptant, sans que rien ne le signale. Ici, le nom de la classe dit ce qu'on lit.\n *\n * Le comptant est **minoritaire** dans la famille : deux venues sur dix le servent. Ce n'est pas un\n * manque à combler, c'est la réalité d'un ensemble de SDK bâti pour le perpétuel.\n */\n@Injectable()\nexport class SpotCatalogService {\n private readonly logger = new Logger(SpotCatalogService.name);\n\n /** Les venues dont XGate sert le comptant. */\n public venues(): readonly XgateEx[] {\n return SPOT_XEXES;\n }\n\n /**\n * Le catalogue **spot** d'une ou plusieurs venues, en une seule liste.\n *\n * Même politique d'échec que le catalogue perpétuel : sur une seule venue, l'échec est relancé ;\n * sur plusieurs, la liste partielle garde sa valeur et l'incident est journalisé.\n *\n * **Une venue sans comptant lève**, elle ne rend pas une liste vide : « cette venue n'a pas de\n * spot » et « cette venue ne cote rien au comptant » ne se corrigent pas de la même façon.\n */\n public async catalog(...xexes: XgateEx[]): Promise<IPair[]> {\n if (xexes.length === 0) {\n throw new Error('spot catalog() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.catalogOf(xex);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`spot catalog(${xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /** Le catalogue comptant d'UNE venue, traduit vers {@link IPair}. */\n private async catalogOf(xex: XgateEx): Promise<IPair[]> {\n if (servesSpot(xex) === false) {\n throw new Error(`spot catalog(${xex}) : cette venue ne sert pas de comptant via XGate.`);\n }\n const pairs = await createPublicSpotXex(xex).spot().getPairs();\n return pairs.map((pair) => ({\n // Les deux venues à comptant NOMMENT leur actif de base : on prend ce qu'elles disent. Re-parser\n // `ROSEBTC` donnerait `ROSEBTC`, le strip de cotation ne connaissant que USDT et USDC — et le\n // spot se cote massivement en BTC, ETH ou BNB.\n symbol: canonicalFromBase(pair.base),\n quote: pair.quote,\n symbolXex: pair.name,\n xex,\n kind: pair.kind as MarketKind,\n // Le comptant porte les mêmes échelles que le perp : `SATSUSDT` (×1) et `1000SATSUSDT`\n // coexistent, et c'est justement entre eux que la comparaison serait fausse.\n multiplier: multiplierFromXex(pair.name),\n assetClass: assetClassOf(xex, pair.xtras),\n ref: pair.ref ?? null,\n szDecimals: pair.szDecimals,\n tickSize: pair.tickSize,\n stepSize: pair.stepSize,\n minQty: pair.minQty,\n minNotional: pair.minNotional,\n maxLeverage: pair.maxLeverage,\n status: pair.status,\n delisted: pair.delisted,\n listedAt: pair.listedAt,\n xtras: pair.xtras,\n }));\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { canonicalFromXex } from '../helpers/symbol-naming';\nimport { SPOT_XEXES, createPublicSpotXex, servesSpot } from '../helpers/xex-registry';\nimport type { MarketKind } from '../interfaces/pair.interfaces';\nimport type { IPrice } from '../interfaces/price.interfaces';\n\n/**\n * LES PRIX DU **COMPTANT** — binance et bybit.\n *\n * Séparé de `PricesService` comme le catalogue et les bougies le sont : deux marchés, deux\n * services. Ici la raison est encore plus directe qu'ailleurs — **un prix comptant et un prix\n * perpétuel ne sont pas le même nombre**. Le perpétuel porte un funding et s'écarte du comptant ;\n * c'est justement cet écart que certaines stratégies exploitent, et le confondre le rendrait\n * invisible.\n *\n * **La moitié des champs sont `null` au comptant, et c'est correct** : `mark`, `oracle`, `funding`\n * et `openInterest` n'existent que pour un perpétuel. Le comptant renseigne `bid`, `ask`, `last`,\n * `volume24h` — ce qui est mesuré, pas ce qui est dérivé d'un contrat.\n */\n@Injectable()\nexport class SpotPricesService {\n private readonly logger = new Logger(SpotPricesService.name);\n\n /** Les venues dont XGate sert les prix comptant. */\n public venues(): readonly XgateEx[] {\n return SPOT_XEXES;\n }\n\n /**\n * Les prix comptant d'une ou plusieurs venues, en une seule liste.\n *\n * Même politique d'échec que partout : seule, une venue en échec fait échouer l'appel ; parmi\n * d'autres, elle est journalisée et ignorée. Chaque prix porte son `xex`.\n */\n public async prices(...xexes: XgateEx[]): Promise<IPrice[]> {\n if (xexes.length === 0) {\n throw new Error('spot prices() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.pricesOf(xex);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`spot prices(${xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /** Les prix comptant d'UNE venue, traduits vers {@link IPrice}. */\n private async pricesOf(xex: XgateEx): Promise<IPrice[]> {\n if (servesSpot(xex) === false) {\n throw new Error(\n `spot prices(${xex}) : cette venue ne sert pas de comptant. Disponibles : ${SPOT_XEXES.join(', ')}.`,\n );\n }\n const spot = createPublicSpotXex(xex).spot();\n if (spot.getPrices === undefined) {\n throw new Error(`spot prices(${xex}) : cette venue ne sert pas de prix comptant.`);\n }\n const prices = await spot.getPrices();\n return prices.map((price) => ({\n // `canonicalFromXex`, et non `canonicalFromBase` : les prix ne portent QUE le symbole\n // concaténé (`BTCUSDT`), jamais la base — contrairement au catalogue, où la venue déclare\n // `baseAsset`. Passer le symbole à `canonicalFromBase` rendrait `BTCUSDT` tel quel, puisque\n // cette fonction ne strippe volontairement aucune cotation.\n //\n // ⚠️ La limite est connue et assumée : le strip ne retire que `USDT`/`USDC` finaux, donc une\n // paire cotée en BTC (`ROSEBTC`, `ETHBTC`) garde son nom entier. Le catalogue comptant, lui,\n // résout ces cas — c'est là qu'il faut aller chercher `symbol` et `quote` justes.\n symbol: canonicalFromXex(price.name, xex),\n symbolXex: price.name,\n xex,\n kind: price.kind as MarketKind,\n mark: price.mark,\n oracle: price.oracle,\n mid: price.mid,\n bid: price.bid,\n ask: price.ask,\n last: price.last,\n funding: price.funding,\n openInterest: price.openInterest,\n volume24h: price.volume24h,\n prevDayPrice: price.prevDayPrice,\n quotedAt: price.quotedAt,\n xtras: price.xtras,\n }));\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { SPOT_XEXES, createPublicSpotXex, servesSpot } from '../helpers/xex-registry';\nimport type { ICandle } from '../interfaces/candle.interfaces';\nimport type { Unsubscribe } from './ws-candles.service';\n\n/**\n * LES BOUGIES DU **COMPTANT** EN TEMPS RÉEL — binance et bybit.\n *\n * Le pendant de `WsCandlesService`, sur l'autre marché. La séparation n'est pas une symétrie\n * décorative : les façades exposent `ws(kind = 'perp')`, donc **un `ws()` nu ouvre un flux de\n * perpétuels**. Un service unique qui aurait oublié de passer le marché aurait diffusé des prix\n * de perpétuel à qui croyait écouter le comptant — deux séries voisines, jamais égales, et rien\n * pour le signaler.\n *\n * Le contrat {@link IXexSpotSource} exige donc `ws('spot')` explicitement, sans valeur par défaut.\n */\n@Injectable()\nexport class SpotWsCandlesService {\n private readonly logger = new Logger(SpotWsCandlesService.name);\n\n /** Les venues dont XGate diffuse le comptant en temps réel. */\n public venues(): readonly XgateEx[] {\n return SPOT_XEXES;\n }\n\n /**\n * Souscrit aux bougies **comptant** d'un marché. Rend la fonction de désabonnement.\n *\n * Le handler reçoit une bougie à la fois, au format unifié ; la bougie en cours est repoussée à\n * chaque mise à jour tant qu'elle n'est pas close.\n */\n public subscribe(\n xex: XgateEx,\n query: { symbol: string; interval: Timeframe },\n handler: (candle: ICandle) => void,\n ): Unsubscribe {\n if (servesSpot(xex) === false) {\n throw new Error(\n `spot subscribe(${xex}) : cette venue ne sert pas de comptant. Disponibles : ${SPOT_XEXES.join(', ')}.`,\n );\n }\n this.logger.log(`souscription bougies COMPTANT ${query.symbol} ${query.interval} sur ${xex}`);\n return createPublicSpotXex(xex)\n .ws('spot')\n .subscribeCandles({ name: query.symbol, interval: query.interval }, (wire) => {\n // La cotation n'est pas déductible d'un symbole concaténé (`ROSEBTC` est cotée en BTC) :\n // on ne prétend rien plutôt que d'affirmer un faux `USDT`.\n handler(toCandle(wire, xex, query.interval, ''));\n });\n }\n\n /**\n * Souscrit au MÊME marché comptant chez plusieurs venues, avec un seul handler.\n *\n * Chaque bougie porte son `xex`. La fonction rendue coupe tous les flux d'un coup.\n */\n public subscribeAll(\n xexes: XgateEx[],\n query: { symbol: string; interval: Timeframe },\n handler: (candle: ICandle) => void,\n ): Unsubscribe {\n if (xexes.length === 0) {\n throw new Error('spot subscribeAll() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const stops: Unsubscribe[] = [];\n for (const xex of xexes) {\n try {\n stops.push(this.subscribe(xex, query, handler));\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`souscription comptant ${xex} refusée, venue ignorée : ${message}`);\n }\n }\n return () => {\n for (const stop of stops) {\n stop();\n }\n };\n }\n}\n","// Repris de `Blips/server/src/execution/components/{protection-math,paper-math}.component.ts` (2026-08-06).\n// Ce calcul a été écrit et éprouvé côté application, mais sa place est ICI : c'est une règle de\n// protection d'ordre, pas une règle de signal. Blips garde ce qui lui appartient — le CHOIX des\n// parts selon le moteur (`tpParts`) et la garde « borne d'entrée vs tp1 ».\n\n/** Sens de la position. Le SL et les TP se posent au sens OPPOSÉ. */\nexport type Direction = 'long' | 'short';\n\n/** Un ordre de protection, prêt à être posé : déclenchement, taille, borne d'exécution. */\nexport interface IProtectionLeg {\n /** Prix auquel l'ordre se déclenche. */\n triggerPrice: string;\n /** Taille **en unités de base**. */\n size: string;\n /**\n * Borne d'exécution du déclenché : sous le trigger en long, au-dessus en short. Hyperliquid\n * l'exige ; les autres venues l'ignorent sans que ce soit une erreur.\n */\n price: string;\n}\n\n/** Ce qu'xgate rend : un SL pleine taille, et N take-profits partiels. */\nexport interface IProtection {\n sl: IProtectionLeg;\n tps: IProtectionLeg[];\n}\n\n/**\n * Ce que l'appelant fournit. **Les pourcentages sont des écarts au prix d'entrée**, en fraction\n * (0.01 = 1 %) : `slPct: 0.02` place le stop 2 % sous l'entrée en long, 2 % au-dessus en short.\n */\n/**\n * UN NIVEAU DE SORTIE — dit en ÉCART ou en PRIX, jamais les deux.\n *\n * Les deux façons de penser un objectif coexistent réellement : « je sors si ça monte de 10 % »\n * raisonne en risque, « je sors si ça touche 62 500 » raisonne en niveau technique. Obliger à\n * convertir l'un dans l'autre revient à faire calculer l'appelant, donc à déplacer l'erreur chez lui.\n *\n * L'union est **exclusive** par construction : donner les deux ne compile pas, et n'en donner aucun\n * non plus. C'est ce qui rend impossible un objectif à moitié défini.\n */\nexport type ITarget = { pct: number; price?: never } | { price: number; pct?: never };\n\n/**\n * Le prix d'un niveau, quel que soit la façon dont il a été exprimé.\n *\n * Un prix absolu est pris tel quel — c'est un niveau que l'appelant a choisi, pas une dérivée de\n * l'entrée. Un pourcentage se projette depuis l'entrée, du côté gagnant pour un take-profit et\n * perdant pour un stop, selon le sens de la position.\n */\nexport function priceOf(\n cible: ITarget,\n entry: number,\n direction: Direction,\n gagnant: boolean,\n): number {\n if (cible.price !== undefined) {\n return round8(cible.price);\n }\n const dir = (direction === 'long' ? 1 : -1) * (gagnant === true ? 1 : -1);\n return round8(entry * (1 + dir * (cible.pct as number)));\n}\n\nexport interface IProtectionInput {\n /** Prix d'entrée de référence. */\n entry: number;\n direction: Direction;\n /** Taille de la position, **en unités de base**. */\n size: number;\n /**\n * Le stop — en **écart** au prix d'entrée (`{ pct: 0.02 }`) ou en **prix absolu**\n * (`{ price: 62500 }`). Toujours du côté perdant quand il est donné en écart.\n */\n sl: ITarget;\n /**\n * Les take-profits, dans l'ordre. Chacun porte son niveau — en écart ou en prix — et `part`, la\n * fraction de la POSITION à sortir là (toujours un pourcentage, jamais un prix).\n *\n * **Une part à 0 ne produit AUCUN leg** — c'est un marqueur (un palier de breakeven, par exemple),\n * pas un ordre. Sauf s'il s'agit du dernier : celui-là clôture toujours.\n */\n tps: Array<ITarget & { part: number }>;\n /** Pas de prix de la paire. */\n tickSize?: string | null;\n /** Pas de quantité de la paire. */\n lotSize?: string | null;\n /**\n * Écart entre le déclenchement et la borne d'exécution, en fraction (défaut 0,5 %). Le déclenché\n * sort SOUS son trigger en long, AU-DESSUS en short.\n */\n slippagePct?: number;\n}\n\nconst SLIPPAGE_DEFAUT = 0.005;\n\n/**\n * LE SEUIL DE CLÔTURE INTÉGRALE — 80 % de la position.\n *\n * Strictement au-dessus, les take-profits sont manifestement censés tout sortir : le dernier absorbe le\n * reliquat et la position ferme entièrement. Trois paliers à 33 % somment à 99 % — ce 1 % restant\n * n'est pas une intention, c'est un arrondi, et le laisser ouvert créerait une position résiduelle\n * que personne ne surveille.\n *\n * À 80 % ou en dessous, l'inverse : deux paliers à 33 % laissent délibérément courir 34 % de la position. Le\n * dernier take-profit prend alors sa part nominale, sans plus — c'est une sortie partielle voulue,\n * et la compléter d'office trahirait l'intention.\n *\n * Le stop, lui, couvre la taille pleine dans les deux cas : ce qui court reste protégé.\n */\nconst SEUIL_CLOTURE = 0.8;\n\n/** Arrondi à huit décimales — la précision de travail, avant tout arrondi à la grille de la paire. */\nfunction round8(value: number): number {\n return Number(value.toFixed(8));\n}\n\n/**\n * Arrondit à un pas. `floor` pour les tailles — jamais `nearest` : arrondir au plus proche ferait\n * dépasser la somme des legs au-delà de la position, et la venue rejetterait le dernier.\n */\nexport function roundToStep(\n value: number,\n step: string | null | undefined,\n mode: 'floor' | 'nearest',\n): number {\n if (step === null || step === undefined || step === '') {\n return Math.round(value * 1e4) / 1e4;\n }\n const stepNum = Number(step);\n if (Number.isFinite(stepNum) === false || stepNum <= 0) {\n return Math.round(value * 1e4) / 1e4;\n }\n // LE PLANCHER SE PREND SUR UN QUOTIENT NETTOYÉ, et c'est indispensable. `1.7 - 1.3` vaut\n // `0.39999999999999991` en flottant : divisé par un pas de `0.1`, on obtient `3.9999…`, dont le\n // plancher est `3` au lieu de `4`. Un pas entier de position se volatilise — précisément le\n // résidu que ce module existe pour empêcher. Neuf décimales suffisent à recoller le quotient sans\n // jamais franchir un pas réel (`0.39 / 0.1` reste `3.9`, donc `3`).\n const quotient = value / stepNum;\n const units = mode === 'floor' ? Math.floor(Number(quotient.toFixed(9))) : Math.round(quotient);\n const dot = step.indexOf('.');\n const decimals = dot === -1 ? 0 : step.length - dot - 1;\n return Number((units * stepNum).toFixed(decimals));\n}\n\n/**\n * Arrondit un PRIX à la grille de la paire : `tickSize` s'il est fourni, sinon la règle Hyperliquid\n * — cinq chiffres significatifs ET au plus `6 − szDecimals` décimales, `szDecimals` étant déduit du\n * pas de quantité. Sans cette seconde branche, une venue sans tick fixe reçoit un prix qu'elle\n * refuse.\n */\nexport function roundPrice(\n value: number,\n tickSize?: string | null,\n lotSize?: string | null,\n): number {\n if (tickSize !== null && tickSize !== undefined && tickSize !== '') {\n return roundToStep(value, tickSize, 'nearest');\n }\n const dot = (lotSize ?? '').indexOf('.');\n const szDecimals = dot === -1 ? 0 : (lotSize as string).length - dot - 1;\n const maxDecimals = 6 - szDecimals;\n const sig = Number(value.toPrecision(5));\n if (maxDecimals <= 0) {\n return Math.round(sig);\n }\n return Number(sig.toFixed(maxDecimals));\n}\n\n/**\n * LES NIVEAUX DE PROTECTION, calculés depuis des POURCENTAGES.\n *\n * L'appelant donne un prix d'entrée, un sens et des écarts ; il ne calcule aucun prix lui-même.\n * Le stop part du côté perdant, les take-profits du côté gagnant — le sens s'en occupe.\n */\n/**\n * Les niveaux depuis des POURCENTAGES seuls — forme courte quand tout est exprimé en écart.\n *\n * {@link buildProtection} ne l'utilise plus : il résout chaque niveau séparément, puisqu'un stop\n * peut être donné en écart et un take-profit en prix absolu dans le même appel.\n */\nexport function buildLevels(\n entry: number,\n direction: Direction,\n slPct: number,\n tpPcts: readonly number[],\n): { sl: number; tps: number[] } {\n const dir = direction === 'long' ? 1 : -1;\n return {\n sl: round8(entry * (1 - dir * slPct)),\n tps: tpPcts.map((pct) => round8(entry * (1 + dir * pct))),\n };\n}\n\n/**\n * LA PROTECTION COMPLÈTE — un stop pleine taille et N take-profits partiels, prêts à poser.\n *\n * **GARANTIE ANTI-RÉSIDU, sous condition de seuil.** Quand les parts somment à **plus de 80 %**, le\n * DERNIER take-profit prend le reliquat EXACT (`size − Σ des précédents`) et la position ferme\n * entièrement : trois paliers à 33 % font 99 %, et ce 1 % restant est un arrondi, pas une intention.\n *\n * **À 80 % ou en dessous, on laisse courir.** Deux paliers à 33 % sortent 66 % et laissent 34 % de\n * position ouverte — c'est une sortie partielle voulue, et la compléter d'office la trahirait. Le\n * dernier palier prend alors sa part nominale, sans plus.\n *\n * Les paliers intermédiaires s'arrondissent au **plancher**, jamais au plus proche : arrondir au\n * plus proche ferait dépasser la somme, et c'est le dernier leg — celui qui clôture — que la venue\n * rejetterait.\n *\n * **LE STOP COUVRE TOUJOURS LA TAILLE PLEINE.** Un stop partiel laisserait une fraction de position\n * sans protection, ce qui est précisément ce qu'on veut rendre impossible : jamais une position\n * nue, même partiellement.\n *\n * Deux cas font disparaître un leg, et c'est voulu :\n * · une part à 0 sur un palier intermédiaire — c'est un marqueur, pas un ordre ;\n * · une taille qui tombe à 0 après arrondi — un ordre de taille nulle serait refusé.\n * Le dernier palier, lui, survit toujours : c'est lui qui ferme.\n */\nexport function buildProtection(input: IProtectionInput): IProtection {\n const slippage = input.slippagePct ?? SLIPPAGE_DEFAUT;\n const long = input.direction === 'long';\n // Chaque niveau se résout INDÉPENDAMMENT : l'un peut être donné en écart, le suivant en prix.\n const sl = priceOf(input.sl, input.entry, input.direction, false);\n const niveaux = input.tps.map((tp) => priceOf(tp, input.entry, input.direction, true));\n // La borne d'exécution : sous le trigger en long, au-dessus en short.\n const borne = (trigger: number): number =>\n long === true ? trigger * (1 - slippage) : trigger * (1 + slippage);\n\n // LA SOMME DES PARTS DÉCIDE DE CE QUE FAIT LE DERNIER PALIER. AU-DESSUS de 80 %, il clôture : le\n // reliquat lui revient. À 80 % ou en dessous, il s'en tient à sa part et le reste court.\n const sommeParts = input.tps.reduce((total, tp) => total + tp.part, 0);\n const clotureIntegrale = sommeParts > SEUIL_CLOTURE;\n\n const legs: IProtectionLeg[] = [];\n let alloue = 0;\n niveaux.forEach((trigger, index) => {\n const dernier = index === niveaux.length - 1;\n const part = input.tps[index]?.part ?? 0;\n if (dernier === false && part <= 0) {\n return;\n }\n // Le dernier prend le RELIQUAT EXACT — mais seulement si l'ensemble vise la clôture.\n const size =\n dernier === true && clotureIntegrale === true\n ? roundToStep(input.size - alloue, input.lotSize, 'floor')\n : roundToStep(part * input.size, input.lotSize, 'floor');\n if (size <= 0) {\n return;\n }\n alloue += size;\n legs.push({\n triggerPrice: String(roundPrice(trigger, input.tickSize, input.lotSize)),\n size: String(size),\n price: String(roundPrice(borne(trigger), input.tickSize, input.lotSize)),\n });\n });\n\n return {\n sl: {\n triggerPrice: String(roundPrice(sl, input.tickSize, input.lotSize)),\n // Taille PLEINE, toujours : le stop couvre l'intégralité de la position.\n size: String(input.size),\n price: String(roundPrice(borne(sl), input.tickSize, input.lotSize)),\n },\n tps: legs,\n };\n}\n","import type { XgateEx } from '../enums/xgate-ex.enums';\nimport type { Direction, ITarget } from '../helpers/protection';\n\n/** Côté d'un ordre. À ne pas confondre avec le SENS d'une position ({@link Direction}). */\nexport type Side = 'buy' | 'sell';\n\n/** Nature d'un ordre, telle qu'XGate la nomme. Le type natif exact reste dans `xtras`. */\nexport type OrderType =\n | 'limit'\n | 'market'\n | 'stop'\n | 'stopMarket'\n | 'takeProfit'\n | 'takeProfitMarket'\n | 'other';\n\n/**\n * ÉTAT D'UN ORDRE — **exactement les sept valeurs des SDK de venue**, orthographe comprise.\n *\n * `canceled` prend un seul L : c'est ce que rendent les huit SDK, unanimement. XGate écrivait\n * `cancelled` et perdait `partiallyFilled` et `expired` ; le `as OrderStatus` de la conversion\n * masquait l'écart au lieu de le signaler, si bien qu'un consommateur testant `=== 'cancelled'`\n * ne matchait **jamais** — un ordre annulé passait pour un ordre encore vivant.\n *\n * Seul `filled` a déplacé tout ce qui était demandé ; `partiallyFilled` en a déplacé une part et\n * garde le reste au carnet.\n */\nexport type OrderStatus =\n | 'open'\n | 'partiallyFilled'\n | 'filled'\n | 'canceled'\n | 'rejected'\n | 'expired'\n | 'other';\n\n/**\n * Les sept statuts, énumérés — pour VALIDER ce qu'une venue rend plutôt que de le caster.\n *\n * Un `as OrderStatus` sur une chaîne inconnue produit une valeur qui n'existe dans aucun `switch` et\n * que personne ne rattrape ; passer par cette liste fait retomber l'inconnu sur `other`, qui lui est\n * traité partout.\n */\nexport const ORDER_STATUSES: readonly OrderStatus[] = [\n 'open',\n 'partiallyFilled',\n 'filled',\n 'canceled',\n 'rejected',\n 'expired',\n 'other',\n];\n\n/** Durée de validité. `alo` = post-only : l'ordre est refusé s'il devait s'exécuter immédiatement. */\nexport type TimeInForce = 'gtc' | 'ioc' | 'fok' | 'alo';\n\n/**\n * UN ORDRE, tel qu'XGate le rend.\n *\n * `xex` dit d'où il vient : un identifiant d'ordre n'a de sens que chez la venue qui l'a émis, et\n * deux venues peuvent parfaitement produire le même.\n */\nexport interface IOrder {\n xex: XgateEx;\n /** Le symbole que la venue attend. */\n symbolXex: string;\n /** Type de marché. */\n kind: 'perp' | 'spot';\n /** L'identifiant de la venue. */\n id: string;\n /** L'identifiant fourni par l'appelant, quand il en a donné un. */\n clientId: string | null;\n side: Side;\n type: OrderType;\n /** `null` pour un ordre au marché. */\n price: string | null;\n /** Taille demandée, **en unités de base**. */\n size: string;\n /** Taille déjà exécutée, **en unités de base**. */\n filled: string;\n status: OrderStatus;\n tif: TimeInForce | null;\n /** L'ordre ne peut que réduire une position existante — jamais en ouvrir une. */\n reduceOnly: boolean | null;\n /** Moment où l'ordre a été soumis. */\n placedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * UNE POSITION ouverte.\n *\n * `size` est **toujours positive** : le sens est porté par `side`. Mélanger les deux — une taille\n * négative pour un short — oblige chaque consommateur à se souvenir de la convention, et l'un d'eux\n * l'oubliera.\n */\nexport interface IPosition {\n xex: XgateEx;\n symbolXex: string;\n /** Nom canonique de l'actif, pour rapprocher la même position entre venues. */\n symbol: string;\n side: 'long' | 'short' | null;\n /** Taille **en unités de base**, toujours positive. */\n size: string;\n entryPrice: string | null;\n markPrice: string | null;\n unrealizedPnl: string | null;\n leverage: number | null;\n liquidationPrice: string | null;\n /** Marge immobilisée par la position. */\n margin: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * UNE TRANSACTION PUBLIQUE — celles de tout le monde sur un marché, pas les siennes.\n *\n * À ne pas confondre avec {@link ITrade}, qui est une exécution **du compte**. Celle-ci n'a ni\n * frais, ni PnL, ni identifiant d'ordre : on ne voit que ce que le marché montre.\n */\nexport interface IPublicTrade {\n xex: XgateEx;\n symbolXex: string;\n price: string;\n size: string;\n /** Sens de l'AGRESSEUR : `buy` = quelqu'un a pris l'offre. */\n side: Side;\n tradedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * L'ÉTAT D'UN COMPTE — ce que toutes les venues disent, sous des noms différents.\n *\n * Les formes natives ne se ressemblent pas : hyperliquid range son équité sous\n * `marginSummary.accountValue`, pacifica sous `accountEquity`, aster sous `totalMarginBalance`.\n * **Mais les concepts, eux, sont les mêmes** — c'est exactement ce qu'XGate existe pour normaliser.\n *\n * `xtras` conserve la forme native complète : ce qui est propre à une venue n'est pas perdu, il\n * n'est simplement pas promu au rang de contrat.\n */\nexport interface IAccountState {\n xex: XgateEx;\n /** Valeur totale du compte, PnL latent compris. */\n equity: string;\n /** Ce qui reste mobilisable — pour ouvrir ou retirer. `null` si la venue ne le publie pas. */\n available: string | null;\n /** Marge immobilisée par les positions et ordres en cours. */\n marginUsed: string | null;\n /** Marge de maintenance : sous ce seuil, la liquidation menace. */\n maintenanceMargin: string | null;\n /**\n * PnL non réalisé des positions ouvertes.\n *\n * **Seule aster le publie au niveau du compte.** hyperliquid et pacifica ne l'exposent pas là —\n * il se lit alors sur les positions (`IPosition.unrealizedPnl`), qui le portent toutes. On rend\n * `null` plutôt que de le reconstituer : une somme calculée ici passerait pour une donnée de la\n * venue.\n */\n unrealizedPnl: string | null;\n /** L'état natif COMPLET, tel que la venue le publie. */\n xtras?: Record<string, unknown>;\n}\n\n/**\n * UN FUNDING PAYÉ — ce qui a réellement été prélevé ou reçu sur une position.\n *\n * À ne pas confondre avec le taux courant d'`IPrice.funding`, qui annonce ce qui *sera* payé. Un\n * calcul de résultat qui ignore ce qui l'a été surestime toute position tenue longtemps.\n */\nexport interface IFundingPaid {\n xex: XgateEx;\n symbolXex: string;\n /** Le taux appliqué, en fraction. */\n rate: string;\n /** Moment où il a été prélevé. */\n appliedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/** UNE EXÉCUTION du compte — ce qui a réellement été rempli, par opposition à ce qui a été demandé. */\nexport interface ITrade {\n xex: XgateEx;\n symbolXex: string;\n kind: 'perp' | 'spot';\n id: string;\n /** L'ordre dont provient cette exécution. */\n orderId: string;\n side: Side;\n price: string;\n size: string;\n fee: string;\n feeAsset: string | null;\n /** PnL réalisé, avant et après frais ; `null` quand la venue ne permet pas de l'établir. */\n grossPnl: string | null;\n netPnl: string | null;\n /** L'exécution était côté maker. `null` si la venue ne le dit pas. */\n maker: boolean | null;\n /** Moment de l'exécution. */\n filledAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * CE QU'ON DEMANDE POUR DÉPLACER LE STOP D'UNE POSITION OUVERTE.\n *\n * Le geste du point mort : une position qui a couru se sécurise en remontant son stop, sans jamais\n * la laisser nue. Les SDK posent le NOUVEAU stop avant d'annuler l'ANCIEN — l'ordre compte, et il\n * est tenu par la venue, pas par l'appelant.\n *\n * **Le sens est celui de la POSITION**, comme partout ailleurs ici : le stop se pose au sens opposé,\n * et c'est le SDK de la venue qui s'en charge.\n */\nexport interface IMoveStop {\n xex: XgateEx;\n /** Le symbole que la venue attend. */\n symbolXex: string;\n /** Sens de la POSITION protégée. */\n direction: Direction;\n /** L'identifiant du stop à remplacer, tel que la venue l'a rendu. */\n stopId: string;\n /** Le nouveau niveau de déclenchement. Arrondi au tick de la paire par XGate. */\n triggerPrice: number;\n /** Taille couverte — normalement la position entière. */\n size: number;\n /** Pas de prix et de quantité de la paire. */\n tickSize?: string | null;\n lotSize?: string | null;\n /** Écart entre déclenchement et borne d'exécution du déclenché. Défaut 0,5 %. */\n slippagePct?: number;\n}\n\n/**\n * CE QU'ON DEMANDE POUR OUVRIR UNE POSITION PROTÉGÉE.\n *\n * **Les protections sont données en POURCENTAGES**, pas en prix : xgate calcule les niveaux depuis\n * le prix d'entrée. L'appelant n'a ni à arrondir au tick, ni à se souvenir que le stop d'un short\n * est au-dessus de l'entrée.\n *\n * **`slPct` est OBLIGATOIRE.** C'est la règle qui a motivé tout ce chemin : jamais un ordre sans\n * stop. Une position ouverte pendant que le serveur tombe reste protégée par la venue elle-même.\n */\nexport interface IEntryWithProtection {\n xex: XgateEx;\n /** Le symbole que la venue attend. */\n symbolXex: string;\n /** Sens de la POSITION à ouvrir. Le stop et les take-profits se posent au sens opposé. */\n direction: Direction;\n /** Taille à ouvrir, **en unités de base**. */\n size: number;\n /** Prix d'entrée visé. Sert de référence aux pourcentages, et de prix limite. */\n entry: number;\n /**\n * LE STOP — en **écart** au prix d'entrée ou en **prix absolu**, jamais les deux.\n *\n * ```typescript\n * sl: { pct: 0.02 } // 2 % sous l'entrée en long, 2 % au-dessus en short\n * sl: { price: 62500 } // exactement 62 500, quel que soit le prix d'entrée\n * ```\n *\n * **Obligatoire.** C'est la règle qui a motivé tout ce chemin : jamais un ordre sans stop.\n */\n sl: ITarget;\n /**\n * LES TAKE-PROFITS, dans l'ordre. Chacun dit son NIVEAU et sa PART.\n *\n * ```typescript\n * tps: [{ pct: 0.03, part: 0.5 }, // sortir 50 % si ça monte de 3 %\n * { price: 62500, part: 0.5 }] // sortir 50 % si ça touche 62 500\n * ```\n *\n * **`part` est toujours une fraction de la POSITION**, jamais un prix : les deux modes ne portent\n * que sur le niveau. Un objectif se pense soit en risque (« +3 % »), soit en niveau technique\n * (« 62 500 ») — obliger à convertir l'un dans l'autre déplacerait le calcul, donc l'erreur, chez\n * l'appelant.\n *\n * Au-dessus de 80 % cumulés, le dernier absorbe le reliquat et la position ferme entièrement ;\n * à 80 % ou en dessous, le reste court.\n */\n tps: Array<ITarget & { part: number }>;\n /** `ioc` pour entrer au marché (taker), `alo` pour poster (maker). Défaut : `ioc`. */\n tif?: TimeInForce;\n /** Identifiant applicatif, remonté tel quel par la venue quand elle le supporte. */\n clientId?: string;\n /** Pas de prix et de quantité de la paire — sans eux, la venue refuse des niveaux mal arrondis. */\n tickSize?: string | null;\n lotSize?: string | null;\n /** Écart entre déclenchement et borne d'exécution du déclenché. Défaut 0,5 %. */\n slippagePct?: number;\n}\n","import { randomUUID } from 'node:crypto';\nimport { Injectable, Logger } from '@nestjs/common';\nimport { XgateEx } from '../enums/xgate-ex.enums';\nimport { type Direction, buildProtection, roundPrice, roundToStep } from '../helpers/protection';\nimport { canonicalFromXex } from '../helpers/symbol-naming';\nimport { type IXexAccess, createAccountXex } from '../helpers/xex-registry';\nimport { ORDER_STATUSES } from '../interfaces/trading.interfaces';\nimport type {\n IAccountState,\n IEntryWithProtection,\n IFundingPaid,\n IMoveStop,\n IOrder,\n IPosition,\n ITrade,\n OrderStatus,\n OrderType,\n Side,\n TimeInForce,\n} from '../interfaces/trading.interfaces';\n\n/** Une venue et de quoi y agir. */\nexport interface ITradingAccess extends IXexAccess {\n xex: XgateEx;\n}\n\n/**\n * LES VENUES OÙ L'OUVERTURE PROTÉGÉE A ÉTÉ PROUVÉE EN RÉEL — pas déduite du code, exécutée.\n *\n * Une venue n'entre ici qu'après un cycle complet observé sur son testnet : le lot part, la venue\n * répond, et le carnet montre l'entrée AVEC son stop. Tant que la preuve manque, ouvrir une position\n * par ce service reviendrait à parier qu'un chemin jamais exercé protège de l'argent réel.\n *\n * **aster en est absent, et c'est un constat mesuré** : son `/fapi/v3/batchOrders` coupe à ~3,26 s\n * (constante, quel que soit le nombre de legs — un lot invalide est pourtant rejeté en 264 ms) en\n * répondant « The request has timed out. », alors que la venue a DÉJÀ créé une partie des ordres.\n * Deux exécutions identiques ont produit deux sous-ensembles différents : une fois le stop et un\n * take-profit, une fois l'entrée seule. Chaque leg s'écrit on-chain (`newChainData.hash`), d'où le\n * dépassement. Un statut inconnu sur une ouverture protégée, c'est exactement la position nue que ce\n * service existe pour rendre impossible — donc on refuse d'ouvrir plutôt que d'ouvrir peut-être.\n */\nconst VENUES_PROUVEES: readonly XgateEx[] = [XgateEx.Pacifica, XgateEx.Hyperliquid, XgateEx.Aster];\n\n/**\n * LE TIMEOUT D'ASTER, QUI N'EST PAS UN ÉCHEC — sa propre documentation le dit.\n *\n * « HTTP 503 return code is used when the API successfully sent the message but not get a response\n * within the timeout period. It is important to **NOT** treat this as a failure operation; the\n * execution status is **UNKNOWN** and could have been a success. »\n * (`V3(Recommended)/EN/aster-finance-futures-api-testnet.md`, l. 147-149)\n *\n * **Sauf qu'elle renvoie `400`, pas `503`** — mesuré le 2026-08-09 : `HTTP 400` avec le message\n * ci-dessous, sur un lot dont les ordres avaient bel et bien été créés. Un `400` annonce une requête\n * malformée ; s'y fier ferait conclure à un rejet là où la venue a exécuté. Le message est donc le\n * seul discriminant, faute de code juste.\n */\nconst ASTER_TIMEOUT = 'The request has timed out.';\n\n/**\n * LES VENUES QUI SAVENT DÉPLACER UN STOP — mesuré sur les dix, pas supposé.\n *\n * Elles sont trois, et ce sont exactement les trois qui comptent. Ailleurs, déplacer un stop\n * demanderait d'annuler puis de reposer, ce qui rouvre la fenêtre nue que tout ceci existe pour\n * fermer : on préfère refuser.\n */\nconst VENUES_MOVE_STOP: readonly XgateEx[] = [XgateEx.Hyperliquid, XgateEx.Pacifica, XgateEx.Aster];\n\n/**\n * LE TRADING : positions, ordres et exécutions — un seul service, et c'est délibéré.\n *\n * Les séparer laisserait diverger trois vues du même état : une position n'est que la somme des\n * ordres exécutés, et un ordre ne se lit pas sans savoir ce qu'il a ouvert.\n *\n * **JAMAIS UN ORDRE SANS STOP.** `openWithProtection` est la seule façon d'ouvrir une position par\n * ce service, et elle exige un `slPct`. La protection part avec l'entrée, en un geste que la venue\n * traite atomiquement : si l'entrée ne remplit pas, la protection ne naît jamais — aucun orphelin.\n * Poser le stop *après* l'entrée laisse une fenêtre pendant laquelle la position est nue, et cette\n * fenêtre s'ouvre précisément quand le serveur tombe.\n *\n * **L'appelant donne des POURCENTAGES**, jamais des prix : xgate calcule les niveaux, arrondit à la\n * grille de la paire, et garantit qu'au-dessus de 80 % de parts cumulées la position ferme\n * entièrement — sans reliquat.\n */\n@Injectable()\nexport class TradingService {\n private readonly logger = new Logger(TradingService.name);\n\n /**\n * OUVRE UNE POSITION AVEC SA PROTECTION, en un geste atomique.\n *\n * Le premier ordre rendu est **l'entrée** ; les suivants sont le stop et les take-profits, dans\n * l'ordre où ils ont été posés.\n *\n * Chaque venue applique son mécanisme natif — hyperliquid groupe les enfants sous l'entrée et les\n * annule lui-même si elle rate, pacifica les embarque dans l'ordre, aster envoie un lot de\n * conditionnels. Le SDK de la venue s'en charge ; xgate fournit des niveaux justes.\n */\n public async openWithProtection(\n access: ITradingAccess,\n input: IEntryWithProtection,\n ): Promise<IOrder[]> {\n if (VENUES_PROUVEES.includes(access.xex) === false) {\n throw new Error(\n `openWithProtection(${access.xex}) : l'ouverture protégée n'a pas été prouvée en réel sur cette venue. Seules ${VENUES_PROUVEES.join(', ')} le sont. Ouvrir ici reviendrait à parier qu'un chemin jamais exercé pose bien le stop.`,\n );\n }\n const protection = buildProtection({\n entry: input.entry,\n direction: input.direction,\n size: input.size,\n sl: input.sl,\n tps: input.tps,\n tickSize: input.tickSize,\n lotSize: input.lotSize,\n slippagePct: input.slippagePct,\n });\n // Le sens de l'ENTRÉE suit la position ; le SDK de la venue pose stop et take-profits au sens\n // opposé — c'est le contrat commun, et le lui rappeler ici le contredirait.\n const side: Side = input.direction === 'long' ? 'buy' : 'sell';\n const perp = this.perpOf(access);\n if (typeof perp.createEntryWithProtection !== 'function') {\n throw new Error(\n `openWithProtection(${access.xex}) : cette venue n'expose pas l'ouverture protégée atomique.`,\n );\n }\n const entry = {\n name: input.symbolXex,\n side,\n type: 'limit',\n size: String(input.size),\n price: String(input.entry),\n tif: input.tif ?? 'ioc',\n reduceOnly: false,\n clientId: input.clientId,\n };\n const consigne = {\n name: input.symbolXex,\n side,\n sl: protection.sl,\n tps: protection.tps,\n clientId: input.clientId,\n };\n try {\n const orders = await perp.createEntryWithProtection(entry, consigne);\n return orders.map((order) => this.toOrder(order, access.xex));\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(ASTER_TIMEOUT) === false) {\n throw error;\n }\n // Statut INCONNU, pas échec : on va LIRE ce que la venue a réellement fait.\n this.logger.warn(\n `openWithProtection(${access.xex}) : la venue a coupé avant de répondre. Statut inconnu — relecture de l'état réel.`,\n );\n return await this.etatApres(access, input.symbolXex);\n }\n }\n\n /**\n * CE QUE LA VENUE A RÉELLEMENT FAIT, après une réponse perdue.\n *\n * Un timeout ne dit pas « rien n'a été créé » : mesuré sur aster, deux exécutions identiques ont\n * produit deux sous-ensembles différents — une fois le stop et un take-profit, une fois l'entrée\n * seule. La seule vérité est au carnet, donc on va l'y chercher.\n *\n * **Lève si la position est nue** : une entrée remplie sans stop est exactement ce que ce service\n * existe pour empêcher, et le silence serait pire que l'erreur. L'appelant doit savoir qu'il a une\n * position à protéger, tout de suite.\n */\n private async etatApres(access: ITradingAccess, symbolXex: string): Promise<IOrder[]> {\n const perp = this.perpOf(access);\n const [ouverts, positions] = await Promise.all([\n perp.getOpens(),\n perp.getPositions({ name: symbolXex }),\n ]);\n const surLaPaire = ouverts.filter((order) => order.name === symbolXex);\n const position = positions.find(\n (candidate) => candidate.name === symbolXex && Number(candidate.size) !== 0,\n );\n const protege = surLaPaire.some((order) => order.reduceOnly === true);\n if (position !== undefined && protege === false) {\n throw new Error(\n `openWithProtection(${access.xex}/${symbolXex}) : POSITION NUE — ${position.size} ouvert sans stop au carnet après une réponse perdue. À protéger immédiatement.`,\n );\n }\n // ENTRÉE ORPHELINE : au carnet sans protection, et pas encore remplie. Rien n'est nu à cet\n // instant, mais elle le deviendra si elle remplit — une bombe à retardement que personne ne\n // surveille. Mesuré sur aster : le lot perd son stop et garde l'entrée. On l'annule, parce\n // qu'aucune position vaut mieux qu'une position sans stop.\n // RIEN N'EST OUVERT : le lot n'a créé qu'une partie de lui-même. Ce qui reste au carnet est du\n // déchet — une entrée sans stop deviendrait nue en remplissant, un stop sans position ne protège\n // rien et s'accumulerait à chaque tentative. Mesuré sur aster, où le lot est NON DÉTERMINISTE :\n // deux essais identiques ont laissé une fois l'entrée seule, une fois le stop seul.\n //\n // On balaie et on lève : `openWithProtection` ouvre protégé ou ne laisse rien.\n if (position === undefined && surLaPaire.length > 0) {\n for (const orphelin of surLaPaire) {\n await perp.cancel({ name: symbolXex, id: orphelin.id }).catch(() => undefined);\n }\n throw new Error(\n `openWithProtection(${access.xex}/${symbolXex}) : réponse perdue, lot incomplet — ${surLaPaire.length} ordre(s) orphelin(s) ANNULÉ(S). Rien n'est ouvert, rien ne traîne.`,\n );\n }\n this.logger.log(\n `openWithProtection(${access.xex}/${symbolXex}) : ${surLaPaire.length} ordre(s) au carnet, position ${position === undefined ? 'absente' : position.size}.`,\n );\n return surLaPaire.map((order) => this.toOrder(order, access.xex));\n }\n\n /** Les positions ouvertes d'un compte. */\n public async positions(access: ITradingAccess, symbolXex?: string): Promise<IPosition[]> {\n const perp = this.perpOf(access);\n // On ne passe la requête QUE s'il y a un symbole : un `{ name: undefined }` se sérialise en\n // `instId=undefined` chez blofin, qui répond « Parameter instId error » — un filtre vide n'est\n // pas la même chose qu'aucun filtre.\n const positions = await perp.getPositions(\n symbolXex === undefined ? undefined : { name: symbolXex },\n );\n return positions.map((position) => ({\n xex: access.xex,\n symbolXex: position.name,\n symbol: canonicalFromXex(position.name, access.xex),\n side: position.side,\n size: position.size,\n entryPrice: position.entryPrice,\n markPrice: position.markPrice,\n unrealizedPnl: position.unrealizedPnl,\n leverage: position.leverage,\n liquidationPrice: position.liquidationPrice,\n margin: position.margin,\n xtras: position.xtras,\n }));\n }\n\n /** Les ordres encore au carnet. */\n public async openOrders(access: ITradingAccess): Promise<IOrder[]> {\n const orders = await this.perpOf(access).getOpens();\n return orders.map((order) => this.toOrder(order, access.xex));\n }\n\n /**\n * Les exécutions du compte — ce qui a réellement été rempli.\n *\n * À ne pas confondre avec les ordres : un ordre annoncé `filled` peut avoir été rempli en\n * plusieurs fois, à des prix différents. C'est ici que se lit le prix moyen réel.\n */\n public async trades(access: ITradingAccess): Promise<ITrade[]> {\n const trades = await this.perpOf(access).getUserTrades();\n return trades.map((trade) => ({\n xex: access.xex,\n symbolXex: trade.name,\n kind: trade.kind as 'perp' | 'spot',\n id: trade.id,\n orderId: trade.orderId,\n side: trade.side as Side,\n price: trade.price,\n size: trade.size,\n fee: trade.fee,\n feeAsset: trade.feeAsset,\n grossPnl: trade.grossPnl,\n netPnl: trade.netPnl,\n maker: trade.maker,\n filledAt: trade.filledAt,\n xtras: trade.xtras,\n }));\n }\n\n /**\n * FERME UNE POSITION ENTIÈREMENT, sur la taille que **la venue** déclare — jamais celle qu'on croit.\n *\n * C'est toute la différence : fermer la taille planifiée laisse un reliquat quand le fill a différé du\n * plan (une entrée partiellement remplie, un take-profit passé entre-temps), et ce reliquat reste\n * ouvert, sans stop, à la charge du compte. On relit donc la position juste avant de la fermer.\n *\n * Rend `null` quand il n'y a rien à fermer — un appel sur une position déjà close n'est pas une erreur.\n */\n public async closePosition(access: ITradingAccess, symbolXex: string): Promise<IOrder | null> {\n const perp = this.perpOf(access);\n const positions = await perp.getPositions({ name: symbolXex });\n const position = positions.find(\n (candidate) => candidate.name === symbolXex && Number(candidate.size) !== 0,\n );\n let closed: IOrder | null = null;\n if (position !== undefined) {\n // `side` porte le sens de la POSITION ; on sort au sens opposé, en `reduceOnly` pour qu'un\n // décalage de taille ne puisse jamais ouvrir une position inverse.\n const exit: Side = position.side === 'long' ? 'sell' : 'buy';\n const order = await perp.place({\n name: symbolXex,\n side: exit,\n type: 'market',\n size: position.size,\n reduceOnly: true,\n });\n closed = this.toOrder(order, access.xex);\n this.logger.log(\n `closePosition(${access.xex}/${symbolXex}) : ${position.size} fermé au marché`,\n );\n }\n return closed;\n }\n\n /**\n * DÉPLACE LE STOP d'une position ouverte — le geste du point mort.\n *\n * Le SDK de la venue pose le NOUVEAU stop **avant** d'annuler l'ancien : la position n'est jamais\n * nue, pas même une milliseconde. C'est le même invariant qu'à l'ouverture, appliqué au milieu de\n * la vie de la position.\n *\n * **Un stop du mauvais côté est refusé ici**, avant l'appel réseau. Pour un long, un stop au-dessus\n * du marché se déclenche immédiatement et solde la position — la venue l'accepte sans broncher, et\n * l'erreur ne se voit qu'une fois l'argent parti. Ce refus n'est pas de la prudence décorative :\n * il a été écrit après avoir vu le cas se produire.\n */\n public async moveStop(\n access: ITradingAccess,\n input: IMoveStop,\n markPrice: number,\n ): Promise<{ symbolXex: string; id: string }> {\n const trigger = roundPrice(input.triggerPrice, input.tickSize, input.lotSize);\n const perdant = input.direction === 'long' ? trigger >= markPrice : trigger <= markPrice;\n if (perdant === true) {\n throw new Error(\n `moveStop(${access.xex}/${input.symbolXex}) : un stop ${input.direction} à ${trigger} est du mauvais côté du marché (${markPrice}) — il se déclencherait aussitôt et solderait la position.`,\n );\n }\n const perp = this.perpOf(access);\n if (typeof perp.moveStop !== 'function') {\n throw new Error(\n `moveStop(${access.xex}) : cette venue ne sait pas déplacer un stop. Disponible sur ${VENUES_MOVE_STOP.join(', ')}.`,\n );\n }\n // La borne d'exécution du déclenché part du DÉCLENCHEMENT, du côté défavorable : un stop de long\n // s'exécute en vendant, donc sa borne est SOUS le seuil. Sans borne, un marché qui saute laisse\n // partir la position à n'importe quel prix.\n const glissement = input.slippagePct ?? 0.005;\n const borne = input.direction === 'long' ? 1 - glissement : 1 + glissement;\n const moved = await perp.moveStop({\n name: input.symbolXex,\n side: input.direction === 'long' ? 'buy' : 'sell',\n stopId: input.stopId,\n triggerPrice: String(trigger),\n size: String(roundToStep(input.size, input.lotSize, 'floor')),\n price: String(roundPrice(trigger * borne, input.tickSize, input.lotSize)),\n });\n this.logger.log(`moveStop(${access.xex}/${input.symbolXex}) : stop porté à ${trigger}`);\n return { symbolXex: moved.name, id: moved.id };\n }\n\n /**\n * LE PRIX DE SORTIE RÉEL d'une position, lu dans l'historique de la venue.\n *\n * Le prix planifié ne dit pas à quel prix on est sorti : un stop se déclenche au marché, un\n * take-profit peut remplir en plusieurs fois. Seul l'historique porte le prix réellement obtenu, et\n * c'est lui qui doit servir au calcul du résultat — sinon le PnL affiché est une fiction.\n *\n * On retient le **dernier** ordre de sortie rempli après `openedAt` : les précédents sont les\n * take-profits partiels, celui-là est la clôture.\n */\n public async exitPrice(\n access: ITradingAccess,\n symbolXex: string,\n direction: Direction,\n openedAt: Date,\n ): Promise<string | null> {\n const exit: Side = direction === 'long' ? 'sell' : 'buy';\n const history = await this.perpOf(access).getHistory({ name: symbolXex });\n const exits = history\n .filter((order) => order.name === symbolXex && order.side === exit)\n .filter((order) => order.status === 'filled' || order.status === 'partiallyFilled')\n .filter((order) => order.placedAt.getTime() > openedAt.getTime())\n .filter((order) => order.price !== null && Number(order.price) > 0)\n .sort((left, right) => right.placedAt.getTime() - left.placedAt.getTime());\n const last = exits[0];\n return last === undefined ? null : last.price;\n }\n\n /**\n * L'HISTORIQUE DES ORDRES du compte — ce qui a été soumis, rempli, annulé ou expiré.\n *\n * À distinguer de {@link trades} : un ordre est une **intention**, une exécution est un **fait**.\n * Un ordre `filled` peut avoir été rempli en plusieurs fois, à des prix différents.\n *\n * Servi par les huit venues qui tradent.\n */\n public async orderHistory(access: ITradingAccess, symbolXex?: string): Promise<IOrder[]> {\n const orders = await this.perpOf(access).getHistory(\n symbolXex === undefined ? undefined : { name: symbolXex },\n );\n return orders.map((order) => this.toOrder(order, access.xex));\n }\n\n /**\n * L'ÉTAT DU COMPTE — équité, disponible, marge immobilisée, marge de maintenance, PnL latent.\n *\n * Les venues rangent ces valeurs sous des noms qui n'ont rien à voir : `marginSummary.accountValue`\n * chez hyperliquid, `accountEquity` chez pacifica, `totalMarginBalance` chez aster — **mais elles\n * disent la même chose**. La forme native complète reste dans `xtras`, rien n'est perdu.\n *\n * **blofin ne l'expose pas** et lève ; les sept autres le servent.\n */\n public async accountInfo(access: ITradingAccess): Promise<IAccountState> {\n const perp = this.perpOf(access);\n if (typeof perp.getAccountInfo !== 'function') {\n throw new Error(\n `accountInfo(${access.xex}) : cette venue n'expose pas l'état de son compte.`,\n );\n }\n const brut = (await perp.getAccountInfo()) as Record<string, unknown>;\n return this.toAccountState(brut, access.xex);\n }\n\n /**\n * RÈGLE LE LEVIER d'une paire, avant d'ouvrir.\n *\n * Le levier détermine la marge immobilisée, donc la taille tenable : le changer après l'ouverture\n * ne rejoue pas la position déjà prise. C'est un réglage préalable, pas un ajustement.\n *\n * **bullet ne l'expose pas** — son levier se fixe au compte, pas à la position — et lève.\n */\n public async setLeverage(\n access: ITradingAccess,\n symbolXex: string,\n leverage: number,\n ): Promise<void> {\n const perp = this.perpOf(access);\n if (typeof perp.updateLeverage !== 'function') {\n throw new Error(`setLeverage(${access.xex}) : cette venue ne règle pas le levier par paire.`);\n }\n await perp.updateLeverage({ name: symbolXex, leverage });\n this.logger.log(`setLeverage(${access.xex}/${symbolXex}) : levier porté à ${leverage}`);\n }\n\n /**\n * CHOISIT LE MODE DE MARGE d'une paire : **isolée** ou **croisée**.\n *\n * En **croisée**, tout le solde du compte garantit la position : elle tient plus longtemps, mais\n * une liquidation emporte l'ensemble. En **isolée**, seule la marge affectée est en jeu : la perte\n * est bornée à ce montant, la liquidation arrive plus tôt.\n *\n * **À régler AVANT d'ouvrir.** Changer le mode d'une position vivante est refusé par la plupart\n * des venues, et là où c'est accepté, cela redistribue la garantie sous les pieds de la position.\n *\n * Servi par les trois venues de référence.\n */\n public async setMarginMode(\n access: ITradingAccess,\n symbolXex: string,\n isolated: boolean,\n ): Promise<void> {\n const perp = this.perpOf(access);\n if (typeof perp.setMarginMode !== 'function') {\n throw new Error(\n `setMarginMode(${access.xex}) : cette venue ne choisit pas son mode de marge.`,\n );\n }\n await perp.setMarginMode({ name: symbolXex, isolated });\n this.logger.log(\n `setMarginMode(${access.xex}/${symbolXex}) : marge ${isolated === true ? 'ISOLÉE' : 'CROISÉE'}`,\n );\n }\n\n /**\n * AJOUTE DE LA MARGE à une position isolée — pour éloigner le prix de liquidation.\n *\n * C'est le geste qui sauve une position sous pression sans la réduire : on renforce la garantie\n * plutôt que de couper. **N'a de sens qu'en marge isolée** ; en croisée, tout le solde garantit\n * déjà la position et il n'y a rien à ajouter.\n *\n * Servi par les trois venues de référence.\n */\n public async addMargin(access: ITradingAccess, symbolXex: string, amount: string): Promise<void> {\n const perp = this.perpOf(access);\n if (typeof perp.addIsolatedMargin !== 'function') {\n throw new Error(`addMargin(${access.xex}) : cette venue n'ajoute pas de marge isolée.`);\n }\n await perp.addIsolatedMargin({ name: symbolXex, amount });\n this.logger.log(`addMargin(${access.xex}/${symbolXex}) : +${amount} de marge isolée`);\n }\n\n /**\n * RETIRE DE LA MARGE d'une position isolée — pour libérer du capital.\n *\n * L'inverse d'{@link addMargin} : la garantie diminue, donc le prix de liquidation **se rapproche**.\n * La venue refuse ce qui mettrait la position sous son seuil de maintenance.\n *\n * ⚠️ **pacifica ne l'expose pas** et lève : chez elle, la marge s'ajoute mais ne se retire pas.\n * Fermer partiellement la position libère alors le capital.\n */\n public async removeMargin(\n access: ITradingAccess,\n symbolXex: string,\n amount: string,\n ): Promise<void> {\n const perp = this.perpOf(access);\n if (typeof perp.removeIsolatedMargin !== 'function') {\n throw new Error(\n `removeMargin(${access.xex}) : cette venue ne retire pas de marge isolée — ferme partiellement la position pour libérer du capital.`,\n );\n }\n await perp.removeIsolatedMargin({ name: symbolXex, amount });\n this.logger.log(`removeMargin(${access.xex}/${symbolXex}) : −${amount} de marge isolée`);\n }\n\n /**\n * L'HISTORIQUE DU FUNDING **PAYÉ** sur une paire — ce qui a réellement été prélevé ou reçu.\n *\n * À ne pas confondre avec le taux courant, que rend `PricesService` : celui-ci annonce ce qui\n * *sera* payé, celui-là dit ce qui *l'a été*. Un backtest qui ignore le funding réel surestime le\n * résultat d'une position tenue longtemps.\n *\n * Servi par les huit venues qui tradent.\n */\n public async fundingHistory(\n access: ITradingAccess,\n symbolXex: string,\n range?: { startTime?: Date; endTime?: Date; limit?: number },\n ): Promise<IFundingPaid[]> {\n const perp = this.perpOf(access);\n if (typeof perp.getFundingHistory !== 'function') {\n throw new Error(\n `fundingHistory(${access.xex}) : cette venue ne publie pas son historique de funding.`,\n );\n }\n const rates = await perp.getFundingHistory({\n name: symbolXex,\n startTime: range?.startTime === undefined ? undefined : toXexDate(range.startTime),\n endTime: range?.endTime === undefined ? undefined : toXexDate(range.endTime),\n limit: range?.limit,\n });\n return rates.map((rate) => ({\n xex: access.xex,\n symbolXex: rate.name,\n rate: rate.rate,\n appliedAt: rate.appliedAt,\n xtras: rate.xtras,\n }));\n }\n\n /**\n * UN IDENTIFIANT APPLICATIF pour un ordre, **au format que la venue accepte**.\n *\n * C'est lui qui relie un ordre à la décision qui l'a produit : la venue le rend tel quel dans\n * `IOrder.clientId`, ce qui permet de retrouver son origine sans tenir de table de correspondance.\n *\n * **hyperliquid exige un hexadécimal préfixé `0x`** (128 bits) là où les autres acceptent un UUID\n * ordinaire. Passer le mauvais format fait rejeter l'ordre — d'où cette méthode plutôt qu'un\n * `randomUUID()` chez l'appelant.\n */\n public newClientOrderId(xex: XgateEx): string {\n const uuid = randomUUID();\n return xex === XgateEx.Hyperliquid ? `0x${uuid.replace(/-/gu, '')}` : uuid;\n }\n\n /**\n * LE COUPE-CIRCUIT : annule TOUS les ordres d'une paire, d'un geste.\n *\n * À réserver aux situations où l'on veut reprendre la main sans discuter — un état incohérent, un\n * arrêt d'urgence, une reprise après incident. Annuler un par un laisse une fenêtre pendant\n * laquelle certains ordres vivent encore.\n *\n * ⚠️ **Cela retire aussi les PROTECTIONS.** Stops et take-profits sont des ordres comme les\n * autres : une position ouverte se retrouve **nue** après ce geste. À n'employer que si la\n * position est fermée, ou si l'on repose une protection immédiatement — jamais pour « faire le\n * ménage » sur une position vivante.\n *\n * Rend le nombre d'ordres annulés quand la venue le publie, `null` sinon.\n */\n public async cancelAll(access: ITradingAccess, symbolXex: string): Promise<number | null> {\n const perp = this.perpOf(access);\n if (typeof perp.cancelAll !== 'function') {\n throw new Error(`cancelAll(${access.xex}) : cette venue n'annule pas en masse.`);\n }\n const positions = await perp.getPositions({ name: symbolXex });\n const ouverte = positions.find(\n (candidate) => candidate.name === symbolXex && Number(candidate.size) !== 0,\n );\n if (ouverte !== undefined) {\n this.logger.warn(\n `cancelAll(${access.xex}/${symbolXex}) : ${ouverte.size} EN POSITION — ses protections partent avec. La position sera NUE.`,\n );\n }\n const { cancelled } = await perp.cancelAll({ name: symbolXex });\n this.logger.log(\n `cancelAll(${access.xex}/${symbolXex}) : ${cancelled ?? '?'} ordre(s) annulé(s)`,\n );\n return cancelled;\n }\n\n /** Annule un ordre par son identifiant de venue. */\n public async cancel(access: ITradingAccess, symbolXex: string, orderId: string): Promise<void> {\n await this.perpOf(access).cancel({ name: symbolXex, id: orderId });\n }\n\n /** Le scope `perp()` de la venue, typé au plus près de ce que le contrat commun garantit. */\n private perpOf(access: ITradingAccess): {\n getPositions(query?: { name?: string }): Promise<IXexPosition[]>;\n getOpens(): Promise<IXexOrder[]>;\n getHistory(query?: { name?: string }): Promise<IXexOrder[]>;\n getAccountInfo?(): Promise<unknown>;\n updateLeverage?(input: { name: string; leverage: number }): Promise<unknown>;\n setMarginMode?(input: { name: string; isolated: boolean }): Promise<void>;\n addIsolatedMargin?(input: { name: string; amount: string }): Promise<void>;\n removeIsolatedMargin?(input: { name: string; amount: string }): Promise<void>;\n getFundingHistory?(query: {\n name: string;\n startTime?: string;\n endTime?: string;\n limit?: number;\n }): Promise<IXexFundingRate[]>;\n getUserTrades(): Promise<IXexTrade[]>;\n place(input: {\n name: string;\n side: Side;\n type: string;\n size: string;\n reduceOnly: boolean;\n }): Promise<IXexOrder>;\n cancel(input: { name: string; id: string }): Promise<unknown>;\n cancelAll?(input: { name: string }): Promise<{ cancelled: number | null }>;\n moveStop?(input: {\n name: string;\n side: Side;\n stopId: string;\n triggerPrice: string;\n size: string;\n price?: string;\n }): Promise<{ name: string; id: string }>;\n createEntryWithProtection?(entry: unknown, protection: unknown): Promise<IXexOrder[]>;\n } {\n const source = createAccountXex(access.xex, access) as unknown as {\n perp(): ReturnType<TradingService['perpOf']>;\n };\n return source.perp();\n }\n\n /**\n * L'état natif d'une venue → {@link IAccountState}.\n *\n * Une lecture EXPLICITE par venue plutôt qu'un parcours à l'aveugle : les noms ne se devinent pas,\n * et prendre le premier champ qui ressemble à une équité produirait un chiffre faux sans que rien\n * ne le signale. `null` là où la venue ne publie pas la valeur — pas `0`, qui se lirait comme\n * « rien » alors que personne n'a rien dit.\n */\n private toAccountState(brut: Record<string, unknown>, xex: XgateEx): IAccountState {\n const texte = (valeur: unknown): string | null =>\n valeur === undefined || valeur === null ? null : String(valeur);\n const marge = brut.marginSummary as Record<string, unknown> | undefined;\n return {\n xex,\n equity:\n texte(marge?.accountValue) ??\n texte(brut.accountEquity) ??\n texte(brut.totalMarginBalance) ??\n '0',\n available:\n texte(brut.withdrawable) ?? texte(brut.availableToSpend) ?? texte(brut.availableBalance),\n marginUsed:\n texte(marge?.totalMarginUsed) ??\n texte(brut.totalMarginUsed) ??\n texte(brut.totalInitialMargin),\n maintenanceMargin:\n texte(brut.crossMaintenanceMarginUsed) ??\n texte(brut.crossMmr) ??\n texte(brut.totalMaintMargin),\n unrealizedPnl: texte(brut.totalUnrealizedProfit),\n xtras: brut,\n };\n }\n\n /**\n * Le statut de la venue, VALIDÉ — jamais casté. Une venue qui inventerait un statut rendrait sinon\n * une valeur absente de tous les `switch` en aval, et personne ne la rattraperait.\n */\n private toStatus(status: string): OrderStatus {\n return ORDER_STATUSES.includes(status as OrderStatus) === true\n ? (status as OrderStatus)\n : 'other';\n }\n\n private toOrder(order: IXexOrder, xex: XgateEx): IOrder {\n return {\n xex,\n symbolXex: order.name,\n kind: order.kind as 'perp' | 'spot',\n id: order.id,\n clientId: order.clientId,\n side: order.side as Side,\n type: order.type as OrderType,\n price: order.price,\n size: order.size,\n filled: order.filled,\n status: this.toStatus(order.status),\n tif: order.tif as TimeInForce | null,\n reduceOnly: order.reduceOnly,\n placedAt: order.placedAt,\n xtras: order.xtras,\n };\n }\n}\n\n/** Un taux de funding tel qu'un SDK de venue le rend. */\ninterface IXexFundingRate {\n name: string;\n rate: string;\n appliedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * Une venue attend ses dates en chaîne UTC `YYYY-MM-DD HH:MM:SS` — c'est la convention commune aux\n * huit SDK. On traduit ici, l'appelant d'XGate ne manipule que des `Date`.\n */\nfunction toXexDate(date: Date): string {\n return date.toISOString().slice(0, 19).replace('T', ' ');\n}\n\n/** Un ordre tel qu'un SDK de venue le rend : **clés longues et `Date`**. */\ninterface IXexOrder {\n name: string;\n kind: string;\n id: string;\n clientId: string | null;\n side: string;\n type: string;\n price: string | null;\n size: string;\n filled: string;\n status: string;\n tif: string | null;\n reduceOnly: boolean | null;\n placedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/** Une position telle qu'un SDK de venue la rend. */\ninterface IXexPosition {\n name: string;\n side: 'long' | 'short' | null;\n size: string;\n entryPrice: string | null;\n markPrice: string | null;\n unrealizedPnl: string | null;\n leverage: number | null;\n liquidationPrice: string | null;\n margin: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/** Une exécution telle qu'un SDK de venue la rend. */\ninterface IXexTrade {\n name: string;\n kind: string;\n id: string;\n orderId: string;\n side: string;\n price: string;\n size: string;\n fee: string;\n feeAsset: string | null;\n grossPnl: string | null;\n netPnl: string | null;\n maker: boolean | null;\n filledAt: Date;\n xtras?: Record<string, unknown>;\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { type IXexAccess, WALLET_XEXES, createAccountXex } from '../helpers/xex-registry';\nimport type {\n IBalance,\n IEquityPoint,\n IMovement,\n MovementKind,\n MovementStatus,\n} from '../interfaces/wallet.interfaces';\n\n/** Une venue et de quoi y lire un compte. */\nexport interface IWalletAccess extends IXexAccess {\n xex: XgateEx;\n}\n\n/**\n * LE PORTEFEUILLE, toutes venues confondues : soldes, mouvements d'argent, valeur du compte.\n *\n * **Les positions n'en font pas partie** — elles appartiennent au bloc trading, indissociable des\n * ordres. Un portefeuille répond à « qu'est-ce que j'ai et d'où ça vient » ; une position répond à\n * « qu'est-ce que je risque », et les deux ne se lisent pas au même moment.\n *\n * **Ce service ne calcule rien.** Il n'additionne pas les soldes entre venues, ne convertit aucun\n * montant, ne reconstitue aucune courbe d'équité là où la venue n'en publie pas. Chaque ligne porte\n * son `xex` : c'est à l'appelant de décider ce qui s'additionne, parce que lui seul sait si deux\n * USDC à deux endroits sont fongibles pour son usage.\n *\n * **Lire un portefeuille est PUBLIC sur les DEX** : l'adresse suffit, aucune signature n'est\n * produite. Seuls les CEX exigent une clé, même en lecture.\n */\n@Injectable()\nexport class WalletService {\n private readonly logger = new Logger(WalletService.name);\n\n /** Les venues dont le portefeuille est lisible aujourd'hui. */\n public venues(): readonly XgateEx[] {\n return WALLET_XEXES;\n }\n\n /**\n * Les soldes d'un ou plusieurs comptes.\n *\n * Même politique d'échec que les autres services : sur un seul accès, l'échec est relancé ; sur\n * plusieurs, la liste partielle garde sa valeur et l'incident est journalisé. Rendre `[]` en\n * silence ferait passer une venue injoignable pour un compte vide — et un compte vide, ça se\n * regarde autrement qu'une panne.\n */\n public async balances(...accesses: IWalletAccess[]): Promise<IBalance[]> {\n return this.collect(accesses, 'balances', async (access) => {\n const source = createAccountXex(access.xex, access);\n const balances = await source.account().getBalances();\n const lignes: IBalance[] = balances.map((balance) => ({\n xex: access.xex,\n asset: balance.asset,\n total: balance.total,\n available: balance.available,\n usdValue: balance.usdValue,\n xtras: balance.xtras,\n }));\n const collateral = await this.perpCollateral(source, access.xex);\n return collateral === null ? lignes : [...lignes, collateral];\n });\n }\n\n /**\n * LE COLLATÉRAL PERPÉTUEL, ajouté aux soldes comptant — sans lui, un portefeuille peut paraître VIDE.\n *\n * `getBalances` ne rend que le **comptant**, chez les huit venues : c'est leur convention, et la\n * documentation de chacune le dit. Or l'argent qui sert à trader n'y est pas. Sur un compte\n * pacifica dont tout le solde est en marge, `getBalances` rend `[]` — ce qui se lit « pas\n * d'argent » alors qu'il y a 499 USDC. Sur hyperliquid, le comptant s'affiche mais la marge\n * manque.\n *\n * La ligne est **marquée `scope: 'perp'`** dans `xtras` : elle s'additionne au comptant sans le\n * doubler, et un appelant qui ne veut que l'un des deux peut trancher.\n *\n * Le champ diffère par venue et n'est pas devinable — d'où la lecture explicite, venue par venue,\n * plutôt qu'un parcours à l'aveugle d'un objet `unknown`.\n */\n private async perpCollateral(\n source: ReturnType<typeof createAccountXex>,\n xex: XgateEx,\n ): Promise<IBalance | null> {\n // Les méthodes s'appellent SUR leur objet : les extraire (`const f = obj.f`) leur retire leur\n // `this`, et la façade échoue sur son propre client.\n const facade = source as unknown as {\n perp?: () => { getAccountInfo?: () => Promise<unknown> };\n };\n if (typeof facade.perp !== 'function') {\n return null;\n }\n const scope = facade.perp();\n if (typeof scope.getAccountInfo !== 'function') {\n return null;\n }\n const info = (await scope.getAccountInfo()) as {\n // pacifica : le solde du compte, en USD, hors comptant (cf. `spot_market_value`).\n balance?: string;\n availableToSpend?: string;\n // hyperliquid : l'équité du compte perp, comptant exclu.\n marginSummary?: { accountValue?: string };\n withdrawable?: string;\n };\n const total = info.balance ?? info.marginSummary?.accountValue;\n if (total === undefined || Number(total) === 0) {\n return null;\n }\n return {\n xex,\n asset: 'USDC',\n total,\n available: info.availableToSpend ?? info.withdrawable ?? null,\n usdValue: total,\n xtras: { scope: 'perp' },\n };\n }\n\n /**\n * Les mouvements d'argent, triés du plus ancien au plus récent, montants **signés**.\n *\n * **Lève si la venue ne les sert pas** plutôt que de rendre une liste vide : « cette venue\n * n'expose pas son historique » et « ce compte n'a jamais bougé » appellent des suites très\n * différentes.\n */\n public async movements(...accesses: IWalletAccess[]): Promise<IMovement[]> {\n const collected = await this.collect(accesses, 'movements', async (access) => {\n const account = createAccountXex(access.xex, access).account();\n if (account.getMovements === undefined) {\n throw new Error(`movements(${access.xex}) : cette venue n'expose pas ses mouvements.`);\n }\n const movements = await account.getMovements();\n return movements.map((movement) => ({\n xex: access.xex,\n kind: movement.kind as MovementKind,\n amount: movement.amount,\n fee: movement.fee,\n asset: movement.asset,\n occurredAt: movement.occurredAt,\n status: movement.status as MovementStatus,\n externalId: movement.externalId,\n xtras: movement.xtras,\n }));\n });\n return collected.sort((left, right) => left.occurredAt.getTime() - right.occurredAt.getTime());\n }\n\n /**\n * La valeur du compte dans le temps.\n *\n * **Seules hyperliquid et pacifica la publient**, et XGate ne la fabrique pas ailleurs :\n * reconstituer une courbe depuis les mouvements et les prix donnerait une valeur qu'aucune venue\n * n'a cotée, avec une méthode incomparable à celle des deux qui la publient. Lève sur une venue\n * qui n'en sert pas.\n */\n public async equityHistory(...accesses: IWalletAccess[]): Promise<IEquityPoint[]> {\n const collected = await this.collect(accesses, 'equityHistory', async (access) => {\n const account = createAccountXex(access.xex, access).account();\n if (account.getEquityHistory === undefined) {\n throw new Error(\n `equityHistory(${access.xex}) : cette venue ne publie pas la valeur de compte dans le temps.`,\n );\n }\n const points = await account.getEquityHistory();\n return points.map((point) => ({\n xex: access.xex,\n measuredAt: point.measuredAt,\n equity: point.equity,\n }));\n });\n return collected.sort((left, right) => left.measuredAt.getTime() - right.measuredAt.getTime());\n }\n\n /** Le motif commun aux trois lectures : un accès seul relance, plusieurs journalisent. */\n private async collect<T>(\n accesses: IWalletAccess[],\n capacite: string,\n lire: (access: IWalletAccess) => Promise<T[]>,\n ): Promise<T[]> {\n if (accesses.length === 0) {\n throw new Error(`${capacite}() : aucun accès fourni — précise au moins une venue.`);\n }\n const solo = accesses.length === 1;\n const collected = await Promise.all(\n accesses.map(async (access) => {\n try {\n return await lire(access);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`${capacite}(${access.xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { lisible } from '../helpers/socket-error';\nimport { type IXexSource, createPublicXex } from '../helpers/xex-registry';\nimport type { ICandle } from '../interfaces/candle.interfaces';\n\n/** Couper le flux. */\nexport type Unsubscribe = () => void;\n\n/** Le client temps réel d'une venue, tel que sa façade le rend. */\ntype WireTempsReel = ReturnType<IXexSource['ws']> & {\n onError?: ((error: unknown) => void) | null;\n};\n\n/**\n * LES BOUGIES EN TEMPS RÉEL — le présent.\n *\n * Séparé de `CandlesService` à dessein : rejouer un historique et suivre un flux ne se\n * consomment pas de la même façon, et n'ont pas les mêmes coûts. Une application qui ne fait que\n * du backfill n'a aucune socket à ouvrir.\n */\n@Injectable()\nexport class WsCandlesService {\n private readonly logger = new Logger(WsCandlesService.name);\n\n /** Un client temps réel par venue — voir {@link wireOf}. La clé EST la venue. */\n private readonly wires = new Map<XgateEx, WireTempsReel>();\n\n /**\n * Souscrit aux bougies d'un marché chez une venue. Rend la fonction de désabonnement.\n *\n * Le handler reçoit **une bougie à la fois**, au format unifié — c'est la forme commune aux neuf\n * SDK. La bougie en cours est repoussée à chaque mise à jour tant qu'elle n'est pas close ;\n * `closedAt` dit jusqu'où elle porte.\n *\n * **Attention à extended** : sur un intervalle qu'elle ne connaît pas, sa façade rend un\n * désabonnement vide au lieu de lever. L'appelant se croit abonné et n'entend jamais rien — un\n * silence indiscernable d'un marché sans échange. C'est un défaut du SDK, pas d'XGate, et il est\n * dans la file des corrections.\n */\n public subscribe(\n xex: XgateEx,\n query: { symbol: string; interval: Timeframe },\n handler: (candle: ICandle) => void,\n ): Unsubscribe {\n const quote = this.quoteOf(query.symbol);\n this.logger.log(`souscription bougies ${query.symbol} ${query.interval} sur ${xex}`);\n return this.wireOf(xex).subscribeCandles(\n { name: query.symbol, interval: query.interval },\n (wire) => {\n handler(toCandle(wire, xex, query.interval, quote));\n },\n );\n }\n\n /**\n * Souscrit au MÊME marché chez plusieurs venues, avec un seul handler.\n *\n * Chaque bougie porte son `xex` : l'appelant sait toujours qui parle. La fonction rendue coupe\n * tous les flux d'un coup — sinon il faudrait en garder N et n'en oublier aucun.\n */\n public subscribeAll(\n xexes: XgateEx[],\n query: { symbol: string; interval: Timeframe },\n handler: (candle: ICandle) => void,\n ): Unsubscribe {\n if (xexes.length === 0) {\n throw new Error('subscribeAll() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const stops: Unsubscribe[] = [];\n for (const xex of xexes) {\n try {\n stops.push(this.subscribe(xex, query, handler));\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`souscription ${xex} refusée, venue ignorée : ${message}`);\n }\n }\n return () => {\n for (const stop of stops) {\n stop();\n }\n };\n }\n\n /**\n * Le client temps réel d'une venue — **créé une seule fois, donc UNE SOCKET PAR VENUE**.\n *\n * C'est la règle que `CandlesStreamService` énonce déjà et que ce service violait : chaque\n * `subscribe()` appelait `createPublicXex(xex)`, qui construit une façade NEUVE. Or la façade\n * mémoïse son client temps réel (`unifiedWs()`), et c'est lui qui porte la socket : une façade\n * par souscription, c'est une socket par souscription.\n *\n * Mesuré le 2026-08-13 sur Blips : 1 104 souscriptions ont ouvert **1 104 sockets** sur sept\n * venues, quand hyperliquid n'en accepte que 10 par IP. Les descripteurs s'épuisent, les venues\n * refusent, et chaque connexion refusée rejetait une promesse que personne n'attendait — le\n * process entier mourait deux minutes après l'ouverture des flux. Ici : **sept sockets**.\n *\n * Rien à fermer explicitement : le ref-count vit dans les SDK (`refs += 1` / `release()`), donc\n * la socket se ferme d'elle-même au dernier désabonnement de la venue, et se rouvre au suivant.\n */\n private wireOf(xex: XgateEx): WireTempsReel {\n let wire = this.wires.get(xex);\n if (wire === undefined) {\n wire = createPublicXex(xex).ws() as WireTempsReel;\n // Le crochet d'erreur du SDK : sans lui, une socket qui casse ne dit rien à personne — les\n // SDK n'écrivent jamais sur la sortie standard, ils ne font que notifier. On le branche donc\n // au moment même où le client naît, sinon un incident survenu avant reste muet.\n if ('onError' in wire) {\n wire.onError = (error: unknown) => {\n this.logger.error(`${xex} : erreur de flux — ${lisible(error)}`);\n };\n }\n this.wires.set(xex, wire);\n }\n return wire;\n }\n\n /** Voir `CandlesService.quoteOf` : la cotation se déduit du symbole natif, faute d'être publiée. */\n private quoteOf(symbolXex: string): string {\n const parts = symbolXex.split('-');\n const last = parts[parts.length - 1];\n return parts.length > 1 && last !== undefined ? last : '';\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { createAccountXex } from '../helpers/xex-registry';\nimport { ORDER_STATUSES } from '../interfaces/trading.interfaces';\nimport type { IOrder, IPublicTrade, ITrade, Side } from '../interfaces/trading.interfaces';\nimport type { ITradingAccess } from './trading.service';\nimport type { Unsubscribe } from './ws-candles.service';\n\n/** Un ordre tel qu'un SDK de venue le diffuse. */\ninterface IXexOrderWire {\n name: string;\n kind: string;\n id: string;\n clientId: string | null;\n side: string;\n type: string;\n price: string | null;\n size: string;\n filled: string;\n status: string;\n tif: string | null;\n reduceOnly: boolean | null;\n placedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/** Une transaction publique telle qu'un SDK de venue la diffuse. */\ninterface IXexPublicTrade {\n name: string;\n price: string;\n size: string;\n side: string;\n tradedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/** Ce qu'un SDK de venue rend sur son flux d'exécutions : **clés longues et `Date`**. */\ninterface IXexUserTrade {\n name: string;\n kind: string;\n id: string;\n orderId: string;\n side: string;\n price: string;\n size: string;\n fee: string;\n feeAsset: string | null;\n grossPnl: string | null;\n netPnl: string | null;\n maker: boolean | null;\n filledAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * LE FLUX DES EXÉCUTIONS DU COMPTE — savoir ce qui s'est rempli, à l'instant où ça se remplit.\n *\n * **C'est la seule façon de connaître son état exact.** Sans lui, on interroge en boucle et on\n * apprend un remplissage avec le retard du sondage : entre-temps, un take-profit partiel a pu\n * modifier la taille de la position, et toute décision prise sur l'ancienne valeur est fausse.\n *\n * Le flux ne dit pas ce qu'on a **demandé** mais ce qui a été **obtenu** : un ordre annoncé `filled`\n * peut l'avoir été en plusieurs fois, à des prix différents. Chaque exécution porte son prix réel.\n *\n * **ON NE TRANSMET QUE CE QUI ARRIVE APRÈS L'OUVERTURE DU FLUX.** Les venues rejouent leur\n * historique à la souscription : mesuré le 2026-08-09 sur hyperliquid, 30 exécutions arrivent dans\n * les six premières secondes, **toutes antérieures** à l'abonnement, la plus ancienne de plus de\n * deux jours. Sans filtre, chaque reconnexion rejouerait deux jours de trades — et un consommateur\n * qui compte les remplissages compterait deux fois les mêmes.\n *\n * Le filtre se fait sur `filledAt` contre l'instant d'abonnement. Un flux dit le **présent** ;\n * l'historique se lit avec `TradingService.trades()`, qui est fait pour ça.\n *\n * **blofin est la seule venue qui ne l'expose pas** ; elle lève. Les sept autres le servent.\n */\n@Injectable()\nexport class WsTradesService {\n private readonly logger = new Logger(WsTradesService.name);\n\n /**\n * S'abonne aux exécutions d'un compte. Rend la fonction de désabonnement.\n *\n * Le handler reçoit **une exécution à la fois**, au format unifié, et **uniquement celles\n * survenues après l'abonnement** — le rejeu d'historique de la venue est écarté.\n */\n public subscribe(access: ITradingAccess, handler: (trade: ITrade) => void): Unsubscribe {\n const source = createAccountXex(access.xex, access) as unknown as {\n ws?: (label?: string) => {\n subscribeUserTrades?: (cb: (trade: IXexUserTrade) => void) => Unsubscribe;\n };\n };\n if (typeof source.ws !== 'function') {\n throw new Error(`userTrades(${access.xex}) : cette venue n'expose pas de temps réel.`);\n }\n const ws = source.ws();\n if (typeof ws.subscribeUserTrades !== 'function') {\n throw new Error(\n `userTrades(${access.xex}) : cette venue ne diffuse pas les exécutions du compte.`,\n );\n }\n // L'instant d'abonnement fait la frontière : tout ce qui porte un `filledAt` antérieur est du\n // rejeu, pas un événement. Relevé AVANT la souscription — la venue peut pousser dès l'ouverture.\n const depuis = Date.now();\n let ecartees = 0;\n this.logger.log(`souscription exécutions sur ${access.xex}`);\n return ws.subscribeUserTrades((trade) => {\n if (trade.filledAt.getTime() < depuis) {\n ecartees += 1;\n if (ecartees === 1) {\n this.logger.log(`${access.xex} : rejeu d'historique écarté (antérieur à l'abonnement).`);\n }\n return;\n }\n handler(this.toTrade(trade, access.xex));\n });\n }\n\n /**\n * S'abonne aux exécutions de PLUSIEURS comptes, avec un seul handler.\n *\n * Chaque exécution porte son `xex` : l'appelant sait toujours de quelle venue elle vient. La\n * fonction rendue coupe tous les flux d'un coup — sinon il faudrait en garder N et n'en oublier\n * aucun.\n *\n * Une venue qui refuse est journalisée et ignorée : le reste des comptes doit continuer d'être\n * suivi. Sur un seul accès, l'échec est celui de l'appel.\n */\n public subscribeAll(accesses: ITradingAccess[], handler: (trade: ITrade) => void): Unsubscribe {\n if (accesses.length === 0) {\n throw new Error('userTrades() : aucun compte demandé — précise au moins un accès.');\n }\n const solo = accesses.length === 1;\n const stops: Unsubscribe[] = [];\n for (const access of accesses) {\n try {\n stops.push(this.subscribe(access, handler));\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`souscription ${access.xex} refusée, venue ignorée : ${message}`);\n }\n }\n return () => {\n for (const stop of stops) {\n try {\n stop();\n } catch {\n // un désabonnement qui échoue ne doit pas empêcher les autres de se fermer\n }\n }\n };\n }\n\n /**\n * S'abonne aux ORDRES du compte — leur naissance, leur remplissage, leur mort.\n *\n * À distinguer des exécutions : un ordre est une **intention** dont on suit le cycle de vie\n * (`open` → `partiallyFilled` → `filled`, ou `canceled`), une exécution est un **fait** ponctuel.\n * Pour savoir qu'un stop vient de se déclencher, c'est ici qu'il faut écouter ; pour savoir à quel\n * prix, c'est {@link subscribe}.\n *\n * **Même règle que pour les exécutions** : seuls les ordres postérieurs à l'abonnement sont\n * transmis, le rejeu d'historique est écarté.\n */\n public subscribeOrders(access: ITradingAccess, handler: (order: IOrder) => void): Unsubscribe {\n const ws = this.wsOf(access);\n if (typeof ws.subscribeOrders !== 'function') {\n throw new Error(`orders(${access.xex}) : cette venue ne diffuse pas ses ordres.`);\n }\n const depuis = Date.now();\n this.logger.log(`souscription ordres sur ${access.xex}`);\n return ws.subscribeOrders((order) => {\n if (order.placedAt.getTime() >= depuis) {\n handler(this.toOrder(order, access.xex));\n }\n });\n }\n\n /**\n * S'abonne aux TRANSACTIONS PUBLIQUES d'un marché — celles de tout le monde, pas les siennes.\n *\n * C'est le flux qui dit ce qui se négocie réellement : prix, taille, sens agresseur. Il sert à\n * mesurer l'activité d'une paire, pas à suivre son compte — pour cela, {@link subscribe}.\n *\n * **Aucun filtre temporel ici** : un trade public est daté de son exécution et arrive en direct,\n * il n'y a pas d'historique rejoué à écarter.\n */\n public subscribePublicTrades(\n access: ITradingAccess,\n symbolXex: string,\n handler: (trade: IPublicTrade) => void,\n ): Unsubscribe {\n const ws = this.wsOf(access);\n if (typeof ws.subscribeTrades !== 'function') {\n throw new Error(`publicTrades(${access.xex}) : cette venue ne diffuse pas les transactions.`);\n }\n this.logger.log(`souscription transactions ${symbolXex} sur ${access.xex}`);\n return ws.subscribeTrades({ name: symbolXex }, (trade) => {\n handler({\n xex: access.xex,\n symbolXex: trade.name,\n price: trade.price,\n size: trade.size,\n side: trade.side as Side,\n tradedAt: trade.tradedAt,\n xtras: trade.xtras,\n });\n });\n }\n\n /** Le client temps réel d'un compte, avec ses souscriptions signées. */\n private wsOf(access: ITradingAccess): {\n subscribeUserTrades?: (cb: (trade: IXexUserTrade) => void) => Unsubscribe;\n subscribeOrders?: (cb: (order: IXexOrderWire) => void) => Unsubscribe;\n subscribeTrades?: (\n query: { name: string },\n cb: (trade: IXexPublicTrade) => void,\n ) => Unsubscribe;\n } {\n const source = createAccountXex(access.xex, access) as unknown as {\n ws?: (label?: string) => ReturnType<WsTradesService['wsOf']>;\n };\n if (typeof source.ws !== 'function') {\n throw new Error(`${access.xex} : cette venue n'expose pas de temps réel.`);\n }\n return source.ws();\n }\n\n private toOrder(order: IXexOrderWire, xex: XgateEx): IOrder {\n return {\n xex,\n symbolXex: order.name,\n kind: order.kind as 'perp' | 'spot',\n id: order.id,\n clientId: order.clientId,\n side: order.side as Side,\n type: order.type as IOrder['type'],\n price: order.price,\n size: order.size,\n filled: order.filled,\n status: ORDER_STATUSES.includes(order.status as IOrder['status'])\n ? (order.status as IOrder['status'])\n : 'other',\n tif: order.tif as IOrder['tif'],\n reduceOnly: order.reduceOnly,\n placedAt: order.placedAt,\n xtras: order.xtras,\n };\n }\n\n private toTrade(trade: IXexUserTrade, xex: XgateEx): ITrade {\n return {\n xex,\n symbolXex: trade.name,\n kind: trade.kind as 'perp' | 'spot',\n id: trade.id,\n orderId: trade.orderId,\n side: trade.side as Side,\n price: trade.price,\n size: trade.size,\n fee: trade.fee,\n feeAsset: trade.feeAsset,\n grossPnl: trade.grossPnl,\n netPnl: trade.netPnl,\n maker: trade.maker,\n filledAt: trade.filledAt,\n xtras: trade.xtras,\n };\n }\n}\n","import { Module } from '@nestjs/common';\nimport { CandlesStreamRegistry } from './services/candles-stream.service';\nimport { CandlesService } from './services/candles.service';\nimport { CatalogService } from './services/catalog.service';\nimport { PricesService } from './services/prices.service';\nimport { SpotCandlesService } from './services/spot-candles.service';\nimport { SpotCatalogService } from './services/spot-catalog.service';\nimport { SpotPricesService } from './services/spot-prices.service';\nimport { SpotWsCandlesService } from './services/spot-ws-candles.service';\nimport { TradingService } from './services/trading.service';\nimport { WalletService } from './services/wallet.service';\nimport { WsCandlesService } from './services/ws-candles.service';\nimport { WsTradesService } from './services/ws-trades.service';\n\n/**\n * Le module d'XGate : `imports: [XgateModule]`, puis on injecte le SEUL service dont on a besoin.\n *\n * Une capacité = un service, indépendant des autres. Le REST et le temps réel sont eux-mêmes\n * séparés (`CandlesService` / `WsCandlesService`) : rejouer le passé et suivre le présent ne se\n * consomment pas de la même façon, et une application qui ne fait que du backfill n'a aucune\n * socket à ouvrir.\n */\n@Module({\n providers: [\n CatalogService,\n CandlesService,\n CandlesStreamRegistry,\n WsCandlesService,\n WsTradesService,\n PricesService,\n SpotCatalogService,\n SpotCandlesService,\n SpotPricesService,\n SpotWsCandlesService,\n WalletService,\n ],\n exports: [\n CatalogService,\n CandlesService,\n CandlesStreamRegistry,\n WsCandlesService,\n WsTradesService,\n PricesService,\n SpotCatalogService,\n SpotCandlesService,\n SpotPricesService,\n SpotWsCandlesService,\n WalletService,\n ],\n})\nexport class XgateModule {}\n","import type { XgateEx } from '../enums/xgate-ex.enums';\n\n/**\n * COMBIEN DE BOUGIES UNE VENUE SERT PAR APPEL — uniquement ce qu'une documentation ou un SDK affirme.\n *\n * Le budget d'une venue se compte en APPELS, pas en bougies : dix fois plus de barres par appel, c'est dix fois\n * moins d'appels ET dix fois moins d'attente cumulée. C'est le seul levier qui décide de la durée d'un backfill.\n *\n * **binance** — « `limit` est plafonné à 1500 par la venue **sur les deux marchés** ; au-delà, elle refuse la\n * requête au lieu de tronquer » (`binance-sdk`, docblock de `getCandles`). Le plafond vaut donc pour le comptant\n * comme pour le perpétuel.\n *\n * **bybit** — `limit [1, 1000]`, défaut **200** (doc officielle v5, `/v5/market/kline`). C'est `category` qui\n * distingue comptant et perpétuel chez elle, pas la limite. ⚠️ SANS `limit` EXPLICITE, BYBIT EN REND 200, quelle\n * que soit la fenêtre demandée — mesuré le 2026-08-16 : 197 bougies par appel sur les six intervalles, les\n * huit cents manquantes n'étant jamais redemandées puisque le curseur avance de la fenêtre entière.\n *\n * **blofin** — 1440 ; la venue clampe EN SILENCE et le SDK lève au-delà (`blofin-sdk`, `getCandles`).\n *\n * **aster** — 1500 ; non documenté, API de forme binance.\n *\n * **pacifica** — **4 000**, et c'est SA DOCUMENTATION qui le dit : « `limit` : Capped at 4000; defaults to 4000\n * if not provided » (`docs.pacifica.fi/api-documentation/api/rest-api/markets/get-candle-data`). Le plafond joue\n * même sans `limit`, puisque 4 000 est aussi le défaut.\n *\n * ⚠️ AU-DELÀ, ELLE NE TRONQUE PAS : ELLE REND UN TABLEAU VIDE, avec `success: true`. Vérifié le 2026-08-24 sur\n * SOL en 15m — 4 000 barres demandées rendent 3 883 bougies, 4 010 en rendent ZÉRO. Une fenêtre trop large ne\n * coûte donc pas quelques barres perdues, elle coûte l'appel entier, en silence.\n *\n * ⚠️ CETTE ENTRÉE A VALU 500 PENDANT DES MOIS, sur la foi du commentaire ci-dessous qui affirmait que pacifica\n * « n'accepte aucun limit ». C'est faux : son endpoint le documente. Huit fois trop d'appels, à quatre secondes\n * de cadence chacun — mesuré le 2026-08-24, un backfill 15m sur ses 74 séries a coûté 1 026 appels et 72\n * minutes là où il en fallait huit fois moins.\n *\n * **hyperliquid** n'accepte, elle, aucun `limit` : son SDK n'envoie que `startTime`/`endTime`\n * (`get-candle-snapshot.ts`). Le nombre sert alors à dimensionner la FENÊTRE demandée, pas à remplir un\n * paramètre. La venue borne d'elle-même à ~5 000 bougies par (coin, intervalle) — et l'horizon que ça couvre\n * dépend de l'activité du coin, ce n'est donc pas une durée fixe.\n *\n * ⚠️ UNE VENUE ABSENTE DE CETTE TABLE N'A PAS DE PLAFOND DOCUMENTÉ — ce qui n'est pas « pas de plafond ». On ne\n * devine aucun chiffre : le jour où l'une d'elles s'en approche, on lit sa documentation et on l'ajoute.\n */\nexport const CANDLES_PER_CALL: Partial<Record<XgateEx, number>> = {\n binance: 1_500,\n bybit: 1_000,\n blofin: 1_440,\n aster: 1_500,\n hyperliquid: 5_000,\n pacifica: 4_000,\n} as Partial<Record<XgateEx, number>>;\n\n/**\n * Combien de bougies demander à une venue en un appel, ou `undefined` si personne ne l'a publié.\n *\n * ⚠️ LA CLÉ EST LA VENUE, PAS LA LIGNE DE CATALOGUE DE L'APPELANT. Un consommateur qui distingue le comptant du\n * perpétuel par deux identifiants à lui (`binance` / `binance-spot`) doit résoudre vers la venue avant de\n * demander : le plafond appartient à binance, pas à l'un de ses marchés. Interroger cette table avec un\n * identifiant maison rendait `undefined` et faisait retomber l'appelant sur une valeur de repli — trois fois trop\n * petite pour binance, deux fois pour bybit.\n */\nexport function candlesPerCallOf(xex: XgateEx): number | undefined {\n return CANDLES_PER_CALL[xex];\n}\n\n/**\n * LA CADENCE MINIMALE ENTRE DEUX APPELS, PAR VENUE — en millisecondes.\n *\n * ⚠️ INTERDIT DE DEVINER UN CHIFFRE ICI. Toute valeur se source dans la documentation de la venue, et la source\n * s'écrit dans ce commentaire. Un throttle inventé se paie en bannissement d'IP, pas en lenteur.\n *\n * Ces cadences sont un PLANCHER : si la venue répond plus lentement que son throttle, c'est sa latence qui\n * commande. Trois d'entre elles ont été corrigées PAR LA MESURE, jamais par confort — deux tours complets du\n * 2026-08-02 ayant compté les refus venue par venue :\n *\n * venue avant → après refus au 1er tour refus au 2e\n * pacifica 2 500 → 4 000 51 / 525 (9,7 %) 0\n * lighter 600 → 1 200 126 / 1 218 (10,3 %) 0\n * hyperliquid 200 → 500 17 / 1 239 (1,4 %) 39 / 708 (5,5 %) à 300 ms\n *\n * ⚠️ LEÇON DU CAS HYPERLIQUID : un premier réglage à 300 ms reposait sur un poids de requête SUPPOSÉ, et il a\n * EMPIRÉ la situation. Une cadence ne se déduit pas d'une hypothèse sur ce que coûte un appel — elle se dérive du\n * budget documenté, puis se vérifie au tour suivant.\n *\n * Détail des budgets :\n *\n * binance weight 2 par kline, budget IP 6 000 weight/min → ~3 000 req/min ; 150 ms est très en dessous.\n * 429 et 418 (ban d'IP) remontés par la venue.\n * bybit 600 req / 5 s en public → 150 ms reste prudent.\n * hyperliquid 1 200 weight/min par IP. À 387 ms réels on tirait 155 req/min en se faisant encore refouler :\n * la requête pèse donc ~8 weight et le budget est atteint vers 150 req/min. 500 ms garde 20 % de\n * marge, la même que celle qui a ramené lighter et pacifica à zéro refus.\n * blofin 500 req/min ET 1 500 req/5 min (docs.blofin.com). C'EST LA SECONDE QUI MORD : 300 req/min, deux\n * fois plus strict — et sa sanction est d'UNE HEURE contre cinq minutes. 250 ms tient 240 req/min.\n * pacifica 125 crédits / 60 s sur IP non identifiée, une kline étant un GET lourd à 3-12 crédits. La plus\n * contraignante de loin, et sa pénalité s'ALLONGE si on insiste. À 4 000 ms on tire 15 req/min,\n * soit 45 à 180 crédits — sous le budget pour tout coût jusqu'à 8.\n * lighter ~60 requêtes pondérées/min (palier Standard), les endpoints pesant ~300. Le budget donne\n * 1 000 ms tout juste ; 1 200 parce qu'un réglage COLLÉ au budget laisse un résidu de refus.\n * aster 2 400 weight/min, une kline au-delà de 1 000 bougies coûtant 10 → ~240 req/min.\n * paradex 1 500 req/min par IP.\n * extended API lente (2 à 4 s par requête observées) — le throttle ne mord jamais.\n */\nexport const REST_THROTTLE_MS: Partial<Record<XgateEx, number>> = {\n binance: 150,\n bybit: 150,\n hyperliquid: 500,\n extended: 200,\n paradex: 200,\n aster: 400,\n lighter: 1_200,\n blofin: 250,\n pacifica: 4_000,\n} as Partial<Record<XgateEx, number>>;\n\n/**\n * La cadence documentée d'une venue, ou `undefined` si personne ne l'a publiée.\n *\n * ⚠️ LE BUDGET EST CELUI DE LA VENUE, PAS DU MARCHÉ. Une IP qui interroge le comptant ET le perpétuel de binance\n * consomme UN SEUL budget : un appelant qui traite les deux en parallèle, chacun à cette cadence, tire deux fois\n * plus vite que ce que la table annonce. C'est à lui de sérialiser par venue.\n */\nexport function restThrottleOf(xex: XgateEx): number | undefined {\n return REST_THROTTLE_MS[xex];\n}\n\n/**\n * TOUT L'HISTORIQUE QU'UNE VENUE ACCEPTE DE SERVIR, en bougies par (marché, intervalle).\n *\n * ⚠️ CE N'EST PAS `CANDLES_PER_CALL`, ET LES CONFONDRE FAIT LIRE UN BACKFILL DE TRAVERS. Le premier plafond dit\n * combien de bougies tiennent dans UN appel : binance en rend 1 500 à la fois et sert pourtant quarante-quatre\n * mois, parce qu'on pagine. Celui-ci dit combien la venue en garde EN TOUT — au-delà, aucune pagination ne sert à\n * rien, elle répond une liste vide.\n *\n * **hyperliquid** : « Only the most recent 5000 candles are available » (documentation officielle,\n * `info` endpoint, `candleSnapshot`). La fenêtre GLISSE : ce qui sort des 5 000 dernières devient définitivement\n * hors de portée. Sondé le 2026-08-16 sur dix symboles au 2024-09-14 en 15m — POL rend ~500 barres (coin peu\n * traité, ses 5 000 bougies s'étalent sur deux ans), les neuf autres rendent ZÉRO.\n *\n * L'horizon couvert dépend donc de l'ACTIVITÉ du marché, jamais d'une durée fixe : en 1d et 1w la venue est\n * complète, le plafond ne mord que sous le jour.\n *\n * ⚠️ UNE VENUE ABSENTE DE CETTE TABLE SERT SON HISTORIQUE COMPLET par pagination, pour autant qu'on sache. Ce\n * n'est pas une valeur par défaut confortable : c'est ce qui a été constaté. Le jour où l'une d'elles s'arrête,\n * on lit sa documentation et on l'ajoute — on ne devine aucun chiffre ici.\n */\nexport const CANDLES_MAX_HISTORY: Partial<Record<XgateEx, number>> = {\n hyperliquid: 5_000,\n} as Partial<Record<XgateEx, number>>;\n\n/**\n * Combien de bougies une venue garde au total, ou `undefined` quand elle sert tout son historique.\n *\n * ⚠️ LA CLÉ EST LA VENUE, PAS LA LIGNE DE CATALOGUE DE L'APPELANT — même règle que `candlesPerCallOf` : un\n * consommateur qui sépare le comptant du perpétuel (`binance` / `binance-spot`) résout vers la venue avant de\n * demander, sinon il lit `undefined` et croit à un historique complet.\n */\nexport function candlesMaxHistoryOf(xex: XgateEx): number | undefined {\n return CANDLES_MAX_HISTORY[xex];\n}\n"]}