@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
JavaScript
// ----------------------------------------------------------------------------
// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
// EDIT THE CORRESPONDENT .ts FILE INSTEAD
// ---------------------------------------------------------------------------
import 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,