npm package: @plutarc/bybit
Description: Fully-typed Bybit V5 REST and WebSocket client for TypeScript/JavaScript
Author: sayedhfatimi
Latest version: 0.1.0

# @plutarc/bybit

A fully-typed Bybit V5 REST and WebSocket client for TypeScript/JavaScript. Zero runtime dependencies — uses only Node.js/Bun built-ins.

## Table of Contents

- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
  - [REST Client](#rest-client)
  - [Public WebSocket](#public-websocket)
  - [Private WebSocket](#private-websocket)
- [REST API Reference](#rest-api-reference)
  - [Client Options](#client-options)
  - [Market](#market)
  - [Order](#order)
  - [Position](#position)
  - [Account](#account)
  - [Asset](#asset)
  - [Spot Leveraged Tokens](#spot-leveraged-tokens)
  - [Spot Margin Trade](#spot-margin-trade)
- [WebSocket API Reference](#websocket-api-reference)
  - [WebSocket Options](#websocket-options)
  - [Public Topics](#public-topics)
  - [Private Topics](#private-topics)
  - [Lifecycle Events](#lifecycle-events)
  - [Connection Management](#connection-management)
- [Utilities](#utilities)
  - [Delta Parser](#delta-parser)
  - [OrderBook Store](#orderbook-store)
  - [Authentication Helpers](#authentication-helpers)
- [Error Handling](#error-handling)
- [Rate Limiting](#rate-limiting)
- [Testnet](#testnet)
- [Custom Logger](#custom-logger)
- [Development](#development)
- [Resources](#resources)
- [License](#license)

## Features

- **Complete V5 REST API** — Market, Order, Position, Account, and Asset endpoints with full TypeScript types
- **Real-time WebSocket** — Public (per-category) and private WebSocket clients with typed event emitters
- **Zero dependencies** — Uses only `node:crypto` and `node:events` built-ins
- **Fully typed** — Every request parameter and response field has TypeScript interfaces
- **Unified V5 API** — Single client for spot, linear, inverse, and option markets via `category` parameter
- **Rate limiting** — Built-in rolling window rate limiter that tracks `X-Bapi-Limit-Status`
- **Auto-retry** — Exponential backoff with jitter for retryable errors (429, 5xx)
- **Auto-reconnect** — WebSocket reconnection with exponential backoff
- **Ping/pong** — Automatic JSON heartbeat to keep WebSocket connections alive
- **Delta parser** — Generic WebSocket state management with orderbook convenience store
- **Authentication** — HMAC-SHA256 signing for both REST and WebSocket
- **Testnet support** — Single flag to switch between production and testnet
- **Dual format** — Ships ESM and CJS builds with full declaration files

## Installation

```bash
# bun
bun add @plutarc/bybit

# npm
npm install @plutarc/bybit

# pnpm
pnpm add @plutarc/bybit
```

## Quick Start

### REST Client

```typescript
import { BybitClient } from "@plutarc/bybit";

const client = new BybitClient({
  apiKey: "your-api-key",
  apiSecret: "your-api-secret",
  testnet: true, // use testnet for development
});

// Get instrument info
const instruments = await client.market.getInstrumentsInfo({
  category: "linear",
});

// Get tickers
const tickers = await client.market.getTickers({
  category: "linear",
  symbol: "BTCUSDT",
});

// Place a limit order
const order = await client.order.place({
  category: "linear",
  symbol: "BTCUSDT",
  side: "Buy",
  orderType: "Limit",
  qty: "0.01",
  price: "50000",
  timeInForce: "GTC",
});

// Get wallet balance
const wallet = await client.account.getWalletBalance({
  accountType: "UNIFIED",
});
```

### Public WebSocket

Bybit public WebSocket streams are per-category — each `BybitPublicWs` instance connects to one market type.

```typescript
import { BybitPublicWs } from "@plutarc/bybit";

const ws = new BybitPublicWs({
  category: "linear", // "spot" | "linear" | "inverse" | "option"
  testnet: true,
});

// Subscribe to real-time trades — fully typed callback
ws.on("publicTrade", (type, data, topic) => {
  for (const trade of data) {
    console.log(`${trade.S} ${trade.v} @ ${trade.p}`);
  }
});

// Subscribe to orderbook updates
ws.on("orderbook", (type, data, topic) => {
  console.log(`Orderbook ${type}: ${data.b.length} bids, ${data.a.length} asks`);
});

// Subscribe to 1-minute klines
ws.on("kline", (type, data, topic) => {
  for (const candle of data) {
    console.log(`O:${candle.open} H:${candle.high} L:${candle.low} C:${candle.close}`);
  }
});

// Start subscriptions and connect
ws.subscribe([
  "publicTrade.BTCUSDT",
  "orderbook.50.BTCUSDT",
  "kline.1.BTCUSDT",
]);
ws.connect();
```

### Private WebSocket

```typescript
import { BybitPrivateWs } from "@plutarc/bybit";

const ws = new BybitPrivateWs({
  apiKey: "your-api-key",
  apiSecret: "your-api-secret",
  testnet: true,
});

// Listen for authentication success
ws.on("authenticated", () => {
  console.log("Authenticated — subscribing to private topics");
  ws.subscribe(["order", "position", "wallet", "execution"]);
});

// Order updates
ws.on("order", (data) => {
  for (const order of data) {
    console.log(`Order ${order.orderId}: ${order.orderStatus}`);
  }
});

// Position updates
ws.on("position", (data) => {
  for (const pos of data) {
    console.log(`${pos.symbol}: size=${pos.size} pnl=${pos.unrealisedPnl}`);
  }
});

// Execution (fill) updates
ws.on("execution", (data) => {
  for (const exec of data) {
    console.log(`Fill: ${exec.execQty} @ ${exec.execPrice}`);
  }
});

ws.connect();
```

## REST API Reference

### Client Options

```typescript
const client = new BybitClient({
  apiKey: "...",               // Optional — required for authenticated endpoints
  apiSecret: "...",            // Optional — required for authenticated endpoints
  testnet: false,              // Use testnet (default: false)
  baseUrl: "https://...",      // Custom base URL (overrides testnet flag)
  recvWindow: 5000,            // Receive window in ms (default: 5000)
  rateLimitPer5Seconds: 600,   // Self-throttle limit (default: 600)
  maxRetries: 3,               // Max retries for retryable errors (default: 3)
  baseRetryDelayMs: 500,       // Base delay for exponential backoff (default: 500)
  requestTimeoutMs: 15000,     // Request timeout (default: 15000)
  logger: console,             // Custom logger (default: silent)
});
```

### Market

All market endpoints are public — no API key required.

```typescript
// Get instrument info
const instruments = await client.market.getInstrumentsInfo({
  category: "linear",
  symbol: "BTCUSDT",
});

// Get tickers
const tickers = await client.market.getTickers({
  category: "linear",
  symbol: "BTCUSDT",
});

// Get orderbook
const book = await client.market.getOrderbook({
  category: "linear",
  symbol: "BTCUSDT",
  limit: 50,
});

// Get klines (candlesticks)
const klines = await client.market.getKline({
  category: "linear",
  symbol: "BTCUSDT",
  interval: "60",
  limit: 100,
});

// Get recent trades
const trades = await client.market.getRecentTrade({
  category: "linear",
  symbol: "BTCUSDT",
  limit: 50,
});

// Get funding rate history
const funding = await client.market.getFundingHistory({
  category: "linear",
  symbol: "BTCUSDT",
});

// Get mark price kline
const markKline = await client.market.getMarkPriceKline({
  category: "linear",
  symbol: "BTCUSDT",
  interval: "60",
});

// Get index price kline
const indexKline = await client.market.getIndexPriceKline({
  category: "linear",
  symbol: "BTCUSDT",
  interval: "60",
});

// Get open interest
const oi = await client.market.getOpenInterest({
  category: "linear",
  symbol: "BTCUSDT",
  intervalTime: "1h",
});

// Get risk limit
const risk = await client.market.getRiskLimit({ category: "linear" });
```

### Order

All order endpoints require authentication. Every method takes a `category` parameter.

```typescript
// Place an order
const order = await client.order.place({
  category: "linear",
  symbol: "BTCUSDT",
  side: "Buy",
  orderType: "Limit",
  qty: "0.01",
  price: "50000",
  timeInForce: "GTC",
});

// Amend an order
const amended = await client.order.amend({
  category: "linear",
  symbol: "BTCUSDT",
  orderId: "order-id",
  price: "51000",
});

// Cancel an order
const cancelled = await client.order.cancel({
  category: "linear",
  symbol: "BTCUSDT",
  orderId: "order-id",
});
// or by client order ID
const cancelled = await client.order.cancel({
  category: "linear",
  symbol: "BTCUSDT",
  orderLinkId: "my-order-1",
});

// Get open orders
const open = await client.order.getOpen({
  category: "linear",
  symbol: "BTCUSDT",
});

// Get order history
const history = await client.order.getHistory({
  category: "linear",
  symbol: "BTCUSDT",
  limit: 50,
});

// Cancel all orders
await client.order.cancelAll({
  category: "linear",
  symbol: "BTCUSDT",
});

// Batch place orders (spot/option only)
const batch = await client.order.placeBatch({
  category: "spot",
  request: [
    { category: "spot", symbol: "BTCUSDT", side: "Buy", orderType: "Limit", qty: "0.01", price: "49000" },
    { category: "spot", symbol: "BTCUSDT", side: "Buy", orderType: "Limit", qty: "0.01", price: "48000" },
  ],
});

// Batch amend / cancel
await client.order.amendBatch({ category: "spot", request: [/* ... */] });
await client.order.cancelBatch({ category: "spot", request: [/* ... */] });
```

### Position

```typescript
// Get position list
const positions = await client.position.getList({
  category: "linear",
  symbol: "BTCUSDT",
});

// Set leverage
await client.position.setLeverage({
  category: "linear",
  symbol: "BTCUSDT",
  buyLeverage: "10",
  sellLeverage: "10",
});

// Switch margin mode (isolated/cross)
await client.position.switchMarginMode({
  category: "linear",
  symbol: "BTCUSDT",
  tradeMode: "ISOLATED",
  buyLeverage: "10",
  sellLeverage: "10",
});

// Set risk limit
await client.position.setRiskLimit({
  category: "linear",
  symbol: "BTCUSDT",
  riskId: 1,
});

// Set take profit / stop loss
await client.position.setTradingStop({
  category: "linear",
  symbol: "BTCUSDT",
  takeProfit: "60000",
  stopLoss: "45000",
  positionIdx: 0,
});

// Switch position mode (one-way / hedge)
await client.position.switchPositionMode({
  category: "linear",
  symbol: "BTCUSDT",
  mode: 0, // 0 = merged single, 3 = both side
});

// Get closed PnL
const pnl = await client.position.getClosedPnl({
  category: "linear",
  symbol: "BTCUSDT",
});
```

### Account

```typescript
// Get wallet balance
const wallet = await client.account.getWalletBalance({
  accountType: "UNIFIED",
  coin: "USDT",
});

// Get account info
const info = await client.account.getAccountInfo();

// Get fee rate
const fees = await client.account.getFeeRate({
  category: "linear",
  symbol: "BTCUSDT",
});

// Get transaction log
const log = await client.account.getTransactionLog({
  category: "linear",
  limit: 50,
});

// Set margin mode
await client.account.setMarginMode({
  setMarginMode: "REGULAR_MARGIN",
});
```

### Asset

```typescript
// Get account coins balance
const balance = await client.asset.getAccountCoinsBalance({
  accountType: "UNIFIED",
  coin: "USDT",
});

// Internal transfer between accounts
const transfer = await client.asset.createInternalTransfer({
  transferId: "uuid-here",
  coin: "USDT",
  amount: "100",
  fromAccountType: "UNIFIED",
  toAccountType: "FUND",
});

// Get transfer records
const records = await client.asset.getInternalTransferRecords();

// Get deposit records
const deposits = await client.asset.getDepositRecords({ coin: "USDT" });

// Get withdrawal records
const withdrawals = await client.asset.getWithdrawalRecords({ coin: "USDT" });

// Get coin info (chains, fees, limits)
const coins = await client.asset.getCoinInfo({ coin: "USDT" });
```

### Spot Leveraged Tokens

```typescript
// Get leveraged token info
const tokens = await client.spotLeverToken.getInfo({ ltCoin: "BTC3L" });

// Get leveraged token market data
const market = await client.spotLeverToken.getMarket({ ltCoin: "BTC3L" });

// Purchase leveraged tokens
const purchase = await client.spotLeverToken.purchase({
  ltCoin: "BTC3L",
  ltAmount: "100",
});

// Redeem leveraged tokens
const redeem = await client.spotLeverToken.redeem({
  ltCoin: "BTC3L",
  ltAmount: "100",
});

// Get purchase/redeem records
const history = await client.spotLeverToken.getRecords({ ltCoin: "BTC3L" });
```

### Spot Margin Trade

```typescript
// Get margin mode status
const status = await client.spotMarginTrade.getStatus();

// Toggle spot margin mode
await client.spotMarginTrade.toggle({ spotMarginMode: "1" });

// Set spot margin leverage
await client.spotMarginTrade.setLeverage({ leverage: "5" });

// Get VIP margin data
const vipData = await client.spotMarginTrade.getVipMarginData();

// Get borrow orders
const borrows = await client.spotMarginTrade.getBorrowOrders();
```

## WebSocket API Reference

### WebSocket Options

```typescript
// Public WebSocket — requires a category to select the stream
const pub = new BybitPublicWs({
  category: "linear",           // Required: "spot" | "linear" | "inverse" | "option"
  testnet: false,                // Use testnet (default: false)
  wsUrl: "wss://...",            // Custom WebSocket URL (overrides testnet/category)
  pingIntervalMs: 20000,         // Heartbeat interval (default: 20000)
  maxReconnectAttempts: 10,      // Max reconnect attempts, 0 = infinite (default: 10)
  baseReconnectDelayMs: 1000,    // Base delay for reconnect backoff (default: 1000)
  logger: console,               // Custom logger (default: silent)
});

// Private WebSocket — authentication required, no category needed
const priv = new BybitPrivateWs({
  apiKey: "...",                 // Required
  apiSecret: "...",              // Required
  testnet: false,
  authExpirySec: 5,              // Auth signature expiry (default: 5)
  // ...same options as public (except category)
});
```

**Category-based URLs:** Each category connects to a different WebSocket endpoint:

| Category | Mainnet URL | Testnet URL |
|----------|-------------|-------------|
| `spot` | `wss://stream.bybit.com/v5/public/spot` | `wss://stream-testnet.bybit.com/v5/public/spot` |
| `linear` | `wss://stream.bybit.com/v5/public/linear` | `wss://stream-testnet.bybit.com/v5/public/linear` |
| `inverse` | `wss://stream.bybit.com/v5/public/inverse` | `wss://stream-testnet.bybit.com/v5/public/inverse` |
| `option` | `wss://stream.bybit.com/v5/public/option` | `wss://stream-testnet.bybit.com/v5/public/option` |
| *(private)* | `wss://stream.bybit.com/v5/private` | `wss://stream-testnet.bybit.com/v5/private` |

To stream multiple categories, create multiple `BybitPublicWs` instances.

### Public Topics

Subscribe using Bybit topic format: `"topic.symbol"` or `"topic.depth.symbol"` for orderbook.

| Event | Data Type | Description |
|-------|-----------|-------------|
| `orderbook` | `WsOrderbook` | Orderbook snapshot/delta (depth: 1, 50, 200, 500) |
| `publicTrade` | `WsPublicTrade[]` | Real-time trades |
| `tickers` | `WsTicker` | Ticker snapshot updates |
| `kline` | `WsKline[]` | Candlestick data |
| `liquidation` | `WsLiquidation` | Liquidation orders |

All public data events receive `(type: WsMessageType, data: T, topic: string)` where `WsMessageType` is `"snapshot" | "delta"`.

- `snapshot` — Full data snapshot (initial load, periodic refresh)
- `delta` — Incremental update (changes only)

The `topic` string is the full subscription topic (e.g., `"orderbook.50.BTCUSDT"`) so you can identify the symbol, depth, or interval.

```typescript
ws.subscribe([
  "publicTrade.BTCUSDT",
  "orderbook.50.BTCUSDT",
  "tickers.BTCUSDT",
  "kline.1.BTCUSDT",
  "liquidation.BTCUSDT",
]);
```

### Private Topics

Private topics do not require a symbol — they stream all data for your account.

| Event | Data Type | Description |
|-------|-----------|-------------|
| `order` | `WsPrivateOrder[]` | Order status updates |
| `execution` | `WsPrivateExecution[]` | Trade executions / fills |
| `position` | `WsPrivatePosition[]` | Position updates |
| `wallet` | `WsPrivateWallet[]` | Wallet balance changes |
| `greeks` | `WsPrivateGreeks[]` | Option greeks updates |

Private events receive `(data: T[])` directly — there is no `type` field for private WebSocket messages.

```typescript
ws.on("authenticated", () => {
  ws.subscribe(["order", "position", "execution", "wallet"]);
});
```

### Lifecycle Events

Both public and private WebSocket clients emit lifecycle events:

| Event | Args | Description |
|-------|------|-------------|
| `open` | — | Connection established (and authenticated for private) |
| `close` | `(code, reason)` | Connection closed |
| `reconnecting` | `(attempt)` | Reconnection attempt |
| `error` | `(error)` | Connection or auth error |
| `authenticated` | — | *(Private only)* Authentication successful |

```typescript
ws.on("open", () => console.log("Connected"));
ws.on("close", (code, reason) => console.log(`Disconnected: ${code}`));
ws.on("reconnecting", (attempt) => console.log(`Reconnecting (attempt ${attempt})`));
ws.on("error", (err) => console.error("WS error:", err));
```

### Connection Management

```typescript
ws.connect();      // Establish connection
ws.disconnect();   // Gracefully disconnect (no auto-reconnect)

ws.subscribe(["publicTrade.BTCUSDT"]);    // Subscribe to topics
ws.unsubscribe(["publicTrade.BTCUSDT"]);  // Unsubscribe from topics

ws.state;               // "connecting" | "connected" | "reconnecting" | "closed"
ws.activeSubscriptions; // string[] of current subscriptions
```

## Utilities

### Delta Parser

A generic, zero-dependency delta parser that maintains local state from Bybit WebSocket delta messages (`snapshot`/`delta`). Works with any WebSocket data type.

```typescript
import { DeltaParser } from "@plutarc/bybit";
import type { WsPublicTrade } from "@plutarc/bybit";

// Create a parser for trade data — keys identify unique rows
const tradeParser = new DeltaParser<WsPublicTrade>({
  keys: ["i"],
  maxItems: 1000, // keep only the most recent 1000 trades
});

ws.on("publicTrade", (type, data) => {
  const snapshot = tradeParser.update(type, data);
  console.log(`${snapshot.length} trades in store`);
});
```

For data where items can be removed (like orderbook levels), use the `isRemoved` callback:

```typescript
interface Level {
  price: string;
  size: string;
}

const parser = new DeltaParser<Level>({
  keys: ["price"],
  isRemoved: (item) => item.size === "0", // Bybit convention: size "0" = remove
});
```

**API:**

| Method / Property | Returns | Description |
|---|---|---|
| `update(type, data)` | `readonly T[]` | Process a delta message, returns full current state |
| `snapshot` | `readonly T[]` | Current state without processing a new message |
| `length` | `number` | Number of items in the store |
| `clear()` | `void` | Reset the store to empty |

### OrderBook Store

A higher-level convenience store for Bybit L2 orderbook data. Uses price-keyed Maps for O(1) updates on Bybit's `[price, size]` array format. Maintains sorted bids and asks with common orderbook queries.

```typescript
import { OrderBookStore } from "@plutarc/bybit";

const book = new OrderBookStore();

// Plug directly into a WebSocket event handler
ws.on("orderbook", (type, data) => book.update(type, data));

// Query the book
console.log(book.bestBid());   // 50000
console.log(book.bestAsk());   // 50001
console.log(book.spread());    // 1
console.log(book.midPrice());  // 50000.5

// Get top 5 levels from each side
const { bids, asks } = book.depth(5);

// Volume at top of book
console.log(book.bidVolume(10)); // total bid size in top 10 levels
console.log(book.askVolume());   // total ask size across all levels

// Sorted level arrays
book.bids; // OrderBookLevel[] sorted desc by price (best bid first)
book.asks; // OrderBookLevel[] sorted asc by price (best ask first)
```

**Composability — use as much or as little as you need:**

```typescript
import { DeltaParser, OrderBookStore } from "@plutarc/bybit";

// 1. Batteries included — OrderBookStore handles snapshot/delta directly
const book = new OrderBookStore();
ws.on("orderbook", (type, data) => book.update(type, data));

// 2. DeltaParser only — feed into your own store (zustand, redux, etc.)
ws.on("orderbook", (type, data) => {
  // handle raw { b: [[price, size], ...], a: [[price, size], ...] }
  useMyStore.setState({ orderbook: data });
});

// 3. Neither — handle raw deltas yourself
ws.on("orderbook", (type, data, topic) => { /* your own logic */ });
```

**API:**

| Method / Property | Returns | Description |
|---|---|---|
| `update(type, data)` | `void` | Process a snapshot/delta message (auto-bound arrow function) |
| `bids` | `readonly OrderBookLevel[]` | All bids sorted descending by price |
| `asks` | `readonly OrderBookLevel[]` | All asks sorted ascending by price |
| `bestBid()` | `number \| undefined` | Highest bid price |
| `bestAsk()` | `number \| undefined` | Lowest ask price |
| `spread()` | `number \| undefined` | Best ask minus best bid |
| `midPrice()` | `number \| undefined` | Average of best bid and best ask |
| `depth(n)` | `{ bids, asks }` | Top N levels from each side |
| `bidVolume(n?)` | `number` | Total bid size (optionally top N levels only) |
| `askVolume(n?)` | `number` | Total ask size (optionally top N levels only) |
| `clear()` | `void` | Reset the book to empty |

### Authentication Helpers

For advanced usage:

```typescript
import { sign, getSignedHeaders, signWs } from "@plutarc/bybit";

// REST API signature
const sig = sign(apiSecret, timestamp, apiKey, recvWindow, payload);

// Full signed headers (auto-generates timestamp)
const headers = getSignedHeaders(apiKey, apiSecret, recvWindow, payload);

// WebSocket auth signature
const wsSig = signWs(apiSecret, expires);
```

## Error Handling

All REST errors throw typed error classes. Bybit wraps all responses in `{ retCode, retMsg, result }` — the client automatically unwraps successful responses and throws on non-zero `retCode`, even when the HTTP status is 200.

```typescript
import { BybitApiError, BybitRateLimitError } from "@plutarc/bybit";

try {
  await client.order.place({ /* ... */ });
} catch (err) {
  if (err instanceof BybitRateLimitError) {
    console.log(`Rate limited — retry after ${err.retryAfterMs}ms`);
  } else if (err instanceof BybitApiError) {
    console.log(`API error ${err.status} (retCode=${err.retCode}): ${err.errorMessage}`);
    console.log(`Retryable: ${err.retryable}`);
  }
}
```

**Retryable errors** (automatically retried up to `maxRetries` times):
- `429` Too Many Requests
- `5xx` Server errors
- Network/timeout errors

**Non-retryable errors** (thrown immediately):
- `400` Bad Request
- `401` Unauthorized
- `403` Forbidden
- `404` Not Found
- Non-zero `retCode` with HTTP 200 (business logic errors)

## Rate Limiting

The client self-throttles to stay within Bybit rate limits:

- Default: 600 requests per 5-second window (Bybit IP-based limit)
- Tracks the `X-Bapi-Limit-Status` response header from Bybit
- Pauses requests when approaching the limit (5-request buffer)
- Configurable via `rateLimitPer5Seconds` option

Note: Bybit also enforces per-UID per-second limits on specific endpoints (e.g., 20 req/s for order creation). These are endpoint-specific and not tracked by the client-side rate limiter.

## Testnet

All clients support testnet with a single flag:

```typescript
// REST
const client = new BybitClient({ testnet: true });
// → https://api-testnet.bybit.com

// Public WebSocket
const ws = new BybitPublicWs({ category: "linear", testnet: true });
// → wss://stream-testnet.bybit.com/v5/public/linear

// Private WebSocket
const priv = new BybitPrivateWs({ apiKey: "...", apiSecret: "...", testnet: true });
// → wss://stream-testnet.bybit.com/v5/private
```

Get testnet API keys at [testnet.bybit.com](https://testnet.bybit.com).

## Custom Logger

Pass any object implementing `{ debug, info, warn, error }` methods. By default the client is silent.

```typescript
// Use console
const client = new BybitClient({ logger: console });

// Use pino
import pino from "pino";
const client = new BybitClient({ logger: pino({ name: "bybit" }) });

// Use winston
import winston from "winston";
const client = new BybitClient({ logger: winston.createLogger({ /* ... */ }) });
```

## Development

```bash
# Install dependencies
bun install

# Type check
bun run typecheck

# Run tests
bun test

# Build
bun run build

# Lint & format
bun run lint
bun run format
```

## Resources

- [Bybit V5 API Documentation](https://bybit-exchange.github.io/docs/v5/intro) — API overview and guides
- [Bybit API Integration Guide](https://bybit-exchange.github.io/docs/v5/guide) — Authentication, rate limits, and integration guidance
- [Bybit WebSocket API](https://bybit-exchange.github.io/docs/v5/ws/connect) — WebSocket documentation
- [Bybit Testnet](https://testnet.bybit.com) — Testnet for development
- [Bybit API Changelog](https://bybit-exchange.github.io/docs/changelog/v5) — API changes and updates

## License

[MIT](LICENSE) — see [LICENSE](LICENSE) for details.