UNPKG

preidman-ccxt

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 100+ exchanges

504 lines (479 loc) 24.2 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { AuthenticationError, ExchangeError, PermissionDenied, InvalidOrder, OrderNotFound, DDoSProtection, NotSupported, ExchangeNotAvailable, InsufficientFunds } = require ('./base/errors'); // --------------------------------------------------------------------------- module.exports = class deribit extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'deribit', 'name': 'Deribit', 'countries': [ 'NL' ], // Netherlands 'version': 'v1', 'userAgent': undefined, 'rateLimit': 2000, 'has': { 'CORS': true, 'editOrder': true, 'fetchOrder': true, 'fetchOrders': false, 'fetchOpenOrders': true, 'fetchClosedOrders': true, 'fetchMyTrades': true, 'fetchTickers': false, }, 'timeframes': {}, 'urls': { 'test': 'https://test.deribit.com', 'logo': 'https://user-images.githubusercontent.com/1294454/41933112-9e2dd65a-798b-11e8-8440-5bab2959fcb8.jpg', 'api': 'https://www.deribit.com', 'www': 'https://www.deribit.com', 'doc': [ 'https://www.deribit.com/pages/docs/api', 'https://github.com/deribit', ], 'fees': 'https://www.deribit.com/pages/information/fees', 'referral': 'https://www.deribit.com/reg-1189.4038', }, 'api': { 'public': { 'get': [ 'test', 'getinstruments', 'index', 'getcurrencies', 'getorderbook', 'getlasttrades', 'getsummary', 'stats', 'getannouncments', ], }, 'private': { 'get': [ 'account', 'getopenorders', 'positions', 'orderhistory', 'orderstate', 'tradehistory', 'newannouncements', ], 'post': [ 'buy', 'sell', 'edit', 'cancel', 'cancelall', ], }, }, 'exceptions': { // 0 or absent Success, No error '9999': PermissionDenied, // "api_not_enabled" User didn't enable API for the Account '10000': AuthenticationError, // "authorization_required" Authorization issue, invalid or absent signature etc '10001': ExchangeError, // "error" Some general failure, no public information available '10002': InvalidOrder, // "qty_too_low" Order quantity is too low '10003': InvalidOrder, // "order_overlap" Rejection, order overlap is found and self-trading is not enabled '10004': OrderNotFound, // "order_not_found" Attempt to operate with order that can't be found by specified id '10005': InvalidOrder, // "price_too_low <Limit>" Price is too low, <Limit> defines current limit for the operation '10006': InvalidOrder, // "price_too_low4idx <Limit>" Price is too low for current index, <Limit> defines current bottom limit for the operation '10007': InvalidOrder, // "price_too_high <Limit>" Price is too high, <Limit> defines current up limit for the operation '10008': InvalidOrder, // "price_too_high4idx <Limit>" Price is too high for current index, <Limit> defines current up limit for the operation '10009': InsufficientFunds, // "not_enough_funds" Account has not enough funds for the operation '10010': OrderNotFound, // "already_closed" Attempt of doing something with closed order '10011': InvalidOrder, // "price_not_allowed" This price is not allowed for some reason '10012': InvalidOrder, // "book_closed" Operation for instrument which order book had been closed '10013': PermissionDenied, // "pme_max_total_open_orders <Limit>" Total limit of open orders has been exceeded, it is applicable for PME users '10014': PermissionDenied, // "pme_max_future_open_orders <Limit>" Limit of count of futures' open orders has been exceeded, it is applicable for PME users '10015': PermissionDenied, // "pme_max_option_open_orders <Limit>" Limit of count of options' open orders has been exceeded, it is applicable for PME users '10016': PermissionDenied, // "pme_max_future_open_orders_size <Limit>" Limit of size for futures has been exceeded, it is applicable for PME users '10017': PermissionDenied, // "pme_max_option_open_orders_size <Limit>" Limit of size for options has been exceeded, it is applicable for PME users '10019': PermissionDenied, // "locked_by_admin" Trading is temporary locked by admin '10020': ExchangeError, // "invalid_or_unsupported_instrument" Instrument name is not valid '10022': InvalidOrder, // "invalid_quantity" quantity was not recognized as a valid number '10023': InvalidOrder, // "invalid_price" price was not recognized as a valid number '10024': InvalidOrder, // "invalid_max_show" max_show parameter was not recognized as a valid number '10025': InvalidOrder, // "invalid_order_id" Order id is missing or its format was not recognized as valid '10026': InvalidOrder, // "price_precision_exceeded" Extra precision of the price is not supported '10027': InvalidOrder, // "non_integer_contract_amount" Futures contract amount was not recognized as integer '10028': DDoSProtection, // "too_many_requests" Allowed request rate has been exceeded '10029': OrderNotFound, // "not_owner_of_order" Attempt to operate with not own order '10030': ExchangeError, // "must_be_websocket_request" REST request where Websocket is expected '10031': ExchangeError, // "invalid_args_for_instrument" Some of arguments are not recognized as valid '10032': InvalidOrder, // "whole_cost_too_low" Total cost is too low '10033': NotSupported, // "not_implemented" Method is not implemented yet '10034': InvalidOrder, // "stop_price_too_high" Stop price is too high '10035': InvalidOrder, // "stop_price_too_low" Stop price is too low '11035': InvalidOrder, // "no_more_stops <Limit>" Allowed amount of stop orders has been exceeded '11036': InvalidOrder, // "invalid_stoppx_for_index_or_last" Invalid StopPx (too high or too low) as to current index or market '11037': InvalidOrder, // "outdated_instrument_for_IV_order" Instrument already not available for trading '11038': InvalidOrder, // "no_adv_for_futures" Advanced orders are not available for futures '11039': InvalidOrder, // "no_adv_postonly" Advanced post-only orders are not supported yet '11040': InvalidOrder, // "impv_not_in_range 0..499%" Implied volatility is out of allowed range '11041': InvalidOrder, // "not_adv_order" Advanced order properties can't be set if the order is not advanced '11042': PermissionDenied, // "permission_denied" Permission for the operation has been denied '11044': OrderNotFound, // "not_open_order" Attempt to do open order operations with the not open order '11045': ExchangeError, // "invalid_event" Event name has not been recognized '11046': ExchangeError, // "outdated_instrument" At several minutes to instrument expiration, corresponding advanced implied volatility orders are not allowed '11047': ExchangeError, // "unsupported_arg_combination" The specified combination of arguments is not supported '11048': ExchangeError, // "not_on_this_server" The requested operation is not available on this server. '11050': ExchangeError, // "invalid_request" Request has not been parsed properly '11051': ExchangeNotAvailable, // "system_maintenance" System is under maintenance '11030': ExchangeError, // "other_reject <Reason>" Some rejects which are not considered as very often, more info may be specified in <Reason> '11031': ExchangeError, // "other_error <Error>" Some errors which are not considered as very often, more info may be specified in <Error> }, 'options': { 'fetchTickerQuotes': true, }, }); } async fetchMarkets (params = {}) { let marketsResponse = await this.publicGetGetinstruments (); let markets = marketsResponse['result']; let result = []; for (let p = 0; p < markets.length; p++) { let market = markets[p]; let id = market['instrumentName']; let base = market['baseCurrency']; let quote = market['currency']; base = this.commonCurrencyCode (base); quote = this.commonCurrencyCode (quote); result.push ({ 'id': id, 'symbol': id, 'base': base, 'quote': quote, 'active': market['isActive'], 'precision': { 'amount': market['minTradeSize'], 'price': market['tickSize'], }, 'limits': { 'amount': { 'min': market['minTradeSize'], }, 'price': { 'min': market['tickSize'], }, }, 'type': market['kind'], 'spot': false, 'future': market['kind'] === 'future', 'option': market['kind'] === 'option', 'info': market, }); } return result; } async fetchBalance (params = {}) { let account = await this.privateGetAccount (); let result = { 'BTC': { 'free': account['result']['availableFunds'], 'used': account['result']['maintenanceMargin'], 'total': account['result']['equity'], }, }; return this.parseBalance (result); } async fetchDepositAddress (currency, params = {}) { let account = await this.privateGetAccount (); return { 'currency': 'BTC', 'address': account['depositAddress'], 'tag': undefined, 'info': account, }; } parseTicker (ticker, market = undefined) { let timestamp = this.safeInteger (ticker, 'created'); let iso8601 = (timestamp === undefined) ? undefined : this.iso8601 (timestamp); let symbol = this.findSymbol (this.safeString (ticker, 'instrumentName'), market); let last = this.safeFloat (ticker, 'last'); return { 'symbol': symbol, 'timestamp': timestamp, 'datetime': iso8601, 'high': this.safeFloat (ticker, 'high'), 'low': this.safeFloat (ticker, 'low'), 'bid': this.safeFloat (ticker, 'bidPrice'), 'bidVolume': undefined, 'ask': this.safeFloat (ticker, 'askPrice'), 'askVolume': undefined, 'vwap': undefined, 'open': undefined, 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': undefined, 'quoteVolume': this.safeFloat (ticker, 'volume'), 'info': ticker, }; } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let response = await this.publicGetGetsummary (this.extend ({ 'instrument': market['id'], }, params)); return this.parseTicker (response['result'], market); } parseTrade (trade, market = undefined) { let id = this.safeString (trade, 'tradeId'); let symbol = undefined; if (market !== undefined) symbol = market['symbol']; let timestamp = this.safeInteger (trade, 'timeStamp'); return { 'info': trade, 'id': id, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'order': undefined, 'type': undefined, 'side': trade['direction'], 'price': this.safeFloat (trade, 'price'), 'amount': this.safeFloat (trade, 'quantity'), }; } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let request = { 'instrument': market['id'], }; if (limit !== undefined) { request['limit'] = limit; } else { request['limit'] = 10000; } let response = await this.publicGetGetlasttrades (this.extend (request, params)); return this.parseTrades (response['result'], market, since, limit); } async fetchOrderBook (symbol, limit = undefined, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let response = await this.publicGetGetorderbook ({ 'instrument': market['id'] }); let timestamp = parseInt (response['usOut'] / 1000); let orderbook = this.parseOrderBook (response['result'], timestamp, 'bids', 'asks', 'price', 'quantity'); return this.extend (orderbook, { 'nonce': this.safeInteger (response, 'tstamp'), }); } parseOrderStatus (status) { const statuses = { 'open': 'open', 'cancelled': 'canceled', 'filled': 'closed', }; if (status in statuses) { return statuses[status]; } return status; } parseOrder (order, market = undefined) { // // { // "orderId": 5258039, // ID of the order // "type": "limit", // not documented, but present in the actual response // "instrument": "BTC-26MAY17", // instrument name (market id) // "direction": "sell", // order direction, "buy" or "sell" // "price": 1860, // float, USD for futures, BTC for options // "label": "", // label set by the owner, up to 32 chars // "quantity": 10, // quantity, in contracts ($10 per contract for futures, ฿1 — for options) // "filledQuantity": 3, // filled quantity, in contracts ($10 per contract for futures, ฿1 — for options) // "avgPrice": 1860, // average fill price of the order // "commission": -0.000001613, // in BTC units // "created": 1494491899308, // creation timestamp // "state": "open", // open, cancelled, etc // "postOnly": false // true for post-only orders only // open orders -------------------------------------------------------- // "lastUpdate": 1494491988754, // timestamp of the last order state change (before this cancelorder of course) // closed orders ------------------------------------------------------ // "tstamp": 1494492913288, // timestamp of the last order state change, documented, but may be missing in the actual response // "modified": 1494492913289, // timestamp of the last db write operation, e.g. trade that doesn't change order status, documented, but may missing in the actual response // "adv": false // advanced type (false, or "usd" or "implv") // "trades": [], // not documented, injected from the outside of the parseOrder method into the order // } // let timestamp = this.safeInteger (order, 'created'); let lastUpdate = this.safeInteger (order, 'lastUpdate'); let lastTradeTimestamp = this.safeInteger2 (order, 'tstamp', 'modified'); let id = this.safeString (order, 'orderId'); let price = this.safeFloat (order, 'price'); let average = this.safeFloat (order, 'avgPrice'); let amount = this.safeFloat (order, 'quantity'); let filled = this.safeFloat (order, 'filledQuantity'); if (lastTradeTimestamp === undefined) { if (filled !== undefined) { if (filled > 0) { lastTradeTimestamp = lastUpdate; } } } let remaining = undefined; let cost = undefined; if (filled !== undefined) { if (amount !== undefined) { remaining = amount - filled; } if (price !== undefined) { cost = price * filled; } } let status = this.parseOrderStatus (this.safeString (order, 'state')); let side = this.safeString (order, 'direction'); if (side !== undefined) { side = side.toLowerCase (); } let feeCost = this.safeFloat (order, 'commission'); if (feeCost !== undefined) { feeCost = Math.abs (feeCost); } let fee = { 'cost': feeCost, 'currency': 'BTC', }; let type = this.safeString (order, 'type'); return { 'info': order, 'id': id, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTradeTimestamp': lastTradeTimestamp, 'symbol': order['instrument'], 'type': type, 'side': side, 'price': price, 'amount': amount, 'cost': cost, 'average': average, 'filled': filled, 'remaining': remaining, 'status': status, 'fee': fee, 'trades': undefined, // todo: parse trades }; } async fetchOrder (id, symbol = undefined, params = {}) { await this.loadMarkets (); let response = await this.privateGetOrderstate ({ 'orderId': id }); return this.parseOrder (response['result']); } async createOrder (symbol, type, side, amount, price = undefined, params = {}) { await this.loadMarkets (); let request = { 'instrument': this.marketId (symbol), 'quantity': amount, 'type': type, }; if (price !== undefined) request['price'] = price; let method = 'privatePost' + this.capitalize (side); let response = await this[method] (this.extend (request, params)); let order = this.safeValue (response['result'], 'order'); if (order === undefined) { return response; } return this.parseOrder (order); } async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { await this.loadMarkets (); let request = { 'orderId': id, }; if (amount !== undefined) request['quantity'] = amount; if (price !== undefined) request['price'] = price; let response = await this.privatePostEdit (this.extend (request, params)); return this.parseOrder (response['result']); } async cancelOrder (id, symbol = undefined, params = {}) { await this.loadMarkets (); let response = await this.privatePostCancel (this.extend ({ 'orderId': id }, params)); return this.parseOrder (response['result']); } async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let request = { 'instrument': market['id'], }; let response = await this.privateGetGetopenorders (this.extend (request, params)); return this.parseOrders (response['result'], market, since, limit); } async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let request = { 'instrument': market['id'], }; let response = await this.privateGetOrderhistory (this.extend (request, params)); return this.parseOrders (response['result'], market, since, limit); } async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let request = { 'instrument': market['id'], }; if (limit !== undefined) { request['count'] = limit; // default = 20 } let response = await this.privateGetTradehistory (this.extend (request, params)); return this.parseTrades (response['result'], market, since, limit); } nonce () { return this.milliseconds (); } sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { let query = '/' + 'api/' + this.version + '/' + api + '/' + path; let url = this.urls['api'] + query; if (api === 'public') { if (Object.keys (params).length) { url += '?' + this.urlencode (params); } } else { this.checkRequiredCredentials (); let nonce = this.nonce ().toString (); let auth = '_=' + nonce + '&_ackey=' + this.apiKey + '&_acsec=' + this.secret + '&_action=' + query; if (method === 'POST') { params = this.keysort (params); auth += '&' + this.urlencode (params); } let hash = this.hash (this.encode (auth), 'sha256', 'base64'); let signature = this.apiKey + '.' + nonce + '.' + this.decode (hash); headers = { 'x-deribit-sig': signature, }; if (method !== 'GET') { headers['Content-Type'] = 'application/x-www-form-urlencoded'; body = this.urlencode (params); } } return { 'url': url, 'method': method, 'body': body, 'headers': headers }; } handleErrors (httpCode, reason, url, method, headers, body, response) { if (!response) { return; // fallback to default error handler } // // {"usOut":1535877098645376,"usIn":1535877098643364,"usDiff":2012,"testnet":false,"success":false,"message":"order_not_found","error":10004} // const error = this.safeString (response, 'error'); if ((error !== undefined) && (error !== '0')) { const feedback = this.id + ' ' + body; const exceptions = this.exceptions; if (error in exceptions) { throw new exceptions[error] (feedback); } throw new ExchangeError (feedback); // unknown message } } };