openbroker 1.3.1 → 1.4.0
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/CHANGELOG.md +12 -0
- package/SKILL.md +7 -4
- package/dist/auto/audit.d.ts +57 -0
- package/dist/auto/audit.d.ts.map +1 -0
- package/dist/auto/audit.js +407 -0
- package/dist/auto/cli.d.ts +2 -0
- package/dist/auto/cli.d.ts.map +1 -0
- package/dist/auto/cli.js +423 -0
- package/dist/auto/events.d.ts +11 -0
- package/dist/auto/events.d.ts.map +1 -0
- package/dist/auto/events.js +36 -0
- package/dist/auto/examples/dca.d.ts +4 -0
- package/dist/auto/examples/dca.d.ts.map +1 -0
- package/dist/auto/examples/dca.js +60 -0
- package/dist/auto/examples/funding-arb.d.ts +4 -0
- package/dist/auto/examples/funding-arb.d.ts.map +1 -0
- package/dist/auto/examples/funding-arb.js +81 -0
- package/dist/auto/examples/grid.d.ts +4 -0
- package/dist/auto/examples/grid.d.ts.map +1 -0
- package/dist/auto/examples/grid.js +114 -0
- package/dist/auto/examples/mm-maker.d.ts +4 -0
- package/dist/auto/examples/mm-maker.d.ts.map +1 -0
- package/dist/auto/examples/mm-maker.js +131 -0
- package/dist/auto/examples/mm-spread.d.ts +4 -0
- package/dist/auto/examples/mm-spread.d.ts.map +1 -0
- package/dist/auto/examples/mm-spread.js +119 -0
- package/dist/auto/examples/price-alert.d.ts +4 -0
- package/dist/auto/examples/price-alert.d.ts.map +1 -0
- package/dist/auto/examples/price-alert.js +85 -0
- package/dist/auto/keep-awake.d.ts +11 -0
- package/dist/auto/keep-awake.d.ts.map +1 -0
- package/dist/auto/keep-awake.js +70 -0
- package/dist/auto/loader.d.ts +22 -0
- package/dist/auto/loader.d.ts.map +1 -0
- package/dist/auto/loader.js +127 -0
- package/dist/auto/prune.d.ts +40 -0
- package/dist/auto/prune.d.ts.map +1 -0
- package/dist/auto/prune.js +204 -0
- package/dist/auto/registry.d.ts +24 -0
- package/dist/auto/registry.d.ts.map +1 -0
- package/dist/auto/registry.js +93 -0
- package/dist/auto/report.d.ts +3 -0
- package/dist/auto/report.d.ts.map +1 -0
- package/dist/auto/report.js +385 -0
- package/dist/auto/runtime.d.ts +33 -0
- package/dist/auto/runtime.d.ts.map +1 -0
- package/dist/auto/runtime.js +844 -0
- package/dist/auto/types.d.ts +236 -0
- package/dist/auto/types.d.ts.map +1 -0
- package/dist/auto/types.js +3 -0
- package/dist/core/client.d.ts +684 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +2040 -0
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +143 -0
- package/dist/core/types.d.ts +221 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/utils.d.ts +61 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +142 -0
- package/dist/core/ws.d.ts +121 -0
- package/dist/core/ws.d.ts.map +1 -0
- package/dist/core/ws.js +222 -0
- package/dist/info/account.d.ts +3 -0
- package/dist/info/account.d.ts.map +1 -0
- package/dist/info/account.js +198 -0
- package/dist/info/all-markets.d.ts +3 -0
- package/dist/info/all-markets.d.ts.map +1 -0
- package/dist/info/all-markets.js +272 -0
- package/dist/info/candles.d.ts +3 -0
- package/dist/info/candles.d.ts.map +1 -0
- package/dist/info/candles.js +120 -0
- package/dist/info/fees.d.ts +3 -0
- package/dist/info/fees.d.ts.map +1 -0
- package/dist/info/fees.js +87 -0
- package/dist/info/fills.d.ts +3 -0
- package/dist/info/fills.d.ts.map +1 -0
- package/dist/info/fills.js +105 -0
- package/dist/info/funding-history.d.ts +3 -0
- package/dist/info/funding-history.d.ts.map +1 -0
- package/dist/info/funding-history.js +98 -0
- package/dist/info/funding-scan.d.ts +3 -0
- package/dist/info/funding-scan.d.ts.map +1 -0
- package/dist/info/funding-scan.js +178 -0
- package/dist/info/funding.d.ts +3 -0
- package/dist/info/funding.d.ts.map +1 -0
- package/dist/info/funding.js +158 -0
- package/dist/info/markets.d.ts +3 -0
- package/dist/info/markets.d.ts.map +1 -0
- package/dist/info/markets.js +178 -0
- package/dist/info/order-status.d.ts +3 -0
- package/dist/info/order-status.d.ts.map +1 -0
- package/dist/info/order-status.js +85 -0
- package/dist/info/orders.d.ts +3 -0
- package/dist/info/orders.d.ts.map +1 -0
- package/dist/info/orders.js +162 -0
- package/dist/info/outcomes.d.ts +3 -0
- package/dist/info/outcomes.d.ts.map +1 -0
- package/dist/info/outcomes.js +175 -0
- package/dist/info/positions.d.ts +3 -0
- package/dist/info/positions.d.ts.map +1 -0
- package/dist/info/positions.js +127 -0
- package/dist/info/rate-limit.d.ts +3 -0
- package/dist/info/rate-limit.d.ts.map +1 -0
- package/dist/info/rate-limit.js +58 -0
- package/dist/info/search-markets.d.ts +3 -0
- package/dist/info/search-markets.d.ts.map +1 -0
- package/dist/info/search-markets.js +296 -0
- package/dist/info/spot.d.ts +3 -0
- package/dist/info/spot.d.ts.map +1 -0
- package/dist/info/spot.js +192 -0
- package/dist/info/trades.d.ts +3 -0
- package/dist/info/trades.d.ts.map +1 -0
- package/dist/info/trades.js +97 -0
- package/dist/lib.d.ts +14 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +17 -0
- package/dist/operations/bracket.d.ts +28 -0
- package/dist/operations/bracket.d.ts.map +1 -0
- package/dist/operations/bracket.js +266 -0
- package/dist/operations/cancel.d.ts +3 -0
- package/dist/operations/cancel.d.ts.map +1 -0
- package/dist/operations/cancel.js +107 -0
- package/dist/operations/chase.d.ts +25 -0
- package/dist/operations/chase.d.ts.map +1 -0
- package/dist/operations/chase.js +215 -0
- package/dist/operations/limit-order.d.ts +3 -0
- package/dist/operations/limit-order.d.ts.map +1 -0
- package/dist/operations/limit-order.js +144 -0
- package/dist/operations/market-order.d.ts +3 -0
- package/dist/operations/market-order.d.ts.map +1 -0
- package/dist/operations/market-order.js +153 -0
- package/dist/operations/outcome-order.d.ts +3 -0
- package/dist/operations/outcome-order.d.ts.map +1 -0
- package/dist/operations/outcome-order.js +171 -0
- package/dist/operations/scale.d.ts +3 -0
- package/dist/operations/scale.d.ts.map +1 -0
- package/dist/operations/scale.js +212 -0
- package/dist/operations/set-tpsl.d.ts +3 -0
- package/dist/operations/set-tpsl.d.ts.map +1 -0
- package/dist/operations/set-tpsl.js +277 -0
- package/dist/operations/spot-order.d.ts +3 -0
- package/dist/operations/spot-order.d.ts.map +1 -0
- package/dist/operations/spot-order.js +173 -0
- package/dist/operations/trigger-order.d.ts +3 -0
- package/dist/operations/trigger-order.d.ts.map +1 -0
- package/dist/operations/trigger-order.js +177 -0
- package/dist/operations/twap-cancel.d.ts +3 -0
- package/dist/operations/twap-cancel.d.ts.map +1 -0
- package/dist/operations/twap-cancel.js +57 -0
- package/dist/operations/twap-status.d.ts +3 -0
- package/dist/operations/twap-status.d.ts.map +1 -0
- package/dist/operations/twap-status.js +81 -0
- package/dist/operations/twap.d.ts +3 -0
- package/dist/operations/twap.d.ts.map +1 -0
- package/dist/operations/twap.js +124 -0
- package/dist/setup/approve-builder.d.ts +3 -0
- package/dist/setup/approve-builder.d.ts.map +1 -0
- package/dist/setup/approve-builder.js +155 -0
- package/dist/setup/env.d.ts +4 -0
- package/dist/setup/env.d.ts.map +1 -0
- package/dist/setup/env.js +8 -0
- package/dist/setup/onboard.d.ts +10 -0
- package/dist/setup/onboard.d.ts.map +1 -0
- package/dist/setup/onboard.js +462 -0
- package/package.json +10 -4
- package/scripts/core/client.ts +13 -3
- package/scripts/info/all-markets.ts +18 -2
- package/scripts/info/search-markets.ts +18 -2
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// View historical funding rates for an asset on Hyperliquid
|
|
3
|
+
import { getClient } from '../core/client.js';
|
|
4
|
+
import { formatPercent, annualizeFundingRate, parseArgs, normalizeCoin } from '../core/utils.js';
|
|
5
|
+
function printUsage() {
|
|
6
|
+
console.log(`
|
|
7
|
+
Usage: openbroker funding-history --coin <symbol> [options]
|
|
8
|
+
|
|
9
|
+
Options:
|
|
10
|
+
--coin <symbol> Asset symbol (required, e.g. ETH, BTC)
|
|
11
|
+
--hours <n> Hours of history to fetch (default: 24)
|
|
12
|
+
--json Output as JSON (machine-readable)
|
|
13
|
+
--help, -h Show this help
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
openbroker funding-history --coin ETH
|
|
17
|
+
openbroker funding-history --coin BTC --hours 168
|
|
18
|
+
`);
|
|
19
|
+
}
|
|
20
|
+
async function main() {
|
|
21
|
+
const args = parseArgs(process.argv.slice(2));
|
|
22
|
+
if (args.help || args.h) {
|
|
23
|
+
printUsage();
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
const coin = args.coin;
|
|
27
|
+
if (!coin) {
|
|
28
|
+
console.error('Error: --coin is required');
|
|
29
|
+
printUsage();
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const hours = parseInt(args.hours) || 24;
|
|
33
|
+
const jsonOutput = args.json;
|
|
34
|
+
const client = getClient();
|
|
35
|
+
if (!jsonOutput) {
|
|
36
|
+
console.log(`Open Broker - ${normalizeCoin(coin)} Funding History (${hours}h)`);
|
|
37
|
+
console.log('='.repeat(40) + '\n');
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
// Load metadata (needed for HIP-3 coin resolution)
|
|
41
|
+
await client.getMetaAndAssetCtxs();
|
|
42
|
+
if (client.isTestnet && coin.includes(':')) {
|
|
43
|
+
await client.loadSingleHip3Dex(coin.split(':')[0]);
|
|
44
|
+
}
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
const startTime = now - (hours * 3_600_000);
|
|
47
|
+
const history = await client.getFundingHistory(normalizeCoin(coin), startTime);
|
|
48
|
+
if (jsonOutput) {
|
|
49
|
+
let assetId = -1;
|
|
50
|
+
try {
|
|
51
|
+
assetId = client.getAssetIndex(normalizeCoin(coin));
|
|
52
|
+
}
|
|
53
|
+
catch { /* noop */ }
|
|
54
|
+
console.log(JSON.stringify({ coin: normalizeCoin(coin), assetId, history }, null, 2));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (history.length === 0) {
|
|
58
|
+
console.log('No funding history found');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Table header
|
|
62
|
+
console.log('Time'.padEnd(20) +
|
|
63
|
+
'Funding Rate'.padEnd(16) +
|
|
64
|
+
'Annualized'.padEnd(14) +
|
|
65
|
+
'Premium');
|
|
66
|
+
console.log('─'.repeat(60));
|
|
67
|
+
let totalRate = 0;
|
|
68
|
+
for (const entry of history) {
|
|
69
|
+
const time = new Date(entry.time).toLocaleString();
|
|
70
|
+
const rate = parseFloat(entry.fundingRate);
|
|
71
|
+
const annualized = annualizeFundingRate(rate);
|
|
72
|
+
const premium = parseFloat(entry.premium);
|
|
73
|
+
totalRate += rate;
|
|
74
|
+
console.log(time.padEnd(20) +
|
|
75
|
+
formatPercent(rate, 6).padEnd(16) +
|
|
76
|
+
formatPercent(annualized).padEnd(14) +
|
|
77
|
+
formatPercent(premium, 4));
|
|
78
|
+
}
|
|
79
|
+
// Summary
|
|
80
|
+
const avgRate = totalRate / history.length;
|
|
81
|
+
const avgAnnualized = annualizeFundingRate(avgRate);
|
|
82
|
+
console.log('─'.repeat(60));
|
|
83
|
+
console.log(`Samples: ${history.length}`);
|
|
84
|
+
console.log(`Avg Hourly Rate: ${formatPercent(avgRate, 6)}`);
|
|
85
|
+
console.log(`Avg Annualized: ${formatPercent(avgAnnualized)}`);
|
|
86
|
+
if (avgRate > 0) {
|
|
87
|
+
console.log('Longs pay shorts');
|
|
88
|
+
}
|
|
89
|
+
else if (avgRate < 0) {
|
|
90
|
+
console.log('Shorts pay longs');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.error('Error fetching funding history:', error);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"funding-scan.d.ts","sourceRoot":"","sources":["../../scripts/info/funding-scan.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// Cross-dex funding rate scanner - scan all assets (main + HIP-3) for funding opportunities
|
|
3
|
+
import { getClient } from '../core/client.js';
|
|
4
|
+
import { formatUsd, annualizeFundingRate, parseArgs, sleep } from '../core/utils.js';
|
|
5
|
+
function printUsage() {
|
|
6
|
+
console.log(`
|
|
7
|
+
Usage: openbroker funding-scan [options]
|
|
8
|
+
|
|
9
|
+
Scan funding rates across all dexes (main perps + HIP-3) and find opportunities.
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
--threshold <n> Min annualized funding rate % to show (default: 25)
|
|
13
|
+
--include-hip3 Include HIP-3 dex assets (default: true)
|
|
14
|
+
--main-only Only scan main perps
|
|
15
|
+
--hip3-only Only scan HIP-3 perps
|
|
16
|
+
--top <n> Show top N results (default: 30)
|
|
17
|
+
--pairs Show correlated pairs with opposing funding
|
|
18
|
+
--json Output as JSON
|
|
19
|
+
--watch Re-scan periodically
|
|
20
|
+
--interval <n> Watch interval in seconds (default: 60)
|
|
21
|
+
--help, -h Show this help
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
openbroker funding-scan
|
|
25
|
+
openbroker funding-scan --threshold 50 --pairs
|
|
26
|
+
openbroker funding-scan --hip3-only --top 20
|
|
27
|
+
openbroker funding-scan --watch --interval 120
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
async function scanFunding(client, options) {
|
|
31
|
+
const allPerps = await client.getAllPerpMetas();
|
|
32
|
+
const results = [];
|
|
33
|
+
for (const dexData of allPerps) {
|
|
34
|
+
const isMain = !dexData.dexName;
|
|
35
|
+
if (options.mainOnly && !isMain)
|
|
36
|
+
continue;
|
|
37
|
+
if (options.hip3Only && isMain)
|
|
38
|
+
continue;
|
|
39
|
+
for (let i = 0; i < dexData.meta.universe.length; i++) {
|
|
40
|
+
const asset = dexData.meta.universe[i];
|
|
41
|
+
const ctx = dexData.assetCtxs[i];
|
|
42
|
+
if (!ctx)
|
|
43
|
+
continue;
|
|
44
|
+
const hourlyRate = parseFloat(ctx.funding);
|
|
45
|
+
const annualizedPct = annualizeFundingRate(hourlyRate) * 100;
|
|
46
|
+
const openInterest = parseFloat(ctx.openInterest);
|
|
47
|
+
const markPx = parseFloat(ctx.markPx);
|
|
48
|
+
if (Math.abs(annualizedPct) < options.threshold)
|
|
49
|
+
continue;
|
|
50
|
+
if (openInterest < 100)
|
|
51
|
+
continue;
|
|
52
|
+
// API returns HIP-3 names already prefixed (e.g., "xyz:CL")
|
|
53
|
+
const coin = asset.name;
|
|
54
|
+
let assetId = isMain ? i : -1;
|
|
55
|
+
if (!isMain) {
|
|
56
|
+
try {
|
|
57
|
+
assetId = client.getAssetIndex(coin);
|
|
58
|
+
}
|
|
59
|
+
catch { /* not registered */ }
|
|
60
|
+
}
|
|
61
|
+
results.push({
|
|
62
|
+
coin,
|
|
63
|
+
assetId,
|
|
64
|
+
dex: dexData.dexName ?? 'main',
|
|
65
|
+
hourlyRate,
|
|
66
|
+
annualizedPct,
|
|
67
|
+
direction: hourlyRate > 0 ? 'longs pay' : 'shorts pay',
|
|
68
|
+
openInterest,
|
|
69
|
+
markPx,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Sort by absolute annualized rate
|
|
74
|
+
results.sort((a, b) => Math.abs(b.annualizedPct) - Math.abs(a.annualizedPct));
|
|
75
|
+
return results.slice(0, options.topN);
|
|
76
|
+
}
|
|
77
|
+
function printResults(results, showPairs) {
|
|
78
|
+
if (results.length === 0) {
|
|
79
|
+
console.log('No assets above threshold');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Table header
|
|
83
|
+
console.log('Coin'.padEnd(16) +
|
|
84
|
+
'Dex'.padEnd(8) +
|
|
85
|
+
'Annualized'.padEnd(14) +
|
|
86
|
+
'Direction'.padEnd(14) +
|
|
87
|
+
'OI'.padEnd(14) +
|
|
88
|
+
'Mark');
|
|
89
|
+
console.log('─'.repeat(75));
|
|
90
|
+
for (const r of results) {
|
|
91
|
+
const annStr = `${r.annualizedPct >= 0 ? '+' : ''}${r.annualizedPct.toFixed(1)}%`;
|
|
92
|
+
const oiStr = formatOI(r.openInterest);
|
|
93
|
+
console.log(r.coin.padEnd(16) +
|
|
94
|
+
r.dex.padEnd(8) +
|
|
95
|
+
annStr.padStart(12).padEnd(14) +
|
|
96
|
+
r.direction.padEnd(14) +
|
|
97
|
+
oiStr.padStart(12).padEnd(14) +
|
|
98
|
+
formatUsd(r.markPx));
|
|
99
|
+
}
|
|
100
|
+
// Show opposing pairs
|
|
101
|
+
if (showPairs) {
|
|
102
|
+
console.log('\nOpposing Funding Pairs:');
|
|
103
|
+
console.log('─'.repeat(75));
|
|
104
|
+
const longs = results.filter(r => r.annualizedPct > 0); // longs pay shorts
|
|
105
|
+
const shorts = results.filter(r => r.annualizedPct < 0); // shorts pay longs
|
|
106
|
+
const pairs = [];
|
|
107
|
+
for (const l of longs) {
|
|
108
|
+
for (const s of shorts) {
|
|
109
|
+
// Only pair across different dexes or correlated assets
|
|
110
|
+
const spread = l.annualizedPct + Math.abs(s.annualizedPct);
|
|
111
|
+
if (spread > 20) {
|
|
112
|
+
pairs.push({ long: l, short: s, spread });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
pairs.sort((a, b) => b.spread - a.spread);
|
|
117
|
+
for (const p of pairs.slice(0, 10)) {
|
|
118
|
+
console.log(` SHORT ${p.long.coin.padEnd(14)} (${p.long.annualizedPct.toFixed(1)}%) ` +
|
|
119
|
+
`+ LONG ${p.short.coin.padEnd(14)} (${p.short.annualizedPct.toFixed(1)}%) ` +
|
|
120
|
+
`= ${p.spread.toFixed(1)}% spread`);
|
|
121
|
+
}
|
|
122
|
+
if (pairs.length === 0) {
|
|
123
|
+
console.log(' No strong opposing pairs found');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function formatOI(oi) {
|
|
128
|
+
if (oi >= 1_000_000)
|
|
129
|
+
return `$${(oi / 1_000_000).toFixed(2)}M`;
|
|
130
|
+
if (oi >= 1_000)
|
|
131
|
+
return `$${(oi / 1_000).toFixed(1)}K`;
|
|
132
|
+
return `$${oi.toFixed(0)}`;
|
|
133
|
+
}
|
|
134
|
+
async function main() {
|
|
135
|
+
const args = parseArgs(process.argv.slice(2));
|
|
136
|
+
if (args.help || args.h) {
|
|
137
|
+
printUsage();
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
const threshold = args.threshold ? parseFloat(args.threshold) : 25;
|
|
141
|
+
const mainOnly = args['main-only'] ?? false;
|
|
142
|
+
const hip3Only = args['hip3-only'] ?? false;
|
|
143
|
+
const topN = parseInt(args.top) || 30;
|
|
144
|
+
const showPairs = args.pairs ?? false;
|
|
145
|
+
const outputJson = args.json ?? false;
|
|
146
|
+
const watch = args.watch ?? false;
|
|
147
|
+
const intervalSec = args.interval ? parseInt(args.interval) : 60;
|
|
148
|
+
const client = getClient();
|
|
149
|
+
if (args.verbose)
|
|
150
|
+
client.verbose = true;
|
|
151
|
+
console.log('Open Broker - Funding Rate Scanner');
|
|
152
|
+
console.log('==================================\n');
|
|
153
|
+
console.log(`Threshold: ${threshold}% annualized | Scope: ${mainOnly ? 'main only' : hip3Only ? 'HIP-3 only' : 'all dexes'}\n`);
|
|
154
|
+
if (client.isTestnet && !mainOnly) {
|
|
155
|
+
console.log('Note: Testnet — HIP-3 dexes not auto-loaded. Only main perps will be scanned.\n');
|
|
156
|
+
}
|
|
157
|
+
const options = { threshold, mainOnly, hip3Only, topN };
|
|
158
|
+
const runScan = async () => {
|
|
159
|
+
const results = await scanFunding(client, options);
|
|
160
|
+
if (outputJson) {
|
|
161
|
+
console.log(JSON.stringify(results, null, 2));
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
printResults(results, showPairs);
|
|
165
|
+
}
|
|
166
|
+
return results;
|
|
167
|
+
};
|
|
168
|
+
await runScan();
|
|
169
|
+
if (watch) {
|
|
170
|
+
console.log(`\nWatching every ${intervalSec}s... (Ctrl+C to stop)\n`);
|
|
171
|
+
while (true) {
|
|
172
|
+
await sleep(intervalSec * 1000);
|
|
173
|
+
console.log(`\n[${new Date().toLocaleTimeString()}] Rescanning...\n`);
|
|
174
|
+
await runScan();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
main().catch(err => { console.error(err); process.exit(1); });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"funding.d.ts","sourceRoot":"","sources":["../../scripts/info/funding.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// Get funding rates from Hyperliquid
|
|
3
|
+
import { getClient } from '../core/client.js';
|
|
4
|
+
import { annualizeFundingRate, parseArgs } from '../core/utils.js';
|
|
5
|
+
async function main() {
|
|
6
|
+
const args = parseArgs(process.argv.slice(2));
|
|
7
|
+
const topN = parseInt(args.top) || 20;
|
|
8
|
+
const filterCoin = args.coin;
|
|
9
|
+
const sortBy = args.sort || 'annualized'; // annualized, hourly, oi
|
|
10
|
+
const showAll = args.all;
|
|
11
|
+
const jsonOutput = args.json;
|
|
12
|
+
const client = getClient();
|
|
13
|
+
const includeHip3 = args['include-hip3'] || args['hip3'] || (filterCoin?.includes(':') ?? false);
|
|
14
|
+
try {
|
|
15
|
+
const meta = await client.getMetaAndAssetCtxs();
|
|
16
|
+
const fundingData = [];
|
|
17
|
+
// Main dex assets
|
|
18
|
+
for (let i = 0; i < meta.meta.universe.length; i++) {
|
|
19
|
+
const asset = meta.meta.universe[i];
|
|
20
|
+
const ctx = meta.assetCtxs[i];
|
|
21
|
+
if (filterCoin && asset.name !== filterCoin)
|
|
22
|
+
continue;
|
|
23
|
+
const hourlyRate = parseFloat(ctx.funding);
|
|
24
|
+
const annualizedRate = annualizeFundingRate(hourlyRate);
|
|
25
|
+
const premium = parseFloat(ctx.premium ?? '0');
|
|
26
|
+
const openInterest = parseFloat(ctx.openInterest);
|
|
27
|
+
const markPx = parseFloat(ctx.markPx);
|
|
28
|
+
// Skip if OI is very low (unless showing all)
|
|
29
|
+
if (!showAll && openInterest < 10000)
|
|
30
|
+
continue;
|
|
31
|
+
fundingData.push({
|
|
32
|
+
coin: asset.name,
|
|
33
|
+
assetId: i,
|
|
34
|
+
hourlyRate,
|
|
35
|
+
annualizedRate,
|
|
36
|
+
premium,
|
|
37
|
+
openInterest,
|
|
38
|
+
markPx,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Include HIP-3 dex assets
|
|
42
|
+
if (client.isTestnet && (includeHip3 || showAll) && !(filterCoin?.includes(':'))) {
|
|
43
|
+
console.log('Note: Testnet — HIP-3 dexes not auto-loaded. Use --coin dexName:COIN to view a specific HIP-3 asset.\n');
|
|
44
|
+
}
|
|
45
|
+
if (includeHip3 || showAll || (filterCoin && filterCoin.includes(':'))) {
|
|
46
|
+
// On testnet, load the specific dex on demand if user specified dex:COIN
|
|
47
|
+
if (client.isTestnet && filterCoin?.includes(':')) {
|
|
48
|
+
await client.loadSingleHip3Dex(filterCoin.split(':')[0]);
|
|
49
|
+
}
|
|
50
|
+
const allPerps = await client.getAllPerpMetas();
|
|
51
|
+
for (const dexData of allPerps) {
|
|
52
|
+
if (!dexData.dexName)
|
|
53
|
+
continue; // Skip main dex (already loaded)
|
|
54
|
+
for (let i = 0; i < dexData.meta.universe.length; i++) {
|
|
55
|
+
const asset = dexData.meta.universe[i];
|
|
56
|
+
const ctx = dexData.assetCtxs[i];
|
|
57
|
+
if (!ctx)
|
|
58
|
+
continue;
|
|
59
|
+
// API returns names already prefixed (e.g., "xyz:CL")
|
|
60
|
+
const coinName = asset.name;
|
|
61
|
+
if (filterCoin && coinName !== filterCoin)
|
|
62
|
+
continue;
|
|
63
|
+
const hourlyRate = parseFloat(ctx.funding);
|
|
64
|
+
const annualizedRate = annualizeFundingRate(hourlyRate);
|
|
65
|
+
const openInterest = parseFloat(ctx.openInterest);
|
|
66
|
+
const markPx = parseFloat(ctx.markPx);
|
|
67
|
+
if (!showAll && openInterest < 1000)
|
|
68
|
+
continue;
|
|
69
|
+
let assetId = -1;
|
|
70
|
+
try {
|
|
71
|
+
assetId = client.getAssetIndex(coinName);
|
|
72
|
+
}
|
|
73
|
+
catch { /* not registered */ }
|
|
74
|
+
fundingData.push({
|
|
75
|
+
coin: coinName,
|
|
76
|
+
assetId,
|
|
77
|
+
hourlyRate,
|
|
78
|
+
annualizedRate,
|
|
79
|
+
premium: 0,
|
|
80
|
+
openInterest,
|
|
81
|
+
markPx,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Sort
|
|
87
|
+
if (sortBy === 'hourly' || sortBy === 'annualized') {
|
|
88
|
+
fundingData.sort((a, b) => Math.abs(b.annualizedRate) - Math.abs(a.annualizedRate));
|
|
89
|
+
}
|
|
90
|
+
else if (sortBy === 'oi') {
|
|
91
|
+
fundingData.sort((a, b) => b.openInterest - a.openInterest);
|
|
92
|
+
}
|
|
93
|
+
// Limit
|
|
94
|
+
const displayData = filterCoin ? fundingData : fundingData.slice(0, topN);
|
|
95
|
+
if (jsonOutput) {
|
|
96
|
+
console.log(JSON.stringify(displayData, null, 2));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (displayData.length === 0) {
|
|
100
|
+
console.log(filterCoin ? `No data for ${filterCoin}` : 'No funding data available');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
console.log('Open Broker - Funding Rates');
|
|
104
|
+
console.log('===========================\n');
|
|
105
|
+
// Table header
|
|
106
|
+
console.log('Coin | Hourly Rate | Annualized | Premium | Open Interest | Mark');
|
|
107
|
+
console.log('---------|-------------|------------|-------------|---------------|----------');
|
|
108
|
+
for (const data of displayData) {
|
|
109
|
+
const hourlyStr = formatRate(data.hourlyRate * 100, 6);
|
|
110
|
+
const annualStr = formatRate(data.annualizedRate * 100, 2);
|
|
111
|
+
const premiumStr = formatRate(data.premium * 100, 4);
|
|
112
|
+
const oiStr = formatOI(data.openInterest);
|
|
113
|
+
const markStr = formatMark(data.markPx);
|
|
114
|
+
console.log(`${data.coin.padEnd(8)} | ${hourlyStr.padStart(11)} | ${annualStr.padStart(10)} | ` +
|
|
115
|
+
`${premiumStr.padStart(11)} | ${oiStr.padStart(13)} | ${markStr.padStart(8)}`);
|
|
116
|
+
}
|
|
117
|
+
// Legend
|
|
118
|
+
console.log('\nLegend:');
|
|
119
|
+
console.log(' Hourly Rate: Funding rate per hour');
|
|
120
|
+
console.log(' Annualized: Hourly × 8760 hours');
|
|
121
|
+
console.log(' Premium: (Mark - Oracle) / Oracle');
|
|
122
|
+
console.log(' OI: Open Interest in contracts');
|
|
123
|
+
// Highlight high funding opportunities
|
|
124
|
+
const highFunding = fundingData.filter(d => Math.abs(d.annualizedRate) > 0.25);
|
|
125
|
+
if (highFunding.length > 0 && !filterCoin) {
|
|
126
|
+
console.log('\n💰 High Funding Opportunities (>25% annualized):');
|
|
127
|
+
for (const data of highFunding.slice(0, 5)) {
|
|
128
|
+
const direction = data.annualizedRate > 0 ? 'SHORT pays LONG' : 'LONG pays SHORT';
|
|
129
|
+
console.log(` ${data.coin}: ${formatRate(data.annualizedRate * 100, 1)}% - ${direction}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error('Error fetching funding rates:', error);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function formatRate(rate, decimals) {
|
|
139
|
+
const sign = rate >= 0 ? '+' : '';
|
|
140
|
+
return `${sign}${rate.toFixed(decimals)}%`;
|
|
141
|
+
}
|
|
142
|
+
function formatOI(oi) {
|
|
143
|
+
if (oi >= 1_000_000)
|
|
144
|
+
return `${(oi / 1_000_000).toFixed(2)}M`;
|
|
145
|
+
if (oi >= 1_000)
|
|
146
|
+
return `${(oi / 1_000).toFixed(1)}K`;
|
|
147
|
+
return oi.toFixed(0);
|
|
148
|
+
}
|
|
149
|
+
function formatMark(price) {
|
|
150
|
+
if (price >= 10000)
|
|
151
|
+
return `$${(price / 1000).toFixed(1)}K`;
|
|
152
|
+
if (price >= 100)
|
|
153
|
+
return `$${price.toFixed(0)}`;
|
|
154
|
+
if (price >= 1)
|
|
155
|
+
return `$${price.toFixed(2)}`;
|
|
156
|
+
return `$${price.toFixed(4)}`;
|
|
157
|
+
}
|
|
158
|
+
main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markets.d.ts","sourceRoot":"","sources":["../../scripts/info/markets.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// Get market info from Hyperliquid
|
|
3
|
+
import { getClient } from '../core/client.js';
|
|
4
|
+
import { formatUsd, parseArgs } from '../core/utils.js';
|
|
5
|
+
async function main() {
|
|
6
|
+
const args = parseArgs(process.argv.slice(2));
|
|
7
|
+
const filterCoin = args.coin;
|
|
8
|
+
const topN = parseInt(args.top) || 30;
|
|
9
|
+
const sortBy = args.sort || 'volume'; // volume, oi, change
|
|
10
|
+
const jsonOutput = args.json;
|
|
11
|
+
const client = getClient();
|
|
12
|
+
const includeHip3 = args['include-hip3'] || args['hip3'] || (filterCoin?.includes(':') ?? false);
|
|
13
|
+
try {
|
|
14
|
+
const meta = await client.getMetaAndAssetCtxs();
|
|
15
|
+
const markets = [];
|
|
16
|
+
for (let i = 0; i < meta.meta.universe.length; i++) {
|
|
17
|
+
const asset = meta.meta.universe[i];
|
|
18
|
+
const ctx = meta.assetCtxs[i];
|
|
19
|
+
if (filterCoin && asset.name !== filterCoin)
|
|
20
|
+
continue;
|
|
21
|
+
const markPx = parseFloat(ctx.markPx);
|
|
22
|
+
const oraclePx = parseFloat(ctx.oraclePx);
|
|
23
|
+
const prevDayPx = parseFloat(ctx.prevDayPx);
|
|
24
|
+
const volume24h = parseFloat(ctx.dayNtlVlm);
|
|
25
|
+
const openInterest = parseFloat(ctx.openInterest);
|
|
26
|
+
const change24h = prevDayPx > 0 ? (markPx - prevDayPx) / prevDayPx : 0;
|
|
27
|
+
markets.push({
|
|
28
|
+
coin: asset.name,
|
|
29
|
+
assetId: i,
|
|
30
|
+
markPx,
|
|
31
|
+
oraclePx,
|
|
32
|
+
prevDayPx,
|
|
33
|
+
change24h,
|
|
34
|
+
volume24h,
|
|
35
|
+
openInterest,
|
|
36
|
+
maxLeverage: asset.maxLeverage,
|
|
37
|
+
szDecimals: asset.szDecimals,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// Include HIP-3 markets
|
|
41
|
+
if (client.isTestnet && includeHip3 && !(filterCoin?.includes(':'))) {
|
|
42
|
+
console.log('Note: Testnet — HIP-3 dexes not auto-loaded. Use --coin dexName:COIN to view a specific HIP-3 asset.\n');
|
|
43
|
+
}
|
|
44
|
+
if (includeHip3 || (filterCoin && filterCoin.includes(':'))) {
|
|
45
|
+
// On testnet, load the specific dex on demand if user specified dex:COIN
|
|
46
|
+
if (client.isTestnet && filterCoin?.includes(':')) {
|
|
47
|
+
await client.loadSingleHip3Dex(filterCoin.split(':')[0]);
|
|
48
|
+
}
|
|
49
|
+
const allPerps = await client.getAllPerpMetas();
|
|
50
|
+
for (const dexData of allPerps) {
|
|
51
|
+
if (!dexData.dexName)
|
|
52
|
+
continue;
|
|
53
|
+
for (let i = 0; i < dexData.meta.universe.length; i++) {
|
|
54
|
+
const asset = dexData.meta.universe[i];
|
|
55
|
+
const ctx = dexData.assetCtxs[i];
|
|
56
|
+
if (!ctx)
|
|
57
|
+
continue;
|
|
58
|
+
// API returns names already prefixed (e.g., "xyz:CL")
|
|
59
|
+
const coinName = asset.name;
|
|
60
|
+
if (filterCoin && coinName !== filterCoin)
|
|
61
|
+
continue;
|
|
62
|
+
const markPx = parseFloat(ctx.markPx);
|
|
63
|
+
const oraclePx = parseFloat(ctx.oraclePx);
|
|
64
|
+
const prevDayPx = parseFloat(ctx.prevDayPx);
|
|
65
|
+
const volume24h = parseFloat(ctx.dayNtlVlm);
|
|
66
|
+
const openInterest = parseFloat(ctx.openInterest);
|
|
67
|
+
const change24h = prevDayPx > 0 ? (markPx - prevDayPx) / prevDayPx : 0;
|
|
68
|
+
let assetId = -1;
|
|
69
|
+
try {
|
|
70
|
+
assetId = client.getAssetIndex(coinName);
|
|
71
|
+
}
|
|
72
|
+
catch { /* not registered */ }
|
|
73
|
+
markets.push({
|
|
74
|
+
coin: coinName,
|
|
75
|
+
assetId,
|
|
76
|
+
markPx,
|
|
77
|
+
oraclePx,
|
|
78
|
+
prevDayPx,
|
|
79
|
+
change24h,
|
|
80
|
+
volume24h,
|
|
81
|
+
openInterest,
|
|
82
|
+
maxLeverage: asset.maxLeverage,
|
|
83
|
+
szDecimals: asset.szDecimals,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Sort
|
|
89
|
+
if (sortBy === 'volume') {
|
|
90
|
+
markets.sort((a, b) => b.volume24h - a.volume24h);
|
|
91
|
+
}
|
|
92
|
+
else if (sortBy === 'oi') {
|
|
93
|
+
markets.sort((a, b) => b.openInterest - a.openInterest);
|
|
94
|
+
}
|
|
95
|
+
else if (sortBy === 'change') {
|
|
96
|
+
markets.sort((a, b) => Math.abs(b.change24h) - Math.abs(a.change24h));
|
|
97
|
+
}
|
|
98
|
+
// Limit
|
|
99
|
+
const displayData = filterCoin ? markets : markets.slice(0, topN);
|
|
100
|
+
if (jsonOutput) {
|
|
101
|
+
console.log(JSON.stringify(displayData, null, 2));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
console.log('Open Broker - Markets');
|
|
105
|
+
console.log('=====================\n');
|
|
106
|
+
if (displayData.length === 0) {
|
|
107
|
+
console.log(filterCoin ? `No data for ${filterCoin}` : 'No market data available');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (filterCoin && displayData.length === 1) {
|
|
111
|
+
// Detailed view for single coin
|
|
112
|
+
const m = displayData[0];
|
|
113
|
+
console.log(`${m.coin}`);
|
|
114
|
+
console.log('─'.repeat(40));
|
|
115
|
+
console.log(`Mark Price: ${formatUsd(m.markPx)}`);
|
|
116
|
+
console.log(`Oracle Price: ${formatUsd(m.oraclePx)}`);
|
|
117
|
+
console.log(`24h Change: ${formatChange(m.change24h)}`);
|
|
118
|
+
console.log(`24h Volume: ${formatVolume(m.volume24h)}`);
|
|
119
|
+
console.log(`Open Interest: ${formatVolume(m.openInterest * m.markPx)} (${formatOI(m.openInterest)} contracts)`);
|
|
120
|
+
console.log(`Max Leverage: ${m.maxLeverage}x`);
|
|
121
|
+
console.log(`Size Decimals: ${m.szDecimals}`);
|
|
122
|
+
console.log(`Min Size: ${(10 ** -m.szDecimals).toFixed(m.szDecimals)}`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// Table view
|
|
126
|
+
console.log('Coin | Mark | 24h Chg | 24h Volume | OI | Lev');
|
|
127
|
+
console.log('---------|--------------|------------|---------------|---------------|-----');
|
|
128
|
+
for (const m of displayData) {
|
|
129
|
+
console.log(`${m.coin.padEnd(8)} | ${formatUsd(m.markPx).padStart(12)} | ` +
|
|
130
|
+
`${formatChange(m.change24h).padStart(10)} | ` +
|
|
131
|
+
`${formatVolume(m.volume24h).padStart(13)} | ` +
|
|
132
|
+
`${formatVolume(m.openInterest * m.markPx).padStart(13)} | ` +
|
|
133
|
+
`${m.maxLeverage}x`);
|
|
134
|
+
}
|
|
135
|
+
// Top movers
|
|
136
|
+
if (!filterCoin) {
|
|
137
|
+
const gainers = [...markets].sort((a, b) => b.change24h - a.change24h).slice(0, 3);
|
|
138
|
+
const losers = [...markets].sort((a, b) => a.change24h - b.change24h).slice(0, 3);
|
|
139
|
+
console.log('\n📈 Top Gainers:');
|
|
140
|
+
for (const m of gainers) {
|
|
141
|
+
if (m.change24h <= 0)
|
|
142
|
+
break;
|
|
143
|
+
console.log(` ${m.coin}: ${formatChange(m.change24h)}`);
|
|
144
|
+
}
|
|
145
|
+
console.log('\n📉 Top Losers:');
|
|
146
|
+
for (const m of losers) {
|
|
147
|
+
if (m.change24h >= 0)
|
|
148
|
+
break;
|
|
149
|
+
console.log(` ${m.coin}: ${formatChange(m.change24h)}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
console.error('Error fetching market data:', error);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function formatChange(change) {
|
|
159
|
+
const sign = change >= 0 ? '+' : '';
|
|
160
|
+
return `${sign}${(change * 100).toFixed(2)}%`;
|
|
161
|
+
}
|
|
162
|
+
function formatVolume(volume) {
|
|
163
|
+
if (volume >= 1_000_000_000)
|
|
164
|
+
return `$${(volume / 1_000_000_000).toFixed(2)}B`;
|
|
165
|
+
if (volume >= 1_000_000)
|
|
166
|
+
return `$${(volume / 1_000_000).toFixed(2)}M`;
|
|
167
|
+
if (volume >= 1_000)
|
|
168
|
+
return `$${(volume / 1_000).toFixed(1)}K`;
|
|
169
|
+
return `$${volume.toFixed(0)}`;
|
|
170
|
+
}
|
|
171
|
+
function formatOI(oi) {
|
|
172
|
+
if (oi >= 1_000_000)
|
|
173
|
+
return `${(oi / 1_000_000).toFixed(2)}M`;
|
|
174
|
+
if (oi >= 1_000)
|
|
175
|
+
return `${(oi / 1_000).toFixed(1)}K`;
|
|
176
|
+
return oi.toFixed(0);
|
|
177
|
+
}
|
|
178
|
+
main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order-status.d.ts","sourceRoot":"","sources":["../../scripts/info/order-status.ts"],"names":[],"mappings":""}
|