ccxt
Version:
1,067 lines (1,064 loc) • 48.5 kB
JavaScript
// ----------------------------------------------------------------------------
// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
// EDIT THE CORRESPONDENT .ts FILE INSTEAD
// ---------------------------------------------------------------------------
import deribitRest from '../deribit.js';
import { NotSupported, ExchangeError, ArgumentsRequired } from '../base/errors.js';
import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp } from '../base/ws/Cache.js';
import { sha256 } from '../static_dependencies/noble-hashes/sha256.js';
// ---------------------------------------------------------------------------
export default class deribit extends deribitRest {
describe() {
return this.deepExtend(super.describe(), {
'has': {
'ws': true,
'watchBalance': true,
'watchTicker': true,
'watchTickers': true,
'watchBidsAsks': true,
'watchTrades': true,
'watchTradesForSymbols': true,
'watchMyTrades': true,
'watchOrders': true,
'watchOrderBook': true,
'watchOrderBookForSymbols': true,
'watchOHLCV': true,
'watchOHLCVForSymbols': true,
},
'urls': {
'test': {
'ws': 'wss://test.deribit.com/ws/api/v2',
},
'api': {
'ws': 'wss://www.deribit.com/ws/api/v2',
},
},
'options': {
'ws': {
'timeframes': {
'1m': '1',
'3m': '3',
'5m': '5',
'15m': '15',
'30m': '30',
'1h': '60',
'2h': '120',
'4h': '180',
'6h': '360',
'12h': '720',
'1d': '1D',
},
// watchTrades replacement
'watchTradesForSymbols': {
'interval': '100ms', // 100ms, agg2, raw
},
// watchOrderBook replacement
'watchOrderBookForSymbols': {
'interval': '100ms',
'useDepthEndpoint': false,
'depth': '20',
'group': 'none', // none, 1, 2, 5, 10, 25, 100, 250
},
},
'currencies': ['BTC', 'ETH', 'SOL', 'USDC'],
},
'streaming': {},
'exceptions': {},
});
}
requestId() {
const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1);
this.options['requestId'] = requestId;
return requestId;
}
/**
* @method
* @name deribit#watchBalance
* @see https://docs.deribit.com/#user-portfolio-currency
* @description watch balance and get the amount of funds available for trading or funds locked in orders
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
*/
async watchBalance(params = {}) {
await this.authenticate(params);
const messageHash = 'balance';
const url = this.urls['api']['ws'];
const currencies = this.safeValue(this.options, 'currencies', []);
const channels = [];
for (let i = 0; i < currencies.length; i++) {
const currencyCode = currencies[i];
channels.push('user.portfolio.' + currencyCode);
}
const subscribe = {
'jsonrpc': '2.0',
'method': 'private/subscribe',
'params': {
'channels': channels,
},
'id': this.requestId(),
};
const request = this.deepExtend(subscribe, params);
return await this.watch(url, messageHash, request, messageHash, request);
}
handleBalance(client, message) {
//
// subscription
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "user.portfolio.btc",
// "data": {
// "total_pl": 0,
// "session_upl": 0,
// "session_rpl": 0,
// "projected_maintenance_margin": 0,
// "projected_initial_margin": 0,
// "projected_delta_total": 0,
// "portfolio_margining_enabled": false,
// "options_vega": 0,
// "options_value": 0,
// "options_theta": 0,
// "options_session_upl": 0,
// "options_session_rpl": 0,
// "options_pl": 0,
// "options_gamma": 0,
// "options_delta": 0,
// "margin_balance": 0.0015,
// "maintenance_margin": 0,
// "initial_margin": 0,
// "futures_session_upl": 0,
// "futures_session_rpl": 0,
// "futures_pl": 0,
// "fee_balance": 0,
// "estimated_liquidation_ratio_map": {},
// "estimated_liquidation_ratio": 0,
// "equity": 0.0015,
// "delta_total_map": {},
// "delta_total": 0,
// "currency": "BTC",
// "balance": 0.0015,
// "available_withdrawal_funds": 0.0015,
// "available_funds": 0.0015
// }
// }
// }
//
const params = this.safeValue(message, 'params', {});
const data = this.safeValue(params, 'data', {});
this.balance['info'] = data;
const currencyId = this.safeString(data, 'currency');
const currencyCode = this.safeCurrencyCode(currencyId);
const balance = this.parseBalance(data);
this.balance[currencyCode] = balance;
const messageHash = 'balance';
client.resolve(this.balance, messageHash);
}
/**
* @method
* @name deribit#watchTicker
* @see https://docs.deribit.com/#ticker-instrument_name-interval
* @description watches a price ticker, a statistical calculation with the information for a specific market.
* @param {string} symbol unified symbol of the market to fetch the ticker for
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {str} [params.interval] specify aggregation and frequency of notifications. Possible values: 100ms, raw
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
async watchTicker(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const url = this.urls['api']['ws'];
const interval = this.safeString(params, 'interval', '100ms');
params = this.omit(params, 'interval');
await this.loadMarkets();
if (interval === 'raw') {
await this.authenticate();
}
const channel = 'ticker.' + market['id'] + '.' + interval;
const message = {
'jsonrpc': '2.0',
'method': 'public/subscribe',
'params': {
'channels': ['ticker.' + market['id'] + '.' + interval],
},
'id': this.requestId(),
};
const request = this.deepExtend(message, params);
return await this.watch(url, channel, request, channel, request);
}
/**
* @method
* @name deribit#watchTickers
* @see https://docs.deribit.com/#ticker-instrument_name-interval
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
* @param {string[]} [symbols] unified symbol of the market to fetch the ticker for
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {str} [params.interval] specify aggregation and frequency of notifications. Possible values: 100ms, raw
* @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, undefined, false);
const url = this.urls['api']['ws'];
const interval = this.safeString(params, 'interval', '100ms');
params = this.omit(params, 'interval');
await this.loadMarkets();
if (interval === 'raw') {
await this.authenticate();
}
const channels = [];
for (let i = 0; i < symbols.length; i++) {
const market = this.market(symbols[i]);
channels.push('ticker.' + market['id'] + '.' + interval);
}
const message = {
'jsonrpc': '2.0',
'method': 'public/subscribe',
'params': {
'channels': channels,
},
'id': this.requestId(),
};
const request = this.deepExtend(message, params);
const newTickers = await this.watchMultiple(url, channels, request, channels, request);
if (this.newUpdates) {
const tickers = {};
tickers[newTickers['symbol']] = newTickers;
return tickers;
}
return this.filterByArray(this.tickers, 'symbol', symbols);
}
handleTicker(client, message) {
//
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "ticker.BTC_USDC-PERPETUAL.raw",
// "data": {
// "timestamp": 1655393725040,
// "stats": [Object],
// "state": "open",
// "settlement_price": 21729.5891,
// "open_interest": 164.501,
// "min_price": 20792.9376,
// "max_price": 21426.225,
// "mark_price": 21109.555,
// "last_price": 21132,
// "instrument_name": "BTC_USDC-PERPETUAL",
// "index_price": 21122.3937,
// "funding_8h": -0.00022427,
// "estimated_delivery_price": 21122.3937,
// "current_funding": -0.00010782,
// "best_bid_price": 21106,
// "best_bid_amount": 1.143,
// "best_ask_price": 21113,
// "best_ask_amount": 0.327
// }
// }
// }
//
const params = this.safeValue(message, 'params', {});
const data = this.safeValue(params, 'data', {});
const marketId = this.safeString(data, 'instrument_name');
const symbol = this.safeSymbol(marketId);
const ticker = this.parseTicker(data);
const messageHash = this.safeString(params, 'channel');
this.tickers[symbol] = ticker;
client.resolve(ticker, messageHash);
}
/**
* @method
* @name deribit#watchBidsAsks
* @see https://docs.deribit.com/#quote-instrument_name
* @description watches best bid & ask for symbols
* @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 watchBidsAsks(symbols = undefined, params = {}) {
await this.loadMarkets();
symbols = this.marketSymbols(symbols, undefined, false);
const url = this.urls['api']['ws'];
const channels = [];
for (let i = 0; i < symbols.length; i++) {
const market = this.market(symbols[i]);
channels.push('quote.' + market['id']);
}
const message = {
'jsonrpc': '2.0',
'method': 'public/subscribe',
'params': {
'channels': channels,
},
'id': this.requestId(),
};
const request = this.deepExtend(message, params);
const newTickers = await this.watchMultiple(url, channels, request, channels, request);
if (this.newUpdates) {
const tickers = {};
tickers[newTickers['symbol']] = newTickers;
return tickers;
}
return this.filterByArray(this.bidsasks, 'symbol', symbols);
}
handleBidAsk(client, message) {
//
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "quote.BTC_USDT",
// "data": {
// "best_bid_amount": 0.026,
// "best_ask_amount": 0.026,
// "best_bid_price": 63908,
// "best_ask_price": 63940,
// "instrument_name": "BTC_USDT",
// "timestamp": 1727765131750
// }
// }
// }
//
const params = this.safeDict(message, 'params', {});
const data = this.safeDict(params, 'data', {});
const ticker = this.parseWsBidAsk(data);
const symbol = ticker['symbol'];
this.bidsasks[symbol] = ticker;
const messageHash = this.safeString(params, 'channel');
client.resolve(ticker, messageHash);
}
parseWsBidAsk(ticker, market = undefined) {
const marketId = this.safeString(ticker, 'instrument_name');
market = this.safeMarket(marketId, market);
const symbol = this.safeString(market, 'symbol');
const timestamp = this.safeInteger(ticker, 'timestamp');
return this.safeTicker({
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'ask': this.safeString(ticker, 'best_ask_price'),
'askVolume': this.safeString(ticker, 'best_ask_amount'),
'bid': this.safeString(ticker, 'best_bid_price'),
'bidVolume': this.safeString(ticker, 'best_bid_amount'),
'info': ticker,
}, market);
}
/**
* @method
* @name deribit#watchTrades
* @description get the list of most recent trades for a particular symbol
* @see https://docs.deribit.com/#trades-instrument_name-interval
* @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
* @param {str} [params.interval] specify aggregation and frequency of notifications. Possible values: 100ms, raw
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
*/
async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
params['callerMethodName'] = 'watchTrades';
return await this.watchTradesForSymbols([symbol], since, limit, params);
}
/**
* @method
* @name deribit#watchTradesForSymbols
* @description get the list of most recent trades for a list of symbols
* @see https://docs.deribit.com/#trades-instrument_name-interval
* @param {string[]} symbols 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 watchTradesForSymbols(symbols, since = undefined, limit = undefined, params = {}) {
let interval = undefined;
[interval, params] = this.handleOptionAndParams(params, 'watchTradesForSymbols', 'interval', '100ms');
if (interval === 'raw') {
await this.authenticate();
}
const trades = await this.watchMultipleWrapper('trades', interval, symbols, params);
if (this.newUpdates) {
const first = this.safeDict(trades, 0);
const tradeSymbol = this.safeString(first, 'symbol');
limit = trades.getLimit(tradeSymbol, limit);
}
return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
}
handleTrades(client, message) {
//
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "trades.BTC_USDC-PERPETUAL.100ms",
// "data": [{
// "trade_seq": 501899,
// "trade_id": "USDC-2436803",
// "timestamp": 1655397355998,
// "tick_direction": 2,
// "price": 21026,
// "mark_price": 21019.9719,
// "instrument_name": "BTC_USDC-PERPETUAL",
// "index_price": 21031.7847,
// "direction": "buy",
// "amount": 0.049
// }]
// }
// }
//
const params = this.safeDict(message, 'params', {});
const channel = this.safeString(params, 'channel', '');
const parts = channel.split('.');
const marketId = this.safeString(parts, 1);
const interval = this.safeString(parts, 2);
const symbol = this.safeSymbol(marketId);
const market = this.safeMarket(marketId);
const trades = this.safeList(params, 'data', []);
if (this.safeValue(this.trades, symbol) === undefined) {
const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
this.trades[symbol] = new ArrayCache(limit);
}
const stored = this.trades[symbol];
for (let i = 0; i < trades.length; i++) {
const trade = trades[i];
const parsed = this.parseTrade(trade, market);
stored.append(parsed);
}
this.trades[symbol] = stored;
const messageHash = 'trades|' + symbol + '|' + interval;
client.resolve(this.trades[symbol], messageHash);
}
/**
* @method
* @name deribit#watchMyTrades
* @description get the list of trades associated with the user
* @see https://docs.deribit.com/#user-trades-instrument_name-interval
* @param {string} symbol unified symbol of the market to fetch trades for. Use 'any' to watch all trades
* @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
* @param {str} [params.interval] specify aggregation and frequency of notifications. Possible values: 100ms, raw
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
*/
async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.authenticate(params);
if (symbol !== undefined) {
await this.loadMarkets();
symbol = this.symbol(symbol);
}
const url = this.urls['api']['ws'];
const interval = this.safeString(params, 'interval', 'raw');
params = this.omit(params, 'interval');
const channel = 'user.trades.any.any.' + interval;
const message = {
'jsonrpc': '2.0',
'method': 'private/subscribe',
'params': {
'channels': [channel],
},
'id': this.requestId(),
};
const request = this.deepExtend(message, params);
const trades = await this.watch(url, channel, request, channel, request);
return this.filterBySymbolSinceLimit(trades, symbol, since, limit, true);
}
handleMyTrades(client, message) {
//
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "user.trades.any.any.raw",
// "data": [{
// "trade_seq": 149546319,
// "trade_id": "219381310",
// "timestamp": 1655421193564,
// "tick_direction": 0,
// "state": "filled",
// "self_trade": false,
// "reduce_only": false,
// "profit_loss": 0,
// "price": 20236.5,
// "post_only": false,
// "order_type": "market",
// "order_id": "46108941243",
// "matching_id": null,
// "mark_price": 20233.96,
// "liquidity": "T",
// "instrument_name": "BTC-PERPETUAL",
// "index_price": 20253.31,
// "fee_currency": "BTC",
// "fee": 2.5e-7,
// "direction": "buy",
// "amount": 10
// }]
// }
// }
//
const params = this.safeValue(message, 'params', {});
const channel = this.safeString(params, 'channel', '');
const trades = this.safeValue(params, 'data', []);
let cachedTrades = this.myTrades;
if (cachedTrades === undefined) {
const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
cachedTrades = new ArrayCacheBySymbolById(limit);
}
const parsed = this.parseTrades(trades);
const marketIds = {};
for (let i = 0; i < parsed.length; i++) {
const trade = parsed[i];
cachedTrades.append(trade);
const symbol = trade['symbol'];
marketIds[symbol] = true;
}
client.resolve(cachedTrades, channel);
}
/**
* @method
* @name deribit#watchOrderBook
* @see https://docs.deribit.com/#book-instrument_name-group-depth-interval
* @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @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
* @param {string} [params.interval] Frequency of notifications. Events will be aggregated over this interval. Possible values: 100ms, raw
* @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 = {}) {
params['callerMethodName'] = 'watchOrderBook';
return await this.watchOrderBookForSymbols([symbol], limit, params);
}
/**
* @method
* @name deribit#watchOrderBookForSymbols
* @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @see https://docs.deribit.com/#book-instrument_name-group-depth-interval
* @param {string[]} symbols unified array of symbols
* @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 watchOrderBookForSymbols(symbols, limit = undefined, params = {}) {
let interval = undefined;
[interval, params] = this.handleOptionAndParams(params, 'watchOrderBookForSymbols', 'interval', '100ms');
if (interval === 'raw') {
await this.authenticate();
}
let descriptor = '';
let useDepthEndpoint = undefined; // for more info, see comment in .options
[useDepthEndpoint, params] = this.handleOptionAndParams(params, 'watchOrderBookForSymbols', 'useDepthEndpoint', false);
if (useDepthEndpoint) {
let depth = undefined;
[depth, params] = this.handleOptionAndParams(params, 'watchOrderBookForSymbols', 'depth', '20');
let group = undefined;
[group, params] = this.handleOptionAndParams(params, 'watchOrderBookForSymbols', 'group', 'none');
descriptor = group + '.' + depth + '.' + interval;
}
else {
descriptor = interval;
}
const orderbook = await this.watchMultipleWrapper('book', descriptor, symbols, params);
return orderbook.limit();
}
handleOrderBook(client, message) {
//
// snapshot
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "book.BTC_USDC-PERPETUAL.raw",
// "data": {
// "type": "snapshot",
// "timestamp": 1655395057025,
// "instrument_name": "BTC_USDC-PERPETUAL",
// "change_id": 1550694837,
// "bids": [
// ["new", 20987, 0.487],
// ["new", 20986, 0.238],
// ],
// "asks": [
// ["new", 20999, 0.092],
// ["new", 21000, 1.238],
// ]
// }
// }
// }
//
// change
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "book.BTC_USDC-PERPETUAL.raw",
// "data": {
// "type": "change",
// "timestamp": 1655395168086,
// "prev_change_id": 1550724481,
// "instrument_name": "BTC_USDC-PERPETUAL",
// "change_id": 1550724483,
// "bids": [
// ["new", 20977, 0.109],
// ["delete", 20975, 0]
// ],
// "asks": []
// }
// }
// }
//
const params = this.safeValue(message, 'params', {});
const data = this.safeValue(params, 'data', {});
const channel = this.safeString(params, 'channel');
const parts = channel.split('.');
let descriptor = '';
const partsLength = parts.length;
const isDetailed = partsLength === 5;
if (isDetailed) {
const group = this.safeString(parts, 2);
const depth = this.safeString(parts, 3);
const interval = this.safeString(parts, 4);
descriptor = group + '.' + depth + '.' + interval;
}
else {
const interval = this.safeString(parts, 2);
descriptor = interval;
}
const marketId = this.safeString(data, 'instrument_name');
const symbol = this.safeSymbol(marketId);
const timestamp = this.safeInteger(data, 'timestamp');
if (!(symbol in this.orderbooks)) {
this.orderbooks[symbol] = this.countedOrderBook();
}
const storedOrderBook = this.orderbooks[symbol];
const asks = this.safeList(data, 'asks', []);
const bids = this.safeList(data, 'bids', []);
this.handleDeltas(storedOrderBook['asks'], asks);
this.handleDeltas(storedOrderBook['bids'], bids);
storedOrderBook['nonce'] = timestamp;
storedOrderBook['timestamp'] = timestamp;
storedOrderBook['datetime'] = this.iso8601(timestamp);
storedOrderBook['symbol'] = symbol;
this.orderbooks[symbol] = storedOrderBook;
const messageHash = 'book|' + symbol + '|' + descriptor;
client.resolve(storedOrderBook, messageHash);
}
cleanOrderBook(data) {
const bids = this.safeList(data, 'bids', []);
const asks = this.safeList(data, 'asks', []);
const cleanedBids = [];
for (let i = 0; i < bids.length; i++) {
cleanedBids.push([bids[i][1], bids[i][2]]);
}
const cleanedAsks = [];
for (let i = 0; i < asks.length; i++) {
cleanedAsks.push([asks[i][1], asks[i][2]]);
}
data['bids'] = cleanedBids;
data['asks'] = cleanedAsks;
return data;
}
handleDelta(bookside, delta) {
const price = delta[1];
const amount = delta[2];
if (delta[0] === 'new' || delta[0] === 'change') {
bookside.storeArray([price, amount, 1]);
}
else if (delta[0] === 'delete') {
bookside.storeArray([price, amount, 0]);
}
}
handleDeltas(bookside, deltas) {
for (let i = 0; i < deltas.length; i++) {
this.handleDelta(bookside, deltas[i]);
}
}
/**
* @method
* @name deribit#watchOrders
* @see https://docs.deribit.com/#user-orders-instrument_name-raw
* @description watches information on multiple orders made by the user
* @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(params);
if (symbol !== undefined) {
symbol = this.symbol(symbol);
}
const url = this.urls['api']['ws'];
const currency = this.safeString(params, 'currency', 'any');
const interval = this.safeString(params, 'interval', 'raw');
const kind = this.safeString(params, 'kind', 'any');
params = this.omit(params, 'interval', 'currency', 'kind');
const channel = 'user.orders.' + kind + '.' + currency + '.' + interval;
const message = {
'jsonrpc': '2.0',
'method': 'private/subscribe',
'params': {
'channels': [channel],
},
'id': this.requestId(),
};
const request = this.deepExtend(message, params);
const orders = await this.watch(url, channel, request, channel, request);
if (this.newUpdates) {
limit = orders.getLimit(symbol, limit);
}
return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true);
}
handleOrders(client, message) {
// Does not return a snapshot of current orders
//
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "user.orders.any.any.raw",
// "data": {
// "web": true,
// "time_in_force": "good_til_cancelled",
// "replaced": false,
// "reduce_only": false,
// "profit_loss": 0,
// "price": 50000,
// "post_only": false,
// "order_type": "limit",
// "order_state": "open",
// "order_id": "46094375191",
// "max_show": 10,
// "last_update_timestamp": 1655401625037,
// "label": '',
// "is_liquidation": false,
// "instrument_name": "BTC-PERPETUAL",
// "filled_amount": 0,
// "direction": "sell",
// "creation_timestamp": 1655401625037,
// "commission": 0,
// "average_price": 0,
// "api": false,
// "amount": 10
// }
// }
// }
//
if (this.orders === undefined) {
const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
this.orders = new ArrayCacheBySymbolById(limit);
}
const params = this.safeValue(message, 'params', {});
const channel = this.safeString(params, 'channel', '');
const data = this.safeValue(params, 'data', {});
let orders = [];
if (Array.isArray(data)) {
orders = this.parseOrders(data);
}
else {
const order = this.parseOrder(data);
orders = [order];
}
const cachedOrders = this.orders;
for (let i = 0; i < orders.length; i++) {
cachedOrders.append(orders[i]);
}
client.resolve(this.orders, channel);
}
/**
* @method
* @name deribit#watchOHLCV
* @see https://docs.deribit.com/#chart-trades-instrument_name-resolution
* @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
* @param {string} symbol unified symbol of the market to fetch OHLCV data for
* @param {string} timeframe the length of time each candle represents
* @param {int} [since] timestamp in ms of the earliest candle to fetch
* @param {int} [limit] the maximum amount of candles to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
*/
async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
symbol = this.symbol(symbol);
const ohlcvs = await this.watchOHLCVForSymbols([[symbol, timeframe]], since, limit, params);
return ohlcvs[symbol][timeframe];
}
/**
* @method
* @name deribit#watchOHLCVForSymbols
* @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
* @see https://docs.deribit.com/#chart-trades-instrument_name-resolution
* @param {string[][]} symbolsAndTimeframes array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
* @param {int} [since] timestamp in ms of the earliest candle to fetch
* @param {int} [limit] the maximum amount of candles to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
*/
async watchOHLCVForSymbols(symbolsAndTimeframes, since = undefined, limit = undefined, params = {}) {
const symbolsLength = symbolsAndTimeframes.length;
if (symbolsLength === 0 || !Array.isArray(symbolsAndTimeframes[0])) {
throw new ArgumentsRequired(this.id + " watchOHLCVForSymbols() requires a an array of symbols and timeframes, like [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]");
}
const [symbol, timeframe, candles] = await this.watchMultipleWrapper('chart.trades', undefined, symbolsAndTimeframes, params);
if (this.newUpdates) {
limit = candles.getLimit(symbol, limit);
}
const filtered = this.filterBySinceLimit(candles, since, limit, 0, true);
return this.createOHLCVObject(symbol, timeframe, filtered);
}
handleOHLCV(client, message) {
//
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "chart.trades.BTC_USDC-PERPETUAL.1",
// "data": {
// "volume": 0,
// "tick": 1655403420000,
// "open": 20951,
// "low": 20951,
// "high": 20951,
// "cost": 0,
// "close": 20951
// }
// }
// }
//
const params = this.safeDict(message, 'params', {});
const channel = this.safeString(params, 'channel', '');
const parts = channel.split('.');
const marketId = this.safeString(parts, 2);
const rawTimeframe = this.safeString(parts, 3);
const market = this.safeMarket(marketId);
const symbol = market['symbol'];
const wsOptions = this.safeDict(this.options, 'ws', {});
const timeframes = this.safeDict(wsOptions, 'timeframes', {});
const unifiedTimeframe = this.findTimeframe(rawTimeframe, timeframes);
this.ohlcvs[symbol] = this.safeDict(this.ohlcvs, symbol, {});
if (this.safeValue(this.ohlcvs[symbol], unifiedTimeframe) === undefined) {
const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000);
this.ohlcvs[symbol][unifiedTimeframe] = new ArrayCacheByTimestamp(limit);
}
const stored = this.ohlcvs[symbol][unifiedTimeframe];
const ohlcv = this.safeDict(params, 'data', {});
// data contains a single OHLCV candle
const parsed = this.parseWsOHLCV(ohlcv, market);
stored.append(parsed);
this.ohlcvs[symbol][unifiedTimeframe] = stored;
const resolveData = [symbol, unifiedTimeframe, stored];
const messageHash = 'chart.trades|' + symbol + '|' + rawTimeframe;
client.resolve(resolveData, messageHash);
}
parseWsOHLCV(ohlcv, market = undefined) {
//
// {
// "c": "28909.0",
// "o": "28915.4",
// "h": "28915.4",
// "l": "28896.1",
// "v": "27.6919",
// "T": 1696687499999,
// "t": 1696687440000
// }
//
return [
this.safeInteger(ohlcv, 'tick'),
this.safeNumber(ohlcv, 'open'),
this.safeNumber(ohlcv, 'high'),
this.safeNumber(ohlcv, 'low'),
this.safeNumber(ohlcv, 'close'),
this.safeNumber(ohlcv, 'volume'),
];
}
async watchMultipleWrapper(channelName, channelDescriptor, symbolsArray = undefined, params = {}) {
await this.loadMarkets();
const url = this.urls['api']['ws'];
const rawSubscriptions = [];
const messageHashes = [];
const isOHLCV = (channelName === 'chart.trades');
const symbols = isOHLCV ? this.getListFromObjectValues(symbolsArray, 0) : symbolsArray;
this.marketSymbols(symbols, undefined, false);
for (let i = 0; i < symbolsArray.length; i++) {
const current = symbolsArray[i];
let market = undefined;
if (isOHLCV) {
market = this.market(current[0]);
const unifiedTf = current[1];
const rawTf = this.safeString(this.timeframes, unifiedTf, unifiedTf);
channelDescriptor = rawTf;
}
else {
market = this.market(current);
}
const message = channelName + '.' + market['id'] + '.' + channelDescriptor;
rawSubscriptions.push(message);
messageHashes.push(channelName + '|' + market['symbol'] + '|' + channelDescriptor);
}
const request = {
'jsonrpc': '2.0',
'method': 'public/subscribe',
'params': {
'channels': rawSubscriptions,
},
'id': this.requestId(),
};
const extendedRequest = this.deepExtend(request, params);
const maxMessageByteLimit = 32768 - 1; // 'Message Too Big: limit 32768B'
const jsonedText = this.json(extendedRequest);
if (jsonedText.length >= maxMessageByteLimit) {
throw new ExchangeError(this.id + ' requested subscription length over limit, try to reduce symbols amount');
}
return await this.watchMultiple(url, messageHashes, extendedRequest, rawSubscriptions);
}
handleMessage(client, message) {
//
// error
// {
// "jsonrpc": "2.0",
// "id": 1,
// "error": {
// "message": "Invalid params",
// "data": {
// "reason": "invalid format",
// "param": "nonce"
// },
// "code": -32602
// },
// "usIn": "1655391709417993",
// "usOut": "1655391709418049",
// "usDiff": 56,
// "testnet": false
// }
//
// subscribe
// {
// "jsonrpc": "2.0",
// "id": 2,
// "result": ["ticker.BTC_USDC-PERPETUAL.raw"],
// "usIn": "1655393625889396",
// "usOut": "1655393625889518",
// "usDiff": 122,
// "testnet": false
// }
//
// notification
// {
// "jsonrpc": "2.0",
// "method": "subscription",
// "params": {
// "channel": "ticker.BTC_USDC-PERPETUAL.raw",
// "data": {
// "timestamp": 1655393724752,
// "stats": [Object],
// "state": "open",
// "settlement_price": 21729.5891,
// "open_interest": 164.501,
// "min_price": 20792.9001,
// "max_price": 21426.1864,
// "mark_price": 21109.4757,
// "last_price": 21132,
// "instrument_name": "BTC_USDC-PERPETUAL",
// "index_price": 21122.3937,
// "funding_8h": -0.00022427,
// "estimated_delivery_price": 21122.3937,
// "current_funding": -0.00011158,
// "best_bid_price": 21106,
// "best_bid_amount": 1.143,
// "best_ask_price": 21113,
// "best_ask_amount": 0.402
// }
// }
// }
//
const error = this.safeValue(message, 'error');
if (error !== undefined) {
throw new ExchangeError(this.id + ' ' + this.json(error));
}
const params = this.safeValue(message, 'params');
const channel = this.safeString(params, 'channel');
if (channel !== undefined) {
const parts = channel.split('.');
const channelId = this.safeString(parts, 0);
const userHandlers = {
'trades': this.handleMyTrades,
'portfolio': this.handleBalance,
'orders': this.handleOrders,
};
const handlers = {
'ticker': this.handleTicker,
'quote': this.handleBidAsk,
'book': this.handleOrderBook,
'trades': this.handleTrades,
'chart': this.handleOHLCV,
'user': this.safeValue(userHandlers, this.safeString(parts, 1)),
};
const handler = this.safeValue(handlers, channelId);
if (handler !== undefined) {
handler.call(this, client, message);
return;
}
throw new NotSupported(this.id + ' no handler found for this message ' + this.json(message));
}
const result = this.safeValue(message, 'result', {});
const accessToken = this.safeString(result, 'access_token');
if (accessToken !== undefined) {
this.handleAuthenticationMessage(client, message);
}
}
handleAuthenticationMessage(client, message) {
//
// {
// "jsonrpc": "2.0",
// "id": 1,
// "result": {
// "token_type": "bearer",
// "scope": "account:read_write block_trade:read_write connection custody:read_write mainaccount name:ccxt trade:read_write wallet:read_write",
// "refresh_token": "1686927372328.1EzFBRmt.logRQWXkPA1oE_Tk0gRsls9Hau7YN6a321XUBnxvR4x6cryhbkKcniUJU-czA8_zKXrqQGpQmfoDwhLIjIsWCvRuu6otbg-LKWlrtTX1GQqLcPaTTHAdZGTMV-HM8HiS03QBd9MIXWRfF53sKj2hdR9nZPZ6MH1XrkpAZPB_peuEEB9wlcc3elzWEZFtCmiy1fnQ8TPHwAJMt3nuUmEcMLt_-F554qrsg_-I66D9xMiifJj4dBemdPfV_PkGPRIwIoKlxDjyv2-xfCw-4eKyo6Hu1m2h6gT1DPOTxSXcBgfBQjpi-_uY3iAIj7U6xjC46PHthEdquhEuCTZl7UfCRZSAWwZA",
// "expires_in": 31536000,
// "access_token": "1686923272328.1CkwEx-u.qHradpIulmuoeboKMEi8PkQ1_4DF8yFE2zywBTtkD32sruVC53b1HwL5OWRuh2nYAndXff4xuXIMRkkEfMAFCeq24prihxxinoS8DDVkKBxedGx4CUPJFeXjmh7wuRGqQOLg1plXOpbF3fwF2KPEkAuETwcpcVY6K9HUVjutNRfxFe2TR7CvuS9x8TATvoPeu7H1ezYl-LkKSaRifdTXuwituXgp4oDbPRyQLniEBWuYF9rY7qbABxuOJlXI1VZ63u7Bh0mGWei-KeVeqHGNpy6OgrFRPXPxa9_U7vaxCyHW3zZ9959TQ1QUMLWtUX-NLBEv3BT5eCieW9HORYIOKfsgkpd3"
// },
// "usIn": "1655391872327712",
// "usOut": "1655391872328515",
// "usDiff": 803,
// "testnet": false
// }
//
const messageHash = 'authenticated';
client.resolve(message, messageHash);
return message;
}
async authenticate(params = {}) {
const url = this.urls['api']['ws'];
const client = this.client(url);
const time = this.milliseconds();
const timeString = this.numberToString(time);
const nonce = timeString;
const messageHash = 'authenticated';
let future = this.safeValue(client.subscriptions, messageHash);
if (future === undefined) {
this.checkRequiredCredentials();
const requestId = this.requestId();
const signature = this.hmac(this.encode(timeString + '\n' + nonce + '\n'), this.encode(this.secret), sha256);
const request = {
'jsonrpc': '2.0',
'id': requestId,
'method': 'public/auth',
'params': {
'grant_type': 'client_signature',
'client_id': this.apiKey,
'timestamp': time,
'signature': signature,
'nonce': nonce,
'data': '',
},
};
future = await this.watch(url, messageHash, this.extend(request, params), messageHash);
client.subscriptions[messageHash] = future;
}
return future;
}
}