@syldel/hl-shared-types 0.0.16 → 0.0.17
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 +27 -0
- package/dist/format/index.d.ts +1 -0
- package/dist/format/index.js +17 -0
- package/dist/format/tick-and-lot.d.ts +89 -0
- package/dist/format/tick-and-lot.js +225 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +54 -50
package/README.md
CHANGED
|
@@ -16,6 +16,33 @@ Ce package centralise les définitions TypeScript pour l'écosystème **Hyperliq
|
|
|
16
16
|
* **Account** : États du compte, positions Perp et soldes Spot.
|
|
17
17
|
* **Market** : Métadonnées des actifs et résumés de marché.
|
|
18
18
|
* **Orders** : Définitions des ordres ouverts et historiques.
|
|
19
|
+
* **Format** : les règles de **tick et de lot** d'Hyperliquid — la seule logique exécutable
|
|
20
|
+
du package, et la seule qui décide ce qui part vers l'exchange.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## ⚠️ `format/` : du code, pas seulement des types
|
|
25
|
+
|
|
26
|
+
`formatPrice`, `formatSize`, `priceDecimals` et `snapPrice` appliquent les règles de
|
|
27
|
+
[tick and lot size](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size)
|
|
28
|
+
d'Hyperliquid. Elles vivent ici pour qu'il n'en existe **qu'une seule implémentation** :
|
|
29
|
+
le gateway les applique en sortie, et le bot doit pouvoir savoir *avant d'envoyer* ce qui
|
|
30
|
+
sera posé. Deux implémentations, c'est la garantie qu'un émetteur croira un jour avoir posé
|
|
31
|
+
autre chose que ce qui l'a été — ce qui s'est produit, et se paie sur un stop loss.
|
|
32
|
+
|
|
33
|
+
Trois conséquences pour qui touche à ce dossier :
|
|
34
|
+
|
|
35
|
+
* **aucune dépendance**, ici moins qu'ailleurs : le consommateur de ce code est le processus
|
|
36
|
+
qui signe les transactions ;
|
|
37
|
+
* **rien ne passe par `Number`** — tout le calcul se fait sur les chiffres écrits, en
|
|
38
|
+
`BigInt` ;
|
|
39
|
+
* **la conformité se prouve** : `test/tick-and-lot.spec.ts` rejoue *tous* les exemples de la
|
|
40
|
+
documentation, sourcés et datés. `npm run build` lance les tests avant de compiler — une
|
|
41
|
+
logique exécutable non testée ne se publie pas.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm test
|
|
45
|
+
```
|
|
19
46
|
|
|
20
47
|
---
|
|
21
48
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './tick-and-lot';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./tick-and-lot"), exports);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* PRIX ET TAILLES : LES RÈGLES DE TICK ET DE LOT D'HYPERLIQUID
|
|
4
|
+
*
|
|
5
|
+
* Source : https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size
|
|
6
|
+
* relevée le 2026-09-23. Deux règles, et une exception :
|
|
7
|
+
*
|
|
8
|
+
* - **taille** : tronquée à `szDecimals`, propre à chaque actif (réponse `meta`) ;
|
|
9
|
+
* - **prix** : au plus 5 chiffres significatifs, ET au plus
|
|
10
|
+
* `MAX_DECIMALS - szDecimals` décimales, `MAX_DECIMALS` valant 6 en perp et
|
|
11
|
+
* 8 en spot ;
|
|
12
|
+
* - **un prix entier est toujours accepté**, quel que soit son nombre de
|
|
13
|
+
* chiffres significatifs (`123456` est valide là où `12345.6` ne l'est pas).
|
|
14
|
+
*
|
|
15
|
+
* Ce module vit ici, et non dans le gateway ou le bot, pour qu'il n'en existe
|
|
16
|
+
* **qu'une seule implémentation** : un prix mal écrit est refusé par
|
|
17
|
+
* Hyperliquid, ou accepté sous une forme tronquée qui n'est plus celle qu'on
|
|
18
|
+
* voulait. Sur un stop loss, la différence entre les deux, c'est une position
|
|
19
|
+
* protégée et une position qui ne l'est pas. Deux implémentations, c'est la
|
|
20
|
+
* garantie qu'un émetteur croira un jour avoir posé autre chose que ce qui
|
|
21
|
+
* l'a été.
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ Un faux gateway de test, lui, doit garder sa **copie indépendante** : s'il
|
|
24
|
+
* importait ces fonctions, un défaut s'y annulerait des deux côtés et aucun
|
|
25
|
+
* test ne le verrait.
|
|
26
|
+
*
|
|
27
|
+
* Rien ne passe par `Number` : tout le calcul se fait sur les chiffres écrits,
|
|
28
|
+
* en `BigInt`. Un `parseFloat` réintroduirait exactement l'erreur d'arrondi que
|
|
29
|
+
* ces règles servent à éviter.
|
|
30
|
+
* ============================================================================
|
|
31
|
+
*/
|
|
32
|
+
/** Marché visé : les deux n'ont pas le même plafond de décimales. */
|
|
33
|
+
export type HLMarketType = 'perp' | 'spot';
|
|
34
|
+
/**
|
|
35
|
+
* Sens de l'arrondi. `truncate` va vers zéro — c'est ce que fait Hyperliquid,
|
|
36
|
+
* et le seul sens autorisé pour une taille. `up` et `down` vont vers +∞ et
|
|
37
|
+
* −∞, ce qui ne diffère de `truncate` que sur une valeur négative.
|
|
38
|
+
*/
|
|
39
|
+
export type RoundingMode = 'truncate' | 'up' | 'down' | 'nearest';
|
|
40
|
+
/** Levée plutôt que de rendre une valeur que l'exchange refuserait. */
|
|
41
|
+
export declare class TickAndLotError extends Error {
|
|
42
|
+
constructor(message: string);
|
|
43
|
+
}
|
|
44
|
+
/** `MAX_DECIMALS` de la documentation, par type de marché. */
|
|
45
|
+
export declare const MAX_PRICE_DECIMALS: Readonly<Record<HLMarketType, number>>;
|
|
46
|
+
/** Le plafond de chiffres significatifs d'un prix non entier. */
|
|
47
|
+
export declare const MAX_PRICE_SIGNIFICANT_DIGITS = 5;
|
|
48
|
+
/**
|
|
49
|
+
* Arrondit une valeur à `decimals` décimales, dans le sens demandé, sans
|
|
50
|
+
* jamais passer par un flottant.
|
|
51
|
+
*/
|
|
52
|
+
export declare function roundDecimal(value: string | number, decimals: number, mode?: RoundingMode): string;
|
|
53
|
+
/**
|
|
54
|
+
* Le nombre de décimales que ce prix a le droit d'avoir — les deux règles
|
|
55
|
+
* réunies en un seul nombre.
|
|
56
|
+
*
|
|
57
|
+
* Le plancher à zéro **est** l'exception des entiers : quand les chiffres
|
|
58
|
+
* significatifs en réclameraient moins que zéro décimale (un prix au-delà de
|
|
59
|
+
* 99 999), la documentation autorise malgré tout n'importe quel entier, donc
|
|
60
|
+
* on s'arrête à l'entier plutôt que de raboter aussi les unités.
|
|
61
|
+
*/
|
|
62
|
+
export declare function priceDecimals(price: string | number, szDecimals: number, type?: HLMarketType): number;
|
|
63
|
+
/**
|
|
64
|
+
* Le prix, réécrit comme Hyperliquid l'accepte : tronqué à ce que
|
|
65
|
+
* `priceDecimals` autorise.
|
|
66
|
+
*
|
|
67
|
+
* @throws {TickAndLotError} si la valeur est illisible, ou tronquée à zéro.
|
|
68
|
+
*/
|
|
69
|
+
export declare function formatPrice(price: string | number, szDecimals: number, type?: HLMarketType): string;
|
|
70
|
+
/**
|
|
71
|
+
* La taille, réécrite comme Hyperliquid l'accepte : **tronquée** à
|
|
72
|
+
* `szDecimals`, jamais arrondie. Arrondir vers le haut, ce serait trader plus
|
|
73
|
+
* que ce qui a été calculé — dépasser une position sur un ordre `reduceOnly`,
|
|
74
|
+
* ou le collatéral sur une ouverture.
|
|
75
|
+
*
|
|
76
|
+
* @throws {TickAndLotError} si la valeur est illisible, ou tronquée à zéro.
|
|
77
|
+
*/
|
|
78
|
+
export declare function formatSize(size: string | number, szDecimals: number): string;
|
|
79
|
+
/**
|
|
80
|
+
* Pose un prix sur la grille de l'actif **dans un sens choisi**, et garantit
|
|
81
|
+
* que le résultat en est un point fixe : `formatPrice(snapPrice(p)) === snapPrice(p)`.
|
|
82
|
+
*
|
|
83
|
+
* C'est ce que `formatPrice` seul ne sait pas faire — il tronque toujours.
|
|
84
|
+
* Un émetteur qui veut qu'un stop ne s'éloigne jamais de son ancre a besoin du
|
|
85
|
+
* sens opposé, et il a besoin que le formatage final ne défasse pas ce choix.
|
|
86
|
+
*
|
|
87
|
+
* @throws {TickAndLotError} si la valeur est illisible, ou ramenée à zéro.
|
|
88
|
+
*/
|
|
89
|
+
export declare function snapPrice(price: string | number, szDecimals: number, type?: HLMarketType, mode?: RoundingMode): string;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ============================================================================
|
|
4
|
+
* PRIX ET TAILLES : LES RÈGLES DE TICK ET DE LOT D'HYPERLIQUID
|
|
5
|
+
*
|
|
6
|
+
* Source : https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size
|
|
7
|
+
* relevée le 2026-09-23. Deux règles, et une exception :
|
|
8
|
+
*
|
|
9
|
+
* - **taille** : tronquée à `szDecimals`, propre à chaque actif (réponse `meta`) ;
|
|
10
|
+
* - **prix** : au plus 5 chiffres significatifs, ET au plus
|
|
11
|
+
* `MAX_DECIMALS - szDecimals` décimales, `MAX_DECIMALS` valant 6 en perp et
|
|
12
|
+
* 8 en spot ;
|
|
13
|
+
* - **un prix entier est toujours accepté**, quel que soit son nombre de
|
|
14
|
+
* chiffres significatifs (`123456` est valide là où `12345.6` ne l'est pas).
|
|
15
|
+
*
|
|
16
|
+
* Ce module vit ici, et non dans le gateway ou le bot, pour qu'il n'en existe
|
|
17
|
+
* **qu'une seule implémentation** : un prix mal écrit est refusé par
|
|
18
|
+
* Hyperliquid, ou accepté sous une forme tronquée qui n'est plus celle qu'on
|
|
19
|
+
* voulait. Sur un stop loss, la différence entre les deux, c'est une position
|
|
20
|
+
* protégée et une position qui ne l'est pas. Deux implémentations, c'est la
|
|
21
|
+
* garantie qu'un émetteur croira un jour avoir posé autre chose que ce qui
|
|
22
|
+
* l'a été.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ Un faux gateway de test, lui, doit garder sa **copie indépendante** : s'il
|
|
25
|
+
* importait ces fonctions, un défaut s'y annulerait des deux côtés et aucun
|
|
26
|
+
* test ne le verrait.
|
|
27
|
+
*
|
|
28
|
+
* Rien ne passe par `Number` : tout le calcul se fait sur les chiffres écrits,
|
|
29
|
+
* en `BigInt`. Un `parseFloat` réintroduirait exactement l'erreur d'arrondi que
|
|
30
|
+
* ces règles servent à éviter.
|
|
31
|
+
* ============================================================================
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.MAX_PRICE_SIGNIFICANT_DIGITS = exports.MAX_PRICE_DECIMALS = exports.TickAndLotError = void 0;
|
|
35
|
+
exports.roundDecimal = roundDecimal;
|
|
36
|
+
exports.priceDecimals = priceDecimals;
|
|
37
|
+
exports.formatPrice = formatPrice;
|
|
38
|
+
exports.formatSize = formatSize;
|
|
39
|
+
exports.snapPrice = snapPrice;
|
|
40
|
+
/** Levée plutôt que de rendre une valeur que l'exchange refuserait. */
|
|
41
|
+
class TickAndLotError extends Error {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = 'TickAndLotError';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.TickAndLotError = TickAndLotError;
|
|
48
|
+
/** `MAX_DECIMALS` de la documentation, par type de marché. */
|
|
49
|
+
exports.MAX_PRICE_DECIMALS = {
|
|
50
|
+
perp: 6,
|
|
51
|
+
spot: 8,
|
|
52
|
+
};
|
|
53
|
+
/** Le plafond de chiffres significatifs d'un prix non entier. */
|
|
54
|
+
exports.MAX_PRICE_SIGNIFICANT_DIGITS = 5;
|
|
55
|
+
/** Un décimal simple, signe optionnel : `12`, `-0.5`, `.75`, `3.`. */
|
|
56
|
+
const PLAIN_DECIMAL = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/;
|
|
57
|
+
const EXPONENTIAL = /^([+-]?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/;
|
|
58
|
+
/**
|
|
59
|
+
* Réécrit une notation scientifique en décimal simple. `String(0.0000001)`
|
|
60
|
+
* rend `'1e-7'`, que la documentation n'accepte pas et que nous refuserions
|
|
61
|
+
* plus loin : autant l'écrire correctement ici.
|
|
62
|
+
*/
|
|
63
|
+
function expandExponential(text) {
|
|
64
|
+
const match = EXPONENTIAL.exec(text);
|
|
65
|
+
if (!match)
|
|
66
|
+
return text;
|
|
67
|
+
const [, sign, int, frac = '', exponentText] = match;
|
|
68
|
+
const digits = `${int}${frac}`;
|
|
69
|
+
const pointIndex = int.length + Number(exponentText);
|
|
70
|
+
if (pointIndex <= 0) {
|
|
71
|
+
return `${sign}0.${'0'.repeat(-pointIndex)}${digits}`;
|
|
72
|
+
}
|
|
73
|
+
if (pointIndex >= digits.length) {
|
|
74
|
+
return `${sign}${digits}${'0'.repeat(pointIndex - digits.length)}`;
|
|
75
|
+
}
|
|
76
|
+
return `${sign}${digits.slice(0, pointIndex)}.${digits.slice(pointIndex)}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Rend le texte décimal d'une valeur.
|
|
80
|
+
*
|
|
81
|
+
* Un `number` est une valeur **calculée** : on l'écrit exactement, notation
|
|
82
|
+
* scientifique dépliée. Une `string` est un texte qui vient déjà d'ailleurs —
|
|
83
|
+
* d'un DTO, de l'API — et doit donc être un décimal simple : une notation
|
|
84
|
+
* scientifique y signale qu'un flottant s'est glissé en amont, et la laisser
|
|
85
|
+
* passer masquerait le défaut.
|
|
86
|
+
*/
|
|
87
|
+
function toDecimalText(value, field) {
|
|
88
|
+
if (typeof value === 'number') {
|
|
89
|
+
if (!Number.isFinite(value)) {
|
|
90
|
+
throw new TickAndLotError(`${field} is not finite: ${String(value)}`);
|
|
91
|
+
}
|
|
92
|
+
return expandExponential(String(value));
|
|
93
|
+
}
|
|
94
|
+
const text = String(value).trim();
|
|
95
|
+
if (!PLAIN_DECIMAL.test(text)) {
|
|
96
|
+
throw new TickAndLotError(`${field} is not a plain decimal string: ${JSON.stringify(value)}`);
|
|
97
|
+
}
|
|
98
|
+
return text;
|
|
99
|
+
}
|
|
100
|
+
function split(text) {
|
|
101
|
+
const negative = text.startsWith('-');
|
|
102
|
+
const [int = '', frac = ''] = text.replace(/^[+-]/, '').split('.');
|
|
103
|
+
return { negative, int: int || '0', frac };
|
|
104
|
+
}
|
|
105
|
+
/** Réécrit une valeur mise à l'échelle, sans zéro de queue ni `-0`. */
|
|
106
|
+
function formatUnits(negative, units, decimals) {
|
|
107
|
+
const digits = units.toString().padStart(decimals + 1, '0');
|
|
108
|
+
const int = digits.slice(0, digits.length - decimals);
|
|
109
|
+
const frac = decimals > 0 ? digits.slice(digits.length - decimals).replace(/0+$/, '') : '';
|
|
110
|
+
const body = frac ? `${int}.${frac}` : int;
|
|
111
|
+
return negative && units !== 0n ? `-${body}` : body;
|
|
112
|
+
}
|
|
113
|
+
function assertWholeCount(value, field) {
|
|
114
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
115
|
+
throw new TickAndLotError(`${field} must be a non-negative integer: ${String(value)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* L'exposant décimal de la valeur — `2` pour 345, `-3` pour 0,001234 — ou
|
|
120
|
+
* `null` pour zéro, qui n'en a pas.
|
|
121
|
+
*/
|
|
122
|
+
function log10Floor(text) {
|
|
123
|
+
const { int, frac } = split(text);
|
|
124
|
+
const significantInt = int.replace(/^0+/, '');
|
|
125
|
+
if (significantInt)
|
|
126
|
+
return significantInt.length - 1;
|
|
127
|
+
const leadingZeros = /^0*/.exec(frac)?.[0].length ?? 0;
|
|
128
|
+
if (leadingZeros === frac.length)
|
|
129
|
+
return null;
|
|
130
|
+
return -(leadingZeros + 1);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Arrondit une valeur à `decimals` décimales, dans le sens demandé, sans
|
|
134
|
+
* jamais passer par un flottant.
|
|
135
|
+
*/
|
|
136
|
+
function roundDecimal(value, decimals, mode = 'truncate') {
|
|
137
|
+
assertWholeCount(decimals, 'decimals');
|
|
138
|
+
const { negative, int, frac } = split(toDecimalText(value, 'value'));
|
|
139
|
+
const kept = frac.slice(0, decimals).padEnd(decimals, '0');
|
|
140
|
+
const dropped = frac.slice(decimals);
|
|
141
|
+
let units = BigInt(`${int}${kept}`);
|
|
142
|
+
if (/[1-9]/.test(dropped)) {
|
|
143
|
+
const awayFromZero = mode === 'nearest'
|
|
144
|
+
? dropped.charAt(0) >= '5'
|
|
145
|
+
: mode === 'up'
|
|
146
|
+
? !negative
|
|
147
|
+
: mode === 'down'
|
|
148
|
+
? negative
|
|
149
|
+
: false;
|
|
150
|
+
if (awayFromZero)
|
|
151
|
+
units += 1n;
|
|
152
|
+
}
|
|
153
|
+
return formatUnits(negative, units, decimals);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Le nombre de décimales que ce prix a le droit d'avoir — les deux règles
|
|
157
|
+
* réunies en un seul nombre.
|
|
158
|
+
*
|
|
159
|
+
* Le plancher à zéro **est** l'exception des entiers : quand les chiffres
|
|
160
|
+
* significatifs en réclameraient moins que zéro décimale (un prix au-delà de
|
|
161
|
+
* 99 999), la documentation autorise malgré tout n'importe quel entier, donc
|
|
162
|
+
* on s'arrête à l'entier plutôt que de raboter aussi les unités.
|
|
163
|
+
*/
|
|
164
|
+
function priceDecimals(price, szDecimals, type = 'perp') {
|
|
165
|
+
assertWholeCount(szDecimals, 'szDecimals');
|
|
166
|
+
const byDecimals = Math.max(exports.MAX_PRICE_DECIMALS[type] - szDecimals, 0);
|
|
167
|
+
const magnitude = log10Floor(toDecimalText(price, 'price'));
|
|
168
|
+
if (magnitude === null)
|
|
169
|
+
return byDecimals;
|
|
170
|
+
const bySignificantDigits = exports.MAX_PRICE_SIGNIFICANT_DIGITS - 1 - magnitude;
|
|
171
|
+
return Math.min(byDecimals, Math.max(bySignificantDigits, 0));
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Le prix, réécrit comme Hyperliquid l'accepte : tronqué à ce que
|
|
175
|
+
* `priceDecimals` autorise.
|
|
176
|
+
*
|
|
177
|
+
* @throws {TickAndLotError} si la valeur est illisible, ou tronquée à zéro.
|
|
178
|
+
*/
|
|
179
|
+
function formatPrice(price, szDecimals, type = 'perp') {
|
|
180
|
+
const decimals = priceDecimals(price, szDecimals, type);
|
|
181
|
+
const formatted = roundDecimal(price, decimals, 'truncate');
|
|
182
|
+
if (formatted === '0') {
|
|
183
|
+
throw new TickAndLotError('Price is too small and was truncated to 0');
|
|
184
|
+
}
|
|
185
|
+
return formatted;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* La taille, réécrite comme Hyperliquid l'accepte : **tronquée** à
|
|
189
|
+
* `szDecimals`, jamais arrondie. Arrondir vers le haut, ce serait trader plus
|
|
190
|
+
* que ce qui a été calculé — dépasser une position sur un ordre `reduceOnly`,
|
|
191
|
+
* ou le collatéral sur une ouverture.
|
|
192
|
+
*
|
|
193
|
+
* @throws {TickAndLotError} si la valeur est illisible, ou tronquée à zéro.
|
|
194
|
+
*/
|
|
195
|
+
function formatSize(size, szDecimals) {
|
|
196
|
+
assertWholeCount(szDecimals, 'szDecimals');
|
|
197
|
+
const formatted = roundDecimal(size, szDecimals, 'truncate');
|
|
198
|
+
if (formatted === '0') {
|
|
199
|
+
throw new TickAndLotError('Size is too small and was truncated to 0');
|
|
200
|
+
}
|
|
201
|
+
return formatted;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Pose un prix sur la grille de l'actif **dans un sens choisi**, et garantit
|
|
205
|
+
* que le résultat en est un point fixe : `formatPrice(snapPrice(p)) === snapPrice(p)`.
|
|
206
|
+
*
|
|
207
|
+
* C'est ce que `formatPrice` seul ne sait pas faire — il tronque toujours.
|
|
208
|
+
* Un émetteur qui veut qu'un stop ne s'éloigne jamais de son ancre a besoin du
|
|
209
|
+
* sens opposé, et il a besoin que le formatage final ne défasse pas ce choix.
|
|
210
|
+
*
|
|
211
|
+
* @throws {TickAndLotError} si la valeur est illisible, ou ramenée à zéro.
|
|
212
|
+
*/
|
|
213
|
+
function snapPrice(price, szDecimals, type = 'perp', mode = 'truncate') {
|
|
214
|
+
const snapped = roundDecimal(price, priceDecimals(price, szDecimals, type), mode);
|
|
215
|
+
// Un arrondi vers l'extérieur peut franchir un palier — 9,9999 devient 10 —
|
|
216
|
+
// et la grille change avec l'ordre de grandeur. On la recalcule une fois.
|
|
217
|
+
const settled = roundDecimal(snapped, priceDecimals(snapped, szDecimals, type), mode);
|
|
218
|
+
if (settled === '0') {
|
|
219
|
+
throw new TickAndLotError('Price is too small and was snapped to 0');
|
|
220
|
+
}
|
|
221
|
+
if (formatPrice(settled, szDecimals, type) !== settled) {
|
|
222
|
+
throw new TickAndLotError(`snapPrice produced ${settled}, which is not on the grid for szDecimals ${szDecimals}`);
|
|
223
|
+
}
|
|
224
|
+
return settled;
|
|
225
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,50 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@syldel/hl-shared-types",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Shared TypeScript interfaces and types for Hyperliquid integration.",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/Syldel/hl-shared-types.git"
|
|
10
|
-
},
|
|
11
|
-
"bugs": {
|
|
12
|
-
"url": "https://github.com/Syldel/hl-shared-types/issues"
|
|
13
|
-
},
|
|
14
|
-
"homepage": "https://github.com/Syldel/hl-shared-types#readme",
|
|
15
|
-
"files": [
|
|
16
|
-
"dist"
|
|
17
|
-
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"
|
|
20
|
-
"lint
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"eslint
|
|
44
|
-
"eslint-
|
|
45
|
-
"
|
|
46
|
-
"prettier": "^
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@syldel/hl-shared-types",
|
|
3
|
+
"version": "0.0.17",
|
|
4
|
+
"description": "Shared TypeScript interfaces and types for Hyperliquid integration.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Syldel/hl-shared-types.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Syldel/hl-shared-types/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/Syldel/hl-shared-types#readme",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "jest",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"lint:fix": "eslint . --fix",
|
|
22
|
+
"clean": "rm -rf dist",
|
|
23
|
+
"build": "npm run lint && npm run test && npm run clean && tsc",
|
|
24
|
+
"prepublishOnly": "npm run build",
|
|
25
|
+
"release": "npm run build && npm version patch && npm publish"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"hyperliquid",
|
|
32
|
+
"trading",
|
|
33
|
+
"types",
|
|
34
|
+
"interfaces",
|
|
35
|
+
"crypto",
|
|
36
|
+
"exchange",
|
|
37
|
+
"defi"
|
|
38
|
+
],
|
|
39
|
+
"author": "Syl. D.",
|
|
40
|
+
"license": "ISC",
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/jest": "^30.0.0",
|
|
43
|
+
"eslint": "^9.39.2",
|
|
44
|
+
"eslint-config-prettier": "^10.1.8",
|
|
45
|
+
"eslint-plugin-jsonc": "^2.21.0",
|
|
46
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
47
|
+
"jest": "^30.5.2",
|
|
48
|
+
"jsonc-eslint-parser": "^2.4.2",
|
|
49
|
+
"prettier": "^3.8.0",
|
|
50
|
+
"ts-jest": "^29.4.13",
|
|
51
|
+
"typescript": "^5.3.3",
|
|
52
|
+
"typescript-eslint": "^8.53.0"
|
|
53
|
+
}
|
|
54
|
+
}
|