ccxt
Version:
505 lines (504 loc) • 20.6 kB
JavaScript
// ---------------------------------------------------------------------------
import paradexRest from '../paradex.js';
import { ArrayCache, ArrayCacheBySymbolById } from '../base/ws/Cache.js';
// ---------------------------------------------------------------------------
export default class paradex extends paradexRest {
describe() {
return this.deepExtend(super.describe(), {
'has': {
'ws': true,
'watchTicker': true,
'watchTickers': true,
'watchOrderBook': true,
'watchOrders': true,
'watchTrades': true,
'watchTradesForSymbols': false,
'watchBalance': false,
'watchOHLCV': false,
},
'urls': {
'logo': 'https://x.com/tradeparadex/photo',
'api': {
'ws': 'wss://ws.api.prod.paradex.trade/v1',
},
'test': {
'ws': 'wss://ws.api.testnet.paradex.trade/v1',
},
'www': 'https://www.paradex.trade/',
'doc': 'https://docs.api.testnet.paradex.trade/',
'fees': 'https://docs.paradex.trade/getting-started/trading-fees',
'referral': '',
},
'options': {},
'streaming': {},
});
}
requestId() {
const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1);
this.options['requestId'] = requestId;
return requestId;
}
async authenticate(params = {}) {
const url = this.urls['api']['ws'];
const client = this.client(url);
const messageHash = 'authenticated';
const future = client.reusableFuture('authenticated');
const authenticated = this.safeValue(client.subscriptions, messageHash);
if (authenticated === undefined) {
const token = await this.authenticateRest();
const request = {
'jsonrpc': '2.0',
'id': this.requestId(),
'method': 'auth',
'params': {
'bearer': token,
},
};
this.watch(url, messageHash, this.deepExtend(request, params), messageHash);
}
return await future;
}
handleAuthenticationMessage(client, message) {
//
// {
// "jsonrpc": "2.0",
// "id": 1,
// "result": { "node_id": "73cf456f7cb78d59" }
// }
//
const result = this.safeDict(message, 'result');
if (result !== undefined) {
// client.resolve (true, messageHash);
const future = this.safeValue(client.futures, 'authenticated');
if (future !== undefined) {
future.resolve(true);
}
}
}
/**
* @method
* @name paradex#watchTrades
* @description get the list of most recent trades for a particular symbol
* @see https://docs.paradex.trade/ws/web-socket-channels/trades/trades
* @param {string} symbol unified symbol of the market to fetch trades for
* @param {int} [since] timestamp in ms of the earliest trade to fetch
* @param {int} [limit] the maximum amount of trades to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=public-trades}
*/
async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let messageHash = 'trades.';
if (symbol !== undefined) {
const market = this.market(symbol);
messageHash += market['id'];
}
else {
messageHash += 'ALL';
}
const url = this.urls['api']['ws'];
const request = {
'jsonrpc': '2.0',
'method': 'subscribe',
'params': {
'channel': messageHash,
},
};
const trades = await this.watch(url, messageHash, this.deepExtend(request, params), messageHash);
if (this.newUpdates) {
limit = trades.getLimit(symbol, limit);
}
return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
}
handleTrade(client, message) {
//
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "trades.ALL",
// "data": {
// "id": "1718179273230201709233240002",
// "market": "kBONK-USD-PERP",
// "side": "BUY",
// "size": "34028",
// "price": "0.028776",
// "created_at": 1718179273230,
// "trade_type": "FILL"
// }
// }
// }
//
const params = this.safeDict(message, 'params', {});
const data = this.safeDict(params, 'data', {});
const parsedTrade = this.parseTrade(data);
const symbol = parsedTrade['symbol'];
const messageHash = this.safeString(params, 'channel');
let stored = this.safeValue(this.trades, symbol);
if (stored === undefined) {
stored = new ArrayCache(this.safeInteger(this.options, 'tradesLimit', 1000));
this.trades[symbol] = stored;
}
stored.append(parsedTrade);
client.resolve(stored, messageHash);
return message;
}
/**
* @method
* @name paradex#watchOrderBook
* @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @see https://docs.paradex.trade/ws/web-socket-channels/order-book/order-book
* @param {string} symbol unified symbol of the market to fetch the order book for
* @param {int} [limit] the maximum amount of order book entries to return
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/?id=order-book-structure} indexed by market symbols
*/
async watchOrderBook(symbol, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const messageHash = 'order_book.' + market['id'] + '.snapshot@15@100ms';
const url = this.urls['api']['ws'];
const request = {
'jsonrpc': '2.0',
'method': 'subscribe',
'params': {
'channel': messageHash,
},
};
const orderbook = await this.watch(url, messageHash, this.deepExtend(request, params), messageHash);
return orderbook.limit();
}
handleOrderBook(client, message) {
//
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "order_book.BTC-USD-PERP.snapshot@15@50ms",
// "data": {
// "seq_no": 14127815,
// "market": "BTC-USD-PERP",
// "last_updated_at": 1718267837265,
// "update_type": "s",
// "inserts": [
// {
// "side": "BUY",
// "price": "67629.7",
// "size": "0.992"
// },
// {
// "side": "SELL",
// "price": "69378.6",
// "size": "3.137"
// }
// ],
// "updates": [],
// "deletes": []
// }
// }
// }
//
const params = this.safeDict(message, 'params', {});
const data = this.safeDict(params, 'data', {});
const marketId = this.safeString(data, 'market');
const market = this.safeMarket(marketId);
const timestamp = this.safeInteger(data, 'last_updated_at');
const symbol = market['symbol'];
if (!(symbol in this.orderbooks)) {
this.orderbooks[symbol] = this.orderBook();
}
const orderbookData = {
'bids': [],
'asks': [],
};
const inserts = this.safeList(data, 'inserts');
for (let i = 0; i < inserts.length; i++) {
const insert = this.safeDict(inserts, i);
const side = this.safeString(insert, 'side');
const price = this.safeString(insert, 'price');
const size = this.safeString(insert, 'size');
if (side === 'BUY') {
orderbookData['bids'].push([price, size]);
}
else {
orderbookData['asks'].push([price, size]);
}
}
const orderbook = this.orderbooks[symbol];
const snapshot = this.parseOrderBook(orderbookData, symbol, timestamp, 'bids', 'asks');
snapshot['nonce'] = this.safeNumber(data, 'seq_no');
orderbook.reset(snapshot);
const messageHash = this.safeString(params, 'channel');
client.resolve(orderbook, messageHash);
}
/**
* @method
* @name paradex#watchTicker
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
* @see https://docs.paradex.trade/ws/web-socket-channels/markets-summary/markets-summary
* @param {string} symbol unified symbol of the market to fetch the ticker for
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure}
*/
async watchTicker(symbol, params = {}) {
await this.loadMarkets();
symbol = this.symbol(symbol);
const channel = 'markets_summary';
const url = this.urls['api']['ws'];
const request = {
'jsonrpc': '2.0',
'method': 'subscribe',
'params': {
'channel': channel,
},
};
const messageHash = channel + '.' + symbol;
return await this.watch(url, messageHash, this.deepExtend(request, params), messageHash);
}
/**
* @method
* @name paradex#watchTickers
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
* @see https://docs.paradex.trade/ws/web-socket-channels/markets-summary/markets-summary
* @param {string[]} symbols unified symbol of the market to fetch the ticker for
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure}
*/
async watchTickers(symbols = undefined, params = {}) {
await this.loadMarkets();
symbols = this.marketSymbols(symbols);
const channel = 'markets_summary';
const url = this.urls['api']['ws'];
const request = {
'jsonrpc': '2.0',
'method': 'subscribe',
'params': {
'channel': channel,
},
};
const messageHashes = [];
if (Array.isArray(symbols)) {
for (let i = 0; i < symbols.length; i++) {
const messageHash = channel + '.' + symbols[i];
messageHashes.push(messageHash);
}
}
else {
messageHashes.push(channel);
}
const newTickers = await this.watchMultiple(url, messageHashes, this.deepExtend(request, params), messageHashes);
if (this.newUpdates) {
const result = {};
result[newTickers['symbol']] = newTickers;
return result;
}
return this.filterByArray(this.tickers, 'symbol', symbols);
}
/**
* @method
* @name paradex#watchOrders
* @description watches information on multiple orders made by the user
* @see https://docs.paradex.trade/ws/web-socket-channels/orders/orders
* @param {string} [symbol] unified market symbol of the market orders were made in
* @param {int} [since] the earliest time in ms to fetch orders for
* @param {int} [limit] the maximum number of order structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/?id=order-structure}
*/
async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
await this.authenticate();
let messageHash = 'orders';
let channel = 'orders.';
if (symbol !== undefined) {
const market = this.market(symbol);
symbol = market['symbol'];
channel += market['id'];
messageHash += ':' + symbol;
}
else {
channel += 'ALL';
}
const url = this.urls['api']['ws'];
const request = {
'jsonrpc': '2.0',
'method': 'subscribe',
'params': {
'channel': channel,
},
};
const orders = await this.watch(url, messageHash, this.deepExtend(request, params), channel);
if (this.newUpdates) {
limit = orders.getLimit(symbol, limit);
}
return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true);
}
handleOrder(client, message) {
//
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "orders.ALL",
// "data": {
// "account": "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x",
// "avg_fill_price": "26000",
// "client_id": "x1234",
// "cancel_reason": "",
// "created_at": 1681493746016,
// "flags": ["REDUCE_ONLY"],
// "id": "123456",
// "instruction": "GTC",
// "last_updated_at": 1681493746016,
// "market": "BTC-USD-PERP",
// "price": "26000",
// "remaining_size": "0",
// "side": "BUY",
// "size": "0.05",
// "status": "NEW",
// "type": "LIMIT"
// }
// }
// }
//
const params = this.safeDict(message, 'params', {});
const data = this.safeDict(params, 'data', {});
const parsed = this.parseOrder(data);
const symbol = this.safeString(parsed, 'symbol');
if (this.orders === undefined) {
const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
this.orders = new ArrayCacheBySymbolById(limit);
}
this.orders.append(parsed);
const messageHash = 'orders';
client.resolve(this.orders, messageHash);
if (symbol !== undefined) {
const symbolMessageHash = messageHash + ':' + symbol;
client.resolve(this.orders, symbolMessageHash);
}
}
handleTicker(client, message) {
//
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "markets_summary",
// "data": {
// "symbol": "ORDI-USD-PERP",
// "oracle_price": "49.80885481",
// "mark_price": "49.80885481",
// "last_traded_price": "62.038",
// "bid": "49.822",
// "ask": "58.167",
// "volume_24h": "0",
// "total_volume": "54542628.66054200416",
// "created_at": 1718334307698,
// "underlying_price": "47.93",
// "open_interest": "6999.5",
// "funding_rate": "0.03919997509811",
// "price_change_rate_24h": ""
// }
// }
// }
//
const params = this.safeDict(message, 'params', {});
const data = this.safeDict(params, 'data', {});
const marketId = this.safeString(data, 'symbol');
const market = this.safeMarket(marketId);
const symbol = market['symbol'];
const channel = this.safeString(params, 'channel');
const messageHash = channel + '.' + symbol;
const ticker = this.parseTicker(data, market);
this.tickers[symbol] = ticker;
client.resolve(ticker, channel);
client.resolve(ticker, messageHash);
return message;
}
handleErrorMessage(client, message) {
//
// {
// "jsonrpc": "2.0",
// "id": 0,
// "error": {
// "code": -32600,
// "message": "invalid subscribe request",
// "data": "invalid channel"
// },
// "usIn": 1718179125962419,
// "usDiff": 76,
// "usOut": 1718179125962495
// }
//
const error = this.safeDict(message, 'error');
if (error === undefined) {
return true;
}
else {
const errorCode = this.safeString(error, 'code');
if (errorCode !== undefined) {
const feedback = this.id + ' ' + this.json(error);
this.throwExactlyMatchedException(this.exceptions['exact'], '-32600', feedback);
const messageString = this.safeValue(error, 'message');
if (messageString !== undefined) {
this.throwBroadlyMatchedException(this.exceptions['broad'], messageString, feedback);
}
}
return false;
}
}
handleMessage(client, message) {
if (!this.handleErrorMessage(client, message)) {
return;
}
//
// auth response
//
// {
// "jsonrpc": "2.0",
// "id": 1,
// "result": { "node_id": "73cf456f7cb78d59" }
// }
//
// subscription message
//
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "trades.ALL",
// "data": {
// "id": "1718179273230201709233240002",
// "market": "kBONK-USD-PERP",
// "side": "BUY",
// "size": "34028",
// "price": "0.028776",
// "created_at": 1718179273230,
// "trade_type": "FILL"
// }
// }
// }
//
const result = this.safeValue(message, 'result');
if (result !== undefined) {
this.handleAuthenticationMessage(client, message);
return;
}
const data = this.safeDict(message, 'params');
if (data !== undefined) {
const channel = this.safeString(data, 'channel');
const parts = channel.split('.');
const name = this.safeString(parts, 0);
const methods = {
'trades': this.handleTrade,
'order_book': this.handleOrderBook,
'markets_summary': this.handleTicker,
'orders': this.handleOrder,
};
const method = this.safeValue(methods, name);
if (method !== undefined) {
method.call(this, client, message);
}
}
}
}