Package Exports
- @solncebro/exchange-engine
- @solncebro/exchange-engine/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@solncebro/exchange-engine) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@solncebro/exchange-engine
Universal TypeScript client library for cryptocurrency trading on Binance and Bybit with unified API, WebSocket support, and native type safety.
Features
- 🔀 Single API for multiple exchanges — same code works with Binance or Bybit
- 🎯 Type-safe unified types — all responses normalized to consistent types (Kline, Ticker, Position, etc.)
- 📊 REST & WebSocket support — fetch historical data and subscribe to real-time streams
- 🔄 Automatic reconnection — resilient WebSocket connections with exponential backoff
- 📝 Comprehensive logging — built-in structured logging via custom logger interface
- 🚀 Zero dependencies — only axios and websocket-engine
Installation
npm install @solncebro/exchange-engine
# or
yarn add @solncebro/exchange-engineQuick Start
import { Exchange } from '@solncebro/exchange-engine';
import { pinoLogger } from './logger'; // your logger instance
// Create exchange instance (works identically for 'binance' or 'bybit')
const exchange = new Exchange('binance', {
config: { apiKey: process.env.API_KEY, secret: process.env.API_SECRET },
logger: pinoLogger,
onNotify: (msg) => telegramBot.send(msg), // optional notifications
});
// Load markets
await exchange.futures.loadMarkets();
// Fetch historical klines
const klines = await exchange.futures.fetchKlines('BTCUSDT', '1h', { limit: 100 });
console.log(klines[0]); // { openTime, open, high, low, close, volume, ... }
// Get current tickers
const tickers = await exchange.futures.fetchTickers();
// Subscribe to real-time klines
exchange.futures.subscribeKlines({
symbol: 'BTCUSDT',
interval: '1m',
handler: (kline) => {
console.log(`[${kline.openTime}] ${kline.close}`);
},
});
// Create an order
const order = await exchange.futures.createOrderWs({
symbol: 'BTCUSDT',
type: 'market',
side: 'buy',
amount: 0.01,
price: 0, // ignored for market orders
params: {}, // exchange-specific params
});
// Fetch position info
const position = await exchange.futures.fetchPosition('BTCUSDT');
console.log(`Leverage: ${position.leverage}, Contracts: ${position.contracts}`);
// Set leverage
await exchange.futures.setLeverage(10, 'BTCUSDT');
// Close connection
await exchange.close();API Reference
Exchange (Main Entry Point)
const exchange = new Exchange('binance' | 'bybit', {
config: { apiKey: string; secret: string; recvWindow?: number };
logger: ExchangeLogger;
onNotify?: (message: string) => void | Promise<void>;
});
// Access exchange clients
exchange.futures // BinanceFutures | BybitLinear
exchange.spot // BinanceSpot | BybitSpot
// Cleanup
await exchange.close();ExchangeClient Interface
All four classes (BinanceFutures, BinanceSpot, BybitLinear, BybitSpot) implement this interface:
Market Data (REST)
// Load and cache market information
await client.loadMarkets(reload?: boolean): Promise<MarketBySymbol>;
// Get all markets (already loaded)
const markets = client.markets; // Record<string, Market>
// Fetch current ticker prices
await client.fetchTickers(): Promise<TickerBySymbol>;
// Fetch historical candlestick data
await client.fetchKlines(
symbol: string,
interval: KlineInterval,
opts?: { limit?: number; startTime?: number; endTime?: number }
): Promise<Kline[]>;
// Get account balance
await client.fetchBalance(): Promise<BalanceByAsset>;Trading (REST + WebSocket)
// Create order via WebSocket (recommended for speed)
await client.createOrderWs({
symbol: string;
type: 'market' | 'limit';
side: 'buy' | 'sell';
amount: number;
price: number;
params?: Record<string, unknown>; // hedgeMode, timeInForce, etc.
}): Promise<Order>;Futures-Specific
// Fetch position details
await client.fetchPosition(symbol: string): Promise<Position>;
// Set leverage (Binance: 2-125x, Bybit: 1-99.5x)
await client.setLeverage(leverage: number, symbol: string): Promise<void>;
// Set margin mode
await client.setMarginMode(marginMode: 'isolated' | 'cross', symbol: string): Promise<void>;Real-Time Data (WebSocket)
// Subscribe to kline updates
client.subscribeKlines({
symbol: string;
interval: KlineInterval;
handler: (kline: Kline) => void;
}): void;
// Unsubscribe
client.unsubscribeKlines({ symbol, interval, handler }): void;Precision
// Format amount to exchange precision
const formatted = client.amountToPrecision('BTCUSDT', 0.12345);
// Format price to exchange precision
const formatted = client.priceToPrecision('BTCUSDT', 65432.1);Unified Types
All types are normalized across exchanges. No raw exchange formats leak out.
// Candlestick
interface Kline {
openTime: number;
open: number;
high: number;
low: number;
close: number;
volume: number;
closeTime: number;
quoteVolume: number;
trades: number;
}
// Current price
interface Ticker {
symbol: string;
close: number;
percentage: number; // 24h change %
timestamp: number;
}
// Market metadata
interface Market {
symbol: string;
baseAsset: string;
quoteAsset: string;
settle: string;
active: boolean;
type: 'spot' | 'swap' | 'future';
linear: boolean;
contractSize: number;
filter: MarketFilter;
}
// Open position (futures)
interface Position {
symbol: string;
side: 'long' | 'short' | 'both';
contracts: number;
entryPrice: number;
markPrice: number;
unrealizedPnl: number;
leverage: number;
marginMode: 'isolated' | 'cross';
liquidationPrice: number;
info: Record<string, unknown>; // raw exchange data
}
// Placed order
interface Order {
id: string;
symbol: string;
side: 'buy' | 'sell';
type: 'market' | 'limit';
amount: number;
price: number;
status: string;
timestamp: number;
}
// Account balance
interface Balance {
asset: string;
free: number;
locked: number;
total: number;
}Logger Interface
Provide any logger that implements this interface:
interface ExchangeLogger {
debug(message: string): void;
info(message: string): void;
warn(message: string): void;
error(message: string): void;
fatal(message: string): void;
}Example with Pino
import pino from 'pino';
const logger = pino({
level: 'info',
transport: {
target: 'pino-pretty',
options: { colorize: true },
},
});
const exchange = new Exchange('binance', {
config: { apiKey, secret },
logger, // pino instance is compatible
});Exchange Differences
API Keys & Permissions
- Binance: Read, Trade, Withdraw permissions (for different features)
- Bybit: Single API key handles all
Order Placement
- Binance:
createOrderWs()uses REST (faster than WS) - Bybit:
createOrderWs()uses dedicated trade WebSocket stream
Position Modes
- Binance: Supports Hedge Mode (separate long/short) and One-Way Mode
- Bybit: Always supports both buy and sell sides simultaneously
Funding Rates
- Binance: 8 times per day at fixed UTC times
- Bybit: Hourly funding
These differences are transparent — the same code works for both.
Performance Tips
Batch requests — use
Promise.all()for multiple operationsconst [tickers, position, balance] = await Promise.all([ client.fetchTickers(), client.fetchPosition('BTCUSDT'), client.fetchBalance(), ]);
Reuse markets — call
loadMarkets()once at startupawait client.loadMarkets(); const symbols = Object.keys(client.markets);
Limit historical data — fetch only needed range
const klines = await client.fetchKlines('BTCUSDT', '1h', { limit: 100, startTime: Date.now() - 100 * 60 * 60 * 1000, // last 100 hours });
Subscribe instead of polling — WebSocket is more efficient
// Instead of: setInterval(() => fetchTickers(), 5000); // Use: client.subscribeKlines({ symbol, interval, handler });
Error Handling
All errors are logged and thrown. Wrap calls in try-catch:
try {
await exchange.futures.setLeverage(100, 'BTCUSDT');
} catch (error) {
console.error(`Failed to set leverage: ${error.message}`);
// error has code, status, response fields for detailed handling
}Extending the Library
Adding new endpoints follows a standard pattern:
- HTTP Client → add method to
BinanceFuturesHttpClientorBybitHttpClient - Normalizer → add raw type + normalization function
- Interface → add method to
ExchangeClient - Implementation → implement in all 4 exchange classes
Example: adding fetchOpenInterest(symbol)
// 1. In BinanceFuturesHttpClient
private async fetchOpenInterestRaw(symbol: string): Promise<BinanceRawOpenInterest> {
return this.get('/fapi/v1/openInterest', { symbol });
}
// 2. In binanceNormalizer.ts
export function normalizeOpenInterest(raw: BinanceRawOpenInterest): OpenInterest {
return { symbol: raw.symbol, openInterest: parseFloat(raw.openInterest) };
}
// 3. In ExchangeClient interface
fetchOpenInterest(symbol: string): Promise<OpenInterest>;
// 4. In BinanceFutures
async fetchOpenInterest(symbol: string): Promise<OpenInterest> {
const raw = await this.httpClient.fetchOpenInterestRaw(symbol);
return normalizeOpenInterest(raw);
}License
MIT
Support
- GitHub Issues: solncebro/exchange-engine
- Documentation: See inline JSDoc comments
- Examples: Check
examples/directory