Skip to content

Commit b653c90

Browse files
committed
improved leaderboard API
1 parent f19a916 commit b653c90

File tree

6 files changed

+369
-49
lines changed

6 files changed

+369
-49
lines changed

bun.lockb

346 Bytes
Binary file not shown.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "hyperliquid-ts",
33
"version": "1.0.1",
4-
"description": "🪖 Unofficial TypeScript SDK for Hyperliquid",
4+
"description": "Unofficial TypeScript SDK for Hyperliquid 🍧",
55
"main": "dist/index.js",
66
"module": "dist/index.js",
77
"types": "dist/index.d.ts",
@@ -34,7 +34,8 @@
3434
"dependencies": {
3535
"@msgpack/msgpack": "^3.0.0-beta2",
3636
"axios": "^1.7.4",
37-
"ethers": "^6.13.2"
37+
"ethers": "^6.13.2",
38+
"tasai": "^1.0.0"
3839
},
3940
"devDependencies": {
4041
"@typescript-eslint/eslint-plugin": "^5.62.0",
Lines changed: 151 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,151 @@
1-
import {HyperliquidAPI} from "../../src/api";
2-
3-
const api = new HyperliquidAPI();
4-
5-
// This will fetch from the API and cache the result
6-
const leaderboard1 = await api.leaderboard.getFilteredAndSortedLeaderboard({
7-
timeWindow: 'month',
8-
minAccountValue: 100_000,
9-
maxAccounts: 1
10-
}, 'roi');
11-
12-
// This will use the cached data
13-
const leaderboard2 = await api.leaderboard.getFilteredAndSortedLeaderboard({
14-
timeWindow: 'week',
15-
minVolume: 1_000_000,
16-
maxAccounts: 1
17-
}, 'pnl');
18-
19-
// If you need to clear the cache manually
20-
await api.leaderboard.clearCache();
21-
22-
// This will fetch from the API again and cache the new result
23-
const leaderboard3 = await api.leaderboard.getFilteredAndSortedLeaderboard({
24-
timeWindow: 'day',
25-
minRoi: 0.05,
26-
maxAccounts: 1
27-
}, 'accountValue');
28-
29-
console.log(JSON.stringify(leaderboard1, null, 2));
30-
console.log(JSON.stringify(leaderboard2, null, 2));
31-
console.log(JSON.stringify(leaderboard3, null, 2));
1+
import { HyperliquidAPI } from "../../src";
2+
import type { LeaderboardFilter, TimeWindow, TraderPosition } from "../../src";
3+
import {Color, t} from "tasai";
4+
const highlight = t.bold.cyan.toFunction();
5+
const header = t.bold.underline.magenta.toFunction();
6+
const subHeader = t.bold.yellow.toFunction();
7+
const positive = t.green.toFunction();
8+
const negative = t.red.toFunction();
9+
const ticker = t.bold.magenta.toFunction();
10+
const value = t.bold.white.toFunction();
11+
const divider = "─".repeat(50);
12+
13+
const leverageColor = (leverage: number) => {
14+
if (leverage < 3) return t.green.toFunction();
15+
if (leverage < 10) return t.fromColor(Color.ORANGE).toFunction(); // Orange
16+
return t.brightRed.toFunction();
17+
};
18+
19+
20+
async function runLeaderboardAnalysis() {
21+
const api = new HyperliquidAPI();
22+
23+
const filter: LeaderboardFilter = {
24+
timeWindow: 'month' as TimeWindow,
25+
minAccountValue: 100_000,
26+
minVolume: 1_000_000,
27+
maxVolume: 100_000_000,
28+
minPnL: 10_000,
29+
minRoi: 0.5,
30+
maxAccounts: 3
31+
};
32+
33+
try {
34+
console.log(header("Fetching leaderboard data..."));
35+
const leaderboard = await api.leaderboard.getLeaderboard();
36+
console.log(highlight(`Total traders: ${leaderboard.leaderboardRows.length}`));
37+
38+
console.log(header("\nFiltering leaderboard, this may take a while..."));
39+
const filteredLeaderboard = await api.leaderboard.filterLeaderboard(leaderboard, filter);
40+
41+
console.log(header("\nSorting leaderboard..."));
42+
const sortedLeaderboard = api.leaderboard.sortLeaderboard(filteredLeaderboard, 'pnl', filter.timeWindow);
43+
const topTraders = sortedLeaderboard.slice(0, filter.maxAccounts);
44+
45+
if (topTraders.length === 0) {
46+
console.log(negative("No traders found matching the specified criteria."));
47+
} else {
48+
console.log(header("\nTop Traders:"));
49+
for (const [index, trader] of topTraders.entries()) {
50+
const performance = trader.windowPerformances.find(([window]) => window === filter.timeWindow)?.[1];
51+
52+
console.log(divider);
53+
console.log(subHeader(`\n#${index + 1}:`));
54+
console.log(highlight(`Address: ${trader.ethAddress}`));
55+
console.log(`Account Value: ${value('$' + formatNumber(trader.accountValue))}`);
56+
console.log(`PnL: ${formatPnL(performance?.pnl)}`);
57+
console.log(`ROI: ${formatPercentage(performance?.roi)}`);
58+
console.log(`Volume: ${value('$' + formatNumber(performance?.vlm))}`);
59+
60+
// Fetch and display current positions
61+
const positions = await api.leaderboard.getTraderOpenPositions(trader.ethAddress);
62+
console.log(subHeader("\nCurrent Positions:"));
63+
if (positions.perp.length > 0 || positions.spot.length > 0) {
64+
if (positions.perp.length > 0) {
65+
console.log(highlight(" Perpetual:"));
66+
positions.perp.forEach(displayPosition);
67+
}
68+
if (positions.spot.length > 0) {
69+
console.log(highlight(" Spot:"));
70+
positions.spot.forEach(displayPosition);
71+
}
72+
} else {
73+
console.log(" No open positions");
74+
}
75+
76+
// Fetch and display best trade
77+
const bestTrade = await getBestTrade(api, trader.ethAddress, filter.timeWindow!);
78+
if (bestTrade) {
79+
console.log(subHeader("\nBest Trade:"));
80+
console.log(` Asset: ${ticker(bestTrade.coin)}`);
81+
console.log(` Side: ${bestTrade.side === 'buy' ? positive(bestTrade.side) : negative(bestTrade.side)}`);
82+
console.log(` Price: ${value('$' + formatNumber(bestTrade.px))}`);
83+
console.log(` Size: ${value(formatNumber(bestTrade.sz, 4))}`);
84+
console.log(` PnL: ${formatPnL(bestTrade.closedPnl)}`);
85+
console.log(` Time: ${new Date(bestTrade.time).toLocaleString()}`);
86+
} else {
87+
console.log(negative("\nNo trades found for this period"));
88+
}
89+
}
90+
}
91+
} catch (error) {
92+
console.error(negative("Error running leaderboard analysis:"), error);
93+
} finally {
94+
api.disconnect();
95+
}
96+
}
97+
98+
function displayPosition(position: TraderPosition) {
99+
console.log(` ${ticker(position.asset)}: ${value(formatNumber(position.size, 4))} @ $${value(formatNumber(position.entryPrice))}`);
100+
if (position.unrealizedPnl) {
101+
console.log(` Unrealized PnL: ${formatPnL(position.unrealizedPnl.toString())}`);
102+
}
103+
if (position.leverage && position.leverage !== 1) {
104+
const leverageStyled = leverageColor(position.leverage)(`${position.leverage}x`);
105+
console.log(` Leverage: ${leverageStyled}`);
106+
}
107+
}
108+
109+
async function getBestTrade(api: HyperliquidAPI, trader: string, timeWindow: TimeWindow) {
110+
const endTime = Date.now();
111+
const startTime = getStartTimeForWindow(timeWindow);
112+
const trades = await api.info.getUserFillsByTime(trader, startTime, endTime);
113+
return trades.reduce((best, current) => {
114+
if (!best || parseFloat(current.closedPnl) > parseFloat(best.closedPnl)) {
115+
return current;
116+
}
117+
return best;
118+
}, null as any);
119+
}
120+
121+
function getStartTimeForWindow(timeWindow: TimeWindow): number {
122+
const now = Date.now();
123+
switch (timeWindow) {
124+
case 'day': return now - 24 * 60 * 60 * 1000;
125+
case 'week': return now - 7 * 24 * 60 * 60 * 1000;
126+
case 'month': return now - 30 * 24 * 60 * 60 * 1000;
127+
default: return 0; // For 'allTime', return 0 to get all trades
128+
}
129+
}
130+
131+
function formatNumber(value: string | number | undefined, decimals: number = 2): string {
132+
return parseFloat(value?.toString() || '0').toLocaleString('en-US', {maximumFractionDigits: decimals});
133+
}
134+
135+
function formatPercentage(value: string | number | undefined): string {
136+
const percentage = (parseFloat(value?.toString() || '0') * 100).toFixed(2);
137+
return percentage.startsWith('-') ? negative(`${percentage}%`) : positive(`${percentage}%`);
138+
}
139+
140+
function formatPnL(value: string | number | undefined): string {
141+
const formatted = `$${formatNumber(value)}`;
142+
return parseFloat(value?.toString() || '0') >= 0 ? positive(formatted) : negative(formatted);
143+
}
144+
145+
runLeaderboardAnalysis().then(() => {
146+
console.log(highlight("\nAnalysis complete. Exiting..."));
147+
process.exit(0);
148+
}).catch(error => {
149+
console.error(negative("Unhandled error:"), error);
150+
process.exit(1);
151+
});

0 commit comments

Comments
 (0)