Complete JavaScript Client
Here’s a full-featured Node.js WebSocket client for the QuantCite API:Copy
const WebSocket = require('ws');
const EventEmitter = require('events');
class QuantCiteClient extends EventEmitter {
constructor(apiKey, baseUrl = 'data.quantcite.com') {
super();
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.uri = `wss://${baseUrl}/api/v1/ws`;
this.ws = null;
this.authenticated = false;
this.subscriptions = new Set();
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 10;
this.reconnectDelay = 1000;
this.pingInterval = null;
this.running = true;
}
async connect() {
return new Promise((resolve, reject) => {
console.log(`Connecting to ${this.uri}`);
this.ws = new WebSocket(this.uri);
this.ws.on('open', () => {
console.log('WebSocket connected successfully');
this.reconnectAttempts = 0;
this.startPingInterval();
});
this.ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
this.handleMessage(message);
// Handle authentication response
if (message.type === 'authentication_success') {
this.authenticated = true;
console.log('Authentication successful');
console.log(`User ID: ${message.user_id}`);
console.log(`Tier: ${message.tier}`);
const usage = message.data_usage || {};
console.log(`Data usage: ${usage.used_gb || 0}GB / ${usage.limit_gb || 50}GB`);
resolve(true);
} else if (message.type === 'authentication_error') {
console.error(`Authentication failed: ${message.message}`);
reject(new Error('Authentication failed'));
} else if (message.type === 'welcome') {
console.log('Welcome message received, authenticating...');
this.authenticate();
}
} catch (error) {
console.error('Error parsing message:', error);
}
});
this.ws.on('error', (error) => {
console.error('WebSocket error:', error);
this.emit('error', error);
reject(error);
});
this.ws.on('close', (code, reason) => {
console.log(`WebSocket closed: ${code} ${reason}`);
this.authenticated = false;
this.stopPingInterval();
if (this.running && this.reconnectAttempts < this.maxReconnectAttempts) {
this.scheduleReconnect();
}
this.emit('disconnect', { code, reason });
});
});
}
authenticate() {
const authMsg = {
type: 'authenticate',
api_key: this.apiKey
};
this.send(authMsg);
console.log('Authentication message sent');
}
scheduleReconnect() {
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
console.log(`Scheduling reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
setTimeout(() => {
if (this.running) {
this.connect().catch(error => {
console.error('Reconnection failed:', error);
});
}
}, delay);
}
startPingInterval() {
this.pingInterval = setInterval(() => {
if (this.authenticated) {
this.ping();
}
}, 30000); // Ping every 30 seconds
}
stopPingInterval() {
if (this.pingInterval) {
clearInterval(this.pingInterval);
this.pingInterval = null;
}
}
send(message) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message));
return true;
} else {
console.error('WebSocket not connected');
return false;
}
}
ping() {
const pingMsg = { type: 'ping' };
this.send(pingMsg);
}
subscribeToSymbol(symbol, exchanges = 'all') {
if (!this.authenticated) {
console.error('Not authenticated. Cannot subscribe.');
return false;
}
const subscribeMsg = {
type: 'subscribe_aggregated',
symbol: symbol,
exchanges: exchanges
};
if (this.send(subscribeMsg)) {
this.subscriptions.add(symbol);
console.log(`Subscription request sent for ${symbol}`);
return true;
}
return false;
}
unsubscribeFromSymbol(symbol) {
if (!this.subscriptions.has(symbol)) {
console.warn(`Not subscribed to ${symbol}`);
return false;
}
const unsubscribeMsg = {
type: 'unsubscribe_aggregated',
symbol: symbol
};
if (this.send(unsubscribeMsg)) {
this.subscriptions.delete(symbol);
console.log(`Unsubscribed from ${symbol}`);
return true;
}
return false;
}
getCurrentOrderbook(symbol) {
if (!this.authenticated) {
console.error('Not authenticated. Cannot request orderbook.');
return false;
}
const requestMsg = {
type: 'get_aggregated_orderbook',
symbol: symbol
};
if (this.send(requestMsg)) {
console.log(`Orderbook snapshot requested for ${symbol}`);
return true;
}
return false;
}
getExchanges() {
if (!this.authenticated) {
console.error('Not authenticated. Cannot request exchanges.');
return false;
}
const requestMsg = { type: 'get_exchanges' };
if (this.send(requestMsg)) {
console.log('Exchange list requested');
return true;
}
return false;
}
getTradingPairs(exchange = null) {
if (!this.authenticated) {
console.error('Not authenticated. Cannot request trading pairs.');
return false;
}
const requestMsg = { type: 'get_pairs' };
if (exchange) {
requestMsg.exchange = exchange;
}
if (this.send(requestMsg)) {
console.log(`Trading pairs requested for ${exchange || 'all exchanges'}`);
return true;
}
return false;
}
handleMessage(data) {
const msgType = data.type;
switch (msgType) {
case 'aggregated_orderbook_update':
this.handleOrderbookUpdate(data);
break;
case 'aggregated_subscription_response':
this.handleSubscriptionResponse(data);
break;
case 'unsubscribe_response':
this.handleUnsubscribeResponse(data);
break;
case 'exchanges_data':
this.handleExchangesData(data);
break;
case 'pairs_data':
this.handlePairsData(data);
break;
case 'aggregated_orderbook_data':
this.handleOrderbookSnapshot(data);
break;
case 'data_limit_warning':
this.handleDataLimitWarning(data);
break;
case 'rate_limit_warning':
this.handleRateLimitWarning(data);
break;
case 'error':
this.handleError(data);
break;
case 'pong':
this.handlePong(data);
break;
default:
console.log(`Received ${msgType}:`, data);
this.emit('message', data);
}
}
handleOrderbookUpdate(data) {
const symbol = data.symbol;
const updateNum = data.update_number;
const orderbook = data.data || {};
const stats = orderbook.market_stats || {};
console.log(`${symbol} Update #${updateNum}: ` +
`Bid: ${stats.best_bid}, Ask: ${stats.best_ask}, Spread: ${stats.spread}`);
// Emit event for external handling
this.emit('orderbook_update', {
symbol,
updateNum,
orderbook,
stats
});
}
handleOrderbookSnapshot(data) {
const symbol = data.symbol;
const timestamp = data.timestamp;
const orderbook = data.data || {};
const stats = orderbook.market_stats || {};
console.log(`${symbol} Snapshot at ${timestamp}: ` +
`Bid: ${stats.best_bid}, Ask: ${stats.best_ask}`);
this.emit('orderbook_snapshot', {
symbol,
timestamp,
orderbook,
stats
});
}
handleSubscriptionResponse(data) {
const symbol = data.symbol;
const success = data.success;
if (success) {
console.log(`Successfully subscribed to ${symbol}`);
this.emit('subscribed', { symbol, data });
} else {
console.error(`Failed to subscribe to ${symbol}: ${data.message}`);
this.emit('subscription_error', { symbol, data });
}
}
handleUnsubscribeResponse(data) {
const symbol = data.symbol;
console.log(`Unsubscribed from ${symbol}`);
this.emit('unsubscribed', { symbol, data });
}
handleExchangesData(data) {
const exchangesInfo = data.data || {};
const supported = exchangesInfo.supported_exchanges || [];
const active = exchangesInfo.active_exchanges || [];
console.log(`Supported exchanges: ${supported.length}`);
console.log(`Active exchanges: ${active.length}`);
this.emit('exchanges_data', exchangesInfo);
}
handlePairsData(data) {
const pairsInfo = data.data || {};
if (pairsInfo.exchange) {
const exchange = pairsInfo.exchange;
const pairs = pairsInfo.pairs || [];
console.log(`${exchange}: ${pairs.length} trading pairs`);
} else {
const totalExchanges = pairsInfo.total_exchanges || 0;
const totalPairs = pairsInfo.total_pairs || 0;
console.log(`Total: ${totalExchanges} exchanges, ${totalPairs} pairs`);
}
this.emit('pairs_data', pairsInfo);
}
handleDataLimitWarning(data) {
console.warn(`Data limit warning: ${data.message}`);
const usage = data.data_usage || {};
console.warn(`Usage: ${usage.used_gb}GB / ${usage.limit_gb}GB`);
this.emit('data_limit_warning', data);
}
handleRateLimitWarning(data) {
console.warn(`Rate limit warning: ${data.message}`);
this.emit('rate_limit_warning', data);
}
handleError(data) {
console.error(`Error: ${data.error} - ${data.message}`);
this.emit('api_error', data);
}
handlePong(data) {
const latency = data.latency_ms || 0;
console.log(`Pong received (latency: ${latency}ms)`);
this.emit('pong', data);
}
async disconnect() {
this.running = false;
this.stopPingInterval();
if (this.ws) {
this.ws.close();
}
}
// Utility methods
getSubscriptions() {
return Array.from(this.subscriptions);
}
isConnected() {
return this.ws && this.ws.readyState === WebSocket.OPEN;
}
isAuthenticated() {
return this.authenticated;
}
}
// Export the client
module.exports = QuantCiteClient;
// Example usage
async function main() {
const client = new QuantCiteClient('demo_key_123');
// Set up event listeners
client.on('orderbook_update', (data) => {
// Handle real-time orderbook updates
console.log(`Orderbook update for ${data.symbol}`);
});
client.on('error', (error) => {
console.error('Client error:', error);
});
client.on('disconnect', (info) => {
console.log('Disconnected:', info);
});
try {
// Connect and authenticate
await client.connect();
// Wait a moment for authentication
setTimeout(async () => {
if (client.isAuthenticated()) {
// Get available exchanges
client.getExchanges();
// Subscribe to BTC/USDT on specific exchanges
client.subscribeToSymbol('BTC/USDT', ['binance', 'okx', 'bybit']);
// Get current orderbook snapshot
setTimeout(() => {
client.getCurrentOrderbook('BTC/USDT');
}, 1000);
// Subscribe to ETH/USDT on all exchanges
setTimeout(() => {
client.subscribeToSymbol('ETH/USDT', 'all');
}, 2000);
}
}, 1000);
} catch (error) {
console.error('Failed to connect:', error);
}
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('Shutting down...');
await client.disconnect();
process.exit(0);
});
}
// Run example if this file is executed directly
if (require.main === module) {
main();
}
Package Configuration
Create apackage.json file:
Copy
{
"name": "quantcite-client",
"version": "1.0.0",
"description": "QuantCite WebSocket API client",
"main": "quantcite-client.js",
"scripts": {
"start": "node quantcite-client.js",
"test": "node test.js"
},
"dependencies": {
"ws": "^8.14.2"
},
"keywords": ["cryptocurrency", "websocket", "api", "trading", "orderbook"],
"author": "Your Name",
"license": "MIT"
}
Copy
npm install
Usage Examples
Basic Usage
Copy
const QuantCiteClient = require('./quantcite-client');
async function basicExample() {
const client = new QuantCiteClient('your_api_key_here');
// Set up event listener
client.on('orderbook_update', (data) => {
console.log(`${data.symbol}: ${data.stats.best_bid} / ${data.stats.best_ask}`);
});
try {
await client.connect();
// Subscribe to Bitcoin data
setTimeout(() => {
client.subscribeToSymbol('BTC/USDT', ['binance', 'okx']);
}, 1000);
} catch (error) {
console.error('Connection failed:', error);
}
}
basicExample();
Event-Driven Processing
Copy
const client = new QuantCiteClient('your_api_key_here');
// Handle different event types
client.on('orderbook_update', (data) => {
// Process real-time orderbook updates
const { symbol, stats } = data;
console.log(`${symbol}: Spread = ${stats.spread}`);
});
client.on('data_limit_warning', (data) => {
// Handle data usage warnings
console.warn('Approaching data limit!');
});
client.on('subscribed', (data) => {
// Confirmation of successful subscription
console.log(`Now receiving data for ${data.symbol}`);
});
client.on('api_error', (data) => {
// Handle API errors
console.error(`API Error: ${data.error}`);
});
// Connect and start
client.connect();
Multiple Symbol Management
Copy
class TradingBot {
constructor(apiKey) {
this.client = new QuantCiteClient(apiKey);
this.symbols = ['BTC/USDT', 'ETH/USDT', 'BNB/USDT'];
this.orderbooks = new Map();
}
async start() {
this.client.on('orderbook_update', (data) => {
this.orderbooks.set(data.symbol, data.orderbook);
this.analyzeSpread(data.symbol, data.stats);
});
await this.client.connect();
// Subscribe to multiple symbols
setTimeout(() => {
this.symbols.forEach(symbol => {
this.client.subscribeToSymbol(symbol, 'all');
});
}, 1000);
}
analyzeSpread(symbol, stats) {
const spread = stats.spread_percent;
if (spread > 0.1) { // 0.1% spread threshold
console.log(`High spread detected for ${symbol}: ${spread.toFixed(4)}%`);
}
}
}
const bot = new TradingBot('your_api_key_here');
bot.start();
Key Features
Event-Driven Architecture
Built with Node.js EventEmitter for efficient real-time data processing and custom event handling.
Automatic Reconnection
Handles connection drops with exponential backoff retry logic and maintains subscriptions.
Comprehensive Error Handling
Proper error handling for all message types with detailed logging and event emission.
Health Monitoring
Built-in ping/pong mechanism with connection health monitoring and latency tracking.
Browser Compatibility
For browser usage, create a simplified version without Node.js dependencies:Copy
class QuantCiteBrowserClient {
constructor(apiKey, baseUrl = 'data.quantcite.com') {
this.apiKey = apiKey;
this.uri = `wss://${baseUrl}/api/v1/ws`;
this.ws = null;
this.authenticated = false;
this.callbacks = {};
}
on(event, callback) {
if (!this.callbacks[event]) {
this.callbacks[event] = [];
}
this.callbacks[event].push(callback);
}
emit(event, data) {
if (this.callbacks[event]) {
this.callbacks[event].forEach(callback => callback(data));
}
}
connect() {
return new Promise((resolve, reject) => {
this.ws = new WebSocket(this.uri);
this.ws.onopen = () => {
console.log('Connected to QuantCite');
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
if (data.type === 'authentication_success') {
this.authenticated = true;
resolve(true);
}
};
this.ws.onerror = (error) => {
reject(error);
};
});
}
// Add other methods as needed...
}
Remember to replace
"demo_key_123" with your actual API key. The client includes automatic reconnection and comprehensive error handling for production use.