Bots de Trading en Hyperliquid con Bun: Guia Completa del SDK (2026)
Hyperliquid es el DEX de perpetuos de mayor crecimiento, procesando miles de millones en volumen diario con ejecucion en menos de un segundo. Su paquete oficial de npm facilita la construccion de bots de trading con Bun. Esta guia cubre la configuracion, ejecucion de ordenes y estrategias de produccion.
Comienza en Hyperliquid: Registrate con nuestro enlace de referido para apoyar esta guia y operar con comisiones reducidas.
Que es Hyperliquid?
Hyperliquid es un exchange descentralizado de futuros perpetuos que funciona en su propia blockchain L1. Caracteristicas principales:
- Velocidad: ~200ms de tiempo de bloque, capacidad de 100k+ TPS
- Liquidez: $1B+ de volumen diario, spreads ajustados
- Sin comisiones de gas: El trading es gratuito (solo pagos de financiamiento)
- Auto-custodia: Opera directamente desde tu billetera
- Apalancamiento 50x: En pares principales
Configuracion con Bun
Instalacion
# Create project
mkdir hyperliquid-bot && cd hyperliquid-bot
bun init -y
# Install Hyperliquid SDK
bun add hyperliquid
# Install additional dependencies
bun add dotenv
Estructura del Proyecto
hyperliquid-bot/
├── src/
│ ├── index.ts
│ ├── client.ts
│ ├── strategies/
│ │ └── grid.ts
│ └── utils/
│ └── helpers.ts
├── .env
├── package.json
└── tsconfig.json
Configuracion del Entorno
# .env
HYPERLIQUID_PRIVATE_KEY=your_private_key_here
HYPERLIQUID_WALLET_ADDRESS=0x...
# Use mainnet or testnet
HYPERLIQUID_TESTNET=false
Configuracion del Cliente
// src/client.ts
import Hyperliquid from "hyperliquid";
import "dotenv/config";
const privateKey = process.env.HYPERLIQUID_PRIVATE_KEY;
const walletAddress = process.env.HYPERLIQUID_WALLET_ADDRESS;
const testnet = process.env.HYPERLIQUID_TESTNET === "true";
if (!privateKey || !walletAddress) {
throw new Error("Missing HYPERLIQUID_PRIVATE_KEY or HYPERLIQUID_WALLET_ADDRESS");
}
export const sdk = new Hyperliquid({
privateKey,
testnet,
walletAddress,
});
// Initialize the SDK
export async function initClient() {
await sdk.connect();
console.log(`Connected to Hyperliquid ${testnet ? "testnet" : "mainnet"}`);
return sdk;
}
Lectura de Datos del Mercado
Obtener Todos los Mercados
// Get available perpetual markets
async function getMarkets() {
const meta = await sdk.info.perpetuals.getMeta();
console.log("Available markets:");
for (const asset of meta.universe) {
console.log(`${asset.name}: ${asset.szDecimals} decimals, max leverage ${asset.maxLeverage}x`);
}
return meta.universe;
}
Obtener el Libro de Ordenes
// Fetch orderbook for a symbol
async function getOrderbook(symbol: string) {
const book = await sdk.info.perpetuals.getL2Book({ coin: symbol });
console.log(`${symbol} Orderbook:`);
console.log("Bids:", book.levels[0].slice(0, 5)); // Top 5 bids
console.log("Asks:", book.levels[1].slice(0, 5)); // Top 5 asks
const bestBid = parseFloat(book.levels[0][0].px);
const bestAsk = parseFloat(book.levels[1][0].px);
const spread = ((bestAsk - bestBid) / bestBid * 100).toFixed(4);
console.log(`Spread: ${spread}%`);
return book;
}
// Usage
await getOrderbook("BTC");
Obtener Estado del Usuario
// Get account balance and positions
async function getAccountState() {
const state = await sdk.info.perpetuals.getClearinghouseState({
user: walletAddress,
});
console.log("Account equity:", state.marginSummary.accountValue);
console.log("Available balance:", state.withdrawable);
console.log("\nOpen positions:");
for (const position of state.assetPositions) {
if (parseFloat(position.position.szi) !== 0) {
console.log(`${position.position.coin}:`);
console.log(` Size: ${position.position.szi}`);
console.log(` Entry: ${position.position.entryPx}`);
console.log(` Unrealized PnL: ${position.position.unrealizedPnl}`);
}
}
return state;
}
Feeds de Precios en Tiempo Real
// Subscribe to price updates via WebSocket
async function subscribeToPrices(symbols: string[]) {
sdk.subscriptions.subscribeToAllMids((data) => {
for (const symbol of symbols) {
if (data.mids[symbol]) {
console.log(`${symbol}: $${data.mids[symbol]}`);
}
}
});
console.log(`Subscribed to: ${symbols.join(", ")}`);
}
// Subscribe to trades
async function subscribeToTrades(symbol: string) {
sdk.subscriptions.subscribeToTrades({ coin: symbol }, (trade) => {
console.log(`${symbol} Trade: ${trade.side} ${trade.sz} @ ${trade.px}`);
});
}
Ejecucion de Operaciones
Ordenes de Mercado
// Place a market order
async function marketOrder(
symbol: string,
isBuy: boolean,
size: number
) {
const order = await sdk.exchange.placeOrder({
coin: symbol,
is_buy: isBuy,
sz: size,
limit_px: isBuy ? 999999 : 0.01, // Market order simulation
order_type: { limit: { tif: "Ioc" } }, // Immediate or cancel
reduce_only: false,
});
console.log(`Market ${isBuy ? "buy" : "sell"} ${size} ${symbol}`);
console.log("Order response:", order);
return order;
}
// Usage
await marketOrder("ETH", true, 0.1); // Buy 0.1 ETH
Ordenes Limite
// Place a limit order
async function limitOrder(
symbol: string,
isBuy: boolean,
size: number,
price: number
) {
const order = await sdk.exchange.placeOrder({
coin: symbol,
is_buy: isBuy,
sz: size,
limit_px: price,
order_type: { limit: { tif: "Gtc" } }, // Good til cancelled
reduce_only: false,
});
console.log(`Limit ${isBuy ? "buy" : "sell"} ${size} ${symbol} @ ${price}`);
return order;
}
// Place order at 1% below current price
async function placeBuyAtDiscount(symbol: string, size: number) {
const book = await sdk.info.perpetuals.getL2Book({ coin: symbol });
const midPrice = (parseFloat(book.levels[0][0].px) + parseFloat(book.levels[1][0].px)) / 2;
const buyPrice = midPrice * 0.99; // 1% discount
return limitOrder(symbol, true, size, buyPrice);
}
Stop Loss y Take Profit
// Place a stop loss order
async function stopLoss(
symbol: string,
size: number,
triggerPrice: number
) {
const order = await sdk.exchange.placeOrder({
coin: symbol,
is_buy: false, // Sell to close long
sz: size,
limit_px: triggerPrice * 0.99, // Slightly below trigger
order_type: {
trigger: {
triggerPx: triggerPrice.toString(),
isMarket: true,
tpsl: "sl",
},
},
reduce_only: true,
});
console.log(`Stop loss set at ${triggerPrice}`);
return order;
}
// Place a take profit order
async function takeProfit(
symbol: string,
size: number,
triggerPrice: number
) {
const order = await sdk.exchange.placeOrder({
coin: symbol,
is_buy: false,
sz: size,
limit_px: triggerPrice * 1.01,
order_type: {
trigger: {
triggerPx: triggerPrice.toString(),
isMarket: true,
tpsl: "tp",
},
},
reduce_only: true,
});
console.log(`Take profit set at ${triggerPrice}`);
return order;
}
Cancelar Ordenes
// Cancel a specific order
async function cancelOrder(symbol: string, orderId: number) {
const result = await sdk.exchange.cancelOrder({
coin: symbol,
o: orderId,
});
console.log(`Cancelled order ${orderId}:`, result);
return result;
}
// Cancel all orders for a symbol
async function cancelAllOrders(symbol: string) {
const openOrders = await sdk.info.perpetuals.getOpenOrders({
user: walletAddress,
});
const symbolOrders = openOrders.filter(o => o.coin === symbol);
for (const order of symbolOrders) {
await cancelOrder(symbol, order.oid);
}
console.log(`Cancelled ${symbolOrders.length} orders for ${symbol}`);
}
// Cancel all orders across all symbols
async function cancelAllOrdersGlobal() {
const result = await sdk.exchange.cancelAllOrders();
console.log("Cancelled all orders:", result);
return result;
}
Construyendo un Bot de Trading de Cuadricula
// src/strategies/grid.ts
interface GridConfig {
symbol: string;
lowerPrice: number;
upperPrice: number;
gridCount: number;
totalSize: number;
}
class GridBot {
private config: GridConfig;
private gridPrices: number[] = [];
private orderSize: number;
constructor(config: GridConfig) {
this.config = config;
this.orderSize = config.totalSize / config.gridCount;
this.calculateGridPrices();
}
private calculateGridPrices() {
const { lowerPrice, upperPrice, gridCount } = this.config;
const step = (upperPrice - lowerPrice) / (gridCount - 1);
for (let i = 0; i < gridCount; i++) {
this.gridPrices.push(lowerPrice + step * i);
}
console.log("Grid prices:", this.gridPrices);
}
async placeInitialOrders() {
const book = await sdk.info.perpetuals.getL2Book({
coin: this.config.symbol
});
const midPrice = (
parseFloat(book.levels[0][0].px) +
parseFloat(book.levels[1][0].px)
) / 2;
console.log(`Current price: ${midPrice}`);
// Place buy orders below current price
// Place sell orders above current price
for (const price of this.gridPrices) {
const isBuy = price < midPrice;
await sdk.exchange.placeOrder({
coin: this.config.symbol,
is_buy: isBuy,
sz: this.orderSize,
limit_px: price,
order_type: { limit: { tif: "Gtc" } },
reduce_only: false,
});
console.log(`Placed ${isBuy ? "buy" : "sell"} @ ${price.toFixed(2)}`);
// Small delay to avoid rate limits
await Bun.sleep(100);
}
}
async monitorAndReplace() {
// Subscribe to fills and replace executed orders
sdk.subscriptions.subscribeToUserFills((fill) => {
console.log(`Fill: ${fill.side} ${fill.sz} @ ${fill.px}`);
// Place opposite order at next grid level
const fillPrice = parseFloat(fill.px);
const isBuy = fill.side === "B";
// Find next grid price
const gridIndex = this.gridPrices.findIndex(
p => Math.abs(p - fillPrice) < 1
);
if (gridIndex !== -1) {
const nextIndex = isBuy ? gridIndex + 1 : gridIndex - 1;
if (nextIndex >= 0 && nextIndex < this.gridPrices.length) {
const nextPrice = this.gridPrices[nextIndex];
// Place opposite order
sdk.exchange.placeOrder({
coin: this.config.symbol,
is_buy: !isBuy,
sz: this.orderSize,
limit_px: nextPrice,
order_type: { limit: { tif: "Gtc" } },
reduce_only: false,
});
console.log(`Replaced with ${!isBuy ? "buy" : "sell"} @ ${nextPrice}`);
}
}
});
}
}
// Usage
const gridBot = new GridBot({
symbol: "ETH",
lowerPrice: 2000,
upperPrice: 2200,
gridCount: 10,
totalSize: 1.0, // 1 ETH total
});
await gridBot.placeInitialOrders();
await gridBot.monitorAndReplace();
Gestion de Riesgo
// Position sizing based on account risk
async function calculatePositionSize(
symbol: string,
riskPercent: number,
stopLossPercent: number
): Promise {
const state = await sdk.info.perpetuals.getClearinghouseState({
user: walletAddress,
});
const accountValue = parseFloat(state.marginSummary.accountValue);
const riskAmount = accountValue * (riskPercent / 100);
const book = await sdk.info.perpetuals.getL2Book({ coin: symbol });
const currentPrice = (
parseFloat(book.levels[0][0].px) +
parseFloat(book.levels[1][0].px)
) / 2;
// Position size = Risk Amount / (Stop Loss Distance)
const stopLossDistance = currentPrice * (stopLossPercent / 100);
const positionSize = riskAmount / stopLossDistance;
console.log(`Account: $${accountValue.toFixed(2)}`);
console.log(`Risk: ${riskPercent}% = $${riskAmount.toFixed(2)}`);
console.log(`Position size: ${positionSize.toFixed(4)} ${symbol}`);
return positionSize;
}
// Example: Risk 1% with 2% stop loss
const size = await calculatePositionSize("BTC", 1, 2);
Manejo de Errores
// Robust order placement with retries
async function placeOrderWithRetry(
orderParams: any,
maxRetries = 3
): Promise {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await sdk.exchange.placeOrder(orderParams);
if (result.response.type === "error") {
throw new Error(result.response.data);
}
return result;
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error.message);
if (attempt === maxRetries) {
throw error;
}
// Exponential backoff
await Bun.sleep(1000 * Math.pow(2, attempt));
}
}
}
// Graceful shutdown
process.on("SIGINT", async () => {
console.log("\nShutting down...");
try {
await sdk.exchange.cancelAllOrders();
console.log("Cancelled all orders");
} catch (error) {
console.error("Error cancelling orders:", error);
}
process.exit(0);
});
Bot de Trading Completo
// src/index.ts
import { initClient, sdk } from "./client";
import "dotenv/config";
const walletAddress = process.env.HYPERLIQUID_WALLET_ADDRESS!;
interface BotConfig {
symbol: string;
sizeUsd: number;
takeProfitPercent: number;
stopLossPercent: number;
}
async function runBot(config: BotConfig) {
await initClient();
console.log(`Starting bot for ${config.symbol}`);
console.log(`Size: $${config.sizeUsd}`);
console.log(`TP: ${config.takeProfitPercent}%, SL: ${config.stopLossPercent}%`);
// Get current price
const book = await sdk.info.perpetuals.getL2Book({ coin: config.symbol });
const currentPrice = (
parseFloat(book.levels[0][0].px) +
parseFloat(book.levels[1][0].px)
) / 2;
const size = config.sizeUsd / currentPrice;
const tpPrice = currentPrice * (1 + config.takeProfitPercent / 100);
const slPrice = currentPrice * (1 - config.stopLossPercent / 100);
console.log(`Current price: $${currentPrice.toFixed(2)}`);
console.log(`Size: ${size.toFixed(4)} ${config.symbol}`);
console.log(`TP: $${tpPrice.toFixed(2)}, SL: $${slPrice.toFixed(2)}`);
// Place market buy
await sdk.exchange.placeOrder({
coin: config.symbol,
is_buy: true,
sz: size,
limit_px: currentPrice * 1.01,
order_type: { limit: { tif: "Ioc" } },
reduce_only: false,
});
console.log("Opened long position");
// Place TP and SL
await sdk.exchange.placeOrder({
coin: config.symbol,
is_buy: false,
sz: size,
limit_px: tpPrice,
order_type: {
trigger: {
triggerPx: tpPrice.toString(),
isMarket: true,
tpsl: "tp",
},
},
reduce_only: true,
});
await sdk.exchange.placeOrder({
coin: config.symbol,
is_buy: false,
sz: size,
limit_px: slPrice,
order_type: {
trigger: {
triggerPx: slPrice.toString(),
isMarket: true,
tpsl: "sl",
},
},
reduce_only: true,
});
console.log("Set TP and SL orders");
// Monitor position
sdk.subscriptions.subscribeToUserFills((fill) => {
console.log(`Fill: ${fill.side} ${fill.sz} @ ${fill.px}`);
if (fill.closedPnl) {
console.log(`Closed PnL: $${fill.closedPnl}`);
process.exit(0);
}
});
}
// Run
runBot({
symbol: "ETH",
sizeUsd: 100,
takeProfitPercent: 2,
stopLossPercent: 1,
});
Mejores Practicas
- Usa testnet primero: Siempre prueba estrategias en testnet antes de mainnet
- Comienza pequeno: Empieza con tamanos de posicion minimos
- Maneja desconexiones: Implementa logica de reconexion para WebSocket
- Registra todo: Mantiene logs detallados para depuracion
- Establece limites estrictos: Implementa tamano maximo de posicion y limites de perdida diaria
- Monitorea la latencia: Rastrea los tiempos de ejecucion de ordenes
Limites de Tasa de la API
| Endpoint | Limite de Tasa |
|---|---|
| Info (lectura) | 1200/min |
| Exchange (escritura) | 600/min |
| WebSocket | 50 suscripciones |
Conclusion
El paquete npm de Hyperliquid combinado con la velocidad de Bun lo hace ideal para construir bots de trading. Comienza con estrategias simples, implementa una gestion de riesgo adecuada, y siempre prueba exhaustivamente en testnet antes de desplegar capital. La combinacion de la baja latencia de Hyperliquid y cero comisiones de gas crea oportunidades que no serian viables en otros DEXs.
Listo para empezar a operar? Unete a Hyperliquid con nuestro enlace de referido y obtén descuentos en comisiones en tus operaciones.