UNPKG

sfccxt

Version:

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

1,054 lines (1,032 loc) 91 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, InvalidNonce, AuthenticationError, RateLimitExceeded, PermissionDenied, BadRequest, BadSymbol, AccountSuspended, OrderImmediatelyFillable, OnMaintenance } = require ('./base/errors'); const { TRUNCATE, TICK_SIZE } = require ('./base/functions/number'); const Precise = require ('./base/Precise'); // --------------------------------------------------------------------------- module.exports = class bitrue extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'bitrue', 'name': 'Bitrue', 'countries': [ 'SG' ], // Singapore, Malta 'rateLimit': 1000, 'certified': false, 'version': 'v1', 'pro': true, // new metainfo interface 'has': { 'CORS': undefined, 'spot': true, 'margin': false, 'swap': undefined, // has but unimplemented 'future': undefined, 'option': false, 'cancelAllOrders': false, 'cancelOrder': true, 'createOrder': true, 'createStopLimitOrder': true, 'createStopMarketOrder': true, 'createStopOrder': true, 'fetchBalance': true, 'fetchBidsAsks': true, 'fetchBorrowRate': false, 'fetchBorrowRateHistories': false, 'fetchBorrowRateHistory': false, 'fetchBorrowRates': false, 'fetchBorrowRatesPerSymbol': false, 'fetchClosedOrders': true, 'fetchCurrencies': true, 'fetchDepositAddress': false, 'fetchDeposits': true, 'fetchMarginMode': false, 'fetchMarkets': true, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrders': false, 'fetchPositionMode': false, 'fetchStatus': true, 'fetchTicker': true, 'fetchTickers': true, 'fetchTime': true, 'fetchTrades': true, 'fetchTradingFee': false, 'fetchTradingFees': false, 'fetchTransactionFees': false, 'fetchTransactions': false, 'fetchTransfers': false, 'fetchWithdrawals': true, 'transfer': false, 'withdraw': true, }, 'timeframes': { '1m': '1m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1H', '2h': '2H', '4h': '4H', '1d': '1D', '1w': '1W', }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/139516488-243a830d-05dd-446b-91c6-c1f18fe30c63.jpg', 'api': { 'v1': 'https://www.bitrue.com/api/v1', 'v2': 'https://www.bitrue.com/api/v2', 'kline': 'https://www.bitrue.com/kline-api', }, 'www': 'https://www.bitrue.com', 'referral': 'https://www.bitrue.com/activity/task/task-landing?inviteCode=EZWETQE&cn=900000', 'doc': [ 'https://github.com/Bitrue-exchange/bitrue-official-api-docs', ], 'fees': 'https://bitrue.zendesk.com/hc/en-001/articles/4405479952537', }, 'api': { 'kline': { 'public': { 'get': { 'public.json': 1, 'public{currency}.json': 1, }, }, }, 'v1': { 'public': { 'get': { 'ping': 1, 'time': 1, 'exchangeInfo': 1, 'depth': { 'cost': 1, 'byLimit': [ [ 100, 1 ], [ 500, 5 ], [ 1000, 10 ] ] }, 'trades': 1, 'historicalTrades': 5, 'aggTrades': 1, 'ticker/24hr': { 'cost': 1, 'noSymbol': 40 }, 'ticker/price': { 'cost': 1, 'noSymbol': 2 }, 'ticker/bookTicker': { 'cost': 1, 'noSymbol': 2 }, 'market/kline': 1, }, }, 'private': { 'get': { 'order': 1, 'openOrders': 1, 'allOrders': 5, 'account': 5, 'myTrades': { 'cost': 5, 'noSymbol': 40 }, 'etf/net-value/{symbol}': 1, 'withdraw/history': 1, 'deposit/history': 1, }, 'post': { 'order': 4, 'withdraw/commit': 1, }, 'delete': { 'order': 1, }, }, }, 'v2': { 'private': { 'get': { 'myTrades': 5, }, }, }, }, 'fees': { 'trading': { 'feeSide': 'get', 'tierBased': false, 'percentage': true, 'taker': this.parseNumber ('0.00098'), 'maker': this.parseNumber ('0.00098'), }, 'future': { 'trading': { 'feeSide': 'quote', 'tierBased': true, 'percentage': true, 'taker': this.parseNumber ('0.000400'), 'maker': this.parseNumber ('0.000200'), 'tiers': { 'taker': [ [ this.parseNumber ('0'), this.parseNumber ('0.000400') ], [ this.parseNumber ('250'), this.parseNumber ('0.000400') ], [ this.parseNumber ('2500'), this.parseNumber ('0.000350') ], [ this.parseNumber ('7500'), this.parseNumber ('0.000320') ], [ this.parseNumber ('22500'), this.parseNumber ('0.000300') ], [ this.parseNumber ('50000'), this.parseNumber ('0.000270') ], [ this.parseNumber ('100000'), this.parseNumber ('0.000250') ], [ this.parseNumber ('200000'), this.parseNumber ('0.000220') ], [ this.parseNumber ('400000'), this.parseNumber ('0.000200') ], [ this.parseNumber ('750000'), this.parseNumber ('0.000170') ], ], 'maker': [ [ this.parseNumber ('0'), this.parseNumber ('0.000200') ], [ this.parseNumber ('250'), this.parseNumber ('0.000160') ], [ this.parseNumber ('2500'), this.parseNumber ('0.000140') ], [ this.parseNumber ('7500'), this.parseNumber ('0.000120') ], [ this.parseNumber ('22500'), this.parseNumber ('0.000100') ], [ this.parseNumber ('50000'), this.parseNumber ('0.000080') ], [ this.parseNumber ('100000'), this.parseNumber ('0.000060') ], [ this.parseNumber ('200000'), this.parseNumber ('0.000040') ], [ this.parseNumber ('400000'), this.parseNumber ('0.000020') ], [ this.parseNumber ('750000'), this.parseNumber ('0') ], ], }, }, }, 'delivery': { 'trading': { 'feeSide': 'base', 'tierBased': true, 'percentage': true, 'taker': this.parseNumber ('0.000500'), 'maker': this.parseNumber ('0.000100'), 'tiers': { 'taker': [ [ this.parseNumber ('0'), this.parseNumber ('0.000500') ], [ this.parseNumber ('250'), this.parseNumber ('0.000450') ], [ this.parseNumber ('2500'), this.parseNumber ('0.000400') ], [ this.parseNumber ('7500'), this.parseNumber ('0.000300') ], [ this.parseNumber ('22500'), this.parseNumber ('0.000250') ], [ this.parseNumber ('50000'), this.parseNumber ('0.000240') ], [ this.parseNumber ('100000'), this.parseNumber ('0.000240') ], [ this.parseNumber ('200000'), this.parseNumber ('0.000240') ], [ this.parseNumber ('400000'), this.parseNumber ('0.000240') ], [ this.parseNumber ('750000'), this.parseNumber ('0.000240') ], ], 'maker': [ [ this.parseNumber ('0'), this.parseNumber ('0.000100') ], [ this.parseNumber ('250'), this.parseNumber ('0.000080') ], [ this.parseNumber ('2500'), this.parseNumber ('0.000050') ], [ this.parseNumber ('7500'), this.parseNumber ('0.0000030') ], [ this.parseNumber ('22500'), this.parseNumber ('0') ], [ this.parseNumber ('50000'), this.parseNumber ('-0.000050') ], [ this.parseNumber ('100000'), this.parseNumber ('-0.000060') ], [ this.parseNumber ('200000'), this.parseNumber ('-0.000070') ], [ this.parseNumber ('400000'), this.parseNumber ('-0.000080') ], [ this.parseNumber ('750000'), this.parseNumber ('-0.000090') ], ], }, }, }, }, // exchange-specific options 'options': { // 'fetchTradesMethod': 'publicGetAggTrades', // publicGetTrades, publicGetHistoricalTrades 'fetchMyTradesMethod': 'v2PrivateGetMyTrades', // v1PrivateGetMyTrades 'hasAlreadyAuthenticatedSuccessfully': false, 'recvWindow': 5 * 1000, // 5 sec, binance default 'timeDifference': 0, // the difference between system clock and Binance clock 'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation 'parseOrderToPrecision': false, // force amounts and costs in parseOrder to precision 'newOrderRespType': { 'market': 'FULL', // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills 'limit': 'FULL', // we change it from 'ACK' by default to 'FULL' (returns immediately if limit is not hit) }, 'networks': { 'ERC20': 'ETH', 'TRC20': 'TRX', 'TRON': 'TRX', }, }, 'commonCurrencies': { 'MIM': 'MIM Swarm', }, 'precisionMode': TICK_SIZE, // https://binance-docs.github.io/apidocs/spot/en/#error-codes-2 'exceptions': { 'exact': { 'System is under maintenance.': OnMaintenance, // {"code":1,"msg":"System is under maintenance."} 'System abnormality': ExchangeError, // {"code":-1000,"msg":"System abnormality"} 'You are not authorized to execute this request.': PermissionDenied, // {"msg":"You are not authorized to execute this request."} 'API key does not exist': AuthenticationError, 'Order would trigger immediately.': OrderImmediatelyFillable, 'Stop price would trigger immediately.': OrderImmediatelyFillable, // {"code":-2010,"msg":"Stop price would trigger immediately."} 'Order would immediately match and take.': OrderImmediatelyFillable, // {"code":-2010,"msg":"Order would immediately match and take."} 'Account has insufficient balance for requested action.': InsufficientFunds, 'Rest API trading is not enabled.': ExchangeNotAvailable, "You don't have permission.": PermissionDenied, // {"msg":"You don't have permission.","success":false} 'Market is closed.': ExchangeNotAvailable, // {"code":-1013,"msg":"Market is closed."} 'Too many requests. Please try again later.': DDoSProtection, // {"msg":"Too many requests. Please try again later.","success":false} '-1000': ExchangeNotAvailable, // {"code":-1000,"msg":"An unknown error occured while processing the request."} '-1001': ExchangeNotAvailable, // 'Internal error; unable to process your request. Please try again.' '-1002': AuthenticationError, // 'You are not authorized to execute this request.' '-1003': RateLimitExceeded, // {"code":-1003,"msg":"Too much request weight used, current limit is 1200 request weight per 1 MINUTE. Please use the websocket for live updates to avoid polling the API."} '-1013': InvalidOrder, // createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL '-1015': RateLimitExceeded, // 'Too many new orders; current limit is %s orders per %s.' '-1016': ExchangeNotAvailable, // 'This service is no longer available.', '-1020': BadRequest, // 'This operation is not supported.' '-1021': InvalidNonce, // 'your time is ahead of server' '-1022': AuthenticationError, // {"code":-1022,"msg":"Signature for this request is not valid."} '-1100': BadRequest, // createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price' '-1101': BadRequest, // Too many parameters; expected %s and received %s. '-1102': BadRequest, // Param %s or %s must be sent, but both were empty '-1103': BadRequest, // An unknown parameter was sent. '-1104': BadRequest, // Not all sent parameters were read, read 8 parameters but was sent 9 '-1105': BadRequest, // Parameter %s was empty. '-1106': BadRequest, // Parameter %s sent when not required. '-1111': BadRequest, // Precision is over the maximum defined for this asset. '-1112': InvalidOrder, // No orders on book for symbol. '-1114': BadRequest, // TimeInForce parameter sent when not required. '-1115': BadRequest, // Invalid timeInForce. '-1116': BadRequest, // Invalid orderType. '-1117': BadRequest, // Invalid side. '-1118': BadRequest, // New client order ID was empty. '-1119': BadRequest, // Original client order ID was empty. '-1120': BadRequest, // Invalid interval. '-1121': BadSymbol, // Invalid symbol. '-1125': AuthenticationError, // This listenKey does not exist. '-1127': BadRequest, // More than %s hours between startTime and endTime. '-1128': BadRequest, // {"code":-1128,"msg":"Combination of optional parameters invalid."} '-1130': BadRequest, // Data sent for paramter %s is not valid. '-1131': BadRequest, // recvWindow must be less than 60000 '-2008': AuthenticationError, // {"code":-2008,"msg":"Invalid Api-Key ID."} '-2010': ExchangeError, // generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc... '-2011': OrderNotFound, // cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER' '-2013': OrderNotFound, // fetchOrder (1, 'BTC/USDT') -> 'Order does not exist' '-2014': AuthenticationError, // { "code":-2014, "msg": "API-key format invalid." } '-2015': AuthenticationError, // "Invalid API-key, IP, or permissions for action." '-2019': InsufficientFunds, // {"code":-2019,"msg":"Margin is insufficient."} '-3005': InsufficientFunds, // {"code":-3005,"msg":"Transferring out not allowed. Transfer out amount exceeds max amount."} '-3006': InsufficientFunds, // {"code":-3006,"msg":"Your borrow amount has exceed maximum borrow amount."} '-3008': InsufficientFunds, // {"code":-3008,"msg":"Borrow not allowed. Your borrow amount has exceed maximum borrow amount."} '-3010': ExchangeError, // {"code":-3010,"msg":"Repay not allowed. Repay amount exceeds borrow amount."} '-3015': ExchangeError, // {"code":-3015,"msg":"Repay amount exceeds borrow amount."} '-3022': AccountSuspended, // You account's trading is banned. '-4028': BadRequest, // {"code":-4028,"msg":"Leverage 100 is not valid"} '-3020': InsufficientFunds, // {"code":-3020,"msg":"Transfer out amount exceeds max amount."} '-3041': InsufficientFunds, // {"code":-3041,"msg":"Balance is not enough"} '-5013': InsufficientFunds, // Asset transfer failed: insufficient balance" '-11008': InsufficientFunds, // {"code":-11008,"msg":"Exceeding the account's maximum borrowable limit."} '-4051': InsufficientFunds, // {"code":-4051,"msg":"Isolated balance insufficient."} }, 'broad': { 'has no operation privilege': PermissionDenied, 'MAX_POSITION': InvalidOrder, // {"code":-2010,"msg":"Filter failure: MAX_POSITION"} }, }, }); } costToPrecision (symbol, cost) { return this.decimalToPrecision (cost, TRUNCATE, this.markets[symbol]['precision']['quote'], this.precisionMode, this.paddingMode); } currencyToPrecision (code, fee, networkCode = undefined) { // info is available in currencies only if the user has configured his api keys if (this.safeValue (this.currencies[code], 'precision') !== undefined) { return this.decimalToPrecision (fee, TRUNCATE, this.currencies[code]['precision'], this.precisionMode, this.paddingMode); } else { return this.numberToString (fee); } } nonce () { return this.milliseconds () - this.options['timeDifference']; } async fetchStatus (params = {}) { /** * @method * @name bitrue#fetchStatus * @description the latest known information on the availability of the exchange API * @param {object} params extra parameters specific to the bitrue api endpoint * @returns {object} a [status structure]{@link https://docs.ccxt.com/en/latest/manual.html#exchange-status-structure} */ const response = await this.v1PublicGetPing (params); // // empty means working status. // // {} // const keys = Object.keys (response); const keysLength = keys.length; const formattedStatus = keysLength ? 'maintenance' : 'ok'; return { 'status': formattedStatus, 'updated': undefined, 'eta': undefined, 'url': undefined, 'info': response, }; } async fetchTime (params = {}) { /** * @method * @name bitrue#fetchTime * @description fetches the current integer timestamp in milliseconds from the exchange server * @param {object} params extra parameters specific to the bitrue api endpoint * @returns {int} the current integer timestamp in milliseconds from the exchange server */ const response = await this.v1PublicGetTime (params); // // { // "serverTime":1635467280514 // } // return this.safeInteger (response, 'serverTime'); } safeNetwork (networkId) { const uppercaseNetworkId = networkId.toUpperCase (); const networksById = { 'Aeternity': 'Aeternity', 'AION': 'AION', 'Algorand': 'Algorand', 'ASK': 'ASK', 'ATOM': 'ATOM', 'AVAX C-Chain': 'AVAX C-Chain', 'bch': 'bch', 'BCH': 'BCH', 'BEP2': 'BEP2', 'BEP20': 'BEP20', 'Bitcoin': 'Bitcoin', 'BRP20': 'BRP20', 'Cardano': 'ADA', 'CasinoCoin': 'CasinoCoin', 'CasinoCoin XRPL': 'CasinoCoin XRPL', 'Contentos': 'Contentos', 'Dash': 'Dash', 'Decoin': 'Decoin', 'DeFiChain': 'DeFiChain', 'DGB': 'DGB', 'Divi': 'Divi', 'dogecoin': 'DOGE', 'EOS': 'EOS', 'ERC20': 'ERC20', 'ETC': 'ETC', 'Filecoin': 'Filecoin', 'FREETON': 'FREETON', 'HBAR': 'HBAR', 'Hedera Hashgraph': 'Hedera Hashgraph', 'HRC20': 'HRC20', 'ICON': 'ICON', 'ICP': 'ICP', 'Ignis': 'Ignis', 'Internet Computer': 'Internet Computer', 'IOTA': 'IOTA', 'KAVA': 'KAVA', 'KSM': 'KSM', 'LiteCoin': 'LiteCoin', 'Luna': 'Luna', 'MATIC': 'MATIC', 'Mobile Coin': 'Mobile Coin', 'MonaCoin': 'MonaCoin', 'Monero': 'Monero', 'NEM': 'NEM', 'NEP5': 'NEP5', 'OMNI': 'OMNI', 'PAC': 'PAC', 'Polkadot': 'Polkadot', 'Ravencoin': 'Ravencoin', 'Safex': 'Safex', 'SOLANA': 'SOL', 'Songbird': 'Songbird', 'Stellar Lumens': 'Stellar Lumens', 'Symbol': 'Symbol', 'Tezos': 'XTZ', 'theta': 'theta', 'THETA': 'THETA', 'TRC20': 'TRC20', 'VeChain': 'VeChain', 'VECHAIN': 'VECHAIN', 'Wanchain': 'Wanchain', 'XinFin Network': 'XinFin Network', 'XRP': 'XRP', 'XRPL': 'XRPL', 'ZIL': 'ZIL', }; return this.safeString2 (networksById, networkId, uppercaseNetworkId, networkId); } async fetchCurrencies (params = {}) { /** * @method * @name bitrue#fetchCurrencies * @description fetches all available currencies on an exchange * @param {object} params extra parameters specific to the bitrue api endpoint * @returns {object} an associative dictionary of currencies */ const response = await this.v1PublicGetExchangeInfo (params); // // { // "timezone":"CTT", // "serverTime":1635464889117, // "rateLimits":[ // {"rateLimitType":"REQUESTS_WEIGHT","interval":"MINUTES","limit":6000}, // {"rateLimitType":"ORDERS","interval":"SECONDS","limit":150}, // {"rateLimitType":"ORDERS","interval":"DAYS","limit":288000}, // ], // "exchangeFilters":[], // "symbols":[ // { // "symbol":"SHABTC", // "status":"TRADING", // "baseAsset":"sha", // "baseAssetPrecision":0, // "quoteAsset":"btc", // "quotePrecision":10, // "orderTypes":["MARKET","LIMIT"], // "icebergAllowed":false, // "filters":[ // {"filterType":"PRICE_FILTER","minPrice":"0.00000001349","maxPrice":"0.00000017537","priceScale":10}, // {"filterType":"LOT_SIZE","minQty":"1.0","minVal":"0.00020","maxQty":"1000000000","volumeScale":0}, // ], // "defaultPrice":"0.0000006100", // }, // ], // "coins":[ // { // coin: "near", // coinFulName: "NEAR Protocol", // chains: [ "BEP20", ], // chainDetail: [ // { // chain: "BEP20", // enableWithdraw: true, // enableDeposit: true, // withdrawFee: "0.2000", // minWithdraw: "5.0000", // maxWithdraw: "1000000000000000.0000", // }, // ], // }, // ], // } // const result = {}; const coins = this.safeValue (response, 'coins', []); for (let i = 0; i < coins.length; i++) { const currency = coins[i]; const id = this.safeString (currency, 'coin'); const name = this.safeString (currency, 'coinFulName'); const code = this.safeCurrencyCode (id); const enableDeposit = this.safeValue (currency, 'enableDeposit'); const enableWithdraw = this.safeValue (currency, 'enableWithdraw'); const networkIds = this.safeValue (currency, 'chains', []); const networks = {}; for (let j = 0; j < networkIds.length; j++) { const networkId = networkIds[j]; const network = this.safeNetwork (networkId); networks[network] = { 'info': networkId, 'id': networkId, 'network': network, 'active': undefined, 'fee': undefined, 'precision': undefined, 'limits': { 'withdraw': { 'min': undefined, 'max': undefined, }, }, }; } const active = (enableWithdraw && enableDeposit); result[code] = { 'id': id, 'name': name, 'code': code, 'precision': undefined, 'info': currency, 'active': active, 'deposit': enableDeposit, 'withdraw': enableWithdraw, 'networks': networks, 'fee': this.safeNumber (currency, 'withdrawFee'), // 'fees': fees, 'limits': { 'withdraw': { 'min': this.safeNumber (currency, 'minWithdraw'), 'max': this.safeNumber (currency, 'maxWithdraw'), }, }, }; } return result; } async fetchMarkets (params = {}) { /** * @method * @name bitrue#fetchMarkets * @description retrieves data on all markets for bitrue * @param {object} params extra parameters specific to the exchange api endpoint * @returns {[object]} an array of objects representing market data */ const response = await this.v1PublicGetExchangeInfo (params); // // { // "timezone":"CTT", // "serverTime":1635464889117, // "rateLimits":[ // {"rateLimitType":"REQUESTS_WEIGHT","interval":"MINUTES","limit":6000}, // {"rateLimitType":"ORDERS","interval":"SECONDS","limit":150}, // {"rateLimitType":"ORDERS","interval":"DAYS","limit":288000}, // ], // "exchangeFilters":[], // "symbols":[ // { // "symbol":"SHABTC", // "status":"TRADING", // "baseAsset":"sha", // "baseAssetPrecision":0, // "quoteAsset":"btc", // "quotePrecision":10, // "orderTypes":["MARKET","LIMIT"], // "icebergAllowed":false, // "filters":[ // {"filterType":"PRICE_FILTER","minPrice":"0.00000001349","maxPrice":"0.00000017537","priceScale":10}, // {"filterType":"LOT_SIZE","minQty":"1.0","minVal":"0.00020","maxQty":"1000000000","volumeScale":0}, // ], // "defaultPrice":"0.0000006100", // }, // ], // "coins":[ // { // "coin":"sbr", // "coinFulName":"Saber", // "enableWithdraw":true, // "enableDeposit":true, // "chains":["SOLANA"], // "withdrawFee":"2.0", // "minWithdraw":"5.0", // "maxWithdraw":"1000000000000000", // }, // ], // } // if (this.options['adjustForTimeDifference']) { await this.loadTimeDifference (); } const markets = this.safeValue (response, 'symbols', []); const result = []; for (let i = 0; i < markets.length; i++) { const market = markets[i]; const id = this.safeString (market, 'symbol'); const lowercaseId = this.safeStringLower (market, 'symbol'); const baseId = this.safeString (market, 'baseAsset'); const quoteId = this.safeString (market, 'quoteAsset'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const filters = this.safeValue (market, 'filters', []); const filtersByType = this.indexBy (filters, 'filterType'); const status = this.safeString (market, 'status'); const priceFilter = this.safeValue (filtersByType, 'PRICE_FILTER', {}); const amountFilter = this.safeValue (filtersByType, 'LOT_SIZE', {}); const defaultPricePrecision = this.safeString (market, 'pricePrecision'); const defaultAmountPrecision = this.safeString (market, 'quantityPrecision'); const pricePrecision = this.safeString (priceFilter, 'priceScale', defaultPricePrecision); const amountPrecision = this.safeString (amountFilter, 'volumeScale', defaultAmountPrecision); const entry = { 'id': id, 'lowercaseId': lowercaseId, 'symbol': base + '/' + quote, 'base': base, 'quote': quote, 'settle': undefined, 'baseId': baseId, 'quoteId': quoteId, 'settleId': undefined, 'type': 'spot', 'spot': true, 'margin': false, 'swap': false, 'future': false, 'option': false, 'active': (status === 'TRADING'), 'contract': false, 'linear': undefined, 'inverse': undefined, 'contractSize': undefined, 'expiry': undefined, 'expiryDatetime': undefined, 'strike': undefined, 'optionType': undefined, 'precision': { 'amount': this.parseNumber (this.parsePrecision (amountPrecision)), 'price': this.parseNumber (this.parsePrecision (pricePrecision)), 'base': this.parseNumber (this.parsePrecision (this.safeString (market, 'baseAssetPrecision'))), 'quote': this.parseNumber (this.parsePrecision (this.safeString (market, 'quotePrecision'))), }, 'limits': { 'leverage': { 'min': undefined, 'max': undefined, }, 'amount': { 'min': this.safeNumber (amountFilter, 'minQty'), 'max': this.safeNumber (amountFilter, 'maxQty'), }, 'price': { 'min': this.safeNumber (priceFilter, 'minPrice'), 'max': this.safeNumber (priceFilter, 'maxPrice'), }, 'cost': { 'min': this.safeNumber (amountFilter, 'minVal'), 'max': undefined, }, }, 'info': market, }; result.push (entry); } return result; } parseBalance (response) { const result = { 'info': response, }; const timestamp = this.safeInteger (response, 'updateTime'); const balances = this.safeValue (response, 'balances', []); for (let i = 0; i < balances.length; i++) { const balance = balances[i]; const currencyId = this.safeString (balance, 'asset'); const code = this.safeCurrencyCode (currencyId); const account = this.account (); account['free'] = this.safeString (balance, 'free'); account['used'] = this.safeString (balance, 'locked'); result[code] = account; } result['timestamp'] = timestamp; result['datetime'] = this.iso8601 (timestamp); return this.safeBalance (result); } async fetchBalance (params = {}) { /** * @method * @name bitrue#fetchBalance * @description query for balance and get the amount of funds available for trading or funds locked in orders * @param {object} params extra parameters specific to the bitrue api endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure} */ await this.loadMarkets (); const response = await this.v1PrivateGetAccount (params); // // { // "makerCommission":0, // "takerCommission":0, // "buyerCommission":0, // "sellerCommission":0, // "updateTime":null, // "balances":[ // {"asset":"sbr","free":"0","locked":"0"}, // {"asset":"ksm","free":"0","locked":"0"}, // {"asset":"neo3s","free":"0","locked":"0"}, // ], // "canTrade":false, // "canWithdraw":false, // "canDeposit":false // } // return this.parseBalance (response); } async fetchOrderBook (symbol, limit = undefined, params = {}) { /** * @method * @name bitrue#fetchOrderBook * @description fetches 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|undefined} limit the maximum amount of order book entries to return * @param {object} params extra parameters specific to the bitrue api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure} indexed by market symbols */ await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; if (limit !== undefined) { request['limit'] = limit; // default 100, max 1000, see https://github.com/Bitrue-exchange/bitrue-official-api-docs#order-book } const response = await this.v1PublicGetDepth (this.extend (request, params)); // // { // "lastUpdateId":1635474910177, // "bids":[ // ["61436.84","0.05",[]], // ["61435.77","0.0124",[]], // ["61434.88","0.012",[]], // ], // "asks":[ // ["61452.46","0.0001",[]], // ["61452.47","0.0597",[]], // ["61452.76","0.0713",[]], // ] // } // const orderbook = this.parseOrderBook (response, symbol); orderbook['nonce'] = this.safeInteger (response, 'lastUpdateId'); return orderbook; } parseTicker (ticker, market = undefined) { // // fetchTicker // // { // "id":397945892, // "last":"1.143411", // "lowestAsk":"1.144223", // "highestBid":"1.141696", // "percentChange":"-0.001432", // "baseVolume":"338287", // "quoteVolume":"415013.244366", // "isFrozen":"0", // "high24hr":"1.370087", // "low24hr":"1.370087", // } // const symbol = this.safeSymbol (undefined, market); const last = this.safeString (ticker, 'last'); return this.safeTicker ({ 'symbol': symbol, 'timestamp': undefined, 'datetime': undefined, 'high': this.safeString (ticker, 'high24hr'), 'low': this.safeString (ticker, 'low24hr'), 'bid': this.safeString (ticker, 'highestBid'), 'bidVolume': undefined, 'ask': this.safeString (ticker, 'lowestAsk'), 'askVolume': undefined, 'vwap': undefined, 'open': undefined, 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': this.safeString (ticker, 'percentChange'), 'average': undefined, 'baseVolume': this.safeString (ticker, 'baseVolume'), 'quoteVolume': this.safeString (ticker, 'quoteVolume'), 'info': ticker, }, market); } async fetchTicker (symbol, params = {}) { /** * @method * @name bitrue#fetchTicker * @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours 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 bitrue api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure} */ await this.loadMarkets (); const market = this.market (symbol); const uppercaseBaseId = this.safeStringUpper (market, 'baseId'); const uppercaseQuoteId = this.safeStringUpper (market, 'quoteId'); const request = { 'currency': uppercaseQuoteId, 'command': 'returnTicker', }; const response = await this.klinePublicGetPublicCurrencyJson (this.extend (request, params)); // // { // "code":"200", // "msg":"success", // "data":{ // "DODO3S_USDT":{ // "id":397945892, // "last":"1.143411", // "lowestAsk":"1.144223", // "highestBid":"1.141696", // "percentChange":"-0.001432", // "baseVolume":"338287", // "quoteVolume":"415013.244366", // "isFrozen":"0", // "high24hr":"1.370087", // "low24hr":"1.370087" // } // } // } // const data = this.safeValue (response, 'data', {}); const id = uppercaseBaseId + '_' + uppercaseQuoteId; const ticker = this.safeValue (data, id); if (ticker === undefined) { throw new ExchangeError (this.id + ' fetchTicker() could not find the ticker for ' + market['symbol']); } return this.parseTicker (ticker, market); } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name bitrue#fetchOHLCV * @description fetches 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|undefined} since timestamp in ms of the earliest candle to fetch * @param {int|undefined} limit the maximum amount of candles to fetch * @param {object} params extra parameters specific to the bitrue api endpoint * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume */ await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], 'scale': this.timeframes[timeframe], }; if (limit !== undefined) { request['limit'] = limit; } const response = await this.v1PublicGetMarketKline (this.extend (request, params)); // // { // "symbol":"BTCUSDT", // "scale":"KLINE_1MIN", // "data":[ // { // "i":"1660825020", // "a":"93458.778", // "v":"3.9774", // "c":"23494.99", // "h":"23509.63", // "l":"23491.93", // "o":"23508.34" // } // ] // } // const data = this.safeValue (response, 'data', []); return this.parseOHLCVs (data, market, timeframe, since, limit); } parseOHLCV (ohlcv, market = undefined) { // // { // "i":"1660825020", // "a":"93458.778", // "v":"3.9774", // "c":"23494.99", // "h":"23509.63", // "l":"23491.93", // "o":"23508.34" // } // return [ this.safeTimestamp (ohlcv, 'i'), this.safeNumber (ohlcv, 'o'), this.safeNumber (ohlcv, 'h'), this.safeNumber (ohlcv, 'l'), this.safeNumber (ohlcv, 'c'), this.safeNumber (ohlcv, 'v'), ]; } async fetchBidsAsks (symbols = undefined, params = {}) { /** * @method * @name bitrue#fetchBidsAsks * @description fetches the bid and ask price and volume for multiple markets * @param {[string]|undefined} symbols unified symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned * @param {object} params extra parameters specific to the bitrue api endpoint * @returns {object} an array of [ticker structures]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure} */ await this.loadMarkets (); const defaultType = this.safeString2 (this.options, 'fetchBidsAsks', 'defaultType', 'spot'); const type = this.safeString (params, 'type', defaultType); const query = this.omit (params, 'type'); let method = undefined; if (type === 'future') { method = 'fapiPublicGetTickerBookTicker'; } else if (type === 'delivery') { method = 'dapiPublicGetTickerBookTicker'; } else { method = 'publicGetTickerBookTicker'; } const response = await this[method] (query); return this.parseTickers (response, symbols); } async fetchTickers (symbols = undefined, params = {}) { /** * @method * @name bitrue#fetchTickers * @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market * @param {[string]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned * @param {object} params extra parameters specific to the bitrue api endpoint * @returns {object} an array of [ticker structures]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure} */ await this.loadMarkets (); const request = { 'command': 'returnTicker', }; const response = await this.klinePublicGetPublicJson (this.extend (request, params)); // // { // "code":"200", // "msg":"success", // "data":{ // "DODO3S_USDT":{ // "id":397945892, // "last":"1.143411", // "lowestAsk":"1.144223", // "highestBid":"1.141696", // "percentChange":"-0.001432", // "baseVolume":"338287", // "quoteVolume":"415013.244366", // "isFrozen":"0", // "high24hr":"1.370087", // "low24hr":"1.370087" // } // } // } // const data = this.safeValue (response, 'data', {}); // the exchange returns market ids with an underscore from the tickers endpoint // the market ids do not have an underscore, so it has to be removed // https://github.com/ccxt/ccxt/issues/13856 const tickers = {}; const marketIds = Object.keys (data); for (let i = 0; i < marketIds.length; i++) { const marketId = marketIds[i].replace ('_', ''); tickers[marketId] = data[marketIds[i]]; } return this.parseTickers (tickers, symbols); } parseTrade (trade, market = undefined) { // // aggregate trades // // { // "a": 26129, // Aggregate tradeId // "p": "0.01633102", // Price // "q": "4.70443515", // Quantity // "f": 27781, // First tradeId // "l": 27781, // Last tradeId // "T": 1498793709153, // Timestamp // "m": true, // Was the buyer the maker? // "M": true // Was the trade the best price match? // } // // recent public trades and old public trades // // { // "id": 28457, // "price": "4.00000100", // "qty": "12.00000000", // "time": 1499865549590, // "isBuyerMaker": true, // "isBestMatch": true // } // // private trades // // { // "symbol":"USDCUSDT", // "id":20725156, // "orderId":2880918576, // "origClientOrderId":null, // "price":"0.9996000000000000", // "qty":"100.0000000000000000", // "commission":null, // "commissionAssert":null, // "time":1635558511000, // "isBuyer":false, // "isMaker":false, // "isBestMatch":true // } // const timestamp = this.safeInteger2 (trade, 'T', 'time'); const priceString = this.safeString2 (trade, 'p', 'price'); const amountString = this.safeString2 (trade, 'q', 'q