90 lines
3.9 KiB
TypeScript
90 lines
3.9 KiB
TypeScript
/**
|
|
* Public Hyperliquid market data — the price source for DXtrade firms whose
|
|
* API user isn't allowed DXtrade's own /marketdata (Tradeify247 answers
|
|
* "No proper permission is configured", code 34). Tradeify247's instruments
|
|
* track Hyperliquid's: crypto on the main dex (BTC, ETH), indices and
|
|
* tokenized stocks on the trade[XYZ] HIP-3 dex (xyz:XYZ100, xyz:SP500…).
|
|
*
|
|
* Only the instrument pick uses it — spread for the cost score, hourly closes
|
|
* for volatility, the mid for sizing. Orders, fills, balances and every
|
|
* take-profit/stop price still come from DXtrade and its own fill prices.
|
|
*
|
|
* No key, no account: POST https://api.hyperliquid.xyz/info.
|
|
*/
|
|
|
|
const INFO_URL = 'https://api.hyperliquid.xyz/info';
|
|
const TIMEOUT_MS = 10_000;
|
|
const META_TTL_MS = 60 * 60_000;
|
|
/** Dexes whose coins a DXtrade symbol may map to, main dex first. */
|
|
const DEXES = ['', 'xyz'];
|
|
|
|
async function info<T>(body: Record<string, unknown>): Promise<T> {
|
|
// Keep the pure quote helpers usable without opening the instance database.
|
|
const { default: axios } = await import('./http-client');
|
|
const res = await axios.post(INFO_URL, body, {
|
|
timeout: TIMEOUT_MS,
|
|
signal: AbortSignal.timeout(TIMEOUT_MS + 5_000),
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
return res.data as T;
|
|
}
|
|
|
|
const g = globalThis as typeof globalThis & { __hlCoins?: { coins: Set<string>; at: number } };
|
|
|
|
/** Every coin on the dexes above ("BTC", "xyz:XYZ100", …), cached an hour. */
|
|
export async function hyperliquidCoins(): Promise<Set<string>> {
|
|
if (g.__hlCoins && Date.now() - g.__hlCoins.at < META_TTL_MS) return g.__hlCoins.coins;
|
|
const coins = new Set<string>();
|
|
for (const dex of DEXES) {
|
|
const meta = await info<{ universe?: { name?: unknown; isDelisted?: unknown }[] }>(dex ? { type: 'meta', dex } : { type: 'meta' });
|
|
for (const u of meta?.universe ?? []) {
|
|
if (typeof u.name === 'string' && u.isDelisted !== true) coins.add(u.name);
|
|
}
|
|
}
|
|
g.__hlCoins = { coins, at: Date.now() };
|
|
return coins;
|
|
}
|
|
|
|
/**
|
|
* The Hyperliquid coin for a DXtrade symbol, or null. "BTC/USD" → "BTC" on
|
|
* the main dex; "XYZ100" or "AAPL/USD" → "xyz:XYZ100" / "xyz:AAPL" on the
|
|
* trade[XYZ] dex when the main dex doesn't list it.
|
|
*/
|
|
export function hyperliquidCoinFor(symbol: string, coins: Set<string>): string | null {
|
|
const base = symbol.toUpperCase().replace(/[/_-]?(USDT|USDC|USD)$/, '');
|
|
if (!base) return null;
|
|
for (const candidate of [base, `xyz:${base}`]) {
|
|
if (coins.has(candidate)) return candidate;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Best bid/ask of an l2Book answer; undefined sides when the book is empty. */
|
|
export function parseL2Book(book: unknown): { bid?: number; ask?: number; time: number } {
|
|
const b = book as { levels?: { px?: unknown }[][]; time?: unknown };
|
|
const px = (side: number) => {
|
|
const v = Number(b?.levels?.[side]?.[0]?.px);
|
|
return Number.isFinite(v) && v > 0 ? v : undefined;
|
|
};
|
|
const time = Number(b?.time);
|
|
return { bid: px(0), ask: px(1), time: Number.isFinite(time) ? time : NaN };
|
|
}
|
|
|
|
export async function hyperliquidQuote(coin: string): Promise<{ bid?: number; ask?: number; time: number }> {
|
|
return parseL2Book(await info({ type: 'l2Book', coin }));
|
|
}
|
|
|
|
/** Hourly closes, oldest first, over the last `hours`. */
|
|
export async function hyperliquidHourlyCloses(coin: string, hours: number): Promise<number[]> {
|
|
const endTime = Date.now();
|
|
const candles = await info<{ t?: unknown; c?: unknown }[]>({
|
|
type: 'candleSnapshot',
|
|
req: { coin, interval: '1h', startTime: endTime - (hours + 1) * 3_600_000, endTime },
|
|
});
|
|
return (Array.isArray(candles) ? candles : [])
|
|
.map((c) => ({ t: Number(c.t), c: Number(c.c) }))
|
|
.filter((c) => Number.isFinite(c.t) && Number.isFinite(c.c) && c.c > 0)
|
|
.sort((a, b) => a.t - b.t)
|
|
.map((c) => c.c);
|
|
}
|