@plutarc/bitmex
v0.2.0
Published
Fully-typed BitMEX REST and WebSocket client for TypeScript/JavaScript
Downloads
155
Maintainers
Readme
@plutarc/bitmex
A fully-typed BitMEX REST and WebSocket client for TypeScript/JavaScript. Zero runtime dependencies — uses only Node.js/Bun built-ins.
Table of Contents
- Features
- Installation
- Quick Start
- REST API Reference
- WebSocket API Reference
- Utilities
- Error Handling
- Rate Limiting
- Testnet
- Custom Logger
- Development
- Resources
- License
Features
- Complete REST API — All BitMEX REST endpoints with full TypeScript types
- Real-time WebSocket — Public and private WebSocket clients with typed event emitters
- Zero dependencies — Uses only
node:cryptoandnode:eventsbuilt-ins - Fully typed — Every request parameter and response field has TypeScript interfaces
- Rate limiting — Built-in rolling window rate limiter that tracks
x-ratelimit-remaining - Auto-retry — Exponential backoff with jitter for retryable errors (429, 5xx)
- Auto-reconnect — WebSocket reconnection with exponential backoff
- Ping/pong — Automatic 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
# bun
bun add @plutarc/bitmex
# npm
npm install @plutarc/bitmex
# pnpm
pnpm add @plutarc/bitmexQuick Start
REST Client
import { BitMEXClient } from "@plutarc/bitmex";
const client = new BitMEXClient({
apiKey: "your-api-key",
apiSecret: "your-api-secret",
testnet: true, // use testnet for development
});
// Get active instruments
const instruments = await client.instrument.getActive();
// Place a limit order
const order = await client.order.place({
symbol: "XBTUSD",
side: "Buy",
ordType: "Limit",
orderQty: 100,
price: 50000,
});
// Get open positions
const positions = await client.position.getOpen();
// Get wallet balance
const wallets = await client.user.getWallet();Public WebSocket
import { BitMEXPublicWs } from "@plutarc/bitmex";
const ws = new BitMEXPublicWs({ testnet: true });
// Subscribe to real-time trades — fully typed callback
ws.on("trade", (action, data) => {
for (const trade of data) {
console.log(`${trade.side} ${trade.size} @ ${trade.price}`);
}
});
// Subscribe to orderbook updates
ws.on("orderBookL2_25", (action, data) => {
console.log(`Orderbook ${action}: ${data.length} entries`);
});
// Subscribe to 1-minute candles
ws.on("tradeBin1m", (action, data) => {
for (const candle of data) {
console.log(`${candle.symbol} O:${candle.open} H:${candle.high} L:${candle.low} C:${candle.close}`);
}
});
// Start subscriptions and connect
ws.subscribe(["trade:XBTUSD", "orderBookL2_25:XBTUSD", "tradeBin1m:XBTUSD"]);
ws.connect();Private WebSocket
import { BitMEXPrivateWs } from "@plutarc/bitmex";
const ws = new BitMEXPrivateWs({
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", (action, data) => {
for (const order of data) {
console.log(`Order ${order.orderID}: ${order.ordStatus}`);
}
});
// Position updates
ws.on("position", (action, data) => {
for (const pos of data) {
console.log(`${pos.symbol}: qty=${pos.currentQty} pnl=${pos.unrealisedPnl}`);
}
});
// Execution (fill) updates
ws.on("execution", (action, data) => {
for (const exec of data) {
console.log(`Fill: ${exec.lastQty} @ ${exec.lastPx}`);
}
});
ws.connect();REST API Reference
Client Options
const client = new BitMEXClient({
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)
rateLimitPerMinute: 100, // Self-throttle limit (default: 100, BitMEX limit: 120)
maxRetries: 3, // Max retries for retryable errors (default: 3)
baseRetryDelayMs: 500, // Base delay for exponential backoff (default: 500)
requestTimeoutMs: 15000, // Request timeout (default: 15000)
authExpirySec: 5, // Signature expiry window (default: 5)
logger: console, // Custom logger (default: silent)
});Order
// Get open orders
const open = await client.order.getOpen({ symbol: "XBTUSD" });
// Get order history
const history = await client.order.getHistory({ symbol: "XBTUSD", count: 100 });
// Query with filters
const orders = await client.order.get({
symbol: "XBTUSD",
filter: { ordStatus: "Filled" },
count: 50,
reverse: true,
});
// Place an order
const order = await client.order.place({
symbol: "XBTUSD",
side: "Buy",
ordType: "Limit",
orderQty: 100,
price: 50000,
timeInForce: "GoodTillCancel",
});
// Place bulk orders
const orders = await client.order.placeBulk([
{ symbol: "XBTUSD", side: "Buy", ordType: "Limit", orderQty: 100, price: 49000 },
{ symbol: "XBTUSD", side: "Buy", ordType: "Limit", orderQty: 100, price: 48000 },
]);
// Amend an order
const amended = await client.order.amend({
orderID: "order-id",
price: 51000,
});
// Amend bulk orders
const amended = await client.order.amendBulk([
{ orderID: "id-1", price: 51000 },
{ orderID: "id-2", price: 47000 },
]);
// Cancel an order
const cancelled = await client.order.cancel({ orderID: "order-id" });
// or by client order ID
const cancelled = await client.order.cancel({ clOrdID: "my-order-1" });
// Cancel all orders
await client.order.cancelAll({ symbol: "XBTUSD" });
// Dead man's switch — cancel all after timeout (ms)
await client.order.cancelAllAfter(60000); // cancel all in 60s
await client.order.cancelAllAfter(0); // disable timer
// Close position at market
const closed = await client.order.closePosition({ symbol: "XBTUSD" });Position
// Get open positions
const positions = await client.position.getOpen("XBTUSD");
// Get all positions (including closed)
const all = await client.position.get();
// Set leverage
await client.position.setLeverage("XBTUSD", 10);
// Set cross margin
await client.position.setIsolateMargin("XBTUSD", false);
// Set isolated margin
await client.position.setIsolateMargin("XBTUSD", true);
// Set risk limit
await client.position.setRiskLimit("XBTUSD", 5000000000);
// Transfer margin to position
await client.position.transferMargin("XBTUSD", 100000);Instrument
// Get specific instrument
const instruments = await client.instrument.get({ symbol: "XBTUSD" });
// Get all active instruments
const active = await client.instrument.getActive();
// Get active instruments and indices
const all = await client.instrument.getActiveAndIndices();
// Get active intervals
const intervals = await client.instrument.getActiveIntervals();
// Get composite index data
const index = await client.instrument.getCompositeIndex({ symbol: ".BXBT" });
// Get all indices
const indices = await client.instrument.getIndices();
// Get USD volume summary
const volume = await client.instrument.getUsdVolume();Trade
// Get recent trades
const trades = await client.trade.get({
symbol: "XBTUSD",
count: 100,
reverse: true,
});
// Get OHLCV candles
const candles = await client.trade.getBucketed({
binSize: "1h",
symbol: "XBTUSD",
count: 100,
reverse: true,
partial: false,
});Order Book
// Get L2 orderbook snapshot (default 25 levels)
const book = await client.orderBook.getL2({ symbol: "XBTUSD" });
// Custom depth
const deepBook = await client.orderBook.getL2({ symbol: "XBTUSD", depth: 50 });Execution
// Get raw executions
const execs = await client.execution.get({
symbol: "XBTUSD",
count: 50,
reverse: true,
});
// Get trade history (fills only)
const fills = await client.execution.getTradeHistory({
symbol: "XBTUSD",
startTime: "2025-01-01T00:00:00.000Z",
});User / Account
// Get current user
const user = await client.user.get();
// Get wallet balance
const wallets = await client.user.getWallet("USDt");
// Get wallet history
const history = await client.user.getWalletHistory({ currency: "USDt", count: 50 });
// Get wallet summary
const summary = await client.user.getWalletSummary("USDt");
// Get commission rates
const fees = await client.user.getCommission();
// Get margin status
const margin = await client.user.getMargin("USDt");
// Update preferences
await client.user.updatePreferences({ locale: "en-US" });Funding
const funding = await client.funding.get({
symbol: "XBTUSD",
count: 100,
reverse: true,
});Insurance
const insurance = await client.insurance.get({ count: 50, reverse: true });Liquidation
const liquidations = await client.liquidation.get({
symbol: "XBTUSD",
count: 100,
reverse: true,
});Settlement
const settlements = await client.settlement.get({ symbol: "XBTUSD" });Stats
const stats = await client.stats.get();
const history = await client.stats.getHistory();
const usd = await client.stats.getHistoryUSD();Quote
// Get quotes
const quotes = await client.quote.get({ symbol: "XBTUSD", count: 10 });
// Get bucketed quotes
const bucketed = await client.quote.getBucketed({
binSize: "1h",
symbol: "XBTUSD",
count: 100,
});Announcement
const announcements = await client.announcement.get();
const urgent = await client.announcement.getUrgent();API Key
const keys = await client.apiKey.get();
const newKey = await client.apiKey.create({ name: "trading-bot" });
await client.apiKey.remove("key-id");Leaderboard
const board = await client.leaderboard.get({ method: "notional" });
const myName = await client.leaderboard.getName();WebSocket API Reference
WebSocket Options
// Public WebSocket — no authentication required
const pub = new BitMEXPublicWs({
testnet: false, // Use testnet (default: false)
wsUrl: "wss://...", // Custom WebSocket URL (overrides testnet flag)
pingIntervalMs: 15000, // Heartbeat interval (default: 15000)
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
const priv = new BitMEXPrivateWs({
apiKey: "...", // Required
apiSecret: "...", // Required
testnet: false,
authExpirySec: 5, // Auth signature expiry (default: 5)
// ...same options as public
});Public Topics
Subscribe using BitMEX topic format: "table:symbol" for symbol-specific topics.
| Event | Data Type | Description |
|-------|-----------|-------------|
| trade | WsTrade[] | Real-time trades |
| tradeBin1m | WsTradeBin[] | 1-minute OHLCV candles |
| tradeBin5m | WsTradeBin[] | 5-minute OHLCV candles |
| tradeBin1h | WsTradeBin[] | 1-hour OHLCV candles |
| tradeBin1d | WsTradeBin[] | Daily OHLCV candles |
| orderBookL2_25 | WsOrderBookL2[] | Orderbook L2 (25 levels) |
| orderBookL2 | WsOrderBookL2[] | Orderbook L2 (full) |
| orderBook10 | WsOrderBook10[] | Top 10 orderbook |
| instrument | WsInstrument[] | Instrument updates |
| funding | WsFunding[] | Funding rate updates |
| liquidation | WsLiquidation[] | Liquidation orders |
| settlement | WsSettlement[] | Settlement data |
| insurance | WsInsurance[] | Insurance fund |
| quote | WsQuote[] | Top-of-book quotes |
| quoteBin1m | WsQuoteBin[] | 1-minute bucketed quotes |
| quoteBin5m | WsQuoteBin[] | 5-minute bucketed quotes |
| quoteBin1h | WsQuoteBin[] | 1-hour bucketed quotes |
| quoteBin1d | WsQuoteBin[] | Daily bucketed quotes |
All data events receive (action: WsAction, data: T[]) where WsAction is "partial" | "insert" | "update" | "delete".
partial— Full snapshot (initial data load)insert— New entries addedupdate— Existing entries modifieddelete— Entries removed
ws.subscribe([
"trade:XBTUSD",
"orderBookL2_25:XBTUSD",
"tradeBin1m:XBTUSD",
"instrument:XBTUSD",
]);Private Topics
Private topics do not require a symbol suffix — they stream all data for your account.
| Event | Data Type | Description |
|-------|-----------|-------------|
| order | WsOrder[] | Order status updates |
| execution | WsExecution[] | Trade executions / fills |
| position | WsPosition[] | Position updates |
| wallet | WsWallet[] | Wallet balance changes |
| margin | WsMargin[] | Margin / available balance |
| transact | WsTransaction[] | Deposit/withdrawal transactions |
| affiliate | WsAffiliate[] | Affiliate status |
ws.on("authenticated", () => {
ws.subscribe(["order", "position", "execution", "wallet", "margin"]);
});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 |
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
ws.connect(); // Establish connection
ws.disconnect(); // Gracefully disconnect (no auto-reconnect)
ws.subscribe(["trade:XBTUSD"]); // Subscribe to topics
ws.unsubscribe(["trade:XBTUSD"]); // Unsubscribe from topics
ws.state; // "connecting" | "connected" | "reconnecting" | "closed"
ws.activeSubscriptions; // string[] of current subscriptionsUtilities
Currency Conversion
Helpers for BitMEX's internal representations:
import { satoshiToBtc, microUsdtToUsdt, convertWalletAmount } from "@plutarc/bitmex";
// BitMEX stores BTC as satoshi (XBt): 1 BTC = 100,000,000 satoshi
satoshiToBtc(100_000_000); // 1.0
// BitMEX stores USDT as micro-USDT (USDt): 1 USDT = 1,000,000 micro
microUsdtToUsdt(1_000_000); // 1.0
// Auto-detect by currency code
convertWalletAmount(100_000_000, "XBt"); // 1.0 BTC
convertWalletAmount(1_000_000, "USDt"); // 1.0 USDTDelta Parser
A generic, zero-dependency delta parser that maintains local state from BitMEX WebSocket delta messages (partial/insert/update/delete). Works with any WebSocket table type.
import { DeltaParser } from "@plutarc/bitmex";
import type { WsOrderBookL2, WsOrder } from "@plutarc/bitmex";
// Create a parser for orderbook data — keys identify unique rows
const bookParser = new DeltaParser<WsOrderBookL2>({ keys: ["id"] });
ws.on("orderBookL2_25", (action, data) => {
const snapshot = bookParser.update(action, data);
// snapshot is the full current orderbook state
console.log(`Book has ${snapshot.length} levels`);
});
// Works with any table — orders, positions, etc.
const orderParser = new DeltaParser<WsOrder>({ keys: ["orderID"] });
ws.on("order", (action, data) => {
const orders = orderParser.update(action, data);
console.log(`${orders.length} active orders`);
});For insert-only tables like trade that grow unboundedly, use maxItems to cap the store:
import type { WsTrade } from "@plutarc/bitmex";
const tradeParser = new DeltaParser<WsTrade>({ keys: ["trdMatchID"], maxItems: 1000 });
ws.on("trade", (action, data) => {
const trades = tradeParser.update(action, data);
// keeps only the most recent 1000 trades
});API:
| Method / Property | Returns | Description |
|---|---|---|
| update(action, 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 L2 orderbook data. Maintains sorted bids and asks with common orderbook queries. Optionally wraps a DeltaParser — you can bring your own or use the built-in one.
import { OrderBookStore } from "@plutarc/bitmex";
const book = new OrderBookStore();
// Plug directly into a WebSocket event handler (update is auto-bound)
ws.on("orderBookL2_25", book.update);
// 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:
import { DeltaParser, OrderBookStore } from "@plutarc/bitmex";
import type { WsOrderBookL2 } from "@plutarc/bitmex";
// 1. Batteries included — OrderBookStore manages everything
const book = new OrderBookStore();
ws.on("orderBookL2_25", book.update);
// 2. DeltaParser only — feed into your own store (zustand, redux, etc.)
const parser = new DeltaParser<WsOrderBookL2>({ keys: ["id"] });
ws.on("orderBookL2_25", (action, data) => {
const snapshot = parser.update(action, data);
useMyStore.setState({ orderbook: snapshot });
});
// 3. Shared DeltaParser + OrderBookStore
const parser = new DeltaParser<WsOrderBookL2>({ keys: ["id"] });
const book = new OrderBookStore({ parser });
ws.on("orderBookL2_25", book.update);
// Access parser.snapshot for raw data, book.bids/asks for sorted view
// 4. Neither — handle raw deltas yourself
ws.on("orderBookL2_25", (action, data) => { /* your own logic */ });API:
| Method / Property | Returns | Description |
|---|---|---|
| update(action, data) | void | Process a 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:
import { sign, getSignedHeaders, signWs } from "@plutarc/bitmex";
// REST API signature
const sig = sign(apiSecret, "GET", "/api/v1/order", expires, "");
// Full signed headers
const headers = getSignedHeaders(apiKey, apiSecret, "GET", "/api/v1/order", "");
// WebSocket auth signature
const wsSig = signWs(apiSecret, expires);Error Handling
All REST errors throw typed error classes:
import { BitMEXApiError, BitMEXRateLimitError } from "@plutarc/bitmex";
try {
await client.order.place({ /* ... */ });
} catch (err) {
if (err instanceof BitMEXRateLimitError) {
console.log(`Rate limited — retry after ${err.retryAfterMs}ms`);
} else if (err instanceof BitMEXApiError) {
console.log(`API error ${err.status}: ${err.errorMessage}`);
console.log(`Retryable: ${err.retryable}`);
}
}Retryable errors (automatically retried up to maxRetries times):
429Too Many Requests5xxServer errors- Network/timeout errors
Non-retryable errors (thrown immediately):
400Bad Request401Unauthorized403Forbidden404Not Found
Rate Limiting
The client self-throttles to stay within BitMEX rate limits:
- Default: 100 requests/minute (BitMEX limit is 120)
- Tracks the
x-ratelimit-remainingresponse header from BitMEX - Pauses requests when approaching the limit (5-request buffer)
- Configurable via
rateLimitPerMinuteoption
Testnet
All clients support testnet with a single flag:
// REST
const client = new BitMEXClient({ testnet: true });
// → https://testnet.bitmex.com
// WebSocket
const ws = new BitMEXPublicWs({ testnet: true });
// → wss://ws.testnet.bitmex.com/realtimeGet testnet API keys at testnet.bitmex.com.
Custom Logger
Pass any object implementing { debug, info, warn, error } methods. By default the client is silent.
// Use console
const client = new BitMEXClient({ logger: console });
// Use pino
import pino from "pino";
const client = new BitMEXClient({ logger: pino({ name: "bitmex" }) });
// Use winston
import winston from "winston";
const client = new BitMEXClient({ logger: winston.createLogger({ /* ... */ }) });Development
# Install dependencies
bun install
# Type check
bun run typecheck
# Run tests
bun test
# Build
bun run build
# Lint & format
bun run lint
bun run formatResources
- BitMEX API Explorer — Interactive API documentation
- BitMEX API Reference — API overview and guides
- BitMEX WebSocket API — WebSocket documentation
- BitMEX Testnet — Testnet for development
- BitMEX API Changelog — API changes and updates
