UNPKG

@jmparsons/ccxt

Version:

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

664 lines (634 loc) 25.2 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { ExchangeError, AuthenticationError, DDoSProtection, ExchangeNotAvailable, InvalidOrder, OrderNotFound, PermissionDenied, InsufficientFunds } = require ('./base/errors'); // --------------------------------------------------------------------------- module.exports = class bibox extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'bibox', 'name': 'Bibox', 'countries': [ 'CN', 'US', 'KR' ], 'version': 'v1', 'has': { 'CORS': false, 'publicAPI': false, 'fetchBalance': true, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchFundingFees': true, 'fetchTickers': true, 'fetchOpenOrders': true, 'fetchClosedOrders': true, 'fetchMyTrades': true, 'fetchOHLCV': true, 'createMarketOrder': false, // or they will return https://github.com/ccxt/ccxt/issues/2338 'withdraw': true, }, 'timeframes': { '1m': '1min', '5m': '5min', '15m': '15min', '30m': '30min', '1h': '1hour', '8h': '12hour', '1d': 'day', '1w': 'week', }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/34902611-2be8bf1a-f830-11e7-91a2-11b2f292e750.jpg', 'api': 'https://api.bibox.com', 'www': 'https://www.bibox.com', 'doc': [ 'https://github.com/Biboxcom/api_reference/wiki/home_en', 'https://github.com/Biboxcom/api_reference/wiki/api_reference', ], 'fees': 'https://bibox.zendesk.com/hc/en-us/articles/115004417013-Fee-Structure-on-Bibox', }, 'api': { 'public': { 'post': [ // TODO: rework for full endpoint/cmd paths here 'mdata', ], 'get': [ 'mdata', ], }, 'private': { 'post': [ 'user', 'orderpending', 'transfer', ], }, }, 'fees': { 'trading': { 'tierBased': false, 'percentage': true, 'taker': 0.001, 'maker': 0.0, }, 'funding': { 'tierBased': false, 'percentage': false, 'withdraw': {}, 'deposit': {}, }, }, 'exceptions': { '2021': InsufficientFunds, // Insufficient balance available for withdrawal '2015': AuthenticationError, // Google authenticator is wrong '2033': OrderNotFound, // operation failed! Orders have been completed or revoked '2067': InvalidOrder, // Does not support market orders '2068': InvalidOrder, // The number of orders can not be less than '3012': AuthenticationError, // invalid apiKey '3024': PermissionDenied, // wrong apikey permissions '3025': AuthenticationError, // signature failed '4000': ExchangeNotAvailable, // current network is unstable '4003': DDoSProtection, // server busy please try again later }, 'commonCurrencies': { 'KEY': 'Bihu', }, }); } async fetchMarkets (params = {}) { let response = await this.publicGetMdata (this.extend ({ 'cmd': 'marketAll', }, params)); let markets = response['result']; let result = []; for (let i = 0; i < markets.length; i++) { let market = markets[i]; let baseId = market['coin_symbol']; let quoteId = market['currency_symbol']; let base = this.commonCurrencyCode (baseId); let quote = this.commonCurrencyCode (quoteId); let symbol = base + '/' + quote; let id = base + '_' + quote; let precision = { 'amount': 8, 'price': 8, }; result.push ({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': base, 'quoteId': quote, 'active': true, 'info': market, 'lot': Math.pow (10, -precision['amount']), 'precision': precision, 'limits': { 'amount': { 'min': Math.pow (10, -precision['amount']), 'max': undefined, }, 'price': { 'min': undefined, 'max': undefined, }, }, }); } return result; } parseTicker (ticker, market = undefined) { // we don't set values that are not defined by the exchange let timestamp = this.safeInteger (ticker, 'timestamp'); let symbol = undefined; if (market) { symbol = market['symbol']; } else { let base = ticker['coin_symbol']; let quote = ticker['currency_symbol']; symbol = this.commonCurrencyCode (base) + '/' + this.commonCurrencyCode (quote); } let last = this.safeFloat (ticker, 'last'); let change = this.safeFloat (ticker, 'change'); let baseVolume = undefined; if ('vol' in ticker) { baseVolume = this.safeFloat (ticker, 'vol'); } else { baseVolume = this.safeFloat (ticker, 'vol24H'); } let open = undefined; if ((typeof last !== 'undefined') && (typeof change !== 'undefined')) open = last - change; let iso8601 = undefined; if (typeof timestamp !== 'undefined') iso8601 = this.iso8601 (timestamp); return { 'symbol': symbol, 'timestamp': timestamp, 'datetime': iso8601, 'high': this.safeFloat (ticker, 'high'), 'low': this.safeFloat (ticker, 'low'), 'bid': this.safeFloat (ticker, 'buy'), 'bidVolume': undefined, 'ask': this.safeFloat (ticker, 'sell'), 'askVolume': undefined, 'vwap': undefined, 'open': open, 'close': last, 'last': last, 'previousClose': undefined, 'change': change, 'percentage': this.safeString (ticker, 'percent'), 'average': undefined, 'baseVolume': baseVolume, 'quoteVolume': this.safeFloat (ticker, 'amount'), 'info': ticker, }; } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let response = await this.publicGetMdata (this.extend ({ 'cmd': 'ticker', 'pair': market['id'], }, params)); return this.parseTicker (response['result'], market); } parseTickers (rawTickers, symbols = undefined) { let tickers = []; for (let i = 0; i < rawTickers.length; i++) { tickers.push (this.parseTicker (rawTickers[i])); } return this.filterByArray (tickers, 'symbol', symbols); } async fetchTickers (symbols = undefined, params = {}) { let response = await this.publicGetMdata (this.extend ({ 'cmd': 'marketAll', }, params)); return this.parseTickers (response['result'], symbols); } parseTrade (trade, market = undefined) { let timestamp = trade['time']; let side = this.safeInteger (trade, 'side'); side = this.safeInteger (trade, 'order_side', side); side = (side === 1) ? 'buy' : 'sell'; if (typeof market === 'undefined') { let marketId = this.safeString (trade, 'pair'); if (typeof marketId !== 'undefined') if (marketId in this.markets_by_id) market = this.markets_by_id[marketId]; } let symbol = (typeof market !== 'undefined') ? market['symbol'] : undefined; let fee = undefined; if ('fee' in trade) { fee = { 'cost': this.safeFloat (trade, 'fee'), 'currency': undefined, }; } return { 'id': this.safeString (trade, 'id'), 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'type': 'limit', 'side': side, 'price': this.safeFloat (trade, 'price'), 'amount': this.safeFloat (trade, 'amount'), 'fee': fee, }; } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let size = (limit) ? limit : 200; let response = await this.publicGetMdata (this.extend ({ 'cmd': 'deals', 'pair': market['id'], 'size': size, }, params)); return this.parseTrades (response['result'], market, since, limit); } async fetchOrderBook (symbol, limit = 200, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let request = { 'cmd': 'depth', 'pair': market['id'], }; request['size'] = limit; // default = 200 ? let response = await this.publicGetMdata (this.extend (request, params)); return this.parseOrderBook (response['result'], this.safeFloat (response['result'], 'update_time'), 'bids', 'asks', 'price', 'volume'); } parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { return [ ohlcv['time'], ohlcv['open'], ohlcv['high'], ohlcv['low'], ohlcv['close'], ohlcv['vol'], ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = 1000, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let response = await this.publicGetMdata (this.extend ({ 'cmd': 'kline', 'pair': market['id'], 'period': this.timeframes[timeframe], 'size': limit, }, params)); return this.parseOHLCVs (response['result'], market, timeframe, since, limit); } async fetchCurrencies (params = {}) { let response = await this.privatePostTransfer ({ 'cmd': 'transfer/coinList', 'body': {}, }); let currencies = response['result']; let result = {}; for (let i = 0; i < currencies.length; i++) { let currency = currencies[i]; let id = currency['symbol']; let code = this.commonCurrencyCode (id); let precision = 8; let deposit = currency['enable_deposit']; let withdraw = currency['enable_withdraw']; let active = (deposit && withdraw) ? true : false; result[code] = { 'id': id, 'code': code, 'info': currency, 'name': currency['name'], 'active': active, 'status': 'ok', 'fee': undefined, 'precision': precision, 'limits': { 'amount': { 'min': Math.pow (10, -precision), 'max': Math.pow (10, precision), }, 'price': { 'min': Math.pow (10, -precision), 'max': Math.pow (10, precision), }, 'cost': { 'min': undefined, 'max': undefined, }, 'withdraw': { 'min': undefined, 'max': Math.pow (10, precision), }, }, }; } return result; } async fetchBalance (params = {}) { await this.loadMarkets (); let response = await this.privatePostTransfer ({ 'cmd': 'transfer/assets', 'body': this.extend ({ 'select': 1, }, params), }); let balances = response['result']; let result = { 'info': balances }; let indexed = undefined; if ('assets_list' in balances) { indexed = this.indexBy (balances['assets_list'], 'coin_symbol'); } else { indexed = balances; } let keys = Object.keys (indexed); for (let i = 0; i < keys.length; i++) { let id = keys[i]; let code = id.toUpperCase (); if (code.indexOf ('TOTAL_') >= 0) { code = code.slice (6); } if (code in this.currencies_by_id) { code = this.currencies_by_id[code]['code']; } let account = this.account (); let balance = indexed[id]; if (typeof balance === 'string') { balance = parseFloat (balance); account['free'] = balance; account['used'] = 0.0; account['total'] = balance; } else { account['free'] = parseFloat (balance['balance']); account['used'] = parseFloat (balance['freeze']); account['total'] = this.sum (account['free'], account['used']); } result[code] = account; } return this.parseBalance (result); } async createOrder (symbol, type, side, amount, price = undefined, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let orderType = (type === 'limit') ? 2 : 1; let orderSide = (side === 'buy') ? 1 : 2; let response = await this.privatePostOrderpending ({ 'cmd': 'orderpending/trade', 'body': this.extend ({ 'pair': market['id'], 'account_type': 0, 'order_type': orderType, 'order_side': orderSide, 'pay_bix': 0, 'amount': amount, 'price': price, }, params), }); return { 'info': response, 'id': this.safeString (response, 'result'), }; } async cancelOrder (id, symbol = undefined, params = {}) { let response = await this.privatePostOrderpending ({ 'cmd': 'orderpending/cancelTrade', 'body': this.extend ({ 'orders_id': id, }, params), }); return response; } parseOrder (order, market = undefined) { let symbol = undefined; if (market) { symbol = market['symbol']; } else { symbol = order['coin_symbol'] + '/' + order['currency_symbol']; } let type = (order['order_type'] === 1) ? 'market' : 'limit'; let timestamp = order['createdAt']; let price = this.safeFloat (order, 'price'); let filled = this.safeFloat (order, 'deal_amount'); let amount = this.safeFloat (order, 'amount'); let cost = this.safeFloat (order, 'money'); let remaining = undefined; if (typeof filled !== 'undefined') { if (typeof amount !== 'undefined') remaining = amount - filled; if (typeof cost === 'undefined') cost = price * filled; } let side = (order['order_side'] === 1) ? 'buy' : 'sell'; let status = this.safeString (order, 'status'); if (typeof status !== 'undefined') status = this.parseOrderStatus (status); let result = { 'info': order, 'id': this.safeString (order, 'id'), 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTradeTimestamp': undefined, 'symbol': symbol, 'type': type, 'side': side, 'price': price, 'amount': amount, 'cost': cost ? cost : parseFloat (price) * filled, 'filled': filled, 'remaining': remaining, 'status': status, 'fee': this.safeFloat (order, 'fee'), }; return result; } parseOrderStatus (status) { let statuses = { // original comments from bibox: '1': 'pending', // pending '2': 'open', // part completed '3': 'closed', // completed '4': 'canceled', // part canceled '5': 'canceled', // canceled '6': 'canceled', // canceling }; return this.safeString (statuses, status, status.toLowerCase ()); } async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { let market = undefined; let pair = undefined; if (typeof symbol !== 'undefined') { await this.loadMarkets (); market = this.market (symbol); pair = market['id']; } let size = (limit) ? limit : 200; let response = await this.privatePostOrderpending ({ 'cmd': 'orderpending/orderPendingList', 'body': this.extend ({ 'pair': pair, 'account_type': 0, // 0 - regular, 1 - margin 'page': 1, 'size': size, }, params), }); let orders = this.safeValue (response['result'], 'items', []); return this.parseOrders (orders, market, since, limit); } async fetchClosedOrders (symbol = undefined, since = undefined, limit = 200, params = {}) { if (typeof symbol === 'undefined') throw new ExchangeError (this.id + ' fetchClosedOrders requires a symbol argument'); await this.loadMarkets (); let market = this.market (symbol); let response = await this.privatePostOrderpending ({ 'cmd': 'orderpending/pendingHistoryList', 'body': this.extend ({ 'pair': market['id'], 'account_type': 0, // 0 - regular, 1 - margin 'page': 1, 'size': limit, }, params), }); let orders = this.safeValue (response['result'], 'items', []); return this.parseOrders (orders, market, since, limit); } async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { if (!symbol) throw new ExchangeError (this.id + ' fetchMyTrades requires a symbol argument'); await this.loadMarkets (); let market = this.market (symbol); let size = (limit) ? limit : 200; let response = await this.privatePostOrderpending ({ 'cmd': 'orderpending/orderHistoryList', 'body': this.extend ({ 'pair': market['id'], 'account_type': 0, // 0 - regular, 1 - margin 'page': 1, 'size': size, }, params), }); let trades = this.safeValue (response['result'], 'items', []); return this.parseOrders (trades, market, since, limit); } async fetchDepositAddress (code, params = {}) { await this.loadMarkets (); let currency = this.currency (code); let response = await this.privatePostTransfer ({ 'cmd': 'transfer/transferIn', 'body': this.extend ({ 'coin_symbol': currency['id'], }, params), }); let address = this.safeString (response, 'result'); let result = { 'info': response, 'address': address, }; return result; } async withdraw (code, amount, address, tag = undefined, params = {}) { this.checkAddress (address); await this.loadMarkets (); let currency = this.currency (code); if (typeof this.password === 'undefined') if (!('trade_pwd' in params)) throw new ExchangeError (this.id + ' withdraw() requires this.password set on the exchange instance or a trade_pwd parameter'); if (!('totp_code' in params)) throw new ExchangeError (this.id + ' withdraw() requires a totp_code parameter for 2FA authentication'); let body = { 'trade_pwd': this.password, 'coin_symbol': currency['id'], 'amount': amount, 'addr': address, }; if (typeof tag !== 'undefined') body['address_remark'] = tag; let response = await this.privatePostTransfer ({ 'cmd': 'transfer/transferOut', 'body': this.extend (body, params), }); return { 'info': response, 'id': undefined, }; } async fetchFundingFees (codes = undefined, params = {}) { // by default it will try load withdrawal fees of all currencies (with separate requests) // however if you define codes = [ 'ETH', 'BTC' ] in args it will only load those await this.loadMarkets (); let withdrawFees = {}; let info = {}; if (typeof codes === 'undefined') codes = Object.keys (this.currencies); for (let i = 0; i < codes.length; i++) { let code = codes[i]; let currency = this.currency (code); let response = await this.privatePostTransfer ({ 'cmd': 'transfer/transferOutInfo', 'body': this.extend ({ 'coin_symbol': currency['id'], }, params), }); info[code] = response; withdrawFees[code] = response['result']['withdraw_fee']; } return { 'info': info, 'withdraw': withdrawFees, 'deposit': {}, }; } sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { let url = this.urls['api'] + '/' + this.version + '/' + path; let cmds = this.json ([ params ]); if (api === 'public') { if (method !== 'GET') body = { 'cmds': cmds }; else if (Object.keys (params).length) url += '?' + this.urlencode (params); } else { this.checkRequiredCredentials (); body = { 'cmds': cmds, 'apikey': this.apiKey, 'sign': this.hmac (this.encode (cmds), this.encode (this.secret), 'md5'), }; } if (typeof body !== 'undefined') body = this.json (body, { 'convertArraysToObjects': true }); headers = { 'Content-Type': 'application/json' }; return { 'url': url, 'method': method, 'body': body, 'headers': headers }; } handleErrors (code, reason, url, method, headers, body) { if (body.length > 0) { if (body[0] === '{') { let response = JSON.parse (body); if ('error' in response) { if ('code' in response['error']) { let code = this.safeString (response['error'], 'code'); let feedback = this.id + ' ' + body; const exceptions = this.exceptions; if (code in exceptions) { throw new exceptions[code] (feedback); } else { throw new ExchangeError (feedback); } } throw new ExchangeError (this.id + ': "error" in response: ' + body); } if (!('result' in response)) throw new ExchangeError (this.id + ' ' + body); } } } async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { let response = await this.fetch2 (path, api, method, params, headers, body); if (method === 'GET') { return response; } else { return response['result'][0]; } } };