Files
himself65__finance-skills/opencli-plugins/hyperliquid/tests/candles.test.js
T
Alex Yang 8072082fda feat: add hyperliquid-reader skill + opencli plugin (#76)
* 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.
2026-06-07 16:21:26 -07:00

36 lines
1.4 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { normalizeCandles, INTERVAL_MS } from '../lib/candles.js';
// Shape captured live from candleSnapshot.
const CANDLES = [
{ t: 1780804800000, T: 1780808399999, s: 'BTC', i: '1h', o: '61685.0', c: '61871.0', h: '61888.0', l: '61463.0', v: '1376.39573', n: 18040 },
{ t: 1780808400000, T: 1780811999999, s: 'BTC', i: '1h', o: '61871.0', c: '61721.0', h: '62130.0', l: '61630.0', v: '1733.82548', n: 24641 },
];
test('normalizeCandles — OHLCV with ISO open time', () => {
const rows = normalizeCandles(CANDLES);
assert.equal(rows.length, 2);
const c = rows[0];
assert.equal(c.time, new Date(1780804800000).toISOString());
assert.equal(c.open, 61685);
assert.equal(c.high, 61888);
assert.equal(c.low, 61463);
assert.equal(c.close, 61871);
assert.equal(c.volume, 1376.39573);
assert.equal(c.trades, 18040);
});
test('normalizeCandles — empty input', () => {
assert.deepEqual(normalizeCandles([]), []);
assert.deepEqual(normalizeCandles(null), []);
});
test('INTERVAL_MS — covers the documented set, consistent math', () => {
assert.equal(INTERVAL_MS['1h'], 3_600_000);
assert.equal(INTERVAL_MS['1d'], 24 * INTERVAL_MS['1h']);
assert.equal(INTERVAL_MS['1w'], 7 * INTERVAL_MS['1d']);
assert.equal(INTERVAL_MS['15m'], 15 * INTERVAL_MS['1m']);
assert.ok(Object.keys(INTERVAL_MS).includes('1M'));
});