UNPKG

@nktkas/hyperliquid

Version:

Hyperliquid API SDK for all major JS runtimes, written in TypeScript.

113 lines (112 loc) 3.87 kB
/** * Price and size formatting per Hyperliquid tick and lot size rules. * * @module */ import { Decimal } from "decimal.js"; import { HyperliquidError } from "../_base.js"; /** * Thrown when a price or size value cannot be formatted to a valid decimal. * * @example * ```ts * import { formatPrice, FormatError } from "@nktkas/hyperliquid/utils"; * * try { * formatPrice("not a number", 0); * } catch (error) { * if (error instanceof FormatError) { * console.error(error.message); * } * } * ``` */ export class FormatError extends HyperliquidError { constructor(message, options){ super(message, options); this.name = "FormatError"; } } // A cloned constructor isolates our rounding mode from the global decimal.js // config, which any other consumer in the process could mutate via Decimal.set. const D = Decimal.clone({ rounding: Decimal.ROUND_DOWN }); /** * Parse a string or number into a finite decimal.js value. * * @throws {FormatError} If the value is unparsable or not finite. */ function toDecimal(value, field) { let d; try { d = new D(value); } catch (cause) { throw new FormatError(`Invalid ${field}: ${JSON.stringify(value)}`, { cause }); } // decimal.js accepts NaN and Infinity as valid Decimals, so guard finiteness. if (!d.isFinite()) { throw new FormatError(`Invalid ${field}: ${String(value)} is not finite`); } return d; } /** * Format price according to Hyperliquid {@link https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size | rules}: * - Maximum 5 significant figures * - Maximum 6 (for perp) or 8 (for spot) - `szDecimals` decimal places * - Integer prices are always allowed regardless of significant figures * * @param price The price to format (as string or number). * @param szDecimals The size decimals of the asset. * @param type The market type: "perp" for perpetuals or "spot" for spot markets. Default: `"perp"`. * @return Formatted price string * * @throws {FormatError} If the price is not a valid finite number, or is truncated to 0. * * @example * ```ts * import { formatPrice } from "@nktkas/hyperliquid/utils"; * * formatPrice("97123.456789", 0); // → "97123" (perp, szDecimals=0) * formatPrice("1.23456789", 5); // → "1.2" (perp, szDecimals=5) * formatPrice("0.0000123456789", 0, "spot"); // → "0.00001234" (spot, 8-decimal ceiling) * ``` */ export function formatPrice(price, szDecimals, type = "perp") { const d = toDecimal(price, "price"); const maxDecimals = Math.max((type === "perp" ? 6 : 8) - szDecimals, 0); let result = d.toDecimalPlaces(maxDecimals); // Integers are exempt from the 5-sig-fig cap. if (!result.isInteger()) { result = result.toSignificantDigits(5); } if (result.isZero()) { throw new FormatError("Price is too small and was truncated to 0"); } return result.toFixed(); } /** * Format size according to Hyperliquid {@link https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size | rules}: * - Truncate decimal places to `szDecimals` * * @param size The size to format (as string or number). * @param szDecimals The size decimals of the asset. * @return Formatted size string * * @throws {FormatError} If the size is not a valid finite number, or is truncated to 0. * * @example * ```ts * import { formatSize } from "@nktkas/hyperliquid/utils"; * * formatSize("1.23456789", 5); // → "1.23456" * formatSize("0.123456789", 2); // → "0.12" * formatSize("100", 0); // → "100" * ``` */ export function formatSize(size, szDecimals) { const d = toDecimal(size, "size"); const result = d.toDecimalPlaces(szDecimals); if (result.isZero()) { throw new FormatError("Size is too small and was truncated to 0"); } return result.toFixed(); } //# sourceMappingURL=_format.js.map