@blackcube/xgate-sdk 0.25.5 → 0.56.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/index.cjs +166 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -2
- package/dist/index.d.ts +84 -2
- package/dist/index.js +166 -5
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.d.cts
CHANGED
|
@@ -39,6 +39,37 @@ declare enum XgateEx {
|
|
|
39
39
|
/** Toutes les venues connues, dans l'ordre de déclaration. */
|
|
40
40
|
declare const XGATE_EXCHANGES: readonly XgateEx[];
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* CE QUE COTE UNE PAIRE — de la crypto, ou autre chose.
|
|
44
|
+
*
|
|
45
|
+
* Les venues perpétuelles ne listent plus seulement des cryptos : bybit publie 164 actions, 43 ETF et
|
|
46
|
+
* quatre matières premières, binance ouvre des `TRADIFI_PERPETUAL` sur NVDA et l'or, extended range
|
|
47
|
+
* ses actions en `RWA`. Le catalogue les rendait toutes indistinctes.
|
|
48
|
+
*
|
|
49
|
+
* ⚠️ CE N'EST PAS UNE COMMODITÉ DE CLASSEMENT. Le 2026-08-24, `PURR` cotait 11 $ chez bybit et
|
|
50
|
+
* 0,13 $ chez hyperliquid : le premier est une ACTION, le second le memecoin natif d'Hyperliquid.
|
|
51
|
+
* Même ticker, deux actifs — et un consommateur qui médiane « le prix de PURR » entre venues
|
|
52
|
+
* publiait un nombre qui n'était le prix d'aucun des deux. Le rapport n'était même pas constant
|
|
53
|
+
* (71 à 116 sur douze jours), ce qui excluait une échelle de cotation.
|
|
54
|
+
*
|
|
55
|
+
* ⚠️ ET CE N'EST PAS DÉDUCTIBLE DU TICKER. `SPX` est le memecoin SPX6900 chez binance, bybit et
|
|
56
|
+
* extended — qui le disent tous les trois (`underlyingSubType: ["Meme"]`, `subCategory: "Meme"`) —
|
|
57
|
+
* alors que l'indice S&P 500 s'appelle `SP500` chez pacifica. Une règle sur le nom aurait classé les
|
|
58
|
+
* deux à l'envers.
|
|
59
|
+
*/
|
|
60
|
+
type AssetClass = 'crypto' | 'stock' | 'commodity' | 'index' | 'etf' | 'forex';
|
|
61
|
+
/**
|
|
62
|
+
* LA CLASSE D'ACTIF D'UNE PAIRE, ou `undefined` quand la venue ne se prononce pas.
|
|
63
|
+
*
|
|
64
|
+
* ⚠️ `undefined` N'EST PAS « CRYPTO ». hyperliquid et pacifica cotent NVDA, TSLA, XAU et PLTR sans
|
|
65
|
+
* publier la moindre catégorie : les classer d'office en crypto reproduirait exactement l'erreur
|
|
66
|
+
* qu'on corrige ici. « On ne sait pas » est une information ; une valeur inventée n'en est pas une.
|
|
67
|
+
*
|
|
68
|
+
* Une catégorie inconnue rend `undefined` plutôt que de retomber sur `crypto` — le jour où une venue
|
|
69
|
+
* en ajoute une, on veut le voir, pas l'absorber.
|
|
70
|
+
*/
|
|
71
|
+
declare function assetClassOf(xex: XgateEx, xtras?: Record<string, unknown>): AssetClass | undefined;
|
|
72
|
+
|
|
42
73
|
/** Type de marché. */
|
|
43
74
|
type MarketKind = 'perp' | 'spot';
|
|
44
75
|
/**
|
|
@@ -77,6 +108,18 @@ interface IPair {
|
|
|
77
108
|
* ramène toutes les venues à la même unité.
|
|
78
109
|
*/
|
|
79
110
|
multiplier: number;
|
|
111
|
+
/**
|
|
112
|
+
* CE QUE LA PAIRE COTE — `undefined` quand la venue ne le dit pas.
|
|
113
|
+
*
|
|
114
|
+
* Les venues perpétuelles ne listent plus que de la crypto : bybit publie 164 actions, 43 ETF et
|
|
115
|
+
* quatre matières premières, binance ouvre des `TRADIFI_PERPETUAL` sur NVDA et l'or. Sans ce
|
|
116
|
+
* champ, un consommateur mélange deux actifs qui partagent un ticker — `PURR` valait 11 $ chez
|
|
117
|
+
* bybit (une ACTION) et 0,13 $ chez hyperliquid (le memecoin) le 2026-08-24.
|
|
118
|
+
*
|
|
119
|
+
* ⚠️ `undefined` NE VEUT PAS DIRE « crypto ». hyperliquid et pacifica cotent NVDA, TSLA et XAU
|
|
120
|
+
* sans publier de catégorie : les supposer crypto referait l'erreur qu'on corrige.
|
|
121
|
+
*/
|
|
122
|
+
assetClass?: AssetClass;
|
|
80
123
|
/**
|
|
81
124
|
* ÉCHÉANCE du contrat. `undefined` pour un perpétuel — l'immense majorité.
|
|
82
125
|
*
|
|
@@ -1447,7 +1490,11 @@ interface IEntryWithProtection {
|
|
|
1447
1490
|
direction: Direction;
|
|
1448
1491
|
/** Taille à ouvrir, **en unités de base**. */
|
|
1449
1492
|
size: number;
|
|
1450
|
-
/**
|
|
1493
|
+
/**
|
|
1494
|
+
* Prix d'entrée visé. Sert de référence aux pourcentages, et de prix limite — ARRONDI à la grille
|
|
1495
|
+
* de la paire par XGate (`tickSize`, sinon la règle hyperliquid), comme les protections. Jusqu'en
|
|
1496
|
+
* 0.56.0 il partait tel quel, et pacifica refuse un prix hors tick.
|
|
1497
|
+
*/
|
|
1451
1498
|
entry: number;
|
|
1452
1499
|
/**
|
|
1453
1500
|
* LE STOP — en **écart** au prix d'entrée ou en **prix absolu**, jamais les deux.
|
|
@@ -1521,8 +1568,43 @@ declare class TradingService {
|
|
|
1521
1568
|
* Chaque venue applique son mécanisme natif — hyperliquid groupe les enfants sous l'entrée et les
|
|
1522
1569
|
* annule lui-même si elle rate, pacifica les embarque dans l'ordre, aster envoie un lot de
|
|
1523
1570
|
* conditionnels. Le SDK de la venue s'en charge ; xgate fournit des niveaux justes.
|
|
1571
|
+
*
|
|
1572
|
+
* **LE PRIX D'ENTRÉE EST ARRONDI À LA GRILLE DE LA PAIRE, comme les protections.** Jusqu'en 0.56.0 il
|
|
1573
|
+
* partait tel quel (`String(input.entry)`) : les protections étaient justes, l'entrée non — et
|
|
1574
|
+
* pacifica l'envoie au wire sans le reformater (hyperliquid, lui, le reformate). Mesuré le
|
|
1575
|
+
* 2026-09-12 en rejouant 171 ouvertures : 79 entrées hors grille (`13.7421` sur un marché à cinq
|
|
1576
|
+
* chiffres significatifs, `75.885` sur un tick de 0,01). Les niveaux en pourcentage se calculent
|
|
1577
|
+
* depuis l'entrée ARRONDIE — celle qui sera réellement posée.
|
|
1578
|
+
*
|
|
1579
|
+
* **LES CIBLES QUE LA VENUE N'EMBARQUE PAS SONT POSÉES ICI.** Pacifica n'embarque qu'un take-profit
|
|
1580
|
+
* dans l'ordre d'entrée et rend les suivants par `pendingTps()` — « à appeler une fois le fill
|
|
1581
|
+
* constaté ». Avec une entrée immédiate (`ioc`, `fok`), le fill est constaté tout de suite : on
|
|
1582
|
+
* relit la position et on pose ce qui manque, en ordres déclenchés reduce-only. Avec une entrée
|
|
1583
|
+
* qui peut rester au carnet (`gtc`, `alo`), rien ne peut être posé avant le fill : l'appelant
|
|
1584
|
+
* complète par {@link completeProtection} quand il le constate. Jusqu'en 0.56.0 ces cibles n'étaient
|
|
1585
|
+
* posées par personne : une position tide à trois paliers sur pacifica partait sans sa dernière.
|
|
1524
1586
|
*/
|
|
1525
1587
|
openWithProtection(access: ITradingAccess, input: IEntryWithProtection): Promise<IOrder[]>;
|
|
1588
|
+
/**
|
|
1589
|
+
* POSE LES CIBLES QUE LA VENUE N'A PAS EMBARQUÉES, une fois l'entrée remplie.
|
|
1590
|
+
*
|
|
1591
|
+
* Le pendant de {@link openWithProtection} pour une entrée qui pouvait rester au carnet (`gtc`,
|
|
1592
|
+
* `alo`) : l'appelant constate le fill — une position sur la paire — et complète. Les niveaux se
|
|
1593
|
+
* recalculent depuis la même consigne, donc les mêmes prix ; la venue qui embarque toutes ses
|
|
1594
|
+
* cibles (hyperliquid, aster) n'a rien en attente et rien n'est posé. Sans position, rien non plus :
|
|
1595
|
+
* un ordre reduce-only sans position serait refusé ou annulé par la venue.
|
|
1596
|
+
*
|
|
1597
|
+
* ⚠️ NON IDEMPOTENT : appelé deux fois après le même fill, il pose les cibles deux fois. L'appelant
|
|
1598
|
+
* le fait UNE fois, quand sa relecture montre la position.
|
|
1599
|
+
*/
|
|
1600
|
+
completeProtection(access: ITradingAccess, input: IEntryWithProtection): Promise<IOrder[]>;
|
|
1601
|
+
/** Ce que la venue rend comme cibles non embarquées — vide chez celles qui embarquent tout. */
|
|
1602
|
+
private ciblesEnAttente;
|
|
1603
|
+
/**
|
|
1604
|
+
* Les cibles en attente, posées une par une en ordres déclenchés reduce-only — le même geste
|
|
1605
|
+
* qu'aster fait pour toutes les siennes. Rien n'est posé sans position sur la paire.
|
|
1606
|
+
*/
|
|
1607
|
+
private poserCiblesEnAttente;
|
|
1526
1608
|
/**
|
|
1527
1609
|
* CE QUE LA VENUE A RÉELLEMENT FAIT, après une réponse perdue.
|
|
1528
1610
|
*
|
|
@@ -1857,4 +1939,4 @@ declare function expiryOf(xtras?: Record<string, unknown>): Date | undefined;
|
|
|
1857
1939
|
*/
|
|
1858
1940
|
declare function isDated(xtras?: Record<string, unknown>): boolean;
|
|
1859
1941
|
|
|
1860
|
-
export { CANDLES_MAX_HISTORY, CANDLES_PER_CALL, CandlesService, CandlesStreamRegistry, CandlesStreamService, CatalogService, type Direction, type IAccountState, type IBalance, type ICandle, type ICandlesQuery, type IEntryWithProtection, type IEquityPoint, type IFundingPaid, type IMoveStop, type IMovement, type IOrder, type IPair, type IPosition, type IPrice, type IProtection, type IProtectionInput, type IProtectionLeg, type IPublicTrade, type ITarget, type ITrade, type ITradingAccess, type IWalletAccess, type IXexAccess, type IXexSpotSource, type MarketKind, type MovementKind, type MovementStatus, ORDER_STATUSES, type OrderStatus, type OrderType, PricesService, REST_THROTTLE_MS, SPOT_XEXES, type Side, SpotCandlesService, SpotCatalogService, SpotPricesService, SpotWsCandlesService, TIMEFRAME_MINUTES, type TimeInForce, Timeframe, TradingService, type Unsubscribe, WALLET_XEXES, WS_SUBSCRIPTION_LIMITS, WalletService, WsCandlesService, WsTradesService, XGATE_EXCHANGES, XgateEx, XgateModule, buildLevels, buildProtection, candlesMaxHistoryOf, candlesPerCallOf, canonicalFromBase, canonicalFromXex, expiryOf, isDated, multiplierFromXex, restThrottleOf, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, subscriptionLimitOf, timeframeMs };
|
|
1942
|
+
export { type AssetClass, CANDLES_MAX_HISTORY, CANDLES_PER_CALL, CandlesService, CandlesStreamRegistry, CandlesStreamService, CatalogService, type Direction, type IAccountState, type IBalance, type ICandle, type ICandlesQuery, type IEntryWithProtection, type IEquityPoint, type IFundingPaid, type IMoveStop, type IMovement, type IOrder, type IPair, type IPosition, type IPrice, type IProtection, type IProtectionInput, type IProtectionLeg, type IPublicTrade, type ITarget, type ITrade, type ITradingAccess, type IWalletAccess, type IXexAccess, type IXexSpotSource, type MarketKind, type MovementKind, type MovementStatus, ORDER_STATUSES, type OrderStatus, type OrderType, PricesService, REST_THROTTLE_MS, SPOT_XEXES, type Side, SpotCandlesService, SpotCatalogService, SpotPricesService, SpotWsCandlesService, TIMEFRAME_MINUTES, type TimeInForce, Timeframe, TradingService, type Unsubscribe, WALLET_XEXES, WS_SUBSCRIPTION_LIMITS, WalletService, WsCandlesService, WsTradesService, XGATE_EXCHANGES, XgateEx, XgateModule, assetClassOf, buildLevels, buildProtection, candlesMaxHistoryOf, candlesPerCallOf, canonicalFromBase, canonicalFromXex, expiryOf, isDated, multiplierFromXex, restThrottleOf, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, subscriptionLimitOf, timeframeMs };
|
package/dist/index.d.ts
CHANGED
|
@@ -39,6 +39,37 @@ declare enum XgateEx {
|
|
|
39
39
|
/** Toutes les venues connues, dans l'ordre de déclaration. */
|
|
40
40
|
declare const XGATE_EXCHANGES: readonly XgateEx[];
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* CE QUE COTE UNE PAIRE — de la crypto, ou autre chose.
|
|
44
|
+
*
|
|
45
|
+
* Les venues perpétuelles ne listent plus seulement des cryptos : bybit publie 164 actions, 43 ETF et
|
|
46
|
+
* quatre matières premières, binance ouvre des `TRADIFI_PERPETUAL` sur NVDA et l'or, extended range
|
|
47
|
+
* ses actions en `RWA`. Le catalogue les rendait toutes indistinctes.
|
|
48
|
+
*
|
|
49
|
+
* ⚠️ CE N'EST PAS UNE COMMODITÉ DE CLASSEMENT. Le 2026-08-24, `PURR` cotait 11 $ chez bybit et
|
|
50
|
+
* 0,13 $ chez hyperliquid : le premier est une ACTION, le second le memecoin natif d'Hyperliquid.
|
|
51
|
+
* Même ticker, deux actifs — et un consommateur qui médiane « le prix de PURR » entre venues
|
|
52
|
+
* publiait un nombre qui n'était le prix d'aucun des deux. Le rapport n'était même pas constant
|
|
53
|
+
* (71 à 116 sur douze jours), ce qui excluait une échelle de cotation.
|
|
54
|
+
*
|
|
55
|
+
* ⚠️ ET CE N'EST PAS DÉDUCTIBLE DU TICKER. `SPX` est le memecoin SPX6900 chez binance, bybit et
|
|
56
|
+
* extended — qui le disent tous les trois (`underlyingSubType: ["Meme"]`, `subCategory: "Meme"`) —
|
|
57
|
+
* alors que l'indice S&P 500 s'appelle `SP500` chez pacifica. Une règle sur le nom aurait classé les
|
|
58
|
+
* deux à l'envers.
|
|
59
|
+
*/
|
|
60
|
+
type AssetClass = 'crypto' | 'stock' | 'commodity' | 'index' | 'etf' | 'forex';
|
|
61
|
+
/**
|
|
62
|
+
* LA CLASSE D'ACTIF D'UNE PAIRE, ou `undefined` quand la venue ne se prononce pas.
|
|
63
|
+
*
|
|
64
|
+
* ⚠️ `undefined` N'EST PAS « CRYPTO ». hyperliquid et pacifica cotent NVDA, TSLA, XAU et PLTR sans
|
|
65
|
+
* publier la moindre catégorie : les classer d'office en crypto reproduirait exactement l'erreur
|
|
66
|
+
* qu'on corrige ici. « On ne sait pas » est une information ; une valeur inventée n'en est pas une.
|
|
67
|
+
*
|
|
68
|
+
* Une catégorie inconnue rend `undefined` plutôt que de retomber sur `crypto` — le jour où une venue
|
|
69
|
+
* en ajoute une, on veut le voir, pas l'absorber.
|
|
70
|
+
*/
|
|
71
|
+
declare function assetClassOf(xex: XgateEx, xtras?: Record<string, unknown>): AssetClass | undefined;
|
|
72
|
+
|
|
42
73
|
/** Type de marché. */
|
|
43
74
|
type MarketKind = 'perp' | 'spot';
|
|
44
75
|
/**
|
|
@@ -77,6 +108,18 @@ interface IPair {
|
|
|
77
108
|
* ramène toutes les venues à la même unité.
|
|
78
109
|
*/
|
|
79
110
|
multiplier: number;
|
|
111
|
+
/**
|
|
112
|
+
* CE QUE LA PAIRE COTE — `undefined` quand la venue ne le dit pas.
|
|
113
|
+
*
|
|
114
|
+
* Les venues perpétuelles ne listent plus que de la crypto : bybit publie 164 actions, 43 ETF et
|
|
115
|
+
* quatre matières premières, binance ouvre des `TRADIFI_PERPETUAL` sur NVDA et l'or. Sans ce
|
|
116
|
+
* champ, un consommateur mélange deux actifs qui partagent un ticker — `PURR` valait 11 $ chez
|
|
117
|
+
* bybit (une ACTION) et 0,13 $ chez hyperliquid (le memecoin) le 2026-08-24.
|
|
118
|
+
*
|
|
119
|
+
* ⚠️ `undefined` NE VEUT PAS DIRE « crypto ». hyperliquid et pacifica cotent NVDA, TSLA et XAU
|
|
120
|
+
* sans publier de catégorie : les supposer crypto referait l'erreur qu'on corrige.
|
|
121
|
+
*/
|
|
122
|
+
assetClass?: AssetClass;
|
|
80
123
|
/**
|
|
81
124
|
* ÉCHÉANCE du contrat. `undefined` pour un perpétuel — l'immense majorité.
|
|
82
125
|
*
|
|
@@ -1447,7 +1490,11 @@ interface IEntryWithProtection {
|
|
|
1447
1490
|
direction: Direction;
|
|
1448
1491
|
/** Taille à ouvrir, **en unités de base**. */
|
|
1449
1492
|
size: number;
|
|
1450
|
-
/**
|
|
1493
|
+
/**
|
|
1494
|
+
* Prix d'entrée visé. Sert de référence aux pourcentages, et de prix limite — ARRONDI à la grille
|
|
1495
|
+
* de la paire par XGate (`tickSize`, sinon la règle hyperliquid), comme les protections. Jusqu'en
|
|
1496
|
+
* 0.56.0 il partait tel quel, et pacifica refuse un prix hors tick.
|
|
1497
|
+
*/
|
|
1451
1498
|
entry: number;
|
|
1452
1499
|
/**
|
|
1453
1500
|
* LE STOP — en **écart** au prix d'entrée ou en **prix absolu**, jamais les deux.
|
|
@@ -1521,8 +1568,43 @@ declare class TradingService {
|
|
|
1521
1568
|
* Chaque venue applique son mécanisme natif — hyperliquid groupe les enfants sous l'entrée et les
|
|
1522
1569
|
* annule lui-même si elle rate, pacifica les embarque dans l'ordre, aster envoie un lot de
|
|
1523
1570
|
* conditionnels. Le SDK de la venue s'en charge ; xgate fournit des niveaux justes.
|
|
1571
|
+
*
|
|
1572
|
+
* **LE PRIX D'ENTRÉE EST ARRONDI À LA GRILLE DE LA PAIRE, comme les protections.** Jusqu'en 0.56.0 il
|
|
1573
|
+
* partait tel quel (`String(input.entry)`) : les protections étaient justes, l'entrée non — et
|
|
1574
|
+
* pacifica l'envoie au wire sans le reformater (hyperliquid, lui, le reformate). Mesuré le
|
|
1575
|
+
* 2026-09-12 en rejouant 171 ouvertures : 79 entrées hors grille (`13.7421` sur un marché à cinq
|
|
1576
|
+
* chiffres significatifs, `75.885` sur un tick de 0,01). Les niveaux en pourcentage se calculent
|
|
1577
|
+
* depuis l'entrée ARRONDIE — celle qui sera réellement posée.
|
|
1578
|
+
*
|
|
1579
|
+
* **LES CIBLES QUE LA VENUE N'EMBARQUE PAS SONT POSÉES ICI.** Pacifica n'embarque qu'un take-profit
|
|
1580
|
+
* dans l'ordre d'entrée et rend les suivants par `pendingTps()` — « à appeler une fois le fill
|
|
1581
|
+
* constaté ». Avec une entrée immédiate (`ioc`, `fok`), le fill est constaté tout de suite : on
|
|
1582
|
+
* relit la position et on pose ce qui manque, en ordres déclenchés reduce-only. Avec une entrée
|
|
1583
|
+
* qui peut rester au carnet (`gtc`, `alo`), rien ne peut être posé avant le fill : l'appelant
|
|
1584
|
+
* complète par {@link completeProtection} quand il le constate. Jusqu'en 0.56.0 ces cibles n'étaient
|
|
1585
|
+
* posées par personne : une position tide à trois paliers sur pacifica partait sans sa dernière.
|
|
1524
1586
|
*/
|
|
1525
1587
|
openWithProtection(access: ITradingAccess, input: IEntryWithProtection): Promise<IOrder[]>;
|
|
1588
|
+
/**
|
|
1589
|
+
* POSE LES CIBLES QUE LA VENUE N'A PAS EMBARQUÉES, une fois l'entrée remplie.
|
|
1590
|
+
*
|
|
1591
|
+
* Le pendant de {@link openWithProtection} pour une entrée qui pouvait rester au carnet (`gtc`,
|
|
1592
|
+
* `alo`) : l'appelant constate le fill — une position sur la paire — et complète. Les niveaux se
|
|
1593
|
+
* recalculent depuis la même consigne, donc les mêmes prix ; la venue qui embarque toutes ses
|
|
1594
|
+
* cibles (hyperliquid, aster) n'a rien en attente et rien n'est posé. Sans position, rien non plus :
|
|
1595
|
+
* un ordre reduce-only sans position serait refusé ou annulé par la venue.
|
|
1596
|
+
*
|
|
1597
|
+
* ⚠️ NON IDEMPOTENT : appelé deux fois après le même fill, il pose les cibles deux fois. L'appelant
|
|
1598
|
+
* le fait UNE fois, quand sa relecture montre la position.
|
|
1599
|
+
*/
|
|
1600
|
+
completeProtection(access: ITradingAccess, input: IEntryWithProtection): Promise<IOrder[]>;
|
|
1601
|
+
/** Ce que la venue rend comme cibles non embarquées — vide chez celles qui embarquent tout. */
|
|
1602
|
+
private ciblesEnAttente;
|
|
1603
|
+
/**
|
|
1604
|
+
* Les cibles en attente, posées une par une en ordres déclenchés reduce-only — le même geste
|
|
1605
|
+
* qu'aster fait pour toutes les siennes. Rien n'est posé sans position sur la paire.
|
|
1606
|
+
*/
|
|
1607
|
+
private poserCiblesEnAttente;
|
|
1526
1608
|
/**
|
|
1527
1609
|
* CE QUE LA VENUE A RÉELLEMENT FAIT, après une réponse perdue.
|
|
1528
1610
|
*
|
|
@@ -1857,4 +1939,4 @@ declare function expiryOf(xtras?: Record<string, unknown>): Date | undefined;
|
|
|
1857
1939
|
*/
|
|
1858
1940
|
declare function isDated(xtras?: Record<string, unknown>): boolean;
|
|
1859
1941
|
|
|
1860
|
-
export { CANDLES_MAX_HISTORY, CANDLES_PER_CALL, CandlesService, CandlesStreamRegistry, CandlesStreamService, CatalogService, type Direction, type IAccountState, type IBalance, type ICandle, type ICandlesQuery, type IEntryWithProtection, type IEquityPoint, type IFundingPaid, type IMoveStop, type IMovement, type IOrder, type IPair, type IPosition, type IPrice, type IProtection, type IProtectionInput, type IProtectionLeg, type IPublicTrade, type ITarget, type ITrade, type ITradingAccess, type IWalletAccess, type IXexAccess, type IXexSpotSource, type MarketKind, type MovementKind, type MovementStatus, ORDER_STATUSES, type OrderStatus, type OrderType, PricesService, REST_THROTTLE_MS, SPOT_XEXES, type Side, SpotCandlesService, SpotCatalogService, SpotPricesService, SpotWsCandlesService, TIMEFRAME_MINUTES, type TimeInForce, Timeframe, TradingService, type Unsubscribe, WALLET_XEXES, WS_SUBSCRIPTION_LIMITS, WalletService, WsCandlesService, WsTradesService, XGATE_EXCHANGES, XgateEx, XgateModule, buildLevels, buildProtection, candlesMaxHistoryOf, candlesPerCallOf, canonicalFromBase, canonicalFromXex, expiryOf, isDated, multiplierFromXex, restThrottleOf, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, subscriptionLimitOf, timeframeMs };
|
|
1942
|
+
export { type AssetClass, CANDLES_MAX_HISTORY, CANDLES_PER_CALL, CandlesService, CandlesStreamRegistry, CandlesStreamService, CatalogService, type Direction, type IAccountState, type IBalance, type ICandle, type ICandlesQuery, type IEntryWithProtection, type IEquityPoint, type IFundingPaid, type IMoveStop, type IMovement, type IOrder, type IPair, type IPosition, type IPrice, type IProtection, type IProtectionInput, type IProtectionLeg, type IPublicTrade, type ITarget, type ITrade, type ITradingAccess, type IWalletAccess, type IXexAccess, type IXexSpotSource, type MarketKind, type MovementKind, type MovementStatus, ORDER_STATUSES, type OrderStatus, type OrderType, PricesService, REST_THROTTLE_MS, SPOT_XEXES, type Side, SpotCandlesService, SpotCatalogService, SpotPricesService, SpotWsCandlesService, TIMEFRAME_MINUTES, type TimeInForce, Timeframe, TradingService, type Unsubscribe, WALLET_XEXES, WS_SUBSCRIPTION_LIMITS, WalletService, WsCandlesService, WsTradesService, XGATE_EXCHANGES, XgateEx, XgateModule, assetClassOf, buildLevels, buildProtection, candlesMaxHistoryOf, candlesPerCallOf, canonicalFromBase, canonicalFromXex, expiryOf, isDated, multiplierFromXex, restThrottleOf, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, subscriptionLimitOf, timeframeMs };
|
package/dist/index.js
CHANGED
|
@@ -677,6 +677,57 @@ CandlesService = __decorateClass([
|
|
|
677
677
|
Injectable()
|
|
678
678
|
], CandlesService);
|
|
679
679
|
|
|
680
|
+
// src/helpers/asset-class.ts
|
|
681
|
+
var BINANCE = {
|
|
682
|
+
COIN: "crypto",
|
|
683
|
+
EQUITY: "stock",
|
|
684
|
+
CN_EQUITY: "stock",
|
|
685
|
+
HK_EQUITY: "stock",
|
|
686
|
+
KR_EQUITY: "stock",
|
|
687
|
+
PREMARKET: "stock",
|
|
688
|
+
COMMODITY: "commodity",
|
|
689
|
+
INDEX: "index"
|
|
690
|
+
};
|
|
691
|
+
var BYBIT = {
|
|
692
|
+
"": "crypto",
|
|
693
|
+
innovation: "crypto",
|
|
694
|
+
stock: "stock",
|
|
695
|
+
commodity: "commodity",
|
|
696
|
+
ETF: "etf"
|
|
697
|
+
};
|
|
698
|
+
var EXTENDED = {
|
|
699
|
+
Equity: "stock",
|
|
700
|
+
Commodity: "commodity",
|
|
701
|
+
"ETF/Index": "index",
|
|
702
|
+
FX: "forex",
|
|
703
|
+
"Pre-market": "stock"
|
|
704
|
+
};
|
|
705
|
+
function assetClassOf(xex, xtras) {
|
|
706
|
+
if (xtras === void 0) {
|
|
707
|
+
return void 0;
|
|
708
|
+
}
|
|
709
|
+
if (xex === "binance") {
|
|
710
|
+
const brut = xtras.underlyingType;
|
|
711
|
+
return typeof brut === "string" ? BINANCE[brut] : void 0;
|
|
712
|
+
}
|
|
713
|
+
if (xex === "bybit") {
|
|
714
|
+
const brut = xtras.symbolType;
|
|
715
|
+
return typeof brut === "string" ? BYBIT[brut] : void 0;
|
|
716
|
+
}
|
|
717
|
+
if (xex === "extended") {
|
|
718
|
+
const categorie = xtras.category;
|
|
719
|
+
if (typeof categorie !== "string") {
|
|
720
|
+
return void 0;
|
|
721
|
+
}
|
|
722
|
+
if (categorie !== "RWA") {
|
|
723
|
+
return "crypto";
|
|
724
|
+
}
|
|
725
|
+
const sous = xtras.subCategory;
|
|
726
|
+
return typeof sous === "string" ? EXTENDED[sous] : void 0;
|
|
727
|
+
}
|
|
728
|
+
return void 0;
|
|
729
|
+
}
|
|
730
|
+
|
|
680
731
|
// src/helpers/contract-expiry.ts
|
|
681
732
|
var DATED_CONTRACT_TYPES = /* @__PURE__ */ new Set([
|
|
682
733
|
// binance : les trimestriels (`BTCUSDT_260925`, `ETHUSDT_261225`).
|
|
@@ -759,6 +810,7 @@ var CatalogService = class {
|
|
|
759
810
|
xex,
|
|
760
811
|
kind: pair.kind,
|
|
761
812
|
multiplier: multiplierFromXex(pair.name),
|
|
813
|
+
assetClass: assetClassOf(xex, pair.xtras),
|
|
762
814
|
expiresAt: expiryOf(pair.xtras),
|
|
763
815
|
ref: pair.ref ?? null,
|
|
764
816
|
szDecimals: pair.szDecimals,
|
|
@@ -947,6 +999,7 @@ var SpotCatalogService = class {
|
|
|
947
999
|
// Le comptant porte les mêmes échelles que le perp : `SATSUSDT` (×1) et `1000SATSUSDT`
|
|
948
1000
|
// coexistent, et c'est justement entre eux que la comparaison serait fausse.
|
|
949
1001
|
multiplier: multiplierFromXex(pair.name),
|
|
1002
|
+
assetClass: assetClassOf(xex, pair.xtras),
|
|
950
1003
|
ref: pair.ref ?? null,
|
|
951
1004
|
szDecimals: pair.szDecimals,
|
|
952
1005
|
tickSize: pair.tickSize,
|
|
@@ -1203,6 +1256,21 @@ var TradingService = class {
|
|
|
1203
1256
|
* Chaque venue applique son mécanisme natif — hyperliquid groupe les enfants sous l'entrée et les
|
|
1204
1257
|
* annule lui-même si elle rate, pacifica les embarque dans l'ordre, aster envoie un lot de
|
|
1205
1258
|
* conditionnels. Le SDK de la venue s'en charge ; xgate fournit des niveaux justes.
|
|
1259
|
+
*
|
|
1260
|
+
* **LE PRIX D'ENTRÉE EST ARRONDI À LA GRILLE DE LA PAIRE, comme les protections.** Jusqu'en 0.56.0 il
|
|
1261
|
+
* partait tel quel (`String(input.entry)`) : les protections étaient justes, l'entrée non — et
|
|
1262
|
+
* pacifica l'envoie au wire sans le reformater (hyperliquid, lui, le reformate). Mesuré le
|
|
1263
|
+
* 2026-09-12 en rejouant 171 ouvertures : 79 entrées hors grille (`13.7421` sur un marché à cinq
|
|
1264
|
+
* chiffres significatifs, `75.885` sur un tick de 0,01). Les niveaux en pourcentage se calculent
|
|
1265
|
+
* depuis l'entrée ARRONDIE — celle qui sera réellement posée.
|
|
1266
|
+
*
|
|
1267
|
+
* **LES CIBLES QUE LA VENUE N'EMBARQUE PAS SONT POSÉES ICI.** Pacifica n'embarque qu'un take-profit
|
|
1268
|
+
* dans l'ordre d'entrée et rend les suivants par `pendingTps()` — « à appeler une fois le fill
|
|
1269
|
+
* constaté ». Avec une entrée immédiate (`ioc`, `fok`), le fill est constaté tout de suite : on
|
|
1270
|
+
* relit la position et on pose ce qui manque, en ordres déclenchés reduce-only. Avec une entrée
|
|
1271
|
+
* qui peut rester au carnet (`gtc`, `alo`), rien ne peut être posé avant le fill : l'appelant
|
|
1272
|
+
* complète par {@link completeProtection} quand il le constate. Jusqu'en 0.56.0 ces cibles n'étaient
|
|
1273
|
+
* posées par personne : une position tide à trois paliers sur pacifica partait sans sa dernière.
|
|
1206
1274
|
*/
|
|
1207
1275
|
async openWithProtection(access, input) {
|
|
1208
1276
|
if (VENUES_PROUVEES.includes(access.xex) === false) {
|
|
@@ -1210,8 +1278,9 @@ var TradingService = class {
|
|
|
1210
1278
|
`openWithProtection(${access.xex}) : l'ouverture prot\xE9g\xE9e n'a pas \xE9t\xE9 prouv\xE9e en r\xE9el sur cette venue. Seules ${VENUES_PROUVEES.join(", ")} le sont. Ouvrir ici reviendrait \xE0 parier qu'un chemin jamais exerc\xE9 pose bien le stop.`
|
|
1211
1279
|
);
|
|
1212
1280
|
}
|
|
1281
|
+
const entryPrice = roundPrice(input.entry, input.tickSize, input.lotSize);
|
|
1213
1282
|
const protection = buildProtection({
|
|
1214
|
-
entry:
|
|
1283
|
+
entry: entryPrice,
|
|
1215
1284
|
direction: input.direction,
|
|
1216
1285
|
size: input.size,
|
|
1217
1286
|
sl: input.sl,
|
|
@@ -1227,13 +1296,14 @@ var TradingService = class {
|
|
|
1227
1296
|
`openWithProtection(${access.xex}) : cette venue n'expose pas l'ouverture prot\xE9g\xE9e atomique.`
|
|
1228
1297
|
);
|
|
1229
1298
|
}
|
|
1299
|
+
const tif = input.tif ?? "ioc";
|
|
1230
1300
|
const entry = {
|
|
1231
1301
|
name: input.symbolXex,
|
|
1232
1302
|
side,
|
|
1233
1303
|
type: "limit",
|
|
1234
1304
|
size: String(input.size),
|
|
1235
|
-
price: String(
|
|
1236
|
-
tif
|
|
1305
|
+
price: String(entryPrice),
|
|
1306
|
+
tif,
|
|
1237
1307
|
reduceOnly: false,
|
|
1238
1308
|
clientId: input.clientId
|
|
1239
1309
|
};
|
|
@@ -1246,7 +1316,25 @@ var TradingService = class {
|
|
|
1246
1316
|
};
|
|
1247
1317
|
try {
|
|
1248
1318
|
const orders = await perp.createEntryWithProtection(entry, consigne);
|
|
1249
|
-
|
|
1319
|
+
const posees = orders.map((order) => this.toOrder(order, access.xex));
|
|
1320
|
+
const enAttente = this.ciblesEnAttente(perp, consigne);
|
|
1321
|
+
if (enAttente.length === 0) {
|
|
1322
|
+
return posees;
|
|
1323
|
+
}
|
|
1324
|
+
if (tif !== "ioc" && tif !== "fok") {
|
|
1325
|
+
this.logger.warn(
|
|
1326
|
+
`openWithProtection(${access.xex}/${input.symbolXex}) : ${enAttente.length} cible(s) que la venue n'embarque pas \u2014 l'entr\xE9e ${tif} peut rester au carnet, appeler completeProtection une fois remplie.`
|
|
1327
|
+
);
|
|
1328
|
+
return posees;
|
|
1329
|
+
}
|
|
1330
|
+
const complement = await this.poserCiblesEnAttente(
|
|
1331
|
+
access,
|
|
1332
|
+
perp,
|
|
1333
|
+
input.symbolXex,
|
|
1334
|
+
side,
|
|
1335
|
+
enAttente
|
|
1336
|
+
);
|
|
1337
|
+
return [...posees, ...complement];
|
|
1250
1338
|
} catch (error) {
|
|
1251
1339
|
const message = error instanceof Error ? error.message : String(error);
|
|
1252
1340
|
if (message.includes(ASTER_TIMEOUT) === false) {
|
|
@@ -1258,6 +1346,79 @@ var TradingService = class {
|
|
|
1258
1346
|
return await this.etatApres(access, input.symbolXex);
|
|
1259
1347
|
}
|
|
1260
1348
|
}
|
|
1349
|
+
/**
|
|
1350
|
+
* POSE LES CIBLES QUE LA VENUE N'A PAS EMBARQUÉES, une fois l'entrée remplie.
|
|
1351
|
+
*
|
|
1352
|
+
* Le pendant de {@link openWithProtection} pour une entrée qui pouvait rester au carnet (`gtc`,
|
|
1353
|
+
* `alo`) : l'appelant constate le fill — une position sur la paire — et complète. Les niveaux se
|
|
1354
|
+
* recalculent depuis la même consigne, donc les mêmes prix ; la venue qui embarque toutes ses
|
|
1355
|
+
* cibles (hyperliquid, aster) n'a rien en attente et rien n'est posé. Sans position, rien non plus :
|
|
1356
|
+
* un ordre reduce-only sans position serait refusé ou annulé par la venue.
|
|
1357
|
+
*
|
|
1358
|
+
* ⚠️ NON IDEMPOTENT : appelé deux fois après le même fill, il pose les cibles deux fois. L'appelant
|
|
1359
|
+
* le fait UNE fois, quand sa relecture montre la position.
|
|
1360
|
+
*/
|
|
1361
|
+
async completeProtection(access, input) {
|
|
1362
|
+
const entryPrice = roundPrice(input.entry, input.tickSize, input.lotSize);
|
|
1363
|
+
const protection = buildProtection({
|
|
1364
|
+
entry: entryPrice,
|
|
1365
|
+
direction: input.direction,
|
|
1366
|
+
size: input.size,
|
|
1367
|
+
sl: input.sl,
|
|
1368
|
+
tps: input.tps,
|
|
1369
|
+
tickSize: input.tickSize,
|
|
1370
|
+
lotSize: input.lotSize,
|
|
1371
|
+
slippagePct: input.slippagePct
|
|
1372
|
+
});
|
|
1373
|
+
const side = input.direction === "long" ? "buy" : "sell";
|
|
1374
|
+
const perp = this.perpOf(access);
|
|
1375
|
+
const enAttente = this.ciblesEnAttente(perp, {
|
|
1376
|
+
name: input.symbolXex,
|
|
1377
|
+
side,
|
|
1378
|
+
sl: protection.sl,
|
|
1379
|
+
tps: protection.tps,
|
|
1380
|
+
clientId: input.clientId
|
|
1381
|
+
});
|
|
1382
|
+
return enAttente.length === 0 ? [] : this.poserCiblesEnAttente(access, perp, input.symbolXex, side, enAttente);
|
|
1383
|
+
}
|
|
1384
|
+
/** Ce que la venue rend comme cibles non embarquées — vide chez celles qui embarquent tout. */
|
|
1385
|
+
ciblesEnAttente(perp, consigne) {
|
|
1386
|
+
return typeof perp.pendingTps === "function" ? perp.pendingTps(consigne) : [];
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Les cibles en attente, posées une par une en ordres déclenchés reduce-only — le même geste
|
|
1390
|
+
* qu'aster fait pour toutes les siennes. Rien n'est posé sans position sur la paire.
|
|
1391
|
+
*/
|
|
1392
|
+
async poserCiblesEnAttente(access, perp, symbolXex, side, enAttente) {
|
|
1393
|
+
const positions = await perp.getPositions({ name: symbolXex });
|
|
1394
|
+
const position = positions.find(
|
|
1395
|
+
(candidate) => candidate.name === symbolXex && Number(candidate.size) !== 0
|
|
1396
|
+
);
|
|
1397
|
+
if (position === void 0) {
|
|
1398
|
+
this.logger.log(
|
|
1399
|
+
`openWithProtection(${access.xex}/${symbolXex}) : pas de position, ${enAttente.length} cible(s) en attente non pos\xE9e(s).`
|
|
1400
|
+
);
|
|
1401
|
+
return [];
|
|
1402
|
+
}
|
|
1403
|
+
const exit = side === "buy" ? "sell" : "buy";
|
|
1404
|
+
const posees = [];
|
|
1405
|
+
for (const cible of enAttente) {
|
|
1406
|
+
const order = await perp.place({
|
|
1407
|
+
name: symbolXex,
|
|
1408
|
+
side: exit,
|
|
1409
|
+
type: "takeProfitMarket",
|
|
1410
|
+
size: cible.size,
|
|
1411
|
+
triggerPrice: cible.triggerPrice,
|
|
1412
|
+
price: cible.price,
|
|
1413
|
+
reduceOnly: true
|
|
1414
|
+
});
|
|
1415
|
+
posees.push(this.toOrder(order, access.xex));
|
|
1416
|
+
}
|
|
1417
|
+
this.logger.log(
|
|
1418
|
+
`openWithProtection(${access.xex}/${symbolXex}) : ${posees.length} cible(s) que la venue n'embarquait pas, pos\xE9e(s).`
|
|
1419
|
+
);
|
|
1420
|
+
return posees;
|
|
1421
|
+
}
|
|
1261
1422
|
/**
|
|
1262
1423
|
* CE QUE LA VENUE A RÉELLEMENT FAIT, après une réponse perdue.
|
|
1263
1424
|
*
|
|
@@ -2163,6 +2324,6 @@ function candlesMaxHistoryOf(xex) {
|
|
|
2163
2324
|
return CANDLES_MAX_HISTORY[xex];
|
|
2164
2325
|
}
|
|
2165
2326
|
|
|
2166
|
-
export { CANDLES_MAX_HISTORY, CANDLES_PER_CALL, CandlesService, CandlesStreamRegistry, CandlesStreamService, CatalogService, ORDER_STATUSES, PricesService, REST_THROTTLE_MS, SPOT_XEXES, SpotCandlesService, SpotCatalogService, SpotPricesService, SpotWsCandlesService, TIMEFRAME_MINUTES, Timeframe, TradingService, WALLET_XEXES, WS_SUBSCRIPTION_LIMITS, WalletService, WsCandlesService, WsTradesService, XGATE_EXCHANGES, XgateEx, XgateModule, buildLevels, buildProtection, candlesMaxHistoryOf, candlesPerCallOf, canonicalFromBase, canonicalFromXex, expiryOf, isDated, multiplierFromXex, restThrottleOf, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, subscriptionLimitOf, timeframeMs };
|
|
2327
|
+
export { CANDLES_MAX_HISTORY, CANDLES_PER_CALL, CandlesService, CandlesStreamRegistry, CandlesStreamService, CatalogService, ORDER_STATUSES, PricesService, REST_THROTTLE_MS, SPOT_XEXES, SpotCandlesService, SpotCatalogService, SpotPricesService, SpotWsCandlesService, TIMEFRAME_MINUTES, Timeframe, TradingService, WALLET_XEXES, WS_SUBSCRIPTION_LIMITS, WalletService, WsCandlesService, WsTradesService, XGATE_EXCHANGES, XgateEx, XgateModule, assetClassOf, buildLevels, buildProtection, candlesMaxHistoryOf, candlesPerCallOf, canonicalFromBase, canonicalFromXex, expiryOf, isDated, multiplierFromXex, restThrottleOf, roundPrice, roundToStep, servesNatively, servesSpot, sourceFor, subscriptionLimitOf, timeframeMs };
|
|
2167
2328
|
//# sourceMappingURL=index.js.map
|
|
2168
2329
|
//# sourceMappingURL=index.js.map
|