mirror of
https://github.com/himself65/finance-skills.git
synced 2026-09-14 14:40:40 +08:00
8072082fda
* feat: add hyperliquid-reader skill + opencli plugin Add a read-only Hyperliquid (app.hyperliquid.xyz) reader, mirroring the tradingview-reader pattern. Unlike TradingView's CDP attach, Hyperliquid exposes a fully public info API (POST https://api.hyperliquid.xyz/info), so this needs no API key, wallet, login, or desktop app. opencli plugin (opencli-plugins/hyperliquid/) — 12 read-only commands: - Market data: markets, spot-markets, mids, book, candles, funding-history, funding-compare (cross-venue HL/Binance/Bybit funding-arb screen). - Account by 0x address: account, positions, spot-balances, open-orders, fills. Skill (plugins/data-providers/skills/hyperliquid-reader/) — SKILL.md (5-step + error reference), README.md, references/commands.md. Registrations: root opencli-plugin.json, data-providers plugin.json (description + keywords), marketplace.json, root README table. Verification: 31 unit tests pass against captured live wire shapes; all 12 command data paths smoke-tested end-to-end against the live API. SKILL.md description is 1019 chars (< 1024 lint cap), no angle brackets. No trade execution: the plugin exposes no write/exchange endpoints. 🤖 Generated with [Claude Code](https://claude.com/claude-code) * refactor: scope hyperliquid-reader to market data only Drop the account-by-address commands (account, positions, spot-balances, open-orders, fills) and their lib/account.js + lib/orders.js helpers and tests. Remove the now-unused normalizeAddress helper from lib/api.js. The reader is now market-data only: markets, spot-markets, mids, book, candles, funding-history, funding-compare (7 commands). Updated SKILL.md, both READMEs, references/commands.md, the plugin/marketplace manifests, and the root README table accordingly. 21 unit tests pass; all 7 market-data data paths re-verified live.
70 lines
2.3 KiB
JavaScript
70 lines
2.3 KiB
JavaScript
/**
|
|
* Hyperliquid info API helpers.
|
|
*
|
|
* Hyperliquid exposes a fully public, read-only POST endpoint:
|
|
* POST https://api.hyperliquid.xyz/info body { "type": "...", ... } → JSON
|
|
*
|
|
* No API key, no auth, no cookies — every market-data read this plugin makes
|
|
* works unauthenticated. Placing trades requires wallet-signed actions on the
|
|
* separate /exchange endpoint, which this plugin intentionally NEVER touches.
|
|
*
|
|
* Funding rates come back per funding interval. Hyperliquid perps fund hourly,
|
|
* so the hourly rate annualizes as rate * 24 * 365. Other venues surfaced via
|
|
* `predictedFundings` may fund on a different interval (commonly 4h) — always
|
|
* normalize with the per-row interval, not a hard-coded hour.
|
|
*/
|
|
|
|
const INFO_URL = 'https://api.hyperliquid.xyz/info';
|
|
|
|
/**
|
|
* POST a request to the Hyperliquid info endpoint and return the parsed body.
|
|
* @param {object} body e.g. { type: 'metaAndAssetCtxs' }
|
|
*/
|
|
export async function infoFetch(body) {
|
|
const res = await fetch(INFO_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(`hyperliquid info ${res.status}: ${text.slice(0, 200)}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
/** Coerce a value (often a numeric string from the API) to a finite Number, else null. */
|
|
export function num(v) {
|
|
if (v == null) return null;
|
|
const n = Number(v);
|
|
return Number.isFinite(n) ? n : null;
|
|
}
|
|
|
|
/** Percent change of `now` vs `prev`; null if either is missing or prev is 0. */
|
|
export function pctChange(now, prev) {
|
|
const a = num(now);
|
|
const b = num(prev);
|
|
if (a == null || b == null || b === 0) return null;
|
|
return ((a - b) / b) * 100;
|
|
}
|
|
|
|
/**
|
|
* Annualize a per-interval funding rate to a percentage APR.
|
|
* @param {string|number} rate funding rate for one interval
|
|
* @param {string|number} [intervalHours=1] length of that interval in hours
|
|
*/
|
|
export function fundingToApr(rate, intervalHours = 1) {
|
|
const r = num(rate);
|
|
const h = num(intervalHours) || 1;
|
|
return r == null ? null : (r / h) * 24 * 365 * 100;
|
|
}
|
|
|
|
/** Millisecond epoch → ISO string (null-safe). */
|
|
export function isoTime(ms) {
|
|
const n = num(ms);
|
|
if (n == null) return null;
|
|
return new Date(n).toISOString();
|
|
}
|
|
|
|
export { INFO_URL };
|