UNPKG

@proton/ccxt

Version:

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

1,104 lines (1,101 loc) 91.7 kB
// ---------------------------------------------------------------------------- // 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 Exchange from './abstract/bitrue.js'; import { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, InvalidNonce, AuthenticationError, RateLimitExceeded, PermissionDenied, BadRequest, BadSymbol, AccountSuspended, OrderImmediatelyFillable, OnMaintenance } from './base/errors.js'; import { Precise } from './base/Precise.js'; import { TRUNCATE, TICK_SIZE } from './base/functions/number.js'; import { sha256 } from './static_dependencies/noble-hashes/sha256.js'; // --------------------------------------------------------------------------- export default class bitrue extends Exchange { describe() { return this.deepExtend(super.describe(), { 'id': 'bitrue', 'name': 'Bitrue', 'countries': ['SG'], 'rateLimit': 1000, 'certified': false, 'version': 'v1', 'pro': true, // new metainfo interface 'has': { 'CORS': undefined, 'spot': true, 'margin': false, 'swap': undefined, '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, 'fetchDepositsWithdrawals': false, 'fetchDepositWithdrawFee': 'emulated', 'fetchDepositWithdrawFees': 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', 'hasAlreadyAuthenticatedSuccessfully': false, 'recvWindow': 5 * 1000, 'timeDifference': 0, 'adjustForTimeDifference': false, 'parseOrderToPrecision': false, 'newOrderRespType': { 'market': 'FULL', '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', }, 'networksById': { 'TRX': 'TRC20', 'ETH': 'ERC20', }, }, '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, 'System abnormality': ExchangeError, 'You are not authorized to execute this request.': PermissionDenied, 'API key does not exist': AuthenticationError, 'Order would trigger immediately.': OrderImmediatelyFillable, 'Stop price would trigger immediately.': OrderImmediatelyFillable, 'Order would immediately match and take.': OrderImmediatelyFillable, 'Account has insufficient balance for requested action.': InsufficientFunds, 'Rest API trading is not enabled.': ExchangeNotAvailable, "You don't have permission.": PermissionDenied, 'Market is closed.': ExchangeNotAvailable, 'Too many requests. Please try again later.': DDoSProtection, '-1000': ExchangeNotAvailable, '-1001': ExchangeNotAvailable, '-1002': AuthenticationError, '-1003': RateLimitExceeded, '-1013': InvalidOrder, '-1015': RateLimitExceeded, '-1016': ExchangeNotAvailable, '-1020': BadRequest, '-1021': InvalidNonce, '-1022': AuthenticationError, '-1100': BadRequest, '-1101': BadRequest, '-1102': BadRequest, '-1103': BadRequest, '-1104': BadRequest, '-1105': BadRequest, '-1106': BadRequest, '-1111': BadRequest, '-1112': InvalidOrder, '-1114': BadRequest, '-1115': BadRequest, '-1116': BadRequest, '-1117': BadRequest, '-1118': BadRequest, '-1119': BadRequest, '-1120': BadRequest, '-1121': BadSymbol, '-1125': AuthenticationError, '-1127': BadRequest, '-1128': BadRequest, '-1130': BadRequest, '-1131': BadRequest, '-2008': AuthenticationError, '-2010': ExchangeError, '-2011': OrderNotFound, '-2013': OrderNotFound, '-2014': AuthenticationError, '-2015': AuthenticationError, '-2019': InsufficientFunds, '-3005': InsufficientFunds, '-3006': InsufficientFunds, '-3008': InsufficientFunds, '-3010': ExchangeError, '-3015': ExchangeError, '-3022': AccountSuspended, '-4028': BadRequest, '-3020': InsufficientFunds, '-3041': InsufficientFunds, '-5013': InsufficientFunds, '-11008': InsufficientFunds, '-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/#/?id=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); let deposit = undefined; let withdraw = undefined; let minWithdrawString = undefined; let maxWithdrawString = undefined; let minWithdrawFeeString = undefined; const networkDetails = this.safeValue(currency, 'chainDetail', []); const networks = {}; for (let j = 0; j < networkDetails.length; j++) { const entry = networkDetails[j]; const networkId = this.safeString(entry, 'chain'); const network = this.networkIdToCode(networkId, code); const enableDeposit = this.safeValue(entry, 'enableDeposit'); deposit = (enableDeposit) ? enableDeposit : deposit; const enableWithdraw = this.safeValue(entry, 'enableWithdraw'); withdraw = (enableWithdraw) ? enableWithdraw : withdraw; const networkWithdrawFeeString = this.safeString(entry, 'withdrawFee'); if (networkWithdrawFeeString !== undefined) { minWithdrawFeeString = (minWithdrawFeeString === undefined) ? networkWithdrawFeeString : Precise.stringMin(networkWithdrawFeeString, minWithdrawFeeString); } const networkMinWithdrawString = this.safeString(entry, 'minWithdraw'); if (networkMinWithdrawString !== undefined) { minWithdrawString = (minWithdrawString === undefined) ? networkMinWithdrawString : Precise.stringMin(networkMinWithdrawString, minWithdrawString); } const networkMaxWithdrawString = this.safeString(entry, 'maxWithdraw'); if (networkMaxWithdrawString !== undefined) { maxWithdrawString = (maxWithdrawString === undefined) ? networkMaxWithdrawString : Precise.stringMax(networkMaxWithdrawString, maxWithdrawString); } networks[network] = { 'info': entry, 'id': networkId, 'network': network, 'deposit': enableDeposit, 'withdraw': enableWithdraw, 'active': enableDeposit && enableWithdraw, 'fee': this.parseNumber(networkWithdrawFeeString), 'precision': undefined, 'limits': { 'withdraw': { 'min': this.parseNumber(networkMinWithdrawString), 'max': this.parseNumber(networkMaxWithdrawString), }, }, }; } result[code] = { 'id': id, 'name': name, 'code': code, 'precision': undefined, 'info': currency, 'active': deposit && withdraw, 'deposit': deposit, 'withdraw': withdraw, 'networks': networks, 'fee': this.parseNumber(minWithdrawFeeString), // 'fees': fees, 'limits': { 'withdraw': { 'min': this.parseNumber(minWithdrawString), 'max': this.parseNumber(maxWithdrawString), }, }, }; } 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/#/?id=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/#/?id=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.safeString(this.timeframes, timeframe, 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} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=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} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=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 // - "T" is timestamp of *api-call* not trades. Use more expensive v1PublicGetHistoricalTrades if actual timestamp of trades matter // - Trades are aggregated by timestamp, price, and side. But "m" is always True. Use method above if side of trades matter // // { // "a": 26129, // Aggregate tradeId // "p": "0.01633102", // Price // "q": "4.70443515", // Quantity // "f": 27781, // First tradeId // "l": 27781, // Last tradeId // "T": 1498793709153, // Timestamp of *Api-call* not trade! // "m": true, // Was the buyer the maker? // Always True -> ignore it and leave side undefined // "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, // Actual timestamp of trade // "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', 'qty'); const marketId = this.safeString(trade, 'symbol'); const symbol = this.safeSymbol(marketId, market); const orderId = this.safeString(trade, 'orderId'); let id = this.safeString2(trade, 't', 'a'); id = this.safeString2(trade, 'id', 'tradeId', id); let side = undefined; const buyerMaker = this.safeValue(trade, 'isBuyerMaker'); // ignore "m" until Bitrue fixes api const isBuyer = this.safeValue(trade, 'isBuyer'); if (buyerMaker !== undefined) { side = buyerMaker ? 'sell' : 'buy'; } if (isBuyer !== undefined) { side = isBuyer ? 'buy' : 'sell'; // this is a true side } let fee = undefined; if ('commission' in trade) { fee = { 'cost': this.safeString(trade, 'commission'), 'currency': this.safeCurrencyCode(this.safeString(trade, 'commissionAssert')), }; } let takerOrMaker = undefined; const isMaker = this.safeValue2(trade, 'isMaker', 'maker'); if (isMaker !== undefined) { takerOrMaker = isMaker ? 'maker' : 'taker'; } return this.safeTrade({ 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'symbol': symbol, 'id': id,